diff --git a/.clang-tidy b/.clang-tidy index b83436861bd..aa361594f56 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -18,6 +18,7 @@ Checks: > -objc-*, -openmp-*, -zircon-*, + cppcoreguidelines-avoid-const-or-ref-data-members, cppcoreguidelines-pro-type-static-cast-downcast, cppcoreguidelines-rvalue-reference-param-not-moved, google-explicit-constructor, diff --git a/.github/workflows/CI-cygwin.yml b/.github/workflows/CI-cygwin.yml index bf67967c5f8..6b0ad2cc5bd 100644 --- a/.github/workflows/CI-cygwin.yml +++ b/.github/workflows/CI-cygwin.yml @@ -37,7 +37,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false diff --git a/.github/workflows/CI-mingw.yml b/.github/workflows/CI-mingw.yml index 1b0cf3e5672..eb88a4a57fc 100644 --- a/.github/workflows/CI-mingw.yml +++ b/.github/workflows/CI-mingw.yml @@ -33,7 +33,7 @@ jobs: timeout-minutes: 19 # max + 3*std of the last 7K runs steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false @@ -49,7 +49,7 @@ jobs: mingw-w64-x86_64-gcc - name: ccache - uses: hendrikmuhs/ccache-action@v1.2 + uses: hendrikmuhs/ccache-action@v1.2.24 with: key: ${{ github.workflow }}-${{ github.job }}-${{ matrix.os }} diff --git a/.github/workflows/CI-unixish-docker.yml b/.github/workflows/CI-unixish-docker.yml index a38feb452f0..02afca633b7 100644 --- a/.github/workflows/CI-unixish-docker.yml +++ b/.github/workflows/CI-unixish-docker.yml @@ -43,7 +43,7 @@ jobs: image: ${{ matrix.image }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false @@ -66,13 +66,13 @@ jobs: # needs to be called after the package installation since # - it doesn't call "apt-get update" - name: ccache - uses: hendrikmuhs/ccache-action@v1.2 + uses: hendrikmuhs/ccache-action@v1.2.24 with: key: ${{ github.workflow }}-${{ matrix.image }} - name: Run CMake run: | - cmake -S . -B cmake.output -Werror=dev -DHAVE_RULES=On -DBUILD_TESTING=On -DBUILD_GUI=${{ matrix.with_gui }} -DWITH_QCHART=On -DBUILD_TRIAGE=${{ matrix.with_gui }} -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + cmake -S . -B cmake.output -Werror=dev --warn-uninitialized -DHAVE_RULES=On -DBUILD_TESTING=On -DBUILD_GUI=${{ matrix.with_gui }} -DWITH_QCHART=On -DBUILD_TRIAGE=${{ matrix.with_gui }} -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - name: CMake build if: matrix.full_build @@ -96,7 +96,7 @@ jobs: image: ${{ matrix.image }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false @@ -114,7 +114,7 @@ jobs: # needs to be called after the package installation since # - it doesn't call "apt-get update" - name: ccache - uses: hendrikmuhs/ccache-action@v1.2 + uses: hendrikmuhs/ccache-action@v1.2.24 with: key: ${{ github.workflow }}-${{ matrix.image }} diff --git a/.github/workflows/CI-unixish.yml b/.github/workflows/CI-unixish.yml index cf555a8ecc6..b172327d18b 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -30,12 +30,12 @@ jobs: CCACHE_SLOPPINESS: pch_defines,time_macros steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false - name: ccache - uses: hendrikmuhs/ccache-action@v1.2 + uses: hendrikmuhs/ccache-action@v1.2.24 with: key: ${{ github.workflow }}-${{ github.job }}-${{ matrix.os }} @@ -58,13 +58,13 @@ jobs: - name: CMake build on ubuntu (with GUI / system tinyxml2) if: contains(matrix.os, 'ubuntu') run: | - cmake -S . -B cmake.output.tinyxml2 -Werror=dev -DHAVE_RULES=On -DBUILD_TESTING=On -DBUILD_GUI=On -DWITH_QCHART=On -DBUILD_TRIAGE=On -DUSE_BUNDLED_TINYXML2=Off -DCMAKE_DISABLE_PRECOMPILE_HEADERS=On -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + cmake -S . -B cmake.output.tinyxml2 -Werror=dev --warn-uninitialized -DHAVE_RULES=On -DBUILD_TESTING=On -DBUILD_GUI=On -DWITH_QCHART=On -DBUILD_TRIAGE=On -DUSE_BUNDLED_TINYXML2=Off -DCMAKE_DISABLE_PRECOMPILE_HEADERS=On -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache cmake --build cmake.output.tinyxml2 -- -j$(nproc) - name: CMake build on macos (with GUI / system tinyxml2) if: contains(matrix.os, 'macos') run: | - cmake -S . -B cmake.output.tinyxml2 -Werror=dev -DHAVE_RULES=On -DBUILD_TESTING=On -DBUILD_GUI=On -DWITH_QCHART=On -DBUILD_TRIAGE=On -DUSE_BUNDLED_TINYXML2=Off -DCMAKE_DISABLE_PRECOMPILE_HEADERS=On -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DQt6_DIR=$(brew --prefix qt@6)/lib/cmake/Qt6 + cmake -S . -B cmake.output.tinyxml2 -Werror=dev --warn-uninitialized -DHAVE_RULES=On -DBUILD_TESTING=On -DBUILD_GUI=On -DWITH_QCHART=On -DBUILD_TRIAGE=On -DUSE_BUNDLED_TINYXML2=Off -DCMAKE_DISABLE_PRECOMPILE_HEADERS=On -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DQt6_DIR=$(brew --prefix qt@6)/lib/cmake/Qt6 cmake --build cmake.output.tinyxml2 -- -j$(nproc) - name: Run CMake test (system tinyxml2) @@ -85,19 +85,19 @@ jobs: CCACHE_SLOPPINESS: pch_defines,time_macros steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false - name: ccache - uses: hendrikmuhs/ccache-action@v1.2 + uses: hendrikmuhs/ccache-action@v1.2.24 with: key: ${{ github.workflow }}-${{ github.job }}-${{ matrix.os }} # TODO: move latest compiler to separate step # TODO: bail out on warnings with latest GCC - name: Set up GCC - uses: egor-tensin/setup-gcc@v1 + uses: egor-tensin/setup-gcc@v2 if: false # matrix.os == 'ubuntu-22.04' with: version: 13 @@ -127,12 +127,12 @@ jobs: - name: Run CMake on ubuntu (with GUI) if: contains(matrix.os, 'ubuntu') run: | - cmake -S . -B cmake.output -Werror=dev -DHAVE_RULES=On -DBUILD_TESTING=On -DBUILD_GUI=On -DWITH_QCHART=On -DBUILD_TRIAGE=On -DCMAKE_DISABLE_PRECOMPILE_HEADERS=On -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_INSTALL_PREFIX=cppcheck-cmake-install + cmake -S . -B cmake.output -Werror=dev --warn-uninitialized -DHAVE_RULES=On -DBUILD_TESTING=On -DBUILD_GUI=On -DWITH_QCHART=On -DBUILD_TRIAGE=On -DCMAKE_DISABLE_PRECOMPILE_HEADERS=On -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_INSTALL_PREFIX=cppcheck-cmake-install - name: Run CMake on macos (with GUI) if: contains(matrix.os, 'macos') run: | - cmake -S . -B cmake.output -Werror=dev -DHAVE_RULES=On -DBUILD_TESTING=On -DBUILD_GUI=On -DWITH_QCHART=On -DBUILD_TRIAGE=On -DCMAKE_DISABLE_PRECOMPILE_HEADERS=On -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_INSTALL_PREFIX=cppcheck-cmake-install -DQt6_DIR=$(brew --prefix qt@6)/lib/cmake/Qt6 + cmake -S . -B cmake.output -Werror=dev --warn-uninitialized -DHAVE_RULES=On -DBUILD_TESTING=On -DBUILD_GUI=On -DWITH_QCHART=On -DBUILD_TRIAGE=On -DCMAKE_DISABLE_PRECOMPILE_HEADERS=On -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_INSTALL_PREFIX=cppcheck-cmake-install -DQt6_DIR=$(brew --prefix qt@6)/lib/cmake/Qt6 - name: Run CMake build run: | @@ -150,17 +150,18 @@ jobs: - name: Run CMake install run: | cmake --build cmake.output --target install + # TODO: validate the installed files - name: Run CMake on ubuntu (no CLI) if: matrix.os == 'ubuntu-22.04' run: | - cmake -S . -B cmake.output_nocli -Werror=dev -DBUILD_TESTING=Off -DBUILD_CLI=Off + cmake -S . -B cmake.output_nocli -Werror=dev --warn-uninitialized -DBUILD_TESTING=Off -DBUILD_CLI=Off - name: Run CMake on ubuntu (no CLI / with tests) if: matrix.os == 'ubuntu-22.04' run: | # the test and CLI code are too intertwined so for now we need to reject that - if cmake -S . -B cmake.output_nocli_tests -Werror=dev -DBUILD_TESTING=On -DBUILD_CLI=Off; then + if cmake -S . -B cmake.output_nocli_tests -Werror=dev --warn-uninitialized -DBUILD_TESTING=On -DBUILD_CLI=Off; then exit 1 else exit 0 @@ -169,18 +170,18 @@ jobs: - name: Run CMake on ubuntu (no CLI / with GUI) if: matrix.os == 'ubuntu-22.04' run: | - cmake -S . -B cmake.output_nocli_gui -Werror=dev -DBUILD_TESTING=Off -DBUILD_CLI=Off -DBUILD_GUI=On + cmake -S . -B cmake.output_nocli_gui -Werror=dev --warn-uninitialized -DBUILD_TESTING=Off -DBUILD_CLI=Off -DBUILD_GUI=On - name: Run CMake on ubuntu (no GUI) if: matrix.os == 'ubuntu-22.04' run: | - cmake -S . -B cmake.output_nogui -Werror=dev -DBUILD_TESTING=Off -DBUILD_GUI=Off + cmake -S . -B cmake.output_nogui -Werror=dev --warn-uninitialized -DBUILD_TESTING=Off -DBUILD_GUI=Off - name: Run CMake on ubuntu (no GUI / with triage) if: matrix.os == 'ubuntu-22.04' run: | # cannot build triage without GUI - if cmake -S . -B cmake.output_nogui_triage -Werror=dev -DBUILD_TESTING=Off -DBUILD_GUI=Off -DBUILD_TRIAGE=On; then + if cmake -S . -B cmake.output_nogui_triage -Werror=dev --warn-uninitialized -DBUILD_TESTING=Off -DBUILD_GUI=Off -DBUILD_TRIAGE=On; then exit 1 else exit 0 @@ -189,7 +190,7 @@ jobs: - name: Run CMake on ubuntu (no CLI / no GUI) if: matrix.os == 'ubuntu-22.04' run: | - cmake -S . -B cmake.output_nocli_nogui -Werror=dev -DBUILD_TESTING=Off -DBUILD_GUI=Off + cmake -S . -B cmake.output_nocli_nogui -Werror=dev --warn-uninitialized -DBUILD_TESTING=Off -DBUILD_GUI=Off build_cmake_cxxstd: @@ -215,12 +216,12 @@ jobs: CCACHE_SLOPPINESS: pch_defines,time_macros steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false - name: ccache - uses: hendrikmuhs/ccache-action@v1.2 + uses: hendrikmuhs/ccache-action@v1.2.24 with: key: ${{ github.workflow }}-${{ github.job }}-${{ matrix.os }}-${{ matrix.cxxstd }} @@ -243,12 +244,12 @@ jobs: - name: Run CMake on ubuntu (with GUI) if: contains(matrix.os, 'ubuntu') run: | - cmake -S . -B cmake.output -Werror=dev -DCMAKE_CXX_STANDARD=${{ matrix.cxxstd }} -DHAVE_RULES=On -DBUILD_TESTING=On -DBUILD_GUI=On -DWITH_QCHART=On -DBUILD_TRIAGE=On -DCMAKE_DISABLE_PRECOMPILE_HEADERS=On -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + cmake -S . -B cmake.output -Werror=dev --warn-uninitialized -DCMAKE_CXX_STANDARD=${{ matrix.cxxstd }} -DHAVE_RULES=On -DBUILD_TESTING=On -DBUILD_GUI=On -DWITH_QCHART=On -DBUILD_TRIAGE=On -DCMAKE_DISABLE_PRECOMPILE_HEADERS=On -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - name: Run CMake on macos (with GUI) if: contains(matrix.os, 'macos') run: | - cmake -S . -B cmake.output -Werror=dev -DCMAKE_CXX_STANDARD=${{ matrix.cxxstd }} -DHAVE_RULES=On -DBUILD_TESTING=On -DBUILD_GUI=On -DWITH_QCHART=On -DBUILD_TRIAGE=On -DCMAKE_DISABLE_PRECOMPILE_HEADERS=On -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DQt6_DIR=$(brew --prefix qt@6)/lib/cmake/Qt6 + cmake -S . -B cmake.output -Werror=dev --warn-uninitialized -DCMAKE_CXX_STANDARD=${{ matrix.cxxstd }} -DHAVE_RULES=On -DBUILD_TESTING=On -DBUILD_GUI=On -DWITH_QCHART=On -DBUILD_TRIAGE=On -DCMAKE_DISABLE_PRECOMPILE_HEADERS=On -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DQt6_DIR=$(brew --prefix qt@6)/lib/cmake/Qt6 - name: Run CMake build run: | @@ -264,12 +265,12 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false - name: ccache - uses: hendrikmuhs/ccache-action@v1.2 + uses: hendrikmuhs/ccache-action@v1.2.24 with: key: ${{ github.workflow }}-${{ github.job }}-${{ matrix.os }} @@ -298,12 +299,12 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false - name: ccache - uses: hendrikmuhs/ccache-action@v1.2 + uses: hendrikmuhs/ccache-action@v1.2.24 with: key: ${{ github.workflow }}-${{ github.job }}-${{ matrix.os }} @@ -332,7 +333,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false @@ -360,12 +361,12 @@ jobs: CCACHE_SLOPPINESS: pch_defines,time_macros steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false - name: ccache - uses: hendrikmuhs/ccache-action@v1.2 + uses: hendrikmuhs/ccache-action@v1.2.24 with: key: ${{ github.workflow }}-${{ github.job }}-${{ matrix.os }} @@ -373,7 +374,7 @@ jobs: run: | # make sure we fail when Boost is requested and not available. # will fail because no package configuration is available. - if cmake -S . -B cmake.output.boost-force-noavail -Werror=dev -DBUILD_TESTING=Off -DUSE_BOOST=On; then + if cmake -S . -B cmake.output.boost-force-noavail -Werror=dev --warn-uninitialized -DBUILD_TESTING=Off -DUSE_BOOST=On; then exit 1 else exit 0 @@ -386,12 +387,12 @@ jobs: - name: Run CMake on macOS (force Boost) run: | - cmake -S . -B cmake.output.boost-force -Werror=dev -DBUILD_TESTING=Off -DUSE_BOOST=On + cmake -S . -B cmake.output.boost-force -Werror=dev --warn-uninitialized -DBUILD_TESTING=Off -DUSE_BOOST=On - name: Run CMake on macOS (no Boost) run: | # make sure Boost is not used when disabled even though it is available - cmake -S . -B cmake.output.boost-no -Werror=dev -DBUILD_TESTING=Off -DUSE_BOOST=Off + cmake -S . -B cmake.output.boost-no -Werror=dev --warn-uninitialized -DBUILD_TESTING=Off -DUSE_BOOST=Off if grep -q '\-DHAVE_BOOST' ./cmake.output.boost-no/compile_commands.json; then exit 1 else @@ -400,7 +401,7 @@ jobs: - name: Run CMake on macOS (with Boost) run: | - cmake -S . -B cmake.output.boost -Werror=dev -DBUILD_TESTING=On -DCMAKE_DISABLE_PRECOMPILE_HEADERS=On -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + cmake -S . -B cmake.output.boost -Werror=dev --warn-uninitialized -DBUILD_TESTING=On -DCMAKE_DISABLE_PRECOMPILE_HEADERS=On -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache grep -q '\-DHAVE_BOOST' ./cmake.output.boost/compile_commands.json - name: Build with CMake on macOS (with Boost) @@ -416,7 +417,7 @@ jobs: CMAKE_VERSION_FULL: 3.22.6 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false @@ -436,11 +437,13 @@ jobs: - name: Run CMake (without GUI) run: | export PATH=cmake-${{ env.CMAKE_VERSION_FULL }}-linux-x86_64/bin:$PATH + # FIXME: cannot use --warn-uninitialized here as it completely breaks the build cmake -S . -B cmake.output -Werror=dev -DHAVE_RULES=On -DBUILD_TESTING=On - name: Run CMake (with GUI) run: | export PATH=cmake-${{ env.CMAKE_VERSION_FULL }}-linux-x86_64/bin:$PATH + # FIXME: cannot use --warn-uninitialized here as it completely breaks the build cmake -S . -B cmake.output.gui -Werror=dev -DHAVE_RULES=On -DBUILD_TESTING=On -DBUILD_GUI=On -DWITH_QCHART=On -DBUILD_TRIAGE=On build: @@ -458,12 +461,12 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false - name: ccache - uses: hendrikmuhs/ccache-action@v1.2 + uses: hendrikmuhs/ccache-action@v1.2.24 with: key: ${{ github.workflow }}-${{ github.job }}-${{ matrix.os }} @@ -596,7 +599,7 @@ jobs: - name: Test Signalhandler run: | - cmake -S . -B build.cmake.signal -Werror=dev -DBUILD_TESTING=On -DCMAKE_COMPILE_WARNING_AS_ERROR=On + cmake -S . -B build.cmake.signal -Werror=dev --warn-uninitialized -DBUILD_TESTING=On -DCMAKE_COMPILE_WARNING_AS_ERROR=On cmake --build build.cmake.signal --target test-signalhandler -- -j$(nproc) # TODO: how to run this without copying the file? cp build.cmake.signal/bin/test-s* . @@ -607,7 +610,7 @@ jobs: - name: Test Stacktrace if: contains(matrix.os, 'ubuntu') run: | - cmake -S . -B build.cmake.stack -Werror=dev -DBUILD_TESTING=On -DCMAKE_COMPILE_WARNING_AS_ERROR=On + cmake -S . -B build.cmake.stack -Werror=dev --warn-uninitialized -DBUILD_TESTING=On -DCMAKE_COMPILE_WARNING_AS_ERROR=On cmake --build build.cmake.stack --target test-stacktrace -- -j$(nproc) # TODO: how to run this without copying the file? cp build.cmake.stack/bin/test-s* . @@ -679,6 +682,12 @@ jobs: test -z "$(nm processexecutor.o)" # TODO: test NO_* defines + - name: Test execinfo.h detection + run: | + make clean + make cli/stacktrace.o | grep HAVE_EXECINFO_H=1 + test -n "$(nm cli/stacktrace.o)" + - name: Test testrunner inclusion/exclusion run: | ! ./testrunner -d TestUtils | grep -v TestUtils > /dev/null @@ -703,12 +712,12 @@ jobs: runs-on: ubuntu-22.04 # run on the latest image only steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false - name: ccache - uses: hendrikmuhs/ccache-action@v1.2 + uses: hendrikmuhs/ccache-action@v1.2.24 with: key: ${{ github.workflow }}-${{ github.job }}-${{ matrix.os }} @@ -728,7 +737,7 @@ jobs: - name: CMake run: | - cmake -S . -B cmake.output -Werror=dev -DHAVE_RULES=On -DBUILD_TESTING=On -DBUILD_GUI=On -DWITH_QCHART=On -DBUILD_TRIAGE=On -DUSE_MATCHCOMPILER=Verify -DENABLE_CHECK_INTERNAL=On -DCPPCHK_GLIBCXX_DEBUG=Off -DCMAKE_DISABLE_PRECOMPILE_HEADERS=On -DCMAKE_GLOBAL_AUTOGEN_TARGET=On -DDISABLE_DMAKE=On + cmake -S . -B cmake.output -Werror=dev --warn-uninitialized -DHAVE_RULES=On -DBUILD_TESTING=On -DBUILD_GUI=On -DWITH_QCHART=On -DBUILD_TRIAGE=On -DUSE_MATCHCOMPILER=Verify -DENABLE_CHECK_INTERNAL=On -DCPPCHK_GLIBCXX_DEBUG=Off -DCMAKE_DISABLE_PRECOMPILE_HEADERS=On -DCMAKE_GLOBAL_AUTOGEN_TARGET=On -DDISABLE_DMAKE=On - name: Generate dependencies run: | diff --git a/.github/workflows/CI-windows.yml b/.github/workflows/CI-windows.yml index 6fa92373a7c..66858c7afbe 100644 --- a/.github/workflows/CI-windows.yml +++ b/.github/workflows/CI-windows.yml @@ -31,7 +31,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false @@ -47,29 +47,30 @@ jobs: modules: 'qtcharts' setup-python: 'false' cache: true - aqtversion: '==3.1.*' # TODO: remove when aqtinstall 3.2.2 is available - name: Run CMake run: | rem TODO: enable rules? - rem specify Release build so matchcompiler is used - cmake -S . -B build -Werror=dev -DCMAKE_BUILD_TYPE=Release -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DBUILD_TESTING=Off -DBUILD_GUI=On -DWITH_QCHART=On -DBUILD_TRIAGE=On -DBUILD_ONLINE_HELP=On -DCMAKE_INSTALL_PREFIX=cppcheck-cmake-install -DCMAKE_COMPILE_WARNING_AS_ERROR=On || exit /b !errorlevel! + cmake -S . -B build -Werror=dev --warn-uninitialized -DCMAKE_BUILD_TYPE=Debug -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DBUILD_TESTING=Off -DBUILD_GUI=On -DWITH_QCHART=On -DBUILD_TRIAGE=On -DBUILD_ONLINE_HELP=On -DISABLE_DMAKE=On -DCMAKE_INSTALL_PREFIX=cppcheck-cmake-install -DCMAKE_COMPILE_WARNING_AS_ERROR=On || exit /b !errorlevel! - - name: Build GUI release + - name: Build GUI run: | - cmake --build build --target cppcheck-gui --config Release || exit /b !errorlevel! + cmake --build build --config Debug --target cppcheck-gui || exit /b !errorlevel! + # TODO: can this be done in CMake? - name: Deploy GUI run: | - windeployqt build\bin\Release || exit /b !errorlevel! - del build\bin\Release\cppcheck-gui.ilk || exit /b !errorlevel! - del build\bin\Release\cppcheck-gui.pdb || exit /b !errorlevel! + windeployqt --no-translations build\bin\Debug || exit /b !errorlevel! + del build\bin\Debug\cppcheck-gui.pdb || exit /b !errorlevel! # TODO: run GUI tests - name: Run CMake install run: | - cmake --build build --target install + rem TODO: the Qt DLLs are not being installed + cmake --build build --config Debug --target install + rem TODO: validate the installed files + rem TODO: the structure does not match an actual Windows installation build_cmake_cxxstd: strategy: @@ -81,7 +82,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false @@ -92,7 +93,7 @@ jobs: - name: Run CMake run: | - cmake -S . -B build.cxxstd -Werror=dev -G "Visual Studio 17 2022" -A x64 -DCMAKE_CXX_STANDARD=${{ matrix.cxxstd }} -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=On -DCMAKE_COMPILE_WARNING_AS_ERROR=On || exit /b !errorlevel! + cmake -S . -B build.cxxstd -Werror=dev --warn-uninitialized -A x64 -DCMAKE_CXX_STANDARD=${{ matrix.cxxstd }} -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=On -DCMAKE_COMPILE_WARNING_AS_ERROR=On || exit /b !errorlevel! - name: Build run: | @@ -107,7 +108,7 @@ jobs: CMAKE_VERSION_FULL: 3.22.6 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false @@ -128,17 +129,16 @@ jobs: modules: 'qtcharts' setup-python: 'false' cache: true - aqtversion: '==3.1.*' # TODO: remove when aqtinstall 3.2.2 is available - name: Run CMake (without GUI) run: | :: TODO: enable DHAVE_RULES? - cmake-${{ env.CMAKE_VERSION_FULL }}-windows-x86_64\bin\cmake.exe -S . -B cmake.output -G "Visual Studio 17 2022" -A x64 -DHAVE_RULES=Off -DBUILD_TESTING=On + cmake-${{ env.CMAKE_VERSION_FULL }}-windows-x86_64\bin\cmake.exe -S . -B cmake.output -A x64 -DHAVE_RULES=Off -DBUILD_TESTING=On - name: Run CMake (with GUI) run: | :: TODO: enable DHAVE_RULES? - cmake-${{ env.CMAKE_VERSION_FULL }}-windows-x86_64\bin\cmake.exe -S . -B cmake.output.gui -G "Visual Studio 17 2022" -A x64 -DHAVE_RULES=Off -DBUILD_TESTING=On -DBUILD_GUI=On -DWITH_QCHART=On -DBUILD_TRIAGE=On + cmake-${{ env.CMAKE_VERSION_FULL }}-windows-x86_64\bin\cmake.exe -S . -B cmake.output.gui -A x64 -DHAVE_RULES=Off -DBUILD_TESTING=On -DBUILD_GUI=On -DWITH_QCHART=On -DBUILD_TRIAGE=On build: strategy: @@ -154,13 +154,13 @@ jobs: PCRE_VERSION: 8.45 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false - name: Set up Python if: matrix.config == 'release' - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: '3.14' check-latest: true @@ -172,7 +172,7 @@ jobs: - name: Cache PCRE id: cache-pcre - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: | externals\pcre.h @@ -272,7 +272,7 @@ jobs: - name: Test SEH wrapper if: matrix.config == 'release' run: | - cmake -S . -B build.cmake.seh -Werror=dev -DBUILD_TESTING=On -DCMAKE_COMPILE_WARNING_AS_ERROR=On || exit /b !errorlevel! + cmake -S . -B build.cmake.seh -Werror=dev --warn-uninitialized -DBUILD_TESTING=On -DCMAKE_COMPILE_WARNING_AS_ERROR=On || exit /b !errorlevel! cmake --build build.cmake.seh --target test-sehwrapper || exit /b !errorlevel! :: TODO: how to run this without copying the file? copy build.cmake.seh\bin\Debug\test-sehwrapper.exe . || exit /b !errorlevel! diff --git a/.github/workflows/buildman.yml b/.github/workflows/buildman.yml index b0b399dd851..e84cf4bffd9 100644 --- a/.github/workflows/buildman.yml +++ b/.github/workflows/buildman.yml @@ -19,7 +19,7 @@ jobs: convert_via_pandoc: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false @@ -38,7 +38,7 @@ jobs: with: args: --output=output/manual-premium.pdf man/manual-premium.md - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: output path: output @@ -46,7 +46,7 @@ jobs: manpage: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false @@ -59,7 +59,7 @@ jobs: run: | make man - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: cppcheck.1 path: cppcheck.1 diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index 7b462c688f0..400fb929786 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -10,7 +10,7 @@ permissions: jobs: Fuzzing: runs-on: ubuntu-latest - if: ${{ github.repository_owner == 'danmar' }} + if: ${{ github.repository_owner == 'cppcheck-opensource' }} steps: - name: Build Fuzzers id: build @@ -27,7 +27,7 @@ jobs: dry-run: false language: c++ - name: Upload Crash - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 if: failure() && steps.build.outcome == 'success' with: name: artifacts diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml index c4f8cc0cf6b..4f2db4ab113 100644 --- a/.github/workflows/clang-tidy.yml +++ b/.github/workflows/clang-tidy.yml @@ -27,7 +27,7 @@ jobs: QT_VERSION: 6.10.0 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false @@ -61,7 +61,7 @@ jobs: - name: Prepare CMake run: | - cmake -S . -B cmake.output -Werror=dev -DHAVE_RULES=On -DBUILD_TESTING=On -DBUILD_GUI=On -DWITH_QCHART=On -DBUILD_TRIAGE=On -DENABLE_CHECK_INTERNAL=On -DCMAKE_GLOBAL_AUTOGEN_TARGET=On -DDISABLE_DMAKE=On -DCPPCHK_GLIBCXX_DEBUG=Off -DCMAKE_COMPILE_WARNING_AS_ERROR=On + cmake -S . -B cmake.output -Werror=dev --warn-uninitialized -DHAVE_RULES=On -DBUILD_TESTING=On -DBUILD_GUI=On -DWITH_QCHART=On -DBUILD_TRIAGE=On -DENABLE_CHECK_INTERNAL=On -DCMAKE_GLOBAL_AUTOGEN_TARGET=On -DDISABLE_DMAKE=On -DCPPCHK_GLIBCXX_DEBUG=Off -DCMAKE_COMPILE_WARNING_AS_ERROR=On env: CC: clang-22 CXX: clang++-22 @@ -86,7 +86,7 @@ jobs: run: | cmake --build cmake.output --target run-clang-tidy-csa 2> /dev/null - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 if: success() || failure() with: name: Compilation Database diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml new file mode 100644 index 00000000000..b53d8d8b4f9 --- /dev/null +++ b/.github/workflows/claude-review.yml @@ -0,0 +1,53 @@ +# Automated code review of pull requests using Claude +# +# A member/owner/collaborator requests a review by writing a PR comment +# containing "@claude review". +# +# pull_request_target is not used because the Claude GitHub App token exchange +# rejects OIDC tokens from that event (401 "Invalid OIDC token"). +# +# issue_comment runs the workflow file from the default branch with access to +# secrets. The PR code is never checked out or executed. +name: claude-review + +on: + issue_comment: + types: [created] + +jobs: + review: + if: | + github.event.issue.pull_request && + contains(github.event.comment.body, '@claude review') && + contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) + + runs-on: ubuntu-24.04 + + permissions: + contents: read + pull-requests: write + id-token: write + + steps: + # checks out the base branch, not the PR head + - uses: actions/checkout@v7 + with: + fetch-depth: 1 + + - name: Claude review + uses: anthropics/claude-code-action@v1 + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + prompt: | + REPO: ${{ github.repository }} + PR NUMBER: ${{ github.event.issue.number }} + + Review this pull request. Focus on correctness bugs, potential + false positives/false negatives in checkers, performance problems + and missing tests. Be concise and only report real issues. + + Use `gh pr diff` to see the changes. Post specific issues as inline + comments with `mcp__github_inline_comment__create_inline_comment` + and post a short overall summary with `gh pr comment`. + claude_args: | + --allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*)" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 12e758d2c9e..4fe5620680d 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -33,15 +33,18 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: persist-credentials: false # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} + config: | + paths-ignore: + - gui/manualtest - name: Build cppcheck if: matrix.language == 'cpp' @@ -49,4 +52,4 @@ jobs: make -j$(nproc) CXXOPTS="-Werror" HAVE_RULES=yes CPPCHK_GLIBCXX_DEBUG= cppcheck - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 diff --git a/.github/workflows/corpus.yml b/.github/workflows/corpus.yml new file mode 100644 index 00000000000..d462222b268 --- /dev/null +++ b/.github/workflows/corpus.yml @@ -0,0 +1,58 @@ +# Syntax reference https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions +# Environment reference https://help.github.com/en/actions/reference/virtual-environments-for-github-hosted-runners +name: corpus + +on: + schedule: + - cron: '0 0 * * 0' + workflow_dispatch: + +permissions: + contents: read + +jobs: + corpus: + runs-on: ubuntu-22.04 + if: ${{ github.repository_owner == 'cppcheck-opensource' }} + + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + + - name: ccache + uses: hendrikmuhs/ccache-action@v1.2.24 + with: + key: ${{ github.workflow }}-${{ runner.os }} + + - name: Install missing software on ubuntu + run: | + sudo apt-get update + sudo apt-get install -y fdupes + + - name: build testrunner + run: | + store_dir=$(pwd)/store + mkdir $store_dir + export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" + make -j$(nproc) CXXOPTS="-Werror" CPPOPTS="-DSTORE_INPUT_DIR=\"\\\"$store_dir\\\"\"" testrunner + + - name: run testrunner + run: | + ./testrunner -q + + - name: de-duplicate files + run: | + set -x + ls -l ./store | wc -l + echo "removing duplicates" + fdupes -qdN ./store > /dev/null + ls -l ./store | wc -l + # print largest size + ls -l ./store | cut -d' ' -f5 | sort -u -n -r | head -n1 + + - uses: actions/upload-artifact@v7 + if: success() + with: + name: corpus + path: ./store diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 13f56172a80..fd3b5063049 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -21,12 +21,12 @@ jobs: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false - name: ccache - uses: hendrikmuhs/ccache-action@v1.2 + uses: hendrikmuhs/ccache-action@v1.2.24 with: key: ${{ github.workflow }}-${{ runner.os }} @@ -57,12 +57,12 @@ jobs: lcov --extract lcov_tmp.info "$(pwd)/*" --output-file lcov.info genhtml lcov.info -o coverage_report --frame --legend --demangle-cpp - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: Coverage results path: coverage_report - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} # file: ./coverage.xml # optional diff --git a/.github/workflows/coverity.yml b/.github/workflows/coverity.yml index 3c07b61d7c7..4c62971e4c4 100644 --- a/.github/workflows/coverity.yml +++ b/.github/workflows/coverity.yml @@ -12,9 +12,9 @@ permissions: jobs: scan: runs-on: ubuntu-latest - if: ${{ github.repository_owner == 'danmar' }} + if: ${{ github.repository_owner == 'cppcheck-opensource' }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false - name: Install missing software on ubuntu diff --git a/.github/workflows/cppcheck-premium.yml b/.github/workflows/cppcheck-premium.yml index 5cb63ca4d5e..9febf366138 100644 --- a/.github/workflows/cppcheck-premium.yml +++ b/.github/workflows/cppcheck-premium.yml @@ -25,7 +25,7 @@ jobs: build: runs-on: ubuntu-24.04 # run on the latest image only steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false @@ -33,9 +33,9 @@ jobs: run: | premium_version=${{ inputs.premium_version }} if [ -z $premium_version ]; then - premium_version=25.8.3 + premium_version=26.9.0 #wget https://files.cppchecksolutions.com/devdrop/cppcheckpremium-$premium_version-amd64.tar.gz -O cppcheckpremium.tar.gz - wget https://files.cppchecksolutions.com/$premium_version/ubuntu-24.04/cppcheckpremium-$premium_version-amd64.tar.gz -O cppcheckpremium.tar.gz + wget https://files.cppchecksolutions.com/release/2026/$premium_version/ubuntu-24.04/cppcheckpremium-$premium_version-amd64.tar.gz -O cppcheckpremium.tar.gz else wget https://files.cppchecksolutions.com/$premium_version/ubuntu-24.04/cppcheckpremium-$premium_version-amd64.tar.gz -O cppcheckpremium.tar.gz fi @@ -44,29 +44,31 @@ jobs: - name: Generate a license file run: | - echo cppcheck > cppcheck.lic - echo 261231 >> cppcheck.lic - echo 80000 >> cppcheck.lic - echo 4b64673f03fb6230 >> cppcheck.lic - echo path:lib >> cppcheck.lic + echo 1 > cppcheck-premium-git.lic + echo 271231 >> cppcheck-premium-git.lic + echo 70c84e8412a64e58 >> cppcheck-premium-git.lic + echo fbb65b6bd40de73b >> cppcheck-premium-git.lic + echo autosar:1 >> cppcheck-premium-git.lic + echo cert:1 >> cppcheck-premium-git.lic + echo misra:1 >> cppcheck-premium-git.lic + echo licensed-to:Cppcheck CI >> cppcheck-premium-git.lic - name: Check run: | - cppcheckpremium/premiumaddon --check-loc-license cppcheck.lic > cppcheck-premium-loc - cppcheckpremium/cppcheck --premium=safety-off -j$(nproc) -D__GNUC__ -D__CPPCHECK__ --suppressions-list=cppcheckpremium-suppressions --platform=unix64 --enable=style --premium=misra-c++-2023 --premium=cert-c++-2016 --inline-suppr lib --error-exitcode=0 --output-format=sarif 2> results.sarif + cppcheckpremium/cppcheck --premium-license-file=cppcheck-premium-git.lic --premium=safety-off -j$(nproc) -D__GNUC__ -D__CPPCHECK__ --suppressions-list=cppcheckpremium-suppressions --platform=unix64 --enable=style --premium=misra-c++-2023 --premium=cert-c++-2016 --inline-suppr lib --error-exitcode=0 --output-format=sarif 2> results.sarif - name: Cat results run: | #sed -i 's|"security-severity":.*||' results.sarif cat results.sarif - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: results path: results.sarif - name: Upload report - uses: github/codeql-action/upload-sarif@v3 + uses: github/codeql-action/upload-sarif@v4 with: sarif_file: results.sarif category: cppcheckpremium diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index fd491c0ec0e..4ec72f935fd 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -24,12 +24,12 @@ jobs: UNCRUSTIFY_VERSION: 0.80.1 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false - name: Cache uncrustify - uses: actions/cache@v4 + uses: actions/cache@v5 id: cache-uncrustify with: path: | diff --git a/.github/workflows/gcc-versions.yml b/.github/workflows/gcc-versions.yml new file mode 100644 index 00000000000..3402bade743 --- /dev/null +++ b/.github/workflows/gcc-versions.yml @@ -0,0 +1,39 @@ +# the purpose of this github action is to test that cppcheck source code can be compiled using old gcc versions. +# the gcc images we use here are copied from the official gcc images on docker hub + +name: gcc-versions + +on: + push: + branches: + - 'main' + - 'releases/**' + - '2.*' + tags: + - '2.*' + pull_request: + +permissions: + contents: read + +jobs: + build: + + strategy: + matrix: + #image: ["ghcr.io/cppcheck-opensource/gcc:5.4", "ghcr.io/cppcheck-opensource/gcc:6.5", "ghcr.io/cppcheck-opensource/gcc:7.5", "ghcr.io/cppcheck-opensource/gcc:8.5", "ghcr.io/cppcheck-opensource/gcc:9.5"] + image: ["ghcr.io/cppcheck-opensource/gcc:5.4"] + fail-fast: false + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Build cppcheck + run: | + # FIXME: simplecpp-1.8.0 can't be compiled with gcc 5.4, so we can't run 'make test' now + #docker run --rm -v ${{ github.workspace }}:/cppcheck -w /cppcheck ${{ matrix.image }} make -j$(nproc) CXXOPTS="-Werror" test + docker run --rm -v ${{ github.workspace }}:/cppcheck -w /cppcheck ${{ matrix.image }} g++ -fsyntax-only -Iexternals -Iexternals/picojson -Iexternals/simplecpp -Iexternals/tinyxml2 -std=c++11 -Wno-multichar lib/*.cpp diff --git a/.github/workflows/iwyu.yml b/.github/workflows/iwyu.yml index ed6971128f5..2e54b92541e 100644 --- a/.github/workflows/iwyu.yml +++ b/.github/workflows/iwyu.yml @@ -30,7 +30,7 @@ jobs: fail-fast: false runs-on: ${{ matrix.os }} - if: ${{ github.repository_owner == 'danmar' }} + if: ${{ github.repository_owner == 'cppcheck-opensource' }} container: image: ${{ matrix.image }} @@ -39,7 +39,7 @@ jobs: QT_VERSION: 6.10.0 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false @@ -71,7 +71,7 @@ jobs: - name: Install missing software on Fedora if: contains(matrix.image, 'fedora') run: | - dnf install -y cmake clang pcre-devel + dnf install -y cmake clang dnf install -y libglvnd-devel # fixes missing dependency for Qt in CMake dnf install -y p7zip-plugins # required as fallback for py7zr in Qt installation dnf install -y python3-pip # fixes missing pip module in jurplel/install-qt-action @@ -125,7 +125,8 @@ jobs: - name: Prepare CMake run: | - cmake -S . -B cmake.output -Werror=dev -DCMAKE_BUILD_TYPE=Release -DHAVE_RULES=On -DBUILD_TESTING=On -DBUILD_GUI=On -DWITH_QCHART=On -DBUILD_TRIAGE=On -DENABLE_CHECK_INTERNAL=On -DCMAKE_GLOBAL_AUTOGEN_TARGET=On -DDISABLE_DMAKE=On -DCMAKE_DISABLE_PRECOMPILE_HEADERS=On -DCPPCHK_GLIBCXX_DEBUG=Off -DUSE_MATCHCOMPILER=Off -DEXTERNALS_AS_SYSTEM=On -DUSE_LIBCXX=${{ matrix.stdlib == 'libc++' }} + # TODO: re-enable HAVE_RULES + cmake -S . -B cmake.output -Werror=dev --warn-uninitialized -DCMAKE_BUILD_TYPE=Release -DHAVE_RULES=Off -DBUILD_TESTING=On -DBUILD_GUI=On -DWITH_QCHART=On -DBUILD_TRIAGE=On -DENABLE_CHECK_INTERNAL=On -DCMAKE_GLOBAL_AUTOGEN_TARGET=On -DDISABLE_DMAKE=On -DCMAKE_DISABLE_PRECOMPILE_HEADERS=On -DCPPCHK_GLIBCXX_DEBUG=Off -DUSE_MATCHCOMPILER=Off -DEXTERNALS_AS_SYSTEM=On -DUSE_LIBCXX=${{ matrix.stdlib == 'libc++' }} env: CC: clang CXX: clang++ @@ -159,13 +160,13 @@ jobs: IWYU: include-what-you-use IWYU_CLANG_INC: ${{ matrix.clang_inc }} - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 if: success() || failure() with: name: Compilation Database (include-what-you-use - ${{ matrix.os }} ${{ matrix.stdlib }}) path: ./cmake.output/compile_commands.json - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 if: ${{ contains(matrix.os, 'macos') && (success() || failure()) }} with: name: macOS Mappings @@ -173,7 +174,7 @@ jobs: ./iwyu-mapgen-apple-libc.py ./macos.imp - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 if: success() || failure() with: name: Logs (include-what-you-use - ${{ matrix.os }} ${{ matrix.stdlib }}) @@ -192,13 +193,13 @@ jobs: fail-fast: false runs-on: ubuntu-22.04 - if: ${{ github.repository_owner == 'danmar' }} + if: ${{ github.repository_owner == 'cppcheck-opensource' }} env: QT_VERSION: 6.10.0 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false @@ -232,7 +233,8 @@ jobs: - name: Prepare CMake run: | - cmake -S . -B cmake.output -Werror=dev -DCMAKE_BUILD_TYPE=Release -DHAVE_RULES=On -DBUILD_TESTING=On -DBUILD_GUI=On -DWITH_QCHART=On -DBUILD_TRIAGE=On -DENABLE_CHECK_INTERNAL=On -DCMAKE_GLOBAL_AUTOGEN_TARGET=On -DDISABLE_DMAKE=On -DCMAKE_DISABLE_PRECOMPILE_HEADERS=On -DCPPCHK_GLIBCXX_DEBUG=Off -DUSE_MATCHCOMPILER=Off -DEXTERNALS_AS_SYSTEM=On -DUSE_LIBCXX=${{ matrix.use_libcxx }} + # TODO: re-enable HAVE_RULES + cmake -S . -B cmake.output -Werror=dev --warn-uninitialized -DCMAKE_BUILD_TYPE=Release -DHAVE_RULES=Off -DBUILD_TESTING=On -DBUILD_GUI=On -DWITH_QCHART=On -DBUILD_TRIAGE=On -DENABLE_CHECK_INTERNAL=On -DCMAKE_GLOBAL_AUTOGEN_TARGET=On -DDISABLE_DMAKE=On -DCMAKE_DISABLE_PRECOMPILE_HEADERS=On -DCPPCHK_GLIBCXX_DEBUG=Off -DUSE_MATCHCOMPILER=Off -DEXTERNALS_AS_SYSTEM=On -DUSE_LIBCXX=${{ matrix.use_libcxx }} env: CC: clang-22 CXX: clang++-22 @@ -254,13 +256,13 @@ jobs: # TODO: run multi-threaded find $PWD/cli $PWD/lib $PWD/test $PWD/gui -maxdepth 1 -name "*.cpp" | xargs -t -n 1 clang-include-cleaner-22 --print=changes --extra-arg=-w --extra-arg=-stdlib=${{ matrix.stdlib }} -p cmake.output > clang-include-cleaner.log 2>&1 - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 if: success() || failure() with: name: Compilation Database (clang-include-cleaner - ${{ matrix.stdlib }}) path: ./cmake.output/compile_commands.json - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 if: success() || failure() with: name: Logs (clang-include-cleaner - ${{ matrix.stdlib }}) diff --git a/.github/workflows/release-windows-mingw.yml b/.github/workflows/release-windows-mingw.yml index 3b9b836347f..8085990e39d 100644 --- a/.github/workflows/release-windows-mingw.yml +++ b/.github/workflows/release-windows-mingw.yml @@ -33,7 +33,7 @@ jobs: timeout-minutes: 19 # max + 3*std of the last 7K runs steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false @@ -63,7 +63,7 @@ jobs: cp /mingw64/bin/libstdc*.dll cppcheck-mingw/ cp /mingw64/bin/libwinpthread-1.dll cppcheck-mingw/ - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: cppcheck-mingw path: cppcheck-mingw diff --git a/.github/workflows/release-windows.yml b/.github/workflows/release-windows.yml index 607c8434baa..aa467d7b875 100644 --- a/.github/workflows/release-windows.yml +++ b/.github/workflows/release-windows.yml @@ -22,7 +22,7 @@ jobs: build: runs-on: windows-2025 - if: ${{ github.repository_owner == 'danmar' }} + if: ${{ github.repository_owner == 'cppcheck-opensource' }} env: PYTHON_VERSION: 3.14 @@ -32,7 +32,7 @@ jobs: BOOST_MINOR_VERSION: 89 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false @@ -55,8 +55,8 @@ jobs: 7z x pcre-%PCRE_VERSION%.zip || exit /b !errorlevel! cd pcre-%PCRE_VERSION% || exit /b !errorlevel! git apply --ignore-space-change ..\externals\pcre.patch || exit /b !errorlevel! - cmake . -G "Visual Studio 17 2022" -A x64 -DPCRE_BUILD_PCRECPP=OFF -DPCRE_BUILD_PCREGREP=OFF -DPCRE_BUILD_TESTS=OFF -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_COMPILE_WARNING_AS_ERROR=On || exit /b !errorlevel! - msbuild -m PCRE.sln -p:Configuration=Release -p:Platform=x64 || exit /b !errorlevel! + cmake . -A x64 -DPCRE_BUILD_PCRECPP=OFF -DPCRE_BUILD_PCREGREP=OFF -DPCRE_BUILD_TESTS=OFF -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_COMPILE_WARNING_AS_ERROR=On || exit /b !errorlevel! + msbuild -m PCRE.slnx -p:Configuration=Release -p:Platform=x64 || exit /b !errorlevel! copy pcre.h ..\externals || exit /b !errorlevel! copy Release\pcre.lib ..\externals\pcre64.lib || exit /b !errorlevel! @@ -85,7 +85,7 @@ jobs: run: | :: TODO: enable rules? :: specify Release build so matchcompiler is used - cmake -S . -B build -Werror=dev -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=Off -DBUILD_GUI=On -DWITH_QCHART=On -DBUILD_ONLINE_HELP=On -DUSE_BOOST=ON -DBOOST_INCLUDEDIR=%GITHUB_WORKSPACE%\boost -DCMAKE_COMPILE_WARNING_AS_ERROR=On || exit /b !errorlevel! + cmake -S . -B build -Werror=dev --warn-uninitialized -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=Off -DBUILD_GUI=On -DWITH_QCHART=On -DBUILD_ONLINE_HELP=On -DUSE_BOOST=ON -DBOOST_INCLUDEDIR=%GITHUB_WORKSPACE%\boost -DCMAKE_COMPILE_WARNING_AS_ERROR=On || exit /b !errorlevel! cmake --build build --target cppcheck-gui --config Release || exit /b !errorlevel! # TODO: package PDBs @@ -95,7 +95,7 @@ jobs: del build\bin\Release\cppcheck-gui.ilk || exit /b !errorlevel! del build\bin\Release\cppcheck-gui.pdb || exit /b !errorlevel! - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: deploy path: build\bin\Release @@ -109,7 +109,7 @@ jobs: env: _CL_: /WX - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: bin path: bin @@ -158,7 +158,7 @@ jobs: :: copy libcrypto-3-x64.dll and libssl-3-x64.dll copy %RUNNER_WORKSPACE%\Qt\Tools\OpenSSLv3\Win_x64\bin\lib*.dll win_installer\files || exit /b !errorlevel! - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: collect path: win_installer\files @@ -173,7 +173,7 @@ jobs: @echo ProductVersion="%PRODUCTVER%" || exit /b !errorlevel! msbuild -m cppcheck.wixproj -p:Platform=x64,ProductVersion=%PRODUCTVER%.${{ github.run_number }} || exit /b !errorlevel! - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: installer path: win_installer/Build/ @@ -209,7 +209,7 @@ jobs: del win_installer\files\Qt6Svg.dll || exit /b !errorlevel! del win_installer\files\vc_redist.x64.exe || exit /b !errorlevel! - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: portable path: win_installer\files diff --git a/.github/workflows/sanitizers.yml b/.github/workflows/sanitizers.yml index d7ff31939d0..9d5a6007a69 100644 --- a/.github/workflows/sanitizers.yml +++ b/.github/workflows/sanitizers.yml @@ -50,17 +50,17 @@ jobs: CCACHE_SLOPPINESS: pch_defines,time_macros steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false - name: ccache - uses: hendrikmuhs/ccache-action@v1.2 + uses: hendrikmuhs/ccache-action@v1.2.24 with: key: ${{ github.workflow }}-${{ github.job }}-${{ matrix.os }}-${{ matrix.sanitizer }} - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: '3.14' check-latest: true @@ -96,7 +96,7 @@ jobs: - name: CMake run: | - cmake -S . -B cmake.output -Werror=dev -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHAVE_RULES=On -DBUILD_TESTING=On -DBUILD_GUI=On -DWITH_QCHART=On -DBUILD_TRIAGE=On -DUSE_MATCHCOMPILER=Verify ${{ matrix.cmake_opts }} -DENABLE_CHECK_INTERNAL=On -DUSE_BOOST=On -DCPPCHK_GLIBCXX_DEBUG=Off -DCMAKE_DISABLE_PRECOMPILE_HEADERS=On -DCMAKE_GLOBAL_AUTOGEN_TARGET=On -DDISABLE_DMAKE=On -DFILESDIR= -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + cmake -S . -B cmake.output -Werror=dev --warn-uninitialized -DCMAKE_BUILD_TYPE=RelWithDebInfo -DHAVE_RULES=On -DBUILD_TESTING=On -DBUILD_GUI=On -DWITH_QCHART=On -DBUILD_TRIAGE=On -DUSE_MATCHCOMPILER=Verify ${{ matrix.cmake_opts }} -DENABLE_CHECK_INTERNAL=On -DUSE_BOOST=On -DCPPCHK_GLIBCXX_DEBUG=Off -DCMAKE_DISABLE_PRECOMPILE_HEADERS=On -DCMAKE_GLOBAL_AUTOGEN_TARGET=On -DDISABLE_DMAKE=On -DFILESDIR= -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache env: CC: clang-22 CXX: clang++-22 diff --git a/.github/workflows/scriptcheck.yml b/.github/workflows/scriptcheck.yml index 844b1d5c2f3..35efc35ca1b 100644 --- a/.github/workflows/scriptcheck.yml +++ b/.github/workflows/scriptcheck.yml @@ -21,17 +21,17 @@ jobs: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false - name: ccache - uses: hendrikmuhs/ccache-action@v1.2 + uses: hendrikmuhs/ccache-action@v1.2.24 with: key: ${{ github.workflow }}-${{ runner.os }} - name: Cache Cppcheck - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: cppcheck key: ${{ runner.os }}-scriptcheck-cppcheck-${{ github.sha }} @@ -56,19 +56,19 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false # TODO: bailout on error - name: Restore Cppcheck - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: cppcheck key: ${{ runner.os }}-scriptcheck-cppcheck-${{ github.sha }} - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} check-latest: true @@ -209,7 +209,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false diff --git a/.github/workflows/selfcheck.yml b/.github/workflows/selfcheck.yml index 1adcc540cf9..77d6d1bc483 100644 --- a/.github/workflows/selfcheck.yml +++ b/.github/workflows/selfcheck.yml @@ -24,12 +24,12 @@ jobs: QT_VERSION: 6.10.0 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false - name: ccache - uses: hendrikmuhs/ccache-action@v1.2 + uses: hendrikmuhs/ccache-action@v1.2.24 with: key: ${{ github.workflow }}-${{ runner.os }} @@ -64,7 +64,7 @@ jobs: # unusedFunction - start - name: CMake run: | - cmake -S . -B cmake.output -Werror=dev -DHAVE_RULES=On -DBUILD_TESTING=On -DBUILD_GUI=ON -DWITH_QCHART=ON -DBUILD_TRIAGE=On -DENABLE_CHECK_INTERNAL=On -DCMAKE_GLOBAL_AUTOGEN_TARGET=On -DDISABLE_DMAKE=On -DCPPCHK_GLIBCXX_DEBUG=Off + cmake -S . -B cmake.output -Werror=dev --warn-uninitialized -DHAVE_RULES=On -DBUILD_TESTING=On -DBUILD_GUI=ON -DWITH_QCHART=ON -DBUILD_TRIAGE=On -DENABLE_CHECK_INTERNAL=On -DCMAKE_GLOBAL_AUTOGEN_TARGET=On -DDISABLE_DMAKE=On -DCPPCHK_GLIBCXX_DEBUG=Off - name: Generate dependencies run: | @@ -90,7 +90,7 @@ jobs: # unusedFunction notest - start - name: CMake (no test) run: | - cmake -S . -B cmake.output.notest -Werror=dev -DHAVE_RULES=On -DBUILD_TESTING=Off -DBUILD_GUI=ON -DBUILD_TRIAGE=On -DWITH_QCHART=ON -DENABLE_CHECK_INTERNAL=On -DCMAKE_GLOBAL_AUTOGEN_TARGET=On -DDISABLE_DMAKE=On -DCPPCHK_GLIBCXX_DEBUG=Off + cmake -S . -B cmake.output.notest -Werror=dev --warn-uninitialized -DHAVE_RULES=On -DBUILD_TESTING=Off -DBUILD_GUI=ON -DBUILD_TRIAGE=On -DWITH_QCHART=ON -DENABLE_CHECK_INTERNAL=On -DCMAKE_GLOBAL_AUTOGEN_TARGET=On -DDISABLE_DMAKE=On -DCPPCHK_GLIBCXX_DEBUG=Off - name: Generate dependencies (no test) run: | @@ -112,7 +112,7 @@ jobs: # unusedFunction notest nogui - start - name: CMake (no test / no gui) run: | - cmake -S . -B cmake.output.notest_nogui -Werror=dev -DHAVE_RULES=On -DBUILD_TESTING=Off -DENABLE_CHECK_INTERNAL=On -DCPPCHK_GLIBCXX_DEBUG=Off + cmake -S . -B cmake.output.notest_nogui -Werror=dev --warn-uninitialized -DHAVE_RULES=On -DBUILD_TESTING=Off -DENABLE_CHECK_INTERNAL=On -DCPPCHK_GLIBCXX_DEBUG=Off - name: Generate dependencies (no test / no gui) run: | @@ -121,8 +121,25 @@ jobs: - name: Self check (unusedFunction / no test / no gui) run: | - supprs="--suppress=unusedFunction:lib/errorlogger.h:197 --suppress=unusedFunction:lib/importproject.cpp:1660 --suppress=unusedFunction:lib/importproject.cpp:1684" - ./cppcheck -q --template=selfcheck --error-exitcode=1 --library=cppcheck-lib -D__CPPCHECK__ -D__GNUC__ --enable=unusedFunction,information --exception-handling -rp=. --project=cmake.output.notest_nogui/compile_commands.json --suppressions-list=.selfcheck_unused_suppressions --inline-suppr $supprs + echo ' + + + unusedFunction + lib/errorlogger.h + verboseMessage + + + unusedFunction + lib/importproject.cpp + selectVsConfigurations + + + unusedFunction + lib/importproject.cpp + getVSConfigs + + ' > supprs.xml + ./cppcheck -q --template=selfcheck --error-exitcode=1 --library=cppcheck-lib -D__CPPCHECK__ -D__GNUC__ --enable=unusedFunction,information --exception-handling -rp=. --project=cmake.output.notest_nogui/compile_commands.json --suppressions-list=.selfcheck_unused_suppressions --inline-suppr --suppress-xml=supprs.xml env: DISABLE_VALUEFLOW: 1 UNUSEDFUNCTION_ONLY: 1 @@ -131,7 +148,7 @@ jobs: # unusedFunction notest nocli - start - name: CMake (no test / no cli) run: | - cmake -S . -B cmake.output.notest_nocli -Werror=dev -DHAVE_RULES=On -DBUILD_TESTING=Off -DBUILD_CLI=Off -DBUILD_GUI=ON -DWITH_QCHART=ON -DBUILD_TRIAGE=On -DENABLE_CHECK_INTERNAL=On -DCMAKE_GLOBAL_AUTOGEN_TARGET=On -DDISABLE_DMAKE=On -DCPPCHK_GLIBCXX_DEBUG=Off + cmake -S . -B cmake.output.notest_nocli -Werror=dev --warn-uninitialized -DHAVE_RULES=On -DBUILD_TESTING=Off -DBUILD_CLI=Off -DBUILD_GUI=ON -DWITH_QCHART=ON -DBUILD_TRIAGE=On -DENABLE_CHECK_INTERNAL=On -DCMAKE_GLOBAL_AUTOGEN_TARGET=On -DDISABLE_DMAKE=On -DCPPCHK_GLIBCXX_DEBUG=Off - name: Generate dependencies (no test / no cli) run: | @@ -154,7 +171,7 @@ jobs: # unusedFunction notest nocli nogui - start - name: CMake (no test / no cli / no gui) run: | - cmake -S . -B cmake.output.notest_nocli_nogui -Werror=dev -DHAVE_RULES=On -DBUILD_TESTING=Off -DBUILD_CLI=Off -DBUILD_GUI=Off -DENABLE_CHECK_INTERNAL=On -DCPPCHK_GLIBCXX_DEBUG=Off + cmake -S . -B cmake.output.notest_nocli_nogui -Werror=dev --warn-uninitialized -DHAVE_RULES=On -DBUILD_TESTING=Off -DBUILD_CLI=Off -DBUILD_GUI=Off -DENABLE_CHECK_INTERNAL=On -DCPPCHK_GLIBCXX_DEBUG=Off - name: Generate dependencies (no test / no cli / no gui) run: | @@ -172,7 +189,7 @@ jobs: - name: Fetch corpus run: | - wget https://github.com/danmar/cppcheck/archive/refs/tags/2.8.tar.gz + wget https://github.com/cppcheck-opensource/cppcheck/archive/refs/tags/2.8.tar.gz tar xvf 2.8.tar.gz - name: CMake (corpus / no test) @@ -198,7 +215,7 @@ jobs: env: DISABLE_VALUEFLOW: 1 - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: Callgrind Output path: ./callgrind.* @@ -211,7 +228,7 @@ jobs: env: DISABLE_VALUEFLOW: 1 - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: Memcheck Output path: ./memcheck.* diff --git a/.github/workflows/valgrind.yml b/.github/workflows/valgrind.yml index 3e4a02dbb48..44f762030c3 100644 --- a/.github/workflows/valgrind.yml +++ b/.github/workflows/valgrind.yml @@ -21,12 +21,12 @@ jobs: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false - name: ccache - uses: hendrikmuhs/ccache-action@v1.2 + uses: hendrikmuhs/ccache-action@v1.2.24 with: key: ${{ github.workflow }}-${{ runner.os }} @@ -64,7 +64,7 @@ jobs: #env: # DEBUGINFOD_URLS: https://debuginfod.ubuntu.com - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 if: success() || failure() with: name: Logs diff --git a/.gitignore b/.gitignore index 434203fe2a9..a821204bf2d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ *.gcno *.gch *.o +*.a *.pyc /cppcheck /cppcheck.exe diff --git a/.selfcheck_suppressions b/.selfcheck_suppressions index 171a5a997d7..a8ee87de10f 100644 --- a/.selfcheck_suppressions +++ b/.selfcheck_suppressions @@ -3,12 +3,46 @@ missingIncludeSystem checkersReport # warnings in Qt generated code we cannot fix +funcArgNamesDifferentUnnamed:*/moc_aboutdialog.cpp +funcArgNamesDifferentUnnamed:*/moc_applicationdialog.cpp +funcArgNamesDifferentUnnamed:*/moc_applicationlist.cpp funcArgNamesDifferent:*/moc_checkthread.cpp +funcArgNamesDifferentUnnamed:*/moc_checkthread.cpp +funcArgNamesDifferentUnnamed:*/moc_codeeditor.cpp funcArgNamesDifferent:*/moc_codeeditstylecontrols.cpp +funcArgNamesDifferentUnnamed:*/moc_codeeditstylecontrols.cpp +funcArgNamesDifferentUnnamed:*/moc_compliancereportdialog.cpp +funcArgNamesDifferentUnnamed:*/moc_codeeditstyledialog.cpp +funcArgNamesDifferentUnnamed:*/moc_fileviewdialog.cpp +funcArgNamesDifferentUnnamed:*/moc_helpdialog.cpp +funcArgNamesDifferentUnnamed:*/moc_libraryaddfunctiondialog.cpp +funcArgNamesDifferentUnnamed:*/moc_librarydialog.cpp +funcArgNamesDifferentUnnamed:*/moc_libraryeditargdialog.cpp +funcArgNamesDifferentUnnamed:*/moc_mainwindow.cpp +funcArgNamesDifferentUnnamed:*/moc_newsuppressiondialog.cpp +funcArgNamesDifferentUnnamed:*/moc_platforms.cpp +funcArgNamesDifferentUnnamed:*/moc_projectfile.cpp +funcArgNamesDifferentUnnamed:*/moc_projectfiledialog.cpp funcArgNamesDifferent:*/moc_resultstree.cpp +funcArgNamesDifferentUnnamed:*/moc_resultstree.cpp funcArgNamesDifferent:*/moc_resultsview.cpp +funcArgNamesDifferentUnnamed:*/moc_resultsview.cpp +funcArgNamesDifferentUnnamed:*/moc_scratchpad.cpp +funcArgNamesDifferentUnnamed:*/moc_settingsdialog.cpp +funcArgNamesDifferentUnnamed:*/moc_statsdialog.cpp +funcArgNamesDifferentUnnamed:*/moc_testcppchecklibrarydata.cpp +funcArgNamesDifferentUnnamed:*/moc_testfilelist.cpp +funcArgNamesDifferentUnnamed:*/moc_testprojectfile.cpp +funcArgNamesDifferentUnnamed:*/moc_testresultstree.cpp +funcArgNamesDifferentUnnamed:*/moc_testtranslationhandler.cpp +funcArgNamesDifferentUnnamed:*/moc_testxmlreportv2.cpp +funcArgNamesDifferentUnnamed:*/moc_threaddetails.cpp funcArgNamesDifferent:*/moc_threadhandler.cpp +funcArgNamesDifferentUnnamed:*/moc_threadhandler.cpp funcArgNamesDifferent:*/moc_threadresult.cpp +funcArgNamesDifferentUnnamed:*/moc_threadresult.cpp +funcArgNamesDifferentUnnamed:*/moc_translationhandler.cpp +funcArgNamesDifferentUnnamed:*/moc_txtreport.cpp naming-varname:*/gui/ui_*.h functionStatic:*/gui/ui_*.h @@ -28,6 +62,8 @@ templateInstantiation:test/testutils.cpp naming-varname:externals/simplecpp/simplecpp.h naming-privateMemberVariable:externals/simplecpp/simplecpp.h +# false positive; lambda captures its owner +danglingLifetime:externals/simplecpp/simplecpp.h:505 # TODO: these warnings need to be addressed upstream uninitMemberVar:externals/tinyxml2/tinyxml2.h @@ -37,7 +73,11 @@ invalidPrintfArgType_sint:externals/tinyxml2/tinyxml2.h naming-privateMemberVariable:externals/tinyxml2/tinyxml2.h functionStatic:externals/tinyxml2/tinyxml2.cpp funcArgNamesDifferent:externals/tinyxml2/tinyxml2.cpp -nullPointerRedundantCheck:externals/tinyxml2/tinyxml2.cpp +funcArgNamesDifferentUnnamed:externals/tinyxml2/tinyxml2.cpp +funcArgNamesDifferentUnnamed:externals/tinyxml2/tinyxml2.h knownConditionTrueFalse:externals/tinyxml2/tinyxml2.cpp +nullPointerRedundantCheck:externals/tinyxml2/tinyxml2.cpp useStlAlgorithm:externals/simplecpp/simplecpp.cpp +funcArgNamesDifferentUnnamed:externals/simplecpp/simplecpp.h missingMemberCopy:externals/simplecpp/simplecpp.h +shadowFunction:externals/simplecpp/simplecpp.h diff --git a/AUTHORS b/AUTHORS index 1104b24fd2b..50ba3a4f67e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,6 +1,7 @@ The cppcheck team, in alphabetical order: 0x41head +Aaron Danen Abhijit Sawant Abhishek Bharadwaj Abigail Buccaneer @@ -10,6 +11,7 @@ Ahti Legonkov Akhilesh Nema Akio Idehara Albert Aribaud +Alvin Che-Chia Chang Aleksandr Pikalev Aleksey Palazhchenko Alexander Alekseev @@ -22,6 +24,7 @@ Alexey Eryomenko Alexey Zhikhartsev Alfi Maulana Ali Can Demiralp +Alvin Che-Chia Chang Allen Winter Alon Alexander Alon Liberman @@ -33,6 +36,7 @@ Andreas Lutsch Andreas Pokorny Andreas Rönnquist Andreas Vollenweider +Andreas Wilhelm Andrei Karas Andrew C Aitchison Andrew C. Martin @@ -82,6 +86,7 @@ Cary Converse Cary R Caspar Kielwein Changkyoon Kim +Chencheng Mao Chris Lalancette Christian Ehrlicher Christian Franke @@ -94,17 +99,21 @@ Cilyan Olowen Claus Jensby Madsen Colomban Wendling Conrado Gouvea +correctmost daisuke-chiba Daniel Friedrich David Korczynski Daniel Marjamäki +Daschle Newberry David Hallas David Korth +David Ramnerö Dávid Slivka Debrard Sebastien Deepak Gupta Degen's Regens dencat +Devansh Varshney Diego de las Heras Dirk Jagdmann Dirk Mueller @@ -130,6 +139,7 @@ Felipe Pena Felix Faber Felix Geyer Felix Passenberg +Felix Patschkowski Felix Wolff Florian Mueller Florin Iucha @@ -140,6 +150,7 @@ Frank Winklmeier Frank Zingsheim Frederik Schwarzer fu7mu4 +Gaël Bonithon Galimov Albert Garrett Bodily Gary Leutheuser @@ -152,6 +163,7 @@ Gerhard Zlabinger Gerik Rhoden Gianfranco Costamagna Gianluca Scacco +Gilmar Santos Jr Gleydson Soares Goncalo Mao-Cheia Goran Džaferi @@ -182,10 +194,13 @@ Jan Egil Ruud Jan Hellwig János Maros Jay Sigbrandt +Jean-François Deverge Jedrzej Klocek +Jeewoong Kim Jens Bäckman Jens Yllman Jérémy Lefaure +Jérôme Duval Jes Ramsing Jesse Boswell Jim Kuhn @@ -202,6 +217,7 @@ Jonathan Clohessy Jonathan Haehne Jonathan Neuschäfer Jonathan Thackray +Jonny Paton José Martins Jose Roquette Joshua Beck @@ -261,6 +277,7 @@ Marian Klymov Mark de Wever Mark Hermeling Markus Elfring +Martin Boye Petersen Martin Delille Martin Ettl Martin Exner @@ -281,6 +298,7 @@ Matt Johnson Maurice Gilden Mavik Melroy van den Berg +Michael de Lang Michael Drake Michael Løiten Miika-Petteri Matikainen @@ -298,6 +316,7 @@ ms Neszt Tibor Nguyen Duong Tuan Ni2c2k +Nick James Kirkby Nick Ridgway Nicolás Alvarez Nicolas Le Cam @@ -326,6 +345,7 @@ Philip Chimento Philipp Kloke Pierre Schweitzer Pieter Duchi +Piotr Idzik Pino Toscano Pranav Khanna Radek Jarecki @@ -362,6 +382,7 @@ Samuel Degrande Samuel Poláček Sandeep Dutta Savvas Etairidis +Sayed Kaif Scott Ehlert Scott Furry Seafarix Ltd. @@ -428,10 +449,12 @@ Vesa Pikki Ville-Pekka Vahteala Ville Skyttä Vincent Le Garrec +Vít Kučera Vladimir Petrigo Wang Haoyu Wang Yang WenChung Chiu +William Jakobsson Wolfgang Stöggl x29a XhmikosR @@ -445,4 +468,5 @@ Zhu Lei Дмитрий Старцев GUI graphics courtesy of Tango Desktop Project: -http://tango.freedesktop.org +https://gitlab.freedesktop.org/tango/tango-icon-theme +https://commons.wikimedia.org/wiki/File:Refresh_file.svg diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c037a10b36..f4e104cf96f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.22) -project(Cppcheck VERSION 2.20.99 LANGUAGES CXX) +project(Cppcheck VERSION 2.22.99 LANGUAGES CXX) include(cmake/options.cmake) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 715a913b6fc..09e88f021e2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ These are some guidelines *any* contributor should follow. They will help to mak ## Code Changes -Code contributions are handled via GitHub pull requests: https://github.com/danmar/cppcheck/pulls. +Code contributions are handled via GitHub pull requests: https://github.com/cppcheck-opensource/cppcheck/pulls. If you file a pull request you might not get a reply immediately. We are a very small team and it might not fit in the current scope or time. @@ -18,7 +18,7 @@ Also after you filed a pull request please be ready to reply to questions and fe Please do not be discouraged if your change was rejected or if the review process might not have been as smooth as it could have been. -Each change should be accompanied with a unit ([C++](https://github.com/danmar/cppcheck/tree/main/test)) or integration ([Python](https://github.com/danmar/cppcheck/tree/main/test/cli)) test to ensure that it doesn't regress with future changes. Negative tests (testing the opposite behavior) would be favorable but might not be required or might already exist depending on the change. Tests which introduce `TODO_ASSERT_` or `@pytest.mark.skip`/`@pytest.mark.xfail` should have tickets filed. +Each change should be accompanied with a unit ([C++](https://github.com/cppcheck-opensource/cppcheck/tree/main/test)) or integration ([Python](https://github.com/cppcheck-opensource/cppcheck/tree/main/test/cli)) test to ensure that it doesn't regress with future changes. Negative tests (testing the opposite behavior) would be favorable but might not be required or might already exist depending on the change. Tests which introduce `TODO_ASSERT_` or `@pytest.mark.skip`/`@pytest.mark.xfail` should have tickets filed. If the change is modifying existing behavior (i.e. adding a feature or fixing a bug) it should be accompanied by an issue in the [tracker](https://trac.cppcheck.net) (if you do not have access we can assist with that). Depending on the change it might also warrant an entry in `releasenotes.txt`. @@ -56,7 +56,7 @@ So if you start spending a lot of time on these, you might want to get into touc ## simplecpp -At its core Cppcheck is relying on the `simplecpp` library which is a preprocessor implementation which was spun off into its [separate project](https://github.com/danmar/simplecpp) with its own [bug tracker](https://github.com/danmar/simplecpp/issues). This is also maintained by the Cppcheck developers and contributions to it are also welcome. +At its core Cppcheck is relying on the `simplecpp` library which is a preprocessor implementation which was spun off into its [separate project](https://github.com/cppcheck-opensource/simplecpp) with its own [bug tracker](https://github.com/cppcheck-opensource/simplecpp/issues). This is also maintained by the Cppcheck developers and contributions to it are also welcome. ## Translations diff --git a/Makefile b/Makefile index cd7a56377e2..6f86e205f77 100644 --- a/Makefile +++ b/Makefile @@ -155,7 +155,13 @@ else ifneq ($(HAVE_RULES),) $(error invalid HAVE_RULES value '$(HAVE_RULES)') endif -HAVE_EXECINFO_H=$(shell echo "\#include " | $(CXX) -c -xc - 2> /dev/null && echo "1" || echo "0") +# older make versions do not support # in $(shell) and newer ones handle the escape sequence literally +REQUIRE_ESCAPE=$(shell echo "\#define DEF" | $(CXX) -c -xc - 2> /dev/null && echo "1" || echo "0") +ifeq ($(REQUIRE_ESCAPE),1) + HAVE_EXECINFO_H=$(shell echo "\#include " | $(CXX) -c -xc - 2> /dev/null && echo "1" || echo "0") +else + HAVE_EXECINFO_H=$(shell echo "#include " | $(CXX) -c -xc - 2> /dev/null && echo "1" || echo "0") +endif override CPPFLAGS += -DHAVE_EXECINFO_H=$(HAVE_EXECINFO_H) override CXXFLAGS += $(CXXOPTS) @@ -202,7 +208,6 @@ LIBOBJ = $(libcppdir)/valueflow.o \ $(libcppdir)/addoninfo.o \ $(libcppdir)/analyzerinfo.o \ $(libcppdir)/astutils.o \ - $(libcppdir)/check.o \ $(libcppdir)/check64bit.o \ $(libcppdir)/checkassert.o \ $(libcppdir)/checkautovariables.o \ @@ -215,6 +220,7 @@ LIBOBJ = $(libcppdir)/valueflow.o \ $(libcppdir)/checkersreport.o \ $(libcppdir)/checkexceptionsafety.o \ $(libcppdir)/checkfunctions.o \ + $(libcppdir)/checkimpl.o \ $(libcppdir)/checkinternal.o \ $(libcppdir)/checkio.o \ $(libcppdir)/checkleakautovar.o \ @@ -222,6 +228,7 @@ LIBOBJ = $(libcppdir)/valueflow.o \ $(libcppdir)/checknullpointer.o \ $(libcppdir)/checkother.o \ $(libcppdir)/checkpostfixoperator.o \ + $(libcppdir)/checks.o \ $(libcppdir)/checksizeof.o \ $(libcppdir)/checkstl.o \ $(libcppdir)/checkstring.o \ @@ -331,6 +338,7 @@ TESTOBJ = test/fixture.o \ test/testpreprocessor.o \ test/testprocessexecutor.o \ test/testprogrammemory.o \ + test/testrefthunk.o \ test/testregex.o \ test/testsarifreport.o \ test/testsettings.o \ @@ -476,46 +484,43 @@ check-nonneg: ###### Build -$(libcppdir)/valueflow.o: lib/valueflow.cpp lib/addoninfo.h lib/analyzer.h lib/astutils.h lib/calculate.h lib/check.h lib/checkers.h lib/checkuninitvar.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/findtoken.h lib/forwardanalyzer.h lib/infer.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/programmemory.h lib/regex.h lib/reverseanalyzer.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenlist.h lib/utils.h lib/valueflow.h lib/valueptr.h lib/vf_analyzers.h lib/vf_common.h lib/vf_settokenvalue.h lib/vfvalue.h +$(libcppdir)/valueflow.o: lib/valueflow.cpp lib/analyzer.h lib/astutils.h lib/calculate.h lib/check.h lib/checkers.h lib/checkimpl.h lib/checkuninitvar.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/findtoken.h lib/forwardanalyzer.h lib/infer.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/programmemory.h lib/refthunk.h lib/reverseanalyzer.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenlist.h lib/utils.h lib/valueflow.h lib/valueptr.h lib/vf_analyzers.h lib/vf_common.h lib/vf_settokenvalue.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/valueflow.cpp -$(libcppdir)/tokenize.o: lib/tokenize.cpp externals/simplecpp/simplecpp.h lib/addoninfo.h lib/astutils.h lib/checkers.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/preprocessor.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/summaries.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h lib/vfvalue.h +$(libcppdir)/tokenize.o: lib/tokenize.cpp externals/simplecpp/simplecpp.h lib/astutils.h lib/checkers.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/preprocessor.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/summaries.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/tokenize.cpp -$(libcppdir)/symboldatabase.o: lib/symboldatabase.cpp lib/addoninfo.h lib/astutils.h lib/checkers.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/keywords.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h lib/vfvalue.h +$(libcppdir)/symboldatabase.o: lib/symboldatabase.cpp lib/astutils.h lib/checkers.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/keywords.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/symboldatabase.cpp $(libcppdir)/addoninfo.o: lib/addoninfo.cpp externals/picojson/picojson.h lib/addoninfo.h lib/config.h lib/json.h lib/path.h lib/standards.h lib/utils.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/addoninfo.cpp -$(libcppdir)/analyzerinfo.o: lib/analyzerinfo.cpp externals/tinyxml2/tinyxml2.h lib/analyzerinfo.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/mathlib.h lib/path.h lib/platform.h lib/standards.h lib/utils.h lib/xml.h +$(libcppdir)/analyzerinfo.o: lib/analyzerinfo.cpp externals/tinyxml2/tinyxml2.h lib/analyzerinfo.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/standards.h lib/utils.h lib/xml.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/analyzerinfo.cpp -$(libcppdir)/astutils.o: lib/astutils.cpp lib/addoninfo.h lib/astutils.h lib/check.h lib/checkclass.h lib/checkers.h lib/config.h lib/errortypes.h lib/findtoken.h lib/infer.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/utils.h lib/valueflow.h lib/valueptr.h lib/vfvalue.h +$(libcppdir)/astutils.o: lib/astutils.cpp lib/astutils.h lib/check.h lib/checkclass.h lib/checkers.h lib/checkimpl.h lib/config.h lib/errortypes.h lib/findtoken.h lib/infer.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/utils.h lib/valueflow.h lib/valueptr.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/astutils.cpp -$(libcppdir)/check.o: lib/check.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h - $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/check.cpp - -$(libcppdir)/check64bit.o: lib/check64bit.cpp lib/addoninfo.h lib/check.h lib/check64bit.h lib/checkers.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h +$(libcppdir)/check64bit.o: lib/check64bit.cpp lib/check.h lib/check64bit.h lib/checkers.h lib/checkimpl.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/check64bit.cpp -$(libcppdir)/checkassert.o: lib/checkassert.cpp lib/addoninfo.h lib/astutils.h lib/check.h lib/checkassert.h lib/checkers.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h +$(libcppdir)/checkassert.o: lib/checkassert.cpp lib/astutils.h lib/check.h lib/checkassert.h lib/checkers.h lib/checkimpl.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkassert.cpp -$(libcppdir)/checkautovariables.o: lib/checkautovariables.cpp lib/addoninfo.h lib/astutils.h lib/check.h lib/checkautovariables.h lib/checkers.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h lib/vfvalue.h +$(libcppdir)/checkautovariables.o: lib/checkautovariables.cpp lib/astutils.h lib/check.h lib/checkautovariables.h lib/checkers.h lib/checkimpl.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkautovariables.cpp -$(libcppdir)/checkbool.o: lib/checkbool.cpp lib/addoninfo.h lib/astutils.h lib/check.h lib/checkbool.h lib/checkers.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h +$(libcppdir)/checkbool.o: lib/checkbool.cpp lib/astutils.h lib/check.h lib/checkbool.h lib/checkers.h lib/checkimpl.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkbool.cpp -$(libcppdir)/checkbufferoverrun.o: lib/checkbufferoverrun.cpp externals/tinyxml2/tinyxml2.h lib/addoninfo.h lib/astutils.h lib/check.h lib/checkbufferoverrun.h lib/checkers.h lib/config.h lib/ctu.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h lib/vfvalue.h lib/xml.h +$(libcppdir)/checkbufferoverrun.o: lib/checkbufferoverrun.cpp externals/tinyxml2/tinyxml2.h lib/astutils.h lib/check.h lib/checkbufferoverrun.h lib/checkers.h lib/checkimpl.h lib/config.h lib/ctu.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h lib/vf_common.h lib/vfvalue.h lib/xml.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkbufferoverrun.cpp -$(libcppdir)/checkclass.o: lib/checkclass.cpp externals/tinyxml2/tinyxml2.h lib/addoninfo.h lib/astutils.h lib/check.h lib/checkclass.h lib/checkers.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h lib/xml.h +$(libcppdir)/checkclass.o: lib/checkclass.cpp externals/tinyxml2/tinyxml2.h lib/astutils.h lib/check.h lib/checkclass.h lib/checkers.h lib/checkimpl.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h lib/xml.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkclass.cpp -$(libcppdir)/checkcondition.o: lib/checkcondition.cpp lib/addoninfo.h lib/astutils.h lib/check.h lib/checkcondition.h lib/checkers.h lib/checkother.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h +$(libcppdir)/checkcondition.o: lib/checkcondition.cpp lib/astutils.h lib/check.h lib/checkcondition.h lib/checkers.h lib/checkimpl.h lib/checkother.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkcondition.cpp $(libcppdir)/checkers.o: lib/checkers.cpp lib/checkers.h lib/config.h @@ -524,73 +529,79 @@ $(libcppdir)/checkers.o: lib/checkers.cpp lib/checkers.h lib/config.h $(libcppdir)/checkersidmapping.o: lib/checkersidmapping.cpp lib/checkers.h lib/config.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkersidmapping.cpp -$(libcppdir)/checkersreport.o: lib/checkersreport.cpp lib/addoninfo.h lib/checkers.h lib/checkersreport.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/utils.h +$(libcppdir)/checkersreport.o: lib/checkersreport.cpp lib/addoninfo.h lib/checkers.h lib/checkersreport.h lib/config.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/utils.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkersreport.cpp -$(libcppdir)/checkexceptionsafety.o: lib/checkexceptionsafety.cpp lib/addoninfo.h lib/astutils.h lib/check.h lib/checkers.h lib/checkexceptionsafety.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h +$(libcppdir)/checkexceptionsafety.o: lib/checkexceptionsafety.cpp lib/astutils.h lib/check.h lib/checkers.h lib/checkexceptionsafety.h lib/checkimpl.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkexceptionsafety.cpp -$(libcppdir)/checkfunctions.o: lib/checkfunctions.cpp lib/addoninfo.h lib/astutils.h lib/check.h lib/checkers.h lib/checkfunctions.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h lib/vfvalue.h +$(libcppdir)/checkfunctions.o: lib/checkfunctions.cpp lib/astutils.h lib/check.h lib/checkers.h lib/checkfunctions.h lib/checkimpl.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkfunctions.cpp -$(libcppdir)/checkinternal.o: lib/checkinternal.cpp lib/addoninfo.h lib/astutils.h lib/check.h lib/checkers.h lib/checkinternal.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h +$(libcppdir)/checkimpl.o: lib/checkimpl.cpp lib/checkers.h lib/checkimpl.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h + $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkimpl.cpp + +$(libcppdir)/checkinternal.o: lib/checkinternal.cpp lib/astutils.h lib/check.h lib/checkers.h lib/checkimpl.h lib/checkinternal.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkinternal.cpp -$(libcppdir)/checkio.o: lib/checkio.cpp lib/addoninfo.h lib/astutils.h lib/check.h lib/checkers.h lib/checkio.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h +$(libcppdir)/checkio.o: lib/checkio.cpp lib/astutils.h lib/check.h lib/checkers.h lib/checkimpl.h lib/checkio.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkio.cpp -$(libcppdir)/checkleakautovar.o: lib/checkleakautovar.cpp lib/addoninfo.h lib/astutils.h lib/check.h lib/checkers.h lib/checkleakautovar.h lib/checkmemoryleak.h lib/checknullpointer.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h +$(libcppdir)/checkleakautovar.o: lib/checkleakautovar.cpp lib/astutils.h lib/check.h lib/checkers.h lib/checkimpl.h lib/checkleakautovar.h lib/checkmemoryleak.h lib/checknullpointer.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkleakautovar.cpp -$(libcppdir)/checkmemoryleak.o: lib/checkmemoryleak.cpp lib/addoninfo.h lib/astutils.h lib/check.h lib/checkers.h lib/checkmemoryleak.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h +$(libcppdir)/checkmemoryleak.o: lib/checkmemoryleak.cpp lib/astutils.h lib/check.h lib/checkers.h lib/checkimpl.h lib/checkmemoryleak.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkmemoryleak.cpp -$(libcppdir)/checknullpointer.o: lib/checknullpointer.cpp lib/addoninfo.h lib/astutils.h lib/check.h lib/checkers.h lib/checknullpointer.h lib/config.h lib/ctu.h lib/errorlogger.h lib/errortypes.h lib/findtoken.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h lib/vfvalue.h +$(libcppdir)/checknullpointer.o: lib/checknullpointer.cpp lib/astutils.h lib/check.h lib/checkers.h lib/checkimpl.h lib/checknullpointer.h lib/config.h lib/ctu.h lib/errorlogger.h lib/errortypes.h lib/findtoken.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checknullpointer.cpp -$(libcppdir)/checkother.o: lib/checkother.cpp lib/addoninfo.h lib/astutils.h lib/check.h lib/checkers.h lib/checkother.h lib/config.h lib/errortypes.h lib/fwdanalysis.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h lib/vfvalue.h +$(libcppdir)/checkother.o: lib/checkother.cpp lib/astutils.h lib/check.h lib/checkers.h lib/checkimpl.h lib/checkother.h lib/config.h lib/errortypes.h lib/fwdanalysis.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkother.cpp -$(libcppdir)/checkpostfixoperator.o: lib/checkpostfixoperator.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/checkpostfixoperator.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h +$(libcppdir)/checkpostfixoperator.o: lib/checkpostfixoperator.cpp lib/check.h lib/checkers.h lib/checkimpl.h lib/checkpostfixoperator.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkpostfixoperator.cpp -$(libcppdir)/checksizeof.o: lib/checksizeof.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/checksizeof.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h +$(libcppdir)/checks.o: lib/checks.cpp lib/check.h lib/check64bit.h lib/checkassert.h lib/checkautovariables.h lib/checkbool.h lib/checkbufferoverrun.h lib/checkclass.h lib/checkcondition.h lib/checkexceptionsafety.h lib/checkfunctions.h lib/checkimpl.h lib/checkinternal.h lib/checkio.h lib/checkleakautovar.h lib/checkmemoryleak.h lib/checknullpointer.h lib/checkother.h lib/checkpostfixoperator.h lib/checks.h lib/checksizeof.h lib/checkstl.h lib/checkstring.h lib/checktype.h lib/checkuninitvar.h lib/checkunusedvar.h lib/checkvaarg.h lib/config.h lib/ctu.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/refthunk.h lib/standards.h lib/vfvalue.h + $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checks.cpp + +$(libcppdir)/checksizeof.o: lib/checksizeof.cpp lib/check.h lib/checkers.h lib/checkimpl.h lib/checksizeof.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checksizeof.cpp -$(libcppdir)/checkstl.o: lib/checkstl.cpp lib/addoninfo.h lib/astutils.h lib/check.h lib/checkers.h lib/checknullpointer.h lib/checkstl.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/pathanalysis.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h lib/vfvalue.h +$(libcppdir)/checkstl.o: lib/checkstl.cpp lib/astutils.h lib/calculate.h lib/check.h lib/checkers.h lib/checkimpl.h lib/checknullpointer.h lib/checkstl.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/pathanalysis.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkstl.cpp -$(libcppdir)/checkstring.o: lib/checkstring.cpp lib/addoninfo.h lib/astutils.h lib/check.h lib/checkers.h lib/checkstring.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h +$(libcppdir)/checkstring.o: lib/checkstring.cpp lib/astutils.h lib/check.h lib/checkers.h lib/checkimpl.h lib/checkstring.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkstring.cpp -$(libcppdir)/checktype.o: lib/checktype.cpp lib/addoninfo.h lib/astutils.h lib/check.h lib/checkers.h lib/checktype.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h lib/vfvalue.h +$(libcppdir)/checktype.o: lib/checktype.cpp lib/astutils.h lib/check.h lib/checkers.h lib/checkimpl.h lib/checktype.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checktype.cpp -$(libcppdir)/checkuninitvar.o: lib/checkuninitvar.cpp lib/addoninfo.h lib/astutils.h lib/check.h lib/checkers.h lib/checknullpointer.h lib/checkuninitvar.h lib/config.h lib/ctu.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h +$(libcppdir)/checkuninitvar.o: lib/checkuninitvar.cpp lib/astutils.h lib/check.h lib/checkers.h lib/checkimpl.h lib/checknullpointer.h lib/checkuninitvar.h lib/config.h lib/ctu.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkuninitvar.cpp -$(libcppdir)/checkunusedfunctions.o: lib/checkunusedfunctions.cpp externals/tinyxml2/tinyxml2.h lib/addoninfo.h lib/analyzerinfo.h lib/astutils.h lib/checkers.h lib/checkunusedfunctions.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h lib/xml.h +$(libcppdir)/checkunusedfunctions.o: lib/checkunusedfunctions.cpp externals/tinyxml2/tinyxml2.h lib/analyzerinfo.h lib/astutils.h lib/checkers.h lib/checkunusedfunctions.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h lib/xml.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkunusedfunctions.cpp -$(libcppdir)/checkunusedvar.o: lib/checkunusedvar.cpp lib/addoninfo.h lib/astutils.h lib/check.h lib/checkers.h lib/checkunusedvar.h lib/config.h lib/errortypes.h lib/fwdanalysis.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h lib/vfvalue.h +$(libcppdir)/checkunusedvar.o: lib/checkunusedvar.cpp lib/astutils.h lib/check.h lib/checkers.h lib/checkimpl.h lib/checkunusedvar.h lib/config.h lib/errortypes.h lib/fwdanalysis.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkunusedvar.cpp -$(libcppdir)/checkvaarg.o: lib/checkvaarg.cpp lib/addoninfo.h lib/astutils.h lib/check.h lib/checkers.h lib/checkvaarg.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h +$(libcppdir)/checkvaarg.o: lib/checkvaarg.cpp lib/astutils.h lib/check.h lib/checkers.h lib/checkimpl.h lib/checkvaarg.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkvaarg.cpp -$(libcppdir)/clangimport.o: lib/clangimport.cpp lib/clangimport.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h +$(libcppdir)/clangimport.o: lib/clangimport.cpp lib/clangimport.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/refthunk.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/clangimport.cpp $(libcppdir)/color.o: lib/color.cpp lib/color.h lib/config.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/color.cpp -$(libcppdir)/cppcheck.o: lib/cppcheck.cpp externals/picojson/picojson.h externals/simplecpp/simplecpp.h lib/addoninfo.h lib/analyzerinfo.h lib/check.h lib/checkers.h lib/checkunusedfunctions.h lib/clangimport.h lib/color.h lib/config.h lib/cppcheck.h lib/ctu.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/json.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/preprocessor.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h lib/version.h lib/vfvalue.h +$(libcppdir)/cppcheck.o: lib/cppcheck.cpp externals/picojson/picojson.h externals/simplecpp/simplecpp.h lib/addoninfo.h lib/analyzerinfo.h lib/check.h lib/checkers.h lib/checks.h lib/checkunusedfunctions.h lib/clangimport.h lib/color.h lib/config.h lib/cppcheck.h lib/ctu.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/json.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/preprocessor.h lib/refthunk.h lib/regex.h lib/rule.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h lib/version.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/cppcheck.cpp -$(libcppdir)/ctu.o: lib/ctu.cpp externals/tinyxml2/tinyxml2.h lib/astutils.h lib/check.h lib/config.h lib/ctu.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h lib/xml.h +$(libcppdir)/ctu.o: lib/ctu.cpp externals/tinyxml2/tinyxml2.h lib/astutils.h lib/check.h lib/config.h lib/ctu.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/refthunk.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h lib/xml.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/ctu.cpp -$(libcppdir)/errorlogger.o: lib/errorlogger.cpp externals/tinyxml2/tinyxml2.h lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/token.h lib/tokenlist.h lib/utils.h lib/vfvalue.h lib/xml.h +$(libcppdir)/errorlogger.o: lib/errorlogger.cpp externals/tinyxml2/tinyxml2.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenlist.h lib/utils.h lib/vfvalue.h lib/xml.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/errorlogger.cpp $(libcppdir)/errortypes.o: lib/errortypes.cpp lib/config.h lib/errortypes.h lib/utils.h @@ -599,13 +610,13 @@ $(libcppdir)/errortypes.o: lib/errortypes.cpp lib/config.h lib/errortypes.h lib/ $(libcppdir)/findtoken.o: lib/findtoken.cpp lib/astutils.h lib/config.h lib/errortypes.h lib/findtoken.h lib/library.h lib/mathlib.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/utils.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/findtoken.cpp -$(libcppdir)/forwardanalyzer.o: lib/forwardanalyzer.cpp lib/addoninfo.h lib/analyzer.h lib/astutils.h lib/checkers.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/forwardanalyzer.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenlist.h lib/utils.h lib/valueptr.h lib/vfvalue.h +$(libcppdir)/forwardanalyzer.o: lib/forwardanalyzer.cpp lib/analyzer.h lib/astutils.h lib/checkers.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/forwardanalyzer.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenlist.h lib/utils.h lib/valueptr.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/forwardanalyzer.cpp -$(libcppdir)/fwdanalysis.o: lib/fwdanalysis.cpp lib/addoninfo.h lib/astutils.h lib/checkers.h lib/config.h lib/errortypes.h lib/fwdanalysis.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/utils.h lib/vfvalue.h +$(libcppdir)/fwdanalysis.o: lib/fwdanalysis.cpp lib/astutils.h lib/checkers.h lib/config.h lib/errortypes.h lib/fwdanalysis.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/utils.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/fwdanalysis.cpp -$(libcppdir)/importproject.o: lib/importproject.cpp externals/picojson/picojson.h externals/tinyxml2/tinyxml2.h lib/addoninfo.h lib/checkers.h lib/config.h lib/errortypes.h lib/filesettings.h lib/importproject.h lib/json.h lib/library.h lib/mathlib.h lib/path.h lib/pathmatch.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/token.h lib/tokenlist.h lib/utils.h lib/vfvalue.h lib/xml.h +$(libcppdir)/importproject.o: lib/importproject.cpp externals/picojson/picojson.h externals/tinyxml2/tinyxml2.h lib/checkers.h lib/config.h lib/filesettings.h lib/importproject.h lib/json.h lib/library.h lib/mathlib.h lib/path.h lib/pathmatch.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/utils.h lib/xml.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/importproject.cpp $(libcppdir)/infer.o: lib/infer.cpp lib/calculate.h lib/config.h lib/errortypes.h lib/infer.h lib/mathlib.h lib/smallvector.h lib/templatesimplifier.h lib/token.h lib/utils.h lib/valueptr.h lib/vfvalue.h @@ -614,7 +625,7 @@ $(libcppdir)/infer.o: lib/infer.cpp lib/calculate.h lib/config.h lib/errortypes. $(libcppdir)/keywords.o: lib/keywords.cpp lib/config.h lib/keywords.h lib/standards.h lib/utils.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/keywords.cpp -$(libcppdir)/library.o: lib/library.cpp externals/tinyxml2/tinyxml2.h lib/astutils.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenlist.h lib/utils.h lib/vfvalue.h lib/xml.h +$(libcppdir)/library.o: lib/library.cpp externals/tinyxml2/tinyxml2.h lib/astutils.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/refthunk.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenlist.h lib/utils.h lib/vfvalue.h lib/xml.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/library.cpp $(libcppdir)/mathlib.o: lib/mathlib.cpp externals/simplecpp/simplecpp.h lib/config.h lib/errortypes.h lib/mathlib.h lib/smallvector.h lib/templatesimplifier.h lib/token.h lib/utils.h lib/vfvalue.h @@ -623,7 +634,7 @@ $(libcppdir)/mathlib.o: lib/mathlib.cpp externals/simplecpp/simplecpp.h lib/conf $(libcppdir)/path.o: lib/path.cpp externals/simplecpp/simplecpp.h lib/config.h lib/path.h lib/standards.h lib/utils.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/path.cpp -$(libcppdir)/pathanalysis.o: lib/pathanalysis.cpp lib/astutils.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/pathanalysis.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/utils.h lib/vfvalue.h +$(libcppdir)/pathanalysis.o: lib/pathanalysis.cpp lib/astutils.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/pathanalysis.h lib/refthunk.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/utils.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/pathanalysis.cpp $(libcppdir)/pathmatch.o: lib/pathmatch.cpp lib/config.h lib/path.h lib/pathmatch.h lib/standards.h @@ -632,70 +643,70 @@ $(libcppdir)/pathmatch.o: lib/pathmatch.cpp lib/config.h lib/path.h lib/pathmatc $(libcppdir)/platform.o: lib/platform.cpp externals/tinyxml2/tinyxml2.h lib/config.h lib/mathlib.h lib/path.h lib/platform.h lib/standards.h lib/utils.h lib/xml.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/platform.cpp -$(libcppdir)/preprocessor.o: lib/preprocessor.cpp externals/simplecpp/simplecpp.h lib/addoninfo.h lib/checkers.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/preprocessor.h lib/regex.h lib/settings.h lib/standards.h lib/suppressions.h lib/utils.h +$(libcppdir)/preprocessor.o: lib/preprocessor.cpp externals/simplecpp/simplecpp.h lib/checkers.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/preprocessor.h lib/refthunk.h lib/settings.h lib/standards.h lib/suppressions.h lib/utils.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/preprocessor.cpp -$(libcppdir)/programmemory.o: lib/programmemory.cpp lib/addoninfo.h lib/astutils.h lib/calculate.h lib/checkers.h lib/config.h lib/errortypes.h lib/infer.h lib/library.h lib/mathlib.h lib/platform.h lib/programmemory.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenlist.h lib/utils.h lib/valueflow.h lib/valueptr.h lib/vfvalue.h +$(libcppdir)/programmemory.o: lib/programmemory.cpp lib/astutils.h lib/calculate.h lib/checkers.h lib/config.h lib/errortypes.h lib/infer.h lib/library.h lib/mathlib.h lib/platform.h lib/programmemory.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenlist.h lib/utils.h lib/valueflow.h lib/valueptr.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/programmemory.cpp $(libcppdir)/regex.o: lib/regex.cpp lib/config.h lib/regex.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/regex.cpp -$(libcppdir)/reverseanalyzer.o: lib/reverseanalyzer.cpp lib/addoninfo.h lib/analyzer.h lib/astutils.h lib/checkers.h lib/config.h lib/errortypes.h lib/forwardanalyzer.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/reverseanalyzer.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/utils.h lib/valueptr.h lib/vfvalue.h +$(libcppdir)/reverseanalyzer.o: lib/reverseanalyzer.cpp lib/analyzer.h lib/astutils.h lib/checkers.h lib/config.h lib/errortypes.h lib/forwardanalyzer.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/reverseanalyzer.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/utils.h lib/valueptr.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/reverseanalyzer.cpp -$(libcppdir)/sarifreport.o: lib/sarifreport.cpp externals/picojson/picojson.h lib/addoninfo.h lib/check.h lib/checkers.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/json.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/sarifreport.h lib/settings.h lib/standards.h lib/utils.h +$(libcppdir)/sarifreport.o: lib/sarifreport.cpp externals/picojson/picojson.h lib/check.h lib/checkers.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/json.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/sarifreport.h lib/settings.h lib/standards.h lib/utils.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/sarifreport.cpp -$(libcppdir)/settings.o: lib/settings.cpp externals/picojson/picojson.h lib/addoninfo.h lib/checkers.h lib/config.h lib/errortypes.h lib/json.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/summaries.h lib/suppressions.h lib/utils.h lib/vfvalue.h +$(libcppdir)/settings.o: lib/settings.cpp externals/picojson/picojson.h lib/addoninfo.h lib/checkers.h lib/config.h lib/errortypes.h lib/json.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/rule.h lib/settings.h lib/standards.h lib/summaries.h lib/suppressions.h lib/utils.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/settings.cpp $(libcppdir)/standards.o: lib/standards.cpp externals/simplecpp/simplecpp.h lib/config.h lib/standards.h lib/utils.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/standards.cpp -$(libcppdir)/summaries.o: lib/summaries.cpp lib/addoninfo.h lib/analyzerinfo.h lib/checkers.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/summaries.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h +$(libcppdir)/summaries.o: lib/summaries.cpp lib/analyzerinfo.h lib/checkers.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/summaries.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/summaries.cpp -$(libcppdir)/suppressions.o: lib/suppressions.cpp externals/tinyxml2/tinyxml2.h lib/addoninfo.h lib/checkers.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/pathmatch.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/token.h lib/tokenlist.h lib/utils.h lib/vfvalue.h lib/xml.h +$(libcppdir)/suppressions.o: lib/suppressions.cpp externals/tinyxml2/tinyxml2.h lib/addoninfo.h lib/checkers.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/pathmatch.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/token.h lib/tokenlist.h lib/utils.h lib/vfvalue.h lib/xml.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/suppressions.cpp -$(libcppdir)/templatesimplifier.o: lib/templatesimplifier.cpp lib/addoninfo.h lib/checkers.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h +$(libcppdir)/templatesimplifier.o: lib/templatesimplifier.cpp lib/checkers.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/templatesimplifier.cpp $(libcppdir)/timer.o: lib/timer.cpp lib/config.h lib/timer.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/timer.cpp -$(libcppdir)/token.o: lib/token.cpp externals/simplecpp/simplecpp.h lib/addoninfo.h lib/astutils.h lib/checkers.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenlist.h lib/tokenrange.h lib/utils.h lib/valueflow.h lib/vfvalue.h +$(libcppdir)/token.o: lib/token.cpp externals/simplecpp/simplecpp.h lib/astutils.h lib/checkers.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenlist.h lib/tokenrange.h lib/utils.h lib/valueflow.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/token.cpp -$(libcppdir)/tokenlist.o: lib/tokenlist.cpp externals/simplecpp/simplecpp.h lib/addoninfo.h lib/astutils.h lib/checkers.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/keywords.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenlist.h lib/utils.h lib/vfvalue.h +$(libcppdir)/tokenlist.o: lib/tokenlist.cpp externals/simplecpp/simplecpp.h lib/astutils.h lib/checkers.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/keywords.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenlist.h lib/utils.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/tokenlist.cpp $(libcppdir)/utils.o: lib/utils.cpp lib/config.h lib/utils.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/utils.cpp -$(libcppdir)/vf_analyzers.o: lib/vf_analyzers.cpp lib/addoninfo.h lib/analyzer.h lib/astutils.h lib/calculate.h lib/checkers.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/programmemory.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/utils.h lib/valueflow.h lib/valueptr.h lib/vf_analyzers.h lib/vf_common.h lib/vf_settokenvalue.h lib/vfvalue.h +$(libcppdir)/vf_analyzers.o: lib/vf_analyzers.cpp lib/analyzer.h lib/astutils.h lib/calculate.h lib/checkers.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/programmemory.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/utils.h lib/valueflow.h lib/valueptr.h lib/vf_analyzers.h lib/vf_common.h lib/vf_settokenvalue.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/vf_analyzers.cpp -$(libcppdir)/vf_common.o: lib/vf_common.cpp lib/addoninfo.h lib/astutils.h lib/checkers.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/utils.h lib/vf_common.h lib/vf_settokenvalue.h lib/vfvalue.h +$(libcppdir)/vf_common.o: lib/vf_common.cpp lib/astutils.h lib/checkers.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/utils.h lib/vf_common.h lib/vf_settokenvalue.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/vf_common.cpp -$(libcppdir)/vf_settokenvalue.o: lib/vf_settokenvalue.cpp lib/addoninfo.h lib/astutils.h lib/calculate.h lib/checkers.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/utils.h lib/valueflow.h lib/vf_common.h lib/vf_settokenvalue.h lib/vfvalue.h +$(libcppdir)/vf_settokenvalue.o: lib/vf_settokenvalue.cpp lib/astutils.h lib/calculate.h lib/checkers.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/utils.h lib/valueflow.h lib/vf_common.h lib/vf_settokenvalue.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/vf_settokenvalue.cpp $(libcppdir)/vfvalue.o: lib/vfvalue.cpp lib/config.h lib/errortypes.h lib/mathlib.h lib/smallvector.h lib/templatesimplifier.h lib/token.h lib/utils.h lib/vfvalue.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/vfvalue.cpp -frontend/frontend.o: frontend/frontend.cpp frontend/frontend.h lib/addoninfo.h lib/checkers.h lib/config.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/utils.h +frontend/frontend.o: frontend/frontend.cpp frontend/frontend.h lib/checkers.h lib/config.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/standards.h lib/utils.h $(CXX) ${INCLUDE_FOR_FE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ frontend/frontend.cpp -cli/cmdlineparser.o: cli/cmdlineparser.cpp cli/cmdlinelogger.h cli/cmdlineparser.h cli/filelister.h externals/tinyxml2/tinyxml2.h lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/importproject.h lib/library.h lib/mathlib.h lib/path.h lib/pathmatch.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/suppressions.h lib/timer.h lib/utils.h lib/xml.h +cli/cmdlineparser.o: cli/cmdlineparser.cpp cli/cmdlinelogger.h cli/cmdlineparser.h cli/filelister.h externals/tinyxml2/tinyxml2.h lib/addoninfo.h lib/check.h lib/checkers.h lib/checks.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/importproject.h lib/library.h lib/mathlib.h lib/path.h lib/pathmatch.h lib/platform.h lib/refthunk.h lib/regex.h lib/rule.h lib/settings.h lib/standards.h lib/suppressions.h lib/utils.h lib/xml.h $(CXX) ${INCLUDE_FOR_CLI} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ cli/cmdlineparser.cpp -cli/cppcheckexecutor.o: cli/cppcheckexecutor.cpp cli/cmdlinelogger.h cli/cmdlineparser.h cli/cppcheckexecutor.h cli/executor.h cli/processexecutor.h cli/sehwrapper.h cli/signalhandler.h cli/singleexecutor.h cli/threadexecutor.h externals/picojson/picojson.h lib/addoninfo.h lib/analyzerinfo.h lib/check.h lib/checkers.h lib/checkersreport.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/json.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/sarifreport.h lib/settings.h lib/standards.h lib/suppressions.h lib/timer.h lib/utils.h +cli/cppcheckexecutor.o: cli/cppcheckexecutor.cpp cli/cmdlinelogger.h cli/cmdlineparser.h cli/cppcheckexecutor.h cli/executor.h cli/processexecutor.h cli/sehwrapper.h cli/signalhandler.h cli/singleexecutor.h cli/threadexecutor.h externals/picojson/picojson.h lib/analyzerinfo.h lib/check.h lib/checkers.h lib/checkersreport.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/json.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/sarifreport.h lib/settings.h lib/standards.h lib/suppressions.h lib/timer.h lib/utils.h $(CXX) ${INCLUDE_FOR_CLI} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ cli/cppcheckexecutor.cpp -cli/executor.o: cli/executor.cpp cli/executor.h lib/addoninfo.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/suppressions.h lib/utils.h +cli/executor.o: cli/executor.cpp cli/executor.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/suppressions.h lib/utils.h $(CXX) ${INCLUDE_FOR_CLI} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ cli/executor.cpp cli/filelister.o: cli/filelister.cpp cli/filelister.h lib/config.h lib/filesettings.h lib/mathlib.h lib/path.h lib/pathmatch.h lib/platform.h lib/standards.h lib/utils.h @@ -704,7 +715,7 @@ cli/filelister.o: cli/filelister.cpp cli/filelister.h lib/config.h lib/filesetti cli/main.o: cli/main.cpp cli/cppcheckexecutor.h lib/config.h lib/filesettings.h lib/mathlib.h lib/path.h lib/platform.h lib/standards.h lib/utils.h $(CXX) ${INCLUDE_FOR_CLI} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ cli/main.cpp -cli/processexecutor.o: cli/processexecutor.cpp cli/executor.h cli/processexecutor.h lib/addoninfo.h lib/check.h lib/checkers.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/suppressions.h lib/timer.h lib/utils.h +cli/processexecutor.o: cli/processexecutor.cpp cli/executor.h cli/processexecutor.h lib/check.h lib/checkers.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/suppressions.h lib/timer.h lib/utils.h $(CXX) ${INCLUDE_FOR_CLI} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ cli/processexecutor.cpp cli/sehwrapper.o: cli/sehwrapper.cpp cli/sehwrapper.h lib/config.h lib/utils.h @@ -713,247 +724,250 @@ cli/sehwrapper.o: cli/sehwrapper.cpp cli/sehwrapper.h lib/config.h lib/utils.h cli/signalhandler.o: cli/signalhandler.cpp cli/signalhandler.h cli/stacktrace.h lib/config.h $(CXX) ${INCLUDE_FOR_CLI} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ cli/signalhandler.cpp -cli/singleexecutor.o: cli/singleexecutor.cpp cli/executor.h cli/singleexecutor.h lib/addoninfo.h lib/check.h lib/checkers.h lib/config.h lib/cppcheck.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/timer.h lib/utils.h +cli/singleexecutor.o: cli/singleexecutor.cpp cli/executor.h cli/singleexecutor.h lib/check.h lib/checkers.h lib/config.h lib/cppcheck.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/standards.h lib/utils.h $(CXX) ${INCLUDE_FOR_CLI} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ cli/singleexecutor.cpp cli/stacktrace.o: cli/stacktrace.cpp cli/stacktrace.h lib/config.h lib/utils.h $(CXX) ${INCLUDE_FOR_CLI} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ cli/stacktrace.cpp -cli/threadexecutor.o: cli/threadexecutor.cpp cli/executor.h cli/threadexecutor.h lib/addoninfo.h lib/check.h lib/checkers.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/suppressions.h lib/timer.h lib/utils.h +cli/threadexecutor.o: cli/threadexecutor.cpp cli/executor.h cli/threadexecutor.h lib/check.h lib/checkers.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/suppressions.h lib/utils.h $(CXX) ${INCLUDE_FOR_CLI} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ cli/threadexecutor.cpp -test/fixture.o: test/fixture.cpp externals/tinyxml2/tinyxml2.h lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/timer.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/xml.h test/fixture.h test/helpers.h test/options.h test/redirect.h +test/fixture.o: test/fixture.cpp externals/tinyxml2/tinyxml2.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/timer.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/xml.h test/fixture.h test/helpers.h test/options.h test/redirect.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/fixture.cpp -test/helpers.o: test/helpers.cpp cli/filelister.h externals/simplecpp/simplecpp.h externals/tinyxml2/tinyxml2.h lib/addoninfo.h lib/checkers.h lib/config.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/pathmatch.h lib/platform.h lib/preprocessor.h lib/regex.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/xml.h test/helpers.h +test/helpers.o: test/helpers.cpp cli/filelister.h externals/simplecpp/simplecpp.h externals/tinyxml2/tinyxml2.h lib/checkers.h lib/config.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/pathmatch.h lib/platform.h lib/preprocessor.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/xml.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/helpers.cpp -test/main.o: test/main.cpp externals/simplecpp/simplecpp.h lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/preprocessor.h lib/regex.h lib/settings.h lib/standards.h lib/utils.h test/fixture.h test/options.h +test/main.o: test/main.cpp externals/simplecpp/simplecpp.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/preprocessor.h lib/refthunk.h lib/settings.h lib/standards.h lib/utils.h test/fixture.h test/options.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/main.cpp test/options.o: test/options.cpp lib/config.h lib/timer.h test/options.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/options.cpp -test/test64bit.o: test/test64bit.cpp lib/addoninfo.h lib/check.h lib/check64bit.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h +test/test64bit.o: test/test64bit.cpp lib/check.h lib/check64bit.h lib/checkers.h lib/checkimpl.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/test64bit.cpp -test/testanalyzerinformation.o: test/testanalyzerinformation.cpp externals/tinyxml2/tinyxml2.h lib/addoninfo.h lib/analyzerinfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/utils.h lib/xml.h test/fixture.h +test/testanalyzerinformation.o: test/testanalyzerinformation.cpp externals/tinyxml2/tinyxml2.h lib/analyzerinfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/utils.h lib/xml.h test/fixture.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testanalyzerinformation.cpp -test/testassert.o: test/testassert.cpp lib/addoninfo.h lib/check.h lib/checkassert.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h +test/testassert.o: test/testassert.cpp lib/check.h lib/checkassert.h lib/checkers.h lib/checkimpl.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testassert.cpp -test/testastutils.o: test/testastutils.cpp lib/addoninfo.h lib/astutils.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h +test/testastutils.o: test/testastutils.cpp lib/astutils.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testastutils.cpp -test/testautovariables.o: test/testautovariables.cpp lib/addoninfo.h lib/check.h lib/checkautovariables.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h +test/testautovariables.o: test/testautovariables.cpp lib/check.h lib/checkautovariables.h lib/checkers.h lib/checkimpl.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testautovariables.cpp -test/testbool.o: test/testbool.cpp lib/addoninfo.h lib/check.h lib/checkbool.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h +test/testbool.o: test/testbool.cpp lib/check.h lib/checkbool.h lib/checkers.h lib/checkimpl.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testbool.cpp -test/testbufferoverrun.o: test/testbufferoverrun.cpp lib/addoninfo.h lib/check.h lib/checkbufferoverrun.h lib/checkers.h lib/color.h lib/config.h lib/ctu.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h +test/testbufferoverrun.o: test/testbufferoverrun.cpp lib/check.h lib/checkbufferoverrun.h lib/checkers.h lib/checkimpl.h lib/color.h lib/config.h lib/ctu.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testbufferoverrun.cpp -test/testcharvar.o: test/testcharvar.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/checkother.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h +test/testcharvar.o: test/testcharvar.cpp lib/check.h lib/checkers.h lib/checkimpl.h lib/checkother.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testcharvar.cpp -test/testcheck.o: test/testcheck.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/utils.h test/fixture.h +test/testcheck.o: test/testcheck.cpp lib/check.h lib/checkers.h lib/checks.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/utils.h test/fixture.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testcheck.cpp -test/testcheckersreport.o: test/testcheckersreport.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/checkersreport.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/utils.h test/fixture.h +test/testcheckersreport.o: test/testcheckersreport.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/checkersreport.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/utils.h test/fixture.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testcheckersreport.cpp -test/testclangimport.o: test/testclangimport.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/clangimport.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h +test/testclangimport.o: test/testclangimport.cpp lib/check.h lib/checkers.h lib/clangimport.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testclangimport.cpp -test/testclass.o: test/testclass.cpp lib/addoninfo.h lib/check.h lib/checkclass.h lib/checkers.h lib/color.h lib/config.h lib/ctu.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h +test/testclass.o: test/testclass.cpp lib/check.h lib/checkclass.h lib/checkers.h lib/checkimpl.h lib/color.h lib/config.h lib/ctu.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testclass.cpp -test/testcmdlineparser.o: test/testcmdlineparser.cpp cli/cmdlinelogger.h cli/cmdlineparser.h lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/suppressions.h lib/timer.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h test/redirect.h +test/testcmdlineparser.o: test/testcmdlineparser.cpp cli/cmdlinelogger.h cli/cmdlineparser.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/regex.h lib/rule.h lib/settings.h lib/standards.h lib/suppressions.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h test/redirect.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testcmdlineparser.cpp -test/testcolor.o: test/testcolor.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/utils.h test/fixture.h +test/testcolor.o: test/testcolor.cpp lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/utils.h test/fixture.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testcolor.cpp -test/testcondition.o: test/testcondition.cpp lib/addoninfo.h lib/check.h lib/checkcondition.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h +test/testcondition.o: test/testcondition.cpp lib/check.h lib/checkcondition.h lib/checkers.h lib/checkimpl.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testcondition.cpp -test/testconstructors.o: test/testconstructors.cpp lib/addoninfo.h lib/check.h lib/checkclass.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h +test/testconstructors.o: test/testconstructors.cpp lib/check.h lib/checkclass.h lib/checkers.h lib/checkimpl.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testconstructors.cpp -test/testcppcheck.o: test/testcppcheck.cpp externals/simplecpp/simplecpp.h lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/preprocessor.h lib/regex.h lib/settings.h lib/standards.h lib/suppressions.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h test/redirect.h +test/testcppcheck.o: test/testcppcheck.cpp externals/simplecpp/simplecpp.h lib/addoninfo.h lib/analyzerinfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/preprocessor.h lib/refthunk.h lib/settings.h lib/standards.h lib/suppressions.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h test/redirect.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testcppcheck.cpp -test/testerrorlogger.o: test/testerrorlogger.cpp externals/tinyxml2/tinyxml2.h lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/suppressions.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/xml.h test/fixture.h test/helpers.h +test/testerrorlogger.o: test/testerrorlogger.cpp externals/tinyxml2/tinyxml2.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/suppressions.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/xml.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testerrorlogger.cpp -test/testexceptionsafety.o: test/testexceptionsafety.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/checkexceptionsafety.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h +test/testexceptionsafety.o: test/testexceptionsafety.cpp lib/check.h lib/checkers.h lib/checkexceptionsafety.h lib/checkimpl.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testexceptionsafety.cpp -test/testexecutor.o: test/testexecutor.cpp cli/executor.h lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/suppressions.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h +test/testexecutor.o: test/testexecutor.cpp cli/executor.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/suppressions.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testexecutor.cpp -test/testfilelister.o: test/testfilelister.cpp cli/filelister.h lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/pathmatch.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/utils.h test/fixture.h +test/testfilelister.o: test/testfilelister.cpp cli/filelister.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/pathmatch.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/utils.h test/fixture.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testfilelister.cpp -test/testfilesettings.o: test/testfilesettings.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/utils.h test/fixture.h +test/testfilesettings.o: test/testfilesettings.cpp lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/utils.h test/fixture.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testfilesettings.cpp -test/testfrontend.o: test/testfrontend.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/utils.h test/fixture.h +test/testfrontend.o: test/testfrontend.cpp lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/utils.h test/fixture.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testfrontend.cpp -test/testfunctions.o: test/testfunctions.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/checkfunctions.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h +test/testfunctions.o: test/testfunctions.cpp lib/check.h lib/checkers.h lib/checkfunctions.h lib/checkimpl.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testfunctions.cpp -test/testgarbage.o: test/testgarbage.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h +test/testgarbage.o: test/testgarbage.cpp lib/check.h lib/checkers.h lib/checks.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testgarbage.cpp -test/testimportproject.o: test/testimportproject.cpp externals/tinyxml2/tinyxml2.h lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/importproject.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/suppressions.h lib/utils.h lib/xml.h test/fixture.h test/redirect.h +test/testimportproject.o: test/testimportproject.cpp lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/importproject.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/suppressions.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h test/redirect.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testimportproject.cpp -test/testincompletestatement.o: test/testincompletestatement.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/checkother.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h +test/testincompletestatement.o: test/testincompletestatement.cpp lib/check.h lib/checkers.h lib/checkimpl.h lib/checkother.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testincompletestatement.cpp -test/testinternal.o: test/testinternal.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/checkinternal.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h +test/testinternal.o: test/testinternal.cpp lib/check.h lib/checkers.h lib/checkimpl.h lib/checkinternal.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testinternal.cpp -test/testio.o: test/testio.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/checkio.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h +test/testio.o: test/testio.cpp lib/check.h lib/checkers.h lib/checkimpl.h lib/checkio.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testio.cpp -test/testleakautovar.o: test/testleakautovar.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/checkleakautovar.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h +test/testleakautovar.o: test/testleakautovar.cpp lib/check.h lib/checkers.h lib/checkimpl.h lib/checkleakautovar.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testleakautovar.cpp -test/testlibrary.o: test/testlibrary.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h +test/testlibrary.o: test/testlibrary.cpp lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testlibrary.cpp -test/testmathlib.o: test/testmathlib.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h +test/testmathlib.o: test/testmathlib.cpp lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testmathlib.cpp -test/testmemleak.o: test/testmemleak.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/checkmemoryleak.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h +test/testmemleak.o: test/testmemleak.cpp lib/check.h lib/checkers.h lib/checkimpl.h lib/checkmemoryleak.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testmemleak.cpp -test/testnullpointer.o: test/testnullpointer.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/checknullpointer.h lib/color.h lib/config.h lib/ctu.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h +test/testnullpointer.o: test/testnullpointer.cpp lib/check.h lib/checkers.h lib/checkimpl.h lib/checknullpointer.h lib/color.h lib/config.h lib/ctu.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testnullpointer.cpp -test/testoptions.o: test/testoptions.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/utils.h test/fixture.h test/options.h +test/testoptions.o: test/testoptions.cpp lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/utils.h test/fixture.h test/options.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testoptions.cpp -test/testother.o: test/testother.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/checkother.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h +test/testother.o: test/testother.cpp lib/check.h lib/checkers.h lib/checkimpl.h lib/checkother.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testother.cpp -test/testpath.o: test/testpath.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h +test/testpath.o: test/testpath.cpp lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testpath.cpp -test/testpathmatch.o: test/testpathmatch.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/pathmatch.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/utils.h test/fixture.h +test/testpathmatch.o: test/testpathmatch.cpp lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/pathmatch.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/utils.h test/fixture.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testpathmatch.cpp -test/testplatform.o: test/testplatform.cpp externals/tinyxml2/tinyxml2.h lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/utils.h lib/xml.h test/fixture.h +test/testplatform.o: test/testplatform.cpp externals/tinyxml2/tinyxml2.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/utils.h lib/xml.h test/fixture.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testplatform.cpp -test/testpostfixoperator.o: test/testpostfixoperator.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/checkpostfixoperator.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h +test/testpostfixoperator.o: test/testpostfixoperator.cpp lib/check.h lib/checkers.h lib/checkimpl.h lib/checkpostfixoperator.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testpostfixoperator.cpp -test/testpreprocessor.o: test/testpreprocessor.cpp externals/simplecpp/simplecpp.h lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/preprocessor.h lib/regex.h lib/settings.h lib/standards.h lib/suppressions.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h +test/testpreprocessor.o: test/testpreprocessor.cpp externals/simplecpp/simplecpp.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/preprocessor.h lib/refthunk.h lib/settings.h lib/standards.h lib/suppressions.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testpreprocessor.cpp -test/testprocessexecutor.o: test/testprocessexecutor.cpp cli/executor.h cli/processexecutor.h lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/suppressions.h lib/timer.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h test/redirect.h +test/testprocessexecutor.o: test/testprocessexecutor.cpp cli/executor.h cli/processexecutor.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/suppressions.h lib/timer.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h test/redirect.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testprocessexecutor.cpp -test/testprogrammemory.o: test/testprogrammemory.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/programmemory.h lib/regex.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h +test/testprogrammemory.o: test/testprogrammemory.cpp lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/programmemory.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testprogrammemory.cpp -test/testregex.o: test/testregex.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/utils.h test/fixture.h +test/testrefthunk.o: test/testrefthunk.cpp lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/utils.h test/fixture.h + $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testrefthunk.cpp + +test/testregex.o: test/testregex.cpp lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/regex.h lib/settings.h lib/standards.h lib/utils.h test/fixture.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testregex.cpp -test/testsarifreport.o: test/testsarifreport.cpp externals/picojson/picojson.h lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/json.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/sarifreport.h lib/settings.h lib/standards.h lib/utils.h test/fixture.h +test/testsarifreport.o: test/testsarifreport.cpp externals/picojson/picojson.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/json.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/sarifreport.h lib/settings.h lib/standards.h lib/utils.h test/fixture.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testsarifreport.cpp -test/testsettings.o: test/testsettings.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/suppressions.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h +test/testsettings.o: test/testsettings.cpp lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/suppressions.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testsettings.cpp -test/testsimplifytemplate.o: test/testsimplifytemplate.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h +test/testsimplifytemplate.o: test/testsimplifytemplate.cpp lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testsimplifytemplate.cpp -test/testsimplifytokens.o: test/testsimplifytokens.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h +test/testsimplifytokens.o: test/testsimplifytokens.cpp lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testsimplifytokens.cpp -test/testsimplifytypedef.o: test/testsimplifytypedef.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h +test/testsimplifytypedef.o: test/testsimplifytypedef.cpp lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testsimplifytypedef.cpp -test/testsimplifyusing.o: test/testsimplifyusing.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h +test/testsimplifyusing.o: test/testsimplifyusing.cpp lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testsimplifyusing.cpp -test/testsingleexecutor.o: test/testsingleexecutor.cpp cli/executor.h cli/singleexecutor.h lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/suppressions.h lib/timer.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h test/redirect.h +test/testsingleexecutor.o: test/testsingleexecutor.cpp cli/executor.h cli/singleexecutor.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/suppressions.h lib/timer.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h test/redirect.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testsingleexecutor.cpp -test/testsizeof.o: test/testsizeof.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/checksizeof.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h +test/testsizeof.o: test/testsizeof.cpp lib/check.h lib/checkers.h lib/checkimpl.h lib/checksizeof.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testsizeof.cpp -test/teststandards.o: test/teststandards.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/utils.h test/fixture.h +test/teststandards.o: test/teststandards.cpp lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/utils.h test/fixture.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/teststandards.cpp -test/teststl.o: test/teststl.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/checkstl.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h +test/teststl.o: test/teststl.cpp lib/check.h lib/checkers.h lib/checkimpl.h lib/checkstl.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/teststl.cpp -test/teststring.o: test/teststring.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/checkstring.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h +test/teststring.o: test/teststring.cpp lib/check.h lib/checkers.h lib/checkimpl.h lib/checkstring.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/teststring.cpp -test/testsummaries.o: test/testsummaries.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/summaries.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h +test/testsummaries.o: test/testsummaries.cpp lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/summaries.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testsummaries.cpp -test/testsuppressions.o: test/testsuppressions.cpp cli/cppcheckexecutor.h cli/executor.h cli/processexecutor.h cli/singleexecutor.h cli/threadexecutor.h lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/suppressions.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h +test/testsuppressions.o: test/testsuppressions.cpp cli/cppcheckexecutor.h cli/executor.h cli/processexecutor.h cli/singleexecutor.h cli/threadexecutor.h lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/suppressions.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testsuppressions.cpp -test/testsymboldatabase.o: test/testsymboldatabase.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h +test/testsymboldatabase.o: test/testsymboldatabase.cpp lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testsymboldatabase.cpp -test/testthreadexecutor.o: test/testthreadexecutor.cpp cli/executor.h cli/threadexecutor.h lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/suppressions.h lib/timer.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h test/redirect.h +test/testthreadexecutor.o: test/testthreadexecutor.cpp cli/executor.h cli/threadexecutor.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/suppressions.h lib/timer.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h test/redirect.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testthreadexecutor.cpp -test/testtimer.o: test/testtimer.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/timer.h lib/utils.h test/fixture.h +test/testtimer.o: test/testtimer.cpp lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/timer.h lib/utils.h test/fixture.h test/redirect.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testtimer.cpp -test/testtoken.o: test/testtoken.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h +test/testtoken.o: test/testtoken.cpp lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testtoken.cpp -test/testtokenize.o: test/testtokenize.cpp externals/simplecpp/simplecpp.h lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/preprocessor.h lib/regex.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h +test/testtokenize.o: test/testtokenize.cpp externals/simplecpp/simplecpp.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/preprocessor.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testtokenize.cpp -test/testtokenlist.o: test/testtokenlist.cpp externals/simplecpp/simplecpp.h lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/preprocessor.h lib/regex.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h +test/testtokenlist.o: test/testtokenlist.cpp externals/simplecpp/simplecpp.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/preprocessor.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testtokenlist.cpp -test/testtokenrange.o: test/testtokenrange.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/tokenrange.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h +test/testtokenrange.o: test/testtokenrange.cpp lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/tokenrange.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testtokenrange.cpp -test/testtype.o: test/testtype.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/checktype.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h +test/testtype.o: test/testtype.cpp lib/check.h lib/checkers.h lib/checkimpl.h lib/checktype.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testtype.cpp -test/testuninitvar.o: test/testuninitvar.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/checkuninitvar.h lib/color.h lib/config.h lib/ctu.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h +test/testuninitvar.o: test/testuninitvar.cpp lib/check.h lib/checkers.h lib/checkimpl.h lib/checkuninitvar.h lib/color.h lib/config.h lib/ctu.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testuninitvar.cpp -test/testunusedfunctions.o: test/testunusedfunctions.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/checkunusedfunctions.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h +test/testunusedfunctions.o: test/testunusedfunctions.cpp lib/check.h lib/checkers.h lib/checkunusedfunctions.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testunusedfunctions.cpp -test/testunusedprivfunc.o: test/testunusedprivfunc.cpp lib/addoninfo.h lib/check.h lib/checkclass.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h +test/testunusedprivfunc.o: test/testunusedprivfunc.cpp lib/check.h lib/checkclass.h lib/checkers.h lib/checkimpl.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testunusedprivfunc.cpp -test/testunusedvar.o: test/testunusedvar.cpp externals/simplecpp/simplecpp.h lib/addoninfo.h lib/check.h lib/checkers.h lib/checkunusedvar.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/preprocessor.h lib/regex.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h +test/testunusedvar.o: test/testunusedvar.cpp externals/simplecpp/simplecpp.h lib/check.h lib/checkers.h lib/checkimpl.h lib/checkunusedvar.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/preprocessor.h lib/refthunk.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testunusedvar.cpp -test/testutils.o: test/testutils.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/utils.h test/fixture.h +test/testutils.o: test/testutils.cpp lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/utils.h test/fixture.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testutils.cpp -test/testvaarg.o: test/testvaarg.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/checkvaarg.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h +test/testvaarg.o: test/testvaarg.cpp lib/check.h lib/checkers.h lib/checkimpl.h lib/checkvaarg.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/tokenize.h lib/tokenlist.h lib/utils.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testvaarg.cpp -test/testvalueflow.o: test/testvalueflow.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h +test/testvalueflow.o: test/testvalueflow.cpp lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testvalueflow.cpp -test/testvarid.o: test/testvarid.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/regex.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h +test/testvarid.o: test/testvarid.cpp lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/refthunk.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testvarid.cpp -test/testvfvalue.o: test/testvfvalue.cpp lib/addoninfo.h lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/regex.h lib/settings.h lib/standards.h lib/utils.h lib/vfvalue.h test/fixture.h +test/testvfvalue.o: test/testvfvalue.cpp lib/check.h lib/checkers.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/refthunk.h lib/settings.h lib/standards.h lib/utils.h lib/vfvalue.h test/fixture.h $(CXX) ${INCLUDE_FOR_TEST} ${CFLAGS_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testvfvalue.cpp externals/simplecpp/simplecpp.o: externals/simplecpp/simplecpp.cpp externals/simplecpp/simplecpp.h diff --git a/addons/README.md b/addons/README.md index 8df8229b970..05502f547b6 100644 --- a/addons/README.md +++ b/addons/README.md @@ -4,19 +4,19 @@ Addons are scripts that analyses Cppcheck dump files to check compatibility with ## Supported addons -+ [misra.py](https://github.com/danmar/cppcheck/blob/main/addons/misra.py) - Used to verify compliance with MISRA C 2012 - a proprietary set of guidelines to avoid such questionable code, developed for embedded systems. Since this standard is proprietary, cppcheck does not display error text by specifying only the number of violated rules (for example, [c2012-21.3]). If you want to display full texts for violated rules, you will need to create a text file containing MISRA rules, which you will have to pass when calling the script with `--rule-texts` key. Some examples of rule texts files available in [tests directory](https://github.com/danmar/cppcheck/blob/main/addons/test/misra/). -+ [y2038.py](https://github.com/danmar/cppcheck/blob/main/addons/y2038.py) - Checks code for [year 2038 problem](https://en.wikipedia.org/wiki/Year_2038_problem) safety. See complete description [here](https://github.com/danmar/cppcheck/blob/main/addons/doc/y2038.md). -+ [threadsafety.py](https://github.com/danmar/cppcheck/blob/main/addons/threadsafety.py) ++ [misra.py](https://github.com/cppcheck-opensource/cppcheck/blob/main/addons/misra.py) + Used to verify compliance with MISRA C 2012 - a proprietary set of guidelines to avoid such questionable code, developed for embedded systems. Since this standard is proprietary, cppcheck does not display error text by specifying only the number of violated rules (for example, [c2012-21.3]). If you want to display full texts for violated rules, you will need to create a text file containing MISRA rules, which you will have to pass when calling the script with `--rule-texts` key. Some examples of rule texts files available in [tests directory](https://github.com/cppcheck-opensource/cppcheck/blob/main/addons/test/misra/). ++ [y2038.py](https://github.com/cppcheck-opensource/cppcheck/blob/main/addons/y2038.py) + Checks code for [year 2038 problem](https://en.wikipedia.org/wiki/Year_2038_problem) safety. See complete description [here](https://github.com/cppcheck-opensource/cppcheck/blob/main/addons/doc/y2038.md). ++ [threadsafety.py](https://github.com/cppcheck-opensource/cppcheck/blob/main/addons/threadsafety.py) Analyse Cppcheck dump files to locate threadsafety issues like static local objects used by multiple threads. -+ [naming.py](https://github.com/danmar/cppcheck/blob/main/addons/naming.py) ++ [naming.py](https://github.com/cppcheck-opensource/cppcheck/blob/main/addons/naming.py) Enforces naming conventions across the code. -+ [namingng.py](https://github.com/danmar/cppcheck/blob/main/addons/namingng.py) ++ [namingng.py](https://github.com/cppcheck-opensource/cppcheck/blob/main/addons/namingng.py) Enforces naming conventions across the code. Enhanced version with support for type prefixes in variable and function names. -+ [findcasts.py](https://github.com/danmar/cppcheck/blob/main/addons/findcasts.py) ++ [findcasts.py](https://github.com/cppcheck-opensource/cppcheck/blob/main/addons/findcasts.py) Locates casts in the code. -+ [misc.py](https://github.com/danmar/cppcheck/blob/main/addons/misc.py) ++ [misc.py](https://github.com/cppcheck-opensource/cppcheck/blob/main/addons/misc.py) Performs miscellaneous checks. ### Other files @@ -68,5 +68,5 @@ This allows you to add additional parameters when calling the script (for exampl When using the graphical interface `cppcheck-gui`, the selection and configuration of addons is carried out on the tab `Addons and tools` in the project settings (`Edit Project File`): -![Screenshot](https://raw.githubusercontent.com/danmar/cppcheck/main/addons/doc/img/cppcheck-gui-addons.png) +![Screenshot](https://raw.githubusercontent.com/cppcheck-opensource/cppcheck/main/addons/doc/img/cppcheck-gui-addons.png) diff --git a/addons/cppcheckdata.py b/addons/cppcheckdata.py index 06028e2005c..4271bd81376 100755 --- a/addons/cppcheckdata.py +++ b/addons/cppcheckdata.py @@ -7,7 +7,11 @@ """ import argparse +import contextlib +import functools +import gc import json +import mmap import os import sys import subprocess @@ -38,6 +42,48 @@ current_dumpfile_suppressions = [] +# Size of the chunks that are fed to the XML parser +_PARSE_CHUNK_SIZE = 1 << 16 + +_DUMP_START_TAG = b'' + + +@contextlib.contextmanager +def _gc_disabled(): + # Loading a dump file creates a lot of objects and none of them become + # garbage. The cyclic garbage collector would traverse them again and + # again, which takes about half of the loading time. + enabled = gc.isenabled() + gc.disable() + try: + yield + finally: + if enabled: + gc.enable() + + +def _feed_without_configurations(parser, filename): + """ + Feed the dump file to the parser, except for the elements. + The configurations are most of the file, and skipping them avoids + parsing the whole file to get the data that is written after them. + A '<' in attribute values is escaped, so the tags are found with a + plain search. + """ + with open(filename, 'rb') as f: + if os.fstat(f.fileno()).st_size == 0: + return + with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as data: + first = data.find(_DUMP_START_TAG) + last = data.rfind(_DUMP_END_TAG) + if first < 0 or last < first: + parser.feed(data[:]) + else: + parser.feed(data[:first]) + parser.feed(data[last + len(_DUMP_END_TAG):]) + + def _load_location(location, element): """Load location from element/dict""" location.file = element.get('file') @@ -1180,6 +1226,155 @@ def __repr__(self): ) +class _DumpInfoBuilder: + """ + XML parser target that loads the data that is not part of a configuration: + the language, platform, raw tokens and suppressions. + """ + + def __init__(self, cppcheck_data): + # Note: a 'data' attribute would be called by the parser for character data + self.cppcheck_data = cppcheck_data + + def start(self, tag, attrib): + data = self.cppcheck_data + if tag == 'tok': + tok = Token(attrib) + tok.file = data.files[int(attrib.get('fileIndex'))] + data.rawTokens.append(tok) + elif tag == 'file': + data.files.append(attrib.get('name')) + elif tag == 'suppression': + data.suppressions.append(Suppression(attrib)) + elif tag == 'platform': + data.platform = Platform(attrib) + elif tag == 'dumps': + data.language = attrib.get('language') + + +class _ConfigurationBuilder: + """ + XML parser target that creates a Configuration for each element. + The objects are created directly from the attributes, no element tree is built. + """ + + def __init__(self): + self.configurations = [] # completed configurations + self.cfg = None + self.cfg_arguments = [] # function arguments for Configuration node initialization + + # Iterating in a . + self.iter_scope_varlist = False + + # Iterating + self.iter_typedef_info = False + + # Iterating + self.iter_directive = False + + def take_configurations(self): + configurations = self.configurations + self.configurations = [] + return configurations + + def start(self, tag, attrib): + # The most common elements are checked first + cfg = self.cfg + + # Parse tokens + if tag == 'token': + if not self.iter_directive and not self.iter_typedef_info: + cfg.tokenlist.append(Token(attrib)) + + # Parse valueflows (list of values) + elif tag == 'value': + cfg.valueflow[-1].values.append(Value(attrib)) + elif tag == 'values': + cfg.valueflow.append(ValueFlow(attrib)) + + # Parse scopes + elif tag == 'scope': + cfg.scopes.append(Scope(attrib)) + elif tag == 'varlist': + self.iter_scope_varlist = True + + # Parse variables + elif tag == 'var': + if self.iter_scope_varlist: + cfg.scopes[-1].varlistId.append(attrib.get('id')) + else: + var = Variable(attrib) + if var.nameTokenId: + cfg.variables.append(var) + else: + self.cfg_arguments.append(var) + + # Parse functions + elif tag == 'function': + cfg.functions.append(Function(attrib, cfg.scopes[-1])) + + # Parse function arguments + elif tag == 'arg': + arg_nr = int(attrib.get('nr')) + arg_variable_id = attrib.get('variable') + cfg.functions[-1].argumentId[arg_nr] = arg_variable_id + + # Parse directives list + elif tag == 'directive': + cfg.directives.append(Directive(attrib)) + self.iter_directive = True + + # Parse macro usage + elif tag == 'macro': + cfg.macro_usage.append(MacroUsage(attrib)) + + # Preprocessor #if/#elif condition + elif tag == 'if-cond': + cfg.preprocessor_if_conditions.append(PreprocessorIfCondition(attrib)) + + # Parse containers + elif tag == 'container': + cfg.containers.append(Container(attrib)) + + # Parse typedef info + elif tag == 'typedef-info': + self.iter_typedef_info = True + elif tag == 'info': + if self.iter_typedef_info: + cfg.typedefInfo.append(TypedefInfo(attrib)) + + # Serialize new configuration node + elif tag == 'dump': + self.cfg = Configuration(attrib.get('cfg')) + + elif tag == 'clang-warning': + cfg.clang_warnings.append({'file': attrib.get('file'), + 'line': int(attrib.get('line')), + 'column': int(attrib.get('column')), + 'message': attrib.get('message')}) + + # Parse standards + elif tag == 'c': + cfg.standards.set_c(attrib) + elif tag == 'cpp': + cfg.standards.set_cpp(attrib) + elif tag == 'posix': + cfg.standards.set_posix(attrib) + + def end(self, tag): + if tag == 'directive': + self.iter_directive = False + elif tag == 'varlist': + self.iter_scope_varlist = False + elif tag == 'typedef-info': + self.iter_typedef_info = False + elif tag == 'dump': + self.cfg.setIdMap(self.cfg_arguments) + self.configurations.append(self.cfg) + self.cfg = None + self.cfg_arguments = [] + + class CppcheckData: """ Class that makes cppcheck dump data available @@ -1191,6 +1386,10 @@ class CppcheckData: suppressions List of Suppressions files Source files for elements occurred in this configuration + To iterate more than once, use the configurations property: it parses the + dump file once and keeps all configurations. To iterate once, use + iterconfigurations(): it parses on each call but uses less memory. + To iterate through all configurations use such code: @code data = cppcheckdata.parsedump(...) @@ -1229,35 +1428,14 @@ def __init__(self, filename): self.platform = None self.suppressions = [] self.files = [] # source files for elements occurred in this configuration + self._configurations = None # set by the configurations property - platform_done = False - rawtokens_done = False - suppressions_done = False - - # Parse general configuration options from node - # We intentionally don't clean node resources here because we - # want to serialize in memory only small part of the XML tree. - for event, node in ElementTree.iterparse(self.filename, events=('start', 'end')): - if platform_done and rawtokens_done and suppressions_done: - break - if node.tag == 'dumps': - self.language = node.get('language') - if node.tag == 'platform' and event == 'start': - self.platform = Platform(node) - platform_done = True - elif node.tag == 'rawtokens' and event == 'end': - for rawtokens_node in node: - if rawtokens_node.tag == 'file': - self.files.append(rawtokens_node.get('name')) - elif rawtokens_node.tag == 'tok': - tok = Token(rawtokens_node) - tok.file = self.files[int(rawtokens_node.get('fileIndex'))] - self.rawTokens.append(tok) - rawtokens_done = True - elif node.tag == 'suppressions' and event == 'end': - for suppressions_node in node: - self.suppressions.append(Suppression(suppressions_node)) - suppressions_done = True + # Parse general configuration options from node, the raw tokens + # and the suppressions. The configurations are parsed by iterconfigurations. + parser = ElementTree.XMLParser(target=_DumpInfoBuilder(self)) + with _gc_disabled(): + _feed_without_configurations(parser, self.filename) + parser.close() global current_dumpfile_suppressions current_dumpfile_suppressions = self.suppressions @@ -1271,151 +1449,37 @@ def __init__(self, filename): def configurations(self): """ Return the list of all available Configuration objects. + The list is loaded on first access and kept for later accesses and + iterconfigurations(). """ - return list(self.iterconfigurations()) + if self._configurations is None: + self._configurations = list(self.iterconfigurations()) + return self._configurations def iterconfigurations(self): """ Create and return iterator for the available Configuration objects. The iterator loops over all Configurations in the dump file tree, in document order. + Uses the kept list if the configurations property has been accessed, + otherwise parses the dump file one configuration at a time. """ - cfg = None - cfg_arguments = [] # function arguments for Configuration node initialization - cfg_function = None - cfg_valueflow = None - - # Iterating in a . - iter_scope_varlist = False - - # Iterating - iter_typedef_info = False - - # Iterating - iter_directive = False - - # Use iterable objects to traverse XML tree for dump files incrementally. - # Iterative approach is required to avoid large memory consumption. - # Calling .clear() is necessary to let the element be garbage collected. - for event, node in ElementTree.iterparse(self.filename, events=('start', 'end')): - # Serialize new configuration node - if node.tag == 'dump': - if event == 'start': - cfg = Configuration(node.get('cfg')) - continue - if event == 'end': - cfg.setIdMap(cfg_arguments) - yield cfg - cfg = None - cfg_arguments = [] - - elif node.tag == 'clang-warning' and event == 'start': - cfg.clang_warnings.append({'file': node.get('file'), - 'line': int(node.get('line')), - 'column': int(node.get('column')), - 'message': node.get('message')}) - # Parse standards - elif node.tag == "standards" and event == 'start': - continue - elif node.tag == 'c' and event == 'start': - cfg.standards.set_c(node) - elif node.tag == 'cpp' and event == 'start': - cfg.standards.set_cpp(node) - elif node.tag == 'posix' and event == 'start': - cfg.standards.set_posix(node) - - # Parse directives list - elif node.tag == 'directive': - if event == 'start': - cfg.directives.append(Directive(node)) - iter_directive = True - elif event == 'end': - iter_directive = False - # Parse macro usage - elif node.tag == 'macro' and event == 'start': - cfg.macro_usage.append(MacroUsage(node)) - - # Preprocessor #if/#elif condition - elif node.tag == "if-cond" and event == 'start': - cfg.preprocessor_if_conditions.append(PreprocessorIfCondition(node)) - - # Parse tokens - elif node.tag == 'tokenlist' and event == 'start': - continue - elif node.tag == 'token' and event == 'start' and not iter_directive and not iter_typedef_info: - cfg.tokenlist.append(Token(node)) - - # Parse scopes - elif node.tag == 'scopes' and event == 'start': - continue - elif node.tag == 'scope' and event == 'start': - cfg.scopes.append(Scope(node)) - elif node.tag == 'varlist': - if event == 'start': - iter_scope_varlist = True - elif event == 'end': - iter_scope_varlist = False - - # Parse functions - elif node.tag == 'functionList' and event == 'start': - continue - elif node.tag == 'function': - if event == 'start': - cfg_function = Function(node, cfg.scopes[-1]) - continue - if event == 'end': - cfg.functions.append(cfg_function) - cfg_function = None - - # Parse function arguments - elif node.tag == 'arg' and event == 'start': - arg_nr = int(node.get('nr')) - arg_variable_id = node.get('variable') - cfg_function.argumentId[arg_nr] = arg_variable_id - - # Parse variables - elif node.tag == 'var' and event == 'start': - if iter_scope_varlist: - cfg.scopes[-1].varlistId.append(node.get('id')) - else: - var = Variable(node) - if var.nameTokenId: - cfg.variables.append(var) - else: - cfg_arguments.append(var) - - # Parse containers - elif node.tag == 'containers' and event == 'start': - continue - elif node.tag == 'container' and event == 'start': - cfg.containers.append(Container(node)) - - # Parse typedef info - elif node.tag == 'typedef-info': - iter_typedef_info = (event == 'start') - elif iter_typedef_info and node.tag == 'info' and event == 'start': - cfg.typedefInfo.append(TypedefInfo(node)) - - # Parse template-token - #elif node.tag == 'TokenAndName' and event == 'start': #todo add processing of containers - # cfg.containers.append(Container(node)) - - # Parse valueflows (list of values) - elif node.tag == 'valueflow' and event == 'start': - continue - elif node.tag == 'values': - if event == 'start': - cfg_valueflow = ValueFlow(node) - continue - if event == 'end': - cfg.valueflow.append(cfg_valueflow) - cfg_valueflow = None - - # Parse values - elif node.tag == 'value' and event == 'start': - cfg_valueflow.values.append(Value(node)) - - # Remove links to the sibling nodes - node.clear() + if self._configurations is not None: + yield from self._configurations + return + # Parse the dump file incrementally to avoid large memory consumption. + # A configuration is returned as soon as its has been parsed. + # The garbage collector is only disabled while parsing, not while the + # caller uses the configuration. + builder = _ConfigurationBuilder() + parser = ElementTree.XMLParser(target=builder) + with open(self.filename, 'rb') as f: + for chunk in iter(functools.partial(f.read, _PARSE_CHUNK_SIZE), b''): + with _gc_disabled(): + parser.feed(chunk) + yield from builder.take_configurations() + with _gc_disabled(): + parser.close() + yield from builder.take_configurations() def __repr__(self): attrs = ["configurations", "platform"] diff --git a/addons/misc.py b/addons/misc.py index 670d6152ff5..5cf073abb72 100644 --- a/addons/misc.py +++ b/addons/misc.py @@ -48,7 +48,7 @@ def isStringLiteral(tokenString): def stringConcatInArrayInit(data): # Get all string macros stringMacros = [] - for cfg in data.iterconfigurations(): + for cfg in data.configurations: for directive in cfg.directives: res = re.match(r'#define[ ]+([A-Za-z0-9_]+)[ ]+".*', directive.str) if res: @@ -76,7 +76,7 @@ def stringConcatInArrayInit(data): def implicitlyVirtual(data): - for cfg in data.iterconfigurations(): + for cfg in data.configurations: for function in cfg.functions: if function.isImplicitlyVirtual is None: continue @@ -85,7 +85,7 @@ def implicitlyVirtual(data): reportError(function.tokenDef, 'style', 'Function \'' + function.name + '\' overrides base class function but is not marked with \'virtual\' keyword.', 'implicitlyVirtual') def ellipsisStructArg(data): - for cfg in data.iterconfigurations(): + for cfg in data.configurations: for tok in cfg.tokenlist: if tok.str != '(': continue diff --git a/addons/misra.py b/addons/misra.py index 43b92b51fb0..7c7774c52e3 100755 --- a/addons/misra.py +++ b/addons/misra.py @@ -538,6 +538,20 @@ def is_source_file(file): return file.endswith('.c') +def has_prior_function_prototype(cfg, function): + """Return whether a global prototype precedes the function definition.""" + for token in cfg.tokenlist: + if token == function.tokenDef: + break + if token.str != function.name or token.scope.type != 'Global': + continue + opening = token.next + if opening and opening.str == '(' and opening.link and \ + opening.link.next and opening.link.next.str == ';': + return True + return False + + def is_header(file): return file.endswith('.h') @@ -1155,8 +1169,14 @@ def numberOfParentheses(tok1, tok2): def findGotoLabel(gotoToken): label = gotoToken.next.str tok = gotoToken.next.next + functionScope = gotoToken.scope + while functionScope and functionScope.type != 'Function': + functionScope = functionScope.nestedIn + functionEnd = functionScope.bodyEnd if functionScope else None while tok: - if tok.str == '}' and tok.scope.type == 'Function': + if tok is functionEnd: + break + if functionEnd is None and tok.str == '}' and tok.scope and tok.scope.type == 'Function': break if tok.str == label and tok.next.str == ':': return tok @@ -2249,6 +2269,8 @@ def misra_8_4(self, cfg): continue if func.token != func.tokenDef: continue + if has_prior_function_prototype(cfg, func): + continue if func.tokenDef.str == 'main': continue self.reportError(func.tokenDef, 8, 4) @@ -3498,8 +3520,9 @@ def misra_17_3(self, cfg): # Additional check for implicit function calls in expressions for token in cfg.tokenlist: - if token.isName and token.function is None and token.valueType is None: - if token.next and token.next.str == "(" and token.next.valueType is None: + if token.isName and token.scope.type != 'Global' and token.function is None and token.valueType is None: + if token.next and token.next.str == "(" and token.next.valueType is None and \ + isFunctionCall(token.next, cfg.standards.c): if token.next.next.str == "*" and \ token.next.next.next.isName and token.next.next.next.valueType is not None and \ token.next.next.next.valueType.pointer > 0 : diff --git a/addons/test/misra/misra-regression-prototypes.c b/addons/test/misra/misra-regression-prototypes.c new file mode 100644 index 00000000000..11ce35c9fa0 --- /dev/null +++ b/addons/test/misra/misra-regression-prototypes.c @@ -0,0 +1,13 @@ +int regression_function(int value); + +int regression_function(int value) +{ + return value; +} + +int main(void) +{ + int local_prototype(int value); + int (*callback)(int) = regression_function; + return callback(0); +} \ No newline at end of file diff --git a/addons/test/misra/misra-test.c b/addons/test/misra/misra-test.c index 6b6f52342aa..2871fd871d4 100644 --- a/addons/test/misra/misra-test.c +++ b/addons/test/misra/misra-test.c @@ -1464,6 +1464,23 @@ static void misra_15_3(int a) { } } +struct misra_15_2_s { + int a; + int b; +}; + +static void misra_15_2_initializer_list(void) { + int err = 0; + if (err == 0) { + err = 1; + goto done; // 15.1 + } + struct misra_15_2_s s = { 1, 2 }; + (void)s; +done: + return; +} + static void misra_15_4(void) { misra_15_4_label: return; diff --git a/addons/test/misra_test.py b/addons/test/misra_test.py index 55a24cb4b27..dc46d8beef8 100644 --- a/addons/test/misra_test.py +++ b/addons/test/misra_test.py @@ -11,6 +11,7 @@ from addons.misra import C11_STDLIB_IDENTIFIERS, C99_STDLIB_IDENTIFIERS,C90_STDLIB_IDENTIFIERS, isStdLibId, isKeyword TEST_SOURCE_FILES = [os.path.join('addons','test','misra','misra-test.c')] +REGRESSION_SOURCE_FILE = os.path.join('addons', 'test', 'misra', 'misra-regression-prototypes.c') def remove_misra_config(s:str): @@ -93,6 +94,20 @@ def test_json_out(checker, capsys, test_files): assert("Required" in json_output['c2012-21.3'][0]['extra']) assert("Advisory" in json_output['c2012-20.1'][0]['extra']) +def test_function_prototype_and_pointer_call_regressions(checker, capsys): + dump_create(REGRESSION_SOURCE_FILE) + sys.argv.append("--cli") + try: + checker.loadRuleTexts("./addons/test/misra/misra_rules_dummy.txt") + checker.parseDump(REGRESSION_SOURCE_FILE + ".dump") + captured = capsys.readouterr().out + json_output = convert_json_output(captured.splitlines()) + assert "c2012-8.4" not in json_output + assert "c2012-17.3" not in json_output + finally: + sys.argv.remove("--cli") + dump_remove(REGRESSION_SOURCE_FILE) + def test_rules_cppcheck_severity(checker, capsys, test_files): checker.loadRuleTexts("./addons/test/misra/misra_rules_dummy.txt") diff --git a/addons/threadsafety.py b/addons/threadsafety.py index 9475e1a5b0d..25b2c16abe3 100755 --- a/addons/threadsafety.py +++ b/addons/threadsafety.py @@ -91,7 +91,6 @@ 'race:stdin', 'race:stdout', 'race:streams', - 'race:strerror', 'race:strsignal', 'race:strtok', 'race:tmbuf', @@ -111,6 +110,8 @@ 'cuserid', 'drand48', 'ecvt', + 'elf_fill', + 'elf_flagelf', 'encrypt', 'endfsent', 'endgrent', @@ -259,7 +260,6 @@ 'siginterrupt', 'sleep', 'srand48', - 'strerror', 'strsignal', 'strtok', 'tmpnam', diff --git a/cfg/boost.cfg b/cfg/boost.cfg index 722c4c83cd7..b09d9ccd9e5 100644 --- a/cfg/boost.cfg +++ b/cfg/boost.cfg @@ -83,12 +83,12 @@ - + - + - - + + diff --git a/cfg/bsd.cfg b/cfg/bsd.cfg index 158d7a8a885..5888327cc3c 100644 --- a/cfg/bsd.cfg +++ b/cfg/bsd.cfg @@ -443,7 +443,7 @@ false - + diff --git a/cfg/gnu.cfg b/cfg/gnu.cfg index 1c8d1597a7f..ee0f4c8790c 100644 --- a/cfg/gnu.cfg +++ b/cfg/gnu.cfg @@ -168,6 +168,322 @@ + + + + + false + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + false + + + + + + + + + + + @@ -241,22 +557,100 @@ true + + + + + - - + + + + + + + false + + + + + + + + - + + + + + + false + + + 1: - + + + + + + false + + + + + + + + + + + + false + + + + + + + + + + + + + + + + + + + + + + + + + + false + + + + + + + + + @@ -265,12 +659,12 @@ - + - + @@ -1597,7 +1991,7 @@ false - + diff --git a/cfg/gtk.cfg b/cfg/gtk.cfg index 9b89a069903..024f567c922 100644 --- a/cfg/gtk.cfg +++ b/cfg/gtk.cfg @@ -4,16 +4,30 @@ - - - - + + + + + + + + + + + + + + + + + + @@ -38,33 +52,39 @@ - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - - - - - @@ -78,6 +98,7 @@ + @@ -112,6 +133,7 @@ + @@ -234,6 +256,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + g_thread_new g_thread_try_new @@ -807,7 +909,6 @@ g_app_info_get_default_for_uri_scheme g_application_new g_application_get_dbus_connection - g_application_get_default g_buffered_input_stream_new g_buffered_output_stream_new g_cancellable_new @@ -910,7 +1011,6 @@ g_zlib_decompressor_new g_object_ref g_object_unref - gtk_widget_destroy g_tree_new @@ -926,6 +1026,17 @@ g_file_attribute_matcher_ref g_file_attribute_matcher_unref + + gtk_window_new + gtk_widget_destroy + gtk_window_destroy + + + + false + + + true @@ -2148,6 +2259,7 @@ + false @@ -3179,8 +3291,7 @@ - - + @@ -3193,7 +3304,7 @@ - + @@ -5280,11 +5391,13 @@ + + @@ -6483,6 +6596,7 @@ + false @@ -9688,6 +9802,14 @@ false + + + false + + + + false @@ -21130,10 +21252,6 @@ false - - - false - false @@ -21790,6 +21908,13 @@ false + + false + + + + + false @@ -22967,6 +23092,13 @@ + + + + + + + diff --git a/cfg/libcurl.cfg b/cfg/libcurl.cfg index 381fbb237d7..949b6de0282 100644 --- a/cfg/libcurl.cfg +++ b/cfg/libcurl.cfg @@ -167,7 +167,7 @@ - + diff --git a/cfg/microsoft_gsl.cfg b/cfg/microsoft_gsl.cfg new file mode 100644 index 00000000000..07fbfcb74a6 --- /dev/null +++ b/cfg/microsoft_gsl.cfg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/cfg/posix.cfg b/cfg/posix.cfg index 751142bacc1..19c5f50c4fa 100644 --- a/cfg/posix.cfg +++ b/cfg/posix.cfg @@ -2123,11 +2123,13 @@ The function 'mktemp' is considered to be dangerous due to race conditions and s + + @@ -2146,11 +2148,13 @@ The function 'mktemp' is considered to be dangerous due to race conditions and s + + @@ -2468,7 +2472,7 @@ The function 'mktemp' is considered to be dangerous due to race conditions and s false - + @@ -2500,7 +2504,7 @@ The function 'mktemp' is considered to be dangerous due to race conditions and s false - + @@ -4200,7 +4204,6 @@ The function 'mktemp' is considered to be dangerous due to race conditions and s - @@ -4600,6 +4603,141 @@ The function 'mktemp' is considered to be dangerous due to race conditions and s + + + false + + + + + + + + + + + + + false + + + + + + + + + + + + + + + + + false + + + + + + + + + + + false + + + + + + + + false + + + + + + + + + + + false + + + + + + + + + false + + + + + + + + false + + + + + + + + + + + + false + + + + + + + + 0:2 + + + + + false + + + + 0:2 + + + + + + + + false + + + + 0:2 + + + + + + + + false + + + + + + @@ -5470,6 +5608,7 @@ The function 'mktemp' is considered to be dangerous due to race conditions and s + @@ -5646,6 +5785,7 @@ The function 'mktemp' is considered to be dangerous due to race conditions and s + @@ -6437,6 +6577,32 @@ The function 'mktemp' is considered to be dangerous due to race conditions and s + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cfg/qt.cfg b/cfg/qt.cfg index 5371e598568..95dc022cf57 100644 --- a/cfg/qt.cfg +++ b/cfg/qt.cfg @@ -410,7 +410,6 @@ - @@ -2428,6 +2427,7 @@ false + @@ -2440,6 +2440,7 @@ false + @@ -5519,7 +5520,7 @@ - + diff --git a/cfg/std.cfg b/cfg/std.cfg index e282dd99995..fc6a6c4d0f8 100644 --- a/cfg/std.cfg +++ b/cfg/std.cfg @@ -3461,7 +3461,7 @@ The obsolete function 'gets' is called. With 'gets' you'll get a buffer overrun - + arg1<arg2?1:0 @@ -3474,8 +3474,21 @@ The obsolete function 'gets' is called. With 'gets' you'll get a buffer overrun + + + + arg1<arg2?1:0 + false + + + + + + + + - + arg1 <= arg2?1:0 @@ -3488,8 +3501,21 @@ The obsolete function 'gets' is called. With 'gets' you'll get a buffer overrun + + + + arg1 <= arg2?1:0 + false + + + + + + + + - + (arg1<arg2 || arg1>arg2)?1:0 @@ -3503,6 +3529,20 @@ The obsolete function 'gets' is called. With 'gets' you'll get a buffer overrun + + + + (arg1<arg2 || arg1>arg2)?1:0 + + false + + + + + + + + @@ -3547,7 +3587,7 @@ The obsolete function 'gets' is called. With 'gets' you'll get a buffer overrun - + @@ -3558,12 +3598,23 @@ The obsolete function 'gets' is called. With 'gets' you'll get a buffer overrun + + + + + false + + + + + + - + @@ -3574,12 +3625,23 @@ The obsolete function 'gets' is called. With 'gets' you'll get a buffer overrun + + + + + false + + + + + + - + @@ -3594,6 +3656,21 @@ The obsolete function 'gets' is called. With 'gets' you'll get a buffer overrun + + + + + false + + + + + + + + + + @@ -3928,7 +4005,7 @@ The obsolete function 'gets' is called. With 'gets' you'll get a buffer overrun - + @@ -3974,12 +4051,12 @@ The obsolete function 'gets' is called. With 'gets' you'll get a buffer overrun - + - + @@ -3989,7 +4066,7 @@ The obsolete function 'gets' is called. With 'gets' you'll get a buffer overrun - + arg1 false @@ -4010,18 +4087,18 @@ The obsolete function 'gets' is called. With 'gets' you'll get a buffer overrun - + arg1 false - + - + @@ -4056,7 +4133,7 @@ The obsolete function 'gets' is called. With 'gets' you'll get a buffer overrun false - + arg1 @@ -4077,17 +4154,17 @@ The obsolete function 'gets' is called. With 'gets' you'll get a buffer overrun false - + arg1 - + - + @@ -4118,7 +4195,7 @@ The obsolete function 'gets' is called. With 'gets' you'll get a buffer overrun - + arg1 false @@ -4136,12 +4213,12 @@ The obsolete function 'gets' is called. With 'gets' you'll get a buffer overrun - + arg1 false - + @@ -4411,7 +4488,7 @@ The obsolete function 'gets' is called. With 'gets' you'll get a buffer overrun - + @@ -4444,7 +4521,7 @@ The obsolete function 'gets' is called. With 'gets' you'll get a buffer overrun - + @@ -4671,7 +4748,7 @@ The obsolete function 'gets' is called. With 'gets' you'll get a buffer overrun - + @@ -4681,6 +4758,16 @@ The obsolete function 'gets' is called. With 'gets' you'll get a buffer overrun + + + + + false + + + + + false @@ -4812,7 +4899,7 @@ The obsolete function 'gets' is called. With 'gets' you'll get a buffer overrun - + arg1 false @@ -4828,9 +4915,9 @@ The obsolete function 'gets' is called. With 'gets' you'll get a buffer overrun - + - + arg1 false @@ -4861,7 +4948,7 @@ The obsolete function 'gets' is called. With 'gets' you'll get a buffer overrun false - + @@ -4942,7 +5029,7 @@ The obsolete function 'gets' is called. With 'gets' you'll get a buffer overrun - + arg1 false @@ -5044,7 +5131,7 @@ The obsolete function 'gets' is called. With 'gets' you'll get a buffer overrun false - + @@ -5057,7 +5144,7 @@ The obsolete function 'gets' is called. With 'gets' you'll get a buffer overrun - + arg1 false @@ -5123,10 +5210,10 @@ The obsolete function 'gets' is called. With 'gets' you'll get a buffer overrun - + false - + arg1 @@ -5233,7 +5320,6 @@ The obsolete function 'gets' is called. With 'gets' you'll get a buffer overrun - @@ -5373,7 +5459,7 @@ The obsolete function 'gets' is called. With 'gets' you'll get a buffer overrun - + arg1 false @@ -5433,7 +5519,7 @@ The obsolete function 'gets' is called. With 'gets' you'll get a buffer overrun false - + @@ -8343,9 +8429,7 @@ initializer list (7) string& replace (const_iterator i1, const_iterator i2, init - - - + false @@ -8814,6 +8898,12 @@ initializer list (7) string& replace (const_iterator i1, const_iterator i2, init + + + + true + + malloc,std::malloc calloc,std::calloc diff --git a/cfg/windows.cfg b/cfg/windows.cfg index 6a0bd111afa..184bb225db3 100644 --- a/cfg/windows.cfg +++ b/cfg/windows.cfg @@ -1331,6 +1331,11 @@ AllocateAndInitializeSid FreeSid + + _create_locale + _wcreate_locale + _free_locale + + + + false + + + + + + + + + + + false + + + + + + + + + + false + + + + + arg1 diff --git a/clang-tidy.md b/clang-tidy.md index f00a39cde12..fe6a6eb7140 100644 --- a/clang-tidy.md +++ b/clang-tidy.md @@ -138,7 +138,6 @@ Does not improve the readability. To be evaluated (need to remove exclusion). `cppcoreguidelines-missing-std-forward`
-`cppcoreguidelines-avoid-const-or-ref-data-members`
`cppcoreguidelines-macro-usage`
`cppcoreguidelines-pro-type-member-init`
`cppcoreguidelines-prefer-member-initializer`
diff --git a/cli/CMakeLists.txt b/cli/CMakeLists.txt index f63f3291849..52103bdb8ec 100644 --- a/cli/CMakeLists.txt +++ b/cli/CMakeLists.txt @@ -40,27 +40,32 @@ if (BUILD_CLI) endif() install(TARGETS cppcheck - RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT applications) install(PROGRAMS ${CMAKE_SOURCE_DIR}/htmlreport/cppcheck-htmlreport - DESTINATION ${CMAKE_INSTALL_FULL_BINDIR} + DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT applications) + # TODO: leverage CMAKE_INSTALL_DATAROOTDIR (share)? + install(FILES ${addons_py} - DESTINATION ${FILESDIR_DEF}/addons + DESTINATION ${FILESDIR_INSTALL}/addons COMPONENT headers) install(FILES ${addons_json} - DESTINATION ${FILESDIR_DEF}/addons + DESTINATION ${FILESDIR_INSTALL}/addons COMPONENT headers) install(FILES ${cfgs} - DESTINATION ${FILESDIR_DEF}/cfg + DESTINATION ${FILESDIR_INSTALL}/cfg COMPONENT headers) install(FILES ${platforms} - DESTINATION ${FILESDIR_DEF}/platforms + DESTINATION ${FILESDIR_INSTALL}/platforms COMPONENT headers) + # TODO: install manpage into CMAKE_INSTALL_MANDIR + # TODO: install documentation into CMAKE_INSTALL_DOCDIR? + endif() diff --git a/cli/cmdlineparser.cpp b/cli/cmdlineparser.cpp index 552af42ab71..842d597b0a4 100644 --- a/cli/cmdlineparser.cpp +++ b/cli/cmdlineparser.cpp @@ -20,6 +20,7 @@ #include "addoninfo.h" #include "check.h" +#include "checks.h" #include "checkers.h" #include "color.h" #include "config.h" @@ -36,7 +37,6 @@ #include "settings.h" #include "standards.h" #include "suppressions.h" -#include "timer.h" #include "utils.h" #include "frontend.h" @@ -50,7 +50,6 @@ #include #include #include -#include #include #include #include @@ -58,9 +57,12 @@ #ifdef HAVE_RULES #include "regex.h" +#include "rule.h" // xml is used for rules #include "xml.h" + +#include #endif static bool addFilesToList(const std::string& fileList, std::vector& pathNames) @@ -360,9 +362,9 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a if (std::strcmp(argv[i], "--doc") == 0) { std::ostringstream doc; // Get documentation.. - for (const Check * it : Check::instances()) { - const std::string& name(it->name()); - const std::string info(it->classInfo()); + for (const Check * const c : CheckInstances::get()) { + const std::string& name(c->name()); + const std::string info(c->classInfo()); if (!name.empty() && !info.empty()) doc << "## " << name << " ##\n" << info << "\n"; @@ -459,6 +461,11 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a mSettings.quiet = true; } + // -CC + else if (std::strcmp(argv[i], "-CC") == 0) { + mSettings.keepComments = true; + } + // Include paths else if (std::strncmp(argv[i], "-I", 2) == 0) { std::string path; @@ -632,6 +639,9 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a else if (std::strcmp(argv[i], "--debug-ignore") == 0) mSettings.debugignore = true; + else if (std::strcmp(argv[i], "--debug-ipc") == 0) + mSettings.debugipc = true; + // Show --debug output after the first simplifications else if (std::strcmp(argv[i], "--debug") == 0 || std::strcmp(argv[i], "--debug-normal") == 0) @@ -1149,7 +1159,15 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a mLogger.printError("invalid --premium option '" + (p2.empty() ? p : p2) + "'."); return Result::Fail; } - mSettings.premiumArgs += "--" + p; + if (p2 == "cert-c-int-precision") { + int tmp; + if (!parseNumberArg(argv[i], 31, tmp, true)) + return Result::Fail; + } + if (p.find(' ') != std::string::npos) + mSettings.premiumArgs += "\"--" + p + "\""; + else + mSettings.premiumArgs += "--" + p; if (isCodingStandard) { // All checkers related to the coding standard should be enabled. The coding standards // do not all undefined behavior or portability issues. @@ -1295,7 +1313,7 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a // Rule given at command line else if (std::strncmp(argv[i], "--rule=", 7) == 0) { #ifdef HAVE_RULES - Settings::Rule rule; + Rule rule; rule.pattern = 7 + argv[i]; if (rule.pattern.empty()) { @@ -1331,7 +1349,8 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a if (node && strcmp(node->Value(), "rules") == 0) node = node->FirstChildElement("rule"); for (; node && strcmp(node->Value(), "rule") == 0; node = node->NextSiblingElement()) { - Settings::Rule rule; + Rule rule; + Regex::Engine regexEngine = Regex::Engine::Pcre; for (const tinyxml2::XMLElement *subnode = node->FirstChildElement(); subnode; subnode = subnode->NextSiblingElement()) { const char * const subname = subnode->Name(); @@ -1364,7 +1383,7 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a else if (std::strcmp(subname, "engine") == 0) { const char * const engine = empty_if_null(subtext); if (std::strcmp(engine, "pcre") == 0) { - rule.engine = Regex::Engine::Pcre; + regexEngine = Regex::Engine::Pcre; } else { mLogger.printError(std::string("unknown regex engine '") + engine + "'."); @@ -1398,7 +1417,7 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a } std::string regex_err; - auto regex = Regex::create(rule.pattern, rule.engine, regex_err); + auto regex = Regex::create(rule.pattern, regexEngine, regex_err); if (!regex) { mLogger.printError("unable to load rule-file '" + ruleFile + "' - pattern '" + rule.pattern + "' failed to compile (" + regex_err + ")."); return Result::Fail; @@ -1430,17 +1449,17 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a else if (std::strncmp(argv[i], "--showtime=", 11) == 0) { const std::string showtimeMode = argv[i] + 11; if (showtimeMode == "file") - mSettings.showtime = ShowTime::FILE; + mSettings.showtime = Settings::ShowTime::FILE; else if (showtimeMode == "file-total") - mSettings.showtime = ShowTime::FILE_TOTAL; + mSettings.showtime = Settings::ShowTime::FILE_TOTAL; else if (showtimeMode == "summary") - mSettings.showtime = ShowTime::SUMMARY; + mSettings.showtime = Settings::ShowTime::SUMMARY; else if (showtimeMode == "top5_file") - mSettings.showtime = ShowTime::TOP5_FILE; + mSettings.showtime = Settings::ShowTime::TOP5_FILE; else if (showtimeMode == "top5_summary") - mSettings.showtime = ShowTime::TOP5_SUMMARY; + mSettings.showtime = Settings::ShowTime::TOP5_SUMMARY; else if (showtimeMode == "none") - mSettings.showtime = ShowTime::NONE; + mSettings.showtime = Settings::ShowTime::NONE; else if (showtimeMode.empty()) { mLogger.printError("no mode provided for --showtime"); return Result::Fail; @@ -1723,6 +1742,12 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a if (mSettings.basePaths.empty() && mSettings.relativePaths) mSettings.basePaths = mPathNames; + if (mSettings.keepComments && !mSettings.preprocessOnly) { + mLogger.printError("-CC may only be used on conjunciton with -E"); + return Result::Fail; + } + + return Result::Success; } @@ -1746,6 +1771,7 @@ void CmdLineParser::printHelp() const " addon json files or through this command line option.\n" " If not present, Cppcheck will try \"python3\" first and\n" " then \"python\".\n" + " -CC Do not remove comments during preprocessing (requires -E).\n" " --cppcheck-build-dir=\n" " Cppcheck work folder. Advantages:\n" " * whole program analysis\n" diff --git a/cli/cmdlineparser.h b/cli/cmdlineparser.h index 54e7ae4b0aa..e712c3035ff 100644 --- a/cli/cmdlineparser.h +++ b/cli/cmdlineparser.h @@ -56,6 +56,8 @@ class CmdLineParser { * @param suppressions Suppressions instance that keeps the suppressions */ CmdLineParser(CmdLineLogger &logger, Settings &settings, Suppressions &suppressions); + CmdLineParser(const CmdLineParser &) = delete; + CmdLineParser &operator=(const CmdLineParser &) = delete; enum class Result : std::uint8_t { Success, Exit, Fail }; @@ -152,10 +154,10 @@ class CmdLineParser { void outputFormatOptionMixingError() const; - CmdLineLogger &mLogger; + CmdLineLogger& mLogger; - Settings &mSettings; - Suppressions &mSuppressions; + Settings& mSettings; + Suppressions& mSuppressions; protected: std::vector mPathNames; diff --git a/cli/cppcheckexecutor.cpp b/cli/cppcheckexecutor.cpp index 69f3e842785..82d4d70c0a0 100644 --- a/cli/cppcheckexecutor.cpp +++ b/cli/cppcheckexecutor.cpp @@ -56,6 +56,7 @@ #include #include #include +#include #include #include #include @@ -267,8 +268,9 @@ int CppCheckExecutor::check(int argc, const char* const argv[]) return EXIT_SUCCESS; } - TimerResults overallTimerResults; - Timer realTimeClock("Overall time", settings.showtime, &overallTimerResults, Timer::Type::OVERALL); + std::unique_ptr overallTimer; + if (settings.showtime == Settings::ShowTime::SUMMARY || settings.showtime == Settings::ShowTime::TOP5_SUMMARY) + overallTimer.reset(new OneShotTimer("Overall time")); settings.loadSummaries(); @@ -277,9 +279,6 @@ int CppCheckExecutor::check(int argc, const char* const argv[]) const int ret = check_wrapper(settings, supprs); - realTimeClock.stop(); - overallTimerResults.showResults(settings.showtime, false, true); - return ret; } @@ -422,7 +421,9 @@ bool CppCheckExecutor::reportUnmatchedSuppressions(const Settings &settings, con int CppCheckExecutor::check_internal(const Settings& settings, Suppressions& supprs) const { StdLogger stdLogger(settings); - TimerResults timerResults; + std::unique_ptr timerResults; + if (settings.showtime != Settings::ShowTime::NONE) + timerResults.reset(new TimerResults); if (settings.reportProgress >= 0) stdLogger.resetLatestProgressOutputTime(); @@ -443,31 +444,35 @@ int CppCheckExecutor::check_internal(const Settings& settings, Suppressions& sup if (!settings.checkersReportFilename.empty()) std::remove(settings.checkersReportFilename.c_str()); - CppCheck cppcheck(settings, supprs, stdLogger, &timerResults, true, executeCommand); + CppCheck cppcheck(settings, supprs, stdLogger, timerResults.get(), true, executeCommand); unsigned int returnValue = 0; if (settings.useSingleJob()) { // Single process - SingleExecutor executor(cppcheck, mFiles, mFileSettings, settings, supprs, stdLogger, &timerResults); + SingleExecutor executor(cppcheck, mFiles, mFileSettings, settings, supprs, stdLogger, timerResults.get()); returnValue = executor.check(); } else { #if defined(HAS_THREADING_MODEL_THREAD) if (settings.executor == Settings::ExecutorType::Thread) { - ThreadExecutor executor(mFiles, mFileSettings, settings, supprs, stdLogger, &timerResults, CppCheckExecutor::executeCommand); + ThreadExecutor executor(mFiles, mFileSettings, settings, supprs, stdLogger, timerResults.get(), CppCheckExecutor::executeCommand); returnValue = executor.check(); } #endif #if defined(HAS_THREADING_MODEL_FORK) if (settings.executor == Settings::ExecutorType::Process) { - ProcessExecutor executor(mFiles, mFileSettings, settings, supprs, stdLogger, &timerResults, CppCheckExecutor::executeCommand); + ProcessExecutor executor(mFiles, mFileSettings, settings, supprs, stdLogger, timerResults.get(), CppCheckExecutor::executeCommand); returnValue = executor.check(); } #endif } // TODO: show time *after* the whole program analysis - if (settings.showtime == ShowTime::SUMMARY || settings.showtime == ShowTime::TOP5_SUMMARY) - timerResults.showResults(settings.showtime); + if (timerResults) { + if (settings.showtime == Settings::ShowTime::SUMMARY) + timerResults->showResults(); + else if (settings.showtime == Settings::ShowTime::TOP5_SUMMARY) + timerResults->showResults(5); + } // TODO: is this run again instead of using previously cached results? returnValue |= cppcheck.analyseWholeProgram(settings.buildDir, mFiles, mFileSettings, stdLogger.getCtuInfo()); @@ -496,6 +501,7 @@ int CppCheckExecutor::check_internal(const Settings& settings, Suppressions& sup void StdLogger::writeCheckersReport(const Suppressions& supprs) { + // TODO: only necessary when we actually issue a checkers report? if (!mCheckersFile.empty()) { std::ofstream fout(mCheckersFile); @@ -505,15 +511,14 @@ void StdLogger::writeCheckersReport(const Suppressions& supprs) } } - const bool summary = mSettings.safety || mSettings.severity.isEnabled(Severity::information); - const bool xmlReport = mSettings.outputFormat == Settings::OutputFormat::xml && mSettings.xml_version == 3; - const bool textReport = !mSettings.checkersReportFilename.empty(); + bool summary, xmlReport, textReport; - if (!summary && !xmlReport && !textReport) + if (!mSettings.collectLogCheckers(&summary, &xmlReport, &textReport)) return; CheckersReport checkersReport(mSettings, mActiveCheckers); + // TODO: include in summary boolean const auto& suppressions = supprs.nomsg.getSuppressions(); const bool summarySuppressed = std::any_of(suppressions.cbegin(), suppressions.cend(), [](const SuppressionList::Suppression& s) { return s.errorId == "checkersReport"; @@ -656,8 +661,13 @@ void StdLogger::reportErr(const ErrorMessage &msg) msgCopy.classification = getClassification(msgCopy.guideline, mSettings.reportType); // TODO: there should be no need for verbose and default messages here + // Don't perform redundant reads for these formats, the code is not needed + // for deduplication + const bool noCode = mSettings.outputFormat == Settings::OutputFormat::xml || + mSettings.outputFormat == Settings::OutputFormat::sarif; const std::string msgStr = - msgCopy.toString(mSettings.verbose, mSettings.templateFormat, mSettings.templateLocation); + msgCopy.toString(mSettings.verbose, mSettings.templateFormat, + mSettings.templateLocation, noCode); // Alert only about unique errors if (!mSettings.emitDuplicates && !mShownErrors.insert(msgStr).second) diff --git a/cli/filelister.cpp b/cli/filelister.cpp index 65a77ea6306..1bc03017e79 100644 --- a/cli/filelister.cpp +++ b/cli/filelister.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -168,6 +168,10 @@ std::string FileLister::addFiles(std::list &files, const std::s if (path.empty()) return "no path specified"; + const std::string::size_type nulPos = path.find('\0'); + if (nulPos != std::string::npos) + return "path '" + path.substr(0, nulPos) + "' contains a NUL character"; + std::list filesSorted; std::string err = addFiles2(filesSorted, path, extra, recursive, ignored, debug); @@ -285,6 +289,10 @@ std::string FileLister::addFiles(std::list &files, const std::s if (path.empty()) return "no path specified"; + const std::string::size_type nulPos = path.find('\0'); + if (nulPos != std::string::npos) + return "path '" + path.substr(0, nulPos) + "' contains a NUL character"; + std::string corrected_path = path; if (endsWith(corrected_path, '/')) corrected_path.erase(corrected_path.end() - 1); diff --git a/cli/main.cpp b/cli/main.cpp index 9bea4336c84..c7949f4955f 100644 --- a/cli/main.cpp +++ b/cli/main.cpp @@ -20,7 +20,7 @@ /** * * @mainpage Cppcheck - * @version 2.20.99 + * @version 2.22.99 * * @section overview_sec Overview * Cppcheck is a simple tool for static analysis of C/C++ code. diff --git a/cli/processexecutor.cpp b/cli/processexecutor.cpp index 11f920db13c..f0f01bcffe7 100644 --- a/cli/processexecutor.cpp +++ b/cli/processexecutor.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -44,9 +45,9 @@ #include #include #include +#include #include #include -#include #include #include #include @@ -78,9 +79,9 @@ ProcessExecutor::ProcessExecutor(const std::list &files, const namespace { class PipeWriter : public ErrorLogger { public: - enum PipeSignal : std::uint8_t {REPORT_OUT='1',REPORT_ERROR='2',REPORT_SUPPR_INLINE='3',REPORT_SUPPR='4',CHILD_END='5',REPORT_METRIC='6'}; + enum PipeSignal : std::uint8_t {REPORT_OUT='1',REPORT_ERROR='2',REPORT_SUPPR_INLINE='3',REPORT_SUPPR='4',CHILD_END='5',REPORT_METRIC='6',REPORT_TIMER='7'}; - explicit PipeWriter(int pipe) : mWpipe(pipe) {} + explicit PipeWriter(int pipe, bool debug) : mWpipe(pipe), mDebug(debug) {} void reportOut(const std::string &outmsg, Color c) override { writeToPipe(REPORT_OUT, static_cast(c) + outmsg); @@ -95,7 +96,7 @@ namespace { { if (suppr.isInline) writeToPipe(REPORT_SUPPR_INLINE, suppressionToString(suppr)); - else if (suppr.checked) + else if (suppr.checked && suppr.hash == 0) writeToPipe(REPORT_SUPPR, suppressionToString(suppr)); } } @@ -104,6 +105,18 @@ namespace { writeToPipe(REPORT_METRIC, metric); } + void writeTimer(const TimerResults* timerResults) const { + if (!timerResults) + return; + + for (const auto& entry : timerResults->getResults()) + { + for (const auto& d : entry.second) { + writeToPipe(REPORT_TIMER, entry.first + ";" + std::to_string(d.count())); + } + } + } + void writeEnd(const std::string& str) const { writeToPipe(CHILD_END, str); } @@ -141,6 +154,9 @@ namespace { void writeToPipe(PipeSignal type, const std::string &data) const { + if (mDebug) + std::cout << "writeToPipe - " << static_cast(type) << " - " << data << std::endl; + { const auto t = static_cast(type); writeToPipeInternal(type, &t, 1); @@ -156,7 +172,8 @@ namespace { writeToPipeInternal(type, data.c_str(), len); } - const int mWpipe; + int mWpipe; + bool mDebug; }; } @@ -188,7 +205,8 @@ bool ProcessExecutor::handleRead(int rpipe, unsigned int &result, const std::str type != PipeWriter::REPORT_SUPPR_INLINE && type != PipeWriter::REPORT_SUPPR && type != PipeWriter::CHILD_END && - type != PipeWriter::REPORT_METRIC) { + type != PipeWriter::REPORT_METRIC && + type != PipeWriter::REPORT_TIMER) { std::cerr << "#### ThreadExecutor::handleRead(" << filename << ") invalid type " << int(type) << std::endl; std::exit(EXIT_FAILURE); } @@ -222,6 +240,9 @@ bool ProcessExecutor::handleRead(int rpipe, unsigned int &result, const std::str } while (bytes_to_read != 0); } + if (mSettings.debugipc) + std::cout << "handleRead - " << type << " - " << buf << std::endl; + bool res = true; if (type == PipeWriter::REPORT_OUT) { // the first character is the color @@ -272,6 +293,20 @@ bool ProcessExecutor::handleRead(int rpipe, unsigned int &result, const std::str res = false; } else if (type == PipeWriter::REPORT_METRIC) { mErrorLogger.reportMetric(buf); + } else if (type == PipeWriter::REPORT_TIMER) { + if (!mTimerResults) { + // TODO: make this non-fatal + std::cerr << "#### ThreadExecutor::handleRead(" << filename << ") received timer results when no timer is enabled" << std::endl; + std::exit(EXIT_FAILURE); + } + const auto parts = splitString(buf, ';'); + if (parts.size() < 2) + { + // TODO: make this non-fatal + std::cerr << "#### ThreadExecutor::handleRead(" << filename << ") adding of timer result failed - insufficient data" << std::endl; + std::exit(EXIT_FAILURE); + } + mTimerResults->addResults(parts[0], std::chrono::milliseconds{strToInt(parts[1])}); } return res; @@ -308,6 +343,12 @@ unsigned int ProcessExecutor::check() return v + p.size(); }); + // pass unmodified suppressions to forked process so we only transfer back the actual changes done by the fork + // and do not see the changes which have already been transferred back + Suppressions supprs; + supprs.nomsg.addSuppressions(mSuppressions.nomsg.getSuppressions()); + supprs.nofail.addSuppressions(mSuppressions.nofail.getSuppressions()); + std::list rpipes; std::map childFile; std::map pipeFile; @@ -346,8 +387,13 @@ unsigned int ProcessExecutor::check() #endif close(pipes[0]); - PipeWriter pipewriter(pipes[1]); - CppCheck fileChecker(mSettings, mSuppressions, pipewriter, mTimerResults, false, mExecuteCommand); + // create a separate result object so we do not get the results which have already been transferred back + std::unique_ptr timerResults; + if (mTimerResults) + timerResults.reset(new TimerResults); + + PipeWriter pipewriter(pipes[1], mSettings.debugipc); + CppCheck fileChecker(mSettings, supprs, pipewriter, timerResults.get(), false, mExecuteCommand); unsigned int resultOfCheck = 0; if (iFileSettings != mFileSettings.end()) { @@ -357,7 +403,9 @@ unsigned int ProcessExecutor::check() resultOfCheck = fileChecker.check(*iFile); } - pipewriter.writeSuppr(mSuppressions.nomsg); + pipewriter.writeSuppr(supprs.nomsg); + + pipewriter.writeTimer(timerResults.get()); pipewriter.writeEnd(std::to_string(resultOfCheck)); std::exit(EXIT_SUCCESS); @@ -468,7 +516,7 @@ void ProcessExecutor::reportInternalChildErr(const std::string &childname, const "cppcheckError", Certainty::normal); - if (!mSuppressions.nomsg.isSuppressed(errmsg, {})) + if (hasToLog(errmsg)) mErrorLogger.reportErr(errmsg); } diff --git a/cli/singleexecutor.cpp b/cli/singleexecutor.cpp index d710a4d4c57..e25fc804490 100644 --- a/cli/singleexecutor.cpp +++ b/cli/singleexecutor.cpp @@ -21,7 +21,6 @@ #include "cppcheck.h" #include "filesettings.h" #include "settings.h" -#include "timer.h" #include #include diff --git a/cli/threadexecutor.cpp b/cli/threadexecutor.cpp index b8581d7e772..5d723366574 100644 --- a/cli/threadexecutor.cpp +++ b/cli/threadexecutor.cpp @@ -26,7 +26,6 @@ #include "filesettings.h" #include "settings.h" #include "suppressions.h" -#include "timer.h" #include #include diff --git a/cmake/compilerDefinitions.cmake b/cmake/compilerDefinitions.cmake index 5f03b83dcee..3daf0e7ba87 100644 --- a/cmake/compilerDefinitions.cmake +++ b/cmake/compilerDefinitions.cmake @@ -20,7 +20,7 @@ endif() if ((USE_LIBCXX AND CMAKE_CXX_COMPILER_ID MATCHES "Clang") OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") if(CPPCHK_GLIBCXX_DEBUG AND CMAKE_BUILD_TYPE STREQUAL "Debug") # TODO: determine proper version for AppleClang - current value is based on the oldest version avaialble in CI - if((CMAKE_CXX_COMPILER_ID STREQUALS "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 18) OR + if((CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 18) OR (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 17)) add_definitions(-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG) else() diff --git a/cmake/compileroptions.cmake b/cmake/compileroptions.cmake index aa7deb8552c..6343a8a5102 100644 --- a/cmake/compileroptions.cmake +++ b/cmake/compileroptions.cmake @@ -134,7 +134,6 @@ elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") # TODO: fix and enable these warnings - or move to suppression list below add_compile_options_safe(-Wno-sign-conversion) add_compile_options_safe(-Wno-shadow-field-in-constructor) - add_compile_options_safe(-Wno-shorten-64-to-32) add_compile_options_safe(-Wno-implicit-int-conversion) add_compile_options_safe(-Wno-double-promotion) add_compile_options_safe(-Wno-shadow-field) diff --git a/cmake/options.cmake b/cmake/options.cmake index d640710e428..bda080fe77b 100644 --- a/cmake/options.cmake +++ b/cmake/options.cmake @@ -134,18 +134,22 @@ set(CMAKE_DISABLE_PRECOMPILE_HEADERS Off CACHE BOOL "Disable precompiled headers # see https://gitlab.kitware.com/cmake/cmake/-/issues/21219 set(CMAKE_PCH_PROLOGUE "") +# TODO: do we need to set these? set(CMAKE_INCLUDE_DIRS_CONFIGCMAKE ${CMAKE_INSTALL_PREFIX}/include CACHE PATH "Output directory for headers") set(CMAKE_LIB_DIRS_CONFIGCMAKE ${CMAKE_INSTALL_PREFIX}/lib CACHE PATH "Output directory for libraries") +# TODO: do we need to set these? set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib) string(LENGTH "${FILESDIR}" _filesdir_len) # override FILESDIR if it is set or empty +# needs to be an absolute path in Cppcheck but a relative one for CMake install if(FILESDIR OR ${_filesdir_len} EQUAL 0) -# TODO: verify that it is an absolute path? - set(FILESDIR_DEF ${FILESDIR}) + set(FILESDIR_INSTALL "${FILESDIR}") # TODO: make relative - leverage CMAKE_INSTALL_DATAROOTDIR? + set(FILESDIR_DEF "${FILESDIR}") else() - set(FILESDIR_DEF ${CMAKE_INSTALL_PREFIX}/share/${PROJECT_NAME} CACHE STRING "Cppcheck files directory") + set(FILESDIR_INSTALL "share/${PROJECT_NAME}") + set(FILESDIR_DEF "${CMAKE_INSTALL_PREFIX}/${FILESDIR_INSTALL}") endif() diff --git a/cmake/printInfo.cmake b/cmake/printInfo.cmake index 87c7e41f284..7869169708d 100644 --- a/cmake/printInfo.cmake +++ b/cmake/printInfo.cmake @@ -8,6 +8,7 @@ message(STATUS "Compiler Version = ${CMAKE_CXX_COMPILER_VERSION}") message(STATUS "Build type = ${CMAKE_BUILD_TYPE}") message(STATUS "CMake C++ Standard = ${CMAKE_CXX_STANDARD}") message(STATUS "CMAKE_INSTALL_PREFIX = ${CMAKE_INSTALL_PREFIX}") +message(STATUS "CMAKE_INSTALL_BINDIR = ${CMAKE_INSTALL_BINDIR}") message(STATUS "CMAKE_DISABLE_PRECOMPILE_HEADERS = ${CMAKE_DISABLE_PRECOMPILE_HEADERS}") message(STATUS "C++ flags (General) = ${CMAKE_CXX_FLAGS}") message(STATUS "C++ flags (Release) = ${CMAKE_CXX_FLAGS_RELEASE}") @@ -109,6 +110,7 @@ message(STATUS) message(STATUS "USE_LIBCXX = ${USE_LIBCXX}") message(STATUS) message(STATUS "FILESDIR = ${FILESDIR}") +message(STATUS "FILESDIR_INSTALL = ${FILESDIR_INSTALL}") message(STATUS "FILESDIR_DEF = ${FILESDIR_DEF}") message(STATUS) diff --git a/cppcheckpremium-suppressions b/cppcheckpremium-suppressions index 1b26fa55cc1..4bd66f2f681 100644 --- a/cppcheckpremium-suppressions +++ b/cppcheckpremium-suppressions @@ -1,8 +1,10 @@ # open source warnings are handled by the selfcheck.yml +knownConditionTrueFalse noExplicitConstructor postfixOperator shadowFunction +uninitMemberVarNoCtor useStlAlgorithm normalCheckLevelMaxBranches @@ -19,8 +21,20 @@ premium-cert-int36-c # too picky premium-cert-arr30-c +premium-cert-ctr50-cpp +premium-cert-dcl06-c +premium-cert-dcl51-cpp +premium-cert-exp20-c +premium-cert-exp34-c +premium-cert-int13-c +premium-cert-int30-c +premium-cert-int31-c premium-cert-int32-c +premium-cert-pre05-c +premium-cert-pre12-c premium-cert-str34-c +premium-cert-str53-cpp + # we intentionally pass structs premium-cert-dcl39-c-* diff --git a/createrelease b/createrelease index 9dd79333119..44ab440b96c 100755 --- a/createrelease +++ b/createrelease @@ -1,6 +1,6 @@ #!/bin/bash # -# A script for creating release packages. The release packages are create in the home directory. +# A script for creating release packages. The release packages are created in the home directory. # # Create release candidate # ======================== @@ -8,129 +8,141 @@ # Review trac roadmap, are tickets closed properly? # Only tickets that should be retargeted should be open. # -# update cppcheck used in premium addon CI -# create jira issue "CI: update cppcheck binary" -# cd ~/cppchecksolutions/addon/tools && python3 ci-update-cppcheck.py +# Execute this script to check if trac tickets can be closed +# tools/release-trac-fixed-open-tickets.py # -# update mappings.. +# Versioning scheme +# ======================== +# VERSION=2.22.0 # the new release tag created +# PREV=2.21.0 # the previous release +# BRANCH=2.22.x # release branch +# +# 2.22.x BRANCH - release branch +# 2.22.0 TAG - the release +# 2.22.1, ... TAG - patch releases +# 2.22.0-rc1 TAG - release candidate +# +# Update mappings: # cd ~/cppchecksolutions/addon/coverage # CPPCHECK_REPO=~/cppchecksolutions/cppcheck python3 coverage.py --code # -# check every isPremiumEnabled call: TODO write helper script -# - every id should be in --errorlist -# git grep 'isPremiumEnabled[(]"' | sed 's/.*isPremiumEnabled[(]"//' | sed 's/".*//' | sort | uniq > ids1.txt -# ./cppcheck --errorlist | grep ' id="' | sed 's/.* id="//' | sed 's/".*//' | sort | uniq > ids2.txt -# diff -y ids1.txt ids2.txt -# - premiumaddon: check coverage.py -# python3 coverage.py --id ; sort ids-*.txt | uniq > ~/cppcheck/ids3.txt -# diff -y ids2.txt ids3.txt +# Check that every premium check id is a real, known Cppcheck error id: every +# id passed to isPremiumEnabled(...) and every id referenced by the premium +# addon's MISRA/CERT/AUTOSAR/CWE coverage mapping (coverage.py) must show up +# in `cppcheck --errorlist`, otherwise the id was renamed/removed and the +# premium check or compliance mapping is silently broken. +# tools/release-check-premium-ids.py # # Windows installer: # - ensure latest build was successful # - ensure cfg files etc are included (win_installer/cppcheck.wxs) # -# self check, fix critical issues: +# Self check, fix critical issues: # make clean && make CXXOPTS=-O2 MATCHCOMPILER=yes -j4 # ./cppcheck -D__CPPCHECK__ -D__GNUC__ -DCHECK_INTERNAL -DHAVE_RULES --std=c++11 --library=cppcheck-lib --library=qt --enable=style --inconclusive --inline-suppr --suppress=bitwiseOnBoolean --suppress=shadowFunction --suppress=useStlAlgorithm --suppress=*:externals/picojson.h --suppress=functionConst --suppress=functionStatic --suppress=normalCheckLevelMaxBranches --xml cli gui/*.cpp lib 2> selfcheck.xml # -# Generate lib/checkers.cpp (TODO the premium checkers should not be statically coded) +# Generate lib/checkers.cpp: (TODO the premium checkers should not be statically coded) # cd ~/cppchecksolutions/cppcheck && python3 tools/get_checkers.py > lib/checkers.cpp # -# Update copyright year TODO release script -# git diff 2.8 -- */*.cpp */*.h | grep '^diff --git a/' | sed 's|.* b/||' | xargs sed -i 's/Copyright (C) 2007-20[12]./Copyright (C) 2007-2022/' +# Update copyright year: TODO release script +# git grep -l 'Copyright (C) 2007-20' -- '*.cpp' '*.h' '*.py' | while read f; do y=$(git log -1 --format=%ad --date=format:%Y --invert-grep -i --grep=copyright -- "$f"); sed -i "s/Copyright (C) 2007-20[0-9][0-9]/Copyright (C) 2007-$y/" "$f"; done # git diff | grep '^diff --git a/' # # Make sure "cppcheck --errorlist" works: # make clean && make -j4 && ./cppcheck --errorlist > errlist.xml && xmllint --noout errlist.xml # # Update AUTHORS using output from: -# git log --format='%aN' 2.7..HEAD | sort -u > AUTHORS2 && diff -y AUTHORS AUTHORS2 | less +# git log --format='%aN' $PREV..HEAD | sort -u > AUTHORS2 && diff -y AUTHORS AUTHORS2 | less +# Include github usernames in PR title and commit message # -# Update GUI translations -# lupdate gui.pro +# Update GUI translations: +# cd ~/cppchecksolutions/cppcheck/gui && lupdate gui.pro # -# Create 2.18.x branch -# git checkout -b 2.18.x ; git push -u origin 2.18.x +# Create new release branch: +# git checkout -b $BRANCH && git push -u origin $BRANCH # in fork: -# * add upstream: git remote add upstream git@github.com:danmar/cppcheck.git -# * add branch: git fetch upstream 2.19.x +# * add upstream: git remote add upstream git@github.com:cppcheck-opensource/cppcheck.git +# * add branch: git fetch upstream $BRANCH # # Release notes: # - ensure safety critical issues are listed properly # - empty the releasenotes.txt in main branch # # Update version numbers in: -# python3 tools/release-set-version.py 2.19.0 +# python3 tools/release-set-version.py $VERSION # Verify: # grep '\.99' */*.[ch]* && grep '[0-9][0-9] dev' */*.[ch]* # egrep "2\.[0-9]+" */*.h */*.cpp man/*.md | grep -v "test/test" | less -# git commit -a -m "2.8: Set versions" +# git commit -a -m "$VERSION: Set versions" # # Build and test the windows installer # # Update the Makefile: # make dmake && ./dmake --release -# git commit -a -m "2.8: Updated Makefile" +# git commit -a -m "$VERSION: Updated Makefile" +# +# Push changes: +# git push # # Ensure that CI is happy # # Tag: -# git tag 2.8-rc1 +# git tag $VERSION-rc1 # git push --tags # # Release -# ======= +# ======================== # # Remove "-rc1" from versions. Test: git grep "\-rc[0-9]" # # Create a release folder on sourceforge: # https://sourceforge.net/projects/cppcheck/files/cppcheck/ # -# git tag 2.8 ; git push --tags -# ./createrelease 2.8 +# git tag $VERSION && git push --tags +# ./createrelease $VERSION # -# copy msi from release-windows, install and test cppcheck -# copy manual from build-manual +# Copy msi from release-windows, install and test cppcheck +# Copy manual from build-manual # # Update download link on index.php main page # # Trac: -# 1. Create ticket "2.18 safety cosmetic changes" -# git log --format=oneline 2.17.0..HEAD | egrep -v "^[0-9a-f]*[ ][ ]*([Ff]ix|fixed|Fixup|Fixes|refs)?[ ]*#*[0-9]+" +# 1. Create ticket "$VERSION safety cosmetic changes" +# git log --format=oneline $PREV..HEAD | egrep -v "^[0-9a-f]*[ ][ ]*([Ff]ix|fixed|Fixup|Fixes|refs)?[ ]*#*[0-9]+" # 2. Check priorities for all tickets in milestone. Should be: safety-* # 3. Create new milestone # 4. Close old milestone # -# write a news +# Write a news # -# save "cppcheck --doc" output on wiki +# Save "cppcheck --doc" output on wiki # -# compile new democlient: +# Compile new democlient: # ssh -t danielmarjamaki,cppcheck@shell.sourceforge.net create # ./build-cppcheck.sh # -# create a ticket with data from http://cppcheck1.osuosl.org:8000/time_gt.html for performance tracking +# Create a ticket with data from http://cppcheck1.osuosl.org:8000/time_gt.html for performance tracking # (example: https://trac.cppcheck.net/ticket/13715) # - type: defect # - component: Performance -# - summary: [meta] performance regressions in 2.x +# - summary: [meta] performance regressions in $VERSION # -# run daca with new release +# Run daca with new release: # 1. edit tools/donate-cpu-server.py. Update OLD_VERSION and SERVER_VERSION # 2. scp -i ~/.ssh/osuosl_id_rsa tools/donate-cpu-server.py danielmarjamaki@cppcheck1.osuosl.org:/var/daca@home/ # # Backup: -# * trac: cd /var && nice tar -cJf ~/trac.tar.xz trac-cppcheck/db/trac.db -# * daca: cd /var && nice tar -cJf ~/daca.tar.xz daca@home +# * trac: rm -f trac.db trac.tar.xz && cp /var/trac-cppcheck/db/trac.db . && nice tar -cJf trac.tar.xz trac.db +# * daca: rm -rf daca@home daca.tar.xz && cp -R /var/daca@home . && nice tar -cJf daca.tar.xz daca@home # * git: git checkout -f && git checkout main && git pull && tar -cJf git.tar.xz .git -# * git log 2.16.0..2.17.0 > Changelog -# * mkdir out && python3 ~/cppchecksolutions/release/getWorkflowAndIssueLogs.py -r danmar/cppcheck -t 2.15.0 -p out +# * git log $PREV..$VERSION > Changelog +# * cd ~/cppcheck && mkdir -p out && python3 ~/cppchecksolutions/release/getWorkflowAndIssueLogs.py -r cppcheck-opensource/cppcheck -t $VERSION -p out -# Folder/tag to use -folder=$1 -tag=$folder.0 +# Folder/tag to use: +tag=$1 +folder=${tag%.*} -# Name of release +# Name of release: releasename=cppcheck-$tag set -e @@ -162,7 +174,7 @@ scp htdocs/* danielmarjamaki,cppcheck@web.sourceforge.net:htdocs/ cd .. rm -rf upload -# Local cppcheck binary +# Local cppcheck binary: mkdir -p ~/.cppcheck/$tag cd ~/.cppcheck/$tag cp -R ~/cppcheck/cfg . diff --git a/externals/simplecpp/simplecpp.cpp b/externals/simplecpp/simplecpp.cpp index 7afc17ab735..8237ac75fab 100644 --- a/externals/simplecpp/simplecpp.cpp +++ b/externals/simplecpp/simplecpp.cpp @@ -3,18 +3,12 @@ * Copyright (C) 2016-2023 simplecpp team */ +// needs to be specified here otherwise _mingw.h will define it as 0x0601 +// causing FileIdInfo not to be available #if defined(_WIN32) # ifndef _WIN32_WINNT # define _WIN32_WINNT 0x0602 # endif -# ifndef NOMINMAX -# define NOMINMAX -# endif -# ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -# endif -# include -# undef ERROR #endif #include "simplecpp.h" @@ -51,10 +45,19 @@ #include #include -#ifdef _WIN32 +#if defined(_WIN32) +# ifndef NOMINMAX +# define NOMINMAX +# endif +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# include +# undef ERROR # include #else # include +# include #endif static bool isHex(const std::string &s) @@ -93,6 +96,8 @@ static const simplecpp::TokenString IFNDEF("ifndef"); static const simplecpp::TokenString DEFINED("defined"); static const simplecpp::TokenString ELSE("else"); static const simplecpp::TokenString ELIF("elif"); +static const simplecpp::TokenString ELIFDEF("elifdef"); +static const simplecpp::TokenString ELIFNDEF("elifndef"); static const simplecpp::TokenString ENDIF("endif"); static const simplecpp::TokenString PRAGMA("pragma"); @@ -275,8 +280,10 @@ class simplecpp::TokenList::Stream { return ch; } - unsigned char peekChar() { - auto ch = static_cast(peek()); + int peekChar() { + int ch = peek(); + if (ch == EOF) + return ch; // For UTF-16 encoded files the BOM is 0xfeff/0xfffe. If the // character is non-ASCII character then replace it with 0xff @@ -285,7 +292,7 @@ class simplecpp::TokenList::Stream { const auto ch2 = static_cast(peek()); unget(); const int ch16 = makeUtf16Char(ch, ch2); - ch = static_cast(((ch16 >= 0x80) ? 0xff : ch16)); + ch = (ch16 >= 0x80) ? 0xff : ch16; } // Handling of newlines.. @@ -470,26 +477,26 @@ namespace { simplecpp::TokenList::TokenList(std::vector &filenames) : frontToken(nullptr), backToken(nullptr), files(filenames) {} -simplecpp::TokenList::TokenList(std::istream &istr, std::vector &filenames, const std::string &filename, OutputList *outputList) +simplecpp::TokenList::TokenList(std::istream &istr, std::vector &filenames, const std::string &filename, const DUI &dui, OutputList *outputList) : frontToken(nullptr), backToken(nullptr), files(filenames) { StdIStream stream(istr); - readfile(stream,filename,outputList); + readfile(stream,filename,dui,outputList); } -simplecpp::TokenList::TokenList(const unsigned char* data, std::size_t size, std::vector &filenames, const std::string &filename, OutputList *outputList, int /*unused*/) +simplecpp::TokenList::TokenList(const unsigned char* data, std::size_t size, std::vector &filenames, const std::string &filename, const DUI &dui, OutputList *outputList, int /*unused*/) : frontToken(nullptr), backToken(nullptr), files(filenames) { StdCharBufStream stream(data, size); - readfile(stream,filename,outputList); + readfile(stream,filename,dui,outputList); } -simplecpp::TokenList::TokenList(const std::string &filename, std::vector &filenames, OutputList *outputList) +simplecpp::TokenList::TokenList(const std::string &filename, std::vector &filenames, const DUI &dui, OutputList *outputList) : frontToken(nullptr), backToken(nullptr), files(filenames) { try { FileStream stream(filename, filenames); - readfile(stream,filename,outputList); + readfile(stream,filename,dui,outputList); } catch (const simplecpp::Output & e) { outputList->emplace_back(e); } @@ -598,7 +605,7 @@ std::string simplecpp::TokenList::stringify(bool linenrs) const return ret.str(); } -static bool isNameChar(unsigned char ch) +static bool isNameChar(int ch) { return std::isalnum(ch) || ch == '_' || ch == '$'; } @@ -635,10 +642,10 @@ static bool isStringLiteralPrefix(const std::string &str) str == "R" || str == "uR" || str == "UR" || str == "LR" || str == "u8R"; } -void simplecpp::TokenList::lineDirective(unsigned int fileIndex, unsigned int line, Location &location) +void simplecpp::TokenList::lineDirective(unsigned int fileIndex_, unsigned int line, Location &location) { - if (fileIndex != location.fileIndex || line >= location.line) { - location.fileIndex = fileIndex; + if (fileIndex_ != location.fileIndex || line >= location.line) { + location.fileIndex = fileIndex_; location.line = line; return; } @@ -654,20 +661,24 @@ void simplecpp::TokenList::lineDirective(unsigned int fileIndex, unsigned int li static const std::string COMMENT_END("*/"); -void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, OutputList *outputList) +void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, const DUI &dui, OutputList *outputList) { - std::stack loc; - unsigned int multiline = 0U; + bool trailing_nl = true; const Token *oldLastToken = nullptr; + const cstd_t cstd = getCStd(dui.std); + const cppstd_t cppstd = getCppStd(dui.std); + Location location(fileIndex(filename), 1, 1); while (stream.good()) { unsigned char ch = stream.readChar(); if (!stream.good()) break; + trailing_nl = false; + if (ch >= 0x80) { if (outputList) { simplecpp::Output err{ @@ -690,65 +701,105 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, } else { location.line += multiline + 1; multiline = 0U; + trailing_nl = true; } if (!multiline) location.col = 1; if (oldLastToken != cback()) { oldLastToken = cback(); - const Token * const llTok = isLastLinePreprocessor(); - if (!llTok) + + // #line 3 + // #line 3 "file.c" + // #3 + // #3 "file.c" + const Token * ppTok = isLastLinePreprocessor(); + if (!ppTok) continue; - const Token * const llNextToken = llTok->next; - if (!llTok->next) + + const auto advanceAndSkipComments = [](const Token* tok) { + do { + tok = tok->next; + } while (tok && tok->comment); + return tok; + }; + + // skip # + ppTok = advanceAndSkipComments(ppTok); + if (!ppTok) continue; - if (llNextToken->next) { - // #file "file.c" - if (llNextToken->str() == "file" && - llNextToken->next->str()[0] == '\"') - { - const Token *strtok = cback(); - while (strtok->comment) - strtok = strtok->previous; - loc.push(location); - location.fileIndex = fileIndex(strtok->str().substr(1U, strtok->str().size() - 2U)); - location.line = 1U; - } - // TODO: add support for "# 3" - // #3 "file.c" - // #line 3 "file.c" - else if ((llNextToken->number && - llNextToken->next->str()[0] == '\"') || - (llNextToken->str() == "line" && - llNextToken->next->number && - llNextToken->next->next && - llNextToken->next->next->str()[0] == '\"')) - { - const Token *strtok = cback(); - while (strtok->comment) - strtok = strtok->previous; - const Token *numtok = strtok->previous; - while (numtok->comment) - numtok = numtok->previous; - lineDirective(fileIndex(replaceAll(strtok->str().substr(1U, strtok->str().size() - 2U),"\\\\","\\")), - std::atol(numtok->str().c_str()), location); - } - // #line 3 - else if (llNextToken->str() == "line" && - llNextToken->next->number) - { - const Token *numtok = cback(); - while (numtok->comment) - numtok = numtok->previous; - lineDirective(location.fileIndex, std::atol(numtok->str().c_str()), location); + + if (ppTok->str() == "line") + ppTok = advanceAndSkipComments(ppTok); + + if (ppTok && (ppTok->str()[0] == '-' || ppTok->str()[0] == '+')) { + if (outputList) { + simplecpp::Output err{ + simplecpp::Output::SYNTAX_ERROR, + location, + "Invalid character in line directive: '" + ppTok->str() + "'." + }; + outputList->emplace_back(std::move(err)); } + clear(); + return; + } + + if (!ppTok || !ppTok->number) + continue; + + constexpr unsigned long line_limit = std::numeric_limits::max(); + unsigned long line; + try { + line = std::min(line_limit, std::stoul(ppTok->str())); + } catch (...) { + line = line_limit; } - // #endfile - else if (llNextToken->str() == "endfile" && !loc.empty()) - { - location = loc.top(); - loc.pop(); + + unsigned long maxline; + if ((cstd != CUnknown && cstd < C99) || (cppstd != CPPUnknown && cppstd < CPP11)) + maxline = 32767; + else + maxline = 2147483647; + + if (line == 0 || line > maxline) { + if (outputList) { + const bool unknown_std = cstd == CUnknown && cppstd == CPPUnknown; + std::string msg = "Line number out of range: " + ppTok->str() + ". "; + if (line == 0) { + msg += "Line number zero is undefined behavior."; + } else { + msg += "Line numbers above " + std::to_string(maxline) + " are "; + if (unknown_std) + msg += "undefined behavior or conditionally supported"; + else if (cppstd >= CPP26) + msg += "conditionally supported"; + else + msg += "undefined behavior"; + if (cstd != CUnknown) + msg += std::string(" in ") + getCStdName(cstd); + else if (cppstd != CPPUnknown) + msg += std::string(" in ") + getCppStdName(cppstd); + msg += "."; + } + simplecpp::Output err{ + simplecpp::Output::PORTABILITY_LINE_DIRECTIVE, + location, msg + }; + outputList->emplace_back(std::move(err)); + } } + + ppTok = advanceAndSkipComments(ppTok); + + unsigned int fileindex; + + if (ppTok && ppTok->str()[0] == '\"') + fileindex = fileIndex(replaceAll(ppTok->str().substr(1U, ppTok->str().size() - 2U),"\\\\","\\")); + else + fileindex = location.fileIndex; + + lineDirective(fileindex, line, location); } continue; @@ -771,8 +822,21 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, ch = stream.readChar(); } stream.ungetChar(); - push_back(new Token(currentToken, location)); - location.adjust(currentToken); + std::string::size_type pos = 0; + unsigned int spliced = 0; + while ((pos = currentToken.find('\\', pos)) != std::string::npos) { + if (pos + 1 < currentToken.size() && currentToken[pos + 1] == '\n') { + currentToken.erase(pos, 2); + ++spliced; + } else { + ++pos; + } + } + if (!currentToken.empty()) { + push_back(new Token(currentToken, location)); + location.adjust(currentToken); + } + location.line += spliced; continue; } } @@ -960,6 +1024,15 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, location.adjust(currentToken); } + if ((cstd != CUnknown || cppstd == CPPUnknown) && !trailing_nl && outputList) { + Output err{ + Output::PORTABILITY_NO_EOF_NEWLINE, + location, + "No newline at end of file is undefined behavior in C." + }; + outputList->emplace_back(std::move(err)); + } + combineOperators(); } @@ -1016,8 +1089,7 @@ static bool isAlternativeAndBitandBitor(const simplecpp::Token* tok) void simplecpp::TokenList::combineOperators() { - std::stack executableScope; - executableScope.push(false); + std::stack> executableScope{{false}}; for (Token *tok = front(); tok; tok = tok->next) { if (tok->op == '{') { if (executableScope.top()) { @@ -1025,7 +1097,7 @@ void simplecpp::TokenList::combineOperators() continue; } const Token *prev = tok->previous; - while (prev && prev->isOneOf(";{}()")) + while (prev && prev->isOneOf(";{}(")) prev = prev->previous; executableScope.push(prev && prev->op == ')'); continue; @@ -1523,7 +1595,7 @@ namespace simplecpp { * @throws std::runtime_error thrown on bad macro syntax */ Macro(const std::string &name, const std::string &value, std::vector &f) : nameTokDef(nullptr), files(f), tokenListDefine(f), valueDefinedInCode_(false) { - const std::string def(name + ' ' + value); + const std::string def(name + ' ' + value + '\n'); StdCharBufStream stream(reinterpret_cast(def.data()), def.size()); tokenListDefine.readfile(stream); if (!parseDefine(tokenListDefine.cfront())) @@ -1602,7 +1674,7 @@ namespace simplecpp { else if (rawtok->op == ')') --par; else if (rawtok->op == '#' && !sameline(rawtok->previous, rawtok)) - throw Error(rawtok->location, "it is invalid to use a preprocessor directive as macro parameter"); + throw invalidDirectiveAsMacroParameter(rawtok->location); rawtokens2.push_back(new Token(rawtok->str(), rawtok1->location, rawtok->whitespaceahead)); rawtok = rawtok->next; } @@ -1635,7 +1707,7 @@ namespace simplecpp { if (output2.cfront() != output2.cback() && macro2tok->str() == this->name()) break; const MacroMap::const_iterator macro = macros.find(macro2tok->str()); - if (macro == macros.end() || !macro->second.functionLike()) + if (macro == macros.end() || !macro->second.functionLike() || macro2tok->isExpandedFrom(¯o->second)) break; TokenList rawtokens2(inputFiles); const Location loc(macro2tok->location); @@ -1698,6 +1770,11 @@ namespace simplecpp { const std::string what; }; + struct invalidDirectiveAsMacroParameter : public Error { + explicit invalidDirectiveAsMacroParameter(const Location &loc) + : Error(loc, "it is invalid to use a preprocessor directive as macro parameter") {} + }; + /** Struct that is thrown when macro is expanded with wrong number of parameters */ struct wrongNumberOfParameters : public Error { wrongNumberOfParameters(const Location &loc, const std::string ¯oName) : Error(loc, "Wrong number of parameters for macro \'" + macroName + "\'.") {} @@ -1739,6 +1816,9 @@ namespace simplecpp { return tok; } + /** + * @throws Error thrown in case of __VA_OPT__ issues + */ bool parseDefine(const Token *nametoken) { nameTokDef = nametoken; variadic = false; @@ -1914,14 +1994,17 @@ namespace simplecpp { if (nameTokInst->str() == "__FILE__") { output.push_back(new Token('\"'+output.file(loc)+'\"', loc)); + output.back()->macro = "__FILE__"; return nameTokInst->next; } if (nameTokInst->str() == "__LINE__") { output.push_back(new Token(toString(loc.line), loc)); + output.back()->macro = "__LINE__"; return nameTokInst->next; } if (nameTokInst->str() == "__COUNTER__") { output.push_back(new Token(toString(usageList.size()-1U), loc)); + output.back()->macro = "__COUNTER__"; return nameTokInst->next; } @@ -2081,39 +2164,37 @@ namespace simplecpp { return functionLike() ? parametertokens2.back()->next : nameTokInst->next; } - const Token *recursiveExpandToken(TokenList &output, TokenList &temp, const Location &loc, const Token *tok, const MacroMap ¯os, const std::set &expandedmacros, const std::vector ¶metertokens) const { - if (!temp.cback() || !temp.cback()->name || !tok->next || tok->next->op != '(') { - output.takeTokens(temp); - return tok->next; - } - - if (!sameline(tok, tok->next)) { - output.takeTokens(temp); - return tok->next; - } - + /** Returns the macro to expand when the last token of @p temp is the name of a + * function-like macro and the tokens after @p tok supply its arguments; nullptr otherwise */ + static const Macro *rescanMacro(const TokenList &temp, const Token *tok, const MacroMap ¯os, const std::set &expandedmacros) { + if (!temp.cback() || !temp.cback()->name || !sameline(tok, tok->next) || tok->next->op != '(') + return nullptr; const MacroMap::const_iterator it = macros.find(temp.cback()->str()); - if (it == macros.end() || expandedmacros.find(temp.cback()->str()) != expandedmacros.end()) { - output.takeTokens(temp); - return tok->next; - } + if (it == macros.end() || expandedmacros.find(temp.cback()->str()) != expandedmacros.end()) + return nullptr; + if (!it->second.functionLike() || temp.cback()->isExpandedFrom(&it->second)) + return nullptr; + return &it->second; + } - const Macro &calledMacro = it->second; - if (!calledMacro.functionLike()) { + const Token *recursiveExpandToken(TokenList &output, TokenList &temp, const Location &loc, const Token *tok, const MacroMap ¯os, const std::set &expandedmacros, const std::vector ¶metertokens) const { + // Expand while the expansion result ends with the name of a function-like + // macro whose arguments are supplied by the tokens that follow it. Each round + // consumes that macro call from the token stream, so tok always advances. + while (const Macro * const calledMacro = rescanMacro(temp, tok, macros, expandedmacros)) { + TokenList temp2(files); + temp2.push_back(new Token(temp.cback()->str(), tok->location)); + + const Token * const tok2 = appendTokens(temp2, loc, tok->next, macros, expandedmacros, parametertokens); + if (!tok2) + break; output.takeTokens(temp); - return tok->next; + output.deleteToken(output.back()); + calledMacro->expand(temp, loc, temp2.cfront(), macros, expandedmacros); + tok = tok2; } - - TokenList temp2(files); - temp2.push_back(new Token(temp.cback()->str(), tok->location)); - - const Token * const tok2 = appendTokens(temp2, loc, tok->next, macros, expandedmacros, parametertokens); - if (!tok2) - return tok->next; output.takeTokens(temp); - output.deleteToken(output.back()); - calledMacro.expand(output, loc, temp2.cfront(), macros, expandedmacros); - return tok2->next; + return tok->next; } const Token *expandToken(TokenList &output, const Location &loc, const Token *tok, const MacroMap ¯os, const std::set &expandedmacros, const std::vector ¶metertokens) const { @@ -2201,6 +2282,8 @@ namespace simplecpp { } output.push_back(newMacroToken(tok->str(), loc, true, tok)); + if (it != macros.end()) + output.back()->markExpandedFrom(&it->second); return tok->next; } @@ -2312,9 +2395,6 @@ namespace simplecpp { const Token *nextTok = B->next; if (canBeConcatenatedStringOrChar) { - if (unexpectedA) - throw invalidHashHash::unexpectedToken(tok->location, name(), A); - // It seems clearer to handle this case separately even though the code is similar-ish, but we don't want to merge here. // TODO The question is whether the ## or varargs may still apply, and how to provoke? if (expandArg(tokensB, B, parametertokens)) { @@ -3070,6 +3150,65 @@ static std::string openHeader(std::ifstream &f, const simplecpp::DUI &dui, const return ""; } +namespace { + struct FileID { +#ifdef _WIN32 + struct { + std::uint64_t VolumeSerialNumber; + struct { + std::uint64_t IdentifierHi; + std::uint64_t IdentifierLo; + } FileId; + } fileIdInfo; + + bool operator==(const FileID &that) const noexcept { + return fileIdInfo.VolumeSerialNumber == that.fileIdInfo.VolumeSerialNumber && + fileIdInfo.FileId.IdentifierHi == that.fileIdInfo.FileId.IdentifierHi && + fileIdInfo.FileId.IdentifierLo == that.fileIdInfo.FileId.IdentifierLo; + } +#else + dev_t dev; + ino_t ino; + + bool operator==(const FileID& that) const noexcept { + return dev == that.dev && ino == that.ino; + } +#endif + struct Hasher { + std::size_t operator()(const FileID &id) const { +#ifdef _WIN32 + return static_cast(id.fileIdInfo.FileId.IdentifierHi ^ id.fileIdInfo.FileId.IdentifierLo ^ + id.fileIdInfo.VolumeSerialNumber); +#else + return static_cast(id.dev) ^ static_cast(id.ino); +#endif + } + }; + }; +} + +struct simplecpp::FileDataCache::Impl +{ + void clear() + { + mIdMap.clear(); + } + + using id_map_type = std::unordered_map; + + id_map_type mIdMap; +}; + +simplecpp::FileDataCache::FileDataCache() + : mImpl(new Impl) +{} + +simplecpp::FileDataCache::~FileDataCache() = default; +simplecpp::FileDataCache::FileDataCache(FileDataCache &&) SIMPLECPP_NOEXCEPT = default; +simplecpp::FileDataCache &simplecpp::FileDataCache::operator=(simplecpp::FileDataCache &&) SIMPLECPP_NOEXCEPT = default; + +static bool getFileId(const std::string &path, FileID &id); + std::pair simplecpp::FileDataCache::tryload(FileDataCache::name_map_type::iterator &name_it, const simplecpp::DUI &dui, std::vector &filenames, simplecpp::OutputList *outputList) { const std::string &path = name_it->first; @@ -3078,25 +3217,25 @@ std::pair simplecpp::FileDataCache::tryload(FileDat if (!getFileId(path, fileId)) return {nullptr, false}; - const auto id_it = mIdMap.find(fileId); - if (id_it != mIdMap.end()) { + const auto id_it = mImpl->mIdMap.find(fileId); + if (id_it != mImpl->mIdMap.end()) { name_it->second = id_it->second; return {id_it->second, false}; } - auto *const data = new FileData {path, TokenList(path, filenames, outputList)}; + auto *const data = new FileData {path, TokenList(path, filenames, {}, outputList)}; if (dui.removeComments) data->tokens.removeComments(); name_it->second = data; - mIdMap.emplace(fileId, data); + mImpl->mIdMap.emplace(fileId, data); mData.emplace_back(data); return {data, true}; } -std::pair simplecpp::FileDataCache::get(const std::string &sourcefile, const std::string &header, const simplecpp::DUI &dui, bool systemheader, std::vector &filenames, simplecpp::OutputList *outputList) +std::pair simplecpp::FileDataCache::get_private(const std::string &sourcefile, const std::string &header, const simplecpp::DUI &dui, bool systemheader, std::vector &filenames, simplecpp::OutputList *outputList) { if (isAbsolutePath(header)) { auto ins = mNameMap.emplace(simplecpp::simplifyPath(header), nullptr); @@ -3142,7 +3281,24 @@ std::pair simplecpp::FileDataCache::get(const std:: return {nullptr, false}; } -bool simplecpp::FileDataCache::getFileId(const std::string &path, FileID &id) +std::pair simplecpp::FileDataCache::get(const std::string &sourcefile, const std::string &header, const simplecpp::DUI &dui, bool systemheader, std::vector &filenames, simplecpp::OutputList *outputList) +{ + auto ret = get_private(sourcefile, header, dui, systemheader, filenames, outputList); + + if (mLoadCallback && ret.first) + mLoadCallback(*ret.first, ret.second); + + return ret; +} + +void simplecpp::FileDataCache::clear() +{ + mImpl->clear(); + mNameMap.clear(); + mData.clear(); +} + +static bool getFileId(const std::string &path, FileID &id) { #ifdef _WIN32 HANDLE hFile = CreateFileA(path.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); @@ -3265,7 +3421,7 @@ simplecpp::FileDataCache simplecpp::load(const simplecpp::TokenList &rawtokens, return cache; } -static bool preprocessToken(simplecpp::TokenList &output, const simplecpp::Token *&tok1, simplecpp::MacroMap ¯os, std::vector &files, simplecpp::OutputList *outputList) +static bool preprocessToken(simplecpp::TokenList &output, const simplecpp::Token *&tok1, simplecpp::MacroMap ¯os, std::vector &files, simplecpp::OutputList *outputList, const simplecpp::DUI &dui) { const simplecpp::Token * const tok = tok1; const simplecpp::MacroMap::const_iterator it = tok->name ? macros.find(tok->str()) : macros.end(); @@ -3273,6 +3429,16 @@ static bool preprocessToken(simplecpp::TokenList &output, const simplecpp::Token simplecpp::TokenList value(files); try { tok1 = it->second.expand(value, tok, macros, files); + } catch (const simplecpp::Macro::invalidDirectiveAsMacroParameter &err) { + if (outputList) { + simplecpp::Output out{ + simplecpp::Output::DIRECTIVE_AS_MACRO_PARAMETER, + err.location, + "failed to expand \'" + tok->str() + "\', " + err.what + }; + outputList->emplace_back(std::move(out)); + } + return false; } catch (const simplecpp::Macro::Error &err) { if (outputList) { simplecpp::Output out{ @@ -3286,7 +3452,7 @@ static bool preprocessToken(simplecpp::TokenList &output, const simplecpp::Token } output.takeTokens(value); } else { - if (!tok->comment) + if (!tok->comment || !dui.removeComments) output.push_back(new simplecpp::Token(*tok)); tok1 = tok->next; } @@ -3329,20 +3495,20 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL std::map sizeOfType(rawtokens.sizeOfType); sizeOfType.insert(std::make_pair("char", sizeof(char))); sizeOfType.insert(std::make_pair("short", sizeof(short))); - sizeOfType.insert(std::make_pair("short int", sizeOfType["short"])); + sizeOfType.insert(std::make_pair("short int", sizeof(short))); sizeOfType.insert(std::make_pair("int", sizeof(int))); sizeOfType.insert(std::make_pair("long", sizeof(long))); - sizeOfType.insert(std::make_pair("long int", sizeOfType["long"])); + sizeOfType.insert(std::make_pair("long int", sizeof(long))); sizeOfType.insert(std::make_pair("long long", sizeof(long long))); sizeOfType.insert(std::make_pair("float", sizeof(float))); sizeOfType.insert(std::make_pair("double", sizeof(double))); sizeOfType.insert(std::make_pair("long double", sizeof(long double))); sizeOfType.insert(std::make_pair("char *", sizeof(char *))); sizeOfType.insert(std::make_pair("short *", sizeof(short *))); - sizeOfType.insert(std::make_pair("short int *", sizeOfType["short *"])); + sizeOfType.insert(std::make_pair("short int *", sizeof(short *))); sizeOfType.insert(std::make_pair("int *", sizeof(int *))); sizeOfType.insert(std::make_pair("long *", sizeof(long *))); - sizeOfType.insert(std::make_pair("long int *", sizeOfType["long *"])); + sizeOfType.insert(std::make_pair("long int *", sizeof(long *))); sizeOfType.insert(std::make_pair("long long *", sizeof(long long *))); sizeOfType.insert(std::make_pair("float *", sizeof(float *))); sizeOfType.insert(std::make_pair("double *", sizeof(double *))); @@ -3379,6 +3545,17 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL } output.clear(); return; + } catch (const simplecpp::Macro::Error& e) { + if (outputList) { + simplecpp::Output err{ + Output::DUI_ERROR, + {}, + e.what + }; + outputList->emplace_back(std::move(err)); + } + output.clear(); + return; } } @@ -3435,8 +3612,19 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL includetokenstack.push(rawtokens.cfront()); for (auto it = dui.includes.cbegin(); it != dui.includes.cend(); ++it) { const FileData *const filedata = cache.get("", *it, dui, false, files, outputList).first; - if (filedata != nullptr && filedata->tokens.cfront() != nullptr) + if (filedata == nullptr) { + if (outputList) { + simplecpp::Output err{ + simplecpp::Output::EXPLICIT_INCLUDE_NOT_FOUND, + {}, + "Can not open include file '" + *it + "' that is explicitly included." + }; + outputList->emplace_back(std::move(err)); + } + } + else if (filedata->tokens.cfront() != nullptr) { includetokenstack.push(filedata->tokens.cfront()); + } } std::map> maybeUsedMacros; @@ -3459,7 +3647,9 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL continue; } - if (ifstates.size() <= 1U && (rawtok->str() == ELIF || rawtok->str() == ELSE || rawtok->str() == ENDIF)) { + if (ifstates.size() <= 1U && (rawtok->str() == ELIF || rawtok->str() == ELIFDEF || + rawtok->str() == ELIFNDEF || rawtok->str() == ELSE || + rawtok->str() == ENDIF)) { if (outputList) { simplecpp::Output err{ Output::SYNTAX_ERROR, @@ -3507,14 +3697,14 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL else it->second = macro; } - } catch (const std::runtime_error &) { + } catch (const std::runtime_error &err) { if (outputList) { - simplecpp::Output err{ + simplecpp::Output out{ Output::SYNTAX_ERROR, rawtok->location, - "Failed to parse #define" + std::string("Failed to parse #define, ") + err.what() }; - outputList->emplace_back(std::move(err)); + outputList->emplace_back(std::move(out)); } output.clear(); return; @@ -3539,7 +3729,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL TokenList inc2(files); if (!inc1.empty() && inc1.cfront()->name) { const Token *inctok = inc1.cfront(); - if (!preprocessToken(inc2, inctok, macros, files, outputList)) { + if (!preprocessToken(inc2, inctok, macros, files, outputList, dui)) { output.clear(); return; } @@ -3600,7 +3790,8 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL rawtok = filedata->tokens.cfront(); continue; } - } else if (rawtok->str() == IF || rawtok->str() == IFDEF || rawtok->str() == IFNDEF || rawtok->str() == ELIF) { + } else if (rawtok->str() == IF || rawtok->str() == IFDEF || rawtok->str() == IFNDEF || rawtok->str() == ELIF || + rawtok->str() == ELIFDEF || rawtok->str() == ELIFNDEF) { if (!sameline(rawtok,rawtok->next)) { if (outputList) { simplecpp::Output out{ @@ -3615,15 +3806,28 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL } bool conditionIsTrue; - if (ifstates.top() == AlwaysFalse || (ifstates.top() == ElseIsTrue && rawtok->str() != ELIF)) { + if (ifstates.top() == AlwaysFalse || (ifstates.top() == ElseIsTrue && rawtok->str() != ELIF && + rawtok->str() != ELIFDEF && rawtok->str() != ELIFNDEF)) { conditionIsTrue = false; } - else if (rawtok->str() == IFDEF) { - conditionIsTrue = (macros.find(rawtok->next->str()) != macros.end() || (hasInclude && rawtok->next->str() == HAS_INCLUDE)); - maybeUsedMacros[rawtok->next->str()].emplace_back(rawtok->next->location); - } else if (rawtok->str() == IFNDEF) { - conditionIsTrue = (macros.find(rawtok->next->str()) == macros.end() && !(hasInclude && rawtok->next->str() == HAS_INCLUDE)); - maybeUsedMacros[rawtok->next->str()].emplace_back(rawtok->next->location); + else if (rawtok->str() == IFDEF || rawtok->str() == ELIFDEF) { + const std::string &name = rawtok->next->str(); + conditionIsTrue = (macros.find(name) != macros.end() || (hasInclude && name == HAS_INCLUDE)); + maybeUsedMacros[name].emplace_back(rawtok->next->location); + if (ifCond) { + const std::string E = conditionIsTrue ? "1" : "0"; + const long long result = conditionIsTrue ? 1 : 0; + ifCond->emplace_back(rawtok->location, E, result); + } + } else if (rawtok->str() == IFNDEF || rawtok->str() == ELIFNDEF) { + const std::string &name = rawtok->next->str(); + conditionIsTrue = (macros.find(name) == macros.end() && !(hasInclude && name == HAS_INCLUDE)); + maybeUsedMacros[name].emplace_back(rawtok->next->location); + if (ifCond) { + const std::string E = conditionIsTrue ? "1" : "0"; + const long long result = conditionIsTrue ? 1 : 0; + ifCond->emplace_back(rawtok->location, E, result); + } } else { /*if (rawtok->str() == IF || rawtok->str() == ELIF)*/ TokenList expr(files); for (const Token *tok = rawtok->next; tok && tok->location.sameline(rawtok->location); tok = tok->next) { @@ -3671,7 +3875,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL bool closingAngularBracket = false; if (tok) { const std::string &sourcefile = rawtokens.file(rawtok->location); - const bool systemheader = (tok && tok->op == '<'); + const bool systemheader = tok->op == '<'; std::string header; if (systemheader) { @@ -3709,7 +3913,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL maybeUsedMacros[rawtok->next->str()].emplace_back(rawtok->next->location); const Token *tmp = tok; - if (!preprocessToken(expr, tmp, macros, files, outputList)) { + if (!preprocessToken(expr, tmp, macros, files, outputList, dui)) { output.clear(); return; } @@ -3746,7 +3950,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL } } - if (rawtok->str() != ELIF) { + if (rawtok->str() != ELIF && rawtok->str() != ELIFDEF && rawtok->str() != ELIFNDEF) { // push a new ifstate.. if (ifstates.top() != True) ifstates.push(AlwaysFalse); @@ -3807,7 +4011,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL const Location loc(rawtok->location); TokenList tokens(files); - if (!preprocessToken(tokens, rawtok, macros, files, outputList)) { + if (!preprocessToken(tokens, rawtok, macros, files, outputList, dui)) { output.clear(); return; } @@ -3866,6 +4070,20 @@ simplecpp::cstd_t simplecpp::getCStd(const std::string &std) return CUnknown; } +const char *simplecpp::getCStdName(cstd_t std) +{ + switch (std) { + case CUnknown: return "C"; + case C89: return "C89"; + case C99: return "C99"; + case C11: return "C11"; + case C17: return "C17"; + case C23: return "C23"; + case C2Y: return "C2Y"; + } + return ""; +} + std::string simplecpp::getCStdString(cstd_t std) { switch (std) { @@ -3918,6 +4136,21 @@ simplecpp::cppstd_t simplecpp::getCppStd(const std::string &std) return CPPUnknown; } +const char *simplecpp::getCppStdName(cppstd_t std) +{ + switch (std) { + case CPPUnknown: return "C++"; + case CPP03: return "C++03"; + case CPP11: return "C++11"; + case CPP14: return "C++14"; + case CPP17: return "C++17"; + case CPP20: return "C++20"; + case CPP23: return "C++23"; + case CPP26: return "C++26"; + } + return ""; +} + std::string simplecpp::getCppStdString(cppstd_t std) { switch (std) { diff --git a/externals/simplecpp/simplecpp.h b/externals/simplecpp/simplecpp.h index d499b132b69..f4dca18374a 100644 --- a/externals/simplecpp/simplecpp.h +++ b/externals/simplecpp/simplecpp.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -41,10 +42,6 @@ # define SIMPLECPP_LIB #endif -#ifndef _WIN32 -# include -#endif - #if defined(_MSC_VER) # pragma warning(push) // suppress warnings about "conversion from 'type1' to 'type2', possible loss of data" @@ -63,6 +60,15 @@ # endif #endif +#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ <= 9 +// Hack to workaround GCC bug. +// Details: https://trac.cppcheck.net/ticket/14850 +// seen on g++ before 10.x +#define SIMPLECPP_NOEXCEPT +#else +#define SIMPLECPP_NOEXCEPT noexcept +#endif + namespace simplecpp { /** C code standard */ enum cstd_t : std::int8_t { CUnknown=-1, C89, C99, C11, C17, C23, C2Y }; @@ -174,9 +180,9 @@ namespace simplecpp { bool isOneOf(const char ops[]) const; bool startsWithOneOf(const char c[]) const; bool endsWithOneOf(const char c[]) const; - static bool isNumberLike(const std::string& str) { - return std::isdigit(static_cast(str[0])) || - (str.size() > 1U && (str[0] == '-' || str[0] == '+') && std::isdigit(static_cast(str[1]))); + static bool isNumberLike(const std::string& s) { + return std::isdigit(static_cast(s[0])) || + (s.size() > 1U && (s[0] == '-' || s[0] == '+') && std::isdigit(static_cast(s[1]))); } TokenString macro; @@ -213,6 +219,9 @@ namespace simplecpp { bool isExpandedFrom(const Macro* m) const { return mExpandedFrom.find(m) != mExpandedFrom.end(); } + void markExpandedFrom(const Macro* m) { + mExpandedFrom.insert(m); + } void printAll() const; void printOut() const; @@ -238,7 +247,10 @@ namespace simplecpp { MISSING_HEADER, INCLUDE_NESTED_TOO_DEEPLY, SYNTAX_ERROR, + DIRECTIVE_AS_MACRO_PARAMETER, PORTABILITY_BACKSLASH, + PORTABILITY_LINE_DIRECTIVE, + PORTABILITY_NO_EOF_NEWLINE, UNHANDLED_CHAR_ERROR, EXPLICIT_INCLUDE_NOT_FOUND, FILE_NOT_FOUND, @@ -251,6 +263,21 @@ namespace simplecpp { using OutputList = std::list; + /** + * Command line preprocessor settings. + * On the command line these are configured by -D, -U, -I, --include, -std + */ + struct SIMPLECPP_LIB DUI { + DUI() = default; + std::list defines; + std::set undefined; + std::list includePaths; + std::list includes; + std::string std; + bool clearIncludeCache{}; + bool removeComments{}; /** remove comment tokens from included files */ + }; + /** List of tokens. */ class SIMPLECPP_LIB TokenList { public: @@ -258,45 +285,45 @@ namespace simplecpp { explicit TokenList(std::vector &filenames); /** generates a token list from the given std::istream parameter */ - TokenList(std::istream &istr, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr); + TokenList(std::istream &istr, std::vector &filenames, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr); /** generates a token list from the given buffer */ template - TokenList(const char (&data)[size], std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) - : TokenList(reinterpret_cast(data), size-1, filenames, filename, outputList, 0) + TokenList(const char (&data)[size], std::vector &filenames, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr) + : TokenList(reinterpret_cast(data), size-1, filenames, filename, dui, outputList, 0) {} /** generates a token list from the given buffer */ template - TokenList(const unsigned char (&data)[size], std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) - : TokenList(data, size-1, filenames, filename, outputList, 0) + TokenList(const unsigned char (&data)[size], std::vector &filenames, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr) + : TokenList(data, size-1, filenames, filename, dui, outputList, 0) {} #if SIMPLECPP_TOKENLIST_ALLOW_PTR /** generates a token list from the given buffer */ - TokenList(const unsigned char* data, std::size_t size, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) - : TokenList(data, size, filenames, filename, outputList, 0) + TokenList(const unsigned char* data, std::size_t size, std::vector &filenames, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr) + : TokenList(data, size, filenames, filename, dui, outputList, 0) {} /** generates a token list from the given buffer */ - TokenList(const char* data, std::size_t size, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) - : TokenList(reinterpret_cast(data), size, filenames, filename, outputList, 0) + TokenList(const char* data, std::size_t size, std::vector &filenames, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr) + : TokenList(reinterpret_cast(data), size, filenames, filename, dui, outputList, 0) {} #endif // SIMPLECPP_TOKENLIST_ALLOW_PTR /** generates a token list from the given buffer */ - TokenList(View data, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) - : TokenList(reinterpret_cast(data.data()), data.size(), filenames, filename, outputList, 0) + TokenList(View data, std::vector &filenames, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr) + : TokenList(reinterpret_cast(data.data()), data.size(), filenames, filename, dui, outputList, 0) {} #ifdef __cpp_lib_span /** generates a token list from the given buffer */ - TokenList(std::span data, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) - : TokenList(reinterpret_cast(data.data()), data.size(), filenames, filename, outputList, 0) + TokenList(std::span data, std::vector &filenames, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr) + : TokenList(reinterpret_cast(data.data()), data.size(), filenames, filename, dui, outputList, 0) {} /** generates a token list from the given buffer */ - TokenList(std::span data, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) - : TokenList(data.data(), data.size(), filenames, filename, outputList, 0) + TokenList(std::span data, std::vector &filenames, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr) + : TokenList(data.data(), data.size(), filenames, filename, dui, outputList, 0) {} #endif // __cpp_lib_span /** generates a token list from the given filename parameter */ - TokenList(const std::string &filename, std::vector &filenames, OutputList *outputList = nullptr); + TokenList(const std::string &filename, std::vector &filenames, const DUI &dui = {}, OutputList *outputList = nullptr); TokenList(const TokenList &other); TokenList(TokenList &&other); ~TokenList(); @@ -312,7 +339,7 @@ namespace simplecpp { void dump(bool linenrs = false) const; std::string stringify(bool linenrs = false) const; - void readfile(Stream &stream, const std::string &filename=std::string(), OutputList *outputList = nullptr); + void readfile(Stream &stream, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr); /** * @throws std::overflow_error thrown on overflow or division by zero * @throws std::runtime_error thrown on invalid expressions @@ -376,7 +403,7 @@ namespace simplecpp { const std::string& file(const Location& loc) const; private: - TokenList(const unsigned char* data, std::size_t size, std::vector &filenames, const std::string &filename, OutputList *outputList, int unused); + TokenList(const unsigned char* data, std::size_t size, std::vector &filenames, const std::string &filename, const DUI &dui, OutputList *outputList, int /*unused*/); void combineOperators(); @@ -396,7 +423,7 @@ namespace simplecpp { void constFoldQuestionOp(Token *&tok1); std::string readUntil(Stream &stream, const Location &location, char start, char end, OutputList *outputList); - void lineDirective(unsigned int fileIndex, unsigned int line, Location &location); + void lineDirective(unsigned int fileIndex_, unsigned int line, Location &location); const Token* lastLineTok(int maxsize=1000) const; const Token* isLastLinePreprocessor(int maxsize=1000) const; @@ -425,21 +452,6 @@ namespace simplecpp { long long result; // condition result }; - /** - * Command line preprocessor settings. - * On the command line these are configured by -D, -U, -I, --include, -std - */ - struct SIMPLECPP_LIB DUI { - DUI() = default; - std::list defines; - std::set undefined; - std::list includePaths; - std::list includes; - std::string std; - bool clearIncludeCache{}; - bool removeComments{}; /** remove comment tokens from included files */ - }; - struct SIMPLECPP_LIB FileData { /** The canonical filename associated with this data */ std::string filename; @@ -449,13 +461,14 @@ namespace simplecpp { class SIMPLECPP_LIB FileDataCache { public: - FileDataCache() = default; + FileDataCache(); + ~FileDataCache(); FileDataCache(const FileDataCache &) = delete; - FileDataCache(FileDataCache &&) = default; + FileDataCache(FileDataCache &&) SIMPLECPP_NOEXCEPT; FileDataCache &operator=(const FileDataCache &) = delete; - FileDataCache &operator=(FileDataCache &&) = default; + FileDataCache &operator=(FileDataCache &&) SIMPLECPP_NOEXCEPT; /** Get the cached data for a file, or load and then return it if it isn't cached. * returns the file data and true if the file was loaded, false if it was cached. */ @@ -469,11 +482,7 @@ namespace simplecpp { mNameMap.emplace(newdata->filename, newdata); } - void clear() { - mNameMap.clear(); - mIdMap.clear(); - mData.clear(); - } + void clear(); using container_type = std::vector>; using iterator = container_type::iterator; @@ -502,52 +511,24 @@ namespace simplecpp { return mData.cend(); } - private: - struct FileID { -#ifdef _WIN32 - struct { - std::uint64_t VolumeSerialNumber; - struct { - std::uint64_t IdentifierHi; - std::uint64_t IdentifierLo; - } FileId; - } fileIdInfo; - - bool operator==(const FileID &that) const noexcept { - return fileIdInfo.VolumeSerialNumber == that.fileIdInfo.VolumeSerialNumber && - fileIdInfo.FileId.IdentifierHi == that.fileIdInfo.FileId.IdentifierHi && - fileIdInfo.FileId.IdentifierLo == that.fileIdInfo.FileId.IdentifierLo; - } -#else - dev_t dev; - ino_t ino; + using load_callback_type = std::function; - bool operator==(const FileID& that) const noexcept { - return dev == that.dev && ino == that.ino; - } -#endif - struct Hasher { - std::size_t operator()(const FileID &id) const { -#ifdef _WIN32 - return static_cast(id.fileIdInfo.FileId.IdentifierHi ^ id.fileIdInfo.FileId.IdentifierLo ^ - id.fileIdInfo.VolumeSerialNumber); -#else - return static_cast(id.dev) ^ static_cast(id.ino); -#endif - } - }; - }; + void set_load_callback(load_callback_type cb) { + mLoadCallback = std::move(cb); + } - using name_map_type = std::unordered_map; - using id_map_type = std::unordered_map; + private: + struct Impl; + std::unique_ptr mImpl; - static bool getFileId(const std::string &path, FileID &id); + using name_map_type = std::unordered_map; std::pair tryload(name_map_type::iterator &name_it, const DUI &dui, std::vector &filenames, OutputList *outputList); + std::pair get_private(const std::string &sourcefile, const std::string &header, const DUI &dui, bool systemheader, std::vector &filenames, OutputList *outputList); container_type mData; name_map_type mNameMap; - id_map_type mIdMap; + load_callback_type mLoadCallback; }; /** Converts character literal (including prefix, but not ud-suffix) to long long value. @@ -610,9 +591,15 @@ namespace simplecpp { /** Returns the C version a given standard */ SIMPLECPP_LIB cstd_t getCStd(const std::string &std); + /** Returns the name of a C standard */ + SIMPLECPP_LIB const char *getCStdName(cstd_t std); + /** Returns the C++ version a given standard */ SIMPLECPP_LIB cppstd_t getCppStd(const std::string &std); + /** Returns the name of a C++ standard */ + SIMPLECPP_LIB const char *getCppStdName(cppstd_t std); + /** Returns the __STDC_VERSION__ value for a given standard */ SIMPLECPP_LIB std::string getCStdString(const std::string &std); SIMPLECPP_LIB std::string getCStdString(cstd_t std); diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt index d640c341458..2acb07f03c0 100644 --- a/gui/CMakeLists.txt +++ b/gui/CMakeLists.txt @@ -15,9 +15,7 @@ CheckOptions: file(GLOB tss "*.ts") QT_WRAP_UI(uis_hdrs ${uis}) QT_ADD_RESOURCES(resources "gui.qrc") - # TODO: passing "-no-obsolete" here breaks the translations - QT_CREATE_TRANSLATION(qms ${CMAKE_CURRENT_SOURCE_DIR} ${tss}) - list(APPEND cppcheck-gui-deps ${hdrs} ${uis_hdrs} ${resources} ${qms}) + list(APPEND cppcheck-gui-deps ${hdrs} ${uis_hdrs} ${resources}) add_custom_target(gui-build-deps SOURCES ${cppcheck-gui-deps}) list(APPEND cppcheck-gui_SOURCES ${srcs}) @@ -27,6 +25,10 @@ CheckOptions: add_executable(cppcheck-gui ${cppcheck-gui-deps} ${cppcheck-gui_SOURCES}) target_link_libraries(cppcheck-gui cppcheck-core simplecpp tinyxml2 picojson frontend) + qt_add_translations(cppcheck-gui + TS_FILES ${tss} + QM_FILES_OUTPUT_VARIABLE qms) + add_dependencies(cppcheck-gui release_translations) set_target_properties(cppcheck-gui PROPERTIES AUTOMOC ON) set_target_properties(cppcheck-gui PROPERTIES WIN32_EXECUTABLE ON) @@ -71,8 +73,8 @@ CheckOptions: add_dependencies(cppcheck-gui online-help.qhc) endif() - install(TARGETS cppcheck-gui RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR} COMPONENT applications) - install(FILES ${qms} DESTINATION ${CMAKE_INSTALL_FULL_BINDIR} COMPONENT applications) + install(TARGETS cppcheck-gui RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT applications) + install(FILES ${qms} DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT applications) install(FILES cppcheck-gui.desktop DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications) diff --git a/gui/application.cpp b/gui/application.cpp index e3cc7b9957b..af68f98bb70 100644 --- a/gui/application.cpp +++ b/gui/application.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2023 Cppcheck team. + * Copyright (C) 2007-2022 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/gui/applicationdialog.cpp b/gui/applicationdialog.cpp index bc8804d6f97..4725f59fc39 100644 --- a/gui/applicationdialog.cpp +++ b/gui/applicationdialog.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2023 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -80,7 +80,7 @@ void ApplicationDialog::ok() { if (mUI->mName->text().isEmpty() || mUI->mPath->text().isEmpty()) { QMessageBox msg(QMessageBox::Warning, - tr("Cppcheck"), + "Cppcheck", tr("You must specify a name, a path and optionally parameters for the application!"), QMessageBox::Ok, this); diff --git a/gui/checkstatistics.cpp b/gui/checkstatistics.cpp index 612e79da63b..830f5eb6b4e 100644 --- a/gui/checkstatistics.cpp +++ b/gui/checkstatistics.cpp @@ -109,10 +109,10 @@ unsigned CheckStatistics::getCount(const QString &tool, ShowTypes::ShowType type QStringList CheckStatistics::getTools() const { QSet ret; - for (const QString& tool: mStyle.keys()) ret.insert(tool); - for (const QString& tool: mWarning.keys()) ret.insert(tool); - for (const QString& tool: mPerformance.keys()) ret.insert(tool); - for (const QString& tool: mPortability.keys()) ret.insert(tool); - for (const QString& tool: mError.keys()) ret.insert(tool); + std::copy(mStyle.keyBegin(), mStyle.keyEnd(), std::inserter(ret, ret.end())); + std::copy(mWarning.keyBegin(), mWarning.keyEnd(), std::inserter(ret, ret.end())); + std::copy(mPerformance.keyBegin(), mPerformance.keyEnd(), std::inserter(ret, ret.end())); + std::copy(mPortability.keyBegin(), mPortability.keyEnd(), std::inserter(ret, ret.end())); + std::copy(mError.keyBegin(), mError.keyEnd(), std::inserter(ret, ret.end())); return ret.values(); } diff --git a/gui/checkthread.cpp b/gui/checkthread.cpp index 07ca4e77ed2..2b4c9ba654f 100644 --- a/gui/checkthread.cpp +++ b/gui/checkthread.cpp @@ -113,13 +113,15 @@ CheckThread::CheckThread(ThreadResult &result, int threadIndex) void CheckThread::setSettings(const Settings &settings, std::shared_ptr supprs) { mFiles.clear(); + mFileSettings.clear(); mSettings = settings; // this is a copy mSuppressions = std::move(supprs); } -void CheckThread::analyseWholeProgram(const std::list &files, const std::string& ctuInfo) +void CheckThread::analyseWholeProgram(const std::list &files, const std::list &fileSettings, const std::string& ctuInfo) { mFiles = files; + mFileSettings = fileSettings; mAnalyseWholeProgram = true; mCtuInfo = ctuInfo; start(); @@ -136,8 +138,9 @@ void CheckThread::run() std::string ctuInfo; ctuInfo.swap(mCtuInfo); qDebug() << "Whole program analysis"; - cppcheck.analyseWholeProgram(mSettings.buildDir, mFiles, {}, ctuInfo); + cppcheck.analyseWholeProgram(mSettings.buildDir, mFiles, mFileSettings, ctuInfo); mFiles.clear(); + mFileSettings.clear(); emit done(); return; } diff --git a/gui/checkthread.h b/gui/checkthread.h index a630ef7fc13..74eb292e6e2 100644 --- a/gui/checkthread.h +++ b/gui/checkthread.h @@ -69,10 +69,11 @@ class CheckThread : public QThread { /** * @brief Run whole program analysis - * @param files All files - * @param ctuInfo Ctu info for addons + * @param files All files + * @param fileSettings All files of the project + * @param ctuInfo Ctu info for addons */ - void analyseWholeProgram(const std::list &files, const std::string& ctuInfo); + void analyseWholeProgram(const std::list &files, const std::list &fileSettings, const std::string& ctuInfo); void setAddonsAndTools(const QStringList &addonsAndTools) { mAddonsAndTools = addonsAndTools; @@ -153,6 +154,7 @@ class CheckThread : public QThread { bool isSuppressed(const SuppressionList::ErrorMessage &errorMessage) const; std::list mFiles; + std::list mFileSettings; bool mAnalyseWholeProgram{}; std::string mCtuInfo; QStringList mAddonsAndTools; diff --git a/gui/codeeditor.h b/gui/codeeditor.h index 4dfbaa66047..00205d1c4e1 100644 --- a/gui/codeeditor.h +++ b/gui/codeeditor.h @@ -129,9 +129,9 @@ class CodeEditor : public QPlainTextEdit { void resizeEvent(QResizeEvent *event) override; private slots: - void updateLineNumberAreaWidth(int newBlockCount); + void updateLineNumberAreaWidth(int /*newBlockCount*/); void highlightErrorLine(); - void updateLineNumberArea(const QRect & /*rect*/, int /*dy*/); + void updateLineNumberArea(const QRect& rect, int dy); private: QString generateStyleString(); diff --git a/gui/compliancereportdialog.cpp b/gui/compliancereportdialog.cpp deleted file mode 100644 index c61299e6e3c..00000000000 --- a/gui/compliancereportdialog.cpp +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "compliancereportdialog.h" - -#include "ui_compliancereportdialog.h" - -#include "errortypes.h" -#include "filelist.h" -#include "filesettings.h" -#include "importproject.h" -#include "projectfile.h" -#include "utils.h" - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static void addHeaders(const QString& file1, QSet &allFiles) { - if (allFiles.contains(file1)) - return; - QFile file(file1); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) - return; - allFiles << file1; - const QRegularExpression re("^#include[ ]*\"([^\">]+)\".*"); - QTextStream in(&file); - QString line = in.readLine(); - while (!in.atEnd()) { - if (line.startsWith("#include")) { - const QRegularExpressionMatch match = re.match(line); - if (match.hasMatch()) { - QString hfile = match.captured(1); - if (file1.contains("/")) - hfile = file1.mid(0,file1.lastIndexOf("/") + 1) + hfile; - addHeaders(hfile, allFiles); - } - } - line = in.readLine(); - } -} - -static std::vector toStdStringList(const QStringList& from) { - std::vector ret; - std::transform(from.cbegin(), from.cend(), std::back_inserter(ret), [](const QString& e) { - return e.toStdString(); - }); - return ret; -} - -ComplianceReportDialog::ComplianceReportDialog(ProjectFile* projectFile, QString resultsFile, QString checkersReport) - : QDialog(nullptr), - mUI(new Ui::ComplianceReportDialog), - mProjectFile(projectFile), - mResultsFile(std::move(resultsFile)), - mCheckersReport(std::move(checkersReport)) -{ - mUI->setupUi(this); - mUI->mEditProjectName->setText(projectFile->getProjectName()); - connect(mUI->buttonBox, &QDialogButtonBox::clicked, this, &ComplianceReportDialog::buttonClicked); - mUI->mCodingStandard->clear(); - if (!projectFile->getCodingStandards().contains("misra-c-2023") && projectFile->getAddons().contains("misra")) - mUI->mCodingStandard->addItem("Misra C 2012"); - for (QString std: projectFile->getCodingStandards()) { - std[0] = std[0].toUpper(); - std = std.replace("-", " ").replace(" c ", " C ").replace(" cpp ", " C++ ").replace(" c++ ", " C++ "); - mUI->mCodingStandard->addItem(std); - } -} - -ComplianceReportDialog::~ComplianceReportDialog() -{ - delete mUI; -} - -void ComplianceReportDialog::buttonClicked(QAbstractButton* button) -{ - switch (mUI->buttonBox->standardButton(button)) { - case QDialogButtonBox::StandardButton::Save: - save(); - break; - case QDialogButtonBox::StandardButton::Close: - close(); - break; - default: - break; - } -} - -void ComplianceReportDialog::save() -{ - const QString std(mUI->mCodingStandard->currentText().toLower().replace(" ", "-")); - - const QString outFile = QFileDialog::getSaveFileName(this, - tr("Compliance report"), - QDir::homePath() + "/" + std + "-compliance-report.html", - tr("HTML files (*.html)")); - if (outFile.isEmpty()) - return; - - close(); - - const QString& projectName = mUI->mEditProjectName->text(); - const QString& projectVersion = mUI->mEditProjectVersion->text(); - const bool files = mUI->mCheckFiles->isChecked(); - - if (projectName != mProjectFile->getProjectName()) { - mProjectFile->setProjectName(projectName); - mProjectFile->write(); - } - - QTemporaryFile tempCheckersReport; - if (tempCheckersReport.open()) { - QTextStream out(&tempCheckersReport); - out << mCheckersReport << "\n"; - tempCheckersReport.close(); - } - - QTemporaryFile tempFiles; - if (files && tempFiles.open()) { - QTextStream out(&tempFiles); - FileList fileList; - fileList.addPathList(mProjectFile->getCheckPaths()); - if (!mProjectFile->getImportProject().isEmpty()) { - QFileInfo inf(mProjectFile->getFilename()); - - QString prjfile; - if (QFileInfo(mProjectFile->getImportProject()).isAbsolute()) - prjfile = mProjectFile->getImportProject(); - else - prjfile = inf.canonicalPath() + '/' + mProjectFile->getImportProject(); - - ImportProject p; - try { - p.import(prjfile.toStdString()); - } catch (InternalError &e) { - QMessageBox msg(QMessageBox::Critical, - tr("Save compliance report"), - tr("Failed to import '%1' (%2), can not show files in compliance report").arg(prjfile).arg(QString::fromStdString(e.errorMessage)), - QMessageBox::Ok, - this); - msg.exec(); - return; - } - - p.ignorePaths(toStdStringList(mProjectFile->getExcludedPaths())); - - QDir dir(inf.absoluteDir()); - for (const FileSettings& fs: p.fileSettings) - fileList.addFile(dir.relativeFilePath(QString::fromStdString(fs.filename()))); - } - - QSet allFiles; - for (const QString &sourcefile: fileList.getFileList()) - addHeaders(sourcefile, allFiles); - for (const QString& fileName: utils::as_const(allFiles)) { - QFile f(fileName); - if (f.open(QFile::ReadOnly)) { - QCryptographicHash hash(QCryptographicHash::Algorithm::Md5); - if (hash.addData(&f)) { - for (auto b: hash.result()) - out << QString::number(static_cast(b),16); - out << " " << fileName << "\n"; - } - } - } - tempFiles.close(); - } - - QStringList suppressions; - for (const auto& suppression: mProjectFile->getSuppressions()) { - if (!suppression.errorId.empty()) - suppressions.append(QString::fromStdString(suppression.errorId)); - } - - QStringList args{"--project-name=" + projectName, - "--project-version=" + projectVersion, - "--output-file=" + outFile, - "--checkers-report=" + tempCheckersReport.fileName()}; - if (!suppressions.isEmpty()) - args << "--suppressions=" + suppressions.join(","); - - args << ("--" + std); - - if (files) - args << "--files=" + tempFiles.fileName(); - args << mResultsFile; - - const QString appPath = QFileInfo(QCoreApplication::applicationFilePath()).canonicalPath(); - - QProcess process; -#ifdef Q_OS_WIN - process.start(appPath + "/compliance-report.exe", args); -#else - process.start(appPath + "/compliance-report", args); -#endif - process.waitForFinished(); - const QString output = process.readAll(); - if (!output.isEmpty()) { - QMessageBox msg(QMessageBox::Critical, - tr("Save compliance report"), - output, - QMessageBox::Ok, - this); - msg.exec(); - } -} diff --git a/gui/compliancereportdialog.h b/gui/compliancereportdialog.h deleted file mode 100644 index c7ef27f1cc5..00000000000 --- a/gui/compliancereportdialog.h +++ /dev/null @@ -1,53 +0,0 @@ -/* -*- C++ -*- - * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2024 Cppcheck team. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#ifndef COMPLIANCEREPORTDIALOG_H -#define COMPLIANCEREPORTDIALOG_H - -#include -#include -#include - -namespace Ui { - class ComplianceReportDialog; -} - -class ProjectFile; -class QAbstractButton; - -class ComplianceReportDialog final : public QDialog -{ - Q_OBJECT - -public: - explicit ComplianceReportDialog(ProjectFile* projectFile, QString resultsFile, QString checkersReport); - ~ComplianceReportDialog() final; - -private slots: - void buttonClicked(QAbstractButton* button); - -private: - void save(); - - Ui::ComplianceReportDialog *mUI; - ProjectFile* mProjectFile; - const QString mResultsFile; - const QString mCheckersReport; -}; - -#endif // COMPLIANCEREPORTDIALOG_H diff --git a/gui/compliancereportdialog.ui b/gui/compliancereportdialog.ui deleted file mode 100644 index b756ec7eab5..00000000000 --- a/gui/compliancereportdialog.ui +++ /dev/null @@ -1,104 +0,0 @@ - - - ComplianceReportDialog - - - - 0 - 0 - 403 - 199 - - - - Compliance Report - - - - - - - - Project version - - - - - - - Project name - - - - - - - - - - - - - Coding Standard - - - - - - - - Misra C - - - - - Cert C - - - - - Cert C++ - - - - - - - - - - List of files with md5 checksums - - - true - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Close|QDialogButtonBox::Save - - - - - - - - diff --git a/gui/cppcheck_de.ts b/gui/cppcheck_de.ts index c0d68ed6d8a..ee401f8c2a4 100644 --- a/gui/cppcheck_de.ts +++ b/gui/cppcheck_de.ts @@ -5,27 +5,32 @@ About + About Cppcheck Über Cppcheck + Version %1 Version %1 + Cppcheck - A tool for static C/C++ code analysis. Cppcheck - Ein Werkzeug zur statischen C/C++-Code-Analyse. + Copyright © 2007-%1 Cppcheck team. Copyright © 2007-2021 Cppcheck team. Copyright © 2007-%1 Cppcheck-Team. + This program is licensed under the terms of the GNU General Public License version 3 Dieses Programm ist unter den Bedingungen @@ -33,11 +38,13 @@ der GNU General Public License Version 3 lizenziert + Visit Cppcheck homepage at %1 Besuchen Sie die Cppcheck-Homepage unter %1 + <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PCRE</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PicoJSON</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TinyXML2</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Boost</li></ul></body></html> <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">pcre</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">picojson</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">tinyxml2</li></ul></body></html> <html><head/><body><p>Vielen Dank den Bibliotheken die wir nutzen:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PCRE</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PicoJSON</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TinyXML2</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Boost</li></ul></body></html> @@ -47,11 +54,13 @@ der GNU General Public License Version 3 lizenziert ApplicationDialog + Add an application Anwendung hinzufügen + Here you can add an application that can open error files. Specify a name for the application, the application executable and command line parameters for the application. The following texts in parameters are replaced with appropriate values when application is executed: @@ -78,21 +87,25 @@ Parameter: -l(line) (file) + &Name: &Name: + &Executable: &Ausführbare Datei: + &Parameters: &Parameter: + Browse Suchen @@ -107,9 +120,8 @@ Parameter: -l(line) (file) Anzeigeanwendung auswählen - Cppcheck - Cppcheck + Cppcheck @@ -120,65 +132,52 @@ Parameter: -l(line) (file) ComplianceReportDialog - Compliance Report - Compliance-Report + Compliance-Report - Project name - Projektname + Projektname - Project version - Projekt-Version + Projekt-Version - Coding Standard - Programmierstandards + Programmierstandards - Misra C - Misra C + Misra C - Cert C - Cert C + Cert C - Cert C++ - Cert C++ + Cert C++ - List of files with md5 checksums - Datei-Listen mit MD5-Prüfsummen + Datei-Listen mit MD5-Prüfsummen - Compliance report - Compliance-Report + Compliance-Report - HTML files (*.html) - HTML-Dateien (*.html) + HTML-Dateien (*.html) - - Save compliance report - Compliance-Report speichern + Compliance-Report speichern - Failed to import '%1' (%2), can not show files in compliance report - Import von '%1' (%2) fehlgeschlagen, Compliance-Report kann nicht angezeigt werden + Import von '%1' (%2) fehlgeschlagen, Compliance-Report kann nicht angezeigt werden @@ -189,10 +188,8 @@ Parameter: -l(line) (file) Konnte die Datei nicht finden: %1 - - Cppcheck - Cppcheck + Cppcheck @@ -204,44 +201,49 @@ Parameter: -l(line) (file) HelpDialog + Cppcheck GUI help Cppcheck GUI Hilfe + Contents Inhalt + Index Index - + Helpfile '%1' was not found Hilfedatei '%1' nicht gefunden - Cppcheck - Cppcheck + Cppcheck LibraryAddFunctionDialog + Add function Funktion hinzufügen + Function name(s) Funktionsname(n) + Number of arguments Anzahl Argumente @@ -250,86 +252,103 @@ Parameter: -l(line) (file) LibraryDialog + Library Editor Bibliothekseditor + Open Öffnen + Save Speichern + Save as Speichern unter + Functions Funktionen + Sort Sortiere + Add Hinzufügen + Filter: Filter: + Comments Kommentare + noreturn Zurückkehrend + False Ja + True Nein + Unknown Unbekannt + return value must be used Rückgabewert muss genutzt werden + ignore function in leaks checking Ignoriere Funktion in Speicherleck-Prüfung + Arguments Argumente + Edit Bearbeiten @@ -345,11 +364,8 @@ Parameter: -l(line) (file) Bibliothek öffnen - - - Cppcheck - Cppcheck + Cppcheck @@ -376,11 +392,13 @@ Parameter: -l(line) (file) LibraryEditArgDialog + Edit argument Argument bearbeiten + <html><head/><body> <p>Is bool value allowed? For instance result from comparison or from '!' operator.</p> <p>Typically, set this if the argument is a pointer, size, etc.</p> @@ -396,11 +414,13 @@ Parameter: -l(line) (file) + Not bool Nicht boolsch + <html><head/><body> <p>Is a null parameter value allowed?</p> <p>Typically this should be used on any pointer parameter that does not allow null.</p> @@ -416,78 +436,99 @@ Parameter: -l(line) (file) + Not null Nicht Null + Not uninit Nicht uninitialisiert + String String + Format string Formatstring + Min size of buffer Minimale Puffergröße + + Type Typ + + None Keine + + argvalue Argumentwert + + mul Multiplikation + + strlen strlen + + Arg Argument 1 + + Arg2 Argument 2 + and und + Valid values Zulässige Werte @@ -495,618 +536,711 @@ Parameter: -l(line) (file) MainWindow - - - - - - - - - - - - - - - Cppcheck - Cppcheck + Cppcheck - + + Standard Standard + &File &Datei - + + &View &Ansicht - + + &Toolbars &Symbolleisten - + + A&nalyze A&nalysieren - + + C++ standard C++-Standard - + + &C standard &C-Standard - + + &Edit &Bearbeiten - + + &License... &Lizenz... - + + A&uthors... &Autoren... - + + &About... Ü&ber... - + + &Files... &Dateien... - - + + + + Analyze files Analysiere Dateien - + + Ctrl+F Strg+F - + + &Directory... &Verzeichnis... - - + + + + Analyze directory Analysiere Verzeichnis - + + Ctrl+D Strg+D - + + Ctrl+R Strg+R - + + &Stop &Stoppen - - + + + + Stop analysis Analyse abbrechen - + + Esc Esc - + + &Save results to file... &Ergebnisse in Datei speichern... - + + Ctrl+S Strg+S - + + &Quit &Beenden - + + &Clear results Ergebnisse &löschen - + + &Preferences &Einstellungen - - - + + + + + Show errors Zeige Fehler - - - + + + + + Show warnings Zeige Warnungen - - + + + + Show performance warnings Zeige Performance-Warnungen - + + Show &hidden Zeige &versteckte - - + + Information Information - + + Show information messages Zeige Informationsmeldungen - + + Show portability warnings Zeige Portabilitätswarnungen - + + Show Cppcheck results Zeige Cppcheck-Ergebnisse - + + Clang Clang - + + Show Clang results Zeige Clang-Ergebnisse - + + &Filter &Filter - + + Filter results Gefilterte Ergebnisse - + + Windows 32-bit ANSI Windows 32-bit, ANSI - + + Windows 32-bit Unicode Windows 32-bit, Unicode - + + Unix 32-bit Unix 32-bit - + + Unix 64-bit Unix 64-bit - + + Windows 64-bit Windows 64-bit - + + &Print... Drucken... - + + Print the Current Report Aktuellen Bericht ausdrucken - + + Print Pre&view... Druckvorschau - + + Open a Print Preview Dialog for the Current Results Druckvorschaudialog für aktuelle Ergebnisse öffnen - + + Open library editor Bibliothekseditor öffnen - + + &Check all Alle &auswählen + Checking for updates + Hide Verstecken - - + + + + Report - + + Filter Filter - + + &Reanalyze modified files Veränderte Dateien neu analysieren - + + Reanal&yze all files Alle Dateien erneut anal&ysieren - + + Ctrl+Q - + + Style war&nings Stilwar&nungen - + + E&rrors F&ehler - + + &Uncheck all Alle a&bwählen - + + Collapse &all Alle &reduzieren - + + &Expand all Alle &erweitern - + + &Standard &Standard - + + Standard items Standardeinträge - + + Toolbar Symbolleiste - + + &Categories &Kategorien - + + Error categories Fehler-Kategorien - + + &Open XML... Öffne &XML... - + + Open P&roject File... Pr&ojektdatei öffnen... - + + Ctrl+Shift+O - + + Sh&ow Scratchpad... &Zeige Schmierzettel... - + + &New Project File... &Neue Projektdatei... - + + Ctrl+Shift+N - + + &Log View &Loganzeige - + + Log View Loganzeige - + + C&lose Project File Projektdatei &schließen - + + &Edit Project File... Projektdatei &bearbeiten... - + + &Statistics &Statistik - + + &Warnings &Warnungen - + + Per&formance warnings Per&formance-Warnungen - + + &Information &Information - + + &Portability &Portabilität - + + P&latforms P&lattformen - + + C++&11 C++&11 - + + C&99 C&99 - + + &Posix Posix - + + C&11 C&11 - + + &C89 &C89 - + + &C++03 &C++03 - + + &Library Editor... &Bibliothekseditor - + + &Auto-detect language Sprache &automatisch erkennen - + + &Enforce C++ C++ &erzwingen - + + E&nforce C C e&rzwingen - + + C++14 C++14 - + + Reanalyze and check library Neu analysieren und Bibliothek prüfen - + + Check configuration (defines, includes) Prüfe Konfiguration (Definitionen, Includes) - + + C++17 C++17 - + + C++20 C++20 - + + Compliance report... - + + Normal - Misra C - Misra C + Misra C - - Misra C++ 2008 - - - - Cert C - Cert C + Cert C - Cert C++ - Cert C++ + Cert C++ - - Misra C++ 2023 - - - - + + Autosar - + + EULA... - + + Thread Details - + + Show thread details - + + &Contents &Inhalte - + + Categories Kategorien - - + + + + Show style warnings Zeige Stilwarnungen - + + Open the help contents Öffnet die Hilfe-Inhalte - + + F1 F1 - + + &Help &Hilfe - - + + Quick Filter: Schnellfilter: - + + None of the selected files is part of the project, nothing to analyze. + + + + Select configuration Konfiguration wählen - + Found project file: %1 Do you want to load this project file instead? @@ -1115,102 +1249,102 @@ Do you want to load this project file instead? Möchten Sie stattdessen diese öffnen? - + File not found Datei nicht gefunden - + Bad XML Fehlerhaftes XML - + Missing attribute Fehlendes Attribut - + Bad attribute value Falscher Attributwert - + Duplicate platform type Plattformtyp doppelt - + Platform type redefined Plattformtyp neu definiert - + Duplicate define - + Failed to load the selected library '%1'. %2 Laden der ausgewählten Bibliothek '%1' schlug fehl. %2 - + File not found: '%1' - + Failed to load/setup addon %1: %2 - + Failed to load %1. Your Cppcheck installation is broken. You can use --data-dir=<directory> at the command line to specify where this file is located. Please note that --data-dir is supposed to be used by installation scripts and therefore the GUI does not start when it is used, all that happens is that the setting is configured. Analysis is aborted. - + Failed to load %1 - %2 Analysis is aborted. - - + + %1 Analysis is aborted. - + License Lizenz - + Authors Autoren - + Save the report file Speichert die Berichtdatei - - + + XML files (*.xml) XML-Dateien (*.xml) - + There was a problem with loading the editor application settings. This is probably because the settings were changed between the Cppcheck versions. Please check (and fix) the editor application settings, otherwise the editor program might not start correctly. @@ -1219,38 +1353,32 @@ This is probably because the settings were changed between the Cppcheck versions Dies wurde vermutlich durch einen Wechsel der Cppcheck-Version hervorgerufen. Bitte prüfen (und korrigieren) Sie die Einstellungen, andernfalls könnte die Editor-Anwendung nicht korrekt starten. - + You must close the project file before selecting new files or directories! Sie müssen die Projektdatei schließen, bevor Sie neue Dateien oder Verzeichnisse auswählen! - + The library '%1' contains unknown elements: %2 Die Bibliothek '%1' enthält unbekannte Elemente: %2 - + Unsupported format Nicht unterstütztes Format - + Unknown element Unbekanntes Element - - Unknown element - Unknown issue - Unbekannter Fehler - - - - - - + + + + Error Fehler @@ -1259,80 +1387,78 @@ Dies wurde vermutlich durch einen Wechsel der Cppcheck-Version hervorgerufen. Bi Laden von %1 fehlgeschlagen. Ihre Cppcheck-Installation ist defekt. Sie können --data-dir=<Verzeichnis> als Kommandozeilenparameter verwenden, um anzugeben, wo die Datei sich befindet. Bitte beachten Sie, dass --data-dir in Installationsroutinen genutzt werden soll, und die GUI bei dessen Nutzung nicht startet, sondern die Einstellungen konfiguriert. - + Open the report file Berichtdatei öffnen - + Text files (*.txt) Textdateien (*.txt) - + CSV files (*.csv) CSV-Dateien (*.csv) - + Project files (*.cppcheck);;All files(*.*) Projektdateien (*.cppcheck);;Alle Dateien(*.*) - + Select Project File Projektdatei auswählen - - - - + + + + Project: Projekt: - + No suitable files found to analyze! Keine passenden Dateien für Analyse gefunden! - + C/C++ Source C/C++-Quellcode - + Compile database Compilerdatenbank - Visual Studio - Visual Studio + Visual Studio - Borland C++ Builder 6 - Borland C++-Builder 6 + Borland C++-Builder 6 - + Select files to analyze Dateien für Analyse auswählen - + Select directory to analyze Verzeichnis für Analyse auswählen - + Select the configuration that will be analyzed Zu analysierende Konfiguration auswählen - + Found project files from the directory. Do you want to proceed analysis without using any of these project files? @@ -1341,7 +1467,7 @@ Do you want to proceed analysis without using any of these project files? - + Current results will be cleared. Opening a new XML file will clear current results. @@ -1352,7 +1478,7 @@ Eine neue XML-Datei zu öffnen wird die aktuellen Ergebnisse löschen Möchten sie fortfahren? - + Analyzer is running. Do you want to stop the analysis and exit Cppcheck? @@ -1361,109 +1487,104 @@ Do you want to stop the analysis and exit Cppcheck? Wollen sie die Analyse abbrechen und Cppcheck beenden? - + About - + XML files (*.xml);;Text files (*.txt);;CSV files (*.csv) XML-Dateien (*.xml);;Textdateien (*.txt);;CSV-Dateien (*.csv) - - Cannot generate a compliance report right now, an analysis must finish successfully. Try to reanalyze the code and ensure there are no critical errors. - - - - + Build dir '%1' does not exist, create it? Erstellungsverzeichnis '%1' existiert nicht. Erstellen? - + To check the project using addons, you need a build directory. - + Failed to open file - + Unknown project file format - + Failed to import project file - + Failed to import '%1': %2 Analysis is stopped. - + Failed to import '%1' (%2), analysis is stopped - + Show Mandatory - + Show Required - + Show Advisory - + Show Document - + Show L1 - + Show L2 - + Show L3 - + Show style - + Show portability - + Show performance - + Show information @@ -1472,22 +1593,22 @@ Analysis is stopped. Import von '%1' fehlgeschlagen; Analyse wurde abgebrochen. - + Project files (*.cppcheck) Projektdateien (*.cppcheck) - + Select Project Filename Projektnamen auswählen - + No project file loaded Keine Projektdatei geladen - + The project file %1 @@ -1504,12 +1625,12 @@ Do you want to remove the file from the recently used projects -list? Möchten Sie die Datei von der Liste der zuletzt benutzten Projekte entfernen? - + Install - + New version available: %1. %2 @@ -1554,26 +1675,31 @@ Options: NewSuppressionDialog + New suppression Neue Fehlerunterdrückung + Error ID Fehler-ID + File name Dateiname + Line number Zeilennummer + Symbol name Symbolname @@ -1591,55 +1717,55 @@ Options: Nativ - Unix 32-bit - Unix 32-bit + Unix 32-bit - Unix 64-bit - Unix 64-bit + Unix 64-bit - Windows 32-bit ANSI - Windows 32-bit, ANSI + Windows 32-bit, ANSI - Windows 32-bit Unicode - Windows 32-bit, Unicode + Windows 32-bit, Unicode - Windows 64-bit - Windows 64-bit + Windows 64-bit ProjectFile + Project File Projektdatei + Paths and Defines Pfade und Definitionen + Import Project (Visual studio / compile database/ Borland C++ Builder 6) Importiere Projekt (Visual Studio / Compile-Datenbank / Borland C++-Builder 6) + Defines must be separated by a semicolon. Example: DEF1;DEF2=5;DEF3=int Definitionen müssen mit einem Semikolon getrennt werden. Beispiel: DEF1;DEF2=5;DEF3=int - + + Note: Put your own custom .cfg files in the same folder as the project file. You should see them above. Hinweis: Legen Sie eigene .cfg-Dateien in den Ordner der Projektdatei. Dann sollten sie oben sichtbar werden. @@ -1648,364 +1774,451 @@ Options: MISRA C 2012 - + + MISRA rule texts MISRA-Regeltexte - + + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> <html><head/><body><p>Text aus Anhang A &quot;Summary of guidelines&quot; aus der MISRA-C-2012-PDF in eine Textdatei einfügen.</p></body></html> - + + ... ... + <html><head/><body><p>You have a choice:</p><p> * Analyze all Debug and Release configurations</p><p> * Only analyze the first matching Debug configuration</p><p><br/></p></body></html> <html><head/><body><p>Sie haben die Auswahl:</p><p> * Alle Debug- und Release-Konfigurationen analysieren</p><p> * Nur die erste passende Debug-Konfiguration analysieren</p><p><br/></p></body></html> - - + + + Browse... Durchsuchen... + Analyze all Visual Studio configurations Alle Visual-Studio-Konfigurationen analysieren + Selected VS Configurations Ausgewählte VS-Konfiguration + Paths: Pfade: + + Add... Hinzufügen... - + + + + Edit Bearbeiten - - + + + + + + Remove Entfernen + Undefines: Un-Definitionen: + Undefines must be separated by a semicolon. Example: UNDEF1;UNDEF2;UNDEF3 Un-Definitionen müssen Semikolon-getrennt sein. Beispiel: UNDEF1;UNDEF2;UNDEF3 + Include Paths: Includepfade: + Up Auf + Down Ab - + + + Include file + + + + + + <html><head/><body><p><span style=" font-family:'monospace'; color:#000000; background-color:#ffffff;">Force inclusion of a header file</span></p></body></html> + + + + + + Browse.. + + + + + Platform Plattform - - + + + + Analysis Analyse - + + This is a workfolder that Cppcheck will use for various purposes. - + + Clang (experimental) - + + Check level - + + Reduced -- meant for usage where developer wants results directly. Limited and faster analysis with fewer results. - + + Normal -- meant for normal analysis in CI. Analysis should finish in reasonable time. - + + Exhaustive -- meant for nightly builds etc. Analysis time can be longer (10x slower than compilation is OK). - + + If you want to design your classes to be as flexible and robust as possible then the public interface must be very robust. Cppcheck will asumme that arguments can take *any* value. - + + Check code in unused templates (should be ON normally, however in theory you can safely ignore warnings in unused templates) Check code in unused templates (slower and less accurate analysis) Prüfe Code in ungenutzten Templates (normalerweise AN, theoretisch können Warnungen in ungenutzten Templates gefahrlos ignoriert werden) - + + Max CTU depth Maximale CTU-Tiefe - + + Max recursion in template instantiation - - Premium License - - - - + + Warning options Warnoptionen - + + Root path: Wurzelverzeichnis: - + + Filepaths in warnings will be relative to this path - + + Warning tags (separated by semicolon) Warnungs-Tags (Semikolon-getrennt) - + + Enable inline suppressions Inline-Fehlerunterdrückung aktivieren - + + 2025 - + + Misra C++ - + + 2008 - + + Cert C Cert C - + + CERT-INT35-C: int precision (if size equals precision, you can leave empty) - + + Autosar - + + Safety profiles (defined in C++ core guidelines) - + + Bug hunting - + + External tools Externe Werkzeuge - + + Cppcheck build dir (whole program analysis, incremental analysis, statistics, etc) Cppcheck-Arbeitsverzeichnis (Vollständige Programmanalyse, inkrementelle Analyse, Statistiken, etc.) - + + Types and Functions - + + Libraries Bibliotheken - + + Parser - + + Cppcheck (built in) - + + Check that each class has a safe public interface - + + Limit analysis - + + Check code in headers (should be ON normally. if you want a limited quick analysis then turn this OFF) - + + If tags are added, you will be able to right click on warnings and set one of these tags. You can manually categorize warnings. - + + Exclude source files - + + Exclude folder... - + + Exclude file... - + + Suppressions Fehlerunterdrückungen - + + Add Hinzufügen - - + + + + Addons Add-Ons - + + Note: Addons require <a href="https://www.python.org/">Python</a> being installed. - + + Y2038 Y2038 - + + Thread safety Threadsicherheit - + + Coding standards Programmierstandards - + + Misra C Misra C - + + 2012 - - + + + + 2023 - + + Cert C++ Cert C++ - + + Bug hunting (Premium) - Clang analyzer - Clang-Analyzer + Clang-Analyzer - Clang-tidy - Clang-Tidy + Clang-Tidy + Defines: Definitionen: @@ -2013,82 +2226,91 @@ Options: ProjectFileDialog - + Project file: %1 Projektdatei: %1 - + Select Cppcheck build dir Wähle Cppcheck-Erstellungsverzeichnis - + Select include directory Wähle Include-Verzeichnisse - + Select a directory to check Wähle zu prüfendes Verzeichnis - + Note: Open source Cppcheck does not fully implement Misra C 2012 Hinweis: Open-Source Cppcheck implementiert Misra C 2012 nicht vollständig - + Clang-tidy (not found) Clang-tidy (nicht gefunden) - Visual Studio - Visual Studio + Visual Studio - + Compile database Compilerdatenbank - Borland C++ Builder 6 - Borland C++-Builder 6 + Borland C++-Builder 6 - + Import Project Projekt importieren - + + C/C++ header + + + + + Include file + + + + Select directory to ignore Wähle zu ignorierendes Verzeichnis - + Source files Quelltext-Dateien - + + All files Alle Dateien - + Exclude file Datei ausschließen - + Select MISRA rule texts file Wähle MISRA-Regeltext-Datei - + MISRA rule texts file (%1) MISRA-Regeltext-Datei (%1) @@ -2121,7 +2343,7 @@ Options: - + (Not found) (nicht gefunden) @@ -2320,11 +2542,6 @@ Options: Tags - - - CWE - - QPlatformTheme @@ -2447,10 +2664,8 @@ Options: Kein Tag - - Cppcheck - Cppcheck + Cppcheck @@ -2523,122 +2738,124 @@ Bitte überprüfen Sie ob der Pfad und die Parameter der Anwendung richtig einge ResultsView - + Print Report Bericht drucken - + No errors found, nothing to print. Keine Funde, nichts zu drucken. - + %p% (%1 of %2 files checked) %p% (%1 von %2 Dateien geprüft) - - Cppcheck - Cppcheck + Cppcheck - + No errors found. Keine Fehler gefunden. - + Errors were found, but they are configured to be hidden. To toggle what kind of errors are shown, open view menu. Es wurden Fehler gefunden, aber sie sind so konfiguriert, ausgeblendet zu werden. Legen Sie unter dem Menü Ansicht fest, welche Arten von Fehlern angezeigt werden sollen. - - + + Failed to read the report. Lesen des Berichts fehlgeschlagen. - + XML format version 1 is no longer supported. XML-Format-Version 1 wird nicht länger unterstützt. - + First included by Zuerst inkludiert von - + Id Id - + Clear Log Protokoll leeren - + Copy this Log entry Diesen Protokolleintrag kopieren - + Copy complete Log Gesamtes Protokoll kopieren - + Analysis was stopped Analyse wurde gestoppt - + There was a critical error with id '%1' Kritischer Fehler mit ID '%1' aufgetreten - + when checking %1 beim Prüfen von %1 - + when checking a file beim Prüfen einer Datei - + Analysis was aborted. Analyse abgebrochen. - - + + Failed to save the report. Der Bericht konnte nicht speichern werden. + Results Berichte + Critical errors Kritische Fehler + Analysis Log Analyseprotokoll + Warning Details Warnungs-Details @@ -2647,26 +2864,31 @@ Legen Sie unter dem Menü Ansicht fest, welche Arten von Fehlern angezeigt werde ScratchPad + Scratchpad Schmierzettel + Copy or write some C/C++ code here: Kopieren oder schreiben Sie C/C++-Code hierher: + Optionally enter a filename (mainly for automatic language detection) and click on "Check": Optional einen Dateinamen (hauptsächlich für automatische Spracherkennung) eingeben und auf "Prüfe" klicken: + filename Dateiname + Check Prüfe @@ -2675,21 +2897,25 @@ Legen Sie unter dem Menü Ansicht fest, welche Arten von Fehlern angezeigt werde Settings + Preferences Einstellungen + General Allgemein + Add... Hinzufügen... + Number of threads: Anzahl der Threads: @@ -2699,56 +2925,67 @@ Legen Sie unter dem Menü Ansicht fest, welche Arten von Fehlern angezeigt werde + Force checking all #ifdef configurations Erzwinge Prüfung aller #ifdef-Konfigurationen + Show full path of files Vollständigen Dateipfad anzeigen + Show "No errors found" message when no errors found "Keine Fehler gefunden"-Meldung anzeigen, wenn keine Fehler gefunden werden + Display error Id in column "Id" Zeige Meldungs-Id in Spalte "Id" + Enable inline suppressions Inline-Fehlerunterdrückung aktivieren + Check for inconclusive errors also Auch nach unklaren Fehlern suchen + Show statistics on check completion Zeige Statistiken nach Prüfungsabschluss + Check for updates Auf Updates prüfen + Show internal warnings in log Interne Warnungen im Log anzeigen + Addons Add-Ons + Python binary (leave this empty to use python in the PATH) Python-Binärdatei (Python aus PATH wird genutzt, wenn leer) @@ -2756,117 +2993,143 @@ Legen Sie unter dem Menü Ansicht fest, welche Arten von Fehlern angezeigt werde + + + ... ... + MISRA addon MISRA-Addon + MISRA rule texts file MISRA-Regeltext-Datei + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> <html><head/><body><p>Text aus Anhang A &quot;Summary of guidelines&quot; aus der MISRA-C-2012-PDF in eine Textdatei einfügen.</p></body></html> + Clang Clang + Clang path (leave empty to use system PATH) Clang-Verzeichnis (PATH wird genutzt, wenn leer) + Visual Studio headers Visual-Studio-Header + <html><head/><body><p>Paths to Visual Studio headers, separated by semicolon ';'.</p><p>You can open a Visual Studio command prompt, write &quot;SET INCLUDE&quot;. Then copy/paste the paths.</p></body></html> <html><head/><body><p>Pfade zu Visual-Studio-Headern, Semikolon-getrennt.</p><p>Sie können eine Visual-Studio-Kommandozeile öffnen, &quot;SET INCLUDE&quot; eingeben und dann die Pfade hier reinkopieren.</p></body></html> + Code Editor Code-Editor + Code Editor Style Code-Editor-Stil + System Style Systemstil + Default Light Style Heller Standardstil + Default Dark Style Dunkler Standardstil + Custom Benutzerdefiniert + Remove Entfernen + Max count: Maximalzahl: + Applications Anwendungen + + Edit... Bearbeiten... + Set as default Als Standard festlegen + Reports Berichte + Save all errors when creating report Alle Fehler beim Erstellen von Berichten speichern + Save full path to files in reports Vollständigen Dateipfad in Berichten speichern + Language Sprache @@ -2923,221 +3186,245 @@ Legen Sie unter dem Menü Ansicht fest, welche Arten von Fehlern angezeigt werde - - + + + + Statistics Statistik - + + Project Projekt + Project: Projekt: + Paths: Pfade: + Include paths: Include-Pfade: + Defines: Definitionen: + Undefines: Un-Definitionen: - + + Previous Scan Vorherige Prüfung + Path Selected: Ausgewählte Pfade: + Number of Files Scanned: Anzahl der geprüften Dateien: + Scan Duration: Prüfungsdauer: + Errors: Fehler: + Warnings: Warnungen: + Stylistic warnings: Stilwarnungen: + Portability warnings: Portabilitätswarnungen: + Performance issues: Performance-Probleme: + Information messages: Informationsmeldungen: + Active checkers: Aktive Checker: + Checkers Checker + History Verlauf + File: Datei: + Copy to Clipboard In die Zwischenablage kopieren + Pdf Export PDF-Export - + 1 day 1 Tag - + %1 days %1 Tage - + 1 hour 1 Stunde - + %1 hours %1 Stunden - + 1 minute 1 Minute - + %1 minutes %1 Minuten - + 1 second 1 Sekunde - + %1 seconds %1 Sekunden - + 0.%1 seconds 0,%1 Sekunden - + and und - + Export PDF Exportiere PDF - + Project Settings Projekteinstellungen - + Paths Pfade - + Include paths Include-Pfade - + Defines Definitionen - + Undefines Un-Definitionen - + Path selected Gewählte Pfade - + Number of files scanned Anzahl geprüfter Dateien - + Scan duration Prüfungsdauer - - + + Errors Fehler @@ -3152,32 +3439,32 @@ Legen Sie unter dem Menü Ansicht fest, welche Arten von Fehlern angezeigt werde Kein Cppcheck-Analyseverzeichnis - - + + Warnings Warnungen - - + + Style warnings Stilwarnungen - - + + Portability warnings Portabilitätswarnungen - - + + Performance warnings Performance-Warnungen - - + + Information messages Informationsmeldungen @@ -3186,6 +3473,7 @@ Legen Sie unter dem Menü Ansicht fest, welche Arten von Fehlern angezeigt werde ThreadDetails + Thread Details @@ -3214,9 +3502,8 @@ The user interface language has been reset to English. Open the Preferences-dial Die Sprache wurde auf Englisch zurückgesetzt. Öffnen Sie den Einstellungen-Dialog um eine verfügbare Sprache auszuwählen. - Cppcheck - Cppcheck + Cppcheck diff --git a/gui/cppcheck_es.ts b/gui/cppcheck_es.ts index 927a56e05e5..b2080055651 100644 --- a/gui/cppcheck_es.ts +++ b/gui/cppcheck_es.ts @@ -5,38 +5,45 @@ About + About Cppcheck Acerca de Cppcheck + Version %1 Versión %1 + Cppcheck - A tool for static C/C++ code analysis. Cppcheck - Una utilidad para el análisis estático de código C/C++. + Copyright © 2007-%1 Cppcheck team. Copyright © 2007-2021 Cppcheck team. Copyright © 2007-2021 el equipo de cppcheck. + This program is licensed under the terms of the GNU General Public License version 3 Este programa está licenciado bajo los términos de GNU General Public License versión 3 + Visit Cppcheck homepage at %1 Visita el sitio de Cppcheck en %1 + <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PCRE</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PicoJSON</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TinyXML2</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Boost</li></ul></body></html> <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">pcre</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">picojson</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">tinyxml2</li></ul></body></html> @@ -46,11 +53,13 @@ of the GNU General Public License version 3 ApplicationDialog + Add an application Añade una aplicación + Here you can add an application that can open error files. Specify a name for the application, the application executable and command line parameters for the application. The following texts in parameters are replaced with appropriate values when application is executed: @@ -66,21 +75,25 @@ Parameters: -l(line) (file) + &Name: &Nombre: + &Executable: &Ejecutable: + &Parameters: &Parámetros: + Browse Buscar @@ -95,9 +108,8 @@ Parameters: -l(line) (file) Selecciona la aplicación para visualizar - Cppcheck - Cppcheck + Cppcheck @@ -105,70 +117,6 @@ Parameters: -l(line) (file) ¡Debes especificar el nombre, la ruta y opcionalmente los parámetros para la aplicación! - - ComplianceReportDialog - - - Compliance Report - - - - - Project name - - - - - Project version - - - - - Coding Standard - - - - - Misra C - - - - - Cert C - - - - - Cert C++ - - - - - List of files with md5 checksums - - - - - Compliance report - - - - - HTML files (*.html) - - - - - - Save compliance report - - - - - Failed to import '%1' (%2), can not show files in compliance report - - - FileViewDialog @@ -177,10 +125,8 @@ Parameters: -l(line) (file) No se ha encontrado el fichero: %1 - - Cppcheck - Cppcheck + Cppcheck @@ -192,44 +138,49 @@ Parameters: -l(line) (file) HelpDialog + Cppcheck GUI help + Contents + Index - + Helpfile '%1' was not found - Cppcheck - Cppcheck + Cppcheck LibraryAddFunctionDialog + Add function Añadir función + Function name(s) Nombre(s) de la función + Number of arguments Número de argumentos @@ -238,86 +189,103 @@ Parameters: -l(line) (file) LibraryDialog + Library Editor Editor de bibliotecas + Open Abrir + Save Guardar + Save as + Functions Funciones + Sort + Add Añadir + Filter: + Comments + noreturn + False Falso + True Verdadero + Unknown Desconocido + return value must be used + ignore function in leaks checking + Arguments Argumentos + Edit Editar @@ -333,11 +301,8 @@ Parameters: -l(line) (file) Abrir archivo de biblioteca - - - Cppcheck - Cppcheck + Cppcheck @@ -366,11 +331,13 @@ Parameters: -l(line) (file) LibraryEditArgDialog + Edit argument Editar argumento + <html><head/><body> <p>Is bool value allowed? For instance result from comparison or from '!' operator.</p> <p>Typically, set this if the argument is a pointer, size, etc.</p> @@ -381,11 +348,13 @@ Parameters: -l(line) (file) + Not bool No bool + <html><head/><body> <p>Is a null parameter value allowed?</p> <p>Typically this should be used on any pointer parameter that does not allow null.</p> @@ -396,78 +365,99 @@ Parameters: -l(line) (file) + Not null No null + Not uninit No uninit + String String + Format string + Min size of buffer + + Type Tipo + + None Ninguno + + argvalue + + mul + + strlen + + Arg + + Arg2 + and + Valid values Valores válidos @@ -475,729 +465,805 @@ Parameters: -l(line) (file) MainWindow - - - - - - - - - - - - - - - Cppcheck - Cppcheck + Cppcheck + &File &Archivo - + + &View &Ver - + + &Toolbars &Herramientas - + + &Help &Ayuda - + + C++ standard C++ estándar - + + &C standard C standard C estándar - + + &Edit &Editar - + + Standard Estándar - + + Categories Categorías - + + &License... &Licencia... - + + A&uthors... A&utores... - + + &About... &Acerca de... - + + &Files... &Ficheros... - - + + + + Analyze files Check files Comprobar archivos - + + Ctrl+F Ctrl+F - + + &Directory... &Carpeta... - - + + + + Analyze directory Check directory Comprobar carpeta - + + Ctrl+D Ctrl+D - + + Ctrl+R Ctrl+R - + + &Stop &Detener - - + + + + Stop analysis Stop checking Detener comprobación - + + Esc Esc - + + &Save results to file... &Guardar los resultados en el fichero... - + + Ctrl+S Ctrl+S - + + &Quit &Salir - + + &Clear results &Limpiar resultados - + + &Preferences &Preferencias - - + + + + Show style warnings Mostrar advertencias de estilo - - - + + + + + Show errors Mostrar errores - - + + Information Información - + + Show information messages Mostrar mensajes de información - + + Show portability warnings Mostrar advertencias de portabilidad - + + Show Cppcheck results - + + Clang - + + Show Clang results - + + &Filter &Filtro - + + Filter results Resultados del filtro - + + Windows 32-bit ANSI Windows 32-bit ANSI - + + Windows 32-bit Unicode Windows 32-bit Unicode - + + Unix 32-bit Unix 32-bit - + + Unix 64-bit Unix 64-bit - + + Windows 64-bit Windows 64-bit - + + &Print... Im&primir... - + + Print the Current Report Imprimir el informe actual - + + Print Pre&view... Pre&visualización de impresión... - + + Open a Print Preview Dialog for the Current Results Abre el diálogo de previsualización de impresión para el informe actual - + + Open library editor Abrir el editor de bibliotecas - + + &Check all &Seleccionar todo + Checking for updates + Hide Ocultar - - + + + + Report - + + A&nalyze - + + Filter Filtro - + + &Reanalyze modified files &Recheck modified files - + + Reanal&yze all files - + + Ctrl+Q - + + Style war&nings - + + E&rrors - + + &Uncheck all &Deseleccionar todo - + + Collapse &all Contraer &todo - + + &Expand all &Expandir todo - + + &Standard &Estándar - + + Standard items Elementos estándar - + + &Contents &Contenidos - + + Open the help contents Abrir la ayuda de contenidos - + + F1 F1 - + + Toolbar Barra de herramientas - + + &Categories &Categorías - + + Error categories Categorías de error - + + &Open XML... &Abrir XML... - + + Open P&roject File... Abrir P&royecto... - + + Ctrl+Shift+O - + + Sh&ow Scratchpad... - + + &New Project File... &Nuevo Proyecto... - + + Ctrl+Shift+N - + + &Log View &Visor del log - + + Log View Visor del log - + + C&lose Project File C&errar Proyecto - + + &Edit Project File... &Editar Proyecto... - + + &Statistics &Estadísticas - + + &Warnings - + + Per&formance warnings - + + &Information - + + &Portability - + + P&latforms - + + C++&11 - + + C&99 - + + &Posix - + + C&11 - + + &C89 - + + &C++03 - + + &Library Editor... - + + &Auto-detect language - + + &Enforce C++ - + + E&nforce C - + + C++14 C++14 - + + Reanalyze and check library - + + Check configuration (defines, includes) - + + C++17 C++17 - + + C++20 C++20 - + + Compliance report... - + + Normal - - Misra C - - - - - Misra C++ 2008 - - - - - Cert C - - - - - Cert C++ - - - - - Misra C++ 2023 - - - - + + Autosar - + + EULA... - + + Thread Details - + + Show thread details - - - + + + + + Show warnings Mostrar advertencias - - + + + + Show performance warnings Mostrar advertencias de rendimiento - + + Show &hidden Mostrar &ocultos - + There was a problem with loading the editor application settings. This is probably because the settings were changed between the Cppcheck versions. Please check (and fix) the editor application settings, otherwise the editor program might not start correctly. - + You must close the project file before selecting new files or directories! ¡Tienes que cerrar el proyecto antes de seleccionar nuevos ficheros o carpetas! - + Select configuration - + File not found Archivo no encontrado - + Bad XML XML malformado - + Missing attribute Falta el atributo - + Bad attribute value - + Unsupported format Formato no soportado - + Duplicate define - + Failed to load the selected library '%1'. %2 - + File not found: '%1' - + Failed to load/setup addon %1: %2 - + Failed to load %1. Your Cppcheck installation is broken. You can use --data-dir=<directory> at the command line to specify where this file is located. Please note that --data-dir is supposed to be used by installation scripts and therefore the GUI does not start when it is used, all that happens is that the setting is configured. Analysis is aborted. - + Failed to load %1 - %2 Analysis is aborted. - - + + %1 Analysis is aborted. - - + + XML files (*.xml) Archivos XML (*.xml) - + Open the report file Abrir informe - + License Licencia - + Authors Autores - + Save the report file Guardar informe - - + + Quick Filter: Filtro rápido: - + Found project file: %1 Do you want to load this project file instead? @@ -1206,118 +1272,107 @@ Do you want to load this project file instead? ¿Quiere cargar este fichero de proyecto en su lugar? - + The library '%1' contains unknown elements: %2 La biblioteca '%1' contiene elementos deconocidos: %2 - + Duplicate platform type - + Platform type redefined - + Unknown element - - Unknown element - Unknown issue - - - - - - - + + + + Error Error - + Text files (*.txt) Ficheros de texto (*.txt) - + CSV files (*.csv) Ficheros CVS (*.cvs) - + Project files (*.cppcheck);;All files(*.*) Ficheros de proyecto (*.cppcheck;;Todos los ficheros (*.*) - + Select Project File Selecciona el archivo de proyecto - - - - + + + + Project: Proyecto: - - No suitable files found to analyze! + + None of the selected files is part of the project, nothing to analyze. - - C/C++ Source - - - - - Compile database + + No suitable files found to analyze! - - Visual Studio + + C/C++ Source - - Borland C++ Builder 6 + + Compile database - + Select files to analyze - + Select directory to analyze - + Select the configuration that will be analyzed - + Found project files from the directory. Do you want to proceed analysis without using any of these project files? - + Current results will be cleared. Opening a new XML file will clear current results. @@ -1325,81 +1380,76 @@ Do you want to proceed? - + Analyzer is running. Do you want to stop the analysis and exit Cppcheck? - + About - + XML files (*.xml);;Text files (*.txt);;CSV files (*.csv) - - Cannot generate a compliance report right now, an analysis must finish successfully. Try to reanalyze the code and ensure there are no critical errors. - - - - + Build dir '%1' does not exist, create it? - + To check the project using addons, you need a build directory. - + Failed to open file - + Unknown project file format - + Failed to import project file - + Failed to import '%1': %2 Analysis is stopped. - + Failed to import '%1' (%2), analysis is stopped - + Project files (*.cppcheck) - + Select Project Filename Selecciona el nombre del proyecto - + No project file loaded No hay ningún proyecto cargado - + The project file %1 @@ -1416,67 +1466,67 @@ Do you want to remove the file from the recently used projects -list? ¿Quiere eliminar el fichero de la lista de proyectos recientes? - + Install - + New version available: %1. %2 - + Show Mandatory - + Show Required - + Show Advisory - + Show Document - + Show L1 - + Show L2 - + Show L3 - + Show style - + Show portability - + Show performance - + Show information @@ -1520,26 +1570,31 @@ Options: NewSuppressionDialog + New suppression + Error ID + File name + Line number + Symbol name @@ -1557,419 +1612,498 @@ Options: - Unix 32-bit - Unix 32-bit + Unix 32-bit - Unix 64-bit - Unix 64-bit + Unix 64-bit - Windows 32-bit ANSI - Windows 32-bit ANSI + Windows 32-bit ANSI - Windows 32-bit Unicode - Windows 32-bit Unicode + Windows 32-bit Unicode - Windows 64-bit - Windows 64-bit + Windows 64-bit ProjectFile + Project File Archivo de proyecto + Paths and Defines + Import Project (Visual studio / compile database/ Borland C++ Builder 6) Import Project (Visual studio / compile database) + Defines must be separated by a semicolon. Example: DEF1;DEF2=5;DEF3=int Defines must be separated by a semicolon ';' - + + Note: Put your own custom .cfg files in the same folder as the project file. You should see them above. Nota: Ponga sus propios archivos .cfg en la misma carpeta que el proyecto. Debería verlos arriba. - + + If tags are added, you will be able to right click on warnings and set one of these tags. You can manually categorize warnings. - + + Exclude source files - + + Exclude folder... - + + Exclude file... - + + Misra C - + + 2012 - - + + + + 2023 - + + MISRA rule texts - + + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> - + + ... + <html><head/><body><p>You have a choice:</p><p> * Analyze all Debug and Release configurations</p><p> * Only analyze the first matching Debug configuration</p><p><br/></p></body></html> - - + + + Browse... + Analyze all Visual Studio configurations + Selected VS Configurations + Paths: Rutas: + + Add... Añadir... - + + + + Edit Editar - - + + + + + + Remove Eliminar + Undefines: + Undefines must be separated by a semicolon. Example: UNDEF1;UNDEF2;UNDEF3 + Include Paths: - + + + Include file + + + + + + <html><head/><body><p><span style=" font-family:'monospace'; color:#000000; background-color:#ffffff;">Force inclusion of a header file</span></p></body></html> + + + + + + Browse.. + + + + + Types and Functions - - + + + + Analysis - + + This is a workfolder that Cppcheck will use for various purposes. - + + Parser - + + Cppcheck (built in) - + + Check level - + + Reduced -- meant for usage where developer wants results directly. Limited and faster analysis with fewer results. - + + Normal -- meant for normal analysis in CI. Analysis should finish in reasonable time. - + + Exhaustive -- meant for nightly builds etc. Analysis time can be longer (10x slower than compilation is OK). - + + Check that each class has a safe public interface - + + Limit analysis - + + Check code in unused templates (should be ON normally, however in theory you can safely ignore warnings in unused templates) Check code in unused templates (slower and less accurate analysis) - + + Max CTU depth - - Premium License - - - - + + Enable inline suppressions Habilitar supresiones inline - + + 2025 - + + Misra C++ - + + 2008 - + + Cert C - + + CERT-INT35-C: int precision (if size equals precision, you can leave empty) - + + Autosar - + + Safety profiles (defined in C++ core guidelines) - + + Bug hunting - + + External tools + Up Subir + Down Bajar - + + Platform - + + Clang (experimental) - + + If you want to design your classes to be as flexible and robust as possible then the public interface must be very robust. Cppcheck will asumme that arguments can take *any* value. - + + Check code in headers (should be ON normally. if you want a limited quick analysis then turn this OFF) - + + Max recursion in template instantiation - + + Warning options - + + Root path: - + + Filepaths in warnings will be relative to this path - + + Warning tags (separated by semicolon) - + + Cppcheck build dir (whole program analysis, incremental analysis, statistics, etc) - + + Libraries - + + Suppressions Supresiones - + + Add Añadir - - + + + + Addons - + + Note: Addons require <a href="https://www.python.org/">Python</a> being installed. - + + Y2038 - + + Thread safety - + + Coding standards - + + Cert C++ - + + Bug hunting (Premium) - - - Clang analyzer - - - - - Clang-tidy - - + Defines: Definiciones: @@ -1977,82 +2111,83 @@ Options: ProjectFileDialog - + Project file: %1 Archivo de proyecto: %1 - + Select Cppcheck build dir - + Select include directory Selecciona una carpeta para incluir - + Select a directory to check Selecciona la carpeta a comprobar - + Note: Open source Cppcheck does not fully implement Misra C 2012 - + Clang-tidy (not found) - - Visual Studio + + Compile database - - Compile database + + Import Project - - Borland C++ Builder 6 + + C/C++ header - - Import Project + + Include file - + Select directory to ignore Selecciona la carpeta a ignorar - + Source files - + + All files - + Exclude file - + Select MISRA rule texts file - + MISRA rule texts file (%1) @@ -2085,7 +2220,7 @@ Options: - + (Not found) @@ -2285,11 +2420,6 @@ Options: Tags - - - CWE - - QPlatformTheme @@ -2426,10 +2556,8 @@ Options: - - Cppcheck - Cppcheck + Cppcheck @@ -2498,121 +2626,123 @@ Por favor comprueba que la ruta a la aplicación y los parámetros son correctos ResultsView + Results Resultados + Critical errors + Analysis Log + Warning Details - - + + Failed to save the report. Error al guardar el informe. - + Print Report Imprimir informe - + No errors found, nothing to print. No se encontraron errores, nada que imprimir. - + %p% (%1 of %2 files checked) %p% (%1 of %2 archivos comprobados) - - Cppcheck - Cppcheck + Cppcheck - + No errors found. No se han encontrado errores. - + Errors were found, but they are configured to be hidden. To toggle what kind of errors are shown, open view menu. Se han encontrado errores, pero están configurados para que no se muestren. Para cambiar el tipo de comportamiento, abra el menú Ver. - - + + Failed to read the report. Error al leer el informe. - + XML format version 1 is no longer supported. - + First included by - + Id Id - + Clear Log - + Copy this Log entry - + Copy complete Log - + Analysis was stopped - + There was a critical error with id '%1' - + when checking %1 - + when checking a file - + Analysis was aborted. @@ -2621,26 +2751,31 @@ Para cambiar el tipo de comportamiento, abra el menú Ver. ScratchPad + Scratchpad Scratchpad + Copy or write some C/C++ code here: + Optionally enter a filename (mainly for automatic language detection) and click on "Check": + filename nombre de archivo + Check Comprobar @@ -2649,11 +2784,13 @@ Para cambiar el tipo de comportamiento, abra el menú Ver. Settings + Preferences Preferencias + General General @@ -2663,46 +2800,55 @@ Para cambiar el tipo de comportamiento, abra el menú Ver. + Force checking all #ifdef configurations Forzar comprobación de todas las configuraciones #ifdef + Display error Id in column "Id" Mostrar el Id del error en la columna "Id" + Enable inline suppressions Habilitar supresiones inline + Check for inconclusive errors also + Show statistics on check completion + Check for updates + Show internal warnings in log + Addons + Python binary (leave this empty to use python in the PATH) @@ -2710,137 +2856,167 @@ Para cambiar el tipo de comportamiento, abra el menú Ver. + + + ... + MISRA addon + MISRA rule texts file + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> + Clang + Clang path (leave empty to use system PATH) + Visual Studio headers + <html><head/><body><p>Paths to Visual Studio headers, separated by semicolon ';'.</p><p>You can open a Visual Studio command prompt, write &quot;SET INCLUDE&quot;. Then copy/paste the paths.</p></body></html> + Code Editor + Code Editor Style + System Style + Default Light Style + Default Dark Style + Custom + Add... Añadir... + Max count: + + Edit... Editar... + Set as default Definir por defecto + Language Idioma + Number of threads: Número de hilos: + Show full path of files Mostrar la ruta completa de los ficheros + Show "No errors found" message when no errors found Mostrar el mensaje "No se han encontrado errores" + Remove Eliminar + Applications Aplicaciones + Reports Informes + Save all errors when creating report Guardar todos los errores cuando se cree el informe + Save full path to files in reports Guardar la ruta completa en los ficheros de informes @@ -2897,221 +3073,245 @@ Para cambiar el tipo de comportamiento, abra el menú Ver. - - + + + + Statistics Estadísticas - + + Project Proyecto + Project: Proyecto: + Paths: Rutas: + Include paths: Incluye las rutas: + Defines: Definiciones: + Undefines: - + + Previous Scan Análisis anterior + Path Selected: Ruta seleccionada: + Number of Files Scanned: Número de archivos analizados: + Scan Duration: Duración del análisis: + Errors: Errores: + Warnings: Advertencias: + Stylistic warnings: Advertencias de estilo: + Portability warnings: Advertencias de portabilidad: + Performance issues: Problemas de rendimiento: + Information messages: Mensajes de información: + Active checkers: + Checkers + History + File: + Copy to Clipboard Copiar al portapapeles + Pdf Export - + 1 day 1 día - + %1 days %1 días - + 1 hour 1 hora - + %1 hours %1 horas - + 1 minute 1 minuto - + %1 minutes %1 minutos - + 1 second 1 segundo - + %1 seconds %1 segundos - + 0.%1 seconds 0.%1 segundos - + and y - + Export PDF - + Project Settings Preferencias del proyecto - + Paths Rutas - + Include paths Incluye las rutas - + Defines Definiciones - + Undefines - + Path selected Ruta seleccionada - + Number of files scanned Número de archivos analizados - + Scan duration Duración del análisis - - + + Errors Errores @@ -3126,32 +3326,32 @@ Para cambiar el tipo de comportamiento, abra el menú Ver. - - + + Warnings Advertencias - - + + Style warnings Advertencias de estilo - - + + Portability warnings Advertencias de portabilidad - - + + Performance warnings Advertencias de rendimiento - - + + Information messages Mensajes de información @@ -3160,6 +3360,7 @@ Para cambiar el tipo de comportamiento, abra el menú Ver. ThreadDetails + Thread Details @@ -3188,9 +3389,8 @@ The user interface language has been reset to English. Open the Preferences-dial El idioma de la interfaz gráfica ha sido cambiado a Inglés. Abra la ventana de Preferencias para seleccionar alguno de los idiomas disponibles. - Cppcheck - Cppcheck + Cppcheck diff --git a/gui/cppcheck_fi.ts b/gui/cppcheck_fi.ts index f6c959aa099..0600febbd04 100644 --- a/gui/cppcheck_fi.ts +++ b/gui/cppcheck_fi.ts @@ -5,27 +5,32 @@ About + About Cppcheck Tietoa ohjelmasta Cppcheck + Version %1 Versio %1 + Cppcheck - A tool for static C/C++ code analysis. Cppcheck - Työkalu C/C++ koodin staattiseen analysointiin. + Copyright © 2007-%1 Cppcheck team. Copyright © 2007-2021 Cppcheck team. Copyright (C) 2007-2021 Daniel Marjamäki ja cppcheck tiimi. + This program is licensed under the terms of the GNU General Public License version 3 Tämä ohjelma on lisensoitu GNU General @@ -33,11 +38,13 @@ Public lisenssin version 3 alaisuuteen + Visit Cppcheck homepage at %1 Cppcheckin kotisivu löytyy osoitteesta %1 + <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PCRE</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PicoJSON</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TinyXML2</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Boost</li></ul></body></html> <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">pcre</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">picojson</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">tinyxml2</li></ul></body></html> @@ -47,11 +54,13 @@ Public lisenssin version 3 alaisuuteen ApplicationDialog + Add an application Lisää uusi ohjelma + Here you can add an application that can open error files. Specify a name for the application, the application executable and command line parameters for the application. The following texts in parameters are replaced with appropriate values when application is executed: @@ -67,21 +76,25 @@ Parameters: -l(line) (file) + &Name: + &Executable: + &Parameters: + Browse Selaa @@ -96,9 +109,8 @@ Parameters: -l(line) (file) Valitse ohjelma jolla avata virhetiedosto - Cppcheck - Cppcheck + Cppcheck @@ -106,70 +118,6 @@ Parameters: -l(line) (file) - - ComplianceReportDialog - - - Compliance Report - - - - - Project name - - - - - Project version - - - - - Coding Standard - - - - - Misra C - - - - - Cert C - - - - - Cert C++ - - - - - List of files with md5 checksums - - - - - Compliance report - - - - - HTML files (*.html) - - - - - - Save compliance report - - - - - Failed to import '%1' (%2), can not show files in compliance report - - - FileViewDialog @@ -180,10 +128,8 @@ Parameters: -l(line) (file) Tiedostoa %1 ei löytynyt - - Cppcheck - Cppcheck + Cppcheck @@ -195,44 +141,49 @@ Parameters: -l(line) (file) HelpDialog + Cppcheck GUI help + Contents + Index - + Helpfile '%1' was not found - Cppcheck - Cppcheck + Cppcheck LibraryAddFunctionDialog + Add function + Function name(s) + Number of arguments @@ -241,86 +192,103 @@ Parameters: -l(line) (file) LibraryDialog + Library Editor + Open + Save + Save as + Functions + Sort + Add + Filter: + Comments + noreturn + False + True + Unknown + return value must be used + ignore function in leaks checking + Arguments + Edit @@ -336,11 +304,8 @@ Parameters: -l(line) (file) - - - Cppcheck - Cppcheck + Cppcheck @@ -369,11 +334,13 @@ Parameters: -l(line) (file) LibraryEditArgDialog + Edit argument + <html><head/><body> <p>Is bool value allowed? For instance result from comparison or from '!' operator.</p> <p>Typically, set this if the argument is a pointer, size, etc.</p> @@ -384,11 +351,13 @@ Parameters: -l(line) (file) + Not bool + <html><head/><body> <p>Is a null parameter value allowed?</p> <p>Typically this should be used on any pointer parameter that does not allow null.</p> @@ -399,78 +368,99 @@ Parameters: -l(line) (file) + Not null + Not uninit + String + Format string + Min size of buffer + + Type + + None + + argvalue + + mul + + strlen + + Arg + + Arg2 + and + Valid values @@ -478,846 +468,911 @@ Parameters: -l(line) (file) MainWindow - - - - - - - - - - - - - - - Cppcheck - Cppcheck + Cppcheck - + + A&nalyze - + + Standard Vakio + &File &Tiedosto - + + &View &Näytä - + + &Toolbars - + + C++ standard - + + &C standard C standard - + + &Edit &Muokkaa - + + &License... &Lisenssi... - + + A&uthors... &Tekijät... - + + &About... &Tietoa ohjelmasta Cppcheck... - + + &Files... &Tiedostot... - - + + + + Analyze files Check files - + + Ctrl+F Ctrl+F - + + &Directory... &Hakemisto... - - + + + + Analyze directory Check directory - + + Ctrl+D Ctrl+D - + + Ctrl+R Ctrl+R - + + &Stop &Pysäytä - - + + + + Stop analysis Stop checking - + + Esc Esc - + + &Save results to file... &Tallenna tulokset tiedostoon... - + + Ctrl+S Ctrl+S - + + &Quit &Lopeta - + + &Clear results &Tyhjennä tulokset - + + &Preferences &Asetukset - - - + + + + + Show errors - - - + + + + + Show warnings - - + + + + Show performance warnings - + + Show &hidden - - + + Information - + + Show information messages - + + Show portability warnings - + + Show Cppcheck results - + + Clang - + + Show Clang results - + + &Filter - + + Filter results - + + Windows 32-bit ANSI - + + Windows 32-bit Unicode - + + Unix 32-bit - + + Unix 64-bit - + + Windows 64-bit - + + &Print... - + + Print the Current Report - + + Print Pre&view... - + + Open a Print Preview Dialog for the Current Results - + + Open library editor - + + &Check all &Valitse kaikki + Checking for updates + Hide - - + + + + Report - + + Filter - + + &Reanalyze modified files &Recheck modified files - + + Reanal&yze all files - + + Ctrl+Q - + + Style war&nings - + + E&rrors - + + &Uncheck all &Poista kaikista valinta - + + Collapse &all &Pienennä kaikki - + + &Expand all &Laajenna kaikki - + + &Standard - + + Standard items - + + Toolbar - + + &Categories - + + Error categories - + + &Open XML... - + + Open P&roject File... - + + Ctrl+Shift+O - + + Sh&ow Scratchpad... - + + &New Project File... - + + Ctrl+Shift+N - + + &Log View - + + Log View - + + C&lose Project File - + + &Edit Project File... - + + &Statistics - + + &Warnings - + + Per&formance warnings - + + &Information - + + &Portability - + + P&latforms - + + C++&11 - + + C&99 - + + &Posix - + + C&11 - + + &C89 - + + &C++03 - + + &Library Editor... - + + &Auto-detect language - + + &Enforce C++ - + + E&nforce C - + + C++14 - + + Reanalyze and check library - + + Check configuration (defines, includes) - + + C++17 - + + C++20 - + + Compliance report... - + + Normal - - Misra C - - - - - Misra C++ 2008 - - - - - Cert C - - - - - Cert C++ - - - - - Misra C++ 2023 - - - - + + Autosar - + + EULA... - + + Thread Details - + + Show thread details - + + &Contents - + + Categories - - + + + + Show style warnings - + + Open the help contents - + + F1 - + + &Help &Ohje - - + + Quick Filter: - + Select configuration - + Found project file: %1 Do you want to load this project file instead? - + File not found - + Bad XML - + Missing attribute - + Bad attribute value - + Duplicate define - + Failed to load the selected library '%1'. %2 - + File not found: '%1' - + Failed to load/setup addon %1: %2 - + Failed to load %1. Your Cppcheck installation is broken. You can use --data-dir=<directory> at the command line to specify where this file is located. Please note that --data-dir is supposed to be used by installation scripts and therefore the GUI does not start when it is used, all that happens is that the setting is configured. Analysis is aborted. - + Failed to load %1 - %2 Analysis is aborted. - - + + %1 Analysis is aborted. - + License Lisenssi - + Authors Tekijät - + Save the report file Tallenna raportti - - + + XML files (*.xml) XML-tiedostot (*xml) - + There was a problem with loading the editor application settings. This is probably because the settings were changed between the Cppcheck versions. Please check (and fix) the editor application settings, otherwise the editor program might not start correctly. - + You must close the project file before selecting new files or directories! - + The library '%1' contains unknown elements: %2 - + Unsupported format - + Duplicate platform type - + Platform type redefined - + Unknown element - - Unknown element - Unknown issue - - - - - - - + + + + Error - + Open the report file - + Text files (*.txt) Tekstitiedostot (*.txt) - + CSV files (*.csv) - + Project files (*.cppcheck);;All files(*.*) - + Select Project File - - - - + + + + Project: - - No suitable files found to analyze! + + None of the selected files is part of the project, nothing to analyze. - - C/C++ Source - - - - - Compile database + + No suitable files found to analyze! - - Visual Studio + + C/C++ Source - - Borland C++ Builder 6 + + Compile database - + Select files to analyze - + Select directory to analyze - + Select the configuration that will be analyzed - + Found project files from the directory. Do you want to proceed analysis without using any of these project files? - + Current results will be cleared. Opening a new XML file will clear current results. @@ -1325,81 +1380,76 @@ Do you want to proceed? - + Analyzer is running. Do you want to stop the analysis and exit Cppcheck? - + About - + XML files (*.xml);;Text files (*.txt);;CSV files (*.csv) - - Cannot generate a compliance report right now, an analysis must finish successfully. Try to reanalyze the code and ensure there are no critical errors. - - - - + Build dir '%1' does not exist, create it? - + To check the project using addons, you need a build directory. - + Failed to open file - + Unknown project file format - + Failed to import project file - + Failed to import '%1': %2 Analysis is stopped. - + Failed to import '%1' (%2), analysis is stopped - + Project files (*.cppcheck) - + Select Project Filename - + No project file loaded - + The project file %1 @@ -1410,67 +1460,67 @@ Do you want to remove the file from the recently used projects -list? - + Install - + New version available: %1. %2 - + Show Mandatory - + Show Required - + Show Advisory - + Show Document - + Show L1 - + Show L2 - + Show L3 - + Show style - + Show portability - + Show performance - + Show information @@ -1514,26 +1564,31 @@ Options: NewSuppressionDialog + New suppression + Error ID + File name + Line number + Symbol name @@ -1550,420 +1605,479 @@ Options: Native - - - Unix 32-bit - - - - - Unix 64-bit - - - - - Windows 32-bit ANSI - - - - - Windows 32-bit Unicode - - - - - Windows 64-bit - - ProjectFile + Project File + Paths and Defines + Import Project (Visual studio / compile database/ Borland C++ Builder 6) Import Project (Visual studio / compile database) + Defines must be separated by a semicolon. Example: DEF1;DEF2=5;DEF3=int Defines must be separated by a semicolon ';' - + + Note: Put your own custom .cfg files in the same folder as the project file. You should see them above. - + + MISRA rule texts - + + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> - + + ... + <html><head/><body><p>You have a choice:</p><p> * Analyze all Debug and Release configurations</p><p> * Only analyze the first matching Debug configuration</p><p><br/></p></body></html> - - + + + Browse... + Analyze all Visual Studio configurations + Selected VS Configurations + Paths: + + Add... - + + + + Edit - - + + + + + + Remove + Undefines: + Undefines must be separated by a semicolon. Example: UNDEF1;UNDEF2;UNDEF3 + Include Paths: + Up + Down - + + + Include file + + + + + + <html><head/><body><p><span style=" font-family:'monospace'; color:#000000; background-color:#ffffff;">Force inclusion of a header file</span></p></body></html> + + + + + + Browse.. + + + + + Platform - - + + + + Analysis - + + This is a workfolder that Cppcheck will use for various purposes. - + + Clang (experimental) - + + Check level - + + Reduced -- meant for usage where developer wants results directly. Limited and faster analysis with fewer results. - + + Normal -- meant for normal analysis in CI. Analysis should finish in reasonable time. - + + Exhaustive -- meant for nightly builds etc. Analysis time can be longer (10x slower than compilation is OK). - + + If you want to design your classes to be as flexible and robust as possible then the public interface must be very robust. Cppcheck will asumme that arguments can take *any* value. - + + Check code in unused templates (should be ON normally, however in theory you can safely ignore warnings in unused templates) Check code in unused templates (slower and less accurate analysis) - + + Max CTU depth - + + Max recursion in template instantiation - - Premium License - - - - + + Warning options - + + Root path: - + + Filepaths in warnings will be relative to this path - + + Warning tags (separated by semicolon) - + + Enable inline suppressions - + + 2025 - + + Misra C++ - + + 2008 - + + Cert C - + + CERT-INT35-C: int precision (if size equals precision, you can leave empty) - + + Autosar - + + Safety profiles (defined in C++ core guidelines) - + + Bug hunting - + + External tools - + + Cppcheck build dir (whole program analysis, incremental analysis, statistics, etc) - + + Types and Functions - + + Libraries - + + Parser - + + Cppcheck (built in) - + + Check that each class has a safe public interface - + + Limit analysis - + + Check code in headers (should be ON normally. if you want a limited quick analysis then turn this OFF) - + + If tags are added, you will be able to right click on warnings and set one of these tags. You can manually categorize warnings. - + + Exclude source files - + + Exclude folder... - + + Exclude file... - + + Suppressions - + + Add - - + + + + Addons - + + Note: Addons require <a href="https://www.python.org/">Python</a> being installed. - + + Y2038 - + + Thread safety - + + Coding standards - + + Misra C - + + 2012 - - + + + + 2023 - + + Cert C++ - + + Bug hunting (Premium) - - - Clang analyzer - - - - - Clang-tidy - - + Defines: @@ -1971,82 +2085,83 @@ Options: ProjectFileDialog - + Project file: %1 - + Select Cppcheck build dir - + Select include directory - + Select a directory to check - + Note: Open source Cppcheck does not fully implement Misra C 2012 - + Clang-tidy (not found) - - Visual Studio + + Compile database - - Compile database + + Import Project - - Borland C++ Builder 6 + + C/C++ header - - Import Project + + Include file - + Select directory to ignore - + Source files - + + All files - + Exclude file - + Select MISRA rule texts file - + MISRA rule texts file (%1) @@ -2081,7 +2196,7 @@ Options: - + (Not found) @@ -2281,11 +2396,6 @@ Options: Tags - - - CWE - - QPlatformTheme @@ -2404,10 +2514,8 @@ Options: - - Cppcheck - Cppcheck + Cppcheck @@ -2477,122 +2585,124 @@ Tarkista että ohjelman polku ja parametrit ovat oikeat. ResultsView - + Print Report - + No errors found, nothing to print. - + %p% (%1 of %2 files checked) - - Cppcheck - Cppcheck + Cppcheck - + No errors found. Virheitä ei löytynyt. - + Errors were found, but they are configured to be hidden. To toggle what kind of errors are shown, open view menu. Virheitä löytyi, mutta asetuksissa kyseiset virheet on määritelty piilotettavaksi. Määrittääksesi minkä tyyppisiä virheitä näytetään, avaa näkymä valikko. - - + + Failed to read the report. - + XML format version 1 is no longer supported. - + First included by - + Id - + Clear Log - + Copy this Log entry - + Copy complete Log - + Analysis was stopped - + There was a critical error with id '%1' - + when checking %1 - + when checking a file - + Analysis was aborted. - - + + Failed to save the report. Raportin tallentaminen epäonnistui. + Results Tulokset + Critical errors + Analysis Log + Warning Details @@ -2601,26 +2711,31 @@ Määrittääksesi minkä tyyppisiä virheitä näytetään, avaa näkymä valik ScratchPad + Scratchpad + Copy or write some C/C++ code here: + Optionally enter a filename (mainly for automatic language detection) and click on "Check": + filename + Check @@ -2629,77 +2744,92 @@ Määrittääksesi minkä tyyppisiä virheitä näytetään, avaa näkymä valik Settings + Preferences Asetukset + General Yleiset + Add... + Number of threads: Säikeiden lukumäärä: + Force checking all #ifdef configurations Check all #ifdef configurations Tarkista kaikki #ifdef kombinaatiot + Show full path of files Näytä tiedostojen täysi polku + Show "No errors found" message when no errors found Näytä "virheitä ei löytynyt"-viesti jos virheitä ei löydy + Display error Id in column "Id" + Enable inline suppressions + Check for inconclusive errors also + Show statistics on check completion + Check for updates + Show internal warnings in log + Addons + Python binary (leave this empty to use python in the PATH) @@ -2707,117 +2837,143 @@ Määrittääksesi minkä tyyppisiä virheitä näytetään, avaa näkymä valik + + + ... + MISRA addon + MISRA rule texts file + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> + Clang + Clang path (leave empty to use system PATH) + Visual Studio headers + <html><head/><body><p>Paths to Visual Studio headers, separated by semicolon ';'.</p><p>You can open a Visual Studio command prompt, write &quot;SET INCLUDE&quot;. Then copy/paste the paths.</p></body></html> + Code Editor + Code Editor Style + System Style + Default Light Style + Default Dark Style + Custom + Remove + Max count: + Applications Ohjelmat + + Edit... + Set as default + Reports Raportit + Save all errors when creating report Tallenna kaikki virheet raporttia luodessa + Save full path to files in reports Tallenna tiedostojen koko polku raportteihin + Language @@ -2870,221 +3026,245 @@ Määrittääksesi minkä tyyppisiä virheitä näytetään, avaa näkymä valik - - + + + + Statistics - + + Project + Project: + Paths: + Include paths: + Defines: + Undefines: - + + Previous Scan + Path Selected: + Number of Files Scanned: + Scan Duration: + Errors: + Warnings: + Stylistic warnings: + Portability warnings: + Performance issues: + Information messages: + Active checkers: + Checkers + History + File: + Copy to Clipboard + Pdf Export - + 1 day - + %1 days - + 1 hour - + %1 hours - + 1 minute - + %1 minutes - + 1 second - + %1 seconds - + 0.%1 seconds - + and - + Export PDF - + Project Settings - + Paths - + Include paths - + Defines - + Undefines - + Path selected - + Number of files scanned - + Scan duration - - + + Errors @@ -3099,32 +3279,32 @@ Määrittääksesi minkä tyyppisiä virheitä näytetään, avaa näkymä valik - - + + Warnings - - + + Style warnings - - + + Portability warnings - - + + Performance warnings - - + + Information messages @@ -3133,6 +3313,7 @@ Määrittääksesi minkä tyyppisiä virheitä näytetään, avaa näkymä valik ThreadDetails + Thread Details @@ -3157,9 +3338,8 @@ The user interface language has been reset to English. Open the Preferences-dial - Cppcheck - Cppcheck + Cppcheck diff --git a/gui/cppcheck_fr.ts b/gui/cppcheck_fr.ts index 82e0ea2bf74..7ac6f3ae89a 100644 --- a/gui/cppcheck_fr.ts +++ b/gui/cppcheck_fr.ts @@ -5,21 +5,25 @@ About + About Cppcheck A propos + Version %1 Version %1 + Cppcheck - A tool for static C/C++ code analysis. Cppcheck - Un outil d'analyse statique de code C/C++. + This program is licensed under the terms of the GNU General Public License version 3 Ce programme est sous licence GNU @@ -27,17 +31,20 @@ General Public License version 3 + Visit Cppcheck homepage at %1 Visitez le site Cppcheck : %1 + <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PCRE</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PicoJSON</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TinyXML2</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Boost</li></ul></body></html> <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">pcre</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">picojson</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">tinyxml2</li></ul></body></html> + Copyright © 2007-%1 Cppcheck team. Copyright © 2007-2021 Cppcheck team. @@ -47,11 +54,13 @@ General Public License version 3 ApplicationDialog + Add an application Ajouter une application + Browse Parcourir @@ -65,23 +74,21 @@ General Public License version 3 Select viewer application Sélection de l'application - - - Cppcheck - - + &Executable: &Exécutable : + &Parameters: &Paramètres : + Here you can add an application that can open error files. Specify a name for the application, the application executable and command line parameters for the application. The following texts in parameters are replaced with appropriate values when application is executed: @@ -107,6 +114,7 @@ Paramètres : -l(ligne) (fichier) + &Name: &Nom : @@ -116,70 +124,6 @@ Paramètres : -l(ligne) (fichier) Vous devez spécifier un nom, un chemin, et eventuellement des paramètres en option à l'application ! - - ComplianceReportDialog - - - Compliance Report - - - - - Project name - - - - - Project version - - - - - Coding Standard - - - - - Misra C - - - - - Cert C - - - - - Cert C++ - - - - - List of files with md5 checksums - - - - - Compliance report - - - - - HTML files (*.html) - - - - - - Save compliance report - - - - - Failed to import '%1' (%2), can not show files in compliance report - - - FileViewDialog @@ -187,12 +131,6 @@ Paramètres : -l(ligne) (fichier) Could not find the file: %1 Ne trouve pas le fichier : %1 - - - - Cppcheck - - Could not read the file: %1 @@ -203,44 +141,45 @@ Paramètres : -l(ligne) (fichier) HelpDialog + Cppcheck GUI help + Contents + Index - + Helpfile '%1' was not found - - - Cppcheck - - LibraryAddFunctionDialog + Add function Ajouter une fonction + Function name(s) Nom(s) de la fonction + Number of arguments Nombre d'arguments @@ -249,66 +188,79 @@ Paramètres : -l(ligne) (fichier) LibraryDialog + Library Editor + Open + Save Sauvegarder + Functions + Add Ajouter + noreturn + False + True + Unknown + return value must be used + ignore function in leaks checking + Arguments + Edit Editer @@ -325,31 +277,28 @@ Paramètres : -l(ligne) (fichier) + Sort + Filter: + Comments + Save as - - - - - Cppcheck - - Save the library as @@ -375,11 +324,13 @@ Paramètres : -l(ligne) (fichier) LibraryEditArgDialog + Edit argument + <html><head/><body> <p>Is bool value allowed? For instance result from comparison or from '!' operator.</p> <p>Typically, set this if the argument is a pointer, size, etc.</p> @@ -390,11 +341,13 @@ Paramètres : -l(ligne) (fichier) + Not bool + <html><head/><body> <p>Is a null parameter value allowed?</p> <p>Typically this should be used on any pointer parameter that does not allow null.</p> @@ -405,639 +358,676 @@ Paramètres : -l(ligne) (fichier) + Not null + Not uninit + String + Format string + Min size of buffer + + Type + + None + + argvalue + + mul + + strlen + + Arg + + Arg2 + and + Valid values MainWindow - - - - - - - - - - - - - - - - - Cppcheck - - + Checking for updates + Hide Cacher + &File &Fichier - + + &View &Affichage - - + + + + Report - + + &Help &Aide - + + &Edit &Édition - + + Standard Standard - + + &License... &Licence... - + + A&uthors... A&uteurs... - + + &About... À &Propos... - + + &Files... &Fichiers... - + + Ctrl+F - + + &Directory... &Répertoires... - + + Ctrl+D - + + Ctrl+R - + + &Stop &Arrêter - + + Esc - + + &Save results to file... &Sauvegarder les résultats dans un fichier... - + + Ctrl+S - + + &Quit &Quitter - + + &Clear results &Effacer les résultats - + + &Preferences &Préférences - + + &Check all &Tout cocher - + + &Uncheck all &Tout décocher - + + Collapse &all &Tout réduire - + + &Expand all &Tout dérouler - + + &Contents &Contenus - + + Open the help contents Ouvir l'aide - + + F1 - + + Compliance report... - + + Normal - - Misra C - - - - - Misra C++ 2008 - - - - - Cert C - - - - - Cert C++ - - - - - Misra C++ 2023 - - - - + + Autosar - + + EULA... - + + Thread Details - + + Show thread details - + License Licence - + Authors Auteurs - + Save the report file Sauvegarder le rapport - - + + XML files (*.xml) Fichiers XML (*.xml) - + About - + Text files (*.txt) Fichiers Texte (*.txt) - + CSV files (*.csv) Fichiers CSV (*.csv) - + + &Toolbars &Boite à outils - + + Categories Catégories - - + + + + Show style warnings Afficher les avertissements de style - - - + + + + + Show errors Afficher les erreurs - + + &Standard - + + Standard items - + + Toolbar - + + &Categories - + + Error categories - + + &Open XML... &Ouvrir un fichier XML... - + + Open P&roject File... Ouvrir un P&rojet... - + + &New Project File... &Nouveau Projet... - + + &Log View &Journal - + + Log View Journal - + + C&lose Project File F&ermer le projet - + + &Edit Project File... &Editer le projet - + + &Statistics Statistiques - - - + + + + + Show warnings Afficher les avertissements - - + + + + Show performance warnings Afficher les avertissements de performance - + + Show &hidden - - + + Information Information - + + Show information messages Afficher les messages d'information - + + Show portability warnings Afficher les problèmes de portabilité - + You must close the project file before selecting new files or directories! Vous devez d'abord fermer le projet avant de choisir des fichiers/répertoires - + Open the report file Ouvrir le rapport - - Cannot generate a compliance report right now, an analysis must finish successfully. Try to reanalyze the code and ensure there are no critical errors. - - - - + Project files (*.cppcheck);;All files(*.*) - + Select Project File - + To check the project using addons, you need a build directory. - + Failed to open file - + Unknown project file format - + Failed to import project file - + Failed to import '%1': %2 Analysis is stopped. - + Failed to import '%1' (%2), analysis is stopped - + Select Project Filename - + No project file loaded - + Install - + New version available: %1. %2 - + Show Mandatory - + Show Required - + Show Advisory - + Show Document - + Show L1 - + Show L2 - + Show L3 - + Show style - + Show portability - + Show performance - + Show information - + There was a problem with loading the editor application settings. This is probably because the settings were changed between the Cppcheck versions. Please check (and fix) the editor application settings, otherwise the editor program might not start correctly. - + + &Filter &Filtre - + + Filter results - - + + Quick Filter: Filtre rapide : - + Found project file: %1 Do you want to load this project file instead? - - - - + + + + Project: Projet : - + The project file %1 @@ -1048,32 +1038,38 @@ Do you want to remove the file from the recently used projects -list? - + + Filter Filtre - + + Windows 32-bit ANSI - + + Windows 32-bit Unicode - + + Unix 32-bit - + + Unix 64-bit - + + Windows 64-bit @@ -1083,105 +1079,105 @@ Do you want to remove the file from the recently used projects -list? - + + C++ standard - - - - + + + + Error Erreur - + File not found Fichier introuvable - + Bad XML Mauvais fichier XML - + Missing attribute Attribut manquant - + Bad attribute value Mauvaise valeur d'attribut - + Failed to load the selected library '%1'. %2 Echec lors du chargement de la bibliothèque '%1'. %2 - + Unsupported format Format non supporté - + The library '%1' contains unknown elements: %2 La bibliothèque '%1' contient des éléments inconnus: %2 - + Duplicate platform type - + Platform type redefined - + + &Print... &Imprimer... - + + Print the Current Report Imprimer le rapport - + + Print Pre&view... Apercu d'impression... - + + Open a Print Preview Dialog for the Current Results - + + Open library editor - - Unknown element - - - - + Unknown element - Unknown issue - + Select configuration @@ -1204,296 +1200,330 @@ Options: - + Build dir '%1' does not exist, create it? - - + + + + Analyze files - - + + + + Analyze directory - + + &Reanalyze modified files - - + + + + Stop analysis - + XML files (*.xml);;Text files (*.txt);;CSV files (*.csv) - + No suitable files found to analyze! - + + None of the selected files is part of the project, nothing to analyze. + + + + Select files to analyze - + Select directory to analyze - + Select the configuration that will be analyzed - + Found project files from the directory. Do you want to proceed analysis without using any of these project files? - + Duplicate define - + File not found: '%1' - + Failed to load/setup addon %1: %2 - + Failed to load %1. Your Cppcheck installation is broken. You can use --data-dir=<directory> at the command line to specify where this file is located. Please note that --data-dir is supposed to be used by installation scripts and therefore the GUI does not start when it is used, all that happens is that the setting is configured. Analysis is aborted. - + Failed to load %1 - %2 Analysis is aborted. - - + + %1 Analysis is aborted. - + Analyzer is running. Do you want to stop the analysis and exit Cppcheck? - + + A&nalyze - + + &C standard - + + Reanal&yze all files - + + Ctrl+Q - + + Style war&nings - + + E&rrors - + + Ctrl+Shift+O - + + Sh&ow Scratchpad... - + + Ctrl+Shift+N - + + &Warnings - + + Per&formance warnings - + + &Information - + + &Portability - + + Show Cppcheck results - + + Clang - + + Show Clang results - + + P&latforms - + + C++&11 - + + C&99 - + + &Posix - + + C&11 - + + &C89 - + + &C++03 - + + &Library Editor... - + + &Auto-detect language - + + &Enforce C++ - + + E&nforce C - + + C++14 - + Project files (*.cppcheck) - + + Reanalyze and check library - + + Check configuration (defines, includes) - + + C++17 - + + C++20 - + C/C++ Source - + Compile database - - Visual Studio - - - - - Borland C++ Builder 6 - - - - + Current results will be cleared. Opening a new XML file will clear current results. @@ -1505,26 +1535,31 @@ Do you want to proceed? NewSuppressionDialog + New suppression + Error ID + File name + Line number + Symbol name @@ -1536,31 +1571,6 @@ Do you want to proceed? Platforms - - - Unix 32-bit - - - - - Unix 64-bit - - - - - Windows 32-bit ANSI - - - - - Windows 32-bit Unicode - - - - - Windows 64-bit - - Native @@ -1571,388 +1581,472 @@ Do you want to proceed? ProjectFile + Project File Fichier Projet + Paths: Chemins : + Defines: + + Add... Ajouter... - + + + + Edit Editer - - + + + + + + Remove Supprimer + Up Monter + Down Descendre - + + Suppressions Suppressions - + + Add Ajouter - + + Note: Put your own custom .cfg files in the same folder as the project file. You should see them above. - + + ... + Include Paths: + Paths and Defines + <html><head/><body><p>You have a choice:</p><p> * Analyze all Debug and Release configurations</p><p> * Only analyze the first matching Debug configuration</p><p><br/></p></body></html> + Analyze all Visual Studio configurations - + + Root path: - + + Warning tags (separated by semicolon) - + + Cppcheck build dir (whole program analysis, incremental analysis, statistics, etc) + Selected VS Configurations - + + + Include file + + + + + + <html><head/><body><p><span style=" font-family:'monospace'; color:#000000; background-color:#ffffff;">Force inclusion of a header file</span></p></body></html> + + + + + + Browse.. + + + + + Types and Functions - + + Libraries - + + Parser - + + Cppcheck (built in) - + + Check that each class has a safe public interface - + + Limit analysis - - + + + + Addons - + + Note: Addons require <a href="https://www.python.org/">Python</a> being installed. - + + Y2038 - + + Thread safety - + + Coding standards - + + Cert C - + + CERT-INT35-C: int precision (if size equals precision, you can leave empty) - + + Autosar - + + Bug hunting - - - Clang analyzer - - - - - Clang-tidy - - - - + + + Browse... + Defines must be separated by a semicolon. Example: DEF1;DEF2=5;DEF3=int - + + Platform - + + This is a workfolder that Cppcheck will use for various purposes. - + + Clang (experimental) - + + Check level - + + Reduced -- meant for usage where developer wants results directly. Limited and faster analysis with fewer results. - + + Normal -- meant for normal analysis in CI. Analysis should finish in reasonable time. - + + Exhaustive -- meant for nightly builds etc. Analysis time can be longer (10x slower than compilation is OK). - + + If you want to design your classes to be as flexible and robust as possible then the public interface must be very robust. Cppcheck will asumme that arguments can take *any* value. - + + Check code in headers (should be ON normally. if you want a limited quick analysis then turn this OFF) - + + Max recursion in template instantiation - - Premium License - - - - + + Warning options - + + Filepaths in warnings will be relative to this path - + + If tags are added, you will be able to right click on warnings and set one of these tags. You can manually categorize warnings. - + + Exclude source files - + + Exclude folder... - + + Exclude file... - + + Enable inline suppressions - + + Misra C - + + 2012 - - + + + + 2023 - + + 2025 - + + MISRA rule texts - + + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> - + + Misra C++ - + + 2008 - + + Cert C++ - + + Safety profiles (defined in C++ core guidelines) - + + Bug hunting (Premium) - + + External tools + Import Project (Visual studio / compile database/ Borland C++ Builder 6) + Undefines: + Undefines must be separated by a semicolon. Example: UNDEF1;UNDEF2;UNDEF3 - - + + + + Analysis - + + Check code in unused templates (should be ON normally, however in theory you can safely ignore warnings in unused templates) Check code in unused templates (slower and less accurate analysis) - + + Max CTU depth @@ -1960,83 +2054,84 @@ Do you want to proceed? ProjectFileDialog - + Project file: %1 Fichier projet : %1 - + Select include directory Selectionner un répertoire à inclure - + Select directory to ignore Selectionner un répertoire à ignorer - + Select a directory to check Selectionner un répertoire à vérifier - + Select Cppcheck build dir - + Import Project - + Clang-tidy (not found) - + Note: Open source Cppcheck does not fully implement Misra C 2012 - + Source files - + + All files - - Exclude file + + C/C++ header - - Select MISRA rule texts file + + Include file - - MISRA rule texts file (%1) + + Exclude file - - Visual Studio + + Select MISRA rule texts file - - Compile database + + MISRA rule texts file (%1) - - Borland C++ Builder 6 + + Compile database @@ -2068,7 +2163,7 @@ Do you want to proceed? - + (Not found) @@ -2267,11 +2362,6 @@ Do you want to proceed? Tags - - - CWE - - QPlatformTheme @@ -2314,12 +2404,6 @@ Do you want to proceed? Undefined file Fichier indéterminé - - - - Cppcheck - - Could not start %1 @@ -2479,121 +2563,119 @@ Please select the default editor application in preferences/Applications.ResultsView + Results Résultats - - - Cppcheck - - - - + No errors found. Pas d'erreurs trouvées. - + Errors were found, but they are configured to be hidden. To toggle what kind of errors are shown, open view menu. Des erreurs ont été trouvées mais sont configurées pour rester cachées. Pour configurer les erreurs affichées, ouvrez le menu d'affichage. - + Analysis was stopped - + There was a critical error with id '%1' - + when checking %1 - + when checking a file - + Analysis was aborted. - - + + Failed to save the report. Erreur lors de la sauvegarde du rapport. - - + + Failed to read the report. Erreur lors de la lecture du rapport - + %p% (%1 of %2 files checked) %p% (%1 fichiers sur %2 vérifiés) - + Id Id - + Print Report Imprimer le rapport - + No errors found, nothing to print. Aucune erreur trouvée. Il n'y a rien à imprimer - + First included by - + XML format version 1 is no longer supported. + Critical errors + Analysis Log + Warning Details - + Clear Log - + Copy this Log entry - + Copy complete Log @@ -2602,26 +2684,31 @@ Pour configurer les erreurs affichées, ouvrez le menu d'affichage.ScratchPad + Scratchpad + filename + Check + Copy or write some C/C++ code here: + Optionally enter a filename (mainly for automatic language detection) and click on "Check": @@ -2630,122 +2717,147 @@ Pour configurer les erreurs affichées, ouvrez le menu d'affichage.Settings + Preferences Préférences + General Général + Number of threads: Nombre de fils : + Max count: + Show full path of files Montrer le chemin complet des fichiers + Show "No errors found" message when no errors found Afficher un message "Pas d'erreur trouvée" lorsque aucune erreur est trouvée + Check for updates + Applications Applications + Reports Rapports + Save all errors when creating report Sauvegarder toutes les erreurs lorsqu'un rapport est créé + Save full path to files in reports Sauvegarder le chemin complet des fichiers dans les rapports + Add... Ajouter... + Force checking all #ifdef configurations + Enable inline suppressions + Language Langue + Remove Supprimer + + Edit... Editer... + Set as default + Display error Id in column "Id" Afficher l'identifiant d'erreur Id dans la colonne "Id" + Check for inconclusive errors also + Show internal warnings in log Montrer les avertissements internes dans le journal + Show statistics on check completion + Addons + Python binary (leave this empty to use python in the PATH) @@ -2753,71 +2865,87 @@ Pour configurer les erreurs affichées, ouvrez le menu d'affichage. + + + ... + Clang + Clang path (leave empty to use system PATH) + Visual Studio headers + <html><head/><body><p>Paths to Visual Studio headers, separated by semicolon ';'.</p><p>You can open a Visual Studio command prompt, write &quot;SET INCLUDE&quot;. Then copy/paste the paths.</p></body></html> + MISRA addon + MISRA rule texts file + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> + Code Editor + Code Editor Style + Default Light Style + Default Dark Style + Custom + System Style @@ -2870,241 +2998,264 @@ Pour configurer les erreurs affichées, ouvrez le menu d'affichage. - - + + + + Statistics Statistiques - + + Project Projet + Project: Projet : + Paths: Chemins : + Include paths: Inclure les chemins : + Defines: - + + Previous Scan Analyse précédente + Path Selected: Chemin sélectionné : + Number of Files Scanned: Nombre de fichiers analysés : + Scan Duration: Durée de l'analyse : + Errors: Erreurs : + Warnings: Avertissements + Stylistic warnings: Avertissements de style + Portability warnings: Avertissements de portabilité + Performance issues: Problème de performance + Information messages: Messages d'information : + Active checkers: + Checkers + Copy to Clipboard Copier vers le presse-papier - + 1 day - + %1 days - + 1 hour - + %1 hours - + 1 minute - + %1 minutes - + 1 second - + %1 seconds - + 0.%1 seconds - + and - + Project Settings - + Paths Chemins - + Include paths - + Defines - + Path selected - + Number of files scanned - + Scan duration - - + + Errors Erreurs - - + + Warnings Avertissements - - + + Style warnings Avertissement de style - - + + Portability warnings - - + + Performance warnings Avertissements de performance - - + + Information messages + Pdf Export - + Export PDF + History + File: @@ -3120,11 +3271,12 @@ Pour configurer les erreurs affichées, ouvrez le menu d'affichage. + Undefines: - + Undefines @@ -3133,6 +3285,7 @@ Pour configurer les erreurs affichées, ouvrez le menu d'affichage.ThreadDetails + Thread Details @@ -3156,11 +3309,6 @@ Pour configurer les erreurs affichées, ouvrez le menu d'affichage. - - - Cppcheck - - TxtReport diff --git a/gui/cppcheck_it.ts b/gui/cppcheck_it.ts index 1c8829bb198..4d0823a7053 100644 --- a/gui/cppcheck_it.ts +++ b/gui/cppcheck_it.ts @@ -5,27 +5,32 @@ About + About Cppcheck Informazioni su Cppcheck + Version %1 Versione %1 + Cppcheck - A tool for static C/C++ code analysis. Cppcheck - Uno strumento per l'analisi statica di codice C/C++ + Copyright © 2007-%1 Cppcheck team. Copyright © 2007-2021 Cppcheck team. Copyright © 2007-2021 il team Cppcheck. + This program is licensed under the terms of the GNU General Public License version 3 Questo programma è rilasciato sotto i termini @@ -33,11 +38,13 @@ della GNU General Public License versione 3 + Visit Cppcheck homepage at %1 Visita la pagina iniziale di Cppcheck all'indirizzo: %1 + <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PCRE</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PicoJSON</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TinyXML2</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Boost</li></ul></body></html> <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">pcre</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">picojson</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">tinyxml2</li></ul></body></html> @@ -47,11 +54,13 @@ della GNU General Public License versione 3 ApplicationDialog + Add an application Aggiungi un'applicazione + Here you can add an application that can open error files. Specify a name for the application, the application executable and command line parameters for the application. The following texts in parameters are replaced with appropriate values when application is executed: @@ -78,21 +87,25 @@ Parametri: -l(line) (file) + &Name: &Nome: + &Executable: &Eseguibile: + &Parameters: &Parametri: + Browse Esplora @@ -107,9 +120,8 @@ Parametri: -l(line) (file) Seleziona l'applicazione di lettura - Cppcheck - Cppcheck + Cppcheck @@ -117,70 +129,6 @@ Parametri: -l(line) (file) Devi specificare un nome, un percorso ed, opzionalmente, i parametri per l'applicazione! - - ComplianceReportDialog - - - Compliance Report - - - - - Project name - - - - - Project version - - - - - Coding Standard - - - - - Misra C - - - - - Cert C - - - - - Cert C++ - - - - - List of files with md5 checksums - - - - - Compliance report - - - - - HTML files (*.html) - - - - - - Save compliance report - - - - - Failed to import '%1' (%2), can not show files in compliance report - - - FileViewDialog @@ -189,10 +137,8 @@ Parametri: -l(line) (file) File non trovato: %1 - - Cppcheck - Cppcheck + Cppcheck @@ -204,44 +150,49 @@ Parametri: -l(line) (file) HelpDialog + Cppcheck GUI help + Contents + Index - + Helpfile '%1' was not found - Cppcheck - Cppcheck + Cppcheck LibraryAddFunctionDialog + Add function + Function name(s) + Number of arguments @@ -250,86 +201,103 @@ Parametri: -l(line) (file) LibraryDialog + Library Editor + Open + Save + Save as + Functions + Sort + Add + Filter: + Comments + noreturn + False + True + Unknown + return value must be used + ignore function in leaks checking + Arguments + Edit Modifica @@ -345,11 +313,8 @@ Parametri: -l(line) (file) - - - Cppcheck - Cppcheck + Cppcheck @@ -378,11 +343,13 @@ Parametri: -l(line) (file) LibraryEditArgDialog + Edit argument + <html><head/><body> <p>Is bool value allowed? For instance result from comparison or from '!' operator.</p> <p>Typically, set this if the argument is a pointer, size, etc.</p> @@ -393,11 +360,13 @@ Parametri: -l(line) (file) + Not bool + <html><head/><body> <p>Is a null parameter value allowed?</p> <p>Typically this should be used on any pointer parameter that does not allow null.</p> @@ -408,78 +377,99 @@ Parametri: -l(line) (file) + Not null + Not uninit + String + Format string + Min size of buffer + + Type + + None + + argvalue + + mul + + strlen + + Arg + + Arg2 + and + Valid values @@ -487,623 +477,699 @@ Parametri: -l(line) (file) MainWindow - - - - - - - - - - - - - - - Cppcheck - Cppcheck + Cppcheck - + + A&nalyze - + + Standard Standard + &File &File - + + &View &Visualizza - + + &Toolbars &Barre degli strumenti - + + C++ standard - + + &C standard C standard - + + &Edit &Modifica - + + &License... &Licenza... - + + A&uthors... A&utori... - + + &About... I&nformazioni su... - + + &Files... &File... - - + + + + Analyze files Check files Scansiona i file - + + Ctrl+F Ctrl+F - + + &Directory... &Cartella... - - + + + + Analyze directory Check directory Scansiona la cartella - + + Ctrl+D Ctrl+D - + + Ctrl+R Ctrl+R - + + &Stop &Ferma - - + + + + Stop analysis Stop checking Ferma la scansione - + + Esc Esc - + + &Save results to file... &Salva i risultati nel file... - + + Ctrl+S Ctrl+S - + + &Quit &Esci - + + &Clear results &Cancella i risultati - + + &Preferences &Preferenze - - - + + + + + Show errors Mostra gli errori - - - + + + + + Show warnings Mostra gli avvisi - - + + + + Show performance warnings Mostra gli avvisi sulle prestazioni - + + Show &hidden Mostra &i nascosti - - + + Information Informazione - + + Show information messages Mostra messaggi di informazione - + + Show portability warnings Mostra gli avvisi sulla portabilità - + + Show Cppcheck results - + + Clang - + + Show Clang results - + + &Filter &Filtro - + + Filter results Filtra i risultati - + + Windows 32-bit ANSI Windows 32-bit, ANSI - + + Windows 32-bit Unicode Windows 32-bit, Unicode - + + Unix 32-bit Unix 32-bit - + + Unix 64-bit Unix 64-bit - + + Windows 64-bit Windows 64-bit - + + &Print... - + + Print the Current Report - + + Print Pre&view... - + + Open a Print Preview Dialog for the Current Results - + + Open library editor - + + &Check all &Seleziona tutto + Checking for updates + Hide Nascondi - - + + + + Report - + + Filter Filtro - + + &Reanalyze modified files &Recheck modified files - + + Reanal&yze all files - + + Ctrl+Q - + + Style war&nings - + + E&rrors - + + &Uncheck all &Deseleziona tutto - + + Collapse &all Riduci &tutto - + + &Expand all &Espandi tutto - + + &Standard &Standard - + + Standard items Oggetti standard - + + Toolbar Barra degli strumenti - + + &Categories &Categorie - + + Error categories Categorie di errore - + + &Open XML... &Apri XML... - + + Open P&roject File... Apri file di p&rogetto... - + + Ctrl+Shift+O - + + Sh&ow Scratchpad... - + + &New Project File... &Nuovo file di progetto... - + + Ctrl+Shift+N - + + &Log View &Visualizza il rapporto - + + Log View Visualizza il rapporto - + + C&lose Project File C&hiudi il file di progetto - + + &Edit Project File... &Modifica il file di progetto... - + + &Statistics &Statistiche - + + &Warnings - + + Per&formance warnings - + + &Information - + + &Portability - + + P&latforms - + + C++&11 - + + C&99 - + + &Posix - + + C&11 - + + &C89 - + + &C++03 - + + &Library Editor... - + + &Auto-detect language - + + &Enforce C++ - + + E&nforce C - + + C++14 C++14 - + + Reanalyze and check library - + + Check configuration (defines, includes) - + + C++17 C++17 - + + C++20 C++20 - + + Compliance report... - + + Normal - - Misra C - - - - - Misra C++ 2008 - - - - - Cert C - - - - - Cert C++ - - - - - Misra C++ 2023 - - - - + + Autosar - + + EULA... - + + Thread Details - + + Show thread details - + + &Contents &Contenuti - + + Categories Categorie - - + + + + Show style warnings Mostra gli avvisi sullo stile - + + Open the help contents Apri i contenuti di aiuto - + + F1 F1 - + + &Help &Aiuto - - + + Quick Filter: Rapido filtro: - + Select configuration - + Found project file: %1 Do you want to load this project file instead? @@ -1112,96 +1178,96 @@ Do you want to load this project file instead? Vuoi piuttosto caricare questo file di progetto? - + File not found - + Bad XML - + Missing attribute - + Bad attribute value - + Unsupported format - + Duplicate define - + Failed to load the selected library '%1'. %2 - + File not found: '%1' - + Failed to load/setup addon %1: %2 - + Failed to load %1. Your Cppcheck installation is broken. You can use --data-dir=<directory> at the command line to specify where this file is located. Please note that --data-dir is supposed to be used by installation scripts and therefore the GUI does not start when it is used, all that happens is that the setting is configured. Analysis is aborted. - + Failed to load %1 - %2 Analysis is aborted. - - + + %1 Analysis is aborted. - + License Licenza - + Authors Autori - + Save the report file Salva il file di rapporto - - + + XML files (*.xml) File XML (*.xml) - + There was a problem with loading the editor application settings. This is probably because the settings were changed between the Cppcheck versions. Please check (and fix) the editor application settings, otherwise the editor program might not start correctly. @@ -1210,127 +1276,116 @@ This is probably because the settings were changed between the Cppcheck versions Probabilmente ciò è avvenuto perché le impostazioni sono state modificate tra le versioni di Cppcheck. Per favore controlla (e sistema) le impostazioni delle applicazioni editor, altrimenti il programma editor può non partire correttamente. - + You must close the project file before selecting new files or directories! Devi chiudere il file di progetto prima di selezionare nuovi file o cartelle! - + The library '%1' contains unknown elements: %2 - + Duplicate platform type - + Platform type redefined - + Unknown element - - Unknown element - Unknown issue - - - - - - - + + + + Error - + Open the report file Apri il file di rapporto - + Text files (*.txt) File di testo (*.txt) - + CSV files (*.csv) Files CSV (*.csv) - + Project files (*.cppcheck);;All files(*.*) Files di progetto (*.cppcheck);;Tutti i files(*.*) - + Select Project File Seleziona il file di progetto - - - - + + + + Project: Progetto: - - No suitable files found to analyze! + + None of the selected files is part of the project, nothing to analyze. - - C/C++ Source - - - - - Compile database + + No suitable files found to analyze! - - Visual Studio + + C/C++ Source - - Borland C++ Builder 6 + + Compile database - + Select files to analyze - + Select directory to analyze - + Select the configuration that will be analyzed - + Found project files from the directory. Do you want to proceed analysis without using any of these project files? - + Current results will be cleared. Opening a new XML file will clear current results. @@ -1338,81 +1393,76 @@ Do you want to proceed? - + Analyzer is running. Do you want to stop the analysis and exit Cppcheck? - + About - + XML files (*.xml);;Text files (*.txt);;CSV files (*.csv) - - Cannot generate a compliance report right now, an analysis must finish successfully. Try to reanalyze the code and ensure there are no critical errors. - - - - + Build dir '%1' does not exist, create it? - + To check the project using addons, you need a build directory. - + Failed to open file - + Unknown project file format - + Failed to import project file - + Failed to import '%1': %2 Analysis is stopped. - + Failed to import '%1' (%2), analysis is stopped - + Project files (*.cppcheck) - + Select Project Filename Seleziona il nome del file di progetto - + No project file loaded Nessun file di progetto caricato - + The project file %1 @@ -1429,67 +1479,67 @@ Do you want to remove the file from the recently used projects -list? Vuoi rimuovere il file dalla lista dei progetti recentemente usati? - + Install - + New version available: %1. %2 - + Show Mandatory - + Show Required - + Show Advisory - + Show Document - + Show L1 - + Show L2 - + Show L3 - + Show style - + Show portability - + Show performance - + Show information @@ -1533,26 +1583,31 @@ Options: NewSuppressionDialog + New suppression + Error ID + File name + Line number + Symbol name @@ -1570,419 +1625,498 @@ Options: - Unix 32-bit - Unix 32-bit + Unix 32-bit - Unix 64-bit - Unix 64-bit + Unix 64-bit - Windows 32-bit ANSI - Windows 32-bit, ANSI + Windows 32-bit, ANSI - Windows 32-bit Unicode - Windows 32-bit, Unicode + Windows 32-bit, Unicode - Windows 64-bit - Windows 64-bit + Windows 64-bit ProjectFile + Project File File di progetto + Paths and Defines + Import Project (Visual studio / compile database/ Borland C++ Builder 6) Import Project (Visual studio / compile database) + Defines must be separated by a semicolon. Example: DEF1;DEF2=5;DEF3=int Defines must be separated by a semicolon ';' - + + Note: Put your own custom .cfg files in the same folder as the project file. You should see them above. - + + If tags are added, you will be able to right click on warnings and set one of these tags. You can manually categorize warnings. - + + Exclude source files - + + Exclude folder... - + + Exclude file... - + + Misra C - + + 2012 - - + + + + 2023 - + + MISRA rule texts - + + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> - + + ... + <html><head/><body><p>You have a choice:</p><p> * Analyze all Debug and Release configurations</p><p> * Only analyze the first matching Debug configuration</p><p><br/></p></body></html> - - + + + Browse... + Analyze all Visual Studio configurations + Selected VS Configurations + Paths: Percorsi: + + Add... Aggiungi... - + + + + Edit Modifica - - + + + + + + Remove Rimuovi + Undefines: + Undefines must be separated by a semicolon. Example: UNDEF1;UNDEF2;UNDEF3 + Include Paths: - + + + Include file + + + + + + <html><head/><body><p><span style=" font-family:'monospace'; color:#000000; background-color:#ffffff;">Force inclusion of a header file</span></p></body></html> + + + + + + Browse.. + + + + + Types and Functions - - + + + + Analysis - + + This is a workfolder that Cppcheck will use for various purposes. - + + Parser - + + Cppcheck (built in) - + + Check level - + + Reduced -- meant for usage where developer wants results directly. Limited and faster analysis with fewer results. - + + Normal -- meant for normal analysis in CI. Analysis should finish in reasonable time. - + + Exhaustive -- meant for nightly builds etc. Analysis time can be longer (10x slower than compilation is OK). - + + Check that each class has a safe public interface - + + Limit analysis - + + Check code in unused templates (should be ON normally, however in theory you can safely ignore warnings in unused templates) Check code in unused templates (slower and less accurate analysis) - + + Max CTU depth - - Premium License - - - - + + Enable inline suppressions Abilita le soppressioni - + + 2025 - + + Misra C++ - + + 2008 - + + Cert C - + + CERT-INT35-C: int precision (if size equals precision, you can leave empty) - + + Autosar - + + Safety profiles (defined in C++ core guidelines) - + + Bug hunting - + + External tools + Up Su + Down Giù - + + Platform - + + Clang (experimental) - + + If you want to design your classes to be as flexible and robust as possible then the public interface must be very robust. Cppcheck will asumme that arguments can take *any* value. - + + Check code in headers (should be ON normally. if you want a limited quick analysis then turn this OFF) - + + Max recursion in template instantiation - + + Warning options - + + Root path: - + + Filepaths in warnings will be relative to this path - + + Warning tags (separated by semicolon) - + + Cppcheck build dir (whole program analysis, incremental analysis, statistics, etc) - + + Libraries - + + Suppressions - + + Add - - + + + + Addons - + + Note: Addons require <a href="https://www.python.org/">Python</a> being installed. - + + Y2038 - + + Thread safety - + + Coding standards - + + Cert C++ - + + Bug hunting (Premium) - - - Clang analyzer - - - - - Clang-tidy - - + Defines: Definizioni: @@ -1990,82 +2124,83 @@ Options: ProjectFileDialog - + Project file: %1 File di progetto: %1 - + Select Cppcheck build dir - + Select include directory Seleziona la cartella da includere - + Select a directory to check Seleziona una cartella da scansionare - + Note: Open source Cppcheck does not fully implement Misra C 2012 - + Clang-tidy (not found) - - Visual Studio + + Compile database - - Compile database + + Import Project - - Borland C++ Builder 6 + + C/C++ header - - Import Project + + Include file - + Select directory to ignore Seleziona la cartella da ignorare - + Source files - + + All files - + Exclude file - + Select MISRA rule texts file - + MISRA rule texts file (%1) @@ -2098,7 +2233,7 @@ Options: - + (Not found) @@ -2298,11 +2433,6 @@ Options: Tags - - - CWE - - QPlatformTheme @@ -2429,10 +2559,8 @@ Options: - - Cppcheck - Cppcheck + Cppcheck @@ -2509,122 +2637,124 @@ Per favore verifica che il percorso dell'applicazione e i parametri siano c ResultsView - + Print Report - + No errors found, nothing to print. - + %p% (%1 of %2 files checked) %p% (%1 su %2 file scansionati) - - Cppcheck - Cppcheck + Cppcheck - + No errors found. Nessun errore trovato. - + Errors were found, but they are configured to be hidden. To toggle what kind of errors are shown, open view menu. Sono stati trovati errori, ma sono stati configurati per essere nascosti. Per vedere il tipo di errori che sono mostrati, apri il menu Visualizza. - - + + Failed to read the report. Apertura del report fallito. - + XML format version 1 is no longer supported. - + First included by - + Id Id - + Clear Log - + Copy this Log entry - + Copy complete Log - + Analysis was stopped - + There was a critical error with id '%1' - + when checking %1 - + when checking a file - + Analysis was aborted. - - + + Failed to save the report. Salvataggio del report fallito. + Results Risultati + Critical errors + Analysis Log + Warning Details @@ -2633,26 +2763,31 @@ Per vedere il tipo di errori che sono mostrati, apri il menu Visualizza.ScratchPad + Scratchpad Blocchetto per appunti + Copy or write some C/C++ code here: + Optionally enter a filename (mainly for automatic language detection) and click on "Check": + filename Nome file + Check Scansiona @@ -2661,21 +2796,25 @@ Per vedere il tipo di errori che sono mostrati, apri il menu Visualizza.Settings + Preferences Preferenze + General Generale + Add... Aggiungi... + Number of threads: Numero di threads: @@ -2685,56 +2824,67 @@ Per vedere il tipo di errori che sono mostrati, apri il menu Visualizza. + Force checking all #ifdef configurations Forza la scansione di tutte le configurazioni #ifdef + Show full path of files Mostra tutto il percorso dei files + Show "No errors found" message when no errors found Mostra il messaggio "Nessun errore trovato" quando nessun errore è stato trovato + Display error Id in column "Id" Mostra l'id dell'errore nella colonna "Id" + Enable inline suppressions Abilita le soppressioni + Check for inconclusive errors also + Show statistics on check completion + Check for updates + Show internal warnings in log + Addons + Python binary (leave this empty to use python in the PATH) @@ -2742,117 +2892,143 @@ Per vedere il tipo di errori che sono mostrati, apri il menu Visualizza. + + + ... + MISRA addon + MISRA rule texts file + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> + Clang + Clang path (leave empty to use system PATH) + Visual Studio headers + <html><head/><body><p>Paths to Visual Studio headers, separated by semicolon ';'.</p><p>You can open a Visual Studio command prompt, write &quot;SET INCLUDE&quot;. Then copy/paste the paths.</p></body></html> + Code Editor + Code Editor Style + System Style + Default Light Style + Default Dark Style + Custom + Remove Rimuovi + Max count: + Applications Applicazioni + + Edit... Modifica... + Set as default Imposta come predefinito + Reports Rapporti + Save all errors when creating report Salva tutti gli errori quando viene creato il rapporto + Save full path to files in reports Salva tutto il percorso ai files nei rapporti + Language Lingua @@ -2909,221 +3085,245 @@ Per vedere il tipo di errori che sono mostrati, apri il menu Visualizza. - - + + + + Statistics Statistiche - + + Project Progetto + Project: Progetto: + Paths: Percorsi: + Include paths: Percorsi di inclusione: + Defines: Definizioni: + Undefines: - + + Previous Scan Precedente Scansione + Path Selected: Selezionato percorso: + Number of Files Scanned: Numero di Files Scansionati: + Scan Duration: Durata della scansione: + Errors: Errori: + Warnings: Avvisi: + Stylistic warnings: Avvisi sullo stile: + Portability warnings: Avvisi sulla portabilità: + Performance issues: Casi sulla performance: + Information messages: Messaggi di informazione: + Active checkers: + Checkers + History + File: + Copy to Clipboard Copia negli Appunti + Pdf Export - + 1 day 1 giorno - + %1 days %1 giorni - + 1 hour 1 ora - + %1 hours %1 ore - + 1 minute 1 minuto - + %1 minutes %1 minuti - + 1 second 1 secondo - + %1 seconds %1 secondi - + 0.%1 seconds 0,%1 secondi - + and e - + Export PDF - + Project Settings Impostazioni progetto - + Paths Percorsi - + Include paths Percorsi di inclusione - + Defines Definizioni - + Undefines - + Path selected Selezionato percorso - + Number of files scanned Numero di file scansionati - + Scan duration Durata della scansione - - + + Errors Errori @@ -3138,32 +3338,32 @@ Per vedere il tipo di errori che sono mostrati, apri il menu Visualizza. - - + + Warnings Avvisi - - + + Style warnings Stilwarnungen - - + + Portability warnings Avvisi sulla portabilità - - + + Performance warnings Avvisi sulle performance - - + + Information messages Messaggi di informazione @@ -3172,6 +3372,7 @@ Per vedere il tipo di errori che sono mostrati, apri il menu Visualizza.ThreadDetails + Thread Details @@ -3200,9 +3401,8 @@ The user interface language has been reset to English. Open the Preferences-dial L'interfaccia utente è stata risettata in Inglese. Apri la finestra di dialogo Preferenze per selezionare una qualunque lingua a disposizione. - Cppcheck - Cppcheck + Cppcheck diff --git a/gui/cppcheck_ja.ts b/gui/cppcheck_ja.ts index 3e486f416dd..75b2bad9985 100644 --- a/gui/cppcheck_ja.ts +++ b/gui/cppcheck_ja.ts @@ -5,38 +5,45 @@ About + About Cppcheck cppcheckについて + Version %1 Version %1 + Cppcheck - A tool for static C/C++ code analysis. CppcheckはC/C++ 静的コード解析ツールです. + Copyright © 2007-%1 Cppcheck team. Copyright © 2007-2021 Cppcheck team. Copyright © 2007-%1 Cppcheck team. + This program is licensed under the terms of the GNU General Public License version 3 本ソフトウェアはGNU General Public License Version3 ライセンスの元で配布されます + Visit Cppcheck homepage at %1 Cppcheckのホームページはこちら %1 + <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PCRE</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PicoJSON</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TinyXML2</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Boost</li></ul></body></html> <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">pcre</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">picojson</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">tinyxml2</li></ul></body></html> <html><head/><body><p>ライブラリの開発者に感謝を捧げます:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PCRE</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PicoJSON</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TinyXML2</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Boost</li></ul></body></html> @@ -46,11 +53,13 @@ of the GNU General Public License version 3 ApplicationDialog + Add an application アプリケーションの追加 + Here you can add an application that can open error files. Specify a name for the application, the application executable and command line parameters for the application. The following texts in parameters are replaced with appropriate values when application is executed: @@ -77,21 +86,25 @@ Parameters: -l(line) (file) + &Name: 表示名(&N): + &Executable: 実行ファイルのパス(&E): + &Parameters: パラメータ(&P): + Browse 参照 @@ -106,9 +119,8 @@ Parameters: -l(line) (file) 表示アプリケーションの選択 - Cppcheck - Cppcheck + Cppcheck @@ -119,65 +131,52 @@ Parameters: -l(line) (file) ComplianceReportDialog - Compliance Report - コンプライアンスレポート + コンプライアンスレポート - Project name - プロジェクト名 + プロジェクト名 - Project version - プロジェクトバージョン + プロジェクトバージョン - Coding Standard - コーディング標準 + コーディング標準 - Misra C - MISRA C + MISRA C - Cert C - CERT C + CERT C - Cert C++ - Cert C++ + Cert C++ - List of files with md5 checksums - md5チェックサムつきファイルリスト + md5チェックサムつきファイルリスト - Compliance report - コンプライアンスレポート + コンプライアンスレポート - HTML files (*.html) - HTMLファイル(*.html) + HTMLファイル(*.html) - - Save compliance report - コンプライアンスレポートの保存 + コンプライアンスレポートの保存 - Failed to import '%1' (%2), can not show files in compliance report - '%1' (%2)のインポートに失敗しました。コンプライアンスレポートに表示できません + '%1' (%2)のインポートに失敗しました。コンプライアンスレポートに表示できません Failed to import '%1', can not show files in compliance report @@ -192,10 +191,8 @@ Parameters: -l(line) (file) ファイル:%1 が見つかりません - - Cppcheck - Cppcheck + Cppcheck @@ -207,44 +204,49 @@ Parameters: -l(line) (file) HelpDialog + Cppcheck GUI help Cppcheck GUI ヘルプ + Contents 目次 + Index インデックス - + Helpfile '%1' was not found ヘルプファイル '%1' が見つかりません - Cppcheck - Cppcheck + Cppcheck LibraryAddFunctionDialog + Add function 関数の追加 + Function name(s) 関数の名称 + Number of arguments 引数の数 @@ -253,86 +255,103 @@ Parameters: -l(line) (file) LibraryDialog + Library Editor ライブラリエディタ + Open 開く + Save 保存する + Save as 別名で保存する + Functions 関数 + Sort ソート + Add 追加 + Filter: フィルタ: + Comments コメント + noreturn noreturn(返り値なし) + False False(偽) + True True(真) + Unknown Unknown(不明) + return value must be used 返り値は使用されなければならない + ignore function in leaks checking リークの解析中に無視する関数 + Arguments Arguments(引数) + Edit 編集 @@ -348,11 +367,8 @@ Parameters: -l(line) (file) ライブラリファイルを開く - - - Cppcheck - Cppcheck + Cppcheck @@ -381,11 +397,13 @@ Parameters: -l(line) (file) LibraryEditArgDialog + Edit argument 引数の編集 + <html><head/><body> <p>Is bool value allowed? For instance result from comparison or from '!' operator.</p> <p>Typically, set this if the argument is a pointer, size, etc.</p> @@ -401,12 +419,14 @@ Parameters: -l(line) (file) + Not bool 非ブール型 非bool値 + <html><head/><body> <p>Is a null parameter value allowed?</p> <p>Typically this should be used on any pointer parameter that does not allow null.</p> @@ -422,78 +442,99 @@ Parameters: -l(line) (file) + Not null 非NULL + Not uninit 未初期化 + String 文字列 + Format string フォーマット文字列 + Min size of buffer バッファの最小サイズ + + Type Type(型) + + None None(無) + + argvalue argvalue(引数の値) + + mul mul(積) + + strlen strlen(文字数) + + Arg Arg(引数) + + Arg2 Arg2(第二引数) + and and(和) + Valid values 妥当な値 @@ -501,612 +542,708 @@ Parameters: -l(line) (file) MainWindow - - - - - - - - - - - - - - - Cppcheck - Cppcheck + Cppcheck + &File ファイル(&F) - + + &View 表示(&V) - + + &Toolbars ツールバー(&T) - + + &Help ヘルプ(&H) - + + C++ standard C++標準 - + + &C standard C standard &C標準 - + + &Edit 編集(&E) - + + Standard 言語規格 - + + Categories カテゴリ - + + &License... ライセンス(&L)... - + + A&uthors... 作者(&u)... - + + &About... Cppcheckについて(&A)... - + + &Files... ファイル選択(&F)... - - + + + + Analyze files Check files ファイルをチェックする - + + Ctrl+F Ctrl+F - + + &Directory... ディレクトリ選択(&D)... - - + + + + Analyze directory Check directory ディレクトリをチェックする - + + Ctrl+D Ctrl+D - + + Ctrl+R Ctrl+R - + + &Stop 停止(&S) - - + + + + Stop analysis Stop checking チェックを停止する - + + Esc Esc - + + &Save results to file... 結果をファイルに保存(&S)... - + + Ctrl+S Ctrl+S - + + &Quit 終了(&Q) - + + &Clear results 結果をクリア(&C) - + + &Preferences 設定(&P) - - + + + + Show style warnings スタイル警告を表示 - - - + + + + + Show errors エラーを表示 - - + + Information 情報 - + + Show information messages 情報メッセージを表示 - + + Show portability warnings 移植可能性の問題を表示 - + + Show Cppcheck results Cppcheck結果を表示する - + + Clang Clang - + + Show Clang results Clangの結果を表示 - + + &Filter フィルター(&F) - + + Filter results フィルタ結果 - + + Windows 32-bit ANSI Windows 32-bit ANSIエンコード - + + Windows 32-bit Unicode Windows 32-bit Unicode - + + Unix 32-bit Unix 32-bit - + + Unix 64-bit Unix 64-bit - + + Windows 64-bit Windows 64-bit - + + &Print... 印刷(&P)... - + + Print the Current Report 現在のレポートを印刷 - + + Print Pre&view... 印刷プレビュー(&v)... - + + Open a Print Preview Dialog for the Current Results 現在のレポートをプレビュー表示 - + + Open library editor ライブラリエディタを開く - + + C&lose Project File プロジェクトを閉じる(&l) - + + &Edit Project File... プロジェクトの編集(&E)... - + + &Statistics 統計情報(&S) - - - + + + + + Show warnings 警告を表示 - - + + + + Show performance warnings パフォーマンス警告を表示 - + + Show &hidden 非表示を表示(&h) - + + &Check all すべてのエラーを表示(&C) + Checking for updates 更新の確認 + Hide 非表示 - - + + + + Report レポート - + + A&nalyze チェック(&n) - + + Filter フィルター - + + &Reanalyze modified files &Recheck modified files 変更ありファイルを再解析(&R) - + + Reanal&yze all files 全ファイル再解析(&y) - + + Ctrl+Q Ctrl+Q - + + Style war&nings スタイル警告(&n) - + + E&rrors エラー(&r) - + + &Uncheck all すべてのエラーを非表示(&U) - + + Collapse &all ツリーを折り畳む(&a) - + + &Expand all ツリーを展開(&E) - + + &Standard 言語規格(&S) - + + Standard items 標準項目 - + + &Contents コンテンツ(&C) - + + Open the help contents ヘルプファイルを開く - + + F1 F1 - + + Toolbar ツールバー - + + &Categories カテゴリ(&C) - + + Error categories エラーカテゴリ - + + &Open XML... XMLを開く(&O)... - + + Open P&roject File... プロジェクトを開く(&R)... - + + Ctrl+Shift+O Ctrl+Shift+O - + + Sh&ow Scratchpad... スクラッチパッドを表示(&o)... - + + &New Project File... 新規プロジェクト(&N)... - + + Ctrl+Shift+N Ctrl+Shift+N - + + &Log View ログを表示(&L) - + + Log View ログ表示 - + + &Warnings 警告(&W) - + + Per&formance warnings パフォーマンス警告(&f) - + + &Information 情報(&I) - + + &Portability 移植可能性(&P) - + + P&latforms プラットフォーム(&l) - + + C++&11 C++11(&1) - + + C&99 C99(&9) - + + &Posix Posix(&P) - + + C&11 C11(&1) - + + &C89 C89(&C) - + + &C++03 C++03(&C) - + + &Library Editor... ライブラリエディタ(&L)... - + + &Auto-detect language 自動言語検出(&A) - + + &Enforce C++ C++ 強制(&E) - + + E&nforce C C 強制(&n) - + + C++14 C++14 - + + Reanalyze and check library ライブラリを再チェックする - + + Check configuration (defines, includes) チェックの設定(define、インクルード) - + + C++17 C++17 - + + C++20 C++20 - + + Compliance report... コンプライアンスレポート... - + + Normal ノーマル - Misra C - MISRA C + MISRA C - Misra C++ 2008 - MISRA C++ 2008 + MISRA C++ 2008 - Cert C - CERT C + CERT C - Cert C++ - Cert C++ + Cert C++ - Misra C++ 2023 - MISRA C++ 2023 + MISRA C++ 2023 - + + Autosar AUTOSAR - + + EULA... EULA... - + + Thread Details スレッド詳細 - + + Show thread details スレッド詳細を表示 - + There was a problem with loading the editor application settings. This is probably because the settings were changed between the Cppcheck versions. Please check (and fix) the editor application settings, otherwise the editor program might not start correctly. @@ -1115,23 +1252,28 @@ This is probably because the settings were changed between the Cppcheck versions Cppcheckの古いバージョンの設定には互換性がありません。エディタアプリケーションの設定を確認して修正してください、そうしないと正しく起動できないかもしれません。 - + You must close the project file before selecting new files or directories! 新しいファイル/ディレクトリをチェックするには現在のプロジェクトを閉じてください! - - + + Quick Filter: クイックフィルタ: - + + None of the selected files is part of the project, nothing to analyze. + + + + Select configuration コンフィグレーションの選択 - + Found project file: %1 Do you want to load this project file instead? @@ -1140,64 +1282,64 @@ Do you want to load this project file instead? 現在のプロジェクトの代わりにこのプロジェクトファイルを読み込んでもかまいませんか? - + The library '%1' contains unknown elements: %2 このライブラリ '%1' には次の不明な要素が含まれています。 %2 - + File not found ファイルがありません - + Bad XML 不正なXML - + Missing attribute 属性がありません - + Bad attribute value 不正な属性があります - + Unsupported format サポートされていないフォーマット - + Duplicate platform type プラットフォームの種類が重複しています - + Platform type redefined プラットフォームの種類が再定義されました - + Unknown element 不明な要素 - + Failed to load the selected library '%1'. %2 選択したライブラリの読み込みに失敗しました '%1' %2 - - - - + + + + Error エラー @@ -1210,73 +1352,72 @@ Do you want to load this project file instead? %1 - %2 の読み込みに失敗 - - + + XML files (*.xml) XML ファイル (*.xml) - + Open the report file レポートを開く - + License ライセンス - + Authors 作者 - + Save the report file レポートを保存 - + Text files (*.txt) テキストファイル (*.txt) - + CSV files (*.csv) CSV形式ファイル (*.csv) - Cannot generate a compliance report right now, an analysis must finish successfully. Try to reanalyze the code and ensure there are no critical errors. - コンプライアンスレポートをすぐに生成できません。解析が完了し成功していなければなりません。コードを再解析して、致命的なエラーがないことを確認してください。 + コンプライアンスレポートをすぐに生成できません。解析が完了し成功していなければなりません。コードを再解析して、致命的なエラーがないことを確認してください。 - + Project files (*.cppcheck);;All files(*.*) プロジェクトファイル (*.cppcheck);;すべてのファイル(*.*) - + Select Project File プロジェクトファイルを選択 - + Failed to open file ファイルを開くのに失敗しました - + Unknown project file format プロジェクトファイルの形式が不明です - + Failed to import project file プロジェクトファイルのインポートに失敗しました - + Failed to import '%1': %2 Analysis is stopped. @@ -1285,70 +1426,68 @@ Analysis is stopped. 解析を停止しました。 - + Failed to import '%1' (%2), analysis is stopped '%1' (%2) のインポートに失敗しました。解析は停止 - + Install インストール - + New version available: %1. %2 新しいバージョンが利用可能です。: %1. %2 - - - - + + + + Project: プロジェクト: - + No suitable files found to analyze! チェック対象のファイルがみつかりません! - + C/C++ Source C/C++のソースコード - + Compile database コンパイルデータベース - Visual Studio - Visual Studio + Visual Studio - Borland C++ Builder 6 - Borland C++ Builder 6 + Borland C++ Builder 6 - + Select files to analyze チェック対象のファイルを選択 - + Select directory to analyze チェックするディレクトリを選択してください - + Select the configuration that will be analyzed チェックの設定を選択 - + Found project files from the directory. Do you want to proceed analysis without using any of these project files? @@ -1357,37 +1496,37 @@ Do you want to proceed analysis without using any of these project files? - + Duplicate define 重複した定義 - + File not found: '%1' ファイルがありません: '%1' - + Failed to load/setup addon %1: %2 アドオンの読み込みまたは設定に失敗 %1 - %2 - + Failed to load %1. Your Cppcheck installation is broken. You can use --data-dir=<directory> at the command line to specify where this file is located. Please note that --data-dir is supposed to be used by installation scripts and therefore the GUI does not start when it is used, all that happens is that the setting is configured. Analysis is aborted. %1のロードに失敗しました。あなたの Cppcheck は正しくインストールされていません。あなたは --data-dir=<directory> コマンドラインオプションを使ってこのファイルの場所を指定できます。ただし、この --data-dir はインストールスクリプトによって使用されていなければなりません。またGUI版はこれを使用しません。さらに、全ての設定は調整済みでなければなりません。 - + Failed to load %1 - %2 Analysis is aborted. 読み込みに失敗 %1 - %2 - - + + %1 Analysis is aborted. @@ -1396,7 +1535,7 @@ Analysis is aborted. 解析は中止した。 - + Current results will be cleared. Opening a new XML file will clear current results. @@ -1406,7 +1545,7 @@ Do you want to proceed? 新しくXMLファイルを開くと現在の結果が削除されます。実行しますか? - + Analyzer is running. Do you want to stop the analysis and exit Cppcheck? @@ -1415,77 +1554,77 @@ Do you want to stop the analysis and exit Cppcheck? チェックを中断して、Cppcheckを終了しますか? - + About CppCheckについて - + XML files (*.xml);;Text files (*.txt);;CSV files (*.csv) XML ファイル (*.xml);;テキストファイル (*.txt);;CSVファイル (*.csv) - + Build dir '%1' does not exist, create it? ビルドディレクトリ'%1'がありません。作成しますか? - + To check the project using addons, you need a build directory. アドオンを使用してプロジェクトをチェックするためには、ビルドディレクトリが必要です。 - + Show Mandatory 必須を表示 - + Show Required 要求を表示 - + Show Advisory 推奨を表示 - + Show Document ドキュメントを表示 - + Show L1 L1を表示 - + Show L2 L2を表示 - + Show L3 L3を表示 - + Show style スタイルを表示 - + Show portability 移植可能性を表示 - + Show performance パフォーマンスを表示 - + Show information 情報を表示 @@ -1494,22 +1633,22 @@ Do you want to stop the analysis and exit Cppcheck? '%1'のインポートに失敗しました。(チェック中断) - + Project files (*.cppcheck) プロジェクトファイル (*.cppcheck) - + Select Project Filename プロジェクトファイル名を選択 - + No project file loaded プロジェクトファイルが読み込まれていません - + The project file %1 @@ -1571,26 +1710,31 @@ Options: NewSuppressionDialog + New suppression 新しい指摘の抑制 + Error ID エラーID + File name ファイル名 + Line number 行数 + Symbol name シンボル名 @@ -1608,57 +1752,57 @@ Options: ネイティブ - Unix 32-bit - Unix 32-bit + Unix 32-bit - Unix 64-bit - Unix 64-bit + Unix 64-bit - Windows 32-bit ANSI - Windows 32-bit ANSIエンコード + Windows 32-bit ANSIエンコード - Windows 32-bit Unicode - Windows 32-bit Unicode + Windows 32-bit Unicode - Windows 64-bit - Windows 64-bit + Windows 64-bit ProjectFile + Project File プロジェクトファイル + Paths and Defines パスと定義 + Import Project (Visual studio / compile database/ Borland C++ Builder 6) Import Project (Visual studio / compile database) プロジェクトのインポート(Visual studio / compile database Borland C++ Builder 6)) + Defines must be separated by a semicolon. Example: DEF1;DEF2=5;DEF3=int Defines must be separated by a semicolon ';' 定義(Define)はセミコロン';'で区切る必要があります。 例: DEF1;DEF2=5;DEF3=int - + + Note: Put your own custom .cfg files in the same folder as the project file. You should see them above. カスタマイズした cfgファイルを同じフォルダにプロジェクトファイルとして保存してください。ここに表示できるようになります。 @@ -1667,329 +1811,417 @@ Options: MISRA C 2012 - + + MISRA rule texts MISRA ルールテキスト - + + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> <html><head/><body><p>MISRA C 2012 pdfのAppendix A &quot;Summary of guidelines&quot; からテキストをコピーペーストしてください。</p></body></html> - + + ... ... + <html><head/><body><p>You have a choice:</p><p> * Analyze all Debug and Release configurations</p><p> * Only analyze the first matching Debug configuration</p><p><br/></p></body></html> <html><head/><body><p>選択済み:</p><p> * 全Debug と Release設定をチェックする</p><p> * 最初にマッチした Debug 設定のみチェックする</p><p><br/></p></body></html> - - + + + Browse... 参照... + Analyze all Visual Studio configurations Visual Studioの全ての設定をチェックする + Selected VS Configurations 選択したVS設定 + Paths: パス: + + Add... 追加... - + + + + Edit 編集 - - + + + + + + Remove 取り除く + Undefines: 定義取り消し(Undefines): + Undefines must be separated by a semicolon. Example: UNDEF1;UNDEF2;UNDEF3 定義の取り消しはセミコロンで区切ります。例: UNDEF1;UNDEF2;UNDEF3 + Include Paths: インクルードパス: - + + + Include file + インクルードファイル + + + + + <html><head/><body><p><span style=" font-family:'monospace'; color:#000000; background-color:#ffffff;">Force inclusion of a header file</span></p></body></html> + <html><head/><body><p><span style=" font-family:'monospace'; color:#000000; background-color:#ffffff;">ヘッダファイルを強制的にインクルードします</span></p></body></html> + + + + + Browse.. + ブラウズ.. + + + + Types and Functions 型と関数 - - + + + + Analysis チェック - + + This is a workfolder that Cppcheck will use for various purposes. Cppcheckがさまざまな目的で使用するワークディレクトリ。 - + + Parser パーサー - + + Cppcheck (built in) Cppcheckビルトイン - + + Check level チェックレベル - + + Reduced -- meant for usage where developer wants results directly. Limited and faster analysis with fewer results. #R 限定的 -- 開発者が直接的に結果を得るための解析を意味します。解析は限定的で高速ですがより少ない結果になります。 - + + Normal -- meant for normal analysis in CI. Analysis should finish in reasonable time. #N 通常 -- CIでの通常の解析を意味します。解析が合理的な時間内に完了すべきです。 - + + Exhaustive -- meant for nightly builds etc. Analysis time can be longer (10x slower than compilation is OK). #E 徹底的 -- ナイトリービルド等を意味します。解析時間はより長くなることがあります (コンパイルの10倍以上時間がかかってもよい)。 - + + Check code in headers (should be ON normally. if you want a limited quick analysis then turn this OFF) ヘッダファイルのコードもチェック (通常はONにしてください、制限するときのみOFF) - Premium License - プレミアムライセンス + プレミアムライセンス - + + If tags are added, you will be able to right click on warnings and set one of these tags. You can manually categorize warnings. タグが追加された場合、警告上で右クリックしてそれらのタグの中の一つを設定できます。警告を分類できます。 - + + Exclude source files 除外するソースファイル - + + Exclude folder... フォルダで除外... - + + Exclude file... ファイルで除外... - + + Check that each class has a safe public interface クラスが安全で公開されたインターフェースをもっているか確認 - + + Limit analysis 解析の制限 - + + Check code in unused templates (should be ON normally, however in theory you can safely ignore warnings in unused templates) Check code in unused templates (slower and less accurate analysis) 未使用テンプレートのコードもチェック (解析に時間がかかり、また正確性は低い) - + + Max CTU depth CTUの最大深さ - + + Enable inline suppressions inline抑制を有効にする - + + Misra C MISRA C - + + 2012 2012 - - + + + + 2023 2023 - + + 2025 2025 - + + Misra C++ MISRA C++ - + + 2008 2008 - + + Cert C++ Cert C++ - + + Safety profiles (defined in C++ core guidelines) 安全性プロファイル(C++コアガイドラインで定義) - + + Bug hunting (Premium) バグハンティング(プレミアム) - + + External tools 外部ツール + Up 上 + Down 下 - + + Platform プラットフォーム - + + Clang (experimental) Clang (実験的) - + + If you want to design your classes to be as flexible and robust as possible then the public interface must be very robust. Cppcheck will asumme that arguments can take *any* value. 可能な限りクラスが柔軟であり堅牢であることを望む場合、公開されたインターフェースが非常に堅牢です。Cppcheckは引数があらゆる値をとりうると仮定します。 - + + Max recursion in template instantiation テンプレートインスタンス化の最大再帰回数 - + + Warning options 警告オプション - + + Root path: ルートパス: - + + Filepaths in warnings will be relative to this path 警告中のファイルパスはこのパスからの相対パスになります - + + Warning tags (separated by semicolon) 警告タグ(セミコロン区切り) - + + Cppcheck build dir (whole program analysis, incremental analysis, statistics, etc) Cppcheck ビルドディレクトリ (全プログラムチェック, 差分チェック, 統計等) - + + Libraries ライブラリ - + + Suppressions 指摘の抑制 - + + Add 追加 - - + + + + Addons アドオン - + + Note: Addons require <a href="https://www.python.org/">Python</a> being installed. 注意: アドオンには<a href="https://www.python.org/">Python</a>が必要です。 - + + Y2038 Y2038 - + + Thread safety スレッドセーフ - + + Coding standards コーディング標準 @@ -1998,12 +2230,14 @@ Options: MISRA C 2012 - + + Cert C CERT C - + + CERT-INT35-C: int precision (if size equals precision, you can leave empty) CERT-INT35-C: int型の精度 (もしサイズが精度と一致する場合空のままにしてください) @@ -2012,27 +2246,28 @@ Options: MISRA C++ 2008 - + + Autosar AUTOSAR - + + Bug hunting バグハント - Clang analyzer - Clang Analyzer + Clang Analyzer - Clang-tidy - Clang-tidy + Clang-tidy + Defines: 定義(Defines): @@ -2040,82 +2275,91 @@ Options: ProjectFileDialog - + Project file: %1 プロジェクトファイル:%1 - + Select Cppcheck build dir Cppcheckビルドディレクトリ - + Select include directory includeディレクトリを選択 - + Select a directory to check チェックするディレクトリを選択してください - + Note: Open source Cppcheck does not fully implement Misra C 2012 注意: オープンソースのCppcheckはMisra C 2012を完全にサポートしていません。 - + Clang-tidy (not found) Clang-tidy (みつかりません) - Visual Studio - Visual Studio + Visual Studio - + Compile database コンパイルデータベース - Borland C++ Builder 6 - Borland C++ Builder 6 + Borland C++ Builder 6 - + Import Project プロジェクトのインポート - + + C/C++ header + C/C++のヘッダー + + + + Include file + ファイルのインクルード + + + Select directory to ignore 除外するディレクトリを選択してください - + Source files ソースファイル - + + All files 全ファイル - + Exclude file 除外ファイル - + Select MISRA rule texts file MISRAルールテキストファイルを選択 - + MISRA rule texts file (%1) MISRAルールテキストファイル (%1) @@ -2156,7 +2400,7 @@ Options: 行 %1: 必須の属性 '%2' が '%3'にない - + (Not found) (見つかりません) @@ -2357,9 +2601,8 @@ Options: タグ - CWE - CWE + CWE @@ -2491,10 +2734,8 @@ Options: タグなし - - Cppcheck - Cppcheck + Cppcheck @@ -2581,80 +2822,82 @@ Please check the application path and parameters are correct. ResultsView + Results 結果 + Critical errors 致命的なエラー + Analysis Log チェックログ + Warning Details 警告の詳細 - - + + Failed to save the report. レポートの保存に失敗しました。 - + Print Report レポートの印刷 - + No errors found, nothing to print. 指摘がないため、印刷するものがありません。 - + %p% (%1 of %2 files checked) %p% (%1 / %2 :ファイル数) - - Cppcheck - Cppcheck + Cppcheck - + No errors found. 警告/エラーは見つかりませんでした。 - + Errors were found, but they are configured to be hidden. To toggle what kind of errors are shown, open view menu. 警告/エラーが見つかりましたが、非表示設定になっています。 - - + + Failed to read the report. レポートの読み込みに失敗. - + XML format version 1 is no longer supported. XML フォーマットバージョン 1 はもうサポートされていません。 - + First included by は次のものが最初にインクルードしました - + Id ID @@ -2663,42 +2906,42 @@ To toggle what kind of errors are shown, open view menu. バグハントの解析は不完全です - + Clear Log ログの消去 - + Copy this Log entry このログ項目をコピー - + Copy complete Log ログ全体をコピー - + Analysis was stopped 解析は停止しした - + There was a critical error with id '%1' id '%1'の致命的なエラーがあります - + when checking %1 %1 をチェックするとき - + when checking a file ファイルをチェックするとき - + Analysis was aborted. 解析は中止した。 @@ -2707,26 +2950,31 @@ To toggle what kind of errors are shown, open view menu. ScratchPad + Scratchpad スクラッチパッド + Copy or write some C/C++ code here: ここに C/C++のコードをコピーペーストまたは記入してください: + Optionally enter a filename (mainly for automatic language detection) and click on "Check": オプション: ファイル名を入力(言語は自動判定)して"チェック"をクリックしてください: + filename ファイル名 + Check チェック @@ -2735,21 +2983,25 @@ To toggle what kind of errors are shown, open view menu. Settings + Preferences 設定 + General 全般 + Add... 追加... + Number of threads: 解析用のスレッド数: @@ -2759,57 +3011,68 @@ To toggle what kind of errors are shown, open view menu. + Force checking all #ifdef configurations Check all #ifdef configurations すべての #ifdef をチェックする + Show full path of files ファイルのフルパスを表示 + Show "No errors found" message when no errors found エラーが無いときは"エラーなし"を表示 + Display error Id in column "Id" エラーIDを "Id" に表示する + Enable inline suppressions inline抑制を有効にする + Check for inconclusive errors also 結論のでない指摘もチェックする + Show statistics on check completion チェック完了時に統計情報を表示する + Check for updates 更新の確認 + Show internal warnings in log ログの内部警告も表示する + Addons アドオン + Python binary (leave this empty to use python in the PATH) Pythonインタプリタの場所(空白の場合システムのPATHから検索) @@ -2817,117 +3080,143 @@ To toggle what kind of errors are shown, open view menu. + + + ... ... + MISRA addon MISRAアドオン + MISRA rule texts file MISRA ルールテキストファイル + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> <html><head/><body><p>Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdfのテキストをテキストファイルにコピー</p></body></html> + Clang Clang + Clang path (leave empty to use system PATH) Clangの場所(空白の場合システムのPATHから検索) + Visual Studio headers Visual Studioのヘッダ + <html><head/><body><p>Paths to Visual Studio headers, separated by semicolon ';'.</p><p>You can open a Visual Studio command prompt, write &quot;SET INCLUDE&quot;. Then copy/paste the paths.</p></body></html> <html><head/><body><p>Visual Studioのヘッダーファイル(セミコロン区切り';')。</p><p>Visual Studio コマンドプロンプトを開き、 &quot;SET INCLUDE&quot;. と入力後、そのパスをコピーペーストしてください。</p></body></html> + Code Editor コードエディタ + Code Editor Style コードエディタスタイル + System Style システムのデフォルトのスタイル + Default Light Style ライトスタイルをデフォルトに + Default Dark Style ダークスタイルをデフォルトに + Custom カスタム + Remove 削除 + Max count: #M 最大数: + Applications アプリケーション + + Edit... 編集... + Set as default デフォルトとして設定 + Reports レポート + Save all errors when creating report レポート作成時にすべての警告/エラーを保存 + Save full path to files in reports レポートにファイルのフルパスを保存 + Language 言語 @@ -2984,221 +3273,245 @@ To toggle what kind of errors are shown, open view menu. - - + + + + Statistics 統計情報 - + + Project プロジェクト + Project: プロジェクト: + Paths: パス: + Include paths: インクルードパス: + Defines: 定義(define): + Undefines: 定義取り消し(undef): - + + Previous Scan 前回の解析 + Path Selected: ディレクトリ選択: + Number of Files Scanned: 解析済みファイル数: + Scan Duration: 解析にかかった時間: + Errors: エラー: + Warnings: 警告: + Stylistic warnings: スタイル警告: + Portability warnings: 移植可能性の警告: + Performance issues: パフォーマンス警告: + Information messages: 情報メッセージ: + Active checkers: 有効なチェッカー: + Checkers チェッカー + History ヒストリー + File: ファイル: + Copy to Clipboard クリップボードにコピー + Pdf Export PDF エクスポート - + 1 day 一日 - + %1 days %1日 - + 1 hour 一時間 - + %1 hours %1時間 - + 1 minute 一分 - + %1 minutes %1分 - + 1 second 一秒 - + %1 seconds %1秒 - + 0.%1 seconds 0.%1秒 - + and と - + Export PDF PDF エクスポート - + Project Settings プロジェクトの設定 - + Paths パス - + Include paths インクルードパス - + Defines 定義(define) - + Undefines 定義取り消し(Undef) - + Path selected 選択されたパス - + Number of files scanned スキャンしたファイルの数 - + Scan duration スキャン期間 - - + + Errors エラー @@ -3213,32 +3526,32 @@ To toggle what kind of errors are shown, open view menu. cppcheckビルドディレクトリがありません - - + + Warnings 警告 - - + + Style warnings スタイル警告 - - + + Portability warnings 移植可能性警告 - - + + Performance warnings パフォーマンス警告 - - + + Information messages 情報メッセージ @@ -3247,6 +3560,7 @@ To toggle what kind of errors are shown, open view menu. ThreadDetails + Thread Details スレッド詳細 @@ -3273,9 +3587,8 @@ The user interface language has been reset to English. Open the Preferences-dial そのため言語を 英語にリセットします。設定ダイアログから利用可能な言語を選択してください。 - Cppcheck - Cppcheck + Cppcheck diff --git a/gui/cppcheck_ka.ts b/gui/cppcheck_ka.ts index ecc47d7d090..97fe21d6f96 100644 --- a/gui/cppcheck_ka.ts +++ b/gui/cppcheck_ka.ts @@ -5,27 +5,32 @@ About + About Cppcheck Cppcheck-ის შესახებ + Version %1 ვერსია %1 + Cppcheck - A tool for static C/C++ code analysis. Cppcheck - ხელსაწყო სტატიკური C/C++ კოდის ანალიზისთვის. + Copyright © 2007-%1 Cppcheck team. Copyright © 2007-2021 Cppcheck team. Copyright © 2007-%1 Cppcheck-ის გუნდი. ყველა უფლება დაცულია. + This program is licensed under the terms of the GNU General Public License version 3 ვრცელდება GNU-ის საჯარო ლიცენზიის, @@ -33,11 +38,13 @@ of the GNU General Public License version 3 + Visit Cppcheck homepage at %1 ეწვიეთ Cppcheck-ის ვებგვერდს მისამართზე %1 + <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PCRE</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PicoJSON</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TinyXML2</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Boost</li></ul></body></html> <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">pcre</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">picojson</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">tinyxml2</li></ul></body></html> <html><head/><body><p>დიდი მადლობა ამ ბიბლიოთეკებს, რომლებსაც ვიყენებთ::</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PCRE</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PicoJSON</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TinyXML2</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Boost</li></ul></body></html> @@ -47,11 +54,13 @@ of the GNU General Public License version 3 ApplicationDialog + Add an application აპლიკაციის დამატება + Here you can add an application that can open error files. Specify a name for the application, the application executable and command line parameters for the application. The following texts in parameters are replaced with appropriate values when application is executed: @@ -67,21 +76,25 @@ Parameters: -l(line) (file) + &Name: &სახელი: + &Executable: &გამშვები ფაილი: + &Parameters: &პარამეტრები: + Browse დათვალიერება @@ -96,9 +109,8 @@ Parameters: -l(line) (file) აირჩიეთ აპლიკაცია - Cppcheck - Cppcheck + Cppcheck @@ -109,65 +121,52 @@ Parameters: -l(line) (file) ComplianceReportDialog - Compliance Report - შესაბამისობის ანგარიში + შესაბამისობის ანგარიში - Project name - პროექტის სახელი + პროექტის სახელი - Project version - პროექტის ვერსია + პროექტის ვერსია - Coding Standard - კოდირების სტანდარტი + კოდირების სტანდარტი - Misra C - Misra C + Misra C - Cert C - Cert C + Cert C - Cert C++ - Cert C++ + Cert C++ - List of files with md5 checksums - MD5 საკონტროლო ჯამების მქონე ფაილების სია + MD5 საკონტროლო ჯამების მქონე ფაილების სია - Compliance report - შესაბამისობის ანგარიში + შესაბამისობის ანგარიში - HTML files (*.html) - HTML ფაილები (*.html) + HTML ფაილები (*.html) - - Save compliance report - შესაბამისობის ანგარიშის შენახვა + შესაბამისობის ანგარიშის შენახვა - Failed to import '%1' (%2), can not show files in compliance report - '%1'-ის (%2) შემოტანა ჩავარდა. შესაბამისობის ანგარიშში ფაილებს ვერ ვაჩვენებ + '%1'-ის (%2) შემოტანა ჩავარდა. შესაბამისობის ანგარიშში ფაილებს ვერ ვაჩვენებ @@ -180,10 +179,8 @@ Parameters: -l(line) (file) ვერ ვიპოვე ფაილი: %1 - - Cppcheck - Cppcheck + Cppcheck @@ -195,44 +192,49 @@ Parameters: -l(line) (file) HelpDialog + Cppcheck GUI help Cppcheck-ის გრაფიკული ინტერფეისის დახმარება + Contents შემცველობა + Index ინდექსი - + Helpfile '%1' was not found დახმარების ფაილი '%1' ვერ ვიპოვე - Cppcheck - Cppcheck + Cppcheck LibraryAddFunctionDialog + Add function ფუნქციის დამატება + Function name(s) ფუნქციის სახელ(ებ)-ი + Number of arguments არგუმენტების რაოდენობა @@ -241,86 +243,103 @@ Parameters: -l(line) (file) LibraryDialog + Library Editor ბიბლიოთეკის რედაქტორი + Open გახსნა + Save შენახვა + Save as შენახვა, როგორც + Functions ფუნქციები + Sort დალაგება + Add დამატება + Filter: ფილტრი: + Comments კომენტარები + noreturn noreturn + False მცდარი + True სიმართლე + Unknown უცნობი + return value must be used უნდა გამოიყენოთ დაბრუნებული მნიშვნელობა + ignore function in leaks checking ფუნქციების გამოტოვება გაჟონვები შემოწმებისას + Arguments არგუმენტები + Edit ჩასწორება @@ -336,11 +355,8 @@ Parameters: -l(line) (file) ბიბლიოთეკის ფაილის გახსნა - - - Cppcheck - Cppcheck + Cppcheck @@ -368,11 +384,13 @@ Parameters: -l(line) (file) LibraryEditArgDialog + Edit argument არგუმენტის ჩასწორება + <html><head/><body> <p>Is bool value allowed? For instance result from comparison or from '!' operator.</p> <p>Typically, set this if the argument is a pointer, size, etc.</p> @@ -383,11 +401,13 @@ Parameters: -l(line) (file) + Not bool ლოგიკური მნ. არაა + <html><head/><body> <p>Is a null parameter value allowed?</p> <p>Typically this should be used on any pointer parameter that does not allow null.</p> @@ -398,78 +418,99 @@ Parameters: -l(line) (file) + Not null ნულოვანი არაა + Not uninit Uninit არაა + String სტრიქონი + Format string სტრიქონის ფორმატი + Min size of buffer ბუფერის მინ. ზომა + + Type ტიპი + + None არაფერი + + argvalue argvalue + + mul mul + + strlen strlen + + Arg არგ + + Arg2 Arg2 + and და + Valid values სწორი მნიშვნელობები @@ -477,623 +518,716 @@ Parameters: -l(line) (file) MainWindow - - - - - - - - - - - - - - - Cppcheck - Cppcheck + Cppcheck - + + A&nalyze ა&ნალიზი - + + Standard სტანდარტული + &File &ფაილი - + + &View &ხედი - + + &Toolbars &ხელსაწყოთა ზოლები - + + C++ standard C++-ის სტანდარტი - + + &C standard C standard &C-ის სტანდარტი - + + &Edit &ჩასწორება - + + &License... &ლიცენზია... - + + A&uthors... &ავტორები... - + + &About... &შესახებ... - + + &Files... ფაილ&ები... - - + + + + Analyze files Check files ფაილების ანალიზი - + + Ctrl+F Ctrl+F - + + &Directory... &საქაღალდე.... - - + + + + Analyze directory Check directory საქაღალდის ანალიზი - + + Ctrl+D Ctrl+D - + + Ctrl+R Ctrl+R - + + &Stop &გაჩერება - - + + + + Stop analysis Stop checking ანალიზის გაჩერება - + + Esc Esc - + + &Save results to file... შედეგები&ს შენახვა ფაილში.... - + + Ctrl+S Ctrl+S - + + &Quit გამოსვლა - + + &Clear results შედეგების &გასუფთავება - + + &Preferences &გამართვა - - - + + + + + Show errors შეცდომების ჩვენება - - - + + + + + Show warnings გაფრთხილების ჩვენება - - + + + + Show performance warnings წარმადობის გაფრთხილებების ჩვენება - + + Show &hidden დამალულის &ჩვენება - - + + Information ინფორმაცია - + + Show information messages ინფორმაციის შეტყობინებების ჩვენება - + + Show portability warnings გადატანადობის გაფრთხილებების ჩვენება - + + Show Cppcheck results Cppcheck-ის შედეგები ჩვენება - + + Clang Clang - + + Show Clang results Clang-ის შედეგები ჩვენება - + + &Filter &ფილტრი - + + Filter results შედეგების გაფილტვრა - + + Windows 32-bit ANSI Windows 32-bit ANSI - + + Windows 32-bit Unicode Windows 32-bit Unicode - + + Unix 32-bit Unix 32-bit - + + Unix 64-bit Unix 64-bit - + + Windows 64-bit Windows 64-bit - + + &Print... &ბეჭდვა… - + + Print the Current Report მიმდინარე ანგარიშის დაბეჭდვა - + + Print Pre&view... საბეჭდის &გადახედვა... - + + Open a Print Preview Dialog for the Current Results მიმდინარე შედეგების დაბეჭდვის მინიატურის დიალოგის ჩვენება - + + Open library editor ბიბლიოთეკის რედაქტორის გახსნა - + + &Check all &ყველას ჩართვა + Checking for updates განახლებების შემოწმება + Hide დამალვა - - + + + + Report - + + Filter ფილტრი - + + &Reanalyze modified files &Recheck modified files ყველა შეცვლილი ფაილის თავიდან ანალი&ზი - + + Reanal&yze all files &ყველა ფაილის თავიდან ანალიზი - + + Ctrl+Q Ctrl+Q - + + Style war&nings ს&ტილის გაფრთხილებები - + + E&rrors &შეცდომები - + + &Uncheck all ყველას ჩამოყ&რა - + + Collapse &all ყველას ჩაკეცვ&ა - + + &Expand all &ყველას ამოკეცვა - + + &Standard &სტანდარტული - + + Standard items სტანდარტული ელემენტები - + + Toolbar ხელსაწყოთა ზოლი - + + &Categories &კატეგორიები - + + Error categories შეცდომის კატეგორიები - + + &Open XML... &XML-ის გახსნა... - + + Open P&roject File... პ&პროექტის ფაილის გახსნა... - + + Ctrl+Shift+O Ctrl+Shift+O - + + Sh&ow Scratchpad... ბლ&ოკნოტის ჩვენება... - + + &New Project File... &ახალი პროექტის ფაილი... - + + Ctrl+Shift+N Ctrl+Shift+N - + + &Log View ჟურნა&ლის ხედი - + + Log View ჟურნალის ხედი - + + C&lose Project File პროექტის ფაი&ლის დახურვა - + + &Edit Project File... პროექტის ფაილის ჩასწორ&ება... - + + &Statistics &სტატისტიკა - + + &Warnings &გაფრთხილებები - + + Per&formance warnings წარმატების გა&ფრთხილებები - + + &Information &ინფორმაცია - + + &Portability &გადატანადობა - + + P&latforms პ&ლატფორმები - + + C++&11 C++&11 - + + C&99 C&99 - + + &Posix &Posix - + + C&11 C&11 - + + &C89 &C89 - + + &C++03 &C++03 - + + &Library Editor... ბიბ&ლიოთეკის რედაქტორი... - + + &Auto-detect language ენის &ავტოდადგენა - + + &Enforce C++ ძ&ალით C++ - + + E&nforce C ძა&ლით C - + + C++14 C++14 - + + Reanalyze and check library ბიბლიოთეკის თავიდან ანალიზი და შემოწმება - + + Check configuration (defines, includes) კონფიგურაციის შემოწმება (defines, includes) - + + C++17 C++17 - + + C++20 C++20 - + + Compliance report... შესაბამისობის ანგარიში... - + + Normal ნორმალური - Misra C - Misra C - - - - Misra C++ 2008 - + Misra C - Cert C - Cert C + Cert C - Cert C++ - Cert C++ - - - - Misra C++ 2023 - + Cert C++ - + + Autosar - + + EULA... - + + Thread Details - + + Show thread details - + + &Contents &შემცველობა - + + Categories კატეგორიები - - + + + + Show style warnings სტილის გაფრთხილების ჩვენება - + + Open the help contents დახმარების შემცველობის გახსნა - + + F1 F1 - + + &Help &დახმარება - - + + Quick Filter: სწრაფი ფილტრი: - + + None of the selected files is part of the project, nothing to analyze. + + + + Select configuration აირჩიეთ კონფიგურაცია - + Found project file: %1 Do you want to load this project file instead? @@ -1102,61 +1236,61 @@ Do you want to load this project file instead? გნებავთ, სამაგიეროდ, ეს პროექტის ფაილი ჩატვირთოთ? - + File not found ფაილი ნაპოვნი არაა - + Bad XML არასწორი XML - + Missing attribute აკლია ატრიბუტი - + Bad attribute value არასწორი ატრიბუტის მნიშვნელობა - + Unsupported format მხარდაუჭერელი ფორმატი - + Duplicate define გამეორებული აღწერა - + Failed to load the selected library '%1'. %2 ჩავარდა ჩატვირთვა მონიშნული ბიბლიოთეკისთვის '%1'. %2 - + File not found: '%1' ფაილი ვერ ვიპოვე: '%1' - + Failed to load/setup addon %1: %2 დამატების (%1) ჩატვირთვა/მორგება ჩავარდა: %2 - + Failed to load %1. Your Cppcheck installation is broken. You can use --data-dir=<directory> at the command line to specify where this file is located. Please note that --data-dir is supposed to be used by installation scripts and therefore the GUI does not start when it is used, all that happens is that the setting is configured. Analysis is aborted. - + Failed to load %1 - %2 Analysis is aborted. @@ -1165,8 +1299,8 @@ Analysis is aborted. ანალიზი შეწყდა. - - + + %1 Analysis is aborted. @@ -1175,149 +1309,141 @@ Analysis is aborted. ანალიზი შეწყვეტილია. - + License ლიცენზია - + Authors ავტორები - + Save the report file ანგარიშის ფაილში ჩაწერა - - + + XML files (*.xml) XML ფაილები (*.xml) - + There was a problem with loading the editor application settings. This is probably because the settings were changed between the Cppcheck versions. Please check (and fix) the editor application settings, otherwise the editor program might not start correctly. - + You must close the project file before selecting new files or directories! ახალი ფაილების ან საქაღალდეების არჩევამდე პრორექტის ფაილი უნდა დახუროთ! - + The library '%1' contains unknown elements: %2 ბიბლიოთეკა '%1' უცნობ ელემენტებს შეიცავს: %2 - + Duplicate platform type გამეორებული პლატფორმის ტიპი - + Platform type redefined პლატფორმის ტიპი თავდან აღიწერა - + Unknown element უცნობი ელემენტი - - Unknown element - Unknown issue - უცნობი პრობლემა - - - - - - + + + + Error შეცდომა - + Open the report file ანგარიშის ფაილის გახსნა - + Text files (*.txt) ტექსტური ფაილები (*.txt) - + CSV files (*.csv) CSV ფაილები (*.csv) - + Project files (*.cppcheck);;All files(*.*) პროექტის ფაილები (*.cppcheck);;ყველა ფაილი(*.*) - + Select Project File აირჩიეთ პროექტის ფაილი - - - - + + + + Project: პროექტი: - + No suitable files found to analyze! ანალიზისათვის შესაფერისი ფაილები აღმოჩენილი არაა! - + C/C++ Source C/C++ საწყისი კოდი - + Compile database მონაცემთა ბაზის კომპილაცია - Visual Studio - Visual Studio + Visual Studio - Borland C++ Builder 6 - Borland C++ Builder 6 + Borland C++ Builder 6 - + Select files to analyze აირჩეთ ფაილები ანალიზისთვის - + Select directory to analyze აირჩიეთ საქაღალდე ანალიზისთვის - + Select the configuration that will be analyzed აირჩიეთ კონფიგურაცია ანალიზისთვის - + Found project files from the directory. Do you want to proceed analysis without using any of these project files? @@ -1326,7 +1452,7 @@ Do you want to proceed analysis without using any of these project files? - + Current results will be cleared. Opening a new XML file will clear current results. @@ -1337,7 +1463,7 @@ Do you want to proceed? გნებავთ, გააგრძელოთ? - + Analyzer is running. Do you want to stop the analysis and exit Cppcheck? @@ -1346,47 +1472,46 @@ Do you want to stop the analysis and exit Cppcheck? გნებავთ, გააჩეროთ ანალიზი და გახვიდეთ Cppcheck-დან? - + About შესახებ - + XML files (*.xml);;Text files (*.txt);;CSV files (*.csv) XML ფაილები (*.xml);;ტექსტური ფაილები (*.txt);;CSV ფაილები (*.csv) - Cannot generate a compliance report right now, an analysis must finish successfully. Try to reanalyze the code and ensure there are no critical errors. - შესაბამისობის ანგარიშის გენერაცია ახლა შეუძლებელია, რადგან ჯერ ანალიზი წარმატებით უნდა დასრულდეს. სცადეთ, კოდის ანალიზი თავიდან გაუშვათ და დარწმუნდეთ, რომ კრიტიკული შეცდომები არ არსებობს. + შესაბამისობის ანგარიშის გენერაცია ახლა შეუძლებელია, რადგან ჯერ ანალიზი წარმატებით უნდა დასრულდეს. სცადეთ, კოდის ანალიზი თავიდან გაუშვათ და დარწმუნდეთ, რომ კრიტიკული შეცდომები არ არსებობს. - + Build dir '%1' does not exist, create it? აგების საქაღალდე (%1) არ არსებობს. შევქმნა? - + To check the project using addons, you need a build directory. პროექტის დამატებებით შესამოწმებლად აგების საქაღალდე გჭირდებათ. - + Failed to open file ფაილის გახსნის შეცდომა - + Unknown project file format უცნობი პროექტის ფაილის ფორმატი - + Failed to import project file პროექტის ფაილის შემოტანა ჩავარდა - + Failed to import '%1': %2 Analysis is stopped. @@ -1395,27 +1520,27 @@ Analysis is stopped. ანალიზი შეწყდა. - + Failed to import '%1' (%2), analysis is stopped '%1'-ის (%2) შემოტანა ჩავარდა. ანალიზი შეწყდა - + Project files (*.cppcheck) პროექტის ფაილები (*.cppcheck) - + Select Project Filename აირჩიეთ პროექტის ფაილის სახელი - + No project file loaded პროექტის ფაილი ჩატვირთული არაა - + The project file %1 @@ -1432,67 +1557,67 @@ Do you want to remove the file from the recently used projects -list? გნებავთ წაშალოთ ეს ფაილი ახლახან გამოყენებული პროექტების სიიდან? - + Install დაყენება - + New version available: %1. %2 ხელმისაწვდომია ახალი ვერსია: %1. %2 - + Show Mandatory - + Show Required - + Show Advisory - + Show Document - + Show L1 - + Show L2 - + Show L3 - + Show style - + Show portability - + Show performance - + Show information @@ -1549,26 +1674,31 @@ Options: NewSuppressionDialog + New suppression ახალი ჩახშობა + Error ID შეცდომის ID + File name ფაილის სახელი + Line number ხაზის ნომერი + Symbol name სიმბოლოს სახელი @@ -1586,419 +1716,506 @@ Options: საკუთარი - Unix 32-bit - Unix 32-bit + Unix 32-bit - Unix 64-bit - Unix 64-bit + Unix 64-bit - Windows 32-bit ANSI - Windows 32-bit ANSI + Windows 32-bit ANSI - Windows 32-bit Unicode - Windows 32-bit Unicode + Windows 32-bit Unicode - Windows 64-bit - Windows 64-bit + Windows 64-bit ProjectFile + Project File პროექტის ფაილი + Paths and Defines ბილიკები და აღწერები + Import Project (Visual studio / compile database/ Borland C++ Builder 6) Import Project (Visual studio / compile database) პროექტის შემოტანა (Visual studio / მონაცემთა ბაზი კომპილაცია / Borland C++ Builder 6) + Defines must be separated by a semicolon. Example: DEF1;DEF2=5;DEF3=int Defines must be separated by a semicolon ';' აღწერები წერტილმძიმით უნდა იყოს გამოყოფილი. მაგ: DEF1;DEF2=5;DEF3=int - + + Note: Put your own custom .cfg files in the same folder as the project file. You should see them above. შენიშვნა: ჩადეთ თქვენი .cfg ფაილები იგივე საქაღალდეში, სადაც პროექტის ფაილია. მათ ზემოთ დაინახავთ. - + + Clang (experimental) Clang (ექსპერიმენტული) - + + If you want to design your classes to be as flexible and robust as possible then the public interface must be very robust. Cppcheck will asumme that arguments can take *any* value. - + + Check code in headers (should be ON normally. if you want a limited quick analysis then turn this OFF) თავსართის ფაილებში არსებული კოდის შეწმება (ნაგულისხმევად, ეს ჩართულია. თუ გნებავთ შეზღუდული სწრაფი ანალიზი, მაშინ გამორთეთ ეს) - + + Max recursion in template instantiation - + + Filepaths in warnings will be relative to this path ფაილის ბილიკები ამ ბილიკის ფარდობითი იქნება - + + If tags are added, you will be able to right click on warnings and set one of these tags. You can manually categorize warnings. თუ ჭდეები დამატებულია, შეგიძლიათ, გაფრთხილებებზე მარჯვენა ღილაკით დააწკაპუნოთ და დააყენოთ ერთ-ერთი ჭდე. გაფრთხილებების კატეგორიებად დალაგება ხელით შეგიძლიათ. - + + Exclude source files კოდის ფაილების ამოღება - + + Exclude folder... საქაღალდის ამოღება... - + + Exclude file... ფაილის ამოღება... - + + MISRA rule texts MISRA-ის წესის ტექსტები - + + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> - + + ... ... + <html><head/><body><p>You have a choice:</p><p> * Analyze all Debug and Release configurations</p><p> * Only analyze the first matching Debug configuration</p><p><br/></p></body></html> - - + + + Browse... ნუსხა... + Analyze all Visual Studio configurations ყველა Visual Studio-ის კონფიგურაციის ანალიზი + Selected VS Configurations არჩეული VS-ის კონფიგურაციები + Paths: ბილიკები: + + Add... დამატება... - + + + + Edit ჩასწორება - - + + + + + + Remove წაშლა + Undefines: წაშლილი მაკროაღწერები: + Undefines must be separated by a semicolon. Example: UNDEF1;UNDEF2;UNDEF3 წაშლილი მაკროაღწერები წერტილმძიმით უნდა იყოს გამოყოფილი. მაგ: UNDEF1;UNDEF2;UNDEF3 + Include Paths: ჩასმის ბილიკები: - + + + Include file + + + + + + <html><head/><body><p><span style=" font-family:'monospace'; color:#000000; background-color:#ffffff;">Force inclusion of a header file</span></p></body></html> + + + + + + Browse.. + + + + + Types and Functions ტიპები და ფუნქციები - - + + + + Analysis ანალიზი - + + This is a workfolder that Cppcheck will use for various purposes. ეს სამუშაო საქაღალდეა, რომელსაც Cppcheck სხვადასხვა მიზნებისთვის გამოიყენებს. - + + Parser ანალიზატორი - + + Cppcheck (built in) Cppcheck (ჩაშენებული) - + + Check level შემოწმების დონე - + + Reduced -- meant for usage where developer wants results directly. Limited and faster analysis with fewer results. - + + Normal -- meant for normal analysis in CI. Analysis should finish in reasonable time. ნორმალური -- ნიშნავს ნორმალურ ანალიზს CI-ში. ანალიზი მისაღებ დროში დასრულდება. - + + Exhaustive -- meant for nightly builds etc. Analysis time can be longer (10x slower than compilation is OK). ამომწურავი -- განკუთვნილია ღამის აგებებისთვის და ა.შ. ანალიზის დრო, შეიძლება, უფრო მეტიც იყოს (კომპილაციაზე 10x მეტი დრო ჩვეულებრივი ამბავია). - + + Check that each class has a safe public interface შემოწმება, აქვს თუ არა ყველა კლასს უსაფრთხო საჯარო ინტერფეისი - + + Limit analysis ანალიზის შეზღუდვა - + + Check code in unused templates (should be ON normally, however in theory you can safely ignore warnings in unused templates) Check code in unused templates (slower and less accurate analysis) - + + Max CTU depth CTU-ის მაქსიმალური სიღრმე - - Premium License - - - - + + Enable inline suppressions შეცდომების ხაზშივე ჩახშობის ჩართვა - + + 2025 2025 - + + Misra C++ Misra C++ - + + 2008 2008 - + + Cert C Cert C - + + CERT-INT35-C: int precision (if size equals precision, you can leave empty) CERT-INT35-C: მთელი რიცხვის სიზუსტე (თუ ზომა სიზუსტეს ემთხვევა, შეგიძლიათ, ცარიელი დატოვოთ) - + + Autosar - + + Safety profiles (defined in C++ core guidelines) - + + Bug hunting შეცდომებზე ნადირობა - + + External tools გარე ხელსაწყოები + Up მაღლა + Down ქვემოთ - + + Platform პლატფორმა - + + Warning options გაფრთხილების მორგება - + + Root path: Root ბილიკი: - + + Warning tags (separated by semicolon) გაფრთხილების ჭდეები (წერტილმძიმით გამოყოფილი) - + + Cppcheck build dir (whole program analysis, incremental analysis, statistics, etc) Cppcheck-ის აგების საქაღალდე (მთელი პროგრამის ანალიზი, ინკრემენტული ანალიზი, სტატისტიკა და ა.შ.) - + + Libraries ბიბლიოთეკები - + + Suppressions ჩახშობები - + + Add დამატება - - + + + + Addons დამატებები - + + Note: Addons require <a href="https://www.python.org/">Python</a> being installed. შენიშვნა: დამატებას დაყენებული <a href="https://www.python.org/">Python</a> სჭირდება. - + + Y2038 Y2038 - + + Thread safety ნაკადების უსაფრთხოება - + + Coding standards კოდირების სტანდარტები - + + Misra C Misra C - + + 2012 2012 - - + + + + 2023 2023 - + + Cert C++ Cert C++ - + + Bug hunting (Premium) შეცდომებზე ნადირობა (ფასიანი) - Clang analyzer - Clang-ის ანალიზატორი + Clang-ის ანალიზატორი - Clang-tidy - Clang-tidy + Clang-tidy + Defines: მაკროაღწრები: @@ -2006,82 +2223,91 @@ Options: ProjectFileDialog - + Project file: %1 პროექტის ფაილი: %1 - + Select Cppcheck build dir აირჩიეთ Cppcheck-ის აგების საქაღალდე - + Select include directory აირჩიეთ ჩასასმელი საქაღალდე - + Select a directory to check აირჩიეთ შესამოწმებელი საქაღალდე - + Note: Open source Cppcheck does not fully implement Misra C 2012 - + Clang-tidy (not found) Clang-tidy (ვერ ვიპოვე) - Visual Studio - Visual Studio + Visual Studio - + Compile database მონაცემთა ბაზის კომპილაცია - Borland C++ Builder 6 - Borland C++ Builder 6 + Borland C++ Builder 6 - + Import Project პროექტის შემოტანა - + + C/C++ header + + + + + Include file + + + + Select directory to ignore აირჩიეთ გამოსატოვებელი საქაღალდე - + Source files კოდის ფაილები - + + All files ყველა ფაილი - + Exclude file ფაილის ამოღება - + Select MISRA rule texts file აირჩიეთ MISRA-ის წესების ტექსტის ფაილი - + MISRA rule texts file (%1) MISRA-ის წესის ტექსტების ფაილი (%1) @@ -2116,7 +2342,7 @@ Options: ხაზი %1: აუცილებელი ატრიბუტი '%2' '%3'-ში აღმოჩენილი არაა - + (Not found) (ვერ ვიპოვე) @@ -2316,11 +2542,6 @@ Options: Tags - - - CWE - - QPlatformTheme @@ -2451,10 +2672,8 @@ Options: ჭდის გარეშე - - Cppcheck - Cppcheck + Cppcheck @@ -2540,56 +2759,54 @@ Please check the application path and parameters are correct. ResultsView - + Print Report ანგარიშის დაბეჭდვა - + No errors found, nothing to print. შეცდომები ვერ ვიპოვე. დასაბეჭდი არაფერია. - + %p% (%1 of %2 files checked) %p% (შემოწმებულია %1 ფაილი %2-დან) - - Cppcheck - Cppcheck + Cppcheck - + No errors found. შეცდომების გარეშე. - + Errors were found, but they are configured to be hidden. To toggle what kind of errors are shown, open view menu. აღმოჩენილია შეცდომები, მაგრამ მითითებულია, რომ ისინი დაიმალოს. იმისათვის, რომ გადართოთ, რა სახის შეცდომებია ნაჩვენები, გახსენით მენიუ 'ხედი'. - - + + Failed to read the report. ანგარიშის წაკითხვა ჩავარდა. - + XML format version 1 is no longer supported. XML-ის ფორმატის პირველი ვერსია მხარდაჭერილი აღარაა. - + First included by პირველად ჩასმულია ფაილში - + Id Id @@ -2598,68 +2815,72 @@ To toggle what kind of errors are shown, open view menu. შეცდომებზე ნადირობის ანალიზი დაუსრულებელია - + Clear Log ჟურნალის გასუფთავება - + Copy this Log entry ამ ჟურნალის ჩანაწერის კოპირება - + Copy complete Log სრული ჟურნალის კოპირება - + Analysis was stopped ანალიზი გაუქმებულია - + There was a critical error with id '%1' აღმოჩენილია კრიტიკული შეცდომა id-ით '%1' - + when checking %1 %1-ის შემოწმებისას - + when checking a file ფაილის შემოწმებისას - + Analysis was aborted. ანალიზი შეწყვეტილია. - - + + Failed to save the report. ანგარიშის შენახვა ჩავარდა. + Results შედეგები + Critical errors კრიტიკული შეცდომები + Analysis Log ანალიზის ჟურნალი + Warning Details გაფრთხილების დეტალები @@ -2668,26 +2889,31 @@ To toggle what kind of errors are shown, open view menu. ScratchPad + Scratchpad მონახაზი + Copy or write some C/C++ code here: დააკოპირეთ ან დაწერეთ C/C++ კოდი აქ: + Optionally enter a filename (mainly for automatic language detection) and click on "Check": შეიყვანეთ არასავალდებულო ფაილის სახელი (ძირითადად ავტომატური ენის დადგენისთვის) და დააწკაპუნეთ ღილაკზე "შემოწმება": + filename ფაილის სახელი + Check შემოწმება @@ -2696,21 +2922,25 @@ To toggle what kind of errors are shown, open view menu. Settings + Preferences მორგება + General ზოგადი + Add... დამატება... + Number of threads: ნაკადების რაოდენობა: @@ -2720,57 +2950,68 @@ To toggle what kind of errors are shown, open view menu. + Force checking all #ifdef configurations Check all #ifdef configurations ყველა #ifdef კონფიგურაციის ნაძალადევი შემოწმება + Show full path of files ფაილების სრული ბილიკის ჩვენება + Show "No errors found" message when no errors found "შეცდომები ნაპოვნი არაა" შეტყობინების ჩვენება, როცა შეცდომები აღმოჩენილი არა + Display error Id in column "Id" სვეტში "Id" შეცდომის Id-ის ჩვენება + Enable inline suppressions შეცდომების ხაზშივე ჩახშობის ჩართვა + Check for inconclusive errors also ასევე, ნაჩვენები იქნება საკამათო შეცდომებიც + Show statistics on check completion სტატისტიკის ჩვენება შემოწმების დასრულებისას + Check for updates განახლებების შემოწმება + Show internal warnings in log ჟურნალში შიდა გაფრთხილებების ჩვენება + Addons დამატებები + Python binary (leave this empty to use python in the PATH) ბილიკი python-ის ინტერპრეტატორამდე. (PATH-ში არსებული python-ის გამოსაყენებლად ცარიელი დატოვეთ) @@ -2778,117 +3019,143 @@ To toggle what kind of errors are shown, open view menu. + + + ... ... + MISRA addon MISRA-ის დამატება + MISRA rule texts file MISRA-ის წესის ტექსტების ფაილი + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> + Clang Clang + Clang path (leave empty to use system PATH) ბილიკი Clang-ის ინტერპრეტატორამდე. (PATH-ში არსებულის გამოსაყენებლად ცარიელი დატოვეთ) + Visual Studio headers Visual Studio-ის თავსართები + <html><head/><body><p>Paths to Visual Studio headers, separated by semicolon ';'.</p><p>You can open a Visual Studio command prompt, write &quot;SET INCLUDE&quot;. Then copy/paste the paths.</p></body></html> + Code Editor კოდის რედაქტორი + Code Editor Style კოდის რედაქტორის სტილი + System Style სისტემის მიყოლა + Default Light Style ნაგულისხმევი ღია სტილი + Default Dark Style ნაგულისხმევი მუქი სტილი + Custom მომხმარებლის + Remove წაშლა + Max count: + Applications აპლიკაციები + + Edit... რედაქტირება... + Set as default ნაგულისხმებად დაყენება + Reports ანგარიში + Save all errors when creating report ყველა შეცდომის შენახვა ანგარიშის შექმნისას + Save full path to files in reports ფაილების სრული ბილიკის ჩვენება ანგარიშებში + Language ენა @@ -2945,221 +3212,245 @@ To toggle what kind of errors are shown, open view menu. - - + + + + Statistics სტატისტიკა - + + Project პროექტი + Project: პროექტი: + Paths: ბილიკები: + Include paths: თავსართის ფაილის ბილიკები: + Defines: მაკროაღწრები: + Undefines: წაშლილი მაკროაღწერები: - + + Previous Scan წინა სკანირება + Path Selected: არჩეული ბილიკი: + Number of Files Scanned: დასკანერებულია ფაილების რაოდენობა: + Scan Duration: სკანირების ხანგრძლივობა: + Errors: შეცდომები: + Warnings: გაფრთხილებები: + Stylistic warnings: სტილისტური გაფრთხილებები: + Portability warnings: გადატანადობის გაფრთხილებები: + Performance issues: წარმადობის პრობლემები: + Information messages: ინფორმაციის შეტყობინებები: + Active checkers: აქტიური შემმოწმებლები: + Checkers შაში + History ისტორია + File: ფაილი: + Copy to Clipboard ბუფერში კოპირება + Pdf Export PDF-ად გატანა - + 1 day 1 დღე - + %1 days %1 დღე - + 1 hour %1 საათი - + %1 hours %1 საათი - + 1 minute %1 წუთი - + %1 minutes %1 წთ - + 1 second 1 წამი - + %1 seconds %1 წამი - + 0.%1 seconds 0.%1 წამი - + and და - + Export PDF PDF-ში გატანა - + Project Settings პროექტის პარამეტრები - + Paths ბილიკები - + Include paths ჩასმის ბილიკები - + Defines აღწერები - + Undefines წაშლილი მაკროაღწერები - + Path selected არჩეული ბილიკი - + Number of files scanned დასკანერებულია ფაილების რაოდენობა - + Scan duration სკანირების ხანგრძლივობა - - + + Errors შედომები @@ -3174,32 +3465,32 @@ To toggle what kind of errors are shown, open view menu. Cppcheck-ის აგების საქაღალდის გარეშე - - + + Warnings გაფრთხილებები - - + + Style warnings სტილის გაფრთხილებები - - + + Portability warnings გადატანადობის გაფრთხილებები - - + + Performance warnings წარმადობის გაფრთხილებები - - + + Information messages საინფორმაციო შეტყობინებები @@ -3208,6 +3499,7 @@ To toggle what kind of errors are shown, open view menu. ThreadDetails + Thread Details @@ -3236,9 +3528,8 @@ The user interface language has been reset to English. Open the Preferences-dial მომხმარებლის ინტერფეისი ინგლისურზე გადაირთო. გახსენით მორგების დიალოგი, რომ ხელმისაწვდომი ენებიდან სასურველი აირჩიოთ. - Cppcheck - Cppcheck + Cppcheck diff --git a/gui/cppcheck_ko.ts b/gui/cppcheck_ko.ts index da6bac85bb7..09f564e6380 100644 --- a/gui/cppcheck_ko.ts +++ b/gui/cppcheck_ko.ts @@ -5,21 +5,25 @@ About + About Cppcheck Cppcheck 정보 + Version %1 버전 %1 + Cppcheck - A tool for static C/C++ code analysis. Cppcheck - 정적 C/C++ 코드 분석 도구. + This program is licensed under the terms of the GNU General Public License version 3 이 프로그램은 GNU General Public License version 3을 @@ -27,17 +31,20 @@ of the GNU General Public License version 3 + Visit Cppcheck homepage at %1 Cppcheck 홈페이지(%1)를 방문해보세요 + <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PCRE</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PicoJSON</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TinyXML2</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Boost</li></ul></body></html> <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">pcre</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">picojson</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">tinyxml2</li></ul></body></html> + Copyright © 2007-%1 Cppcheck team. Copyright © 2007-2021 Cppcheck team. @@ -47,11 +54,13 @@ of the GNU General Public License version 3 ApplicationDialog + Add an application 응용 프로그램 추가 + Here you can add an application that can open error files. Specify a name for the application, the application executable and command line parameters for the application. The following texts in parameters are replaced with appropriate values when application is executed: @@ -77,21 +86,25 @@ Kate로 파일을 열고, 해당 행으로 이동하는 예제: + &Name: 이름(&N): + &Executable: 실행 파일(&E): + &Parameters: 명령행 인자(&P): + Browse 찾기 @@ -106,9 +119,8 @@ Kate로 파일을 열고, 해당 행으로 이동하는 예제: 뷰어 프로그램 선택 - Cppcheck - Cppcheck + Cppcheck @@ -116,70 +128,6 @@ Kate로 파일을 열고, 해당 행으로 이동하는 예제: - - ComplianceReportDialog - - - Compliance Report - - - - - Project name - - - - - Project version - - - - - Coding Standard - - - - - Misra C - - - - - Cert C - - - - - Cert C++ - - - - - List of files with md5 checksums - - - - - Compliance report - - - - - HTML files (*.html) - - - - - - Save compliance report - - - - - Failed to import '%1' (%2), can not show files in compliance report - - - FileViewDialog @@ -188,10 +136,8 @@ Kate로 파일을 열고, 해당 행으로 이동하는 예제: 파일 찾기 실패: %1 - - Cppcheck - Cppcheck + Cppcheck @@ -203,44 +149,49 @@ Kate로 파일을 열고, 해당 행으로 이동하는 예제: HelpDialog + Cppcheck GUI help + Contents + Index - + Helpfile '%1' was not found - Cppcheck - Cppcheck + Cppcheck LibraryAddFunctionDialog + Add function + Function name(s) + Number of arguments @@ -249,66 +200,79 @@ Kate로 파일을 열고, 해당 행으로 이동하는 예제: LibraryDialog + Library Editor + Open + Save + Functions + Add + noreturn + False + True + Unknown + return value must be used + ignore function in leaks checking + Arguments + Edit 편집 @@ -325,30 +289,31 @@ Kate로 파일을 열고, 해당 행으로 이동하는 예제: + Sort + Filter: + Comments + Save as - - - Cppcheck - Cppcheck + Cppcheck @@ -375,11 +340,13 @@ Kate로 파일을 열고, 해당 행으로 이동하는 예제: LibraryEditArgDialog + Edit argument + <html><head/><body> <p>Is bool value allowed? For instance result from comparison or from '!' operator.</p> <p>Typically, set this if the argument is a pointer, size, etc.</p> @@ -390,11 +357,13 @@ Kate로 파일을 열고, 해당 행으로 이동하는 예제: + Not bool + <html><head/><body> <p>Is a null parameter value allowed?</p> <p>Typically this should be used on any pointer parameter that does not allow null.</p> @@ -405,78 +374,99 @@ Kate로 파일을 열고, 해당 행으로 이동하는 예제: + Not null + Not uninit + String + Format string + Min size of buffer + + Type + + None + + argvalue + + mul + + strlen + + Arg + + Arg2 + and + Valid values @@ -484,400 +474,431 @@ Kate로 파일을 열고, 해당 행으로 이동하는 예제: MainWindow - - - - - - - - - - - - - - - Cppcheck - Cppcheck + Cppcheck + Checking for updates + Hide 숨기기 + &File 파일(&F) - + + &View 보기(&V) - + + &Toolbars 도구바(&T) - - + + + + Report - + + &Help 도움말(&H) - + + &Edit 편집(&E) - + + Standard 표준 도구 - + + Categories 분류 도구 - + + Filter 필터 도구 - + + &License... 저작권(&L)... - + + A&uthors... 제작자(&u)... - + + &About... 정보(&A)... - + + &Files... 파일(&F)... - + + Ctrl+F Ctrl+F - + + &Directory... 디렉토리(&D)... - + + Ctrl+D Ctrl+D - + + Ctrl+R Ctrl+R - + + &Stop 중지(&S) - + + Esc Esc - + + &Save results to file... 결과를 파일에 저장(&S)... - + + Ctrl+S Ctrl+S - + + &Quit 종료(&Q) - + + &Clear results 결과 지우기(&C) - + + &Preferences 설정(&P) - - + + + + Show style warnings 스타일 경고 표시 - - - + + + + + Show errors 애러 표시 - + + &Check all 전체 선택(&C) - + + &Uncheck all 전체 해제(&U) - + + Collapse &all 전체 접기(&A) - + + &Expand all 전체 펼치기(&E) - + + &Standard 표준 도구(&S) - + + Standard items 표준 아이템 - + + &Contents 내용(&C) - + + Open the help contents 도움말을 엽니다 - + + F1 F1 - + + Toolbar 도구바 - + + &Categories 분류 도구(&C) - + + Error categories 에러 종류 - + + &Open XML... XML 열기(&O)... - + + Open P&roject File... 프로젝트 파일 열기(&R)... - + + &New Project File... 새 프로젝트 파일(&N)... - + + &Log View 로그 보기(&L) - + + Log View 로그 보기 - + + C&lose Project File 프로젝트 파일 닫기(&L) - + + &Edit Project File... 프로젝트 파일 편집(&E)... - + + &Statistics 통계 보기(&S) - - - + + + + + Show warnings 경고 표시 - - + + + + Show performance warnings 성능 경고 표시 - + + Show &hidden 숨기기 보기(&H) - + + Compliance report... - + + Normal - - Misra C - - - - - Misra C++ 2008 - - - - - Cert C - - - - - Cert C++ - - - - - Misra C++ 2023 - - - - + + Autosar - + + EULA... - + + Thread Details - + + Show thread details - - + + Information 정보 - + + Show information messages 정보 표시 - + + Show portability warnings 이식성 경고 표시 - + + &Filter 필터 도구(&F) - + + Filter results 필터링 결과 - + + Windows 32-bit ANSI Windows 32-bit ANSI - + + Windows 32-bit Unicode Windows 32-bit Unicode - + + Unix 32-bit Unix 32-bit - + + Unix 64-bit Unix 64-bit - + + Windows 64-bit Windows 64-bit - - + + Quick Filter: 빠른 필터: - + There was a problem with loading the editor application settings. This is probably because the settings were changed between the Cppcheck versions. Please check (and fix) the editor application settings, otherwise the editor program might not start correctly. @@ -886,12 +907,17 @@ This is probably because the settings were changed between the Cppcheck versions Cppcheck 버전간 설정 방법 차이때문인 것으로 보입니다. 편집기 설정을 검사(및 수정)해주세요, 그렇지 않으면 편집기가 제대로 시작하지 않습니다. - + + None of the selected files is part of the project, nothing to analyze. + + + + You must close the project file before selecting new files or directories! 새로운 파일이나 디렉토리를 선택하기 전에 프로젝트 파일을 닫으세요! - + Found project file: %1 Do you want to load this project file instead? @@ -900,215 +926,210 @@ Do you want to load this project file instead? 이 프로젝트 파일을 불러오겠습니까? - - + + XML files (*.xml) XML 파일 (*.xml) - + Open the report file 보고서 파일 열기 - + License 저작권 - + Authors 제작자 - + Save the report file 보고서 파일 저장 - + Text files (*.txt) 텍스트 파일 (*.txt) - + CSV files (*.csv) CSV 파일 (*.csv) - - Cannot generate a compliance report right now, an analysis must finish successfully. Try to reanalyze the code and ensure there are no critical errors. - - - - + Project files (*.cppcheck);;All files(*.*) 프로젝트 파일 (*.cppcheck);;모든 파일(*.*) - + Select Project File 프로젝트 파일 선택 - + Failed to open file - + Unknown project file format - + Failed to import project file - + Failed to import '%1': %2 Analysis is stopped. - + Failed to import '%1' (%2), analysis is stopped - + Install - + New version available: %1. %2 - + Show Mandatory - + Show Required - + Show Advisory - + Show Document - + Show L1 - + Show L2 - + Show L3 - + Show style - + Show portability - + Show performance - + Show information - - - - + + + + Project: 프로젝트: - + Duplicate define - + File not found: '%1' - + Failed to load/setup addon %1: %2 - + Failed to load %1. Your Cppcheck installation is broken. You can use --data-dir=<directory> at the command line to specify where this file is located. Please note that --data-dir is supposed to be used by installation scripts and therefore the GUI does not start when it is used, all that happens is that the setting is configured. Analysis is aborted. - + Failed to load %1 - %2 Analysis is aborted. - - + + %1 Analysis is aborted. - + About - + To check the project using addons, you need a build directory. - + Select Project Filename 프로젝트 파일이름 선택 - + No project file loaded 프로젝트 파일 불러오기 실패 - + The project file %1 @@ -1130,103 +1151,103 @@ Do you want to remove the file from the recently used projects -list? - + + C++ standard - - - - + + + + Error - + File not found - + Bad XML - + Missing attribute - + Bad attribute value - + Failed to load the selected library '%1'. %2 - + Unsupported format - + The library '%1' contains unknown elements: %2 - + Duplicate platform type - + Platform type redefined - + + &Print... - + + Print the Current Report - + + Print Pre&view... - + + Open a Print Preview Dialog for the Current Results - + + Open library editor - - Unknown element - - - - + Unknown element - Unknown issue - + Select configuration @@ -1249,259 +1270,288 @@ Options: - + Build dir '%1' does not exist, create it? - - + + + + Analyze files - - + + + + Analyze directory - + + &Reanalyze modified files - - + + + + Stop analysis - + XML files (*.xml);;Text files (*.txt);;CSV files (*.csv) - + No suitable files found to analyze! - + Select files to analyze - + Select directory to analyze - + Select the configuration that will be analyzed - + Found project files from the directory. Do you want to proceed analysis without using any of these project files? - + Analyzer is running. Do you want to stop the analysis and exit Cppcheck? - + + A&nalyze - + + &C standard - + + Reanal&yze all files - + + Ctrl+Q - + + Style war&nings - + + E&rrors - + + Ctrl+Shift+O - + + Sh&ow Scratchpad... - + + Ctrl+Shift+N - + + &Warnings - + + Per&formance warnings - + + &Information - + + &Portability - + + Show Cppcheck results - + + Clang - + + Show Clang results - + + P&latforms - + + C++&11 - + + C&99 - + + &Posix - + + C&11 - + + &C89 - + + &C++03 - + + &Library Editor... - + + &Auto-detect language - + + &Enforce C++ - + + E&nforce C - + + C++14 C++14 - + Project files (*.cppcheck) - + + Reanalyze and check library - + + Check configuration (defines, includes) - + + C++17 C++17 - + + C++20 C++20 - + C/C++ Source - + Compile database - - Visual Studio - - - - - Borland C++ Builder 6 - - - - + Current results will be cleared. Opening a new XML file will clear current results. @@ -1513,26 +1563,31 @@ Do you want to proceed? NewSuppressionDialog + New suppression + Error ID + File name + Line number + Symbol name @@ -1545,29 +1600,24 @@ Do you want to proceed? Platforms - Unix 32-bit - Unix 32-bit + Unix 32-bit - Unix 64-bit - Unix 64-bit + Unix 64-bit - Windows 32-bit ANSI - Windows 32-bit ANSI + Windows 32-bit ANSI - Windows 32-bit Unicode - Windows 32-bit Unicode + Windows 32-bit Unicode - Windows 64-bit - Windows 64-bit + Windows 64-bit @@ -1579,388 +1629,472 @@ Do you want to proceed? ProjectFile + Project File 프로젝트 파일 + Defines: Defines: + Paths: 경로: + + Add... 추가... - + + + + Edit 편집 - - + + + + + + Remove 제거 + Up 위로 + Down 아래로 - + + Suppressions - + + Add - + + Note: Put your own custom .cfg files in the same folder as the project file. You should see them above. - + + ... + Include Paths: + Paths and Defines + <html><head/><body><p>You have a choice:</p><p> * Analyze all Debug and Release configurations</p><p> * Only analyze the first matching Debug configuration</p><p><br/></p></body></html> + Analyze all Visual Studio configurations - + + Root path: - + + Warning tags (separated by semicolon) - + + Cppcheck build dir (whole program analysis, incremental analysis, statistics, etc) + Selected VS Configurations - + + + Include file + + + + + + <html><head/><body><p><span style=" font-family:'monospace'; color:#000000; background-color:#ffffff;">Force inclusion of a header file</span></p></body></html> + + + + + + Browse.. + + + + + Types and Functions - + + Libraries - + + Parser - + + Cppcheck (built in) - + + Check that each class has a safe public interface - + + Limit analysis - - + + + + Addons - + + Note: Addons require <a href="https://www.python.org/">Python</a> being installed. - + + Y2038 - + + Thread safety - + + Coding standards - + + Cert C - + + CERT-INT35-C: int precision (if size equals precision, you can leave empty) - + + Autosar - + + Bug hunting - - - Clang analyzer - - - - - Clang-tidy - - - - + + + Browse... + Defines must be separated by a semicolon. Example: DEF1;DEF2=5;DEF3=int - + + Platform - + + This is a workfolder that Cppcheck will use for various purposes. - + + Clang (experimental) - + + Check level - + + Reduced -- meant for usage where developer wants results directly. Limited and faster analysis with fewer results. - + + Normal -- meant for normal analysis in CI. Analysis should finish in reasonable time. - + + Exhaustive -- meant for nightly builds etc. Analysis time can be longer (10x slower than compilation is OK). - + + If you want to design your classes to be as flexible and robust as possible then the public interface must be very robust. Cppcheck will asumme that arguments can take *any* value. - + + Check code in headers (should be ON normally. if you want a limited quick analysis then turn this OFF) - + + Max recursion in template instantiation - - Premium License - - - - + + Warning options - + + Filepaths in warnings will be relative to this path - + + If tags are added, you will be able to right click on warnings and set one of these tags. You can manually categorize warnings. - + + Exclude source files - + + Exclude folder... - + + Exclude file... - + + Enable inline suppressions Inline suppression 사용 - + + Misra C - + + 2012 - - + + + + 2023 - + + 2025 - + + MISRA rule texts - + + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> - + + Misra C++ - + + 2008 - + + Cert C++ - + + Safety profiles (defined in C++ core guidelines) - + + Bug hunting (Premium) - + + External tools + Import Project (Visual studio / compile database/ Borland C++ Builder 6) + Undefines: + Undefines must be separated by a semicolon. Example: UNDEF1;UNDEF2;UNDEF3 - - + + + + Analysis - + + Check code in unused templates (should be ON normally, however in theory you can safely ignore warnings in unused templates) Check code in unused templates (slower and less accurate analysis) - + + Max CTU depth @@ -1968,83 +2102,84 @@ Do you want to proceed? ProjectFileDialog - + Project file: %1 프로젝트 파일: %1 - + Select include directory Include 디렉토리 선택 - + Select a directory to check 검사할 디렉토리 선택 - + Select directory to ignore 무시할 디렉토리 선택 - + Select Cppcheck build dir - + Import Project - + Clang-tidy (not found) - + Note: Open source Cppcheck does not fully implement Misra C 2012 - + Source files - + + All files - - Exclude file + + C/C++ header - - Select MISRA rule texts file + + Include file - - MISRA rule texts file (%1) + + Exclude file - - Visual Studio + + Select MISRA rule texts file - - Compile database + + MISRA rule texts file (%1) - - Borland C++ Builder 6 + + Compile database @@ -2076,7 +2211,7 @@ Do you want to proceed? - + (Not found) @@ -2275,11 +2410,6 @@ Do you want to proceed? Tags - - - CWE - - QPlatformTheme @@ -2371,10 +2501,8 @@ Do you want to proceed? 숨기기 - - Cppcheck - Cppcheck + Cppcheck @@ -2483,121 +2611,123 @@ Please check the application path and parameters are correct. ResultsView + Results 결과 - - + + Failed to save the report. 결과 저장 실패. - + %p% (%1 of %2 files checked) %p% (%2 중 %1 파일 검사됨) - - Cppcheck - Cppcheck + Cppcheck - + No errors found. 에러가 발견되지 않았습니다. - + Errors were found, but they are configured to be hidden. To toggle what kind of errors are shown, open view menu. 에러가 발견되었지만, 감추도록 설정되어 있습니다. 에러 종류를 표시하도록 설정하려면, 보기 메뉴를 선택하세요. - - + + Failed to read the report. 결과 불러오기 실패. - + Analysis was stopped - + There was a critical error with id '%1' - + when checking %1 - + when checking a file - + Analysis was aborted. - + Id - + Print Report - + No errors found, nothing to print. - + First included by - + XML format version 1 is no longer supported. + Critical errors + Analysis Log + Warning Details - + Clear Log - + Copy this Log entry - + Copy complete Log @@ -2606,26 +2736,31 @@ To toggle what kind of errors are shown, open view menu. ScratchPad + Scratchpad + filename + Check 검사 + Copy or write some C/C++ code here: + Optionally enter a filename (mainly for automatic language detection) and click on "Check": @@ -2634,16 +2769,19 @@ To toggle what kind of errors are shown, open view menu. Settings + Preferences 설정 + General 일반 + Number of threads: 쓰레드 수: @@ -2653,107 +2791,129 @@ To toggle what kind of errors are shown, open view menu. + Force checking all #ifdef configurations 모든 #ifdef 설정을 강제로 검사 + Show full path of files 파일의 전체 경로 표시 + Show "No errors found" message when no errors found 에러가 발견되지 않는 경우 "에러가 없습니다." 메시지 표시 + Enable inline suppressions Inline suppression 사용 + Check for updates + Add... 추가... + Remove 제거 + Applications 응용 프로그램 + + Edit... 편집... + Set as default 기본으로 지정 + Reports 보고서 + Save all errors when creating report 보고서 생성 시 모든 에러 저장 + Save full path to files in reports 보고서에 파일의 전체 경로 저장 + Language 언어 + Display error Id in column "Id" + Check for inconclusive errors also + Show internal warnings in log + Show statistics on check completion + Max count: + Addons + Python binary (leave this empty to use python in the PATH) @@ -2761,71 +2921,87 @@ To toggle what kind of errors are shown, open view menu. + + + ... + Clang + Clang path (leave empty to use system PATH) + Visual Studio headers + <html><head/><body><p>Paths to Visual Studio headers, separated by semicolon ';'.</p><p>You can open a Visual Studio command prompt, write &quot;SET INCLUDE&quot;. Then copy/paste the paths.</p></body></html> + MISRA addon + MISRA rule texts file + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> + Code Editor + Code Editor Style + Default Light Style + Default Dark Style + Custom + System Style @@ -2882,241 +3058,264 @@ To toggle what kind of errors are shown, open view menu. - - + + + + Statistics 통계 - + + Project 프로젝트 + Project: 프로젝트: + Paths: 경로: + Include paths: Include 경로: + Defines: Defines: - + + Previous Scan 직전 검사 + Path Selected: 선택된 경로: + Number of Files Scanned: 검사된 파일 수: + Scan Duration: 검사 시간: + Errors: 에러: + Warnings: 경고: + Stylistic warnings: 스타일 경고: + Portability warnings: 이식성 경고: + Performance issues: 성능 경고: + Information messages: 정보 메시지: + Active checkers: + Checkers + Copy to Clipboard 클립보드에 복사 - + 1 day 1일 - + %1 days %1일 - + 1 hour 1시간 - + %1 hours %1시간 - + 1 minute 1분 - + %1 minutes %1분 - + 1 second 1초 - + %1 seconds %1초 - + 0.%1 seconds 0.%1초 - + and 및 - + Project Settings 프로젝트 설정 - + Paths 경로 - + Include paths Include 경로 - + Defines Defines - + Path selected 선택된 경로 - + Number of files scanned 검사된 파일 수 - + Scan duration 검사 시간 - - + + Errors 에러 - - + + Warnings 경고 - - + + Style warnings 스타일 경고 - - + + Portability warnings 이식성 경고 - - + + Performance warnings 성능 경고 - - + + Information messages 정보 메시지 + Pdf Export - + Export PDF + History + File: @@ -3132,11 +3331,12 @@ To toggle what kind of errors are shown, open view menu. + Undefines: - + Undefines @@ -3145,6 +3345,7 @@ To toggle what kind of errors are shown, open view menu. ThreadDetails + Thread Details @@ -3173,9 +3374,8 @@ The user interface language has been reset to English. Open the Preferences-dial 언어가 영어로 초기화 됐습니다. 설정창을 열어서 설정 가능한 언어를 선택하세요. - Cppcheck - Cppcheck + Cppcheck diff --git a/gui/cppcheck_nl.ts b/gui/cppcheck_nl.ts index 2d3149d2f0a..3bab077e6e3 100644 --- a/gui/cppcheck_nl.ts +++ b/gui/cppcheck_nl.ts @@ -5,27 +5,32 @@ About + About Cppcheck Over Cppcheck + Version %1 Versie %1 + Cppcheck - A tool for static C/C++ code analysis. Cppcheck - Een tool voor statische C/C++ code analyse. + Copyright © 2007-%1 Cppcheck team. Copyright © 2007-2021 Cppcheck team. Copyright © 2007-2021 het cppcheck team. + This program is licensed under the terms of the GNU General Public License version 3 Dit programma is beschikbaar onder te termen @@ -33,11 +38,13 @@ van de GNU General Public License versie 3 + Visit Cppcheck homepage at %1 Bezoek de Cppcheck homepage op %1 + <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PCRE</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PicoJSON</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TinyXML2</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Boost</li></ul></body></html> <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">pcre</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">picojson</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">tinyxml2</li></ul></body></html> @@ -47,11 +54,13 @@ van de GNU General Public License versie 3 ApplicationDialog + Add an application Voeg een nieuwe applicatie toe + Here you can add an application that can open error files. Specify a name for the application, the application executable and command line parameters for the application. The following texts in parameters are replaced with appropriate values when application is executed: @@ -77,21 +86,25 @@ Parameters: -l(lijn) (bestand) + &Name: &Naam: + &Executable: &Uitvoerbaar: + &Parameters: + Browse Bladeren @@ -106,9 +119,8 @@ Parameters: -l(lijn) (bestand) Selecteer applicatie - Cppcheck - Cppcheck + Cppcheck @@ -116,70 +128,6 @@ Parameters: -l(lijn) (bestand) Geef een naam op, een pad en eventueel parameters voor de toepassing! - - ComplianceReportDialog - - - Compliance Report - - - - - Project name - - - - - Project version - - - - - Coding Standard - - - - - Misra C - - - - - Cert C - - - - - Cert C++ - - - - - List of files with md5 checksums - - - - - Compliance report - - - - - HTML files (*.html) - - - - - - Save compliance report - - - - - Failed to import '%1' (%2), can not show files in compliance report - - - FileViewDialog @@ -190,10 +138,8 @@ Parameters: -l(lijn) (bestand) Kon het bestand niet vinden: %1 - - Cppcheck - Cppcheck + Cppcheck @@ -205,44 +151,49 @@ Parameters: -l(lijn) (bestand) HelpDialog + Cppcheck GUI help + Contents + Index - + Helpfile '%1' was not found - Cppcheck - Cppcheck + Cppcheck LibraryAddFunctionDialog + Add function + Function name(s) + Number of arguments @@ -251,86 +202,103 @@ Parameters: -l(lijn) (bestand) LibraryDialog + Library Editor + Open + Save Opslaan + Save as + Functions + Sort + Add + Filter: + Comments + noreturn + False + True + Unknown + return value must be used + ignore function in leaks checking + Arguments + Edit Bewerk @@ -346,11 +314,8 @@ Parameters: -l(lijn) (bestand) - - - Cppcheck - Cppcheck + Cppcheck @@ -379,11 +344,13 @@ Parameters: -l(lijn) (bestand) LibraryEditArgDialog + Edit argument + <html><head/><body> <p>Is bool value allowed? For instance result from comparison or from '!' operator.</p> <p>Typically, set this if the argument is a pointer, size, etc.</p> @@ -394,11 +361,13 @@ Parameters: -l(lijn) (bestand) + Not bool + <html><head/><body> <p>Is a null parameter value allowed?</p> <p>Typically this should be used on any pointer parameter that does not allow null.</p> @@ -409,78 +378,99 @@ Parameters: -l(lijn) (bestand) + Not null + Not uninit + String + Format string + Min size of buffer + + Type + + None + + argvalue + + mul + + strlen + + Arg + + Arg2 + and + Valid values @@ -488,623 +478,699 @@ Parameters: -l(lijn) (bestand) MainWindow - - - - - - - - - - - - - - - Cppcheck - Cppcheck + Cppcheck - + + A&nalyze - + + Standard Standaard + &File &Bestand - + + &View &Weergave - + + &Toolbars &Werkbalken - + + C++ standard C++standaard - + + &C standard C standard C standaard - + + &Edit Be&werken - + + &License... &Licentie... - + + A&uthors... A&uteurs... - + + &About... &Over... - + + &Files... &Bestanden... - - + + + + Analyze files Check files Controleer bestanden - + + Ctrl+F Ctrl+F - + + &Directory... &Mappen... - - + + + + Analyze directory Check directory Controleer Map - + + Ctrl+D Ctrl+D - + + Ctrl+R Ctrl+R - + + &Stop &Stop - - + + + + Stop analysis Stop checking Stop controle - + + Esc Esc - + + &Save results to file... &Resultaten opslaan... - + + Ctrl+S Ctrl+S - + + &Quit &Afsluiten - + + &Clear results &Resultaten wissen - + + &Preferences &Voorkeuren - - - + + + + + Show errors Toon fouten - - - + + + + + Show warnings Toon waarschuwingen - - + + + + Show performance warnings Toon presentatie waarschuwingen - + + Show &hidden Toon &verborgen - - + + Information Informatie - + + Show information messages Toon informatie bericht - + + Show portability warnings Toon portabiliteit waarschuwingen - + + Show Cppcheck results - + + Clang - + + Show Clang results - + + &Filter &Filter - + + Filter results Filter resultaten - + + Windows 32-bit ANSI Windows 32-bit ANSI - + + Windows 32-bit Unicode - + + Unix 32-bit - + + Unix 64-bit - + + Windows 64-bit - + + &Print... - + + Print the Current Report - + + Print Pre&view... - + + Open a Print Preview Dialog for the Current Results - + + Open library editor - + + &Check all &Controleer alles + Checking for updates + Hide Verberg - - + + + + Report - + + Filter - + + &Reanalyze modified files &Recheck modified files - + + Reanal&yze all files - + + Ctrl+Q - + + Style war&nings - + + E&rrors - + + &Uncheck all Selecteer &niets - + + Collapse &all Alles Inkl&appen - + + &Expand all Alles &Uitklappen - + + &Standard &Standaard - + + Standard items Standaard items - + + Toolbar Werkbalk - + + &Categories &Categorieën - + + Error categories Foute Categorieën - + + &Open XML... - + + Open P&roject File... Open P&oject bestand... - + + Ctrl+Shift+O - + + Sh&ow Scratchpad... - + + &New Project File... &Nieuw Project Bestand... - + + Ctrl+Shift+N - + + &Log View &Log weergave - + + Log View Log weergave - + + C&lose Project File &Sluit Project Bestand - + + &Edit Project File... &Bewerk Project Bestand... - + + &Statistics &Statistieken - + + &Warnings - + + Per&formance warnings - + + &Information - + + &Portability - + + P&latforms - + + C++&11 - + + C&99 - + + &Posix - + + C&11 - + + &C89 - + + &C++03 - + + &Library Editor... - + + &Auto-detect language - + + &Enforce C++ - + + E&nforce C - + + C++14 - + + Reanalyze and check library - + + Check configuration (defines, includes) - + + C++17 - + + C++20 - + + Compliance report... - + + Normal - - Misra C - - - - - Misra C++ 2008 - - - - - Cert C - - - - - Cert C++ - - - - - Misra C++ 2023 - - - - + + Autosar - + + EULA... - + + Thread Details - + + Show thread details - + + &Contents &Inhoud - + + Categories Categorieën - - + + + + Show style warnings Toon stijl waarschuwingen - + + Open the help contents Open de help inhoud - + + F1 - + + &Help &Help - - + + Quick Filter: Snel Filter: - + Select configuration - + Found project file: %1 Do you want to load this project file instead? @@ -1112,91 +1178,91 @@ Do you want to load this project file instead? Wilt u dit project laden in plaats van? - + File not found - + Bad XML - + Missing attribute - + Bad attribute value - + Duplicate define - + Failed to load the selected library '%1'. %2 - + File not found: '%1' - + Failed to load/setup addon %1: %2 - + Failed to load %1. Your Cppcheck installation is broken. You can use --data-dir=<directory> at the command line to specify where this file is located. Please note that --data-dir is supposed to be used by installation scripts and therefore the GUI does not start when it is used, all that happens is that the setting is configured. Analysis is aborted. - + Failed to load %1 - %2 Analysis is aborted. - - + + %1 Analysis is aborted. - + License Licentie - + Authors Auteurs - + Save the report file Rapport opslaan - - + + XML files (*.xml) XML bestanden (*.xml) - + There was a problem with loading the editor application settings. This is probably because the settings were changed between the Cppcheck versions. Please check (and fix) the editor application settings, otherwise the editor program might not start correctly. @@ -1205,132 +1271,121 @@ This is probably because the settings were changed between the Cppcheck versions Dit is waarschijnlijk omdat de instellingen zijn gewijzigd tussen de versies van cppcheck. Controleer (en maak) de bewerker instellingen, anders zal de bewerker niet correct starten. - + You must close the project file before selecting new files or directories! Je moet project bestanden sluiten voordat je nieuwe bestanden of mappen selekteerd! - + The library '%1' contains unknown elements: %2 - + Unsupported format - + Duplicate platform type - + Platform type redefined - + Unknown element - - Unknown element - Unknown issue - - - - - - - + + + + Error - + Open the report file Open het rapport bestand - + Text files (*.txt) Tekst bestanden (*.txt) - + CSV files (*.csv) CSV bestanden (*.csv) - + Project files (*.cppcheck);;All files(*.*) Project bestanden (*.cppcheck);;Alle bestanden(*.*) - + Select Project File Selecteer project bestand - - - - + + + + Project: Project: - - No suitable files found to analyze! + + None of the selected files is part of the project, nothing to analyze. - - C/C++ Source - - - - - Compile database + + No suitable files found to analyze! - - Visual Studio + + C/C++ Source - - Borland C++ Builder 6 + + Compile database - + Select files to analyze - + Select directory to analyze - + Select the configuration that will be analyzed - + Found project files from the directory. Do you want to proceed analysis without using any of these project files? - + Current results will be cleared. Opening a new XML file will clear current results. @@ -1338,81 +1393,76 @@ Do you want to proceed? - + Analyzer is running. Do you want to stop the analysis and exit Cppcheck? - + About - + XML files (*.xml);;Text files (*.txt);;CSV files (*.csv) - - Cannot generate a compliance report right now, an analysis must finish successfully. Try to reanalyze the code and ensure there are no critical errors. - - - - + Build dir '%1' does not exist, create it? - + To check the project using addons, you need a build directory. - + Failed to open file - + Unknown project file format - + Failed to import project file - + Failed to import '%1': %2 Analysis is stopped. - + Failed to import '%1' (%2), analysis is stopped - + Project files (*.cppcheck) - + Select Project Filename Selecteer project bestandsnaam - + No project file loaded Geen project bestand geladen - + The project file %1 @@ -1428,67 +1478,67 @@ Kan niet worden gevonden! Wilt u het bestand van de onlangs gebruikte project verwijderen -lijst? - + Install - + New version available: %1. %2 - + Show Mandatory - + Show Required - + Show Advisory - + Show Document - + Show L1 - + Show L2 - + Show L3 - + Show style - + Show portability - + Show performance - + Show information @@ -1532,26 +1582,31 @@ Options: NewSuppressionDialog + New suppression + Error ID + File name + Line number + Symbol name @@ -1568,420 +1623,479 @@ Options: Native - - - Unix 32-bit - - - - - Unix 64-bit - - - - - Windows 32-bit ANSI - - - - - Windows 32-bit Unicode - - - - - Windows 64-bit - - ProjectFile + Project File Project Bestand + Paths and Defines + Import Project (Visual studio / compile database/ Borland C++ Builder 6) Import Project (Visual studio / compile database) + Defines must be separated by a semicolon. Example: DEF1;DEF2=5;DEF3=int Defines must be separated by a semicolon ';' - + + Note: Put your own custom .cfg files in the same folder as the project file. You should see them above. - + + If tags are added, you will be able to right click on warnings and set one of these tags. You can manually categorize warnings. - + + Exclude source files - + + Exclude folder... - + + Exclude file... - + + Misra C - + + 2012 - - + + + + 2023 - + + MISRA rule texts - + + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> - + + ... + <html><head/><body><p>You have a choice:</p><p> * Analyze all Debug and Release configurations</p><p> * Only analyze the first matching Debug configuration</p><p><br/></p></body></html> - - + + + Browse... + Analyze all Visual Studio configurations + Selected VS Configurations + Paths: Paden: + + Add... Toevoegen... - + + + + Edit Bewerk - - + + + + + + Remove Verwijder + Undefines: + Undefines must be separated by a semicolon. Example: UNDEF1;UNDEF2;UNDEF3 + Include Paths: - + + + Include file + + + + + + <html><head/><body><p><span style=" font-family:'monospace'; color:#000000; background-color:#ffffff;">Force inclusion of a header file</span></p></body></html> + + + + + + Browse.. + + + + + Types and Functions - - + + + + Analysis - + + This is a workfolder that Cppcheck will use for various purposes. - + + Parser - + + Cppcheck (built in) - + + Check level - + + Reduced -- meant for usage where developer wants results directly. Limited and faster analysis with fewer results. - + + Normal -- meant for normal analysis in CI. Analysis should finish in reasonable time. - + + Exhaustive -- meant for nightly builds etc. Analysis time can be longer (10x slower than compilation is OK). - + + Check that each class has a safe public interface - + + Limit analysis - + + Check code in unused templates (should be ON normally, however in theory you can safely ignore warnings in unused templates) Check code in unused templates (slower and less accurate analysis) - + + Max CTU depth - - Premium License - - - - + + Enable inline suppressions Schakel inline suppressies in - + + 2025 - + + Misra C++ - + + 2008 - + + Cert C - + + CERT-INT35-C: int precision (if size equals precision, you can leave empty) - + + Autosar - + + Safety profiles (defined in C++ core guidelines) - + + Bug hunting - + + External tools + Up Omhoog + Down Omlaag - + + Platform - + + Clang (experimental) - + + If you want to design your classes to be as flexible and robust as possible then the public interface must be very robust. Cppcheck will asumme that arguments can take *any* value. - + + Check code in headers (should be ON normally. if you want a limited quick analysis then turn this OFF) - + + Max recursion in template instantiation - + + Warning options - + + Root path: - + + Filepaths in warnings will be relative to this path - + + Warning tags (separated by semicolon) - + + Cppcheck build dir (whole program analysis, incremental analysis, statistics, etc) - + + Libraries - + + Suppressions - + + Add - - + + + + Addons - + + Note: Addons require <a href="https://www.python.org/">Python</a> being installed. - + + Y2038 - + + Thread safety - + + Coding standards - + + Cert C++ - + + Bug hunting (Premium) - - - Clang analyzer - - - - - Clang-tidy - - + Defines: Omschrijft: @@ -1989,82 +2103,83 @@ Options: ProjectFileDialog - + Project file: %1 Project Bestand %1 - + Select Cppcheck build dir - + Select include directory Selecteer include map - + Select a directory to check Selecteer een map om te controleren - + Note: Open source Cppcheck does not fully implement Misra C 2012 - + Clang-tidy (not found) - - Visual Studio + + Compile database - - Compile database + + Import Project - - Borland C++ Builder 6 + + C/C++ header - - Import Project + + Include file - + Select directory to ignore Selecteer een map om te negeren - + Source files - + + All files - + Exclude file - + Select MISRA rule texts file - + MISRA rule texts file (%1) @@ -2099,7 +2214,7 @@ Options: - + (Not found) @@ -2299,11 +2414,6 @@ Options: Tags - - - CWE - - QPlatformTheme @@ -2430,10 +2540,8 @@ Options: - - Cppcheck - Cppcheck + Cppcheck @@ -2510,122 +2618,124 @@ Gelieve te controleren of de het pad en de parameters correct zijn. ResultsView - + Print Report - + No errors found, nothing to print. - + %p% (%1 of %2 files checked) %p% (%1 van %2 bestanden gecontroleerd) - - Cppcheck - Cppcheck + Cppcheck - + No errors found. Geen fouten gevonden. - + Errors were found, but they are configured to be hidden. To toggle what kind of errors are shown, open view menu. Fouten werden gevonden, maar volgens de configuratie zijn deze verborgen. Gebruik het uitzicht menu om te selecteren welke fouten getoond worden. - - + + Failed to read the report. Kon rapport niet lezen. - + XML format version 1 is no longer supported. - + First included by - + Id Id - + Clear Log - + Copy this Log entry - + Copy complete Log - + Analysis was stopped - + There was a critical error with id '%1' - + when checking %1 - + when checking a file - + Analysis was aborted. - - + + Failed to save the report. Kon het rapport niet opslaan. + Results Resultaten + Critical errors + Analysis Log + Warning Details @@ -2634,26 +2744,31 @@ Gebruik het uitzicht menu om te selecteren welke fouten getoond worden.ScratchPad + Scratchpad Scratchpad + Copy or write some C/C++ code here: + Optionally enter a filename (mainly for automatic language detection) and click on "Check": + filename bestandsnaam + Check Controleer @@ -2662,21 +2777,25 @@ Gebruik het uitzicht menu om te selecteren welke fouten getoond worden.Settings + Preferences Instellingen + General Algemeen + Add... Toevoegen... + Number of threads: Aantal threads: @@ -2686,57 +2805,68 @@ Gebruik het uitzicht menu om te selecteren welke fouten getoond worden. + Force checking all #ifdef configurations Check all #ifdef configurations Controleer alle #ifdef combinaties + Show full path of files Toon het volledige pad van bestanden + Show "No errors found" message when no errors found Toon "Geen fouten gevonden" indien geen fouten gevonden werden + Display error Id in column "Id" Toon fout ld in kolom "Id" + Enable inline suppressions Schakel inline suppressies in + Check for inconclusive errors also + Show statistics on check completion + Check for updates + Show internal warnings in log + Addons + Python binary (leave this empty to use python in the PATH) @@ -2744,117 +2874,143 @@ Gebruik het uitzicht menu om te selecteren welke fouten getoond worden. + + + ... + MISRA addon + MISRA rule texts file + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> + Clang + Clang path (leave empty to use system PATH) + Visual Studio headers + <html><head/><body><p>Paths to Visual Studio headers, separated by semicolon ';'.</p><p>You can open a Visual Studio command prompt, write &quot;SET INCLUDE&quot;. Then copy/paste the paths.</p></body></html> + Code Editor + Code Editor Style + System Style + Default Light Style + Default Dark Style + Custom + Remove Verwijder + Max count: + Applications Applicaties + + Edit... Bewerk... + Set as default Instellen als standaard + Reports Rapporten + Save all errors when creating report Alle fouten opslaan + Save full path to files in reports Volledig pad opslaan + Language Taal @@ -2907,221 +3063,245 @@ Gebruik het uitzicht menu om te selecteren welke fouten getoond worden. - - + + + + Statistics Statistieken - + + Project Project + Project: Project: + Paths: Paden: + Include paths: Bevat paden: + Defines: Omschrijft: + Undefines: - + + Previous Scan Vorige scan + Path Selected: Pad Geselekteerd: + Number of Files Scanned: Aantal bestanden gescanned: + Scan Duration: Scan tijd: + Errors: Fouten: + Warnings: Waarschuwingen: + Stylistic warnings: Stilistisch waarschuwingen: + Portability warnings: Portabiliteit waarschuwingen: + Performance issues: Presentatie problemen: + Information messages: Informatie bericht: + Active checkers: + Checkers + History + File: + Copy to Clipboard Kopieer naar Clipbord + Pdf Export - + 1 day 1 dag - + %1 days %1 dagen - + 1 hour 1 uur - + %1 hours %1 uren - + 1 minute 1 minuut - + %1 minutes %1 minuten - + 1 second 1 seconde - + %1 seconds %1 secondes - + 0.%1 seconds 0.%1 secondes - + and en - + Export PDF - + Project Settings Project instellingen - + Paths Paden - + Include paths Bevat paden - + Defines Omschrijft - + Undefines - + Path selected Pad Geselekteerd - + Number of files scanned Aantal bestanden gescanned - + Scan duration Scan tijd - - + + Errors Fouten @@ -3136,32 +3316,32 @@ Gebruik het uitzicht menu om te selecteren welke fouten getoond worden. - - + + Warnings Waarschuwingen - - + + Style warnings Stijl waarschuwingen - - + + Portability warnings Portabiliteit waarschuwingen - - + + Performance warnings Presentatie waarschuwingen - - + + Information messages Informatie bericht @@ -3170,6 +3350,7 @@ Gebruik het uitzicht menu om te selecteren welke fouten getoond worden.ThreadDetails + Thread Details @@ -3198,9 +3379,8 @@ The user interface language has been reset to English. Open the Preferences-dial De gebruikerstaal is gereset naar Engels. Open het dialoogvenster om een van de beschikbare talen te selecteren. - Cppcheck - Cppcheck + Cppcheck diff --git a/gui/cppcheck_ru.ts b/gui/cppcheck_ru.ts index 124e4d783aa..d9a1a27c0a2 100644 --- a/gui/cppcheck_ru.ts +++ b/gui/cppcheck_ru.ts @@ -5,27 +5,32 @@ About + About Cppcheck О Cppcheck + Version %1 Версия %1 + Cppcheck - A tool for static C/C++ code analysis. Cppcheck - программа для статического анализа кода на языках С/С++. + Copyright © 2007-%1 Cppcheck team. Copyright © 2007-2021 Cppcheck team. Copyright © 2007-2021 Cppcheck team. + This program is licensed under the terms of the GNU General Public License version 3 Эта программа распространяется на @@ -33,11 +38,13 @@ of the GNU General Public License version 3 + Visit Cppcheck homepage at %1 Посетите домашнюю страницу: %1 + <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PCRE</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PicoJSON</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TinyXML2</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Boost</li></ul></body></html> <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">pcre</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">picojson</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">tinyxml2</li></ul></body></html> @@ -47,11 +54,13 @@ of the GNU General Public License version 3 ApplicationDialog + Add an application Добавление приложения + Here you can add an application that can open error files. Specify a name for the application, the application executable and command line parameters for the application. The following texts in parameters are replaced with appropriate values when application is executed: @@ -77,21 +86,25 @@ Parameters: -l(line) (file) + &Name: &Название: + &Executable: &Программа: + &Parameters: &Параметры: + Browse Просмотреть @@ -106,9 +119,8 @@ Parameters: -l(line) (file) Выберите приложение - Cppcheck - Cppcheck + Cppcheck @@ -116,70 +128,6 @@ Parameters: -l(line) (file) Вы должны задать название и путь к приложению! - - ComplianceReportDialog - - - Compliance Report - - - - - Project name - - - - - Project version - - - - - Coding Standard - - - - - Misra C - - - - - Cert C - - - - - Cert C++ - - - - - List of files with md5 checksums - - - - - Compliance report - - - - - HTML files (*.html) - - - - - - Save compliance report - - - - - Failed to import '%1' (%2), can not show files in compliance report - - - FileViewDialog @@ -190,10 +138,8 @@ Parameters: -l(line) (file) Невозможно найти файл: %1 - - Cppcheck - Cppcheck + Cppcheck @@ -205,44 +151,49 @@ Parameters: -l(line) (file) HelpDialog + Cppcheck GUI help + Contents + Index - + Helpfile '%1' was not found - Cppcheck - Cppcheck + Cppcheck LibraryAddFunctionDialog + Add function Добавить функцию + Function name(s) Имя(имена) функции + Number of arguments Количество аргументов @@ -251,86 +202,103 @@ Parameters: -l(line) (file) LibraryDialog + Library Editor Редактор библиотек + Open Открыть + Save Сохранить + Save as Сохранить как + Functions Функции + Sort Сортировать + Add Добавить + Filter: Фильтр: + Comments Комментарии + noreturn + False + True + Unknown + return value must be used должно быть использовано возвращаемое значение + ignore function in leaks checking пропускать функцию при проверке на утечки + Arguments Аргументы + Edit Изменить @@ -346,11 +314,8 @@ Parameters: -l(line) (file) Открыть файл библиотеки - - - Cppcheck - Cppcheck + Cppcheck @@ -379,11 +344,13 @@ Parameters: -l(line) (file) LibraryEditArgDialog + Edit argument Редактировать аргумент + <html><head/><body> <p>Is bool value allowed? For instance result from comparison or from '!' operator.</p> <p>Typically, set this if the argument is a pointer, size, etc.</p> @@ -394,11 +361,13 @@ Parameters: -l(line) (file) + Not bool + <html><head/><body> <p>Is a null parameter value allowed?</p> <p>Typically this should be used on any pointer parameter that does not allow null.</p> @@ -409,78 +378,99 @@ Parameters: -l(line) (file) + Not null + Not uninit + String + Format string + Min size of buffer + + Type + + None + + argvalue + + mul + + strlen + + Arg + + Arg2 + and + Valid values @@ -488,623 +478,704 @@ Parameters: -l(line) (file) MainWindow - - - - - - - - - - - - - - - Cppcheck - Cppcheck + Cppcheck - + + A&nalyze Анализ - + + Standard Стандартные + &File &Файл - + + &View &Вид - + + &Toolbars &Панель инструментов - + + C++ standard Стандарт C++ - + + &C standard C standard &Стандарт C - + + &Edit &Правка - + + &License... &Лицензия... - + + A&uthors... &Авторы... - + + &About... &О программе... - + + &Files... &Файлы... - - + + + + Analyze files Check files Проверить файлы - + + Ctrl+F Ctrl+F - + + &Directory... &Каталог... - - + + + + Analyze directory Check directory Проверка каталога - + + Ctrl+D Ctrl+D - + + Ctrl+R Ctrl+R - + + &Stop Остановить - - + + + + Stop analysis Stop checking Остановить проверку - + + Esc Esc - + + &Save results to file... Сохранить отчёт в файл... - + + Ctrl+S Ctrl+S - + + &Quit Выход - + + &Clear results Очистить отчёт - + + &Preferences Параметры - - - + + + + + Show errors Показать ошибки - - - + + + + + Show warnings Показать предупреждения - - + + + + Show performance warnings Показать предупреждения производительности - + + Show &hidden Показать скрытые - - + + Information Информационные сообщения - + + Show information messages Показать информационные сообщения - + + Show portability warnings Показать предупреждения переносимости - + + Show Cppcheck results Просмотр результатов Cppcheck - + + Clang Clang - + + Show Clang results Просмотр результатов Clang - + + &Filter Фильтр - + + Filter results Результаты фильтрации - + + Windows 32-bit ANSI Windows 32-bit ANSI - + + Windows 32-bit Unicode Windows 32-bit Unicode - + + Unix 32-bit Unix 32-bit - + + Unix 64-bit Unix 64-bit - + + Windows 64-bit Windows 64-bit - + + &Print... Печать... - + + Print the Current Report Напечатать текущий отчет - + + Print Pre&view... Предварительный просмотр... - + + Open a Print Preview Dialog for the Current Results Открыть диалог печати для текущих результатов - + + Open library editor Открыть редактор библиотек - + + &Check all Отметить все + Checking for updates + Hide Скрыть - - + + + + Report - + + Filter Фильтр - + + &Reanalyze modified files &Recheck modified files Заново проверить измененные файлы - + + Reanal&yze all files Заново проверить все файлы - + + Ctrl+Q - + + Style war&nings Стилистические предупреждения - + + E&rrors Ошибки - + + &Uncheck all Сбросить все - + + Collapse &all Свернуть все - + + &Expand all Развернуть все - + + &Standard Стандартные - + + Standard items Стандартные элементы - + + Toolbar Панель инструментов - + + &Categories Категории - + + Error categories Категории ошибок - + + &Open XML... &Открыть XML... - + + Open P&roject File... Открыть файл &проекта... - + + Ctrl+Shift+O - + + Sh&ow Scratchpad... Показать Блокнот - + + &New Project File... &Новый файл проекта... - + + Ctrl+Shift+N - + + &Log View Посмотреть &лог - + + Log View Посмотреть лог - + + C&lose Project File &Закрыть файл проекта - + + &Edit Project File... &Изменить файл проекта... - + + &Statistics &Статистика - + + &Warnings Предупреждения - + + Per&formance warnings Предупреждения производительности - + + &Information Информационные предупреждения - + + &Portability Предупреждения переносимости - + + P&latforms Платформы - + + C++&11 - + + C&99 - + + &Posix - + + C&11 - + + &C89 - + + &C++03 - + + &Library Editor... Редактор библиотеки - + + &Auto-detect language Автоматическое определение языка - + + &Enforce C++ Принудительно C++ - + + E&nforce C Принудительно C - + + C++14 C++14 - + + Reanalyze and check library Повторный анализ библиотеки - + + Check configuration (defines, includes) Проверить конфигурацию (defines, includes) - + + C++17 C++17 - + + C++20 C++20 - + + Compliance report... - + + Normal - - Misra C - - - - - Misra C++ 2008 - - - - - Cert C - - - - - Cert C++ - - - - - Misra C++ 2023 - - - - + + Autosar - + + EULA... - + + Thread Details - + + Show thread details - + + &Contents Помощь - + + Categories Категории - - + + + + Show style warnings Показать стилистические предупреждения - + + Open the help contents Открыть помощь - + + F1 F1 - + + &Help Помощь - - + + Quick Filter: Быстрый фильтр: - + + None of the selected files is part of the project, nothing to analyze. + + + + Select configuration Выбор конфигурации - + Found project file: %1 Do you want to load this project file instead? @@ -1113,97 +1184,97 @@ Do you want to load this project file instead? Вы хотите загрузить этот проект? - + File not found Файл не найден - + Bad XML Некорректный XML - + Missing attribute Пропущен атрибут - + Bad attribute value Некорректное значение атрибута - + Unsupported format Неподдерживаемый формат - + Duplicate define - + Failed to load the selected library '%1'. %2 Не удалось загрузить выбранную библиотеку '%1'. %2 - + File not found: '%1' - + Failed to load/setup addon %1: %2 - + Failed to load %1. Your Cppcheck installation is broken. You can use --data-dir=<directory> at the command line to specify where this file is located. Please note that --data-dir is supposed to be used by installation scripts and therefore the GUI does not start when it is used, all that happens is that the setting is configured. Analysis is aborted. - + Failed to load %1 - %2 Analysis is aborted. - - + + %1 Analysis is aborted. - + License Лицензия - + Authors Авторы - + Save the report file Сохранить файл с отчетом - - + + XML files (*.xml) XML-файлы (*.xml) - + There was a problem with loading the editor application settings. This is probably because the settings were changed between the Cppcheck versions. Please check (and fix) the editor application settings, otherwise the editor program might not start correctly. @@ -1212,43 +1283,37 @@ This is probably because the settings were changed between the Cppcheck versions Возможно, это связано с изменениями в версии программы. Пожалуйста, проверьте (и исправьте) настройки приложения. - + You must close the project file before selecting new files or directories! Вы должны закрыть проект перед выбором новых файлов или каталогов! - + The library '%1' contains unknown elements: %2 Библиотека '%1' содержит неизвестные элементы: %2 - + Duplicate platform type Дубликат типа платформы - + Platform type redefined Переобъявление типа платформы - + Unknown element Неизвестный элемент - - Unknown element - Unknown issue - Неизвестная проблема - - - - - - + + + + Error Ошибка @@ -1257,80 +1322,78 @@ This is probably because the settings were changed between the Cppcheck versions Невозможно загрузить %1. Cppcheck установлен некорректно. Вы можете использовать --data-dir=<directory> в командной строке для указания расположения файлов конфигурации. Обратите внимание, что --data-dir предназначен для использования сценариями установки. При включении данной опции, графический интерфейс пользователя не запускается. - + Open the report file Открыть файл с отчетом - + Text files (*.txt) Текстовые файлы (*.txt) - + CSV files (*.csv) CSV файлы(*.csv) - + Project files (*.cppcheck);;All files(*.*) Файлы проекта (*.cppcheck);;Все файлы(*.*) - + Select Project File Выберите файл проекта - - - - + + + + Project: Проект: - + No suitable files found to analyze! Не найдено подходящих файлов для анализа - + C/C++ Source Исходный код C/C++ - + Compile database - Visual Studio - Visual Studio + Visual Studio - Borland C++ Builder 6 - Borland C++ Builder 6 + Borland C++ Builder 6 - + Select files to analyze Выбор файлов для анализа - + Select directory to analyze Выбор каталога для анализа - + Select the configuration that will be analyzed Выбор используемой конфигурации - + Found project files from the directory. Do you want to proceed analysis without using any of these project files? @@ -1339,7 +1402,7 @@ Do you want to proceed analysis without using any of these project files? - + Current results will be cleared. Opening a new XML file will clear current results. @@ -1350,7 +1413,7 @@ Do you want to proceed? Вы хотите продолжить? - + Analyzer is running. Do you want to stop the analysis and exit Cppcheck? @@ -1359,109 +1422,104 @@ Do you want to stop the analysis and exit Cppcheck? Вы хотите остановить анализ и выйти из Cppcheck? - + About - + XML files (*.xml);;Text files (*.txt);;CSV files (*.csv) XML файлы (*.xml);;Текстовые файлы (*.txt);;CSV файлы (*.csv) - - Cannot generate a compliance report right now, an analysis must finish successfully. Try to reanalyze the code and ensure there are no critical errors. - - - - + Build dir '%1' does not exist, create it? Директория для сборки '%1' не существует, создать? - + To check the project using addons, you need a build directory. - + Failed to open file - + Unknown project file format - + Failed to import project file - + Failed to import '%1': %2 Analysis is stopped. - + Failed to import '%1' (%2), analysis is stopped - + Show Mandatory - + Show Required - + Show Advisory - + Show Document - + Show L1 - + Show L2 - + Show L3 - + Show style - + Show portability - + Show performance - + Show information @@ -1470,22 +1528,22 @@ Analysis is stopped. Невозможно импортировать '%1', анализ остановлен - + Project files (*.cppcheck) Файлы проекта (*.cppcheck) - + Select Project Filename Выберите имя файла для проекта - + No project file loaded Файл с проектом не загружен - + The project file %1 @@ -1501,12 +1559,12 @@ Do you want to remove the file from the recently used projects -list? Хотите удалить его из списка проектов? - + Install - + New version available: %1. %2 @@ -1563,26 +1621,31 @@ Options: NewSuppressionDialog + New suppression Новое подавление + Error ID ID + File name Имя файла + Line number Номер строки + Symbol name Имя символа @@ -1600,102 +1663,111 @@ Options: - Unix 32-bit - Unix 32-bit + Unix 32-bit - Unix 64-bit - Unix 64-bit + Unix 64-bit - Windows 32-bit ANSI - Windows 32-bit ANSI + Windows 32-bit ANSI - Windows 32-bit Unicode - Windows 32-bit Unicode + Windows 32-bit Unicode - Windows 64-bit - Windows 64-bit + Windows 64-bit ProjectFile + Project File Файл проекта + Paths and Defines Каталоги и определения + Import Project (Visual studio / compile database/ Borland C++ Builder 6) Import Project (Visual studio / compile database) Импорт проекта (Visual studio / compile database/ Borland C++ Builder 6) + Defines must be separated by a semicolon. Example: DEF1;DEF2=5;DEF3=int Defines must be separated by a semicolon ';' Defines должны быть разделены точкой с запятой ';' - + + Note: Put your own custom .cfg files in the same folder as the project file. You should see them above. Положите свои .cfg-файлы в один каталог с файлом проекта. Вы увидите их сверху. - + + Clang (experimental) - + + If you want to design your classes to be as flexible and robust as possible then the public interface must be very robust. Cppcheck will asumme that arguments can take *any* value. - + + Check code in headers (should be ON normally. if you want a limited quick analysis then turn this OFF) - + + Max recursion in template instantiation - + + Filepaths in warnings will be relative to this path - + + If tags are added, you will be able to right click on warnings and set one of these tags. You can manually categorize warnings. - + + Exclude source files - + + Exclude folder... - + + Exclude file... @@ -1704,319 +1776,389 @@ Options: MISRA C 2012 - + + MISRA rule texts Файл с текстами правил MISRA - + + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> <html><head/><body><p>Скопируйте текст из Appendix A &quot;Summary of guidelines&quot; из фала правил MISRA C 2012 pdf в текстовый файл.</p></body></html> - + + ... ... + <html><head/><body><p>You have a choice:</p><p> * Analyze all Debug and Release configurations</p><p> * Only analyze the first matching Debug configuration</p><p><br/></p></body></html> <html><head/><body><p>Выберите:</p><p> * Анализ всех конфигураций Debug и Release</p><p> * Анализ только первой подходящей конфигурации Debug</p><p><br/></p></body></html> - - + + + Browse... Обзор... + Analyze all Visual Studio configurations Анализировать все конфигурации Visual Studio + Selected VS Configurations + Paths: Пути: + + Add... Добавить... - + + + + Edit Изменить - - + + + + + + Remove Удалить + Undefines: Удаленные макроопределения: + Undefines must be separated by a semicolon. Example: UNDEF1;UNDEF2;UNDEF3 Удаленные макроопределения должны быть разделены точкой с запятой, например: UNDEF1;UNDEF2;UNDEF3 + Include Paths: Пути заголовочных файлов: - + + + Include file + + + + + + <html><head/><body><p><span style=" font-family:'monospace'; color:#000000; background-color:#ffffff;">Force inclusion of a header file</span></p></body></html> + + + + + + Browse.. + + + + + Types and Functions - - + + + + Analysis Анализ - + + This is a workfolder that Cppcheck will use for various purposes. - + + Parser - + + Cppcheck (built in) - + + Check level - + + Reduced -- meant for usage where developer wants results directly. Limited and faster analysis with fewer results. - + + Normal -- meant for normal analysis in CI. Analysis should finish in reasonable time. - + + Exhaustive -- meant for nightly builds etc. Analysis time can be longer (10x slower than compilation is OK). - + + Check that each class has a safe public interface - + + Limit analysis - + + Check code in unused templates (should be ON normally, however in theory you can safely ignore warnings in unused templates) Check code in unused templates (slower and less accurate analysis) Проверить код в неиспользуемых шаблонах - + + Max CTU depth Максимальная глубина CTU - - Premium License - - - - + + Enable inline suppressions Включить inline-подавление ошибок - + + 2025 - + + Misra C++ - + + 2008 - + + Cert C - + + CERT-INT35-C: int precision (if size equals precision, you can leave empty) - + + Autosar - + + Safety profiles (defined in C++ core guidelines) - + + Bug hunting - + + External tools Внешние инструменты + Up Вверх + Down Вниз - + + Platform Платформа - + + Warning options Опции предупреждений - + + Root path: Корневой каталог: - + + Warning tags (separated by semicolon) Теги предупреждений (через ';') - + + Cppcheck build dir (whole program analysis, incremental analysis, statistics, etc) Каталог сборки Cppcheck - + + Libraries Библиотеки - + + Suppressions Подавления - + + Add Добавить - - + + + + Addons Дополнения - + + Note: Addons require <a href="https://www.python.org/">Python</a> being installed. - + + Y2038 - + + Thread safety - + + Coding standards Стандарты кодирования - + + Misra C - + + 2012 - - + + + + 2023 - + + Cert C++ - + + Bug hunting (Premium) - - - Clang analyzer - - - - - Clang-tidy - - + Defines: Объявленные макроопределения: @@ -2024,82 +2166,91 @@ Options: ProjectFileDialog - + Project file: %1 Файл проекта: %1 - + Select Cppcheck build dir Выбрать директорию сборки Cppcheck - + Select include directory Выберите директорию для поиска заголовочных файлов - + Select a directory to check Выберите директорию для проверки - + Note: Open source Cppcheck does not fully implement Misra C 2012 - + Clang-tidy (not found) Clang-tidy (не найден) - Visual Studio - Visual Studio + Visual Studio - + Compile database - Borland C++ Builder 6 - Borland C++ Builder 6 + Borland C++ Builder 6 - + Import Project Импорт проекта - + + C/C++ header + + + + + Include file + + + + Select directory to ignore Выберите директорию, которую надо проигнорировать - + Source files - + + All files - + Exclude file - + Select MISRA rule texts file Выбрать файл текстов правил MISRA - + MISRA rule texts file (%1) Файл текстов правил MISRA (%1) @@ -2134,7 +2285,7 @@ Options: - + (Not found) (Недоступно) @@ -2334,11 +2485,6 @@ Options: Tags - - - CWE - - QPlatformTheme @@ -2469,10 +2615,8 @@ Options: Тег отсутствует - - Cppcheck - Cppcheck + Cppcheck @@ -2555,122 +2699,124 @@ Please check the application path and parameters are correct. ResultsView - + Print Report Распечатать отчет - + No errors found, nothing to print. Ошибок не найдено, нечего распечатывать. - + %p% (%1 of %2 files checked) %p% (%1 из %2 файлов проверено) - - Cppcheck - Cppcheck + Cppcheck - + No errors found. Ошибок не найдено. - + Errors were found, but they are configured to be hidden. To toggle what kind of errors are shown, open view menu. Были обнаружены ошибки, но они настроены быть скрыты. Для переключения какие ошибки отображаются, откройте меню представления. - - + + Failed to read the report. Не удалось прочитать отчет. - + XML format version 1 is no longer supported. XML формат версии 1 больше не поддерживается. - + First included by Только первый включенный - + Id Id - + Clear Log Очистить лог - + Copy this Log entry Скопировать данную запись - + Copy complete Log Скопировать полный лог - + Analysis was stopped - + There was a critical error with id '%1' - + when checking %1 - + when checking a file - + Analysis was aborted. - - + + Failed to save the report. Не удалось сохранить отчет. + Results Результаты + Critical errors + Analysis Log Лог анализа + Warning Details Детали предупреждения @@ -2679,26 +2825,31 @@ To toggle what kind of errors are shown, open view menu. ScratchPad + Scratchpad Блокнот + Copy or write some C/C++ code here: Исходный код C/C++: + Optionally enter a filename (mainly for automatic language detection) and click on "Check": При необходимости введите имя файла и нажмите "Проверить": + filename имя файла + Check Проверить @@ -2707,21 +2858,25 @@ To toggle what kind of errors are shown, open view menu. Settings + Preferences Параметры + General Общие + Add... Добавить... + Number of threads: Количество потоков исполнения: @@ -2731,57 +2886,68 @@ To toggle what kind of errors are shown, open view menu. + Force checking all #ifdef configurations Check all #ifdef configurations Проверять все варианты #ifdef конфигураций + Show full path of files Показывать полные пути к файлам + Show "No errors found" message when no errors found Показывать сообщение, если ошибок не найдено + Display error Id in column "Id" Отображать номер ошибки в колонке "id" + Enable inline suppressions Включить inline-подавление ошибок + Check for inconclusive errors also Показывать также спорные ошибки + Show statistics on check completion Показывать статистику после завершения проверки + Check for updates + Show internal warnings in log Показывать внутренние предупреждения в логе + Addons Дополнения + Python binary (leave this empty to use python in the PATH) Python (оставьте пустым для использования python из PATH) @@ -2789,117 +2955,143 @@ To toggle what kind of errors are shown, open view menu. + + + ... ... + MISRA addon Дополнение MISRA + MISRA rule texts file Файл с текстами правил MISRA: + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> <html><head/><body><p>Скопируйте текст из Appendix A &quot;Summary of guidelines&quot; из фала правил MISRA C 2012 pdf в текстовый файл.</p></body></html> + Clang Clang + Clang path (leave empty to use system PATH) Clang (оставьте пустым для использования clang из PATH) + Visual Studio headers Заголовочные файлы Visual Studio + <html><head/><body><p>Paths to Visual Studio headers, separated by semicolon ';'.</p><p>You can open a Visual Studio command prompt, write &quot;SET INCLUDE&quot;. Then copy/paste the paths.</p></body></html> <html><head/><body><p>Путь до заголовочных файлов Visual Studio headers, разделенных символом ';'.</p><p>Вы можете открыть командную строку Visual Studio, ввести &quot;SET INCLUDE&quot; и скопировать пути.</p></body></html> + Code Editor Редактор + Code Editor Style Оформление + System Style + Default Light Style + Default Dark Style + Custom + Remove Удалить + Max count: + Applications Приложения + + Edit... Изменить... + Set as default Установить по умолчанию + Reports Отчёты + Save all errors when creating report Сохранять все ошибки при создании отчёта + Save full path to files in reports Сохранять полные пути к файлам в отчётах + Language Язык @@ -2956,221 +3148,245 @@ To toggle what kind of errors are shown, open view menu. - - + + + + Statistics Статистика - + + Project Проект + Project: Проект: + Paths: Пути: + Include paths: Включенные пути: + Defines: Объявленные макроопределения: + Undefines: Удаленные макроопределения: - + + Previous Scan Последнее сканирование + Path Selected: Выбранный путь: + Number of Files Scanned: Количество просканированных файлов: + Scan Duration: Продолжительность сканирования: + Errors: Ошибки: + Warnings: Предупреждения: + Stylistic warnings: Стилистические предупреждения: + Portability warnings: Предупреждения переносимости: + Performance issues: Проблемы с производительностью: + Information messages: Информационные сообщения: + Active checkers: + Checkers + History История + File: Файл: + Copy to Clipboard Скопировать в буфер обмена + Pdf Export Экспорт PDF - + 1 day 1 день - + %1 days %1 дней - + 1 hour 1 час - + %1 hours %1 часов - + 1 minute 1 минута - + %1 minutes %1 минут - + 1 second 1 секунда - + %1 seconds %1 секунд - + 0.%1 seconds 0.1%1 секунд - + and и - + Export PDF Экспорт PDF - + Project Settings Настройки проекта - + Paths Пути - + Include paths Включенные пути - + Defines Объявленные макроопределения: - + Undefines Удаленные макроопределения: - + Path selected Выбранные пути - + Number of files scanned Количество просканированных файлов - + Scan duration Продолжительность сканирования - - + + Errors Ошибки @@ -3185,32 +3401,32 @@ To toggle what kind of errors are shown, open view menu. Не задана директория сборки - - + + Warnings Предупреждения - - + + Style warnings Стилистические предупреждения - - + + Portability warnings Предупреждения переносимости - - + + Performance warnings Предупреждения производительности - - + + Information messages Информационные сообщения @@ -3219,6 +3435,7 @@ To toggle what kind of errors are shown, open view menu. ThreadDetails + Thread Details @@ -3247,9 +3464,8 @@ The user interface language has been reset to English. Open the Preferences-dial Язык пользовательского интерфейса был сброшен на английский. Откройте Настройки-диалог для выбора любого из доступных языков. - Cppcheck - Cppcheck + Cppcheck diff --git a/gui/cppcheck_sr.ts b/gui/cppcheck_sr.ts index 70b04dd196f..81da0d77edf 100644 --- a/gui/cppcheck_sr.ts +++ b/gui/cppcheck_sr.ts @@ -5,27 +5,32 @@ About + About Cppcheck About Cppcheck + Version %1 Version %1 + Cppcheck - A tool for static C/C++ code analysis. Cppcheck - A tool for static C/C++ code analysis. + Copyright © 2007-%1 Cppcheck team. Copyright © 2007-2021 Cppcheck team. + This program is licensed under the terms of the GNU General Public License version 3 This program is licensed under the terms @@ -33,11 +38,13 @@ of the GNU General Public License version 3 + Visit Cppcheck homepage at %1 Visit Cppcheck homepage at %1 + <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PCRE</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PicoJSON</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TinyXML2</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Boost</li></ul></body></html> <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">pcre</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">picojson</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">tinyxml2</li></ul></body></html> @@ -47,11 +54,13 @@ of the GNU General Public License version 3 ApplicationDialog + Add an application Add a new application + Here you can add an application that can open error files. Specify a name for the application, the application executable and command line parameters for the application. The following texts in parameters are replaced with appropriate values when application is executed: @@ -67,21 +76,25 @@ Parameters: -l(line) (file) + &Name: + &Executable: + &Parameters: + Browse Browse @@ -96,9 +109,8 @@ Parameters: -l(line) (file) Select viewer application - Cppcheck - Cppcheck + Cppcheck @@ -106,70 +118,6 @@ Parameters: -l(line) (file) - - ComplianceReportDialog - - - Compliance Report - - - - - Project name - - - - - Project version - - - - - Coding Standard - - - - - Misra C - - - - - Cert C - - - - - Cert C++ - - - - - List of files with md5 checksums - - - - - Compliance report - - - - - HTML files (*.html) - - - - - - Save compliance report - - - - - Failed to import '%1' (%2), can not show files in compliance report - - - FileViewDialog @@ -178,10 +126,8 @@ Parameters: -l(line) (file) Could not find the file: %1 - - Cppcheck - Cppcheck + Cppcheck @@ -193,44 +139,49 @@ Parameters: -l(line) (file) HelpDialog + Cppcheck GUI help + Contents + Index - + Helpfile '%1' was not found - Cppcheck - Cppcheck + Cppcheck LibraryAddFunctionDialog + Add function + Function name(s) + Number of arguments @@ -239,86 +190,103 @@ Parameters: -l(line) (file) LibraryDialog + Library Editor + Open + Save + Save as + Functions + Sort + Add + Filter: + Comments + noreturn + False + True + Unknown + return value must be used + ignore function in leaks checking + Arguments + Edit @@ -334,11 +302,8 @@ Parameters: -l(line) (file) - - - Cppcheck - Cppcheck + Cppcheck @@ -367,11 +332,13 @@ Parameters: -l(line) (file) LibraryEditArgDialog + Edit argument + <html><head/><body> <p>Is bool value allowed? For instance result from comparison or from '!' operator.</p> <p>Typically, set this if the argument is a pointer, size, etc.</p> @@ -382,11 +349,13 @@ Parameters: -l(line) (file) + Not bool + <html><head/><body> <p>Is a null parameter value allowed?</p> <p>Typically this should be used on any pointer parameter that does not allow null.</p> @@ -397,78 +366,99 @@ Parameters: -l(line) (file) + Not null + Not uninit + String + Format string + Min size of buffer + + Type + + None + + argvalue + + mul + + strlen + + Arg + + Arg2 + and + Valid values @@ -476,846 +466,911 @@ Parameters: -l(line) (file) MainWindow - - - - - - - - - - - - - - - Cppcheck - Cppcheck + Cppcheck - + + A&nalyze - + + Standard Standard + &File &File - + + &View &View - + + &Toolbars - + + C++ standard - + + &C standard C standard - + + &Edit &Edit - + + &License... &License... - + + A&uthors... A&uthors... - + + &About... &About... - + + &Files... &Files... - - + + + + Analyze files Check files - + + Ctrl+F Ctrl+F - + + &Directory... &Directory... - - + + + + Analyze directory Check directory - + + Ctrl+D Ctrl+D - + + Ctrl+R Ctrl+R - + + &Stop &Stop - - + + + + Stop analysis Stop checking - + + Esc Esc - + + &Save results to file... &Save results to file... - + + Ctrl+S Ctrl+S - + + &Quit &Quit - + + &Clear results &Clear results - + + &Preferences &Preferences - - - + + + + + Show errors - - - + + + + + Show warnings - - + + + + Show performance warnings - + + Show &hidden - - + + Information - + + Show information messages - + + Show portability warnings - + + Show Cppcheck results - + + Clang - + + Show Clang results - + + &Filter - + + Filter results - + + Windows 32-bit ANSI Windows 32-bit ANSI - + + Windows 32-bit Unicode Windows 32-bit Unicode - + + Unix 32-bit Unix 32-bit - + + Unix 64-bit Unix 64-bit - + + Windows 64-bit Windows 64-bit - + + &Print... - + + Print the Current Report - + + Print Pre&view... - + + Open a Print Preview Dialog for the Current Results - + + Open library editor - + + &Check all &Check all + Checking for updates + Hide - - + + + + Report - + + Filter - + + &Reanalyze modified files &Recheck modified files - + + Reanal&yze all files - + + Ctrl+Q - + + Style war&nings - + + E&rrors - + + &Uncheck all &Uncheck all - + + Collapse &all Collapse &all - + + &Expand all &Expand all - + + &Standard - + + Standard items - + + Toolbar - + + &Categories - + + Error categories - + + &Open XML... - + + Open P&roject File... - + + Ctrl+Shift+O - + + Sh&ow Scratchpad... - + + &New Project File... - + + Ctrl+Shift+N - + + &Log View - + + Log View - + + C&lose Project File - + + &Edit Project File... - + + &Statistics - + + &Warnings - + + Per&formance warnings - + + &Information - + + &Portability - + + P&latforms - + + C++&11 - + + C&99 - + + &Posix - + + C&11 - + + &C89 - + + &C++03 - + + &Library Editor... - + + &Auto-detect language - + + &Enforce C++ - + + E&nforce C - + + C++14 C++14 - + + Reanalyze and check library - + + Check configuration (defines, includes) - + + C++17 C++17 - + + C++20 C++20 - + + Compliance report... - + + Normal - - Misra C - - - - - Misra C++ 2008 - - - - - Cert C - - - - - Cert C++ - - - - - Misra C++ 2023 - - - - + + Autosar - + + EULA... - + + Thread Details - + + Show thread details - + + &Contents - + + Categories - - + + + + Show style warnings - + + Open the help contents - + + F1 F1 - + + &Help &Help - - + + Quick Filter: - + Select configuration - + Found project file: %1 Do you want to load this project file instead? - + File not found - + Bad XML - + Missing attribute - + Bad attribute value - + Duplicate define - + Failed to load the selected library '%1'. %2 - + File not found: '%1' - + Failed to load/setup addon %1: %2 - + Failed to load %1. Your Cppcheck installation is broken. You can use --data-dir=<directory> at the command line to specify where this file is located. Please note that --data-dir is supposed to be used by installation scripts and therefore the GUI does not start when it is used, all that happens is that the setting is configured. Analysis is aborted. - + Failed to load %1 - %2 Analysis is aborted. - - + + %1 Analysis is aborted. - + License License - + Authors Authors - + Save the report file Save the report file - - + + XML files (*.xml) XML files (*.xml) - + There was a problem with loading the editor application settings. This is probably because the settings were changed between the Cppcheck versions. Please check (and fix) the editor application settings, otherwise the editor program might not start correctly. - + You must close the project file before selecting new files or directories! - + The library '%1' contains unknown elements: %2 - + Unsupported format - + Duplicate platform type - + Platform type redefined - + Unknown element - - Unknown element - Unknown issue - - - - - - - + + + + Error - + Open the report file - + Text files (*.txt) Text files (*.txt) - + CSV files (*.csv) - + Project files (*.cppcheck);;All files(*.*) - + Select Project File - - - - + + + + Project: - - No suitable files found to analyze! + + None of the selected files is part of the project, nothing to analyze. - - C/C++ Source - - - - - Compile database + + No suitable files found to analyze! - - Visual Studio + + C/C++ Source - - Borland C++ Builder 6 + + Compile database - + Select files to analyze - + Select directory to analyze - + Select the configuration that will be analyzed - + Found project files from the directory. Do you want to proceed analysis without using any of these project files? - + Current results will be cleared. Opening a new XML file will clear current results. @@ -1323,81 +1378,76 @@ Do you want to proceed? - + Analyzer is running. Do you want to stop the analysis and exit Cppcheck? - + About - + XML files (*.xml);;Text files (*.txt);;CSV files (*.csv) - - Cannot generate a compliance report right now, an analysis must finish successfully. Try to reanalyze the code and ensure there are no critical errors. - - - - + Build dir '%1' does not exist, create it? - + To check the project using addons, you need a build directory. - + Failed to open file - + Unknown project file format - + Failed to import project file - + Failed to import '%1': %2 Analysis is stopped. - + Failed to import '%1' (%2), analysis is stopped - + Project files (*.cppcheck) - + Select Project Filename - + No project file loaded - + The project file %1 @@ -1408,67 +1458,67 @@ Do you want to remove the file from the recently used projects -list? - + Install - + New version available: %1. %2 - + Show Mandatory - + Show Required - + Show Advisory - + Show Document - + Show L1 - + Show L2 - + Show L3 - + Show style - + Show portability - + Show performance - + Show information @@ -1512,26 +1562,31 @@ Options: NewSuppressionDialog + New suppression + Error ID + File name + Line number + Symbol name @@ -1549,419 +1604,498 @@ Options: - Unix 32-bit - Unix 32-bit + Unix 32-bit - Unix 64-bit - Unix 64-bit + Unix 64-bit - Windows 32-bit ANSI - Windows 32-bit ANSI + Windows 32-bit ANSI - Windows 32-bit Unicode - Windows 32-bit Unicode + Windows 32-bit Unicode - Windows 64-bit - Windows 64-bit + Windows 64-bit ProjectFile + Project File + Paths and Defines + Import Project (Visual studio / compile database/ Borland C++ Builder 6) Import Project (Visual studio / compile database) + Defines must be separated by a semicolon. Example: DEF1;DEF2=5;DEF3=int Defines must be separated by a semicolon ';' - + + Note: Put your own custom .cfg files in the same folder as the project file. You should see them above. - + + MISRA rule texts - + + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> - + + ... + <html><head/><body><p>You have a choice:</p><p> * Analyze all Debug and Release configurations</p><p> * Only analyze the first matching Debug configuration</p><p><br/></p></body></html> - - + + + Browse... + Analyze all Visual Studio configurations + Selected VS Configurations + Paths: + + Add... - + + + + Edit - - + + + + + + Remove + Undefines: + Undefines must be separated by a semicolon. Example: UNDEF1;UNDEF2;UNDEF3 + Include Paths: + Up + Down - + + + Include file + + + + + + <html><head/><body><p><span style=" font-family:'monospace'; color:#000000; background-color:#ffffff;">Force inclusion of a header file</span></p></body></html> + + + + + + Browse.. + + + + + Platform - - + + + + Analysis - + + This is a workfolder that Cppcheck will use for various purposes. - + + Clang (experimental) - + + Check level - + + Reduced -- meant for usage where developer wants results directly. Limited and faster analysis with fewer results. - + + Normal -- meant for normal analysis in CI. Analysis should finish in reasonable time. - + + Exhaustive -- meant for nightly builds etc. Analysis time can be longer (10x slower than compilation is OK). - + + If you want to design your classes to be as flexible and robust as possible then the public interface must be very robust. Cppcheck will asumme that arguments can take *any* value. - + + Check code in unused templates (should be ON normally, however in theory you can safely ignore warnings in unused templates) Check code in unused templates (slower and less accurate analysis) - + + Max CTU depth - + + Max recursion in template instantiation - - Premium License - - - - + + Warning options - + + Root path: - + + Filepaths in warnings will be relative to this path - + + Warning tags (separated by semicolon) - + + Enable inline suppressions - + + 2025 - + + Misra C++ - + + 2008 - + + Cert C - + + CERT-INT35-C: int precision (if size equals precision, you can leave empty) - + + Autosar - + + Safety profiles (defined in C++ core guidelines) - + + Bug hunting - + + External tools - + + Cppcheck build dir (whole program analysis, incremental analysis, statistics, etc) - + + Types and Functions - + + Libraries - + + Parser - + + Cppcheck (built in) - + + Check that each class has a safe public interface - + + Limit analysis - + + Check code in headers (should be ON normally. if you want a limited quick analysis then turn this OFF) - + + If tags are added, you will be able to right click on warnings and set one of these tags. You can manually categorize warnings. - + + Exclude source files - + + Exclude folder... - + + Exclude file... - + + Suppressions - + + Add - - + + + + Addons - + + Note: Addons require <a href="https://www.python.org/">Python</a> being installed. - + + Y2038 - + + Thread safety - + + Coding standards - + + Misra C - + + 2012 - - + + + + 2023 - + + Cert C++ - + + Bug hunting (Premium) - - - Clang analyzer - - - - - Clang-tidy - - + Defines: @@ -1969,82 +2103,83 @@ Options: ProjectFileDialog - + Project file: %1 - + Select Cppcheck build dir - + Select include directory - + Select a directory to check - + Note: Open source Cppcheck does not fully implement Misra C 2012 - + Clang-tidy (not found) - - Visual Studio + + Compile database - - Compile database + + Import Project - - Borland C++ Builder 6 + + C/C++ header - - Import Project + + Include file - + Select directory to ignore - + Source files - + + All files - + Exclude file - + Select MISRA rule texts file - + MISRA rule texts file (%1) @@ -2077,7 +2212,7 @@ Options: - + (Not found) @@ -2277,11 +2412,6 @@ Options: Tags - - - CWE - - QPlatformTheme @@ -2400,10 +2530,8 @@ Options: - - Cppcheck - Cppcheck + Cppcheck @@ -2472,122 +2600,124 @@ Please check the application path and parameters are correct. ResultsView - + Print Report - + No errors found, nothing to print. - + %p% (%1 of %2 files checked) - - Cppcheck - Cppcheck + Cppcheck - + No errors found. No errors found. - + Errors were found, but they are configured to be hidden. To toggle what kind of errors are shown, open view menu. Errors were found, but they are configured to be hidden. To toggle what kind of errors are shown, open view menu. - - + + Failed to read the report. - + XML format version 1 is no longer supported. - + First included by - + Id - + Clear Log - + Copy this Log entry - + Copy complete Log - + Analysis was stopped - + There was a critical error with id '%1' - + when checking %1 - + when checking a file - + Analysis was aborted. - - + + Failed to save the report. Failed to save the report. + Results Results + Critical errors + Analysis Log + Warning Details @@ -2596,26 +2726,31 @@ To toggle what kind of errors are shown, open view menu. ScratchPad + Scratchpad + Copy or write some C/C++ code here: + Optionally enter a filename (mainly for automatic language detection) and click on "Check": + filename + Check @@ -2624,76 +2759,91 @@ To toggle what kind of errors are shown, open view menu. Settings + Preferences Preferences + General General + Add... + Number of threads: Number of threads: + Force checking all #ifdef configurations Check all #ifdef configurations + Show full path of files Show full path of files + Show "No errors found" message when no errors found Show "No errors found" message when no errors found + Display error Id in column "Id" + Enable inline suppressions + Check for inconclusive errors also + Show statistics on check completion + Check for updates + Show internal warnings in log + Addons + Python binary (leave this empty to use python in the PATH) @@ -2701,117 +2851,143 @@ To toggle what kind of errors are shown, open view menu. + + + ... + MISRA addon + MISRA rule texts file + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> + Clang + Clang path (leave empty to use system PATH) + Visual Studio headers + <html><head/><body><p>Paths to Visual Studio headers, separated by semicolon ';'.</p><p>You can open a Visual Studio command prompt, write &quot;SET INCLUDE&quot;. Then copy/paste the paths.</p></body></html> + Code Editor + Code Editor Style + System Style + Default Light Style + Default Dark Style + Custom + Remove + Max count: + Applications Applications + + Edit... + Set as default + Reports Reports + Save all errors when creating report Save all errors when creating report + Save full path to files in reports Save full path to files in reports + Language @@ -2864,221 +3040,245 @@ To toggle what kind of errors are shown, open view menu. - - + + + + Statistics - + + Project + Project: + Paths: + Include paths: + Defines: + Undefines: - + + Previous Scan + Path Selected: + Number of Files Scanned: + Scan Duration: + Errors: + Warnings: + Stylistic warnings: + Portability warnings: + Performance issues: + Information messages: + Active checkers: + Checkers + History + File: + Copy to Clipboard + Pdf Export - + 1 day - + %1 days - + 1 hour - + %1 hours - + 1 minute - + %1 minutes - + 1 second - + %1 seconds - + 0.%1 seconds - + and - + Export PDF - + Project Settings - + Paths - + Include paths - + Defines - + Undefines - + Path selected - + Number of files scanned - + Scan duration - - + + Errors @@ -3093,32 +3293,32 @@ To toggle what kind of errors are shown, open view menu. - - + + Warnings - - + + Style warnings - - + + Portability warnings - - + + Performance warnings - - + + Information messages @@ -3127,6 +3327,7 @@ To toggle what kind of errors are shown, open view menu. ThreadDetails + Thread Details @@ -3151,9 +3352,8 @@ The user interface language has been reset to English. Open the Preferences-dial - Cppcheck - Cppcheck + Cppcheck diff --git a/gui/cppcheck_sv.ts b/gui/cppcheck_sv.ts index f37121c2f2f..80726023e09 100644 --- a/gui/cppcheck_sv.ts +++ b/gui/cppcheck_sv.ts @@ -5,27 +5,32 @@ About + About Cppcheck Om Cppcheck + Version %1 Version %1 + Cppcheck - A tool for static C/C++ code analysis. Cppcheck - Ett verktyg för statisk analys av C/C++ kod. + Copyright © 2007-%1 Cppcheck team. Copyright © 2007-2021 Cppcheck team. Copyright © 2007-2021 Cppcheck team. + This program is licensed under the terms of the GNU General Public License version 3 This program is licensed under the terms @@ -33,11 +38,13 @@ of the GNU General Public License version 3 + Visit Cppcheck homepage at %1 Hemsida: %1 + <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PCRE</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PicoJSON</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TinyXML2</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Boost</li></ul></body></html> <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">pcre</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">picojson</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">tinyxml2</li></ul></body></html> @@ -47,11 +54,13 @@ of the GNU General Public License version 3 ApplicationDialog + Add an application Lägg till program + Here you can add an application that can open error files. Specify a name for the application, the application executable and command line parameters for the application. The following texts in parameters are replaced with appropriate values when application is executed: @@ -77,21 +86,25 @@ Parametrar: -l(line) (file) + &Name: Namn: + &Executable: Körbar fil: + &Parameters: Parametrar: + Browse Bläddra @@ -106,9 +119,8 @@ Parametrar: -l(line) (file) Välj program - Cppcheck - Cppcheck + Cppcheck @@ -116,70 +128,6 @@ Parametrar: -l(line) (file) Du måste ange namn, sökväg samt eventuellt parametrar för applikationen! - - ComplianceReportDialog - - - Compliance Report - - - - - Project name - - - - - Project version - - - - - Coding Standard - - - - - Misra C - - - - - Cert C - - - - - Cert C++ - - - - - List of files with md5 checksums - - - - - Compliance report - - - - - HTML files (*.html) - - - - - - Save compliance report - - - - - Failed to import '%1' (%2), can not show files in compliance report - - - FileViewDialog @@ -190,10 +138,8 @@ Parametrar: -l(line) (file) Kunde inte hitta filen: %1 - - Cppcheck - Cppcheck + Cppcheck @@ -205,44 +151,49 @@ Parametrar: -l(line) (file) HelpDialog + Cppcheck GUI help + Contents + Index - + Helpfile '%1' was not found - Cppcheck - Cppcheck + Cppcheck LibraryAddFunctionDialog + Add function Lägg till funktion + Function name(s) Funktion namn + Number of arguments Antal argument @@ -251,86 +202,103 @@ Parametrar: -l(line) (file) LibraryDialog + Library Editor Library Editor + Open Öppna + Save Spara + Save as Spara som + Functions Funktioner + Sort Sortera + Add Lägg till + Filter: Filter: + Comments Kommentar + noreturn noreturn + False False + True True + Unknown Vet ej + return value must be used retur värde måste användas + ignore function in leaks checking Ignorera funktionen när cppcheck letar efter läckor + Arguments Argument + Edit Redigera @@ -346,11 +314,8 @@ Parametrar: -l(line) (file) Öppna Library fil - - - Cppcheck - Cppcheck + Cppcheck @@ -379,11 +344,13 @@ Parametrar: -l(line) (file) LibraryEditArgDialog + Edit argument Konfigurera argument + <html><head/><body> <p>Is bool value allowed? For instance result from comparison or from '!' operator.</p> <p>Typically, set this if the argument is a pointer, size, etc.</p> @@ -397,11 +364,13 @@ Exempel: + Not bool Ej bool + <html><head/><body> <p>Is a null parameter value allowed?</p> <p>Typically this should be used on any pointer parameter that does not allow null.</p> @@ -415,78 +384,99 @@ Exempel: + Not null Ej null + Not uninit Ej uninit + String Sträng + Format string Format sträng + Min size of buffer Minsta storlek för buffer + + Type Typ + + None Ingen + + argvalue argvalue + + mul mul + + strlen strlen + + Arg Arg + + Arg2 Arg2 + and och + Valid values Tillåtna värden @@ -494,624 +484,705 @@ Exempel: MainWindow - - - - - - - - - - - - - - - Cppcheck - Cppcheck + Cppcheck - + + A&nalyze Analysera - + + Standard Standard + &File &Arkiv - + + &View &Visa - + + &Toolbars Verktygsfält - + + C++ standard C++ standard - + + &C standard C standard C standard - + + &Edit &Redigera - + + &License... &Licens... - + + A&uthors... &Utvecklat av... - + + &About... &Om... - + + &Files... &Filer... - - + + + + Analyze files Check files Analysera filer - + + Ctrl+F Ctrl+F - + + &Directory... &Katalog... - - + + + + Analyze directory Check directory Analysera mapp - + + Ctrl+D Ctrl+D - + + Ctrl+R Ctrl+R - + + &Stop &Stoppa - - + + + + Stop analysis Stop checking Stoppa analys - + + Esc Esc - + + &Save results to file... &Spara resultat till fil... - + + Ctrl+S Ctrl+S - + + &Quit &Avsluta - + + &Clear results &Töm resultat - + + &Preferences &Inställningar - - - + + + + + Show errors Visa fel - - - + + + + + Show warnings Visa varningar - - + + + + Show performance warnings Visa prestanda varningar - + + Show &hidden Visa dolda - - + + Information Information - + + Show information messages Visa informations meddelanden - + + Show portability warnings Visa portabilitets varningar - + + Show Cppcheck results Visa Cppcheck resultat - + + Clang Clang - + + Show Clang results Visa Clang resultat - + + &Filter &Filter - + + Filter results Filtrera resultat - + + Windows 32-bit ANSI Windows 32-bit ANSI - + + Windows 32-bit Unicode Windows 32-bit Unicode - + + Unix 32-bit Unix 32-bit - + + Unix 64-bit Unix 64-bit - + + Windows 64-bit Windows 64-bit - + + &Print... Skriv ut... - + + Print the Current Report Skriv ut aktuell rapport - + + Print Pre&view... Förhandsgranska utskrift... - + + Open a Print Preview Dialog for the Current Results Öppnar förhandsgranskning för nuvarande resultat - + + Open library editor Öppna library editor - + + &Check all &Kryssa alla + Checking for updates + Hide Dölj - - + + + + Report - + + Filter Filter - + + &Reanalyze modified files &Recheck modified files Analysera om ändrade filer - + + Reanal&yze all files Analysera om alla filer - + + Ctrl+Q - + + Style war&nings Style varningar - + + E&rrors Fel - + + &Uncheck all Kryssa &ur alla - + + Collapse &all Ingen bra översättning! &Fäll ihop alla - + + &Expand all &Expandera alla - + + &Standard &Standard - + + Standard items Standard poster - + + Toolbar Verktygsfält - + + &Categories &Kategorier - + + Error categories Fel kategorier - + + &Open XML... &Öppna XML... - + + Open P&roject File... Öppna Projektfil... - + + Ctrl+Shift+O - + + Sh&ow Scratchpad... Visa Scratchpad... - + + &New Project File... Ny projektfil... - + + Ctrl+Shift+N - + + &Log View - + + Log View Logg vy - + + C&lose Project File Stäng projektfil - + + &Edit Project File... Redigera projektfil... - + + &Statistics Statistik - + + &Warnings Varningar - + + Per&formance warnings Optimerings varningar - + + &Information Information - + + &Portability Portabilitet - + + P&latforms Plattformar - + + C++&11 C++11 - + + C&99 C99 - + + &Posix Posix - + + C&11 C11 - + + &C89 C89 - + + &C++03 C++03 - + + &Library Editor... Library Editor... - + + &Auto-detect language Detektera språk automatiskt - + + &Enforce C++ Tvinga C++ - + + E&nforce C Tvinga C - + + C++14 C++14 - + + Reanalyze and check library - + + Check configuration (defines, includes) - + + C++17 C++17 - + + C++20 C++20 - + + Compliance report... - + + Normal - - Misra C - - - - - Misra C++ 2008 - - - - - Cert C - - - - - Cert C++ - - - - - Misra C++ 2023 - - - - + + Autosar - + + EULA... - + + Thread Details - + + Show thread details - + + &Contents &Innehåll - + + Categories Kategorier - - + + + + Show style warnings Visa stil varningar - + + Open the help contents Öppna hjälp - + + F1 F1 - + + &Help &Hjälp - - + + Quick Filter: Snabbfilter: - + + None of the selected files is part of the project, nothing to analyze. + + + + Select configuration Välj konfiguration - + Found project file: %1 Do you want to load this project file instead? @@ -1120,97 +1191,97 @@ Do you want to load this project file instead? Vill du ladda denna projektfil istället? - + File not found Filen hittades ej - + Bad XML Ogiltig XML - + Missing attribute Attribut finns ej - + Bad attribute value Ogiltigt attribut värde - + Unsupported format Format stöds ej - + Duplicate define - + Failed to load the selected library '%1'. %2 Misslyckades att ladda valda library '%1'. %2 - + File not found: '%1' - + Failed to load/setup addon %1: %2 - + Failed to load %1. Your Cppcheck installation is broken. You can use --data-dir=<directory> at the command line to specify where this file is located. Please note that --data-dir is supposed to be used by installation scripts and therefore the GUI does not start when it is used, all that happens is that the setting is configured. Analysis is aborted. - + Failed to load %1 - %2 Analysis is aborted. - - + + %1 Analysis is aborted. - + License Licens - + Authors Utvecklare - + Save the report file Spara rapport - - + + XML files (*.xml) XML filer (*.xml) - + There was a problem with loading the editor application settings. This is probably because the settings were changed between the Cppcheck versions. Please check (and fix) the editor application settings, otherwise the editor program might not start correctly. @@ -1219,43 +1290,37 @@ This is probably because the settings were changed between the Cppcheck versions En trolig orsak är att inställningarna ändrats för olika Cppcheck versioner. Kontrollera programinställningarna. - + You must close the project file before selecting new files or directories! Du måste stänga projektfilen innan nya filer eller sökvägar kan väljas! - + The library '%1' contains unknown elements: %2 Library filen '%1' har element som ej hanteras: %2 - + Duplicate platform type Dubbel plattformstyp - + Platform type redefined Plattformstyp definieras igen - + Unknown element Element hanteras ej - - Unknown element - Unknown issue - Något problem - - - - - - + + + + Error Fel @@ -1264,80 +1329,74 @@ En trolig orsak är att inställningarna ändrats för olika Cppcheck versioner. Misslyckades att ladda %1. Din Cppcheck installation är ej komplett. Du kan använda --data-dir<directory> på kommandoraden för att specificera var denna fil finns. Det är meningen att --data-dir kommandot skall köras under installationen,så GUIt kommer ej visas när --data-dir används allt som händer är att en inställning görs. - + Open the report file Öppna rapportfilen - + Text files (*.txt) Text filer (*.txt) - + CSV files (*.csv) CSV filer (*.csv) - + Project files (*.cppcheck);;All files(*.*) Projektfiler (*.cppcheck);;Alla filer(*.*) - + Select Project File Välj projektfil - - - - + + + + Project: Projekt: - + No suitable files found to analyze! Inga filer hittades att analysera! - + C/C++ Source - + Compile database - Visual Studio - Visual Studio - - - - Borland C++ Builder 6 - + Visual Studio - + Select files to analyze Välj filer att analysera - + Select directory to analyze Välj mapp att analysera - + Select the configuration that will be analyzed Välj konfiguration som kommer analyseras - + Found project files from the directory. Do you want to proceed analysis without using any of these project files? @@ -1346,7 +1405,7 @@ Do you want to proceed analysis without using any of these project files? - + Current results will be cleared. Opening a new XML file will clear current results. @@ -1354,7 +1413,7 @@ Do you want to proceed? - + Analyzer is running. Do you want to stop the analysis and exit Cppcheck? @@ -1363,109 +1422,104 @@ Do you want to stop the analysis and exit Cppcheck? Vill du stoppa analysen och avsluta Cppcheck? - + About - + XML files (*.xml);;Text files (*.txt);;CSV files (*.csv) XML filer (*.xml);;Text filer (*.txt);;CSV filer (*.csv) - - Cannot generate a compliance report right now, an analysis must finish successfully. Try to reanalyze the code and ensure there are no critical errors. - - - - + Build dir '%1' does not exist, create it? Build dir '%1' existerar ej, skapa den? - + To check the project using addons, you need a build directory. - + Failed to open file - + Unknown project file format - + Failed to import project file - + Failed to import '%1': %2 Analysis is stopped. - + Failed to import '%1' (%2), analysis is stopped - + Show Mandatory - + Show Required - + Show Advisory - + Show Document - + Show L1 - + Show L2 - + Show L3 - + Show style - + Show portability - + Show performance - + Show information @@ -1474,22 +1528,22 @@ Analysis is stopped. Misslyckades att importera '%1', analysen stoppas - + Project files (*.cppcheck) Projekt filer (*.cppcheck) - + Select Project Filename Välj Projektfil - + No project file loaded Inget projekt laddat - + The project file %1 @@ -1506,12 +1560,12 @@ Do you want to remove the file from the recently used projects -list? Vill du ta bort filen från 'senast använda projekt'-listan? - + Install - + New version available: %1. %2 @@ -1568,26 +1622,31 @@ Options: NewSuppressionDialog + New suppression + Error ID + File name + Line number + Symbol name @@ -1605,419 +1664,506 @@ Options: Native - Unix 32-bit - Unix 32-bit + Unix 32-bit - Unix 64-bit - Unix 64-bit + Unix 64-bit - Windows 32-bit ANSI - Windows 32-bit ANSI + Windows 32-bit ANSI - Windows 32-bit Unicode - Windows 32-bit Unicode + Windows 32-bit Unicode - Windows 64-bit - Windows 64-bit + Windows 64-bit ProjectFile + Project File Projektfil + Paths and Defines Sökvägar och defines + Import Project (Visual studio / compile database/ Borland C++ Builder 6) Import Project (Visual studio / compile database) Importera Projekt (Visual Studio / compile database) + Defines must be separated by a semicolon. Example: DEF1;DEF2=5;DEF3=int Defines must be separated by a semicolon ';' Defines separeras med semicolon ';' - + + Note: Put your own custom .cfg files in the same folder as the project file. You should see them above. Obs: Lägg dina egna .cfg filer i samma folder som projekt filen. De skall isåfall visas ovan. - + + ... ... + <html><head/><body><p>You have a choice:</p><p> * Analyze all Debug and Release configurations</p><p> * Only analyze the first matching Debug configuration</p><p><br/></p></body></html> <html><head/><body><p>Du har ett val:</p><p> * Analysera alla Debug och Release konfigurationer</p><p> * Analysera bara den första matchande Debug konfigurationen</p><p><br/></p></body></html> - - + + + Browse... + Analyze all Visual Studio configurations Analysera alla Visual Studio konfigurationer + Selected VS Configurations + Paths: Sökvägar: + + Add... Lägg till... - + + + + Edit Redigera - - + + + + + + Remove Ta bort + Undefines: + Undefines must be separated by a semicolon. Example: UNDEF1;UNDEF2;UNDEF3 + Include Paths: Include sökvägar: - + + + Include file + + + + + + <html><head/><body><p><span style=" font-family:'monospace'; color:#000000; background-color:#ffffff;">Force inclusion of a header file</span></p></body></html> + + + + + + Browse.. + + + + + This is a workfolder that Cppcheck will use for various purposes. - + + Clang (experimental) - + + Check level - + + Reduced -- meant for usage where developer wants results directly. Limited and faster analysis with fewer results. - + + Normal -- meant for normal analysis in CI. Analysis should finish in reasonable time. - + + Exhaustive -- meant for nightly builds etc. Analysis time can be longer (10x slower than compilation is OK). - + + If you want to design your classes to be as flexible and robust as possible then the public interface must be very robust. Cppcheck will asumme that arguments can take *any* value. - + + Check code in headers (should be ON normally. if you want a limited quick analysis then turn this OFF) - + + Max recursion in template instantiation - - Premium License - - - - + + Filepaths in warnings will be relative to this path - + + If tags are added, you will be able to right click on warnings and set one of these tags. You can manually categorize warnings. - + + Exclude source files - + + Exclude folder... - + + Exclude file... - + + Enable inline suppressions Använd inline suppressions - + + Misra C - + + 2012 - - + + + + 2023 - + + 2025 - + + MISRA rule texts - + + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> - + + Misra C++ - + + 2008 - + + Cert C++ - + + Safety profiles (defined in C++ core guidelines) - + + Bug hunting (Premium) - + + External tools + Up Upp + Down Ned - + + Platform - - + + + + Analysis - + + Parser - + + Cppcheck (built in) - + + Check that each class has a safe public interface - + + Limit analysis - + + Check code in unused templates (should be ON normally, however in theory you can safely ignore warnings in unused templates) Check code in unused templates (slower and less accurate analysis) - + + Max CTU depth - + + Warning options - + + Root path: Bas sökväg: - + + Warning tags (separated by semicolon) Varnings taggar (separerade med semikolon) - + + Cppcheck build dir (whole program analysis, incremental analysis, statistics, etc) Cppcheck build dir (whole program analys, incremental analys, statistik, etc) - + + Types and Functions - + + Libraries Libraries - + + Suppressions Suppressions - + + Add Lägg till - - + + + + Addons Addons - + + Note: Addons require <a href="https://www.python.org/">Python</a> being installed. - + + Y2038 Y2038 - + + Thread safety Tråd säkerhet - + + Coding standards Kodstandarder - + + Cert C - + + CERT-INT35-C: int precision (if size equals precision, you can leave empty) - + + Autosar - + + Bug hunting - Clang analyzer - Clang analyzer + Clang analyzer - Clang-tidy - Clang-tidy + Clang-tidy + Defines: Defines: @@ -2025,82 +2171,87 @@ Options: ProjectFileDialog - + Project file: %1 Projektfil: %1 - + Note: Open source Cppcheck does not fully implement Misra C 2012 - + Clang-tidy (not found) - + Select Cppcheck build dir Välj Cppcheck build dir - + + C/C++ header + + + + + Include file + + + + Select include directory Välj include sökväg - + Source files - + + All files - + Exclude file - + Select MISRA rule texts file - + MISRA rule texts file (%1) - + Select a directory to check Välj mapp att analysera - Visual Studio - Visual Studio + Visual Studio - + Compile database - - Borland C++ Builder 6 - - - - + Import Project Importera Projekt - + Select directory to ignore Välj sökväg att ignorera @@ -2135,7 +2286,7 @@ Options: - + (Not found) @@ -2335,11 +2486,6 @@ Options: Tags - - - CWE - - QPlatformTheme @@ -2470,10 +2616,8 @@ Options: Ingen tag - - Cppcheck - Cppcheck + Cppcheck @@ -2559,122 +2703,124 @@ Kontrollera att sökvägen och parametrarna är korrekta. ResultsView - + Print Report Skriv ut rapport - + No errors found, nothing to print. Inga fel hittades, inget att skriva ut. - + %p% (%1 of %2 files checked) %p% (%1 av %2 filer analyserade) - - Cppcheck - Cppcheck + Cppcheck - + No errors found. Inga fel hittades. - + Errors were found, but they are configured to be hidden. To toggle what kind of errors are shown, open view menu. Fel hittades, men de visas ej. För att ställa in vilka fel som skall visas använd visa menyn. - - + + Failed to read the report. Misslyckades att läsa rapporten. - + XML format version 1 is no longer supported. XML format version 1 stöds ej längre. - + First included by Först inkluderad av - + Id Id - + Clear Log - + Copy this Log entry - + Copy complete Log - + Analysis was stopped - + There was a critical error with id '%1' - + when checking %1 - + when checking a file - + Analysis was aborted. - - + + Failed to save the report. Misslyckades med att spara rapporten. + Results Resultat + Critical errors + Analysis Log Analys Log + Warning Details Varningsdetaljer @@ -2683,26 +2829,31 @@ För att ställa in vilka fel som skall visas använd visa menyn. ScratchPad + Scratchpad Scratchpad + Copy or write some C/C++ code here: + Optionally enter a filename (mainly for automatic language detection) and click on "Check": + filename Filnamn + Check Analysera @@ -2711,21 +2862,25 @@ För att ställa in vilka fel som skall visas använd visa menyn. Settings + Preferences Inställningar + General Allmänt + Add... Lägg till... + Number of threads: Antal trådar: @@ -2735,57 +2890,68 @@ För att ställa in vilka fel som skall visas använd visa menyn. + Force checking all #ifdef configurations Check all #ifdef configurations Kontrollera alla #ifdef konfigurationer + Show full path of files Visa den fulla sökvägen för filer + Show "No errors found" message when no errors found Visa "Inga fel hittades" meddelande när inga fel hittas + Display error Id in column "Id" Visa meddelande id i kolumn "Id" + Enable inline suppressions Använd inline suppressions + Check for inconclusive errors also Kör inconclusive analys + Show statistics on check completion Visa statistik när analys är klar + Check for updates + Show internal warnings in log Visa interna fel i loggen + Addons Addons + Python binary (leave this empty to use python in the PATH) Python binär fil (lämna tom för att använda python i PATH) @@ -2793,117 +2959,143 @@ För att ställa in vilka fel som skall visas använd visa menyn. + + + ... ... + MISRA addon + MISRA rule texts file + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> + Clang Clang + Clang path (leave empty to use system PATH) Clang sökväg (lämna tom för att använda PATH) + Visual Studio headers Visual Studio headers + <html><head/><body><p>Paths to Visual Studio headers, separated by semicolon ';'.</p><p>You can open a Visual Studio command prompt, write &quot;SET INCLUDE&quot;. Then copy/paste the paths.</p></body></html> <html><head/><body><p>Sökvägar till Visual Studio headers, separerade med semikolon ';'.</p><p>Du kan öppna en Visual Studio command prompt, och skriva &quot;SET INCLUDE&quot;. Sedan kopiera och klistra in sökvägarna.</p></body></html> + Code Editor + Code Editor Style + System Style + Default Light Style + Default Dark Style + Custom + Remove Ta bort + Max count: + Applications Program + + Edit... Redigera... + Set as default Sätt förvald + Reports Rapporter + Save all errors when creating report Spara alla fel + Save full path to files in reports Spara fulla sökvägar + Language Språk @@ -2960,221 +3152,245 @@ För att ställa in vilka fel som skall visas använd visa menyn. - - + + + + Statistics Statistik - + + Project Projekt + Project: Projekt: + Paths: Sökvägar: + Include paths: Include sökvägar: + Defines: Defines: + Undefines: - + + Previous Scan Föregående analys + Path Selected: Vald sökväg: + Number of Files Scanned: Antal analyserade filer: + Scan Duration: Analys tid: + Errors: Fel: + Warnings: Varningar: + Stylistic warnings: Stil varningar: + Portability warnings: Portabilitets varningar: + Performance issues: Prestanda varningar: + Information messages: Informations meddelanden: + Active checkers: + Checkers + History Historik + File: Fil: + Copy to Clipboard Kopiera + Pdf Export Pdf Export - + 1 day 1 dag - + %1 days %1 dagar - + 1 hour 1 timme - + %1 hours %1 timmar - + 1 minute 1 minut - + %1 minutes %1 minuter - + 1 second 1 sekund - + %1 seconds %1 sekunder - + 0.%1 seconds 0.%1 sekunder - + and och - + Export PDF Exportera PDF - + Project Settings Projekt inställningar - + Paths Sökvägar - + Include paths Include sökvägar - + Defines Definitioner - + Undefines - + Path selected Vald sökväg - + Number of files scanned Antal analyserade filer - + Scan duration Tid - - + + Errors Fel @@ -3189,32 +3405,32 @@ För att ställa in vilka fel som skall visas använd visa menyn. Ingen Cppcheck build dir - - + + Warnings Varningar - - + + Style warnings Stil varningar - - + + Portability warnings Portabilitetsvarningar - - + + Performance warnings Prestanda varningar - - + + Information messages Informationsmeddelanden @@ -3223,6 +3439,7 @@ För att ställa in vilka fel som skall visas använd visa menyn. ThreadDetails + Thread Details @@ -3251,9 +3468,8 @@ The user interface language has been reset to English. Open the Preferences-dial Språket har nollställts till Engelska. Öppna Preferences och välj något av de tillgängliga språken. - Cppcheck - Cppcheck + Cppcheck diff --git a/gui/cppcheck_zh_CN.ts b/gui/cppcheck_zh_CN.ts index 02477e72e81..3bc2a493818 100644 --- a/gui/cppcheck_zh_CN.ts +++ b/gui/cppcheck_zh_CN.ts @@ -5,38 +5,45 @@ About + About Cppcheck 关于 Cppcheck + Version %1 版本 %1 + Cppcheck - A tool for static C/C++ code analysis. Cppcheck - C/C++ 静态代码分析工具。 + Copyright © 2007-%1 Cppcheck team. Copyright © 2007-2021 Cppcheck team. 版权所有 © 2007-%1 Cppcheck 团队。 + This program is licensed under the terms of the GNU General Public License version 3 该程序在 GNU 通用公共授权版本 3 的条款下发布 + Visit Cppcheck homepage at %1 访问 Cppcheck 主页: %1 + <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PCRE</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PicoJSON</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TinyXML2</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Boost</li></ul></body></html> <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">pcre</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">picojson</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">tinyxml2</li></ul></body></html> @@ -46,11 +53,13 @@ of the GNU General Public License version 3 ApplicationDialog + Add an application 添加应用程序 + Here you can add an application that can open error files. Specify a name for the application, the application executable and command line parameters for the application. The following texts in parameters are replaced with appropriate values when application is executed: @@ -76,21 +85,25 @@ Parameters: -l(line) (file) + &Name: 名称(&N): + &Executable: 可执行文件(&E): + &Parameters: 参数(&P): + Browse 浏览 @@ -105,9 +118,8 @@ Parameters: -l(line) (file) 选择查看应用程序 - Cppcheck - Cppcheck + Cppcheck @@ -115,70 +127,6 @@ Parameters: -l(line) (file) 你必须为应用程序指定名称、路径以及可选参数! - - ComplianceReportDialog - - - Compliance Report - - - - - Project name - - - - - Project version - - - - - Coding Standard - - - - - Misra C - - - - - Cert C - - - - - Cert C++ - - - - - List of files with md5 checksums - - - - - Compliance report - - - - - HTML files (*.html) - - - - - - Save compliance report - - - - - Failed to import '%1' (%2), can not show files in compliance report - - - FileViewDialog @@ -187,10 +135,8 @@ Parameters: -l(line) (file) 无法找到文件: %1 - - Cppcheck - Cppcheck + Cppcheck @@ -202,44 +148,49 @@ Parameters: -l(line) (file) HelpDialog + Cppcheck GUI help Cppcheck GUI 帮助 + Contents 内容 + Index 索引 - + Helpfile '%1' was not found 帮助文件 '%1' 未找到 - Cppcheck - Cppcheck + Cppcheck LibraryAddFunctionDialog + Add function 添加函数 + Function name(s) 函数名 + Number of arguments 参数个数 @@ -248,86 +199,103 @@ Parameters: -l(line) (file) LibraryDialog + Library Editor 库编辑器 + Open 打开 + Save 保存 + Save as 另存为 + Functions 函数 + Sort 排序 + Add 添加 + Filter: 过滤: + Comments 注释 + noreturn 无返回 + False 否 + True 是 + Unknown 未知 + return value must be used 返回值必须被使用 + ignore function in leaks checking 在泄漏检查中忽略函数 + Arguments 参数 + Edit 编辑 @@ -343,11 +311,8 @@ Parameters: -l(line) (file) 打开库文件 - - - Cppcheck - Cppcheck + Cppcheck @@ -376,11 +341,13 @@ Parameters: -l(line) (file) LibraryEditArgDialog + Edit argument 编辑参数 + <html><head/><body> <p>Is bool value allowed? For instance result from comparison or from '!' operator.</p> <p>Typically, set this if the argument is a pointer, size, etc.</p> @@ -396,11 +363,13 @@ Parameters: -l(line) (file) + Not bool 非布尔值 + <html><head/><body> <p>Is a null parameter value allowed?</p> <p>Typically this should be used on any pointer parameter that does not allow null.</p> @@ -416,78 +385,99 @@ Parameters: -l(line) (file) + Not null 非空 + Not uninit 非未初始化 + String 字符串 + Format string 格式化字符串 + Min size of buffer 最小缓冲区大小 + + Type 类型 + + None 无 + + argvalue argvalue + + mul mul + + strlen strlen + + Arg 参数1 + + Arg2 参数2 + and 并且 + Valid values 有效值 @@ -495,612 +485,688 @@ Parameters: -l(line) (file) MainWindow - - - - - - - - - - - - - - - Cppcheck - Cppcheck + Cppcheck + &File 文件(&F) - + + &View 查看(&V) - + + &Toolbars 工具栏(&T) - + + &Help 帮助(&H) - + + C++ standard C++ 标准 - + + &C standard C standard &C 标准 - + + &Edit 编辑(&E) - + + Standard 标准 - + + Categories 分类 - + + &License... 许可证(&L)... - + + A&uthors... 作者(&U)... - + + &About... 关于(&A)... - + + &Files... 文件(&F)... - - + + + + Analyze files Check files 分析文件 - + + Ctrl+F Ctrl+F - + + &Directory... 目录(&D)... - - + + + + Analyze directory Check directory 分析目录 - + + Ctrl+D Ctrl+D - + + Ctrl+R Ctrl+R - + + &Stop 停止(&S) - - + + + + Stop analysis Stop checking 停止分析 - + + Esc Esc - + + &Save results to file... 保存结果到文件(&S)... - + + Ctrl+S Ctrl+S - + + &Quit 退出(&Q) - + + &Clear results 清空结果(&C) - + + &Preferences 首选项(&P) - - + + + + Show style warnings 显示风格警告 - - - + + + + + Show errors 显示错误 - - + + Information 信息 - + + Show information messages 显示信息消息 - + + Show portability warnings 显示可移植性警告 - + + Show Cppcheck results 显示 Cppcheck 结果 - + + Clang Clang - + + Show Clang results 显示 Clang 结果 - + + &Filter 滤器(&F) - + + Filter results 过滤结果 - + + Windows 32-bit ANSI - + + Windows 32-bit Unicode - + + Unix 32-bit - + + Unix 64-bit - + + Windows 64-bit - + + &Print... 打印(&P)... - + + Print the Current Report 打印当前报告 - + + Print Pre&view... 打印预览(&v)... - + + Open a Print Preview Dialog for the Current Results 打开当前结果的打印预览窗口 - + + Open library editor 打开库编辑器 - + + C&lose Project File 关闭项目文件(&L) - + + &Edit Project File... 编辑项目文件(&E)... - + + &Statistics 统计(&S) - - - + + + + + Show warnings 显示警告 - - + + + + Show performance warnings 显示性能警告 - + + Show &hidden 显示隐藏项(&H) - + + &Check all 全部选中(&C) + Checking for updates + Hide 隐藏 - - + + + + Report - + + A&nalyze 分析(&A) - + + Filter 滤器 - + + &Reanalyze modified files &Recheck modified files 重新分析已修改的文件(&R) - + + Reanal&yze all files 重新分析全部文件(&y) - + + Ctrl+Q Ctrl+Q - + + Style war&nings 风格警告(&n) - + + E&rrors 编辑(&r) - + + &Uncheck all 全部取消选中(&U) - + + Collapse &all 全部折叠(&A) - + + &Expand all 全部展开(&E) - + + &Standard 标准(&S) - + + Standard items 标准项 - + + &Contents 内容(&C) - + + Open the help contents 打开帮助内容 - + + F1 F1 - + + Toolbar 工具栏 - + + &Categories 分类(&C) - + + Error categories 错误分类 - + + &Open XML... 打开 XML (&O)... - + + Open P&roject File... 打开项目文件(&R)... - + + Ctrl+Shift+O Ctrl+Shift+O - + + Sh&ow Scratchpad... 显示便条(&o)... - + + &New Project File... 新建项目文件(&N)... - + + Ctrl+Shift+N Ctrl+Shift+N - + + &Log View 日志视图(&L) - + + Log View 日志视图 - + + &Warnings 警告(&W) - + + Per&formance warnings 性能警告(&f) - + + &Information 信息(&I) - + + &Portability 可移植性(&P) - + + P&latforms 平台(&l) - + + C++&11 C++&11 - + + C&99 C&99 - + + &Posix &Posix - + + C&11 C&11 - + + &C89 &C89 - + + &C++03 &C++03 - + + &Library Editor... 库编辑器(&L)... - + + &Auto-detect language 自动检测语言(&A) - + + &Enforce C++ &Enforce C++ - + + E&nforce C E&nforce C - + + C++14 C++14 - + + Reanalyze and check library 重新分析并检查库 - + + Check configuration (defines, includes) 检查配置(defines, includes) - + + C++17 C++17 - + + C++20 C++20 - + + Compliance report... - + + Normal 常规 - - Misra C - - - - - Misra C++ 2008 - - - - - Cert C - - - - - Cert C++ - - - - - Misra C++ 2023 - - - - + + Autosar - + + EULA... - + + Thread Details - + + Show thread details - + There was a problem with loading the editor application settings. This is probably because the settings were changed between the Cppcheck versions. Please check (and fix) the editor application settings, otherwise the editor program might not start correctly. @@ -1109,23 +1175,28 @@ This is probably because the settings were changed between the Cppcheck versions 这可能是因为 Cppcheck 不同版本间的设置有所不同。请检查(并修复)编辑器应用程序设置,否则编辑器程序可能不会正确启动。 - + You must close the project file before selecting new files or directories! 在选择新的文件或目录之前,你必须先关闭此项目文件! - - + + Quick Filter: 快速滤器: - + + None of the selected files is part of the project, nothing to analyze. + + + + Select configuration 选择配置 - + Found project file: %1 Do you want to load this project file instead? @@ -1134,70 +1205,64 @@ Do you want to load this project file instead? 你是否想加载该项目文件? - + The library '%1' contains unknown elements: %2 库 '%1' 包含未知元素: %2 - + File not found 文件未找到 - + Bad XML 无效的 XML - + Missing attribute 缺失属性 - + Bad attribute value 无效的属性值 - + Unsupported format 不支持的格式 - + Duplicate platform type 重复的平台类型 - + Platform type redefined 平台类型重定义 - + Unknown element 位置元素 - - Unknown element - Unknown issue - 未知问题 - - - + Failed to load the selected library '%1'. %2 选择的库 '%1' 加载失败。 %2 - - - - + + + + Error 错误 @@ -1206,143 +1271,136 @@ Do you want to load this project file instead? 加载 %1 失败。您的 Cppcheck 安装已损坏。您可以在命令行添加 --data-dir=<目录> 参数来指定文件位置。请注意,'--data-dir' 参数应当由安装脚本使用,因此,当使用此参数时,GUI不会启动,所发生的一切只是配置了设置。 - - + + XML files (*.xml) XML 文件(*.xml) - + Open the report file 打开报告文件 - + License 许可证 - + Authors 作者 - + Save the report file 保存报告文件 - + Text files (*.txt) 文本文件(*.txt) - + CSV files (*.csv) CSV 文件(*.csv) - - Cannot generate a compliance report right now, an analysis must finish successfully. Try to reanalyze the code and ensure there are no critical errors. - - - - + Project files (*.cppcheck);;All files(*.*) 项目文件(*.cppcheck);;所有文件(*.*) - + Select Project File 选择项目文件 - + Failed to open file - + Unknown project file format - + Failed to import project file - + Failed to import '%1': %2 Analysis is stopped. - + Failed to import '%1' (%2), analysis is stopped - + Install - + New version available: %1. %2 - - - - + + + + Project: 项目: - + No suitable files found to analyze! 没有找到合适的文件来分析! - + C/C++ Source C/C++ 源码 - + Compile database Compile database - Visual Studio - Visual Studio + Visual Studio - Borland C++ Builder 6 - Borland C++ Builder 6 + Borland C++ Builder 6 - + Select files to analyze 选择要分析的文件 - + Select directory to analyze 选择要分析的目录 - + Select the configuration that will be analyzed 选择要分析的配置 - + Found project files from the directory. Do you want to proceed analysis without using any of these project files? @@ -1351,44 +1409,44 @@ Do you want to proceed analysis without using any of these project files? - + Duplicate define - + File not found: '%1' - + Failed to load/setup addon %1: %2 - + Failed to load %1. Your Cppcheck installation is broken. You can use --data-dir=<directory> at the command line to specify where this file is located. Please note that --data-dir is supposed to be used by installation scripts and therefore the GUI does not start when it is used, all that happens is that the setting is configured. Analysis is aborted. - + Failed to load %1 - %2 Analysis is aborted. - - + + %1 Analysis is aborted. - + Current results will be cleared. Opening a new XML file will clear current results. @@ -1399,7 +1457,7 @@ Do you want to proceed? 你想继续吗? - + Analyzer is running. Do you want to stop the analysis and exit Cppcheck? @@ -1408,77 +1466,77 @@ Do you want to stop the analysis and exit Cppcheck? 您想停止分析并退出 Cppcheck 吗? - + About - + XML files (*.xml);;Text files (*.txt);;CSV files (*.csv) XML 文件 (*.xml);;文本文件 (*.txt);;CSV 文件 (*.csv) - + Build dir '%1' does not exist, create it? 构建文件夹 '%1' 不能存在,创建它吗? - + To check the project using addons, you need a build directory. 要使用插件检查项目,您需要一个构建目录。 - + Show Mandatory - + Show Required - + Show Advisory - + Show Document - + Show L1 - + Show L2 - + Show L3 - + Show style - + Show portability - + Show performance - + Show information @@ -1487,22 +1545,22 @@ Do you want to stop the analysis and exit Cppcheck? 导入 '%1' 失败,分析已停止 - + Project files (*.cppcheck) 项目文件 (*.cppcheck) - + Select Project Filename 选择项目文件名 - + No project file loaded 项目文件未加载 - + The project file %1 @@ -1570,26 +1628,31 @@ Options: NewSuppressionDialog + New suppression 新建抑制 + Error ID 错误 ID + File name 文件名 + Line number 行号 + Symbol name 符号名 @@ -1606,78 +1669,62 @@ Options: Native 本地 - - - Unix 32-bit - - - - - Unix 64-bit - - - - - Windows 32-bit ANSI - - - - - Windows 32-bit Unicode - - - - - Windows 64-bit - - ProjectFile + Project File 项目文件 + Paths and Defines 路径和定义 + Import Project (Visual studio / compile database/ Borland C++ Builder 6) Import Project (Visual studio / compile database) 导入项目 (Visual studio / compile database/ Borland C++ Builder 6) + Defines must be separated by a semicolon. Example: DEF1;DEF2=5;DEF3=int Defines must be separated by a semicolon ';' 定义必须用分号分隔。例如:DEF1;DEF2=5;DEF3=int - + + Note: Put your own custom .cfg files in the same folder as the project file. You should see them above. 注意:把你自己的 .cfg 文件放在和项目文件相同的文件夹中。你应该在上面看到它们。 - + + If tags are added, you will be able to right click on warnings and set one of these tags. You can manually categorize warnings. 如果添加了标记,您将能够右键单击警告并设置其中一个标记。您可以手动对警告进行分类。 - + + Exclude source files 排除源文件 - + + Exclude folder... 排除文件夹... - + + Exclude file... 排除文件... @@ -1686,344 +1733,427 @@ Options: MISRA C 2012 - + + MISRA rule texts MISRA 规则文本 - + + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> <html><head/><body><p>从 MISRA C 2012 PDF 的附录 A &quot;指南摘要&quot; 复制/粘贴文本到一个文本文件。</p></body></html> - + + ... ... + <html><head/><body><p>You have a choice:</p><p> * Analyze all Debug and Release configurations</p><p> * Only analyze the first matching Debug configuration</p><p><br/></p></body></html> <html><head/><body><p>您有一个选择:</p><p> * 分析所有的 Debug 和 Release 配置</p><p> * 只分析第一个匹配的 Debug 配置</p><p><br/></p></body></html> - - + + + Browse... 浏览... + Analyze all Visual Studio configurations 分析全部 Visual Studio 配置 + Selected VS Configurations 已选择的 VS 配置 + Paths: 路径: + + Add... 添加... - + + + + Edit 编辑 - - + + + + + + Remove 移除 + Undefines: 未定义: + Undefines must be separated by a semicolon. Example: UNDEF1;UNDEF2;UNDEF3 未定义必须用分号分隔。例如:UNDEF1;UNDEF2;UNDEF3 + Include Paths: 包含目录: - + + + Include file + + + + + + <html><head/><body><p><span style=" font-family:'monospace'; color:#000000; background-color:#ffffff;">Force inclusion of a header file</span></p></body></html> + + + + + + Browse.. + + + + + Types and Functions 类型和函数 - - + + + + Analysis 分析 - + + This is a workfolder that Cppcheck will use for various purposes. 这是一个 Cppcheck 将用于各种目的的工作文件夹。 - + + Parser 解析器 - + + Cppcheck (built in) Cppcheck (内建) - + + Check level - + + Reduced -- meant for usage where developer wants results directly. Limited and faster analysis with fewer results. - + + Normal -- meant for normal analysis in CI. Analysis should finish in reasonable time. - + + Exhaustive -- meant for nightly builds etc. Analysis time can be longer (10x slower than compilation is OK). - + + Check that each class has a safe public interface 检查每个类是否有一个安全的公共接口 - + + Limit analysis 极限分析 - + + Check code in unused templates (should be ON normally, however in theory you can safely ignore warnings in unused templates) Check code in unused templates (slower and less accurate analysis) 检查未使用模板中的代码(正常情况下应该是打开,但理论上可以忽略未使用模板中的警告) - + + Max CTU depth 最大 CTU 深度 - - Premium License - - - - + + Enable inline suppressions 启用内联方案 - + + 2025 - + + Misra C++ - + + 2008 - + + Cert C - + + CERT-INT35-C: int precision (if size equals precision, you can leave empty) - + + Autosar - + + Safety profiles (defined in C++ core guidelines) - + + Bug hunting - + + External tools 外部工具 + Up 向上 + Down 向下 - + + Platform 平台 - + + Clang (experimental) Clang (实验性的) - + + If you want to design your classes to be as flexible and robust as possible then the public interface must be very robust. Cppcheck will asumme that arguments can take *any* value. 如果你想要设计你的类尽可能的灵活和健壮,那么公共接口必须非常健壮。Cppcheck 将假设参数可以取 *任何* 值。 - + + Check code in headers (should be ON normally. if you want a limited quick analysis then turn this OFF) 检查头文件中的代码(通常应该是打开的。如果您想要一个有限的快速分析,那么关掉它)) - + + Max recursion in template instantiation 模板实例化中的最大递归 - + + Warning options 警告选项 - + + Root path: 根路径: - + + Filepaths in warnings will be relative to this path 警告中的文件路径将相对于此路径 - + + Warning tags (separated by semicolon) 警告标志(用分号隔开) - + + Cppcheck build dir (whole program analysis, incremental analysis, statistics, etc) Cppcheck 构建目录 (整个程序分析、增量分析、统计数据等) - + + Libraries 库 - + + Suppressions 抑制 - + + Add 添加 - - + + + + Addons 插件 - + + Note: Addons require <a href="https://www.python.org/">Python</a> being installed. 注意:插件需要安装 <a href="https://www.python.org/">Python</a>。 - + + Y2038 Y2038 - + + Thread safety 线程安全 - + + Coding standards 编码标准 - + + Misra C - + + 2012 - - + + + + 2023 - + + Cert C++ - + + Bug hunting (Premium) - Clang analyzer - Clang analyzer + Clang analyzer - Clang-tidy - Clang-tidy + Clang-tidy + Defines: 定义: @@ -2031,82 +2161,91 @@ Options: ProjectFileDialog - + Project file: %1 项目文件: %1 - + Select Cppcheck build dir 选择 Cppcheck 构建目录 - + Select include directory 选择 Include 目录 - + Select a directory to check 选择一个检查目录 - + Note: Open source Cppcheck does not fully implement Misra C 2012 - + Clang-tidy (not found) Clang-tidy (未找到) - Visual Studio - Visual Studio + Visual Studio - + Compile database Compile database - Borland C++ Builder 6 - Borland C++ Builder 6 + Borland C++ Builder 6 - + Import Project 导入项目 - + + C/C++ header + + + + + Include file + + + + Select directory to ignore 选择忽略的目录 - + Source files 源文件 - + + All files 全部文件 - + Exclude file 排除文件 - + Select MISRA rule texts file 选择 MISRA 规则文本文件 - + MISRA rule texts file (%1) MISRA 规则文本文件 (%1) @@ -2139,7 +2278,7 @@ Options: 第%1行:在 "%3" 中缺失的必选属性 "%2" - + (Not found) (未找到) @@ -2339,11 +2478,6 @@ Options: Tags - - - CWE - - QPlatformTheme @@ -2474,10 +2608,8 @@ Options: 取消标记 - - Cppcheck - Cppcheck + Cppcheck @@ -2564,81 +2696,83 @@ Please check the application path and parameters are correct. ResultsView + Results 结果 + Critical errors + Analysis Log 分析日志 + Warning Details 警告详情 - - + + Failed to save the report. 保存报告失败。 - + Print Report 打印报告 - + No errors found, nothing to print. 没有错误发现,没有可打印内容。 - + %p% (%1 of %2 files checked) %p% (%2 个文件已检查 %1 个) - - Cppcheck - Cppcheck + Cppcheck - + No errors found. 未发现错误。 - + Errors were found, but they are configured to be hidden. To toggle what kind of errors are shown, open view menu. 发现错误,但它们被设为隐藏。 打开“查看”菜单,切换需要显示的错误。 - - + + Failed to read the report. 读取报告失败。 - + XML format version 1 is no longer supported. 不再支持 XML 格式版本 1。 - + First included by 首次包含于 - + Id Id @@ -2647,42 +2781,42 @@ To toggle what kind of errors are shown, open view menu. 错误搜寻分析未完成 - + Clear Log 清空日志 - + Copy this Log entry 复制此日志条目 - + Copy complete Log 复制完整日志 - + Analysis was stopped - + There was a critical error with id '%1' - + when checking %1 - + when checking a file - + Analysis was aborted. @@ -2691,26 +2825,31 @@ To toggle what kind of errors are shown, open view menu. ScratchPad + Scratchpad 便条 + Copy or write some C/C++ code here: 在这里复制或输入一些 C/C++ 代码: + Optionally enter a filename (mainly for automatic language detection) and click on "Check": 可选择输入文件名 (主要用来自动语言检测) 然后点击 "检查": + filename 文件名 + Check 检查 @@ -2719,21 +2858,25 @@ To toggle what kind of errors are shown, open view menu. Settings + Preferences 首选项 + General 常规 + Add... 添加... + Number of threads: 线程个数: @@ -2743,57 +2886,68 @@ To toggle what kind of errors are shown, open view menu. + Force checking all #ifdef configurations Check all #ifdef configurations 强制检查所有 #ifdef 配置 + Show full path of files 显示文件的完整路径 + Show "No errors found" message when no errors found 当未找到错误,显示“未发现错误”消息 + Display error Id in column "Id" 在列“Id”中显示错误 Id + Enable inline suppressions 启用内联方案 + Check for inconclusive errors also 检查不确定的错误 + Show statistics on check completion 检查完成后显示统计数据 + Check for updates + Show internal warnings in log 在日志中显示内建警告 + Addons 插件 + Python binary (leave this empty to use python in the PATH) Python 二进制 (留空将使用 PATH 路径中的 python) @@ -2801,117 +2955,143 @@ To toggle what kind of errors are shown, open view menu. + + + ... ... + MISRA addon MISRA 插件 + MISRA rule texts file MISRA 规则文本文件 + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> <html><head/><body><p>从 MISRA C 2012 PDF 的附录 A &quot;指南摘要&quot; 复制/粘贴文本到一个文本文件。</p></body></html> + Clang Clang + Clang path (leave empty to use system PATH) Clang 路径 (留空将使用系统 PATH 路径) + Visual Studio headers Visual Studio 头文件 + <html><head/><body><p>Paths to Visual Studio headers, separated by semicolon ';'.</p><p>You can open a Visual Studio command prompt, write &quot;SET INCLUDE&quot;. Then copy/paste the paths.</p></body></html> <html><head/><body><p>Visual Studio 头文件路径,用分号 ';' 分割。</p><p>你可以打开一个 Visual Studio 命令提示符,输入 &quot;SET INCLUDE&quot;。然后复制/粘贴路径。</p></body></html> + Code Editor 代码编辑器 + Code Editor Style 代码编辑器风格 + System Style 系统风格 + Default Light Style 默认浅色风格 + Default Dark Style 默认深色风格 + Custom 自定义 + Remove 移除 + Max count: + Applications 应用程序 + + Edit... 编辑... + Set as default 设为默认 + Reports 报告 + Save all errors when creating report 创建报告时,保存所有错误 + Save full path to files in reports 在报告中保存文件的完整路径 + Language 语言 @@ -2968,221 +3148,245 @@ To toggle what kind of errors are shown, open view menu. - - + + + + Statistics 统计 - + + Project 项目 + Project: 项目: + Paths: 路径: + Include paths: 包含路径: + Defines: 定义: + Undefines: 未定义: - + + Previous Scan 上一次扫描 + Path Selected: 选中的路径: + Number of Files Scanned: 扫描的文件数: + Scan Duration: 扫描时间: + Errors: 错误: + Warnings: 警告: + Stylistic warnings: Stylistic 警告: + Portability warnings: 可移植性警告: + Performance issues: 性能警告: + Information messages: 信息: + Active checkers: + Checkers + History 历史 + File: 文件: + Copy to Clipboard 复制到剪贴板 + Pdf Export 导出 PDF - + 1 day 1 天 - + %1 days %1 天 - + 1 hour 1 小时 - + %1 hours %1 小时 - + 1 minute 1 分钟 - + %1 minutes %1 分钟 - + 1 second 1 秒 - + %1 seconds %1 秒 - + 0.%1 seconds 0.%1 秒 - + and 与 - + Export PDF 导出 PDF - + Project Settings 项目设置 - + Paths 路径 - + Include paths 包含路径 - + Defines 定义 - + Undefines 未定义 - + Path selected 选中的路径 - + Number of files scanned 扫描的文件数 - + Scan duration 扫描时间 - - + + Errors 错误 @@ -3197,32 +3401,32 @@ To toggle what kind of errors are shown, open view menu. 没有 cppcheck 构建目录 - - + + Warnings 警告 - - + + Style warnings 风格警告 - - + + Portability warnings 移植可能性警告 - - + + Performance warnings 性能警告 - - + + Information messages 信息 @@ -3231,6 +3435,7 @@ To toggle what kind of errors are shown, open view menu. ThreadDetails + Thread Details @@ -3259,9 +3464,8 @@ The user interface language has been reset to English. Open the Preferences-dial 用户界面语言已被重置为英语。打开“首选项”对话框,选择任何可用的语言。 - Cppcheck - Cppcheck + Cppcheck diff --git a/gui/cppcheck_zh_TW.ts b/gui/cppcheck_zh_TW.ts index 644e92e7b49..b1951e9f29f 100644 --- a/gui/cppcheck_zh_TW.ts +++ b/gui/cppcheck_zh_TW.ts @@ -5,26 +5,31 @@ About + About Cppcheck 關於 Cppcheck + Version %1 版本 %1 + Cppcheck - A tool for static C/C++ code analysis. Cppcheck - 一款靜態 C/C++ 程式碼分析工具。 + Copyright © 2007-%1 Cppcheck team. 著作權 © 2007-%1 Cppcheck 團隊。 + This program is licensed under the terms of the GNU General Public License version 3 該程式是根據 GNU 通用公眾授權條款第 3 版 @@ -32,11 +37,13 @@ of the GNU General Public License version 3 + Visit Cppcheck homepage at %1 訪問 Cppcheck 主頁: %1 + <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PCRE</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">PicoJSON</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">TinyXML2</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Boost</li></ul></body></html> <html><head/><body><p>Many thanks to these libraries that we use:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">pcre</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">picojson</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">qt</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">tinyxml2</li></ul></body></html> @@ -46,11 +53,13 @@ of the GNU General Public License version 3 ApplicationDialog + Add an application 新增應用程式 + Here you can add an application that can open error files. Specify a name for the application, the application executable and command line parameters for the application. The following texts in parameters are replaced with appropriate values when application is executed: @@ -66,21 +75,25 @@ Parameters: -l(line) (file) + &Name: 名稱(&N): + &Executable: 執行檔(&E): + &Parameters: 參數(&P): + Browse 瀏覽 @@ -95,9 +108,8 @@ Parameters: -l(line) (file) 選取檢視器應用程式 - Cppcheck - Cppcheck + Cppcheck @@ -108,65 +120,16 @@ Parameters: -l(line) (file) ComplianceReportDialog - - Compliance Report - - - - Project name - 專案名稱 + 專案名稱 - Project version - 專案版本 + 專案版本 - - Coding Standard - - - - - Misra C - - - - - Cert C - - - - - Cert C++ - - - - - List of files with md5 checksums - - - - - Compliance report - - - - HTML files (*.html) - HTML 檔案 (*.html) - - - - - Save compliance report - - - - - Failed to import '%1' (%2), can not show files in compliance report - + HTML 檔案 (*.html) @@ -177,10 +140,8 @@ Parameters: -l(line) (file) 無法找到檔案: %1 - - Cppcheck - Cppcheck + Cppcheck @@ -192,44 +153,49 @@ Parameters: -l(line) (file) HelpDialog + Cppcheck GUI help Cppcheck GUI 幫助 + Contents 內容 + Index 索引 - + Helpfile '%1' was not found 找不到幫助檔 '%1' - Cppcheck - Cppcheck + Cppcheck LibraryAddFunctionDialog + Add function 新增函式 + Function name(s) 函式名稱 + Number of arguments 引數數量 @@ -238,86 +204,103 @@ Parameters: -l(line) (file) LibraryDialog + Library Editor 程式庫編輯器 + Open 開啟 + Save 儲存 + Save as 另存為 + Functions 函式 + Sort 排序 + Add 新增 + Filter: + Comments 註釋 + noreturn + False + True + Unknown 未知 + return value must be used + ignore function in leaks checking + Arguments 引數 + Edit 編輯 @@ -333,11 +316,8 @@ Parameters: -l(line) (file) 開啟程式庫檔案 - - - Cppcheck - Cppcheck + Cppcheck @@ -364,11 +344,13 @@ Parameters: -l(line) (file) LibraryEditArgDialog + Edit argument 編輯引數 + <html><head/><body> <p>Is bool value allowed? For instance result from comparison or from '!' operator.</p> <p>Typically, set this if the argument is a pointer, size, etc.</p> @@ -379,11 +361,13 @@ Parameters: -l(line) (file) + Not bool + <html><head/><body> <p>Is a null parameter value allowed?</p> <p>Typically this should be used on any pointer parameter that does not allow null.</p> @@ -394,78 +378,99 @@ Parameters: -l(line) (file) + Not null + Not uninit + String 字串 + Format string + Min size of buffer + + Type 型別 + + None + + argvalue + + mul + + strlen + + Arg + + Arg2 + and + Valid values 有效值 @@ -473,597 +478,681 @@ Parameters: -l(line) (file) MainWindow - - - - - - - - - - - - - - - Cppcheck - Cppcheck + Cppcheck + Checking for updates 檢查更新 + Hide 隱藏 + &File 檔案(&F) - + + &View 檢視(&V) - + + &Toolbars 工具條(&T) - - + + + + Report - + + &Help 幫助(&H) - + + A&nalyze 分析(&N) - + + C++ standard C++ 標準 - + + &C standard C 標準(&C) - + + &Edit 編輯(&E) - + + Standard 標準 - + + Categories 分類 - + + Filter 篩選 - + + &License... 授權(&L)... - + + A&uthors... 作者(&U)... - + + &About... 關於(&A)... - + + &Files... 檔案(&F)... - - + + + + Analyze files 分析檔案 - + + Ctrl+F Ctrl+F - + + &Directory... 目錄(&D)... - - + + + + Analyze directory 分析目錄 - + + Ctrl+D Ctrl+D - + + &Reanalyze modified files 重新分析已修改的檔案(&R) - + + Ctrl+R Ctrl+R - + + Reanal&yze all files 重新分析所有檔案(&Y) - + + &Stop 停止(&S) - - + + + + Stop analysis 停止分析 - + + Esc Esc - + + &Save results to file... 儲存結果為檔案(&S)... - + + Ctrl+S Ctrl+S - + + &Quit 退出(&Q) - + + Ctrl+Q Ctrl+Q - + + &Clear results 清除結果(&C) - + + &Preferences 偏好設定(&P) - + + Style war&nings 樣式警告(&N) - - + + + + Show style warnings 顯示樣式警告 - + + E&rrors 錯誤(&R) - - - + + + + + Show errors 顯示錯誤 - + + &Check all 全部檢查(&C) - + + &Uncheck all - + + Collapse &all 全部摺疊(&A) - + + &Expand all 全部展開(&E) - + + &Standard 標準(&S) - + + Standard items 標準項目 - + + &Contents 內容(&C) - + + Open the help contents 開啟幫助內容 - + + F1 F1 - + + Toolbar 工具條 - + + &Categories 分類(&C) - + + Error categories 錯誤分類 - + + &Open XML... 開啟 XML(&O)... - + + Open P&roject File... 開啟專案檔(&R)... - + + Ctrl+Shift+O Ctrl+Shift+O - + + Sh&ow Scratchpad... - + + &New Project File... 新增專案檔(&N)... - + + Ctrl+Shift+N Ctrl+Shift+N - + + &Log View 日誌檢視(&L) - + + Log View 日誌檢視 - + + C&lose Project File 關閉專案檔(&L) - + + &Edit Project File... 編輯專案檔(&E)... - + + &Statistics 統計資料(&S) - + + &Warnings 警告(&W) - - - + + + + + Show warnings 顯示警告 - + + Per&formance warnings 效能警告(&F) - - + + + + Show performance warnings 顯示下效能警告 - + + Show &hidden 顯示隱藏項目(&H) - + + &Information 資訊(&I) - + + Show information messages 顯示資訊訊息 - + + &Portability 可移植性(&P) - + + Show portability warnings 顯示可移植性警告 - + + Show Cppcheck results 顯示 Cppcheck 結果 - + + Clang Clang - + + Show Clang results 顯示 Clang 結果 - + + &Filter 篩選(&F) - + + Filter results 篩選結果 - + + Windows 32-bit ANSI Windows 32 位元 ANSI - + + Windows 32-bit Unicode Windows 32 位元 Unicode - + + Unix 32-bit Unix 32 位元 - + + Unix 64-bit Unix 64 位元 - + + Windows 64-bit Windows 64 位元 - + + P&latforms 平臺(&L) - + + C++&11 C++&11 - + + C&99 C&99 - + + &Posix - + + C&11 C&11 - + + &C89 &C89 - + + &C++03 &C++03 - + + &Print... 列印(&P)... - + + Print the Current Report 列印當前報告 - + + Print Pre&view... 列印預覽(&V)... - + + Open a Print Preview Dialog for the Current Results 開啟當前結果的列印預覽視窗 - + + &Library Editor... 程式庫編輯器(&L)... - + + Open library editor 開啟程式庫編輯器 - + + &Auto-detect language 自動偵測語言(&A) - + + &Enforce C++ - + + E&nforce C - + + C++14 C++14 - + + Reanalyze and check library 重新分析並檢查程式庫 - + + Check configuration (defines, includes) 檢查組態 (定義、包含) - + + C++17 C++17 - + + C++20 C++20 - + + Compliance report... - + + Normal - - Misra C - - - - Misra C++ 2008 - Misra C++ 2008 + Misra C++ 2008 - - Cert C - - - - - Cert C++ - - - - Misra C++ 2023 - Misra C++ 2023 + Misra C++ 2023 - + + Autosar - + + EULA... - + + Thread Details - + + Show thread details @@ -1091,202 +1180,199 @@ Options: Cppcheck GUI - 命令行參數 - - + + Quick Filter: 快速篩選: - - - - + + + + Project: 專案: - + There was a problem with loading the editor application settings. This is probably because the settings were changed between the Cppcheck versions. Please check (and fix) the editor application settings, otherwise the editor program might not start correctly. - + + None of the selected files is part of the project, nothing to analyze. + + + + No suitable files found to analyze! 找不到適合的檔案來分析! - + You must close the project file before selecting new files or directories! 您必須在選取新檔案或目錄之前關閉該專案檔! - + C/C++ Source C/C++ 來源檔 - + Compile database 編譯資料庫 - Visual Studio - Visual Studio + Visual Studio - Borland C++ Builder 6 - Borland C++ Builder 6 + Borland C++ Builder 6 - + Select files to analyze 選取要分析的檔案 - + Select directory to analyze 選取要分析的目錄 - + Select configuration 選取組態 - + Select the configuration that will be analyzed 選取要分析的組態 - + Found project file: %1 Do you want to load this project file instead? - + Found project files from the directory. Do you want to proceed analysis without using any of these project files? - - + + Information 資訊 - + The library '%1' contains unknown elements: %2 - + File not found 找不到檔案 - + Bad XML - + Missing attribute - + Bad attribute value - + Unsupported format 未支援的格式 - + Duplicate platform type 重複的平臺型別 - + Platform type redefined 平臺型別重定義 - + Duplicate define - + Unknown element 未知的元素 - - Unknown element - Unknown issue - 未知的議題 - - - + Failed to load the selected library '%1'. %2 無法載入選取的程式庫 '%1'。 %2 - + File not found: '%1' - + Failed to load/setup addon %1: %2 - - - - + + + + Error 錯誤 - + Failed to load %1. Your Cppcheck installation is broken. You can use --data-dir=<directory> at the command line to specify where this file is located. Please note that --data-dir is supposed to be used by installation scripts and therefore the GUI does not start when it is used, all that happens is that the setting is configured. Analysis is aborted. - + Failed to load %1 - %2 Analysis is aborted. - - + + %1 Analysis is aborted. - + Current results will be cleared. Opening a new XML file will clear current results. @@ -1294,18 +1380,18 @@ Do you want to proceed? - - + + XML files (*.xml) XML 檔案 (*.xml) - + Open the report file 開啟報告檔 - + Analyzer is running. Do you want to stop the analysis and exit Cppcheck? @@ -1314,82 +1400,77 @@ Do you want to stop the analysis and exit Cppcheck? 您想停止分析並離開 Cppcheck 嗎? - + About 關於 - + License 授權 - + Authors 作者 - + XML files (*.xml);;Text files (*.txt);;CSV files (*.csv) XML 檔案 (*.xml);;文字檔 (*.txt);;CSV 檔案 (*.csv) - + Save the report file 儲存報告檔 - + Text files (*.txt) 文字檔 (*.txt) - + CSV files (*.csv) CSV 檔案 (*.csv) - - Cannot generate a compliance report right now, an analysis must finish successfully. Try to reanalyze the code and ensure there are no critical errors. - - - - + Project files (*.cppcheck);;All files(*.*) 專案檔 (*.cppcheck);;所有檔案 (*.*) - + Select Project File 選取專案檔 - + Build dir '%1' does not exist, create it? 建置目錄 '%1' 不存在,是否建立它? - + To check the project using addons, you need a build directory. - + Failed to open file 無法開啟檔案 - + Unknown project file format 未知的專案檔格式 - + Failed to import project file 無法匯入專案檔 - + Failed to import '%1': %2 Analysis is stopped. @@ -1398,62 +1479,62 @@ Analysis is stopped. 停止分析。 - + Failed to import '%1' (%2), analysis is stopped - + Show Mandatory - + Show Required - + Show Advisory - + Show Document - + Show L1 - + Show L2 - + Show L3 - + Show style - + Show portability - + Show performance - + Show information @@ -1462,22 +1543,22 @@ Analysis is stopped. 無法匯入 '%1',停止分析 - + Project files (*.cppcheck) 專案檔 (*.cppcheck) - + Select Project Filename 選取專案檔案名稱 - + No project file loaded - + The project file %1 @@ -1494,12 +1575,12 @@ Do you want to remove the file from the recently used projects -list? 您要從最近使用的專案列表中移除該檔案嗎? - + Install 安章 - + New version available: %1. %2 可用的新版本: %1. %2 @@ -1508,26 +1589,31 @@ Do you want to remove the file from the recently used projects -list? NewSuppressionDialog + New suppression 新建抑制 + Error ID 錯誤 ID + File name 檔案名稱 + Line number 行號 + Symbol name 符號名稱 @@ -1545,351 +1631,429 @@ Do you want to remove the file from the recently used projects -list? 原生 - Unix 32-bit - Unix 32 位元 + Unix 32 位元 - Unix 64-bit - Unix 64 位元 + Unix 64 位元 - Windows 32-bit ANSI - Windows 32 位元 ANSI + Windows 32 位元 ANSI - Windows 32-bit Unicode - Windows 32 位元 Unicode + Windows 32 位元 Unicode - Windows 64-bit - Windows 64 位元 + Windows 64 位元 ProjectFile + Project File 專案檔 + Paths and Defines 路徑與定義 + Import Project (Visual studio / compile database/ Borland C++ Builder 6) 匯入專案 (Visual Studio / 編譯資料庫 / Borland C++ Builder 6) - - + + + Browse... 瀏覽... + <html><head/><body><p>You have a choice:</p><p> * Analyze all Debug and Release configurations</p><p> * Only analyze the first matching Debug configuration</p><p><br/></p></body></html> + Analyze all Visual Studio configurations 分析所有 Visual Studio 組態 + Selected VS Configurations 選取 VS 組態 + Paths: 路徑: + + Add... 新增... - + + + + Edit 編輯 - - + + + + + + Remove 移除 + Defines: 定義: + Defines must be separated by a semicolon. Example: DEF1;DEF2=5;DEF3=int + Undefines: 未定義: + Undefines must be separated by a semicolon. Example: UNDEF1;UNDEF2;UNDEF3 + Include Paths: 包含路徑: + Up + Down - + + + Include file + + + + + + <html><head/><body><p><span style=" font-family:'monospace'; color:#000000; background-color:#ffffff;">Force inclusion of a header file</span></p></body></html> + + + + + + Browse.. + + + + + Types and Functions 型別與函式 - + + Platform 平臺 - + + Libraries 程式庫 - + + Note: Put your own custom .cfg files in the same folder as the project file. You should see them above. - - + + + + Analysis 分析 - + + Cppcheck build dir (whole program analysis, incremental analysis, statistics, etc) - + + This is a workfolder that Cppcheck will use for various purposes. - + + Parser 剖析器 - + + Cppcheck (built in) Cppcheck (內建) - + + Clang (experimental) - + + Check level - + + Reduced -- meant for usage where developer wants results directly. Limited and faster analysis with fewer results. - + + Normal -- meant for normal analysis in CI. Analysis should finish in reasonable time. - + + Exhaustive -- meant for nightly builds etc. Analysis time can be longer (10x slower than compilation is OK). - + + If you want to design your classes to be as flexible and robust as possible then the public interface must be very robust. Cppcheck will asumme that arguments can take *any* value. - + + Check that each class has a safe public interface - + + Limit analysis - + + Check code in headers (should be ON normally. if you want a limited quick analysis then turn this OFF) - + + Check code in unused templates (should be ON normally, however in theory you can safely ignore warnings in unused templates) - + + Max CTU depth - + + Max recursion in template instantiation - - Premium License - - - - + + Warning options 警告選項 - + + Root path: 根路徑: - + + Filepaths in warnings will be relative to this path - + + Warning tags (separated by semicolon) 警告標記 (由分號分隔) - + + If tags are added, you will be able to right click on warnings and set one of these tags. You can manually categorize warnings. - + + Exclude source files 排除來源檔 - + + Exclude folder... 排除資料夾... - + + Exclude file... 排除檔案... - + + Suppressions 抑制 - + + Add 新增 - + + Enable inline suppressions - - + + + + Addons - + + Note: Addons require <a href="https://www.python.org/">Python</a> being installed. - + + Y2038 Y2038 - + + Thread safety 執行緒安全 - + + Coding standards - + + Misra C - + + 2012 - - + + + + 2023 - + + 2025 - + + Misra C++ - + + 2008 @@ -1898,17 +2062,20 @@ Do you want to remove the file from the recently used projects -list? Misra C 2012 - + + MISRA rule texts - + + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> - + + ... ... @@ -1917,135 +2084,150 @@ Do you want to remove the file from the recently used projects -list? Misra C++ 2008 - + + Cert C - + + CERT-INT35-C: int precision (if size equals precision, you can leave empty) - + + Cert C++ - + + Autosar - + + Safety profiles (defined in C++ core guidelines) - + + Bug hunting (Premium) - + + Bug hunting - + + External tools 外部工具 - Clang-tidy - Clang-tidy + Clang-tidy - Clang analyzer - Clang 分析器 + Clang 分析器 ProjectFileDialog - + Project file: %1 專案檔: %1 - + Note: Open source Cppcheck does not fully implement Misra C 2012 - + Clang-tidy (not found) Clang-tidy (找不到) - + Select Cppcheck build dir 選取 Cppcheck 建置目錄 - Visual Studio - Visual Studio + Visual Studio - + Compile database 編譯資料庫 - Borland C++ Builder 6 - Borland C++ Builder 6 + Borland C++ Builder 6 - + Import Project 匯入專案 - + + C/C++ header + + + + + Include file + + + + Select a directory to check 選取要檢查的目錄 - + Select include directory 選取包含目錄 - + Select directory to ignore 選取要忽略的目錄 - + Source files 來源檔 - + + All files 所有檔案 - + Exclude file 排除檔案 - + Select MISRA rule texts file 選取 MISRA 規則文字檔 - + MISRA rule texts file (%1) MISRA 規則文字檔 (%1) @@ -2198,7 +2380,7 @@ Do you want to remove the file from the recently used projects -list? - + (Not found) (找不到) @@ -2277,11 +2459,6 @@ Do you want to remove the file from the recently used projects -list? Tags - - - CWE - - QPlatformTheme @@ -2402,10 +2579,8 @@ Do you want to remove the file from the recently used projects -list? 取消標記 - - Cppcheck - Cppcheck + Cppcheck @@ -2479,120 +2654,122 @@ Please check the application path and parameters are correct. ResultsView + Results 結果 + Critical errors + Analysis Log 分析日誌 + Warning Details 警告詳細資訊 - - + + Failed to save the report. 無法載入報告。 - + Print Report 列印報告 - + No errors found, nothing to print. - + %p% (%1 of %2 files checked) - - Cppcheck - Cppcheck + Cppcheck - + No errors found. 找不到錯誤。 - + Errors were found, but they are configured to be hidden. To toggle what kind of errors are shown, open view menu. - - + + Failed to read the report. 無法讀取報告。 - + XML format version 1 is no longer supported. 不再支援 XML 格式版本 1。 - + First included by - + Id 識別號 - + Clear Log 清除日誌 - + Copy this Log entry 複製該日誌條目 - + Copy complete Log 複製完整的日誌 - + Analysis was stopped - + There was a critical error with id '%1' - + when checking %1 - + when checking a file - + Analysis was aborted. @@ -2601,26 +2778,31 @@ To toggle what kind of errors are shown, open view menu. ScratchPad + Scratchpad + Copy or write some C/C++ code here: + Optionally enter a filename (mainly for automatic language detection) and click on "Check": + filename 檔案名稱 + Check @@ -2629,122 +2811,147 @@ To toggle what kind of errors are shown, open view menu. Settings + Preferences 偏好設定 + General 一般 + Number of threads: 執行緒數量: + Max count: + Force checking all #ifdef configurations + Show full path of files 顯示檔案的完整路徑 + Show "No errors found" message when no errors found + Display error Id in column "Id" + Enable inline suppressions + Check for inconclusive errors also + Show statistics on check completion + Check for updates 檢查更新 + Show internal warnings in log 顯示日誌中的內部警告 + Applications 應用程式 + Add... 新增... + + Edit... 編輯... + Remove 移除 + Set as default 設定為預設值 + Reports 報告 + Save all errors when creating report + Save full path to files in reports 在報告中儲存檔案的完整路徑 + Language 語言 + Addons + Python binary (leave this empty to use python in the PATH) @@ -2752,71 +2959,87 @@ To toggle what kind of errors are shown, open view menu. + + + ... ... + MISRA addon + MISRA rule texts file MISRA 規則文字檔 + <html><head/><body><p>Copy/paste the text from Appendix A &quot;Summary of guidelines&quot; from the MISRA C 2012 pdf to a text file.</p></body></html> + Clang Clang + Clang path (leave empty to use system PATH) + Visual Studio headers Visual Studio 標頭檔 + <html><head/><body><p>Paths to Visual Studio headers, separated by semicolon ';'.</p><p>You can open a Visual Studio command prompt, write &quot;SET INCLUDE&quot;. Then copy/paste the paths.</p></body></html> + Code Editor 程式碼編輯器 + Code Editor Style 程式碼編輯器樣式 + System Style 系統樣式 + Default Light Style + Default Dark Style + Custom 自訂 @@ -2869,120 +3092,144 @@ To toggle what kind of errors are shown, open view menu. - - + + + + Statistics 統計資料 - + + Project 專案 + Project: 專案: + Paths: 路徑: + Include paths: 包含路徑: + Defines: 定義: + Undefines: 未定義: - + + Previous Scan 上一次掃描 + Path Selected: 選取的路徑: + Number of Files Scanned: 已掃描的檔案數量: + Scan Duration: 掃描時間: + Errors: 錯誤: + Warnings: 警告: + Stylistic warnings: + Portability warnings: 可移植性警告: + Performance issues: 效能議題: + Information messages: 資訊訊息: + Active checkers: + Checkers + History 歷史紀錄 + File: 檔案: + Copy to Clipboard 複製到剪貼簿 + Pdf Export Pdf 匯出 @@ -2997,133 +3244,133 @@ To toggle what kind of errors are shown, open view menu. 沒有 cppcheck 建置目錄 - + 1 day 1 天 - + %1 days %1 天 - + 1 hour 1 小時 - + %1 hours %1 小時 - + 1 minute 1 分鐘 - + %1 minutes %1 分鐘 - + 1 second 1 秒鐘 - + %1 seconds %1 秒鐘 - + 0.%1 seconds 0.%1 秒鐘 - + and - - + + Errors 錯誤 - - + + Warnings 警告 - - + + Style warnings 樣式警告 - - + + Portability warnings 可移植性警告 - - + + Performance warnings 效能警告 - - + + Information messages 資訊訊息 - + Export PDF 匯出 PDF - + Project Settings 專案設定 - + Paths 路徑 - + Include paths 包含路徑 - + Defines 定義 - + Undefines 未定義 - + Path selected 選取的路徑 - + Number of files scanned 已掃描的檔案數量 - + Scan duration 掃描時間 @@ -3132,6 +3379,7 @@ To toggle what kind of errors are shown, open view menu. ThreadDetails + Thread Details @@ -3155,11 +3403,6 @@ To toggle what kind of errors are shown, open view menu. The user interface language has been reset to English. Open the Preferences-dialog to select any of the available languages. - - - Cppcheck - - TxtReport diff --git a/gui/cppchecklibrarydata.h b/gui/cppchecklibrarydata.h index ddccf3b6534..71d8382ef34 100644 --- a/gui/cppchecklibrarydata.h +++ b/gui/cppchecklibrarydata.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/gui/fileviewdialog.cpp b/gui/fileviewdialog.cpp index 3f66f008678..033c34130d7 100644 --- a/gui/fileviewdialog.cpp +++ b/gui/fileviewdialog.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -54,7 +54,7 @@ void FileViewDialog::loadTextFile(const QString &filename, QTextEdit *edit) msg = msg.arg(filename); QMessageBox msgbox(QMessageBox::Critical, - tr("Cppcheck"), + "Cppcheck", msg, QMessageBox::Ok, this); @@ -68,7 +68,7 @@ void FileViewDialog::loadTextFile(const QString &filename, QTextEdit *edit) msg = msg.arg(filename); QMessageBox msgbox(QMessageBox::Critical, - tr("Cppcheck"), + "Cppcheck", msg, QMessageBox::Ok, this); diff --git a/gui/gui.pro b/gui/gui.pro index 8706e86c86e..37c70471b9a 100644 --- a/gui/gui.pro +++ b/gui/gui.pro @@ -58,7 +58,6 @@ win32 { RESOURCES = gui.qrc FORMS = about.ui \ applicationdialog.ui \ - compliancereportdialog.ui \ fileview.ui \ helpdialog.ui \ mainwindow.ui \ @@ -138,7 +137,6 @@ HEADERS += aboutdialog.h \ codeeditstyledialog.h \ codeeditor.h \ common.h \ - compliancereportdialog.h \ csvreport.h \ erroritem.h \ filelist.h \ @@ -181,7 +179,6 @@ SOURCES += aboutdialog.cpp \ codeeditstyledialog.cpp \ codeeditor.cpp \ common.cpp \ - compliancereportdialog.cpp \ csvreport.cpp \ erroritem.cpp \ filelist.cpp \ diff --git a/gui/gui.qrc b/gui/gui.qrc index c40e1ba7a1c..e53ce8ad7d4 100644 --- a/gui/gui.qrc +++ b/gui/gui.qrc @@ -2,31 +2,67 @@ cppcheck-gui.png images/dialog-error.png + images/dialog-error@2x.png + images/dialog-error@3x.png images/dialog-information.png + images/dialog-information@2x.png + images/dialog-information@3x.png images/dialog-warning.png + images/dialog-warning@2x.png + images/dialog-warning@3x.png images/edit-clear.png + images/edit-clear@2x.png + images/edit-clear@3x.png images/go-down.png + images/go-down@2x.png + images/go-down@3x.png images/help-browser.png + images/help-browser@2x.png + images/help-browser@3x.png images/media-floppy.png + images/media-floppy@2x.png + images/media-floppy@3x.png images/preferences-system.png + images/preferences-system@2x.png + images/preferences-system@3x.png images/process-stop.png + images/process-stop@2x.png + images/process-stop@3x.png images/text-x-generic.png + images/text-x-generic@2x.png + images/text-x-generic@3x.png images/view-recheck.png - images/view-refresh.png - images/showerrors.png - images/showstylewarnings.png - images/openproject.png - images/scratchpad.png - images/showwarnings.png - images/showperformance.png + images/view-recheck@2x.png + images/view-recheck@3x.png + images/refresh-file.png + images/refresh-file@2x.png + images/refresh-file@3x.png + images/folder.png + images/folder@2x.png + images/folder@3x.png + images/accessories-text-editor.png + images/accessories-text-editor@2x.png + images/accessories-text-editor@3x.png images/utilities-system-monitor.png + images/utilities-system-monitor@2x.png + images/utilities-system-monitor@3x.png ../COPYING ../AUTHORS images/go-home.png + images/go-home@2x.png + images/go-home@3x.png images/go-next.png + images/go-next@2x.png + images/go-next@3x.png images/go-previous.png + images/go-previous@2x.png + images/go-previous@3x.png images/applications-development.png + images/applications-development@2x.png + images/applications-development@3x.png images/applications-system.png + images/applications-system@2x.png + images/applications-system@3x.png images/llvm-dragon.png images/verify.svg diff --git a/gui/help/commandline.html b/gui/help/commandline.html new file mode 100644 index 00000000000..d66d7fb598b --- /dev/null +++ b/gui/help/commandline.html @@ -0,0 +1,63 @@ + + +Command line options + + + + +

Command line options

+ +

The GUI can be started from a terminal or a shortcut with options:

+ +
cppcheck-gui [OPTIONS] [files or paths]
+ + + + + + + + + + +
OptionDescription
-p <file>Open the project file and analyze + it. If the project has a build dir with earlier results, these are shown + instead; click Reanalyze all files to analyze again.
-l <file>Open a results file in XML + format.
-d <directory>Used with -l: + the folder that was analyzed to create the results file. Relative paths in + the results file are relative to this folder. It is needed to show the + source code of the warnings.
-h, --helpShow a short description + of the options.
-v, --versionShow the version of + the GUI.
--data-dir=<directory>Meant for + installation scripts. Stores the folder where Cppcheck's data files + (translations, library configurations, platforms, addons) are installed. + The GUI is not started when this option is used.
files or pathsAnalyze these files and folders without + a project. If one of the arguments is a project file + (*.cppcheck), it is opened as with -p. If one of + them is an XML file, it is opened as a results file.
+ +

Examples

+ +

Open a project:

+
cppcheck-gui -p myproject.cppcheck
+

or simply:

+
cppcheck-gui myproject.cppcheck
+ +

Look at the results of a CI job. The job ran Cppcheck in the folder +/home/me/src/myproject and saved the results:

+
cppcheck-gui -l results.xml -d /home/me/src/myproject
+ +

Analyze a folder without a project:

+
cppcheck-gui src/
+ +

Starting without options

+ +

When the GUI is started without options, it opens the project that was open +when it was closed last time, and shows the results of the last analysis if +the project has a build dir.

+ + + + + diff --git a/gui/help/images/add-application.png b/gui/help/images/add-application.png new file mode 100644 index 00000000000..68e4b422ace Binary files /dev/null and b/gui/help/images/add-application.png differ diff --git a/gui/help/images/context-menu-tag.png b/gui/help/images/context-menu-tag.png new file mode 100644 index 00000000000..69c510f3d48 Binary files /dev/null and b/gui/help/images/context-menu-tag.png differ diff --git a/gui/help/images/context-menu.png b/gui/help/images/context-menu.png new file mode 100644 index 00000000000..6afa8f80542 Binary files /dev/null and b/gui/help/images/context-menu.png differ diff --git a/gui/help/images/index-mainwindow.png b/gui/help/images/index-mainwindow.png deleted file mode 100644 index aa67a5e836d..00000000000 Binary files a/gui/help/images/index-mainwindow.png and /dev/null differ diff --git a/gui/help/images/library-editor.png b/gui/help/images/library-editor.png new file mode 100644 index 00000000000..5bdf7db02ba Binary files /dev/null and b/gui/help/images/library-editor.png differ diff --git a/gui/help/images/main-window.png b/gui/help/images/main-window.png new file mode 100644 index 00000000000..e678199f2e0 Binary files /dev/null and b/gui/help/images/main-window.png differ diff --git a/gui/help/images/menu-analyze.png b/gui/help/images/menu-analyze.png new file mode 100644 index 00000000000..6aa110f8dd4 Binary files /dev/null and b/gui/help/images/menu-analyze.png differ diff --git a/gui/help/images/menu-edit.png b/gui/help/images/menu-edit.png new file mode 100644 index 00000000000..18b587ceb44 Binary files /dev/null and b/gui/help/images/menu-edit.png differ diff --git a/gui/help/images/menu-file.png b/gui/help/images/menu-file.png new file mode 100644 index 00000000000..4b7a3fcfd9f Binary files /dev/null and b/gui/help/images/menu-file.png differ diff --git a/gui/help/images/menu-help.png b/gui/help/images/menu-help.png new file mode 100644 index 00000000000..a76d218063b Binary files /dev/null and b/gui/help/images/menu-help.png differ diff --git a/gui/help/images/menu-view-report.png b/gui/help/images/menu-view-report.png new file mode 100644 index 00000000000..65050ce279c Binary files /dev/null and b/gui/help/images/menu-view-report.png differ diff --git a/gui/help/images/menu-view.png b/gui/help/images/menu-view.png new file mode 100644 index 00000000000..40d66eaf972 Binary files /dev/null and b/gui/help/images/menu-view.png differ diff --git a/gui/help/images/new-suppression.png b/gui/help/images/new-suppression.png new file mode 100644 index 00000000000..13c615172e4 Binary files /dev/null and b/gui/help/images/new-suppression.png differ diff --git a/gui/help/images/preferences-addons.png b/gui/help/images/preferences-addons.png new file mode 100644 index 00000000000..70eb0642256 Binary files /dev/null and b/gui/help/images/preferences-addons.png differ diff --git a/gui/help/images/preferences-applications.png b/gui/help/images/preferences-applications.png new file mode 100644 index 00000000000..41a94dcda32 Binary files /dev/null and b/gui/help/images/preferences-applications.png differ diff --git a/gui/help/images/preferences-clang.png b/gui/help/images/preferences-clang.png new file mode 100644 index 00000000000..bb00ea2d31c Binary files /dev/null and b/gui/help/images/preferences-clang.png differ diff --git a/gui/help/images/preferences-code-editor.png b/gui/help/images/preferences-code-editor.png new file mode 100644 index 00000000000..5edb3658d6d Binary files /dev/null and b/gui/help/images/preferences-code-editor.png differ diff --git a/gui/help/images/preferences-general.png b/gui/help/images/preferences-general.png new file mode 100644 index 00000000000..98f56728201 Binary files /dev/null and b/gui/help/images/preferences-general.png differ diff --git a/gui/help/images/preferences-language.png b/gui/help/images/preferences-language.png new file mode 100644 index 00000000000..32aa867fcb2 Binary files /dev/null and b/gui/help/images/preferences-language.png differ diff --git a/gui/help/images/preferences-reports.png b/gui/help/images/preferences-reports.png new file mode 100644 index 00000000000..d18a150ac5a Binary files /dev/null and b/gui/help/images/preferences-reports.png differ diff --git a/gui/help/images/projectfile-addons.png b/gui/help/images/projectfile-addons.png new file mode 100644 index 00000000000..9aa09829fbb Binary files /dev/null and b/gui/help/images/projectfile-addons.png differ diff --git a/gui/help/images/projectfile-analysis.png b/gui/help/images/projectfile-analysis.png new file mode 100644 index 00000000000..1d846829455 Binary files /dev/null and b/gui/help/images/projectfile-analysis.png differ diff --git a/gui/help/images/projectfile-import.png b/gui/help/images/projectfile-import.png new file mode 100644 index 00000000000..ae487d9bf3c Binary files /dev/null and b/gui/help/images/projectfile-import.png differ diff --git a/gui/help/images/projectfile-paths-and-defines.png b/gui/help/images/projectfile-paths-and-defines.png new file mode 100644 index 00000000000..a6ec3a4d2c8 Binary files /dev/null and b/gui/help/images/projectfile-paths-and-defines.png differ diff --git a/gui/help/images/projectfile-types-and-functions.png b/gui/help/images/projectfile-types-and-functions.png new file mode 100644 index 00000000000..4b2fb57f435 Binary files /dev/null and b/gui/help/images/projectfile-types-and-functions.png differ diff --git a/gui/help/images/projectfile-warning-options.png b/gui/help/images/projectfile-warning-options.png new file mode 100644 index 00000000000..779925309af Binary files /dev/null and b/gui/help/images/projectfile-warning-options.png differ diff --git a/gui/help/images/quick-filter.png b/gui/help/images/quick-filter.png new file mode 100644 index 00000000000..cefbe833e42 Binary files /dev/null and b/gui/help/images/quick-filter.png differ diff --git a/gui/help/images/report-misra.png b/gui/help/images/report-misra.png new file mode 100644 index 00000000000..30071346a44 Binary files /dev/null and b/gui/help/images/report-misra.png differ diff --git a/gui/help/images/scratchpad.png b/gui/help/images/scratchpad.png new file mode 100644 index 00000000000..ca1dd74ad3d Binary files /dev/null and b/gui/help/images/scratchpad.png differ diff --git a/gui/help/images/statistics-previous-scan.png b/gui/help/images/statistics-previous-scan.png new file mode 100644 index 00000000000..a2ec1c45157 Binary files /dev/null and b/gui/help/images/statistics-previous-scan.png differ diff --git a/gui/help/images/statistics-project.png b/gui/help/images/statistics-project.png new file mode 100644 index 00000000000..5e909732bf4 Binary files /dev/null and b/gui/help/images/statistics-project.png differ diff --git a/gui/help/images/statistics-statistics.png b/gui/help/images/statistics-statistics.png new file mode 100644 index 00000000000..1bbaddbe18f Binary files /dev/null and b/gui/help/images/statistics-statistics.png differ diff --git a/gui/help/images/toolbar.png b/gui/help/images/toolbar.png new file mode 100644 index 00000000000..41664a4edb2 Binary files /dev/null and b/gui/help/images/toolbar.png differ diff --git a/gui/help/images/walkthrough-analysis.png b/gui/help/images/walkthrough-analysis.png deleted file mode 100644 index 94efb2890d8..00000000000 Binary files a/gui/help/images/walkthrough-analysis.png and /dev/null differ diff --git a/gui/help/images/walkthrough-import-project.png b/gui/help/images/walkthrough-import-project.png deleted file mode 100644 index 44b18e7f6b2..00000000000 Binary files a/gui/help/images/walkthrough-import-project.png and /dev/null differ diff --git a/gui/help/images/walkthrough-library.png b/gui/help/images/walkthrough-library.png deleted file mode 100644 index fbb5ae14d09..00000000000 Binary files a/gui/help/images/walkthrough-library.png and /dev/null differ diff --git a/gui/help/images/walkthrough-new-project.png b/gui/help/images/walkthrough-new-project.png deleted file mode 100644 index 55bf7ce2f4f..00000000000 Binary files a/gui/help/images/walkthrough-new-project.png and /dev/null differ diff --git a/gui/help/images/walkthrough-toolbar-severities.png b/gui/help/images/walkthrough-toolbar-severities.png deleted file mode 100644 index f06e3aa41cf..00000000000 Binary files a/gui/help/images/walkthrough-toolbar-severities.png and /dev/null differ diff --git a/gui/help/images/walkthrough-warning-menu.png b/gui/help/images/walkthrough-warning-menu.png deleted file mode 100644 index 0154b5ccbc9..00000000000 Binary files a/gui/help/images/walkthrough-warning-menu.png and /dev/null differ diff --git a/gui/help/index.html b/gui/help/index.html index 4d2cd43f824..6272ec01151 100644 --- a/gui/help/index.html +++ b/gui/help/index.html @@ -1,21 +1,103 @@ Cppcheck GUI +

Cppcheck GUI

-

With the Cppcheck GUI you can analyze your code.

+

Cppcheck is a static analysis tool for C and C++ code. It looks for bugs +such as undefined behavior, memory leaks, buffer overruns, null pointer +dereferences and uninitialized variables, and it points out code that is +suspicious, slow or hard to maintain. Cppcheck tries hard to avoid false +positives: when it reports something there is usually a reason to look at +it.

+ +

The Cppcheck GUI is a graphical front end for Cppcheck. With the GUI you +can:

+
    +
  • create and edit project files that describe what code to analyze and how + to analyze it,
  • +
  • import Visual Studio solutions and projects, compile databases + (compile_commands.json) and Borland C++ Builder 6 projects,
  • +
  • run the analysis in several threads and watch the results come in,
  • +
  • browse the warnings, read the source code next to them and follow the + path that leads to each bug,
  • +
  • filter, hide, suppress and tag warnings,
  • +
  • open the code in your favorite editor,
  • +
  • save results as XML, text or CSV and print them,
  • +
  • try out small pieces of code in the scratchpad,
  • +
  • write configuration files for your own libraries.
  • +
+ +

Contents

-

If you want to get started, look at the walk through.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PageWhat you will find there
Getting startedA step by step walk through: create a project, run the analysis and + look at the first results. Start here if you are new to Cppcheck.
The main windowAll menus, toolbars and panels of the main window.
Analyzing without a projectQuick analysis of files and folders, and the settings in the + Analyze menu.
Project filesEvery option in the project file dialog explained in detail.
Investigating warningsHow to read the results list, the warning details and the code + view, and how to decide if a warning is a real bug.
Severities and report typesWhat error, warning, style, performance, portability and information + mean, and how the compliance report types classify warnings.
Hiding and suppressing warningsTemporary hiding, permanent suppressions in the project file, and + inline suppressions in the code.
Tagging warningsCategorize warnings with your own tags.
Results files, printing and statisticsSave and open results, print reports and view statistics.
Scratchpad, library editor and other toolsTools for trying out code and for describing your own libraries.
PreferencesEvery option in the preferences dialog.
Command line optionsHow to start the GUI with a project or a results file.
TroubleshootingAnswers to common questions and problems.
+ +

Projects or quick analysis?

+ +

There are two ways to use the GUI:

+
    +
  • With a project file (recommended). A project file + (*.cppcheck) stores where your code is, the include paths, + the preprocessor defines, the libraries you use, suppressions, tags and + much more. The analysis is more accurate when Cppcheck knows these + things, and you only configure them once. See + Getting started and + Project files.
  • +
  • Without a project file. Use Analyze > Files... or + Analyze > Directory... to analyze some code quickly. See + Analyzing without a project.
  • +
-

Example

+

More information

-
+

The Cppcheck manual describes the analysis itself in more detail, including +the command line tool, the configuration file format and addons. You can find +it at https://cppcheck.sourceforge.io/manual.pdf.

+ +

The Cppcheck home page is +https://cppcheck.sourceforge.io/. +There you can download new versions, read about the checks and report +problems.

+ + - diff --git a/gui/help/investigating-warnings.html b/gui/help/investigating-warnings.html index 5eec66bd054..d1275a8ba01 100644 --- a/gui/help/investigating-warnings.html +++ b/gui/help/investigating-warnings.html @@ -1,22 +1,183 @@ Investigating warnings +

Investigating warnings

-

When you have run the analysis it is time to look at the results.

+

When the analysis is done (or while it is still running) it is time to look +at the results. This page explains how to read a warning, how to decide if it +is a real bug, and what you can do with it.

-

If you click on a warning then the corresponding code will be shown in the -"Warning details" at the bottom.

+

Reading a warning

-

You can right click warnings to get options. The difference of -"hiding" a warning and "suppressing" a warning is that -the suppression is permanent and hiding the warning is only temporary. When -suppressing warning(s), that is saved in the project file. +

+

Click a warning in the results list. The lower part of the window then +shows:

+
    +
  • the id of the warning, for example arrayIndexOutOfBounds. + The id identifies the kind of problem. Search for it in the Cppcheck + documentation or on the internet to learn more about it,
  • +
  • the CWE number, if there is one. CWE (Common Weakness Enumeration) + is a standard list of software weaknesses. Look it up at + cwe.mitre.org for a general + description of the problem,
  • +
  • the full message. It can be longer than the summary in the list,
  • +
  • the source code with the line of the warning highlighted.
  • +
+ +

In the example above Cppcheck says that buf[16] is accessed at +index 16. The array has 16 elements, so the valid indexes are 0 to 15. The loop +condition i <= BUFFER_SIZE should be +i < BUFFER_SIZE.

+ +

Notes: the path to the bug

+ +

Many warnings have notes. Click the arrow in front of the warning to see +them. Notes show the steps that lead to the problem, in order, for +example:

+
Assuming that condition 'i<=16' is not redundant   (line 9)
+Array index out of bounds                         (line 10)
+ +

Click a note to show its line in the code view. For a bug that depends on +values, the notes usually tell you where the value comes from, which +conditions Cppcheck assumes, and where the bad value is used. Follow the notes +to understand the warning.

+ +

A note like Assuming that condition 'x' is not redundant means that +Cppcheck uses the condition in your code as a hint about possible values. For +example, if the code checks if (p == NULL) after it has already +used p, then either the check is not needed or the code can use a +null pointer. Cppcheck warns because one of them must be wrong.

+ +

Is it a real bug?

+ +

Cppcheck tries to avoid false warnings, but no static analysis tool is +perfect. To decide if a warning is right:

+
    +
  1. Read the message and the notes, and look at the code they point to.
  2. +
  3. Think about the values Cppcheck mentions. Can they really happen? Look at + the callers of the function if needed.
  4. +
  5. Check if Cppcheck may be missing information. Warnings in code that uses + macros or types that Cppcheck could not see, because an include path is + missing, can be wrong. Look for missingInclude information + messages and fix the include paths in the project.
  6. +
  7. Check if a library configuration is missing. If your code calls a + function that never returns (like a fatal error handler) and Cppcheck + does not know that, it may warn about code after the call. Select the + library in the project or write a configuration for your own functions. + See Libraries.
  8. +
+ +

Even if a warning is not a bug today, it can point to code that is hard to +understand or easy to break. Many developers prefer to change the code so that +the warning goes away, instead of suppressing it.

+ +

If you are sure Cppcheck is wrong, suppress the warning (see +Hiding and suppressing warnings). It is also +very helpful if you report the false warning, with a small code example, at +https://trac.cppcheck.net/ or in the +Cppcheck forum.

+ +

Inconclusive warnings

+ +

Sometimes Cppcheck finds something suspicious but can not be sure that it +is a bug. These inconclusive warnings are not shown by default. Enable +Check for inconclusive errors also in the +preferences to see them. An extra +Inconclusive column then marks them. Expect more false warnings among +them.

+ +

Where to start

+ +

The first analysis of a large project can give many warnings. Some +tips:

+
    +
  • Start with errors, then warnings. Use the + severity buttons in the toolbar to hide the + other severities.
  • +
  • Look at the information messages early. They are not bugs, but they + tell you about problems in the configuration, such as headers that can + not be found. Fixing them makes the other results better.
  • +
  • Sort by the Id column to handle one kind of warning at a time. If + one kind of warning is wrong everywhere, it may be a configuration + problem.
  • +
  • Use the quick filter to find warnings about a function, a variable or a + file.
  • +
  • With a build dir, sort by Since date to see the newest warnings + first. New warnings are the easiest to fix, because the code is fresh in + your mind.
  • +
  • Use tags to remember what you have looked + at.
  • +
+ +

Opening the code in an editor

+ +

Double-click a warning, or select it and press Enter, to open the file in +your default editor at the line of the warning. You can also right-click a +warning and choose an editor at the top of the context menu.

+ +

Editors are configured in the +preferences on the Applications tab. On some systems the GUI finds +installed editors automatically.

+ +

If the file is not found, for example because the results were loaded from +an XML file created on another computer, the GUI asks you to select the +folder where the file is.

+ + +

The context menu

+ +

Right-click one or more selected warnings to get the context menu. Select +several warnings with Ctrl+click or Shift+click.

+ +

+ + + + + + + + + + + +
CommandDescription
(editors)Open the file of the warning in this editor.
Recheck N file(s)Analyze the selected files again. Select + file items (the top level items in the list) to enable this. Useful after + you have fixed something in a file. Not available while an analysis is + running or for results loaded from an XML file.
CopyCopy the selected warnings, notes or file names to the + clipboard as text, in the format + file:line:column:severity: summary [id], followed by one line + for each note. Paste it in a bug report or a chat.
Hide N result(s)Hide the selected warnings until the next + analysis. See Hiding warnings.
Hide all with idHide all warnings with the same id as the + selected warning, until the next analysis.
Suppress selected id(s)Suppress all warnings with the ids of + the selected warnings, in all files. The suppression is saved in the + project file, so the warnings are not reported again. See + Hiding and suppressing warnings.
Open containing folderOpen the folder of the file in your + file manager.
TagGive the selected warnings a tag. Only shown if the + project has tags. See Tagging warnings.
+ +

Analyzing again

+ +

After you have changed the code:

+
    +
  • Right-click a file in the results list and choose Recheck to + analyze just that file again.
  • +
  • Use Reanalyze all files in the toolbar to analyze everything again. + With a build dir only changed files are analyzed again, so this is + fast.
  • +
  • Use Analyze > Reanalyze modified files (Ctrl+R) to analyze only + the files that changed since the last analysis. See + The main window for when this is + available.
  • +
+ + - diff --git a/gui/help/mainwindow.html b/gui/help/mainwindow.html new file mode 100644 index 00000000000..d12fe51c787 --- /dev/null +++ b/gui/help/mainwindow.html @@ -0,0 +1,293 @@ + + +The main window + + + + +

The main window

+ +

This page describes the parts of the main window and every command in the +menus and toolbars.

+ +

+ +

From top to bottom the main window has:

+
    +
  • the menu bar,
  • +
  • the toolbars: the standard toolbar, the severity toolbar and the + quick filter,
  • +
  • the progress bar, which is shown while an analysis is running,
  • +
  • the results list with all warnings,
  • +
  • the lower panel with two tabs: Warning Details (the message + and the source code of the selected warning) and Analysis Log.
  • +
+ +

The title bar shows the name of the project file that is open. You can drag +the bar between the results list and the lower panel to give more space to +either of them. The GUI remembers the window size, the column widths and the +position of the bar.

+ +

The results list

+ +

The results are grouped by file. Each file is a top level item; click the +arrow to expand it, or use View > Expand all. Each warning in a file +can have child items called notes, which describe the path through the +code that leads to the problem.

+ +

The columns are:

+ + + + + + + + + + + + + +
ColumnMeaning
FileThe file of the warning. By default the path is shown + relative to the project (see Show full path of files in the + preferences and Root path in the + project file).
LineThe line number.
Severityerror, warning, style, performance, portability, + information, or note for the notes. See + Severities. The icon in the File column + shows the severity too.
Classification, Level, Guideline, RuleOnly shown when a + compliance report type (Misra, Cert, Autosar) is selected in + View > Report. See Report + types.
InconclusiveShown when inconclusive warnings are enabled in + the preferences. Marks warnings where Cppcheck is not sure.
SummaryA short description of the problem.
IdThe id of the checker message, for example + nullPointer. You need it for suppressions. The column can be + turned off in the preferences.
Since dateThe date the warning was first seen. Only + available for projects with a build dir; the date is kept from analysis + to analysis as long as the warning is still there. This makes it easy to + see which warnings are new.
TagsYour own tag for the warning. See + Tagging.
CWEThe Common Weakness + Enumeration number of the problem, if there is one.
+ +

Click a column header to sort by that column. Drag the edge of a column +header to change its width.

+ +

Double-click a warning, or select it and press Enter, to open the file in +your default editor at the line of the warning. You must configure the editor +first in the preferences.

+ +

Right-click warnings to get a context menu. See +The context menu.

+ +

Warning Details and the code view

+ +

When you select a warning, the Warning Details tab shows the id, the +CWE number and the full message. Some messages are longer than the summary in +the list and explain the problem in more detail. If the file was included from +another file, it also shows which file included it.

+ +

Below the message, the code view shows the source file with the line of the +warning highlighted. Select a note to see the line of the note. The code view +is read only; use your editor to change the code. The colors of the code view +can be changed in the preferences.

+ +

Analysis Log

+ +

The Analysis Log tab shows which files were analyzed and with which +configuration: the preprocessor defines, undefines, include paths and +platform. Look here if the results are not what you expected, for example to +check that the right include paths are used. Right-click the log to clear it +or copy one or all entries.

+ +

If Show internal warnings in log is enabled in the preferences, +internal debug warnings are shown here too.

+ +

If a critical error happens, for example a syntax error that stops the +analysis of a file, a red message is shown above the results list.

+ +

Toolbars

+ +

+ +

The standard toolbar (left) has, from left to right: Open Project +File, Save results to file, Reanalyze all files, +Stop, Clear results, Preferences and About. These +commands are described with the menus below.

+ +

The severity toolbar (right) has one toggle button for each +severity: error, warning, style, portability, performance and information. +When a button is pressed, warnings with that severity are shown. Release it to +hide them. The last two buttons show or hide results from Cppcheck itself and +results from Clang tools (Clang-Tidy). When you select a compliance report +type, the buttons change to the classifications of that standard, for example +Mandatory, Required and Advisory for Misra.

+ +

The quick filter field shows only warnings that contain the text you +type. The text is searched (ignoring case) in the summary, the message, the id, +the classification and the files and texts of the notes. Click the small cross +in the field to clear the filter.

+ +

+ +

The toolbars can be turned on and off with View > Toolbars.

+ +

The File menu

+ +

+ + + + + + + + + + + + +
CommandDescription
Open XML...Open a results file in XML format that was saved + earlier by the GUI or created with the command line tool + (cppcheck --xml ...). The current results are cleared. See + Opening results.
New Project File...Create a new project file. First you + choose the file name, then the project + file dialog opens. When you click OK the analysis starts.
Open Project File...Open an existing project file + (*.cppcheck). If the project has a build dir with results + from an earlier analysis, those results are shown. Otherwise the analysis + starts.
Edit Project File...Change the settings of the open project. + When you click OK, the project file is saved and the code is analyzed + again.
Close Project FileClose the project and clear the results. + You must close the project before you can analyze files or folders + without a project.
(recent projects)The project files you opened recently. + Click one to open it.
Save results to file...Save the results as XML, text or CSV. + See Saving results.
Print Preview... / Print...Preview or print the results. + See Printing.
QuitClose the GUI. If an analysis is running, you are asked + if you want to stop it.
+ +

The Edit menu

+ +

+ + + + + +
CommandDescription
Clear resultsRemove all results from the list. If the + project has a build dir, the cached analysis results in the build dir are + deleted too, so the next analysis analyzes every file from scratch.
PreferencesOpen the preferences + dialog.
+ +

The View menu

+ +

+ + + + + + + + + + + + + +
CommandDescription
ToolbarsShow or hide the standard toolbar, the severity + toolbar (Categories) and the quick filter.
ReportChoose how warnings are classified: Normal + (Cppcheck severities) or according to a coding standard: Autosar, Cert C, + Cert C++, Misra C, Misra C++ 2008 or Misra C++ 2023. See + Report types.
Show errors ... Show informationThe same as the buttons in + the severity toolbar.
Check all / Uncheck allShow or hide all severities at + once.
Collapse all / Expand allCollapse or expand all files and + warnings in the results list.
Show hiddenShow the warnings again that you have hidden with + the context menu. Only enabled when there are hidden warnings.
Show Scratchpad...Open the + scratchpad, where you can type or paste code and analyze it.
StatisticsShow statistics + about the project and the last analysis.
Library Editor...Open the + library editor to create or change a library configuration (.cfg) + file.
Thread DetailsShow what each analysis thread is doing right + now. See Thread details.
+ +

The Analyze menu

+ +

+ + + + + + + + + + + + + +
CommandDescription
Files...Analyze one or more files without a project. You can + also select a compile database, a Visual Studio solution or project, or a + Borland C++ Builder 6 project here. See + Analyzing without a project.
Directory...Analyze all source files in a folder and its + subfolders, without a project.
Reanalyze modified filesAnalyze again only the files that + changed since the last analysis in this session, including files that + include a changed header. This works for files and folders you selected + with Files... or Directory... and for projects that list + their paths in the project file. For projects that import a compile + database or a Visual Studio project, use Reanalyze all files + instead.
Reanalyze all filesAnalyze all files again. For a project with + a build dir, unchanged files are not analyzed again; their cached results + are used.
Reanalyze and check libraryAnalyze the project again and + report functions and types for which Cppcheck has no configuration. Use it + to find out what is missing in your library configurations. See + Checking the library + configuration.
Check configuration (defines, includes)Analyze the project + again and report problems in the configuration, such as include files + that can not be found. Use it when you set up a new project.
StopStop the running analysis (Esc). Results found so far are + kept.
C standard / C++ standardThe language standard used for + files when the standard is not known in another way (for example from an + imported project).
Auto-detect language / Enforce C++ / Enforce CBy default the + language of each file is chosen from its file extension: .c + files are C and .cpp, .cc, .cxx and + similar are C++. Choose Enforce C++ or Enforce C to analyze + all files as that language, for example if your C++ code is in + .c files.
Native ... Windows 64-bitThe platform to use when the project + does not choose a platform, or when you analyze without a project. + Native is the platform you run Cppcheck on. See + Platform.
+ +

The Help menu

+ +

+ + + + + + + +
CommandDescription
ContentsOpen this help (F1).
License...Show the license of Cppcheck (GNU GPL v3).
Authors...Show the people who have contributed to + Cppcheck.
About...Show the version of Cppcheck.
+ +

Cppcheck Premium also shows EULA... in this menu.

+ +

Keyboard shortcuts

+ + + + + + + + + + + + + + +
ShortcutCommand
Ctrl+Shift+NNew Project File
Ctrl+Shift+OOpen Project File
Ctrl+SSave results to file
Ctrl+PPrint
Ctrl+QQuit
Ctrl+FAnalyze files
Ctrl+DAnalyze directory
Ctrl+RReanalyze modified files
EscStop the analysis
F1Help
EnterOpen the selected warning in the default editor
+ + + + + diff --git a/gui/help/manual.html b/gui/help/manual.html deleted file mode 100644 index a237cb8485b..00000000000 --- a/gui/help/manual.html +++ /dev/null @@ -1,947 +0,0 @@ - -Cppcheck 1.46

Chapter 1. Introduction

Cppcheck is an analysis tool for C/C++ code. Unlike C/C++ compilers - and many other analysis tools, it doesn't detect syntax errors. Cppcheck - only detects the types of bugs that the compilers normally fail to detect. - The goal is no false positives.

Supported code and platforms:

  • You can check non-standard code that includes various compiler - extensions, inline assembly code, etc.

  • Cppcheck should be compilable by any C++ compiler that handles - the latest C++ standard.

  • Cppcheck should work on any platform that has sufficient cpu and - memory.

Accuracy

Please understand that there are limits of Cppcheck. Cppcheck is - rarely wrong about reported errors. But there are many bugs that it - doesn't detect.

You will find more bugs in your software by testing your software - carefully, than by using Cppcheck. You will find more bugs in your - software by instrumenting your software, than by using Cppcheck. But - Cppcheck can still detect some of the bugs that you miss when testing and - instrumenting your software.


Chapter 2. Getting started

2.1. First test

Here is a simple code

int main()
-{
-    char a[10];
-    a[10] = 0;
-    return 0;
-}

If you save that into file1.c and - execute:

cppcheck file1.c

The output from cppcheck will then be:

Checking file1.c...
-[file1.c:4]: (error) Array 'a[10]' index 10 out of bounds

2.2. Checking all files in a folder

Normally a program has many sourcefiles. And you want to check - them all. Cppcheck can check all sourcefiles in a directory:

cppcheck path

If "path" is a folder then cppcheck will check all sourcefiles in - this folder.

Checking path/file1.cpp...
-1/2 files checked 50% done
-Checking path/file2.cpp...
-2/2 files checked 100% done

2.3. Excluding a file or folder from checking

There is no command to exclude a file or folder from checking. But - you can exclude a file or folder by being more careful when including - files and folders in the checking.

Imagine for example that the folder "src" contain the folders "a", - "b" and "c". To exclude "c" this command can be used:

cppcheck src/a src/b

All files under "src/a" and "src/b" are then checked.

The flag --file-list might also be - useful.


2.4. Severities

The possible severities for messages are:

error

used when bugs are found

warning

suggestions about defensive programming to prevent - bugs

style

stylistic issues related to code cleanup (unused functions, - redundant code, constness, and such)

performance

suggestions for making the code faster


2.5. Enable messages

By default only error messages are shown. - Through the --enable command more checks can be - enabled.


2.5.1. Stylistic issues

With --enable=style you enable most - warning, style and - performance messages.

Here is a simple code example:

void f(int x)
-{
-    int i;
-    if (x == 0)
-    {
-        i = 0;
-    }
-}

There are no bugs in that code so Cppcheck won't report anything - by default. To enable the stylistic messages, use the --enable=style - command:

cppcheck --enable=style file3.c

The output from Cppcheck is now:

Checking file3.c...
-[file3.c:3]: (style) Variable 'i' is assigned a value that is never used
-[file3.c:3]: (style) The scope of the variable i can be reduced


2.5.2. Unused functions

This check will try to find unused functions. It is best to use - this when the whole program is checked, so that all usages is seen by - cppcheck.

cppcheck --enable=unusedFunction path

2.5.3. Enable all checks

To enable all checks your can use the - --enable=all flag:

cppcheck --enable=all path

2.6. Saving results in file

Many times you will want to save the results in a file. You can - use the normal shell redirection for piping error output to a - file.

cppcheck file1.c 2> err.txt

2.7. Multithreaded checking

To use 4 threads to check the files in a folder:

cppcheck -j 4 path

Chapter 3. Preprocessor configurations

By default Cppcheck will check all preprocessor configurations - (except those that have #error in them). This is the recommended - behaviour.

But if you want to manually limit the checking you can do so with - -D.

Beware that only the macros, which are given here and the macros - defined in source files and known header files are considered. That - excludes all the macros defined in some system header files, which are by - default not examined by cppcheck.

The usage: if you, for example, want to limit the checking so the - only configuration to check should be "DEBUG=1;__cplusplus" then something - like this can be used:

cppcheck -DDEBUG=1 -D__cplusplus path

Chapter 4. XML output

Cppcheck can generate the output in XML format.

Use the --xml flag when you execute cppcheck:

cppcheck --xml file1.cpp

The xml format is:

<?xml version="1.0"?>
-<results>
-  <error file="file1.cpp" line="123" id="someError"
-               severity="error" msg="some error text"/>
-</results>

Attributes:

file

filename. Both relative and absolute paths are possible

line

a number

id

id of error. These are always valid symbolnames.

severity

either error or style. - warning and performance are - saved as style.

msg

the error message


Chapter 5. Reformatting the output

If you want to reformat the output so it looks different you can use - templates.

To get Visual Studio compatible output you can use "--template - vs":

cppcheck --template vs gui/test.cpp

This output will look like this:

Checking gui/test.cpp...
-gui/test.cpp(31): error: Memory leak: b
-gui/test.cpp(16): error: Mismatching allocation and deallocation: k

To get gcc compatible output you can use "--template gcc":

cppcheck --template gcc gui/test.cpp

The output will look like this:

Checking gui/test.cpp...
-gui/test.cpp:31: error: Memory leak: b
-gui/test.cpp:16: error: Mismatching allocation and deallocation: k

You can write your own pattern (for example a comma-separated - format):

cppcheck --template "{file},{line},{severity},{id},{message}" gui/test.cpp

The output will look like this:

Checking gui/test.cpp...
-gui/test.cpp,31,error,memleak,Memory leak: b
-gui/test.cpp,16,error,mismatchAllocDealloc,Mismatching allocation and deallocation: k


Chapter 6. Suppressions

If you want to filter out certain errors you can suppress these. - First you need to create a suppressions file. The format is:

[error id]:[filename]:[line]
-[error id]:[filename2]
-[error id]

The error id is the id that you want to suppress. - The easiest way to get it is to use the --xml command - line flag. Copy and paste the id string from the xml - output.

Here is an example:

memleak:file1.cpp
-exceptNew:file1.cpp
-uninitvar

You can then use the suppressions file:

cppcheck --suppressions suppressions.txt src/

-

Chapter 7. Exception safety

Cppcheck has a few checks that ensure that you don't break the basic - guarantee of exception safety. It doesn't have any checks for the strong - guarantee yet.

Example:

Fred::Fred() : a(new int[20]), b(new int[20])
-{
-}

By default cppcheck will not detect any problems in that - code.

To enable the exception safety checking you can use - --enable:

cppcheck --enable=exceptNew --enable=exceptRealloc fred.cpp

The output will be:

[fred.cpp:3]: (style) Upon exception there is memory leak: a

If an exception occurs when b is allocated, - a will leak.

Here is another example:

int *p;
-
-int a(int sz)
-{
-    delete [] p;
-    if (sz <= 0)
-        throw std::runtime_error("size <= 0");
-    p = new int[sz];
-}

Check that with Cppcheck:

cppcheck --enable=exceptNew --enable=exceptRealloc except2.cpp

The output from Cppcheck is:

[except2.cpp:7]: (error) Throwing exception in invalid state, p points at deallocated memory

Chapter 8. Html report

You can convert the xml output from cppcheck into a html report. - You'll need python and the pygments module - (http://pygments.org/) for this to work. In the Cppcheck source - tree there is a folder "htmlreport" that contains a script that transforms - a Cppcheck xml file into html output.

This command generates the help screen:

htmlreport/cppcheck-htmlreport -h

The output screen says:

Usage: cppcheck-htmlreport [options]
-
-Options:
-  -h, --help      show this help message and exit
-  --file=FILE     The cppcheck xml output file to read defects from.
-                  Default is reading from stdin.
-  --report-dir=REPORT_DIR
-                  The directory where the html report content is written.
-  --source-dir=SOURCE_DIR
-                  Base directory where source code files can be found.

An example usage:

./cppcheck gui/test.cpp --xml 2> err.xml
-htmlreport/cppcheck-htmlreport --file=err.xml --report-dir=test1 --source-dir=.

Chapter 9. Graphical user interface

9.1. Introduction

A Cppcheck GUI is available.

The main screen is shown immediately when the GUI is - started.


9.2. Check source code

Use the Check menu.


9.3. Inspecting results

The results are shown in a list.

You can show/hide certain types of messages through the - View menu.

Results can be saved to an xml file that can later be opened. See - Save results to file and Open - XML.


9.4. Settings

The language can be changed at any time by using the - Language menu.

More settings are available in - Edit>Preferences.


9.5. Project files

The project files are used to store project specific settings. - These settings are:

  • include folders

  • preprocessor defines

It isn't recommended to provide the paths to the standard C/C++ - headers - Cppcheck has internal knowledge about ANSI C/C++ and it isn't - recommended that this known functionality is redefined. But feel free to - try it.

As you can read in chapter 3 in this manual the default is that - Cppcheck checks all configurations. So only provide preprocessor defines - if you want to limit the checking.

diff --git a/gui/help/online-help.qhp b/gui/help/online-help.qhp index 23411d0f3b4..29f73530ca2 100644 --- a/gui/help/online-help.qhp +++ b/gui/help/online-help.qhp @@ -5,46 +5,136 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + style.css index.html - images/index-mainwindow.png - investigating-warnings.html - preferences.html + walkthrough.html + mainwindow.html + standalone-analysis.html projectfiledialog.html + investigating-warnings.html severities.html + suppressions.html + tagging.html + reports.html + tools.html + preferences.html + commandline.html + troubleshooting.html + images/add-application.png + images/context-menu-tag.png + images/context-menu.png + images/library-editor.png + images/main-window.png + images/menu-analyze.png + images/menu-edit.png + images/menu-file.png + images/menu-help.png + images/menu-view-report.png + images/menu-view.png + images/new-suppression.png + images/preferences-addons.png + images/preferences-applications.png + images/preferences-clang.png + images/preferences-code-editor.png + images/preferences-general.png + images/preferences-language.png + images/preferences-reports.png + images/projectfile-addons.png + images/projectfile-analysis.png + images/projectfile-import.png + images/projectfile-paths-and-defines.png + images/projectfile-types-and-functions.png + images/projectfile-warning-options.png + images/quick-filter.png + images/report-misra.png + images/scratchpad.png images/severities-error.png - images/severities-warning.png - images/severities-style.png + images/severities-information.png images/severities-performance.png images/severities-portability.png - images/severities-information.png - standalone-analysis.html - tagging.html - walkthrough.html - images/walkthrough-analysis.png - images/walkthrough-library.png - images/walkthrough-toolbar-severities.png - images/walkthrough-import-project.png - images/walkthrough-new-project.png - images/walkthrough-warning-menu.png + images/severities-style.png + images/severities-warning.png + images/statistics-previous-scan.png + images/statistics-project.png + images/statistics-statistics.png + images/toolbar.png diff --git a/gui/help/preferences.html b/gui/help/preferences.html index 5a591227b3c..4c298e59e14 100644 --- a/gui/help/preferences.html +++ b/gui/help/preferences.html @@ -1,74 +1,195 @@ Preferences +

Preferences

-

Number of threads
Number of threads to use in analysis. Each -thread checks its own source file.

- -

Force checking of all #ifdef configurations
Cppcheck try to check -all code and will therefore guess different preprocessor configurations. The -maximum number of configurations that is checked is 14 by default.

- -

Show full path of files
Show the full paths in the results.

- -

Show "No errors found" message when no errors found
-If you want to get a message box about this.

- -

Display error id column "Id"
Show error id in results

- -

Enable inline suppressions
You can suppress warnings with comments. -See the Cppcheck manual (https://cppcheck.sourceforge.io/manual.pdf) for more -information about inline suppressions.

- -

Check for inconclusive errors also
When full analysis of the code -can not determine if there should be a warning or not, it is inconclusive. -Normally Cppcheck does not warn then.

- -

Show statistics on check completion
Show statistics in a window -when analysis finish.

- -

Show internal warnings in log
Internal warnings (for debugging) is -shown in the Analysis log.

- -

Applications
Configure external editor to open from context menu -when you right click on a warning.

- -

Save all errors when creating report
If hidden warnings should be -saved or not.

- -

Save full path to files in report
If you use Root path the -warnings on the screen will not have the full path.

- -

Language
Configure language to use for GUI.

- -

Python binary
To be able to execute addons, Cppcheck needs to know -where python is. Unless you configure something, Cppcheck will try to execute -python in your PATH.

- -

MISRA rule texts
Only needed if you want to use the MISRA addon. -Cppcheck is not legally allowed to distribute the MISRA rule texts and these -must be provided by users. The MISRA rule texts are proprietary. An example -rule text file: -

Appendix A Summary of guidelines
-Rule 1.1
-Text of rule 1.1
-Rule 1.2
-Text of rule 1.2
-

- -

Clang path
The path to clang binary. If no path is provided -then system PATH is used.

- -

Visual studio headers
If you want to use the Visual Studio headers -in the analysis you can provide the path(s) here. Hint: Open a visual studio -command prompt and type SET INCLUDE. Then copy/paste the paths.

- -

Code editor style
The visual theme to use for the code editor that -is used when you investigate results.

+

Open the preferences with Edit > Preferences or the preferences +button in the toolbar. The preferences are your personal settings; they are +stored for your user account, not in the project file. Click OK to +apply the changes.

+ +

Some settings exist both here and in the project file, for example +Enable inline suppressions. When a project is open, the project +setting is used.

+ +

General

+ +

+ + + + + + + + + + + + + +
OptionDescription
Number of threadsHow many files are analyzed at the same time. + Each thread analyzes its own file. Max count shows the number of + processor cores of your computer, which is the highest value you can + enter. More threads make the analysis faster, but use more memory. A value + equal to the number of cores is usually a good choice.
Force checking all #ifdef configurationsBy default Cppcheck + analyzes at most 12 preprocessor configurations of each file. With this + option all configurations are analyzed. This finds more bugs in code with + many #ifdefs, but can be very slow. See + Defines.
Show full path of filesShow the full path of files in the + results list, instead of the path relative to the project.
Show "No errors found" message when no errors foundShow a + message box when an analysis finishes without warnings.
Display error Id in column "Id"Show the Id column in + the results list. The id is needed for suppressions.
Enable inline suppressionsAllow comments like + // cppcheck-suppress nullPointer in the code to suppress + warnings. Only used when no project is open; projects have their own + setting. See Inline + suppressions.
Check for inconclusive errors alsoAlso report warnings where + Cppcheck is not sure that there is a problem. These give more findings, but + also more false warnings. An Inconclusive column is added to the + results list.
Show statistics on check completionOpen the + statistics dialog when an analysis + finishes.
Check for updatesWhen the GUI starts, check on the Cppcheck + web site if there is a newer version. If there is, a message is shown at + the top of the main window. This needs an internet connection; nothing + else is sent.
Show internal warnings in logShow debug warnings from the + analysis in the Analysis Log. Only useful if you develop Cppcheck or + investigate a problem with the analysis.
+ + +

Applications

+ +

+ +

The editors (or other programs) that you can open a warning in. They are +shown at the top of the context menu of the results list. The default +application is used when you double-click a warning or press Enter.

+ +

On some systems the GUI finds installed editors automatically and adds them +to the list.

+ +
    +
  • Add...: add an application.
  • +
  • Edit...: change the selected application.
  • +
  • Remove: remove the selected application.
  • +
  • Set as default: make the selected application the default one.
  • +
+ +

+ +

For each application you enter a name for the menu, the executable and the +command line parameters. These texts in the parameters are replaced when the +application is started:

+ + + + + + + +
TextReplaced with
(file)The file name of the warning
(line)The line number
(message)The warning message
(severity)The severity
+ +

Examples for some common editors:

+ + + + + + + + + + + + + + + +
EditorExecutableParameters
Visual Studio Codecode--goto (file):(line)
Katekate-l(line) (file)
Qt Creatorqtcreator-client (file):(line)
Notepad++notepad++.exe-n(line) (file)
Vim (in a terminal)xterm-e vim +(line) (file)
Emacsemacs+(line) (file)
+ +

Reports

+ +

+ + + + + +
OptionDescription
Save all errors when creating reportAlso save hidden warnings + when you save the results to a file. By default only the visible warnings + are saved.
Save full path to files in reportsSave the full path of each + file. By default the path relative to the project (or the root + path) is saved.
+ +

See Results files, printing and statistics.

+ +

Language

+ +

+ +

The language of the user interface. The GUI is translated to several +languages; select one and click OK. This help is only available in +English.

+ + +

Addons

+ +

+ + + + + +
OptionDescription
Python binaryThe Python interpreter used to run addons. Leave + it empty to use the Python found in the PATH. If the file you enter does + not exist, a red warning is shown.
MISRA rule texts fileA text file with the MISRA C rule texts, + used by the Misra addon to show the rule texts in the warnings. Cppcheck is + not allowed to distribute the rule texts, so you must create the file from + your copy of the MISRA C document. See + Addons for the format. This is + the same setting as MISRA rule texts in the project dialog.
+ + +

Clang

+ +

+ + + + + +
OptionDescription
Clang pathThe folder where clang and + clang-tidy are installed. Used by the Clang parser and by + Clang-Tidy (see Analysis and + External tools in the project + dialog). Leave it empty to search the system PATH. On Windows, Clang is + also found in C:\Program Files\LLVM\bin.
Visual Studio headersPaths to the Visual Studio headers, + separated by semicolons. Clang needs them to parse code that uses the + Visual Studio libraries. To get the paths, open a Visual Studio command + prompt, type SET INCLUDE and copy/paste the paths.
+ + +

Code Editor

+ +

+ +

The colors of the code view in the lower part of the main window.

+
    +
  • System Style: use the colors of your desktop theme.
  • +
  • Default Light Style: dark text on a light background.
  • +
  • Default Dark Style: light text on a dark background.
  • +
  • Custom: your own colors. Click Edit... to choose the + foreground and background colors of the editor, the highlighted line and + the line numbers, and the color and font weight of keywords, class names, + strings, comments and symbols. A preview shows the result.
  • +
+ + diff --git a/gui/help/projectfiledialog.html b/gui/help/projectfiledialog.html index 52b4eba444f..e357ebbc91d 100644 --- a/gui/help/projectfiledialog.html +++ b/gui/help/projectfiledialog.html @@ -1,179 +1,474 @@ -Project File Dialog +Project files + -

Project File Dialog

+

Project files

-

The Project file dialog contains 4 tabs:

-
    -
  • Paths and defines; paths to check and basic preprocessor settings. -
  • Types and Functions; configuration of platform and 3rd party libraries -
  • Analysis; analysis options -
  • Warning options; formatting warnings, suppressing warnings, etc -
  • Addons; extra analysis with addons -
+

A project file (*.cppcheck) stores everything Cppcheck needs to +know to analyze your code: which files to analyze, include paths, defines, +libraries, platform, analysis options, suppressions, tags and addons. It is +an XML file, so you can put it in version control and share it with your +team. Paths in the project file are relative to the folder of the project +file.

-

Paths and defines

+

Create a project with File > New Project File... and change it +with File > Edit Project File.... Both open the Project file +dialog described on this page. When you click OK the project file is +saved and the analysis starts. Click Cancel to close the dialog without +saving.

-

It is recommended to import a project file.

+

The dialog has five tabs:

+
    +
  • Paths and Defines: what code to analyze and the + preprocessor settings,
  • +
  • Types and Functions: the platform and the libraries + your code uses,
  • +
  • Analysis: the build dir, the parser and options + that control how deep the analysis is,
  • +
  • Warning options: root path, tags, excluded + files and suppressions,
  • +
  • Addons: addons, coding standards and external + tools.
  • +
-

Import project

+ +

Paths and Defines

+ +

+ +

On this tab you tell Cppcheck what code to analyze. You can either import a +project from your build system, or enter the paths, defines and include paths +yourself. Importing is recommended: you get the exact include paths and +defines that the compiler uses, and you do not have to update the Cppcheck +project when the build changes.

+ + +

Import Project

+ +

Click Browse... to select the project to import. Supported +formats:

+ + + + + + + + + +
FormatNotes
Compile database
compile_commands.json
Contains the exact compiler command line of every source file. CMake + writes it when you configure with + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON. Meson and Ninja write it + too, and for other build systems (for example make) you can create it + with tools such as Bear. When a compile database is imported, each + file is analyzed with just the configuration given on its command line.
Visual Studio
*.sln, *.slnx, + *.vcxproj
Cppcheck reads the source files, include paths and defines of each + configuration (such as Debug|Win32 or + Release|x64).
Borland C++ Builder 6
*.bpr
 
+ +

Click the brush button next to the field to remove the import.

+ +

When a project is imported, the Paths, Include Paths and +Undefines fields are disabled because the information comes from the +imported project. Defines and Include file can still be used; +they are added to what the imported project says.

+ +

Analyze all Visual Studio configurations and Selected VS +Configurations: only enabled when a Visual Studio solution or project is +imported. A Visual Studio project usually has several configurations, and +analyzing all of them takes time. Check Analyze all Visual Studio +configurations to analyze every Debug and Release configuration. +Otherwise check the configurations you want in the list. If you analyze +several configurations of a file, warnings that are the same in all of them +are only reported once.

+ +

Paths

+ +

The folders and files to analyze (only when no project is imported). +Click Add... to add a folder, select a path and click Edit to +change it, or Remove to remove it. All C and C++ source files in the +folders and their subfolders are analyzed (.c, +.cpp, .cxx, .cc, .c++, +.tpp, .txx, .ipp, .ixx). +Headers are analyzed when they are included from a source file.

+ +

To leave out some files or folders, use Exclude source +files on the Warning options tab.

+ + +

Defines

+ +

Preprocessor macros that are defined for the analysis, like the +-D option of a compiler. Separate them with semicolons, and give a +value with =:

+ +
DEBUG;VERSION=5;WORD=int
+ +

To understand when you need defines, you need to know how Cppcheck handles +preprocessor conditions. A compiler only sees one configuration of your code. +Cppcheck instead tries to analyze all configurations. Look at this code:

+ +
#ifdef A
+    code1
+#endif
+#ifdef B
+    code2
+#endif
-Project to import. Cppcheck will get: +

Cppcheck automatically analyzes the code both with and without +A and B defined, so bugs in both code1 +and code2 are found. You normally do not have to set defines. Use +them when:

    -
  • What files to check -
  • Preprocessor defines -
  • Preprocessor include paths -
  • Language standard if set +
  • a macro must always be defined for the code to make sense, for example + a macro that your build system always sets,
  • +
  • a macro has a value that matters, for example + BUFFER_SIZE=1024,
  • +
  • Cppcheck reports a syntax error because a macro it does not know is used + in a way that only works when the macro is defined.
-

Paths (If you do not import project)

+

By default at most 12 configurations are analyzed for each file. If there +are more, an information message tells you. Enable Force checking +all #ifdef configurations in the preferences +to analyze all of them, which can take a long time.

-

What paths to check.

+

Undefines

-

Defines (If you do not import project)

+

Macros that must never be defined, like the -U option of a +compiler. Separate them with semicolons:

-

Cppcheck automatically checks the code with different preprocessor -configurations.

+
A;C
-
#ifdef A
-code1
-#endif
-#ifdef B
-code2
-#endif
+

In the example above, if A is undefined, Cppcheck does not +analyze the configuration where A is defined. Use this for +configurations that are never used, for example code for a platform you do not +support. It makes the analysis faster and avoids warnings for code you do not +care about.

-

Cppcheck will automatically perform analysis both when A is defined and B is -defined. So any bugs in both code1 and code2 will be detected.

+ +

Include Paths

-

If you want to configure that A will always be defined in Cppcheck analysis -you can do that here.

+

The folders where Cppcheck looks for header files that are included with +#include "..." or #include <...>, like the +-I option of a compiler. Folders are searched in the order of the +list; use Up and Down to change the order.

-

Defines are separated by semicolon. So you can for instance write:

+

Add the folders of your own headers and of the headers of 3rd party +libraries that are part of your project. Cppcheck needs them to know your +types, macros, functions and classes. Without them, some bugs can not be found +and you may get false warnings.

-
A;B=3;C
+

Do not add the folders of the standard library and system headers +(for example /usr/include or the Visual Studio include +folders). Cppcheck has built-in knowledge of the standard library and of the +libraries you select on the Types and Functions tab, and analyzing the +system headers makes the analysis slow and can cause wrong results. That is +why the missingIncludeSystem information messages say that Cppcheck does +not need the standard headers.

-

Undefines (If you do not import project)

+

Include file

-

Cppcheck automatically checks the code with different preprocessor -configurations.

+

A header file that is included before every source file, like the +-include option of GCC or /FI of Visual Studio. Use +it if your build forces a header to be included, for example a configuration +header with macros that all files need.

-
#ifdef A
-code1
-#endif
-#ifdef B
-code2
-#endif
+ +

Types and Functions

-

Cppcheck will automatically perform analysis both when A is defined and B is -defined. So any bugs in both code1 and code2 will be detected.

+

-

If you want to configure that A is never defined in Cppcheck analysis you -can do that here.

+ +

Platform

-

Undefines are separated by semicolon. So you can for instance write:

+

The platform decides the sizes of the basic types (short, +int, long, long long, pointers, +size_t, wchar_t) and whether char is +signed. Cppcheck uses this, for example, to find integer overflows, buffer +overruns with sizeof and portability problems.

-
A;C
+

Choose one of the built-in platforms:

+
    +
  • Native: the computer you run Cppcheck on,
  • +
  • Windows 32-bit ANSI, Windows 32-bit Unicode, + Windows 64-bit,
  • +
  • Unix 32-bit, Unix 64-bit.
  • +
-

Include paths (If you do not import project)

+

For embedded targets and other special platforms, choose one of the +platform files (*.xml) in the list, for example +avr8.xml, arm32-wchar_t2.xml or +pic16.xml. You can write your own platform file and put it in the +folder of the project file; it then appears in the list. See the Cppcheck +manual for the format.

-

Specify include paths.

+

If you leave the platform empty, the platform selected in the +Analyze menu of the main window is used.

-

Types and Functions

+ +

Libraries

-

Cppcheck uses the Platform setting to determine size of -short/int/long/pointer/etc.

+

Check the libraries that your code uses. Each library has a configuration +file (.cfg) that describes its functions, types and macros, for +example:

+
    +
  • which functions allocate and free memory or other resources, so that + leaks and double frees can be found,
  • +
  • which arguments must not be null or uninitialized, and which values are + valid,
  • +
  • which functions never return (like exit()),
  • +
  • which return values must be used,
  • +
  • format strings, buffer sizes and so on.
  • +
-

Check the libraries that you use in the Libraries listbox.

+

The standard library (std.cfg) is always used. Some +commonly used libraries are posix (Linux and Unix APIs), +windows (Windows API), gnu, qt, boost, +wxwidgets, gtk, sdl, opengl, openssl, +sqlite3 and googletest.

+

If your code uses a library that is not in the list, or your own library, +you can write a configuration file for it. Put the .cfg file in +the same folder as the project file and it appears in the list. You can create +and edit configuration files with the library +editor. Use Analyze > Reanalyze and check library to find +functions that are not configured.

+ +

Analysis

+

+ +

Cppcheck build dir

-

This is a work-folder that Cppcheck uses. Each Cppcheck project should have -a separate build dir. It is used for:

+

A work folder for Cppcheck. Each project needs its own build dir; do not +share one build dir between projects. A new project automatically gets +<name>-cppcheck-build-dir next to the project file. If the +folder does not exist when the analysis starts, the GUI asks if it should +create it.

+ +

The build dir is optional, but strongly recommended. It is used for:

    -
  • whole program analysis -
  • debug output -
  • faster analysis (if a source file has changed check it, if source file is - not changed then reuse old results) -
  • statistics +
  • Incremental analysis. Cppcheck stores the results of each file. When + you analyze again, files that have not changed (including the headers they + include) are not analyzed again; their stored results are used. This makes + the second analysis of a large project much faster.
  • +
  • Whole program analysis. Some checks need information from all + files, for example finding functions that are never called + (unusedFunction) or bugs where a value is passed from one file to a + function in another file. These checks are only run when there is a build + dir.
  • +
  • Addons. Addons (Misra, Y2038, Thread safety) and Clang-Tidy need a + build dir.
  • +
  • Results history. The results of the last analysis are saved + (lastResults.xml) and shown when you open the project. The + Since date column shows when each warning was first seen.
  • +
  • Statistics of previous analyses.
+

Do not put the build dir in version control. Edit > Clear +results deletes the stored results in the build dir.

+

Parser

-

It is in general recommended to use Cppcheck parser. However you can choose -to use Clang parser; Clang will be executed with a command line flag that tells -it to dump its AST and Cppcheck will read that AST and convert it into a -corresponding Cppcheck AST and use that.

+

Cppcheck (built in) is recommended. Cppcheck has its own parser that +works well even if the code can not be compiled, if headers are missing or if +the code uses compiler extensions.

-

Analysis

+

Clang (experimental) uses Clang to parse the code. Clang is run with +a flag that makes it write its abstract syntax tree (AST); Cppcheck reads this +AST, converts it and runs its checks on it. Clang must be installed (see +Clang path in the preferences), and the +code must be correct enough for Clang to accept it. This is experimental and +not all checks work as well as with the built in parser.

-

Configure what kind of analysis you want.

+

Check level

-

The Normal analysis is recommended for most use cases. Especially if -you use Cppcheck in CI.

+

How much time Cppcheck may spend on the analysis. A higher level finds more +bugs but takes longer.

+
    +
  • Reduced: a limited and fast analysis, for when you want results + right away, for example while you are writing code.
  • +
  • Normal: the default. Meant for normal analysis in CI; it should + finish in reasonable time.
  • +
  • Exhaustive: a deeper analysis of the control flow and of values, + for nightly builds and similar. It can take much longer; up to 10 times + the compilation time is normal.
  • +
-

The Bug hunting can be used if you really want to find a bug in your -code and can invest time looking at bad results and providing extra -configuration.

+

Analysis

-

Limit analysis

+

Check that each class has a safe public interface: normally Cppcheck +only considers the values that are actually passed to a function. With this +option, Cppcheck assumes that the arguments of public member functions can +have any value, and warns if a function does not handle some value +safely, even if no caller passes that value today. Use it if you want your +classes to be robust against all possible use. It gives more warnings, and you +must decide for each of them if it matters.

-

You can turn off checking of headers. That could be interesting if Cppcheck -is very slow. But normally, you should check the code in headers.

+

Limit analysis

-

It is possible to check the code in unused templates. However the Cppcheck -AST will be incomplete/wrong. The recommendation is that you do not check -unused templates to avoid wrong warnings. The templates will be checked -properly when you do use them.

+

Check code in headers: should normally be on. If you turn it off, +Cppcheck does not report warnings in the code of header files and skips some +analysis of it. This can make the analysis much faster for code with large +headers, but you will miss bugs in headers.

-

Max CTU depth: How deep should the whole program analysis be. The risk with -a "too high" value is that Cppcheck will be slow.

+

Check code in unused templates: should normally be on. If it is off, +templates that are never instantiated are not analyzed. Cppcheck can not know +the template arguments in unused templates, so the analysis of them is less +precise. Templates that are used are always analyzed with the actual template +arguments.

-

Max recursion in template instantiation: Max recursion when Cppcheck -instantiates templates. The risk with a "too high" value is that -Cppcheck will be slow and can require much memory.

+

Max CTU depth: how deep the whole program analysis follows function +calls between files (CTU means "cross translation unit"). A higher value finds +bugs where values pass through more function calls, but makes the analysis +slower. The default is 2.

+

Max recursion in template instantiation: how deep recursive templates +are instantiated. Code that uses template metaprogramming can need a higher +value. A high value can make the analysis slow and use a lot of memory. The +default is 100.

+

Warning options

+

+ +

Root path

-

The root path for warnings. Cppcheck will strip away this part of the path -from warnings. For instance if there is a warning in -

../myproject/foo/bar/file.cpp
and the root path is -
../myproject/foo
then the path for the warning will be -
bar/file.cpp
.

+

File paths in warnings are shown relative to this path. If it is empty, the +folder of the project file is used. For example, if a warning is in

+
../myproject/foo/bar/file.cpp
+

and the root path is

+
../myproject/foo
+

then the warning is shown for

+
bar/file.cpp
-

Warning Tags

+

Warning tags

-

Tags allow you to manually categorize warnings.

+

Your own names for categories of warnings, separated by semicolons, for +example todo;false positive;fixed later. When you have entered +tags, you can right-click warnings and give them a tag. See +Tagging warnings. Only letters, digits and spaces +are allowed in tag names.

+

Exclude source files

-

Excluded source files will not be analyzed by Cppcheck.

+

Files and folders that should not be analyzed. Use it for generated code, +3rd party code that you do not maintain, test data and similar. Click +Exclude folder... or Exclude file... to add entries, Edit +to change the selected entry and Remove to remove it.

+

Excluding works for imported projects too. Note that an excluded header is +still read when it is included from a source file that is analyzed, because +Cppcheck needs it to understand that source file.

+ +

Suppressions

-

List of suppressions. These warnings will not be shown.

+

Warnings that should not be reported. Click Add to add a +suppression; double-click a suppression to edit it, and click Remove to +remove it.

-

Addons

+

+ +

A suppression has these fields:

+
    +
  • Error ID (required): the id of the warning, for example + nullPointer. You can select it from the list or type it. Use + * to suppress all warnings (for example for some file).
  • +
  • File name (optional): only suppress the warning in this file. You + can use the wildcards * and ?, for example + src/generated/*.
  • +
  • Line number (optional): only suppress the warning on this line. + This is fragile: when the code changes, the line number changes too. + Inline suppressions are better for single warnings.
  • +
  • Symbol name (optional): only suppress warnings about this + variable, function or other symbol.
  • +
+ +

You can also add suppressions from the results list with the context menu. +See Hiding and suppressing warnings.

-

Y2038
32-bit timers that count number of seconds since 1970 will -overflow in year 2038. Check that the code does not use such timers.

+

Enable inline suppressions

-

Thread safety
Check that the code is thread safe

+

When this is checked, comments in the code like

+
// cppcheck-suppress nullPointer
+

suppress warnings. See Inline +suppressions.

-

MISRA
Ensure that the MISRA coding standard is followed. Please -note you need to have a textfile with the misra rule texts to get proper -warning messages. Cppcheck is not legally allowed to distribute the misra -rule texts.

+ +

Addons

-

Clang-tidy
Run Clang-tidy

+

+ +

Addons are extra checkers, written in Python, that run after the normal +analysis. Addons need Python to be installed (see Python binary in the +preferences) and the project must have a +build dir. If an addon can not be found, it is shown as +(Not found) and can not be selected.

+ +

Addons

+ +

Y2038: on many 32-bit systems time_t is a signed 32-bit +integer that counts seconds since 1970. It overflows on 19 January 2038. This +addon warns about code that uses such 32-bit time values.

+ +

Thread safety: warns about code that is not safe to use from several +threads, for example local static variables and calls to functions that are +not thread safe.

+ +

Coding standards

+ +

Misra C: checks the code against the MISRA C guidelines, a coding +standard for safety critical C code. Open source Cppcheck has a Misra addon +that does not cover all rules.

+ +

The MISRA rule texts are copyrighted and Cppcheck is not allowed to +distribute them. Without them, Misra warnings only show the rule number. To +get the rule texts in the warnings, buy the MISRA C document, copy the text of +Appendix A "Summary of guidelines" to a text file, and select that file in +MISRA rule texts. The file should look like this:

+
Appendix A Summary of guidelines
+Rule 1.1
+Text of rule 1.1
+Rule 1.2
+Text of rule 1.2
+

The rule text file is a user setting; it is not stored in the project +file.

+ +

Misra C++, Cert C, Cert C++, Autosar and +Safety profiles are only available in Cppcheck Premium. Cppcheck +Premium also lets you choose the version of Misra C (2012, 2023 or 2025) and +Misra C++ (2008 or 2023). For Cert C you can give the precision of +int, which is used for rule INT35-C if the precision is not the +same as the size.

+ +

Bug hunting (Premium)

+ +

A very deep analysis that tries to find all bugs of some kinds, at the cost +of more false warnings and a longer analysis. Only available in Cppcheck +Premium.

+ +

External tools

+ +

Clang-Tidy: also run Clang-Tidy on the code and show its warnings +together with the Cppcheck warnings. Clang-Tidy must be installed. It is +searched for in the Clang path from the +preferences, or in the system PATH. If it is not found, the checkbox shows +(not found). Use the Clang button in the severity toolbar to show +or hide the Clang-Tidy results.

+ + - diff --git a/gui/help/reports.html b/gui/help/reports.html new file mode 100644 index 00000000000..7f9b0ab6124 --- /dev/null +++ b/gui/help/reports.html @@ -0,0 +1,125 @@ + + +Results files, printing and statistics + + + + +

Results files, printing and statistics

+ + +

Saving results

+ +

Select File > Save results to file... (Ctrl+S) or click the save +button in the toolbar. Choose a file name and a format:

+ + + + + + +
FormatContents
XML files (*.xml)All information about each warning: file, + line, column, severity, id, CWE, message, notes, tags and more. This is + the same XML format (version 2) as the command line tool writes with + --xml. The GUI can open it again, and many other tools, for + example CI systems and report generators, can read it. Use this format if + you want to keep the results.
Text files (*.txt)One line per warning, in the classic + Cppcheck text format:
+ [src/buffer.c:9] -> [src/buffer.c:10]: (error) Array 'buf[16]' + accessed at index 16, which is out of bounds.
+ The locations of the notes are listed first, connected with + ->.
CSV files (*.csv)One line per warning with the columns file, + line, severity, id and summary. Open it in a spreadsheet to sort, count + and filter warnings.
+ +

Two preferences on the Reports tab affect +what is saved:

+
    +
  • Save all errors when creating report: also save warnings that are + hidden. By default only the warnings you can see are saved, so you can use + the severity buttons, the quick filter and hiding to choose what goes in + the report.
  • +
  • Save full path to files in reports: save the full path of each + file instead of the path relative to the project.
  • +
+ + +

Opening results

+ +

Select File > Open XML... to open a results file in XML format. +The current results are cleared first. The file can come from the GUI or from +the command line tool, for example from a CI job:

+ +
cppcheck --project=compile_commands.json --xml 2> results.xml
+ +

When results are opened from a file, the GUI needs to find the source files +to show the code. If the paths in the file are relative, the GUI asks for the +folder they are relative to when you select a warning. You can also give the +folder on the command line with the -d option; see +Command line options.

+ +

XML files in the old version 1 format can not be opened. Use +--xml with a recent Cppcheck version to create files in the +version 2 format.

+ +

For results opened from a file, the code can not be analyzed again with +Recheck, because the GUI does not know how it was analyzed.

+ + +

Printing

+ +

Select File > Print... (Ctrl+P) to print the results, or File +> Print Preview... to see what will be printed first. The printout has +one line per warning with the file, line, severity and summary. Like saved +reports, only the warnings you can see are printed. You can print to a PDF +file if your system has a PDF printer.

+ + +

Statistics

+ +

Select View > Statistics to see statistics about the project and +the last analysis. Enable Show statistics on check completion in the +preferences to show the statistics +automatically after each analysis.

+ +

The statistics dialog has these tabs:

+ +

Project

+

The root path of the project, the paths that are analyzed, the include +paths, the defines and the undefines.

+

+ +

Previous Scan

+

The path that was analyzed, the number of files and how long the analysis +took.

+

+ +

Statistics

+

The number of warnings of each severity, and the number of active +checkers.

+

+ +

Checkers

+

A report of which checkers were active in the analysis and which were not. +Use it to see what was checked, for example to document the analysis for a +safety certification.

+ +

History

+

A chart of the number of warnings over time, read from the build dir of the +project. Only available if the GUI was built with Qt Charts and the project +has a build dir.

+ +

Click Copy to Clipboard to copy the statistics as text and HTML, for +example to paste them in an e-mail or a document. Click Pdf Export to +save them as a PDF file.

+ +

Note: the statistics count the warnings of the last +analysis in this session. When you open a project with a build dir, the +results of the previous session are shown, but they are not counted until you +analyze again.

+ + + + + diff --git a/gui/help/severities.html b/gui/help/severities.html index 791c78c68e6..7e9f9e66d5d 100644 --- a/gui/help/severities.html +++ b/gui/help/severities.html @@ -1,47 +1,188 @@ -Severities +Severities and report types + -

Severities

+

Severities and report types

-

error

+

Every warning has a severity that tells you what kind of problem it +is and how serious it is. The severity is shown in the Severity column +and as an icon in the results list. Use the buttons in the severity toolbar, +or the View menu, to show or hide each severity.

-
-

when code is executed there is some bad behavior (undefined behavior, leak)

+

error

-

warning

+

The code has a bug that causes bad behavior when it is executed: undefined +behavior, a crash, a memory or resource leak, and similar. Errors should +always be fixed.

-
-

when code is executed there might be undefined behavior

+

Examples: array index out of bounds (arrayIndexOutOfBounds), +null pointer dereference (nullPointer), use of an uninitialized +variable (uninitvar), memory leak (memleak), resource +leak (resourceLeak), use after free +(deallocuse), division by zero (zerodiv), +mismatching allocation and deallocation (mismatchAllocDealloc).

-

style

+
void f()
+{
+    char buf[10];
+    buf[10] = 0;     // error: arrayIndexOutOfBounds
+}
-
-

point out possible mistakes, and suggest more defensive programming. Examples: +

warning

+ +

The code can cause bad behavior in some situations, or there is code that is +almost certainly not doing what the programmer wanted. Warnings should be +looked at carefully; most of them are bugs.

+ +

Examples: a null pointer check after the pointer has already been used +(nullPointerRedundantCheck), not checking the result of +malloc() (nullPointerOutOfMemory), wrong argument type +for a printf format (invalidPrintfArgType_sint), assignment in a +condition where a comparison was meant, a member variable that is not +initialized in a constructor (uninitMemberVar).

+ +
void f(struct S *s)
+{
+    int x = s->x;
+    if (!s)          // warning: nullPointerRedundantCheck
+        return;
+}
+ +

style

+ +

The code is not wrong, but it can be improved: it is confusing, redundant, +or not as safe as it could be. Style warnings often point to real bugs, for +example a condition that is always true can mean that the wrong variable is +used. They also suggest more defensive programming.

+ +

Examples:

+
    +
  • unused variables, functions and code (unusedVariable, + unreadVariable, unusedFunction),
  • +
  • conditions that are always true or false + (knownConditionTrueFalse),
  • +
  • variables, parameters and member functions that can be const + (constVariable, constParameterPointer, + functionConst),
  • +
  • the scope of a variable can be reduced (variableScope),
  • +
  • duplicate code in if/else branches (duplicateBranch),
  • +
  • suspicious operator precedence (clarifyCalculation).
  • +
+ +
int f(int x)
+{
+    if (x > 0 && x > 10)   // style: the first condition is redundant
+        return 1;
+    return 0;
+}
+ +

performance

+ +

Suggestions to make the code faster. They are based on common knowledge +about what is slow. It is not certain that you get a measurable speed +improvement by fixing them; it depends on how often the code runs.

+ +

Examples: passing a large object by value instead of by const reference +(passedByValue), using it++ instead of +++it for iterators (postfixOperator), inefficient use +of STL containers.

+ +
void print(std::vector<std::string> names);   // performance: passedByValue
+ +

portability

+ +

The code may work on your platform but not on others: implementation +defined behavior, 64-bit problems, or undefined behavior that happens to work +"as you want" with your compiler.

+ +

Examples: storing a pointer in an int +(AssignmentAddressToInteger), shifting a negative value, casting +between incompatible pointer types (invalidPointerCast), assuming +the size of a type.

+ +

Some portability warnings depend on the platform you have selected; see +Platform.

+ +

information

+ +

Messages about the analysis itself, not about your code. If you get +information messages, your code may be fine but the Cppcheck configuration can +be improved. Fixing the configuration usually gives better results.

+ +

Examples:

    -
  • unused code/variables/functions -
  • conditions that are always true/false -
  • constness -
  • operator precedence +
  • missingInclude: an include file of your project was not + found. Add the folder to the + include paths.
  • +
  • missingIncludeSystem: a standard or system header was not + found. This is normal and you do not need to do anything; Cppcheck has + built-in knowledge of the standard library. You can hide these with the + severity toolbar or suppress the id.
  • +
  • toomanyconfigs: a file has more preprocessor configurations + than Cppcheck analyzes by default. See + Defines.
  • +
  • normalCheckLevelMaxBranches: the analysis of a function was + limited. Use the Exhaustive check level to analyze it fully.
  • +
  • messages from Analyze > Reanalyze and check library about + functions that are not configured.
-

-

performance

+

note

-
-

Suggestions for making the code faster. These suggestions are only based on common knowledge. It is not certain you'll get any measurable difference in speed by fixing these messages.

+

Notes are not warnings. They are shown as child items of a warning and +explain the path through the code that leads to it. See +Investigating warnings.

-

portability

+ +

Report types

+ +

If you work with a coding standard, you can show the results classified +according to that standard. Select the standard in View > Report:

+ +

+ + + + + + + + + + + + +
ReportClassificationsExtra columns
NormalThe Cppcheck severities described above. 
AutosarRequired, AdvisoryClassification, Guideline
Cert C, Cert C++L1, L2, L3 (the CERT priority levels; L1 is + the most severe)Level, Rule
Misra C, Misra C++ 2023Mandatory, Required, AdvisoryClassification, Guideline
Misra C++ 2008Required, Advisory, DocumentClassification, Guideline
+ +

With a compliance report type:

+
    +
  • Each warning is mapped to the guideline or rule of the standard that it + relates to. The Guideline or Rule column shows it, and the + Classification or Level column shows how strict the + guideline is.
  • +
  • The icons and the buttons in the severity toolbar follow the + classification: Mandatory and L1 use the error icon, Required and L2 the + warning icon, Advisory and L3 the style icon, and Document the + information icon.
  • +
  • Warnings that are not related to any guideline of the standard are not + shown.
  • +
-
-

portability warnings. Implementation defined behavior. 64-bit portability. Some undefined behavior that probably works "as you want". etc.

+

-

information

+

The report type only changes how results are shown; the analysis is the +same. To check the code against the rules of a coding standard, enable the +standard on the Addons tab of the +project. Open source Cppcheck has a Misra C addon; checking the other +standards needs Cppcheck Premium.

-
-

Configuration problems. If you get such output then your code is ok but your cppcheck configuration could be improved.

+ diff --git a/gui/help/standalone-analysis.html b/gui/help/standalone-analysis.html index ad7fae5fe55..bf7cc0096b3 100644 --- a/gui/help/standalone-analysis.html +++ b/gui/help/standalone-analysis.html @@ -1,17 +1,125 @@ - -Standalone analysis +Analyzing without a project + -

Standalone analysis

+

Analyzing without a project

+ +

You can analyze code quickly without creating a project file. This is +useful to try Cppcheck, or to look at a few files. For regular use a project +is better: with a properly configured project Cppcheck knows your include +paths, defines and libraries, and the analysis is more accurate. You also get +features that need a project, such as saved suppressions, tags and the build +dir. See Project files.

+ +

If a project is open, close it first with File > Close +Project File. The GUI does not let you analyze files or folders while a +project is open.

+ +

Analyzing files

+ +

Select Analyze > Files... (Ctrl+F) and select one or more source +files. The analysis starts at once.

+ +

In the same file dialog you can also select a project of your build +system. Cppcheck then analyzes the files of that project with its include +paths and defines, just as if you had imported it in a project file:

+
    +
  • a compile database (compile_commands.json),
  • +
  • a Visual Studio solution (*.sln, *.slnx) or + project (*.vcxproj). For a solution, a dialog asks which + configuration (for example Debug|x64) to analyze,
  • +
  • a Borland C++ Builder 6 project (*.bpr).
  • +
+ +

Analyzing a directory

+ +

Select Analyze > Directory... (Ctrl+D) and select a folder. +Cppcheck analyzes all source files in the folder and its subfolders. Files +with these extensions are analyzed: .c, .cpp, +.cxx, .cc, .c++, .tpp, +.txx, .ipp and .ixx.

+ +

Header files are not analyzed on their own. The code in a header is +analyzed when a source file includes it, as long as Cppcheck can find the +header.

+ +

If the folder contains a Cppcheck project file, the GUI asks if you want to +open that project instead. If there are several project files, it asks if you +want to continue without them.

+ +

Which settings are used

+ +

Without a project, the analysis uses:

+
    +
  • the preferences: number of threads, + Force checking all #ifdef configurations, Enable inline + suppressions, Check for inconclusive errors also,
  • +
  • the settings in the Analyze menu: the C and C++ standard, the + language (auto-detect, enforce C or enforce C++) and the platform.
  • +
+ +

The Analyze menu settings are remembered when you close the GUI.

+ +

Language standard

+ +

Analyze > C standard and Analyze > C++ standard select +the language version. It affects how some code is parsed and which checks +apply. For example, some functions are deprecated in newer standards, and +some library functions only exist in newer standards.

+ +

Language

+ +

With Auto-detect language the file extension decides if a file is C +or C++. Choose Enforce C or Enforce C++ to treat all files as +one language.

+ +

Platform

+ +

The platform decides the size of the basic types (short, +int, long, pointers, wchar_t and so on) +and whether char is signed. Choose the platform the code will run +on:

+
    +
  • Native: the computer you run Cppcheck on.
  • +
  • Unix 32-bit / Unix 64-bit: Linux, BSD, macOS and similar + on 32-bit or 64-bit processors.
  • +
  • Windows 32-bit ANSI / Windows 32-bit Unicode: 32-bit + Windows. In the ANSI variant the Windows "T" macros and types such as + TCHAR and _tcscpy mean the char + versions; in the Unicode variant they mean the wchar_t + versions.
  • +
  • Windows 64-bit: 64-bit Windows, with Unicode.
  • +
+ +

Limitations

+ +

Without a project:

+
    +
  • There are no include paths, so Cppcheck may not find your own headers. You + will see information messages about missing includes, and some + warnings can be missing because types and macros are unknown.
  • +
  • Suppressions made with the context menu can not be saved. The warnings + disappear from the list but are reported again in the next analysis. Use + inline suppressions in the code instead, or create a project. See + Hiding and suppressing warnings.
  • +
  • Warnings can not be tagged.
  • +
  • There is no build dir. Every analysis analyzes all files, the Since + date column is not available, and checks that need whole program + analysis, such as unused functions, are not run.
  • +
  • Addons and coding standards (Misra, Cert, ...) can not be used.
  • +
+ +

Analyzing again

-

It is possible to quickly analyze files. Open the Analyze menu and click on -either Files... or Directory....

+

After you change the code, use Analyze > Reanalyze modified files +(Ctrl+R) to analyze the files that changed, or Reanalyze all files to +analyze everything again.

-

It is recommended that you create a project for analysis. A properly configured -project will give you better analysis.

+ diff --git a/gui/help/style.css b/gui/help/style.css new file mode 100644 index 00000000000..126f42f3fe7 --- /dev/null +++ b/gui/help/style.css @@ -0,0 +1,68 @@ +/* Stylesheet for the Cppcheck GUI help. Qt's help browser only supports a + subset of CSS, so keep this simple. */ + +body { + font-family: sans-serif; + margin: 8px; +} + +h1 { + color: #1f4e79; + font-size: x-large; +} + +h2 { + color: #1f4e79; + font-size: large; + margin-top: 20px; +} + +h3 { + color: #2e75b6; + font-size: medium; + margin-top: 14px; +} + +pre { + background-color: #f3f3f3; + font-family: monospace; + margin-left: 16px; +} + +code { + font-family: monospace; +} + +table.options { + border-collapse: collapse; + border-color: #c8c8c8; + border-style: solid; +} + +table.options th { + background-color: #e4ecf5; + padding: 4px; + text-align: left; +} + +table.options td { + padding: 4px; + vertical-align: top; +} + +p.note { + background-color: #fff8dc; +} + +p.tip { + background-color: #eaf5ea; +} + +p.nav { + color: #707070; + font-size: small; +} + +p.figure { + margin-left: 16px; +} diff --git a/gui/help/suppressions.html b/gui/help/suppressions.html new file mode 100644 index 00000000000..2baf18c318b --- /dev/null +++ b/gui/help/suppressions.html @@ -0,0 +1,158 @@ + + +Hiding and suppressing warnings + + + + +

Hiding and suppressing warnings

+ +

There are several ways to get rid of warnings you do not want to see. They +differ in how long they last and where they are stored:

+ + + + + + + + + + + + + + + + + + +
MethodLastsStored inBest for
HideUntil the next analysisNowhereGetting warnings out of the way while you work
Severity buttons and filterUntil you change themGUI settingsConcentrating on some kinds of warnings
Suppress from the context menuPermanentProject fileTurning off a kind of warning for the whole project
Suppressions in the project dialogPermanentProject fileSuppressions for some files, lines or symbols
Inline suppressionsPermanentSource codeA single false warning
Excluding filesPermanentProject fileCode you do not want to analyze at all
+ +

The difference between hiding and suppressing: a hidden warning +comes back the next time you analyze the code. A suppressed warning is not +reported again until you remove the suppression.

+ + +

Hiding warnings

+ +

Select one or more warnings, right-click and choose Hide N +result(s). To hide all warnings with the same id as the selected warning, +choose Hide all with id.

+ +

+ +

Hidden warnings are not deleted. Use View > Show hidden to show +them again. They are also shown again after the next analysis.

+ +

By default, hidden warnings are not saved when you save the results to a +file. Enable Save all errors when creating report in the +preferences to save them too.

+ + +

Severity buttons and the quick filter

+ +

The buttons in the severity toolbar show or hide all warnings of a +severity, and the quick filter shows only warnings that contain some text. See +The main window. These do not change the results; +they only change what you see.

+ + +

Suppressing from the context menu

+ +

Select one or more warnings, right-click and choose Suppress selected +id(s). All warnings with the ids of the selected warnings are removed from +the list, and a suppression for each id is added to the project file. The +warnings are not reported in future analyses.

+ +

Note: this suppresses the id in all files of the +project, not only the selected warning. If you only want to suppress one +warning, use an inline suppression, or add a suppression +with a file name or line number in the project dialog.

+ +

This only works when a project is open. Without a project the warnings are +removed from the list, but nothing is saved. Critical errors, such as syntax +errors, can not be suppressed this way.

+ + +

Suppressions in the project dialog

+ +

Open File > Edit Project File... and go to the Warning +options tab. The Suppressions list shows all suppressions of the +project, including the ones made with the context menu.

+ +

+ +

Click Add to add a suppression. You must give the Error ID; +the other fields make the suppression more specific:

+
    +
  • File name: only in files that match. Wildcards * and + ? can be used, for example src/legacy/*.
  • +
  • Line number: only on this line of the file.
  • +
  • Symbol name: only for warnings about this variable or + function.
  • +
+ +

Some examples:

+ + + + + + + + +
Error IDFile nameLineEffect
unusedFunction  No unused function warnings at all, for example for a library whose + functions are called by other programs.
*src/generated/* No warnings in generated code (but the code is still analyzed).
nullPointersrc/io.c120No null pointer warning on line 120 of + src/io.c.
+ +

Double-click a suppression to edit it, or select it and click +Remove to remove it. After you click OK, the project is analyzed +again.

+ + +

Inline suppressions

+ +

An inline suppression is a comment in the source code, just before the line +with the warning, or at the end of that line:

+ +
void f()
+{
+    char *p = get_buffer();
+    // cppcheck-suppress nullPointer
+    *p = 0;
+
+    *p = 1; // cppcheck-suppress nullPointer
+}
+ +

You can suppress several ids at once, and add a comment that explains +why:

+ +
// cppcheck-suppress[nullPointer,uninitvar] get_buffer() never returns NULL
+ +

Inline suppressions are the best way to suppress a single false warning: +they stay with the code when it is moved, and the next person who reads the +code sees that the warning was looked at. See the Cppcheck manual for more +forms, for example suppressing a whole block or file.

+ +

Inline suppressions must be enabled:

+
    +
  • for a project, with Enable inline suppressions on the Warning + options tab of the project dialog (enabled by default for new + projects),
  • +
  • without a project, with Enable inline suppressions in the + preferences.
  • +
+ + +

Excluding files

+ +

To not analyze some files or folders at all, add them to Exclude source +files on the Warning options tab of the project dialog. See +Exclude source files.

+ + + + + diff --git a/gui/help/tagging.html b/gui/help/tagging.html index 7ac4e7ee8d9..4f0a86452ea 100644 --- a/gui/help/tagging.html +++ b/gui/help/tagging.html @@ -1,22 +1,68 @@ Tagging warnings +

Tagging warnings

-

You can manually categorize warnings.

+

Tags let you categorize warnings by hand. You choose the names of the tags +yourself. Some ideas:

+
    +
  • a review status: todo, checked, + fixed later,
  • +
  • your opinion of the warning: bug, false + positive, not important,
  • +
  • who should look at it: frontend, network, + team b.
  • +
-

You choose the names of the categories yourself in the project file -dialog.

+

Unlike suppressions, tags do not remove warnings. They are a way to keep +track of what you have looked at and what you have decided about each +warning.

-

If tag names are configured, then you can tag the warnings by -right-clicking on them and selecting the proper tag in the -context menu.

+

Defining tags

-

Tags are saved in the project file and will be permanent.

+

Open File > Edit Project File..., go to the Warning options +tab and enter the tag names in Warning tags, separated by semicolons:

+ +
todo;false positive;fixed later
+ +

+ +

Tag names can contain letters, digits and spaces. Tags need a project; there +are no tags when you analyze without a project.

+ +

Tagging warnings

+ +

Select one or more warnings, right-click, open the Tag submenu and +choose a tag. Choose No tag to remove the tag. The Tag submenu is +only shown if the project has tags.

+ +

+ +

The tag is shown in the Tags column. Click the column header to sort +the warnings by tag, so that warnings with the same tag are listed +together.

+ +

Where tags are stored

+ +

The tags you give to warnings are saved in the project file at once. Each +tagged warning is identified by a hash of the warning and the code of the +function it is in, so the tag stays with the warning when code elsewhere in +the file changes or lines are added above the function. If the function itself +is changed, the warning gets a new hash and the tag is lost. That is +intended: when the code has changed, the warning should be looked at +again.

+ +

Because the tags are in the project file, everyone who uses the project file +sees the same tags. Tags are also included when you save the results as +XML.

+ + - diff --git a/gui/help/tools.html b/gui/help/tools.html new file mode 100644 index 00000000000..6301283bbd0 --- /dev/null +++ b/gui/help/tools.html @@ -0,0 +1,146 @@ + + +Scratchpad, library editor and other tools + + + + +

Scratchpad, library editor and other tools

+ + +

Scratchpad

+ +

The scratchpad lets you analyze a piece of code without saving it in a file. +Use it to try out how Cppcheck handles some code, to reduce a false warning to +a small example before you report it, or to check a code snippet from a +review.

+ +

Open it with View > Show Scratchpad....

+ +

+ +
    +
  1. Type or paste C or C++ code in the editor.
  2. +
  3. Optionally enter a file name. It is mainly used to decide the language: + test.c is analyzed as C and test.cpp as C++. If + you leave it empty, the code is analyzed as C++.
  4. +
  5. Click Check.
  6. +
+ +

The results are shown in the results list of the main window, and they +replace the results that were shown before. The code is analyzed with the +settings of the open project, or with the settings of the Analyze menu +if no project is open. The code can not include your own headers, since it is +not stored in a file.

+ +

The scratchpad keeps its text while the GUI is running, so you can change +the code and click Check again.

+ + +

Library editor

+ +

A library configuration file (.cfg) tells Cppcheck how the +functions of a library behave. Cppcheck comes with configurations for many +libraries (see Libraries). For +your own libraries, or libraries that have no configuration, you can write one +yourself. The library editor helps you with the function part of the +configuration.

+ +

Open it with View > Library Editor....

+ +

+ +

Working with files

+ +

Click Open to load an existing .cfg file, Save +to save your changes and Save as to save to a new file. To start a new +configuration, add functions and click Save as.

+ +

Save your configuration in the folder of your project file. It then +appears in the Libraries list on the Types and Functions tab of +the project dialog, where you can select it.

+ +

The editor only shows the functions of a configuration. Other parts of the +file, such as memory and resource allocation functions, defines and types, are +kept when you save. If a file contains elements that the editor does not know, +it can not be opened in the editor; use a text editor for such files.

+ +

Functions

+ +

The list on the left shows the functions of the configuration. Type in +Filter to show only functions whose names contain the text. Click +Sort to sort the list by name.

+ +

Click Add to add a function. Enter the function name and the number +of arguments. If several functions have the same properties, you can enter +their names separated by commas; they then share one entry.

+ +

Select a function to see and change its properties:

+ + + + + + + + +
PropertyMeaning
CommentsYour notes about the function. They are saved as + an XML comment before the function in the file.
noreturnTrue if the function never returns, like + exit() or a fatal error handler. Then Cppcheck knows that the + code after a call is not reached. False if it always returns. + Unknown if it depends.
return value must be usedWarn if the return value is ignored, + for example for functions that return an error code or a new object.
ignore function in leaks checkingThe function does not take + ownership of a pointer that is passed to it. Without this, Cppcheck may + assume that the function frees or stores the memory, and stop tracking a + possible leak.
ArgumentsThe properties of each argument. Select an argument + and click Edit to change them.
+ +

Argument properties

+ + + + + + + + + + +
PropertyMeaning
Not boolA boolean value (for example the result of a + comparison) is wrong for this argument. Typical for pointers and + sizes.
Not nullA null pointer must not be passed.
Not uninitThe value must be initialized. For pointers this + means the data it points at is read by the function.
StringThe argument must be a zero terminated string.
Format stringThe argument is a printf style format string; + the following arguments are checked against it.
Min size of bufferThe minimum size of the buffer that is + passed. argvalue: the value of another argument (for example the + size argument of memset); mul: the product of two + arguments; strlen: the length of a string argument.
Valid valuesThe values that are allowed, for example + 0: (zero or more), 1:255 or + 0,2:5. Cppcheck warns if another value can be passed.
+ +

For all features of the configuration format, see the Cppcheck manual and +the files in the cfg folder of the Cppcheck installation. You can +edit .cfg files in a text editor too.

+ +

To find which functions in your code are not configured yet, use +Analyze > Reanalyze and check library; see +Checking the library +configuration.

+ + +

Thread details

+ +

View > Thread Details shows what each analysis thread is doing: +the file it analyzes, when it started on the file and how far it has come. +Use it to find files that take a long time to analyze. If one file takes very +long, you can for example exclude it, reduce the number of configurations with +undefines, or use a lower check +level.

+ +

The number of threads is set in the +preferences.

+ + + + + diff --git a/gui/help/troubleshooting.html b/gui/help/troubleshooting.html new file mode 100644 index 00000000000..b9b4cd99ef9 --- /dev/null +++ b/gui/help/troubleshooting.html @@ -0,0 +1,190 @@ + + +Troubleshooting + + + + +

Troubleshooting

+ +

Answers to common questions and problems.

+ +

I get many "Include file not found" messages

+ +

There are two kinds:

+
    +
  • missingIncludeSystem: a standard or system header, such as + <stdio.h> or <vector>, was not found. + This is normal. Cppcheck does not need these headers, because it knows the + standard library and the libraries you select in the project. Hide the + information severity, or suppress the id + missingIncludeSystem in the project.
  • +
  • missingInclude: one of your own headers was not found. Fix this: + add the folder of the header to the + include paths of the project, or import the project from your build + system so that Cppcheck gets the same include paths as the compiler.
  • +
+ +

Use Analyze > Check configuration (defines, includes) to get a +list of the configuration problems of the project.

+ +

Cppcheck reports a syntax error or an unknown macro

+ +

Cppcheck does not analyze a file it can not parse. A message like There +is an unknown macro here somewhere. Configuration is required. means that +the code uses a macro that Cppcheck does not know, in a place where the code +can not be parsed without knowing what the macro means. For example:

+ +
DECLARE_HANDLER(foo)    // what is this?
+{
+    ...
+}
+ +

To fix it, do one of these:

+
    +
  • add the include path of the header that defines the macro,
  • +
  • select the library that the macro belongs to (for example qt for + Q_OBJECT and signals, or windows for the + Windows API macros),
  • +
  • give the macro a value in + Defines, for example DECLARE_HANDLER(x)=void x(), or in a + library configuration file.
  • +
+ +

If the code is correct and compiles, but Cppcheck still reports a syntax +error, please report it as a Cppcheck bug.

+ +

The analysis is slow

+ +
    +
  • Use a build dir. Then + unchanged files are not analyzed again.
  • +
  • Increase the Number of threads in the + preferences.
  • +
  • Use the Reduced or Normal check level instead of + Exhaustive.
  • +
  • Make sure that Force checking all #ifdef configurations is off in + the preferences.
  • +
  • Use undefines to skip + configurations you do not need, or import a compile database: then each + file is analyzed with one configuration only.
  • +
  • Do not add system include folders (such as /usr/include) to + the include paths.
  • +
  • Exclude generated code and + 3rd party code.
  • +
  • Use View > Thread Details to find files that take long; see + Thread details.
  • +
+ +

A warning is wrong (false positive)

+ +

First check the configuration: missing include paths and missing library +configurations are common causes. For example, if your code calls a function +that never returns and Cppcheck does not know that, it can report problems in +code that is never reached. Configure the function as noreturn in a +library configuration (see Library +editor).

+ +

If the configuration is right, suppress the warning (see +Hiding and suppressing warnings) and please +report the false positive to the Cppcheck project with a small code example. +Use the scratchpad to make the example as +small as possible.

+ +

A bug that I know of is not found

+ +
    +
  • Check that the severity of the warning is shown in the severity toolbar, + that the quick filter is empty, that no warnings are hidden (View > + Show hidden) and that View > Report is Normal.
  • +
  • Check that the file was analyzed: look in the Analysis Log.
  • +
  • Check that the code was analyzed with the right configuration. If the + code is in an #ifdef block, it may only be analyzed when a + macro is defined.
  • +
  • Select the libraries that the code uses, so that Cppcheck knows how their + functions behave.
  • +
  • Try the Exhaustive check level and enable inconclusive warnings in + the preferences.
  • +
  • Bugs that span several files need a build dir (whole program + analysis).
  • +
+ +

Cppcheck can not find all bugs. If you think the bug should be found, +create a small example in the scratchpad and report it to the Cppcheck +project.

+ + +

Checking the library configuration

+ +

Select Analyze > Reanalyze and check library. The project is +analyzed again, and Cppcheck reports information messages for functions +it has no configuration for, for example:

+
    +
  • checkLibraryNoReturn: Cppcheck does not know if the function + can return. Configure noreturn for it.
  • +
  • checkLibraryFunction: the function is not configured at all. + If it is a library function, configure it so that Cppcheck can check how it + is used.
  • +
+ +

Write the missing configuration in a .cfg file with the +library editor or a text editor, put it +in the folder of the project file and select it on the Types and +Functions tab of the project.

+ +

Addons do not work

+ +
    +
  • Addons are Python scripts. Install Python, and if it is not in the PATH, + set the Python binary in the + preferences.
  • +
  • The project needs a build + dir. Without it, the analysis is stopped with the message To check + the project using addons, you need a build directory.
  • +
  • If an addon is shown as (Not found) in the project dialog, the + Cppcheck installation is incomplete.
  • +
+ +

Clang-Tidy is "not found"

+ +

Install Clang-Tidy (it is part of LLVM/Clang). If it is not in the PATH, set +the Clang path in the preferences to the +folder where clang-tidy is.

+ +

Double-clicking a warning does nothing

+ +

No editor is configured, or no default editor is selected. Add your editor +on the Applications tab of the +preferences and click Set as default. If the editor does not start, +check the executable path and the parameters.

+ +

The results are old

+ +

When you open a project with a build dir, the results of the last analysis +are shown, not a new analysis. Click Reanalyze all files to analyze the +current code.

+ +

"Failed to load std.cfg"

+ +

The GUI can not find its data files. The Cppcheck installation is broken; +reinstall Cppcheck. If you built Cppcheck yourself, make sure the +cfg folder is installed next to the program, or configure the data +folder with the --data-dir command line +option.

+ +

Where can I get more help?

+ + + + + + + diff --git a/gui/help/walkthrough.html b/gui/help/walkthrough.html index 9f27ce05f6c..dbaff99a073 100644 --- a/gui/help/walkthrough.html +++ b/gui/help/walkthrough.html @@ -1,69 +1,156 @@ -Walk through +Getting started + -

Quick walk through

+

Getting started

-This is a quick and short walk through to get you started. +

This page walks you through a first analysis: you create a project file, +run the analysis and look at the results. It takes a few minutes. The other +pages in this help explain each step in more detail.

-

Step 1: Create a project.

+

Step 1: Create a project file

-

Create a new project:

+

Select File > New Project File... (Ctrl+Shift+N). A file dialog +asks where to save the project file. Save it in the root folder of your code, +for example next to your CMakeLists.txt or Visual Studio +solution. Paths in the project file are stored relative to the project file, +so the project keeps working if you move the whole folder or check it into +version control.

-
+

When you have chosen a file name, the Project file dialog opens. A +new project automatically gets a build dir called +<name>-cppcheck-build-dir. Cppcheck stores its work files +there; it makes repeated analysis much faster and is required for some +features. See Cppcheck build dir.

-

In the Paths and Defines tab, it is recommended that you import your -project file at the top.

+

Step 2: Tell Cppcheck where the code is

-
+

Open the Paths and Defines tab. You have two choices.

-

In the Types and Functions tab, try to activate all 3rd party -libraries you use (windows, posix, ...).

+

Choice A: Import a project (recommended)

-
+

If you build your code with Visual Studio, CMake, Meson, Bear or any other +tool that can produce a compile database, import it. Click Browse... +next to Import Project and select one of:

+
    +
  • a Visual Studio solution (*.sln, *.slnx) or + project (*.vcxproj),
  • +
  • a compile database (compile_commands.json). CMake creates it + if you configure with -DCMAKE_EXPORT_COMPILE_COMMANDS=ON,
  • +
  • a Borland C++ Builder 6 project (*.bpr).
  • +
-

In the Analysis tab, leave the default settings to start with.

+

Cppcheck then reads the list of source files, the include paths, the +preprocessor defines and the language standard from the imported file. The +Paths, Include Paths and Undefines fields are disabled +because the values come from the imported project.

-

In the Warnings options tab, leave the default settings to start -with.

+

-

In the Addons tab, leave the default settings to start with.

+

Choice B: Enter the paths yourself

+

If you can not import a project, enter the information manually:

+
    +
  • Paths: click Add... and select the folders (or files) to + analyze. Cppcheck analyzes all C/C++ source files in these folders and + their subfolders.
  • +
  • Include Paths: add the folders where your own header files are. + Cppcheck needs your headers to understand types, macros and functions. + You do not need to add the standard library or system headers; Cppcheck + has built-in knowledge about them.
  • +
  • Defines: optionally enter macros, for example + DEBUG;VERSION=3.
  • +
-

Step 2: Analyze code.

+

-

When the project file has been created, the analysis will start -automatically.

+

Step 3: Select libraries

-

While analysis is performed in the background, you can investigate the -results.

+

Open the Types and Functions tab. In the Libraries list, +check the libraries your code uses, for example posix, windows, +qt, boost, gtk or sdl. A library configuration tells +Cppcheck how the functions and types in that library behave: which functions +allocate memory, which arguments must not be null, which functions never +return, and so on. This gives you more and better warnings.

-
+

Set the Platform to the target of your code if it differs from the +computer you run Cppcheck on. This matters for portability and overflow +checks, since the sizes of int, long and pointers +differ between platforms.

+

-

Step 3: Investigate warnings.

+

Step 4: Leave the rest as it is

-

In the toolbar you choose what types of warnings you want to see -(error/warning/style/performance/portability/information).

+

The Analysis, Warning options and Addons tabs have good +default values. You can come back later and change them; see +Project files for a description of every +option.

-
+

Click OK. The project file is saved. If the build dir does not exist +yet, the GUI asks if it should create it: answer Yes.

+

Step 5: Watch the analysis

-

All warnings are shown in a list. If you select a warning in the list, then -details about that warning is shown.

+

The analysis starts immediately. A progress bar above the results shows +how many files have been analyzed. You do not have to wait: warnings appear +in the list as soon as they are found, and you can start looking at them. +To stop the analysis, click the Stop button in the toolbar or press +Esc.

-

If you right click on warning(s) then you get a context menu.

+

Step 6: Look at the results

-
+

The results list groups the warnings by file. Click the arrow next to a +file name, or use View > Expand all, to see the warnings. Click a +warning to show its details and the source code in the lower part of the +window. The line with the problem is highlighted.

-

The difference of "Hide" and "Suppress" is that -suppressions are saved in the project file. The suppressed warnings will not be -shown again unless you remove the suppression. When you hide a warning then -they will be temporarily hidden; the next time you analyze your code these -warnings will be shown again.

+

+ +

Some warnings have an arrow in front of them. Expand them to see +notes: these show the path through the code that leads to the +problem, for example where a variable got the value that later causes the +bug. Click a note to jump to that line.

+ +

Use the colored buttons in the toolbar to show or hide warnings by +severity. Start with errors and warnings; they are the most likely to be +real bugs. See Severities.

+ +

+ +

Right-click a warning to get more options: open the file in your editor, +copy the warning, hide it, suppress it or tag it.

+ +

+ +

Read Investigating warnings to +learn how to work efficiently with the results.

+ +

Step 7: Fix the code and analyze again

+ +

Fix the bugs in your editor. Then click Reanalyze all files in the +toolbar. Because the project has a build dir, Cppcheck only does the full +analysis again for files that changed (or that include a changed header). For +the other files the results are taken from the build dir, so this is +fast.

+ +

If you do not agree with a warning, you can suppress it so that it is not +shown again. See Hiding and suppressing +warnings.

+ +

Next time

+ +

The project files you used recently are listed in the File menu. +When you open a project that has a build dir, the GUI first shows the results +from the last analysis. Click Reanalyze all files in the toolbar to +analyze the code again. Thanks to the build dir, files that did not change +are not analyzed again; their old results are reused.

+ + - diff --git a/gui/helpdialog.cpp b/gui/helpdialog.cpp index 0768318531c..bef304c5471 100644 --- a/gui/helpdialog.cpp +++ b/gui/helpdialog.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2024 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -84,7 +84,7 @@ HelpDialog::HelpDialog(QWidget *parent) : if (helpFile.isEmpty()) { const QString msg = tr("Helpfile '%1' was not found").arg("online-help.qhc"); QMessageBox msgBox(QMessageBox::Warning, - tr("Cppcheck"), + "Cppcheck", msg, QMessageBox::Ok, this); diff --git a/gui/images/accessories-text-editor.png b/gui/images/accessories-text-editor.png new file mode 100644 index 00000000000..c3d245dece9 Binary files /dev/null and b/gui/images/accessories-text-editor.png differ diff --git a/gui/images/accessories-text-editor@2x.png b/gui/images/accessories-text-editor@2x.png new file mode 100644 index 00000000000..4f98e727f4b Binary files /dev/null and b/gui/images/accessories-text-editor@2x.png differ diff --git a/gui/images/accessories-text-editor@3x.png b/gui/images/accessories-text-editor@3x.png new file mode 100644 index 00000000000..a7ac91dbdca Binary files /dev/null and b/gui/images/accessories-text-editor@3x.png differ diff --git a/gui/images/applications-development@2x.png b/gui/images/applications-development@2x.png new file mode 100644 index 00000000000..ffa971cf026 Binary files /dev/null and b/gui/images/applications-development@2x.png differ diff --git a/gui/images/applications-development@3x.png b/gui/images/applications-development@3x.png new file mode 100644 index 00000000000..c6af10a8c8c Binary files /dev/null and b/gui/images/applications-development@3x.png differ diff --git a/gui/images/applications-system@2x.png b/gui/images/applications-system@2x.png new file mode 100644 index 00000000000..f4c85d6bf08 Binary files /dev/null and b/gui/images/applications-system@2x.png differ diff --git a/gui/images/applications-system@3x.png b/gui/images/applications-system@3x.png new file mode 100644 index 00000000000..d3e4bea5dd1 Binary files /dev/null and b/gui/images/applications-system@3x.png differ diff --git a/gui/images/dialog-error@2x.png b/gui/images/dialog-error@2x.png new file mode 100644 index 00000000000..7b35ae62b8f Binary files /dev/null and b/gui/images/dialog-error@2x.png differ diff --git a/gui/images/dialog-error@3x.png b/gui/images/dialog-error@3x.png new file mode 100644 index 00000000000..bd56f682ca0 Binary files /dev/null and b/gui/images/dialog-error@3x.png differ diff --git a/gui/images/dialog-information@2x.png b/gui/images/dialog-information@2x.png new file mode 100644 index 00000000000..e5d60dfad0e Binary files /dev/null and b/gui/images/dialog-information@2x.png differ diff --git a/gui/images/dialog-information@3x.png b/gui/images/dialog-information@3x.png new file mode 100644 index 00000000000..bd265a75c58 Binary files /dev/null and b/gui/images/dialog-information@3x.png differ diff --git a/gui/images/dialog-warning@2x.png b/gui/images/dialog-warning@2x.png new file mode 100644 index 00000000000..e4060e0d85f Binary files /dev/null and b/gui/images/dialog-warning@2x.png differ diff --git a/gui/images/dialog-warning@3x.png b/gui/images/dialog-warning@3x.png new file mode 100644 index 00000000000..c9fb58eba39 Binary files /dev/null and b/gui/images/dialog-warning@3x.png differ diff --git a/gui/images/edit-clear@2x.png b/gui/images/edit-clear@2x.png new file mode 100644 index 00000000000..d3ac583ba79 Binary files /dev/null and b/gui/images/edit-clear@2x.png differ diff --git a/gui/images/edit-clear@3x.png b/gui/images/edit-clear@3x.png new file mode 100644 index 00000000000..0d9b8b928ca Binary files /dev/null and b/gui/images/edit-clear@3x.png differ diff --git a/gui/images/folder.png b/gui/images/folder.png new file mode 100644 index 00000000000..01f45b82aa5 Binary files /dev/null and b/gui/images/folder.png differ diff --git a/gui/images/folder@2x.png b/gui/images/folder@2x.png new file mode 100644 index 00000000000..dc42da0508f Binary files /dev/null and b/gui/images/folder@2x.png differ diff --git a/gui/images/folder@3x.png b/gui/images/folder@3x.png new file mode 100644 index 00000000000..0145b776055 Binary files /dev/null and b/gui/images/folder@3x.png differ diff --git a/gui/images/go-down@2x.png b/gui/images/go-down@2x.png new file mode 100644 index 00000000000..963fec7711e Binary files /dev/null and b/gui/images/go-down@2x.png differ diff --git a/gui/images/go-down@3x.png b/gui/images/go-down@3x.png new file mode 100644 index 00000000000..7423b6ad889 Binary files /dev/null and b/gui/images/go-down@3x.png differ diff --git a/gui/images/go-home@2x.png b/gui/images/go-home@2x.png new file mode 100644 index 00000000000..4cb6b5e806d Binary files /dev/null and b/gui/images/go-home@2x.png differ diff --git a/gui/images/go-home@3x.png b/gui/images/go-home@3x.png new file mode 100644 index 00000000000..0a3e9134507 Binary files /dev/null and b/gui/images/go-home@3x.png differ diff --git a/gui/images/go-next@2x.png b/gui/images/go-next@2x.png new file mode 100644 index 00000000000..2db28c810f1 Binary files /dev/null and b/gui/images/go-next@2x.png differ diff --git a/gui/images/go-next@3x.png b/gui/images/go-next@3x.png new file mode 100644 index 00000000000..5ab9353c318 Binary files /dev/null and b/gui/images/go-next@3x.png differ diff --git a/gui/images/go-previous@2x.png b/gui/images/go-previous@2x.png new file mode 100644 index 00000000000..d67aae9cb92 Binary files /dev/null and b/gui/images/go-previous@2x.png differ diff --git a/gui/images/go-previous@3x.png b/gui/images/go-previous@3x.png new file mode 100644 index 00000000000..1d2875d11b9 Binary files /dev/null and b/gui/images/go-previous@3x.png differ diff --git a/gui/images/help-browser@2x.png b/gui/images/help-browser@2x.png new file mode 100644 index 00000000000..3590ff11252 Binary files /dev/null and b/gui/images/help-browser@2x.png differ diff --git a/gui/images/help-browser@3x.png b/gui/images/help-browser@3x.png new file mode 100644 index 00000000000..9fd9898b829 Binary files /dev/null and b/gui/images/help-browser@3x.png differ diff --git a/gui/images/media-floppy@2x.png b/gui/images/media-floppy@2x.png new file mode 100644 index 00000000000..5222a17ecff Binary files /dev/null and b/gui/images/media-floppy@2x.png differ diff --git a/gui/images/media-floppy@3x.png b/gui/images/media-floppy@3x.png new file mode 100644 index 00000000000..aaf5e1a03d2 Binary files /dev/null and b/gui/images/media-floppy@3x.png differ diff --git a/gui/images/openproject.png b/gui/images/openproject.png deleted file mode 100644 index 63df868d1e4..00000000000 Binary files a/gui/images/openproject.png and /dev/null differ diff --git a/gui/images/preferences-system@2x.png b/gui/images/preferences-system@2x.png new file mode 100644 index 00000000000..1903ab50f48 Binary files /dev/null and b/gui/images/preferences-system@2x.png differ diff --git a/gui/images/preferences-system@3x.png b/gui/images/preferences-system@3x.png new file mode 100644 index 00000000000..e9aa07dad70 Binary files /dev/null and b/gui/images/preferences-system@3x.png differ diff --git a/gui/images/process-stop@2x.png b/gui/images/process-stop@2x.png new file mode 100644 index 00000000000..2548cb73125 Binary files /dev/null and b/gui/images/process-stop@2x.png differ diff --git a/gui/images/process-stop@3x.png b/gui/images/process-stop@3x.png new file mode 100644 index 00000000000..9bc59b6af54 Binary files /dev/null and b/gui/images/process-stop@3x.png differ diff --git a/gui/images/refresh-file.png b/gui/images/refresh-file.png new file mode 100644 index 00000000000..ea2eae814dd Binary files /dev/null and b/gui/images/refresh-file.png differ diff --git a/gui/images/refresh-file@2x.png b/gui/images/refresh-file@2x.png new file mode 100644 index 00000000000..48438c461e9 Binary files /dev/null and b/gui/images/refresh-file@2x.png differ diff --git a/gui/images/refresh-file@3x.png b/gui/images/refresh-file@3x.png new file mode 100644 index 00000000000..eed7540f6ae Binary files /dev/null and b/gui/images/refresh-file@3x.png differ diff --git a/gui/images/scratchpad.png b/gui/images/scratchpad.png deleted file mode 100644 index 7495eca6479..00000000000 Binary files a/gui/images/scratchpad.png and /dev/null differ diff --git a/gui/images/showerrors.png b/gui/images/showerrors.png deleted file mode 100644 index 6bad55fbd4a..00000000000 Binary files a/gui/images/showerrors.png and /dev/null differ diff --git a/gui/images/showperformance.png b/gui/images/showperformance.png deleted file mode 100644 index ea9a1c8abce..00000000000 Binary files a/gui/images/showperformance.png and /dev/null differ diff --git a/gui/images/showstylewarnings.png b/gui/images/showstylewarnings.png deleted file mode 100644 index 70de0695f8c..00000000000 Binary files a/gui/images/showstylewarnings.png and /dev/null differ diff --git a/gui/images/showwarnings.png b/gui/images/showwarnings.png deleted file mode 100644 index 196884717cd..00000000000 Binary files a/gui/images/showwarnings.png and /dev/null differ diff --git a/gui/images/text-x-generic@2x.png b/gui/images/text-x-generic@2x.png new file mode 100644 index 00000000000..8d0bf4fb9c1 Binary files /dev/null and b/gui/images/text-x-generic@2x.png differ diff --git a/gui/images/text-x-generic@3x.png b/gui/images/text-x-generic@3x.png new file mode 100644 index 00000000000..fea2f3cd65b Binary files /dev/null and b/gui/images/text-x-generic@3x.png differ diff --git a/gui/images/utilities-system-monitor@2x.png b/gui/images/utilities-system-monitor@2x.png new file mode 100644 index 00000000000..b0303db204c Binary files /dev/null and b/gui/images/utilities-system-monitor@2x.png differ diff --git a/gui/images/utilities-system-monitor@3x.png b/gui/images/utilities-system-monitor@3x.png new file mode 100644 index 00000000000..bd1c41e011e Binary files /dev/null and b/gui/images/utilities-system-monitor@3x.png differ diff --git a/gui/images/view-recheck@2x.png b/gui/images/view-recheck@2x.png new file mode 100644 index 00000000000..bcb08d91cac Binary files /dev/null and b/gui/images/view-recheck@2x.png differ diff --git a/gui/images/view-recheck@3x.png b/gui/images/view-recheck@3x.png new file mode 100644 index 00000000000..6f1ac358f02 Binary files /dev/null and b/gui/images/view-recheck@3x.png differ diff --git a/gui/images/view-refresh.png b/gui/images/view-refresh.png deleted file mode 100644 index 5fadc405f86..00000000000 Binary files a/gui/images/view-refresh.png and /dev/null differ diff --git a/gui/librarydialog.cpp b/gui/librarydialog.cpp index 3199af9a975..147363a3a25 100644 --- a/gui/librarydialog.cpp +++ b/gui/librarydialog.cpp @@ -107,7 +107,7 @@ void LibraryDialog::openCfg() QFile file(selectedFile); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { QMessageBox msg(QMessageBox::Critical, - tr("Cppcheck"), + "Cppcheck", tr("Cannot open file %1.").arg(selectedFile), QMessageBox::Ok, this); @@ -119,7 +119,7 @@ void LibraryDialog::openCfg() const QString errmsg = tempdata.open(file); if (!errmsg.isNull()) { QMessageBox msg(QMessageBox::Critical, - tr("Cppcheck"), + "Cppcheck", tr("Failed to load %1. %2.").arg(selectedFile).arg(errmsg), QMessageBox::Ok, this); @@ -156,7 +156,7 @@ void LibraryDialog::saveCfg() mUi->buttonSave->setEnabled(false); } else { QMessageBox msg(QMessageBox::Critical, - tr("Cppcheck"), + "Cppcheck", tr("Cannot save file %1.").arg(mFileName), QMessageBox::Ok, this); diff --git a/gui/librarydialog.h b/gui/librarydialog.h index 285d36a820a..c8e522b10d4 100644 --- a/gui/librarydialog.h +++ b/gui/librarydialog.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2024 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -47,10 +47,10 @@ private slots: void addFunction(); void changeFunction(); void editArg(); - void editFunctionName(QListWidgetItem* /*item*/); - void filterFunctions(const QString& /*filter*/); + void editFunctionName(QListWidgetItem* item); + void filterFunctions(const QString& filter); void selectFunction(); - void sortFunctions(bool /*sort*/); + void sortFunctions(bool sort); private: Ui::LibraryDialog *mUi; diff --git a/gui/main.cpp b/gui/main.cpp index efdb2a33c72..c855b2529f6 100644 --- a/gui/main.cpp +++ b/gui/main.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 161eec381b3..5ddb790b77b 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -23,14 +23,12 @@ #include "aboutdialog.h" #include "analyzerinfo.h" #include "checkers.h" -#include "checkstatistics.h" #include "checkthread.h" #include "common.h" #include "cppcheck.h" #include "errortypes.h" #include "filelist.h" #include "filesettings.h" -#include "compliancereportdialog.h" #include "fileviewdialog.h" #include "helpdialog.h" #include "importproject.h" @@ -99,10 +97,10 @@ #include #include #include +#include #include #include #include -#include #include #include #include @@ -122,6 +120,27 @@ static QString fromNativePath(const QString& p) { #endif } +// The files selected in the results view are not necessarily spelled the same way as the +// project or files.txt spells them, compare them as absolute paths +static QString comparablePath(const QString& path) { + return QDir::cleanPath(QFileInfo(path).absoluteFilePath()); +} + +static QSet comparablePaths(const QStringList& paths) { + QSet ret; + std::transform(paths.cbegin(), paths.cend(), std::inserter(ret, ret.end()), comparablePath); + return ret; +} + +template> +static T toStdStringList(const QStringList& stringList) { + T ret; + std::transform(stringList.cbegin(), stringList.cend(), std::back_inserter(ret), [](const QString& s) { + return s.toStdString(); + }); + return ret; +} + MainWindow::MainWindow(TranslationHandler* th, QSettings* settings) : mSettings(settings), mApplications(new ApplicationList(this)), @@ -193,7 +212,6 @@ MainWindow::MainWindow(TranslationHandler* th, QSettings* settings) : connect(mUI->mActionStop, &QAction::triggered, this, &MainWindow::stopAnalysis); connect(mUI->mActionSave, &QAction::triggered, this, &MainWindow::save); - connect(mUI->mActionComplianceReport, &QAction::triggered, this, &MainWindow::complianceReport); // About menu connect(mUI->mActionAbout, &QAction::triggered, this, &MainWindow::about); @@ -245,8 +263,6 @@ MainWindow::MainWindow(TranslationHandler* th, QSettings* settings) : formatAndSetTitle(); } - mUI->mActionComplianceReport->setVisible(isCppcheckPremium()); - enableCheckButtons(true); mUI->mActionPrint->setShortcut(QKeySequence::Print); @@ -463,7 +479,7 @@ void MainWindow::loadSettings() "Please check (and fix) the editor application settings, otherwise the editor " "program might not start correctly."); QMessageBox msgBox(QMessageBox::Warning, - tr("Cppcheck"), + "Cppcheck", msg, QMessageBox::Ok, this); @@ -566,14 +582,31 @@ void MainWindow::saveSettings() const mUI->mResults->saveSettings(mSettings); } -void MainWindow::doAnalyzeProject(ImportProject p, const bool checkLibrary, const bool checkConfiguration) +void MainWindow::doAnalyzeProject(ImportProject p, const bool checkLib, const bool checkConfig, const QStringList& selectedFiles, const Recheck recheck) { Settings checkSettings; auto supprs = std::make_shared(); if (!getCppcheckSettings(checkSettings, *supprs)) return; - clearResults(); + // Filter the selected files + if (recheck == Recheck::Selected) { + const QSet selected = comparablePaths(selectedFiles); + p.fileSettings.remove_if([&](const FileSettings& fs) { + return !selected.contains(comparablePath(QString::fromStdString(fs.filename()))); + }); + if (p.fileSettings.empty()) { + QMessageBox msg(QMessageBox::Information, + "Cppcheck", + tr("None of the selected files is part of the project, nothing to analyze."), + QMessageBox::Ok, + this); + msg.exec(); + return; + } + } + + clearResultsForAnalysis(checkSettings.buildDir, selectedFiles, recheck); mIsLogfileLoaded = false; if (mProjectFile) { @@ -584,7 +617,7 @@ void MainWindow::doAnalyzeProject(ImportProject p, const bool checkLibrary, cons }); p.ignorePaths(v); - if (!mProjectFile->getAnalyzeAllVsConfigs()) { + if (checkSettings.platform.type == Platform::Native && !mProjectFile->getAnalyzeAllVsConfigs()) { const Platform::Type platform = static_cast(mSettings->value(SETTINGS_CHECKED_PLATFORM, 0).toInt()); std::vector configurations; const QStringList configs = mProjectFile->getVsConfigurations(); @@ -610,8 +643,8 @@ void MainWindow::doAnalyzeProject(ImportProject p, const bool checkLibrary, cons mUI->mResults->setCheckDirectory(checkPath); checkSettings.force = false; - checkSettings.checkLibrary = checkLibrary; - checkSettings.checkConfiguration = checkConfiguration; + checkSettings.checkLibrary = checkLib; + checkSettings.checkConfiguration = checkConfig; if (mProjectFile) qDebug() << "Checking project file" << mProjectFile->getFilename(); @@ -638,7 +671,7 @@ void MainWindow::doAnalyzeProject(ImportProject p, const bool checkLibrary, cons mUI->mResults->setCheckSettings(checkSettings); } -void MainWindow::doAnalyzeFiles(const QStringList &files, const bool checkLibrary, const bool checkConfiguration) +void MainWindow::doAnalyzeFiles(const QStringList &files, const bool checkLib, const bool checkConfig, const QStringList& selectedFiles, const Recheck recheck) { if (files.isEmpty()) return; @@ -648,7 +681,7 @@ void MainWindow::doAnalyzeFiles(const QStringList &files, const bool checkLibrar if (!getCppcheckSettings(checkSettings, *supprs)) return; - clearResults(); + clearResultsForAnalysis(checkSettings.buildDir, selectedFiles, recheck); mIsLogfileLoaded = false; FileList pathList; @@ -660,13 +693,12 @@ void MainWindow::doAnalyzeFiles(const QStringList &files, const bool checkLibrar } QStringList fileNames = pathList.getFileList(); - mUI->mResults->clear(true); mUI->mResults->setResultsSource(ResultsTree::ResultsSource::Analysis); mThread->clearFiles(); if (fileNames.isEmpty()) { QMessageBox msg(QMessageBox::Warning, - tr("Cppcheck"), + "Cppcheck", tr("No suitable files found to analyze!"), QMessageBox::Ok, this); @@ -679,7 +711,7 @@ void MainWindow::doAnalyzeFiles(const QStringList &files, const bool checkLibrar // TODO: lock UI here? mUI->mResults->checkingStarted(fdetails.size()); mThread->setFiles(std::move(fdetails)); - if (mProjectFile && !checkConfiguration) + if (mProjectFile && !checkConfig) mThread->setAddonsAndTools(mProjectFile->getAddonsAndTools()); mThread->setSuppressions(mProjectFile ? mProjectFile->getCheckingSuppressions() : QList()); QDir inf(mCurrentDirectory); @@ -689,18 +721,15 @@ void MainWindow::doAnalyzeFiles(const QStringList &files, const bool checkLibrar checkLockDownUI(); // lock UI while checking mUI->mResults->setCheckDirectory(checkPath); - checkSettings.checkLibrary = checkLibrary; - checkSettings.checkConfiguration = checkConfiguration; + checkSettings.checkLibrary = checkLib; + checkSettings.checkConfiguration = checkConfig; if (mProjectFile) qDebug() << "Checking project file" << mProjectFile->getFilename(); if (!checkSettings.buildDir.empty()) { checkSettings.loadSummaries(); - std::list sourcefiles; - std::transform(fileNames.cbegin(), fileNames.cend(), std::back_inserter(sourcefiles), [](const QString& s) { - return s.toStdString(); - }); + const auto& sourcefiles = toStdStringList>(fileNames); AnalyzerInformation::writeFilesTxt(checkSettings.buildDir, sourcefiles, {}); } @@ -751,7 +780,7 @@ QStringList MainWindow::selectFilesToAnalyze(QFileDialog::FileMode mode) { if (mProjectFile) { QMessageBox msgBox(this); - msgBox.setWindowTitle(tr("Cppcheck")); + msgBox.setWindowTitle("Cppcheck"); const QString msg(tr("You must close the project file before selecting new files or directories!")); msgBox.setText(msg); msgBox.setIcon(QMessageBox::Critical); @@ -768,8 +797,8 @@ QStringList MainWindow::selectFilesToAnalyze(QFileDialog::FileMode mode) QMap filters; filters[tr("C/C++ Source")] = FileList::getDefaultFilters().join(" "); filters[tr("Compile database")] = compile_commands_json; - filters[tr("Visual Studio")] = "*.sln *.slnx *.vcxproj"; - filters[tr("Borland C++ Builder 6")] = "*.bpr"; + filters["Visual Studio"] = "*.sln *.slnx *.vcxproj"; + filters["Borland C++ Builder 6"] = "*.bpr"; QString lastFilter = mSettings->value(SETTINGS_LAST_ANALYZE_FILES_FILTER).toString(); selected = QFileDialog::getOpenFileNames(this, tr("Select files to analyze"), @@ -857,7 +886,7 @@ void MainWindow::analyzeDirectory() if (projFiles.size() == 1) { // If one project file found, suggest loading it QMessageBox msgBox(this); - msgBox.setWindowTitle(tr("Cppcheck")); + msgBox.setWindowTitle("Cppcheck"); const QString msg(tr("Found project file: %1\n\nDo you want to " "load this project file instead?").arg(projFiles[0])); msgBox.setText(msg); @@ -879,7 +908,7 @@ void MainWindow::analyzeDirectory() // If multiple project files found inform that there are project // files also available. QMessageBox msgBox(this); - msgBox.setWindowTitle(tr("Cppcheck")); + msgBox.setWindowTitle("Cppcheck"); const QString msg(tr("Found project files from the directory.\n\n" "Do you want to proceed analysis without " "using any of these project files?")); @@ -1126,6 +1155,8 @@ bool MainWindow::getCppcheckSettings(Settings& settings, Suppressions& supprs) for (const QString& undefine : undefines) settings.userUndefs.insert(undefine.toStdString()); + mProjectFile->setSettingsUserIncludes(settings); + const QStringList libraries = mProjectFile->getLibraries(); for (const QString& library : libraries) { settings.libraries.emplace_back(library.toStdString()); @@ -1153,10 +1184,7 @@ bool MainWindow::getCppcheckSettings(Settings& settings, Suppressions& supprs) const QString platform = mProjectFile->getPlatform(); if (platform.endsWith(".xml")) { - const std::vector paths = { - Path::getCurrentPath(), // TODO: do we want to look in CWD? - QCoreApplication::applicationFilePath().toStdString(), - }; + const std::vector paths = toStdStringList(mProjectFile->getSearchPaths("platform")); settings.platform.loadFromFile(paths, platform.toStdString()); } else { for (int i = Platform::Type::Native; i <= Platform::Type::Unix64; i++) { @@ -1374,11 +1402,6 @@ void MainWindow::reAnalyzeSelected(const QStringList& files) return; if (mProjectFile) { - // Clear details, statistics and progress - mUI->mResults->clear(false); - for (int i = 0; i < files.size(); ++i) - mUI->mResults->clearRecheckFile(files[i]); - analyzeProject(mProjectFile, files); return; } @@ -1388,10 +1411,7 @@ void MainWindow::reAnalyzeSelected(const QStringList& files) if (!getCppcheckSettings(checkSettings, *supprs)) return; - // Clear details, statistics and progress - mUI->mResults->clear(false); - for (int i = 0; i < files.size(); ++i) - mUI->mResults->clearRecheckFile(files[i]); + clearResultsForAnalysis(checkSettings.buildDir, files, Recheck::Selected); mCurrentDirectory = mUI->mResults->getCheckDirectory(); FileList pathList; @@ -1446,7 +1466,7 @@ void MainWindow::reAnalyze(bool all) void MainWindow::clearResults() { if (mProjectFile && !mProjectFile->getBuildDir().isEmpty()) { - QDir dir(QFileInfo(mProjectFile->getFilename()).absolutePath() + '/' + mProjectFile->getBuildDir()); + QDir dir(QDir(QFileInfo(mProjectFile->getFilename()).absolutePath()).filePath(mProjectFile->getBuildDir())); for (const QString& f: dir.entryList(QDir::Files)) { if (!f.endsWith("files.txt")) { static const QRegularExpression rx("^.*.s[0-9]+$"); @@ -1460,11 +1480,47 @@ void MainWindow::clearResults() enableResultsButtons(); } +void MainWindow::clearResultsForAnalysis(const std::string& buildDir, const QStringList& selectedFiles, const Recheck recheck) +{ + switch (recheck) { + case Recheck::None: + clearResults(); + break; + case Recheck::Selected: + // Only the selected files are analyzed, the results of the other files are kept + mUI->mResults->clear(false); + for (const QString& f : selectedFiles) + mUI->mResults->clearRecheckFile(f); + enableResultsButtons(); + break; + case Recheck::Cached: { + // Every file reports its results again, either analyzed or replayed from the analyzer + // information. Remove the analyzer information of the selected files so that they really + // are analyzed again, also by the clang tools that keep their results next to it. + const QSet selected = comparablePaths(selectedFiles); + QDir dir(QString::fromStdString(buildDir)); + std::ifstream filesTxt(buildDir + "/files.txt"); + std::string line; + while (std::getline(filesTxt, line)) { + AnalyzerInformation::Info info; + if (!info.parse(line) || !selected.contains(comparablePath(QString::fromStdString(info.sourceFile)))) + continue; + const QString afile = QString::fromStdString(info.afile); + for (const QString& f : dir.entryList({afile, afile + ".*"}, QDir::Files)) + dir.remove(f); + } + mUI->mResults->clear(true); + enableResultsButtons(); + break; + } + } +} + void MainWindow::openResults() { if (mUI->mResults->hasResults()) { QMessageBox msgBox(this); - msgBox.setWindowTitle(tr("Cppcheck")); + msgBox.setWindowTitle("Cppcheck"); const QString msg(tr("Current results will be cleared.\n\n" "Opening a new XML file will clear current results.\n" "Do you want to proceed?")); @@ -1528,10 +1584,6 @@ void MainWindow::enableCheckButtons(bool enable) } mUI->mActionAnalyzeDirectory->setEnabled(enable); - - if (isCppcheckPremium()) { - mUI->mActionComplianceReport->setEnabled(enable && mProjectFile && (mProjectFile->getAddons().contains("misra") || !mProjectFile->getCodingStandards().empty())); - } } void MainWindow::enableResultsButtons() @@ -1593,7 +1645,7 @@ void MainWindow::closeEvent(QCloseEvent *event) "Do you want to stop the analysis and exit Cppcheck?")); QMessageBox msg(QMessageBox::Warning, - tr("Cppcheck"), + "Cppcheck", text, QMessageBox::Yes | QMessageBox::No, this); @@ -1705,28 +1757,6 @@ void MainWindow::save() } } -void MainWindow::complianceReport() -{ - if (!mUI->mResults->isSuccess()) { - QMessageBox m(QMessageBox::Critical, - "Cppcheck", - tr("Cannot generate a compliance report right now, an analysis must finish successfully. Try to reanalyze the code and ensure there are no critical errors."), - QMessageBox::Ok, - this); - m.exec(); - return; - } - - QTemporaryFile tempResults; - (void)tempResults.open(); // TODO: check result - tempResults.close(); - - mUI->mResults->save(tempResults.fileName(), Report::XMLV2, mCppcheckCfgProductName); - - ComplianceReportDialog dlg(mProjectFile, tempResults.fileName(), mUI->mResults->getStatistics()->getCheckersReport()); - dlg.exec(); -} - void MainWindow::resultsAdded() { enableResultsButtons(); @@ -1884,37 +1914,20 @@ bool MainWindow::loadLastResults() return true; } -void MainWindow::analyzeProject(const ProjectFile *projectFile, const QStringList& recheckFiles, const bool checkLibrary, const bool checkConfiguration) +void MainWindow::analyzeProject(const ProjectFile *projectFile, const QStringList& selectedFiles, const bool checkLib, const bool checkConfig) { Settings::terminate(false); + // With a build directory the whole project is analyzed: the analyzer information cache skips + // the unchanged files, and files.txt stays complete so whole program analysis sees every file. + // Without a build directory there is no cache, so only the selected files are analyzed. + Recheck recheck = Recheck::None; + if (!selectedFiles.isEmpty()) + recheck = projectFile->getBuildDir().isEmpty() ? Recheck::Selected : Recheck::Cached; + QFileInfo inf(projectFile->getFilename()); const QString& rootpath = projectFile->getRootPath(); - if (isCppcheckPremium() && !projectFile->getLicenseFile().isEmpty()) { - if (rootpath.isEmpty() || rootpath == ".") - QDir::setCurrent(inf.absolutePath()); - else if (QDir(rootpath).isAbsolute()) - QDir::setCurrent(rootpath); - else - QDir::setCurrent(inf.absolutePath() + "/" + rootpath); - - QString licenseFile = projectFile->getLicenseFile(); - if (!QFileInfo(licenseFile).isAbsolute() && !rootpath.isEmpty()) - licenseFile = inf.absolutePath() + "/" + licenseFile; - -#ifdef Q_OS_WIN - const QString premiumaddon = QCoreApplication::applicationDirPath() + "/premiumaddon.exe"; -#else - const QString premiumaddon = QCoreApplication::applicationDirPath() + "/premiumaddon"; -#endif - const std::vector args{"--check-loc-license", licenseFile.toStdString()}; - std::string output; - CheckThread::executeCommand(premiumaddon.toStdString(), args, "", output); - std::ofstream fout(inf.absolutePath().toStdString() + "/cppcheck-premium-loc"); - fout << output; - } - QDir::setCurrent(inf.absolutePath()); mThread->setAddonsAndTools(projectFile->getAddonsAndTools()); @@ -1934,7 +1947,7 @@ void MainWindow::analyzeProject(const ProjectFile *projectFile, const QStringLis buildDir = inf.canonicalPath() + '/' + buildDir; if (!QDir(buildDir).exists()) { QMessageBox msg(QMessageBox::Question, - tr("Cppcheck"), + "Cppcheck", tr("Build dir '%1' does not exist, create it?").arg(buildDir), QMessageBox::Yes | QMessageBox::No, this); @@ -1942,7 +1955,7 @@ void MainWindow::analyzeProject(const ProjectFile *projectFile, const QStringLis QDir().mkpath(buildDir); } else if (!projectFile->getAddons().isEmpty()) { QMessageBox m(QMessageBox::Critical, - tr("Cppcheck"), + "Cppcheck", tr("To check the project using addons, you need a build directory."), QMessageBox::Ok, this); @@ -1997,7 +2010,7 @@ void MainWindow::analyzeProject(const ProjectFile *projectFile, const QStringLis if (!errorMessage.isEmpty()) { QMessageBox msg(QMessageBox::Critical, - tr("Cppcheck"), + "Cppcheck", tr("Failed to import '%1': %2\n\nAnalysis is stopped.").arg(prjfile).arg(errorMessage), QMessageBox::Ok, this); @@ -2006,18 +2019,18 @@ void MainWindow::analyzeProject(const ProjectFile *projectFile, const QStringLis } } catch (InternalError &e) { QMessageBox msg(QMessageBox::Critical, - tr("Cppcheck"), + "Cppcheck", tr("Failed to import '%1' (%2), analysis is stopped").arg(prjfile).arg(QString::fromStdString(e.errorMessage)), QMessageBox::Ok, this); msg.exec(); return; } - doAnalyzeProject(p, checkLibrary, checkConfiguration); // TODO: avoid copy + doAnalyzeProject(p, checkLib, checkConfig, selectedFiles, recheck); // TODO: avoid copy return; } - QStringList paths = recheckFiles.isEmpty() ? projectFile->getCheckPaths() : recheckFiles; + QStringList paths = recheck == Recheck::Selected ? selectedFiles : projectFile->getCheckPaths(); // If paths not given then check the root path (which may be the project // file's location, see above). This is to keep the compatibility with @@ -2026,7 +2039,7 @@ void MainWindow::analyzeProject(const ProjectFile *projectFile, const QStringLis if (paths.isEmpty()) { paths << mCurrentDirectory; } - doAnalyzeFiles(paths, checkLibrary, checkConfiguration); + doAnalyzeFiles(paths, checkLib, checkConfig, selectedFiles, recheck); } void MainWindow::newProjectFile() @@ -2078,7 +2091,7 @@ void MainWindow::editProjectFile() { if (!mProjectFile) { QMessageBox msg(QMessageBox::Critical, - tr("Cppcheck"), + "Cppcheck", tr("No project file loaded"), QMessageBox::Ok, this); @@ -2166,7 +2179,7 @@ void MainWindow::openRecentProject() "used projects -list?").arg(project)); QMessageBox msg(QMessageBox::Warning, - tr("Cppcheck"), + "Cppcheck", text, QMessageBox::Yes | QMessageBox::No, this); diff --git a/gui/mainwindow.h b/gui/mainwindow.h index 45506f3f6b8..a92476a8c20 100644 --- a/gui/mainwindow.h +++ b/gui/mainwindow.h @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -176,9 +177,6 @@ public slots: /** @brief Slot to save results */ void save(); - /** @brief Slot to generate compliance report */ - void complianceReport(); - /** @brief Slot to create new project file */ void newProjectFile(); @@ -252,6 +250,19 @@ private slots: void changeReportType(); private: + /** @brief How an analysis treats the files selected for a recheck */ + enum class Recheck : std::uint8_t { + /** Not a recheck, everything is analyzed from scratch */ + None, + /** Only the selected files are analyzed, the results of the other files are kept */ + Selected, + /** + * Everything is analyzed. The analyzer information in the build directory is kept, + * except for the selected files, so the unchanged files are not analyzed again. + */ + Cached + }; + bool isCppcheckPremium() const; /** Get filename for last results */ @@ -269,11 +280,11 @@ private slots: /** * @brief Analyze the project. * @param projectFile Pointer to the project to analyze. - * @param recheckFiles files to recheck, empty => check all files - * @param checkLibrary Flag to indicate if the library should be checked. - * @param checkConfiguration Flag to indicate if the configuration should be checked. + * @param selectedFiles files to recheck, empty => check all files + * @param checkLib Flag to indicate if the library should be checked. + * @param checkConfig Flag to indicate if the configuration should be checked. */ - void analyzeProject(const ProjectFile *projectFile, const QStringList& recheckFiles, bool checkLibrary = false, bool checkConfiguration = false); + void analyzeProject(const ProjectFile *projectFile, const QStringList& selectedFiles, bool checkLib = false, bool checkConfig = false); /** * @brief Set current language @@ -309,19 +320,31 @@ private slots: /** * @brief Analyze project * @param p imported project - * @param checkLibrary Flag to indicate if library should be checked - * @param checkConfiguration Flag to indicate if the configuration should be checked. + * @param checkLib Flag to indicate if library should be checked + * @param checkConfig Flag to indicate if the configuration should be checked. + * @param selectedFiles Files selected for a recheck + * @param recheck How the selected files are rechecked */ - void doAnalyzeProject(ImportProject p, bool checkLibrary = false, bool checkConfiguration = false); + void doAnalyzeProject(ImportProject p, bool checkLib = false, bool checkConfig = false, const QStringList& selectedFiles = QStringList(), Recheck recheck = Recheck::None); /** * @brief Analyze all files specified in parameter files * * @param files List of files and/or directories to analyze - * @param checkLibrary Flag to indicate if library should be checked - * @param checkConfiguration Flag to indicate if the configuration should be checked. + * @param checkLib Flag to indicate if library should be checked + * @param checkConfig Flag to indicate if the configuration should be checked. + * @param selectedFiles Files selected for a recheck + * @param recheck How the selected files are rechecked + */ + void doAnalyzeFiles(const QStringList &files, bool checkLib = false, bool checkConfig = false, const QStringList& selectedFiles = QStringList(), Recheck recheck = Recheck::None); + + /** + * @brief Clear the results that a new analysis replaces + * @param buildDir Build directory of the analysis + * @param selectedFiles Files selected for a recheck + * @param recheck How the selected files are rechecked */ - void doAnalyzeFiles(const QStringList &files, bool checkLibrary = false, bool checkConfiguration = false); + void clearResultsForAnalysis(const std::string& buildDir, const QStringList& selectedFiles, Recheck recheck); /** * @brief Get our default cppcheck settings and read project file. diff --git a/gui/mainwindow.ui b/gui/mainwindow.ui index 551874d3225..33201f86c78 100644 --- a/gui/mainwindow.ui +++ b/gui/mainwindow.ui @@ -23,7 +23,7 @@ - Cppcheck + Cppcheck @@ -140,7 +140,6 @@ - @@ -364,7 +363,7 @@ - :/images/view-refresh.png:/images/view-refresh.png + :/images/refresh-file.png:/images/refresh-file.png &Reanalyze modified files @@ -462,7 +461,7 @@ - :/images/showerrors.png:/images/showerrors.png + :/images/dialog-error.png:/images/dialog-error.png E&rrors @@ -540,7 +539,7 @@ - :/images/openproject.png:/images/openproject.png + :/images/folder.png:/images/folder.png Open P&roject File... @@ -552,7 +551,7 @@ - :/images/scratchpad.png:/images/scratchpad.png + :/images/accessories-text-editor.png:/images/accessories-text-editor.png Sh&ow Scratchpad... @@ -608,7 +607,7 @@ - :/images/showwarnings.png:/images/showwarnings.png + :/images/dialog-warning.png:/images/dialog-warning.png &Warnings @@ -626,7 +625,7 @@ - :/images/showperformance.png:/images/showperformance.png + :/images/utilities-system-monitor.png:/images/utilities-system-monitor.png Per&formance warnings @@ -685,7 +684,7 @@ :/cppcheck-gui.png:/cppcheck-gui.png - Cppcheck + Cppcheck Show Cppcheck results @@ -993,7 +992,7 @@ true - Misra C + Misra C @@ -1001,7 +1000,7 @@ true - Misra C++ 2008 + Misra C++ 2008 @@ -1009,7 +1008,7 @@ true - Cert C + Cert C @@ -1017,7 +1016,7 @@ true - Cert C++ + Cert C++ @@ -1025,7 +1024,7 @@ true - Misra C++ 2023 + Misra C++ 2023 diff --git a/gui/manualtest/docs/handbook.html b/gui/manualtest/docs/handbook.html new file mode 100644 index 00000000000..1da9f571224 --- /dev/null +++ b/gui/manualtest/docs/handbook.html @@ -0,0 +1,453 @@ + + + + + +gui-test Handbook + + + +
+ +
+
gui-test
+

gui-test Handbook

+

The manual tests for cppcheck-gui are written in Gherkin, the plain-language Given/When/Then format. gui-test turns them into automated tests: Claude explores the GUI once and writes a script, and from then on the script is replayed without any AI.

+

Updated 2026-10-24

+ +

Overview

+

Working with gui-test has three steps:

+
    +
  1. Write the test in plain words: what the starting point is, what you do and what you expect to see.
  2. +
  3. Generate the script, once. Claude does the test in the application and writes down what it did as a script.
  4. +
  5. Run the tests as often as you like. The scripts are replayed without AI, and you get a report with the results and screenshots.
  6. +
+

All of this is done in the web interface. The application runs on its own virtual screen, so your desktop is not touched while tests run.

+ +
+ + + Claude writes the script once, every run replays it without AI + + + + + + + + + + A test in words + Gherkin scenarios + + gen.py + Claude does it once + + Replay passes? + + Script saved + in FILE.scripts.json + + run.py + replays it, no AI + + report.html + and results.json + yes + no: Claude gets the reason, up to 3 tries + every run + +
From a test in words to a report: gen.py once, run.py every run.
+
+ +

The tests are kept in tests/, and every run puts its report, logs and screenshots in a new directory under results/.

+ +

Getting started

+

Start web.py with the cppcheck-gui binaries you want to test, then open http://127.0.0.1:8080/ in a browser.

+
./web.py --opensource ~/cppcheck/build/bin/cppcheck-gui \
+         --premium ~/premium/build/bin/cppcheck-gui --src ~/cppcheck
+
    +
  • --opensource and --premium each take the binary of that edition. Give one, both or none; without a binary you can edit tests but not generate or run them.
  • +
  • --src is the git checkout the binaries are built from. It is only used for the git SHA, branch and commit in the report; the default is the directory of the binary.
  • +
  • --dir (default tests), --out (default results) and --port (default 8080) change where test files and results live and which port is used. The server only listens on 127.0.0.1.
  • +
+

Some packages must be installed first. On Ubuntu (checked on 26.04):

+
sudo apt install xvfb xdotool dbus at-spi2-core python3-gi gir1.2-atspi-2.0 python3-pil tesseract-ocr tesseract-ocr-eng
+

The tesseract packages are only needed for ocr checks. To generate scripts the claude command line tool must be installed too.

+ +

The web interface

+
    +
  • Header: Save, Run all (every test in every file), the Edition picker, a status message and a link to this handbook. The Edition picker only shows the editions that web.py got a binary for, and "both" when it got two. Without a binary there is no picker, and tests can be edited but not generated or run.
  • +
  • Left column: three boxes. +
      +
    • Test files, with New file. A file is flagged invalid when a test in it has errors, and regenerate N when N tests have an outdated script.
    • +
    • Test projects, with New test project.
    • +
    • Results: Reports lists the earlier runs with their date, edition, version, test files and result. Click a date to open the report.
    • +
  • +
  • Main area: the open test file with its tests, the open test, or the open test project. Each has an ✕ in the top right corner to close it.
  • +
  • Job box: while a script is generated or tests run, it shows the progress, the results as they come in and a link to the report. Stop ends the job. Its ✕ hides the box until the next job.
  • +
+ +

Gherkin

+

The manual tests are written in Gherkin, the language that Cucumber, behave and other behaviour-driven development tools use. Gherkin describes behaviour in plain words with a small set of keywords, so a test reads the same for a developer, a tester and Claude. Each test file in tests/ is a .feature file with one feature, and every scenario in it is one test.

+
Feature: Preferences
+
+  Background:
+    Given the application is started with no project loaded
+
+  @id:t28ce8afe @project:embedded @edition:opensource
+  Scenario Outline: Misra rule text file
+    Given Edit > Preferences is open on the Addons tab
+    When I select the rule text file {project:embedded}/<rulefile>
+    And I close the dialog with OK
+    And I open the project file {project:embedded}/embedded.cppcheck
+    And I wait until the analysis is done
+    Then the Misra error for the unused parameter has the rule text from the file
+    And there are no internalError results
+
+    Examples:
+      | rulefile              |
+      | rules.txt             |
+      | rules with spaces.txt |
+ +

Keywords

+
+ + + + + + + + + + + + + + + + +
KeywordMeaning here
Feature:The first line of the file. Its text is the title of the test file, shown in the editor and the report. Lines below it, before the first scenario, are a free description
Background:Steps that come before every scenario of the file, typically Given the application is started with no project loaded. At most one, before the scenarios
Scenario:One test. The text after the colon is the name of the test, unique within the file. Example: means the same
Scenario Outline:A test that is run once for every row of its Examples table. Scenario Template: means the same
Examples:The table of a scenario outline. The first row is the header with the column names; <column> in a step is replaced by the value of the row. See Examples (test matrix)
GivenThe starting point: the state that the application is in before the test does anything
WhenAn action of the user: a click, a menu entry, typing, opening a file
ThenThe expected result: something that can be seen in the GUI or in a file. Every scenario needs at least one Then
And, But, *Continue the step above with the same meaning, e.g. a second Then. A scenario can not start with And or But
"""A doc string: lines of text that belong to the step above, e.g. code to type into the scratch pad. It ends with another """
| a | b |A data table that belongs to the step above
@tagTags on the line above a scenario, see below
# textA comment. Comments are lost when the web interface saves the file
TODO:Not standard Gherkin: a description line under the scenario name that says why the test can't be automated yet
+

A scenario can switch between When and Then more than once, when a result has to be checked in the middle of the test:

+
  Scenario: Filter the results
+    Given the project file {project:basic-project}/basic.cppcheck is opened from the File menu
+    And the analysis is done
+    When I type "arrayIndex" in the Filter field
+    Then only the arrayIndexOutOfBounds error in b.c is shown
+    When I clear the Filter field
+    Then all errors are shown again
+ +

Tags

+

Everything about a test that is not a step is a tag on the line above the scenario. Tags can't contain spaces.

+
+ + + + + + + +
TagMeaning
@id:t1a2b3c4dIdentifies the test, also when it is renamed. The script is stored by this id. Added automatically when it is missing; don't change it
@ticket:14893The ticket of the test, shown as #14893
@project:NAMEA test project that the test uses, one tag per project
@edition:opensourceAn edition that the test is for, one tag per edition. Without the tag the test is for all editions
@disabledThe test is not run
+

Other tags are kept but have no meaning for gui-test.

+ +

Writing good steps

+

Claude reads the steps as they are when it makes the script, and the Then steps become the checks. A good scenario is one that a new tester could follow without asking:

+
    +
  • Use the words of the GUI: the menu entries, button texts and dialog titles as they are shown, e.g. When I open File > Edit Project File.
  • +
  • Write Given steps as a state (Given the analysis is done), When steps as an action (When I click Recheck) and Then steps as something that can be seen (Then the nullPointer error in a.c is gone).
  • +
  • One thing per Then step. Then there are no results for a.c and And there are no results for b.c become two checks, and a failure points at the right one.
  • +
  • Be concrete about results: name the file, the error id and the line instead of "the results are correct".
  • +
  • Say how to wait: And I wait until the analysis is done.
  • +
  • Refer to files with {project:NAME}/path, and to the scratch directory with {work}.
  • +
  • Keep the steps that all tests of a file share in the Background.
  • +
+

Changing the steps, the Background or the Examples makes the script of the test outdated, because the script was made for the old words.

+ +

Differences from Cucumber

+
    +
  • There are no step definitions. In Cucumber every step is matched by a pattern to code that someone writes. Here Claude reads the scenario once and writes a script for it, and the script is replayed. Steps can therefore be free text; they don't have to repeat fixed phrases.
  • +
  • The script of a scenario is stored next to the feature file in FILE.scripts.json, by test id.
  • +
  • Rule:, tags on Examples, and more than one Feature per file are not supported.
  • +
  • Placeholders in braces are gui-test variables, not Gherkin: {project:NAME}, {work} and {edition}.
  • +
  • TODO: description lines are a gui-test convention.
  • +
+ +

Writing tests

+

A test is a Gherkin scenario, written as if for a person: Given steps say where the test starts, When steps what is done and Then steps the result to expect. Claude reads exactly these words when it makes the script, so be concrete.

+
Feature: Results list
+
+  Background:
+    Given the application is started with no project loaded
+
+  @id:t389a5f3a @project:basic-project
+  Scenario: Clear results
+    Given the project file {project:basic-project}/basic.cppcheck is opened from the File menu
+    And the analysis is done
+    When I click Clear results
+    Then the results are cleared: the result tree is empty
+

The files can be edited in the web interface or in a text editor. The Gherkin chapter describes the format; the web interface shows the tags of a scenario (ticket, editions, test projects, enabled) as separate fields.

+

Tests live in test files (tests/*.feature), one file per area of the GUI. Click a file in the left column to open it, or New file to create one. An open file shows:

+
    +
  • Title: the Feature: line, shown in the report.
  • +
  • Background: the steps that are done before every test of the file, e.g. Given the application is started with no project loaded.
  • +
  • Tests: the list of tests. Click one to edit it, + New test to add one. A test is flagged invalid when it has errors and regenerate when its script is outdated.
  • +
  • Run tests: runs all tests in the file.
  • +
+

Nothing is written to disk until you press Save; a dot after the file name means there are unsaved changes. Errors, such as a test without a Then step, are shown under the field and must be fixed before you can save.

+

The test editor has these fields. The ↑ and ↓ buttons move the test in the list, and Delete removes it.

+
+ + + + + + + + + + + +
FieldWhat to write
NameUnique within the file. Shown in reports and the history, so a rename starts a new history line
TicketOptional, e.g. a Trac ticket number
enabledUntick to leave the test out of every run without deleting it
EditionsTick the editions the test runs in; by default all. The script is the same for all editions, {edition} is replaced by the edition's name
Scenario stepsOne Gherkin step per line, e.g. "When I open File > New Project File". Every Then step becomes at least one check in the script. Use <column> for the values of the Examples
TODOWhy a test can't be automated yet, e.g. "cannot be automated". Leave empty otherwise. In the file it is a line TODO: ... below the scenario name
Test projectsTick the projects whose files the test uses, see below
ScriptThe script of the test, with the buttons Generate script, Run test and, when it is outdated, Approve the script. See Scripts
ExamplesA Gherkin table; every row is a separate run and the test becomes a Scenario Outline
+ +

Examples (test matrix)

+

A Scenario Outline is run once for every row of its Examples table:

+
    When I select the platform <platform>
+    ...
+    Examples:
+      | platform | lang |
+      | native   | C    |
+      | unix32   | C++  |
+

That is 2 runs. A step like "When I select the platform <platform>" gets the value of each run, in the script it is {platform}. The runs are named after the test plus the values, e.g. Relative paths [platform=unix32, lang=C++], and each one gets its own line in the report. The editor shows how many runs the Examples give.

+ +

Test projects

+

A test project is a directory of files that tests use: source files, a .cppcheck project file, a .cfg file. They live in tests/projects/NAME and are edited under Test projects in the left column. Before every run the project is copied to a new empty directory, so a test always starts with the same files, even if an earlier test changed them. In a script the copy is {project:NAME}, or {project} when the test has only one project.

+

Changing a file in a project makes the scripts of the tests that use it outdated, because the script was made for the old files.

+ +

Scripts

+

A test runs only when it has an up-to-date script. Generate it with Claude once, check it, and from then on it is replayed without AI.

+ +

Generating a script

+

Open the test and press Generate script. gen.py starts the application with the test projects in place, and Claude does the test by hand: it looks at the accessibility tree, clicks and types, and checks the expected result (the Then steps). It then answers with a script. The script is replayed from scratch and saved in FILE.scripts.json only if that replay passes. When the replay fails, Claude is told why and tries again, 3 attempts in all. While a script is generated the editor is locked.

+
    +
  • A Scenario Outline is explored with the first row of its Examples. The script uses {column} where the values belong.
  • +
  • When the application does not do what the expected result says, Claude still writes the script that checks it and marks that one check "observed_fail": true. The script is saved, and the test fails until the bug is fixed. That is a finding, not a broken script.
  • +
  • When the test can't be done through the GUI, Claude answers CANNOT: <reason> and no script is saved.
  • +
+

Read the generated script before you trust it, especially its expect steps: they decide what the test proves.

+ +

The script format

+

A script is a JSON list of steps. Each step has do (an action) or expect (a check), and a label in the words of the test that is shown when it fails. Elements are found by name (part of the accessible name, case-insensitive) and role, and if needed path, nth and exact.

+
[
+  {"do": "click", "name": "Help", "role": "menu item", "label": "Open the Help menu"},
+  {"do": "click", "name": "Contents", "role": "menu item", "label": "Choose Help > Contents"},
+  {"expect": "visible", "name": "Cppcheck GUI help", "role": "dialog", "label": "A help window opens"}
+]
+
+ + + + + + + + + + + + + + + +
StepWhat it does
do: click, rightclick, doubleclick, focus, hoverAct on an element; "mouse": true clicks with the mouse
do: settextReplace the text of an edit field with value
do: type, keyKeyboard input to the focused element; keys uses xdotool names like ctrl+o, Return
do: waitWait for an element to appear, or with "gone": true to disappear
do: restartQuit and start the application again; settings are kept
do: sleep, write, copyPause, write a file in {work}, or copy a file in a test project
expect: visible, goneAn element is shown, or is not
expect: stateAn element is or is not checked, enabled, focused, selected, expanded...
expect: valueThe value or text of a field equals something
expect: text, ocrText is (not) on the screen; ocr reads pixels with tesseract, for context menus AT-SPI can't see
expect: fileA file contains, does not contain or matches a regex
expect: alive, exitedThe application is still running, or has quit by itself with status 0
expect: captureSearch regex in an element and save group 1 as a name; {name} in the later steps is replaced by it, for text that changes such as the version
+

Checks and actions wait for their element up to timeout seconds, 5 by default. The placeholders {work}, {home}, {project:NAME}, {column} (of the Examples) and {edition} are replaced before the run, captured values when the capture step has passed. The full description is under How to write a script below the script in the editor.

+ +

Outdated scripts

+

A script is made for the test's steps, the Background of the file, TODO, Examples and the files of its test projects. When any of those change, the script is OUTDATED: it is kept but skipped by every run, and the editor says so in red. You then have three choices:

+
    +
  1. Generate script again.
  2. +
  3. Approve the script when the change does not affect it, e.g. a typo fix in a step.
  4. +
  5. Edit the script by hand in the editor; it has to be valid JSON before you can save.
  6. +
+ +

Running tests

+

Every test starts a fresh application with an empty HOME and fresh copies of its test projects, so tests never depend on each other.

+ +

From the web interface

+

Pick the edition in the header first: opensource, premium or both. Then:

+
    +
  • Run test in an open test runs that test, all the rows of its Examples.
  • +
  • Run tests in an open test file runs every test in that file.
  • +
  • Run all runs every test in every file.
  • +
+

With "both", the run is done for one edition and then the other, and each edition gets its own report. The job box shows the test and step that is running and the results as they come. Stop ends the run at once: the test that was running is SKIP "aborted by user", and the report is written with the tests that are done. Only one job runs at a time.

+ +

From the command line

+
./run.py ~/cppcheck/build/bin/cppcheck-gui tests/application.feature tests/projects.feature --edition opensource
+
+ + + + + + + + + +
OptionMeaning
APPThe binary to test
FILE ...One or more test files
--edition opensource|premiumAPP is this edition; only the tests for it run. Without it all tests run and there is no history per edition
--test TEXTOnly tests whose name contains TEXT (case-insensitive)
--id IDOnly the test with this id, all the rows of its Examples
--src DIRGit checkout for the SHA in the report; default the directory of APP
--out DIRWhere the run directories go; default results
+

run.py prints one line per test and the path of the report, and exits with 1 when a test failed, so it can be used in a script or CI job. Scripts are generated the same way with ./gen.py APP FILE.feature; it takes --edition, --test, --id, --force (also tests that have a script), --model and --attempts.

+ +

What happens in a run

+
    +
  1. The tests to run are selected: enabled, for this edition, not filtered out. Tests without a script, or with an outdated one, are SKIP.
  2. +
  3. For each test, the test projects are copied and the application is started on a new virtual screen with an empty HOME.
  4. +
  5. The script is replayed step by step. After every step that acts on the GUI (click, type, key, settext, restart...) a screenshot is saved.
  6. +
  7. At the end, or at the first failing step, the step log, a final screenshot and the application log are saved, and the log is searched for a crash.
  8. +
  9. After the last test, results.json and report.html are written in the run directory.
  10. +
+ +

Reading the report

+

Each run writes one self-contained report.html; read it top-down: the verdict first, then what changed, then the details of each failure.

+
+ The top of a report: verdict, run details, number boxes, results per test file, the last runs and the status history +
The top of a report from three runs of two test files.
+
+
+ + + + + + + + + + +
PartWhat it tells you
HeaderFAILED or PASSED with the count, and the run details: date, edition, duration, git SHA ("+ uncommitted changes" when the checkout was dirty), branch, commit, application, host, test files
Number boxesPassed, failed (how many crashed the app), skipped, duration, and with history the change against the previous run
Results per test fileA bar per file, green/red/amber. Shows whether failures are in one part of the GUI. Only when the run has more than one file
The last runsPass, fail and skip counts for up to 15 runs of the same edition; the number on a column is its failures
Changed since the previous runNEW failures, tests that are fixed, and tests that are STILL FAILING
Status historyA row per test that did not pass in every run: "new failure", "failing since 10/22 12:00 (b8b27a)", "fixed since", "flaky: 4 changes", "never ran"
Failed and skipped testsA card per test: the reason, the step strip, the screenshots after the last 3 steps and the final screen with a red frame
All testsEvery test grouped by file, with its time, links to its files and its step screenshots
+ +

The step strip

+

Every test has a strip with a cell per script step: green passed, red failed, grey not run. A failure at step 2 of 14 usually means the test or the application broke early. A failure at the last step usually means one check is wrong, or the bug the check describes. Under the strip it says e.g. "failed at step 9 of 14", "not run" or "did not start".

+ +

Files in a run directory

+

A run directory is results/<date>-<time>-<edition>-<sha>/, e.g. 20261022-120317-opensource-b8b27a95/. The files of a test are named after the test (NAME below), with spaces and other special characters replaced by _. They are also linked from the report.

+
+ + + + + + + + +
FileContent
report.htmlThe report
results.jsonThe run details and every result. The history is read from these files
NAME.txtThe step log: each step, ok or FAIL, its time and its screenshot
NAME.pngThe screen at the end of the test, or when it failed
NAME.stepNN.pngThe screen after step NN, for steps that act on the GUI
NAME.app.logWhat the application printed
+

The history compares runs of the same edition in the same results directory. Tests are matched by name.

+ +

Troubleshooting

+

Start with the reason in the report and the final screenshot: together they usually say whether the application, the script or the test is wrong.

+
+ + + + + + + + + + + + +
What you seeWhat it means and what to do
SKIP "no script"The test has no script yet. Generate one, or write it by hand
SKIP "OUTDATED SCRIPT"The test or one of its test projects changed after the script was made. Generate again, or approve the script if the change doesn't matter to it
FAIL "timeout waiting for element to appear"An element with that name and role wasn't found within the timeout. The reason lists the most similar visible elements and the open windows: a renamed button shows up there. Fix the name in the script, or regenerate
FAIL with observed_fail=True in the reasonClaude saw the application not do what the expected result says when it made the script. It is a known bug, and the test fails until the bug is fixed
FAIL with CRASHThe application was not running at the end of the test, or its log has a fatal message (SIGSEGV, SIGABRT, an assertion, AddressSanitizer, QFATAL...). A crash fails the test even if every step passed. Read NAME.app.log. A test that quits the application with status 0 is not a crash
FAIL "could not start"The session or the application did not start. Check that the packages under Getting started are installed, and that the binary runs
"flaky" in the status historyThe test changes between PASS and FAIL. Usually timing: give the slow step a longer timeout, or add a wait step before it
A context menu can't be foundSome menus are not in the accessibility tree. Check them with an ocr step and choose with key Down and Return
"path ... is outside the test directories"write, copy and file steps may only use {work}, {home} and the test project copies
Generate script or Run test is greyed outweb.py has no binary for the edition picked in the header, or the test is not for that edition. Run test also needs an up-to-date script
+ +

Reference

+

The commands, with all their options. web.py, run.py, gen.py and guitest.py also print their help with --help.

+
web.py [--opensource APP] [--premium APP] [--src DIR] [--dir tests] [--out results] [--port 8080]
+run.py APP FILE.feature... [--edition opensource|premium] [--test TEXT] [--id ID] [--src DIR] [--out results]
+gen.py APP FILE.feature [--edition opensource|premium] [--test TEXT] [--id ID] [--force] [--model M] [--attempts 3]
+testfile.py convert IN.md|IN.json OUT.feature
+ +

guitest.py

+

guitest.py is what Claude uses to drive the GUI, and you can use it by hand to look at the GUI the way a script sees it. The session lives in $GUITEST_STATE (default /tmp/guitest-<uid>/state.json).

+
guitest.py start ~/cppcheck/build/bin/cppcheck-gui
+guitest.py tree --showing --depth 3
+guitest.py click --name Help --role "menu item"
+guitest.py screenshot /tmp/screen.png
+guitest.py stop
+
+ + + + + + + + + + + + +
CommandWhat it does
start APP [ARGS]Start Xvfb, D-Bus, AT-SPI and the application. --home DIR (default an empty directory), --size 1280x900
stop, restartStop everything; quit and start the application again with its settings kept
alive, logExit 0 if the application runs; print what it wrote to stdout and stderr
windows, textsThe top level windows and dialogs; every visible text
treeThe accessibility tree. --showing only visible elements, --depth N, --path P one subtree
click, rightclick, doubleclick, focus, hoverAct on an element found with --name, --role, --path, --nth, --exact. --mouse for a real mouse click
settext VALUE, type VALUE, key KEYSSet a field's text; type into the focused element; press keys (xdotool names)
waitWait for an element, --timeout 10, --gone to wait until it disappears
info, suggestRole, name, text, value and states of one element; the visible elements most like --name/--role
ocr, screenshot FILEText on the screen read by tesseract; a PNG of the screen
+
+
+ + diff --git a/gui/manualtest/docs/report.png b/gui/manualtest/docs/report.png new file mode 100644 index 00000000000..2a24756da06 Binary files /dev/null and b/gui/manualtest/docs/report.png differ diff --git a/gui/manualtest/gen.py b/gui/manualtest/gen.py new file mode 100755 index 00000000000..0a3360065dd --- /dev/null +++ b/gui/manualtest/gen.py @@ -0,0 +1,234 @@ +#!/usr/bin/env python3 +"""Let Claude write the scripts of the tests. The scripts are replayed by run.py without AI. + + gen.py APP FILE.feature [--edition opensource|premium] [--test SUBSTRING] [--force] [--model M] + +For every test without a script (or every test with --force) Claude gets the scenario, drives the +GUI once with the guitest.py commands and answers with a script, see testfile.SCRIPT_DOC. +The script is run again from scratch and is saved in FILE.scripts.json only if that replay passes. +A Scenario Outline is explored with the first row of its Examples, the script uses {column} where +the values of the row belong. With --edition APP is that edition, tests that are not for it are +left out; the script is used for all the editions of the test. +""" + +import argparse +import json +import os +import re +import signal +import subprocess +import sys +import tempfile + +import runner +import testfile + +HERE = os.path.dirname(os.path.abspath(__file__)) +GUITEST = os.path.join(HERE, 'guitest.py') + +PROMPT = """You are writing an automated GUI test script for a Qt application that has been started for you. +First do the test by hand, driving the application only with this command line tool (each call is separate, +the session is already running): + + python3 {guitest} + +{help} + +Tips: +- Start with `tree --showing --depth 3`, `windows` and `texts` to see what is on screen. +- Use `info --name X` to see the roles and states of an element, e.g. the [checked] state. +- Menu items: click the menu (e.g. --name File --role "menu item"), then click the entry. +- Keyboard input goes to the focused element: `focus` first, then `type` / `key`. +- The files of the test are in the test projects, they are already in place, use them as they are:{projects} + Do not make copies of them. In the script write {{project:NAME}} instead of the directory, it is another + directory every run. Anything else that you need can be created in {work} (use Write), do not touch other places. +- A menu that is on screen but not in `tree`/`texts` (e.g. a context menu) can be read with `ocr`; check it in the + script with {{"expect": "ocr", "contains": "..."}} and use the keyboard (`key Down`, `key Return`) to choose in it. +- Never read or modify the application source code. + +Test name: {name} + +The test is written in Gherkin: the Background and the Given steps are the starting point, the When steps are +what you do and the Then steps are the EXPECTED result. + +{body} + +When you have done the test and checked the EXPECTED result (the Then steps), answer with a script that does the +same without you. The script is replayed by a program, so: +- use the exact roles and names that you saw. Give a name and a role. Use "path" only when nothing else tells + the element apart, paths change when the dialogs change (a file dialog's name field is found by its label) +- a name matches any element that contains it, so always give the role too: a toolbar button + "Preferences" and the dialog "Preferences" have the same name, the dialog is role "dialog" +- give every step a "label" that says what it is for, in the words of the test +- end with "expect" steps that check the EXPECTED result, at least one for each Then step +- the files of the test are the test projects, refer to them with {{project:NAME}} paths; files that + you made yourself are created by "write" steps (in {{work}}), not by you +- the application is started fresh before the script, with an empty HOME +- {matrixnote} +- {editionnote} + +{scriptdoc} + +{previous}Check the EXPECTED result honestly. If the application does not do what EXPECTED says that is a finding, not +a mistake in the script: still write the script that checks EXPECTED (it will fail, that is the point) and put +the line `OBSERVED: FAIL - ` before the JSON block and add "observed_fail": true to +the one check that fails. Otherwise put `OBSERVED: PASS`. + +Finish with the script as one JSON list in a ```json code block and nothing after it. +If the test cannot be done through the GUI answer with the single line CANNOT: . +""" + + +PREVIOUS = """An earlier attempt gave this script: +{script} + +When it was replayed from scratch by the program it failed: +{error} + +Find out what is wrong (look at the GUI again if needed) and answer with a corrected script. + +""" + + +def start_session(app, root, projects): + work = tempfile.mkdtemp(prefix='guitest-work-') + state = os.path.join(work, 'state.json') + env = dict(os.environ, GUITEST_STATE=state) + sub = lambda *a, **kw: subprocess.run([sys.executable, GUITEST, *a], env=env, text=True, **kw) + variables = testfile.materialize(root, projects, work) + r = sub('start', app, capture_output=True) + if r.returncode: + sys.exit('error: could not start: ' + (r.stdout + r.stderr).strip()) + with open(state) as f: + home = json.load(f)['env']['HOME'] + return work, home, env, sub, variables + + +def explore(app, root, test, combo, edition, model, background, previous=''): + """Claude does the test by hand and returns (script, error); with a script the error is (OBSERVED, reason).""" + work, home, env, sub, variables = start_session(app, root, test.get('projects', [])) + try: + note = ('this is a Scenario Outline, you explore it with the Examples row %s. Write {%s} in the script wherever ' + 'that value is used, the script is run once for every row of the Examples' % ( + ', '.join('%s=%s' % kv for kv in combo.items()), '}, {'.join(combo)) + ) if combo else 'there are no Examples, write the values in the script as they are' + others = [e for e in testfile.editions(test) if e != edition] + if not edition: + editionnote = 'there is one edition of the application' + elif others: + editionnote = ('the application is the %s edition. The same script is also run with the %s edition, ' + 'write {edition} where the name of the edition is shown' % (edition, ' and '.join(others))) + else: + editionnote = 'the script is only run with the %s edition' % edition + projects = ''.join('\n {project:%s} = %s, files: %s' % (n, variables['project:' + n], + ', '.join(sorted(testfile.project_files(root, n)))) + for n in test.get('projects', [])) or ' none' + prompt = PROMPT.format(guitest=GUITEST, help=sub('--help', capture_output=True).stdout, work=work, + name=test['name'], body=testfile.body(test, combo, edition, background), matrixnote=note, + editionnote=editionnote, + scriptdoc=testfile.SCRIPT_DOC, previous=previous, projects=projects) + cmd = ['claude', '-p', '--allowedTools', 'Bash(python3 %s:*)' % GUITEST, 'Read', 'Write', '--add-dir', work] + if model: + cmd += ['--model', model] + p = subprocess.run(cmd, input=prompt, env=env, text=True, capture_output=True, cwd=work) + finally: + sub('stop', capture_output=True) + m = re.findall(r'^CANNOT:\s*(.*)$', p.stdout, re.M) + if m: + return None, 'Claude: cannot be tested through the GUI: ' + m[-1] + m = re.findall(r'```json\s*(.*?)```', p.stdout, re.S) + if not m: + return None, 'no script in the answer of Claude: ' + (p.stdout.strip() or p.stderr.strip())[:300] + try: + script = json.loads(m[-1]) + except ValueError as e: + return None, 'the script is not valid JSON: %s' % e + errors = testfile.validate_script(script) + observed = re.findall(r'^OBSERVED:\s*(PASS|FAIL)\s*-?\s*(.*)$', p.stdout, re.M) + return (None, '; '.join(errors)) if errors else (script, observed[-1] if observed else ('PASS', '')) + + +def replay(app, root, test, script): + work, home, env, sub, variables = start_session(app, root, test.get('projects', [])) + try: + ok, msg, log = runner.run_script(sub, script, work, home, variables) + finally: + sub('stop', capture_output=True) + return ok, msg + + +def store(path, test_id, script, made_for): + """Put the script in the file as it is now, the file may have been saved by the web interface meanwhile.""" + doc = testfile.ensure_ids(testfile.load(path)) + for t in doc['tests']: + if t['id'] == test_id: + t['script'], t['script_for'] = script, made_for + testfile.save(path, doc) + + +def main(): + ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument('app') + ap.add_argument('file') + ap.add_argument('--edition', choices=testfile.EDITIONS, help='the edition that APP is, only the tests for it') + ap.add_argument('--test', help='only tests whose name contains this') + ap.add_argument('--id', help='only the test with this id') + ap.add_argument('--force', action='store_true', help='also tests that have a script') + ap.add_argument('--model') + ap.add_argument('--attempts', type=int, default=3, help='tries per test, a failed replay is told to Claude') + args = ap.parse_args() + signal.signal(signal.SIGINT, signal.default_int_handler) # also when started in the background + if not args.file.endswith(testfile.EXT): + sys.exit('error: FILE must be a %s test file, convert markdown or JSON with testfile.py convert' % testfile.EXT) + app = os.path.abspath(args.app) + doc = testfile.ensure_ids(testfile.load(args.file)) + root = os.path.dirname(os.path.abspath(args.file)) + errors = testfile.validate(doc, root) + if errors: + sys.exit('error: ' + '; '.join(errors)) + + failed = 0 + for t in doc['tests']: + if args.test and args.test.lower() not in t['name'].lower() or args.id and t['id'] != args.id: + continue + if (t.get('script') and not args.force) or not t.get('enabled', True): + continue + if args.edition and args.edition not in testfile.editions(t): + print('%s: not a test for the %s edition' % (t['name'], args.edition), flush=True) + continue + combo = testfile.combinations(t)[0] + values = dict(combo, edition=args.edition) if args.edition else combo + made_for = testfile.definition_hash(t, root, doc.get('background')) # the test may be edited while Claude works + print('%s: exploring...' % t['name'], flush=True) + previous, saved = '', False + for attempt in range(1, args.attempts + 1): + script, err = explore(app, root, t, combo, args.edition, args.model, doc.get('background'), previous) + if script is not None: + # the replay uses the first combination, as the exploration did + observed, why = err + ok, err = replay(app, root, t, testfile.subst_deep(script, values)) + # a test that fails on the check that Claude saw fail by hand is a finding: the script is right + m = re.match(r'step (\d+)/', err) + finding = (not ok and observed == 'FAIL' and m and script[int(m.group(1)) - 1].get('observed_fail')) + if ok or finding: + store(args.file, t['id'], script, made_for) + print('%s: saved a script with %d steps (attempt %d)%s' % ( + t['name'], len(script), attempt, + '. It FAILS on a check, as Claude saw: ' + why if finding else ''), flush=True) + saved = True + break + err = 'the replay of the script failed:\n' + err + previous = PREVIOUS.format(script=json.dumps(script, indent=1), error=err) + print('%s: attempt %d failed. %s' % (t['name'], attempt, err), flush=True) + if re.search(r'usage limit|rate limit|limit reached|out of credits', str(err), re.I): + sys.exit('error: Claude refuses to answer (limit), stopping. Run gen.py again later.') + if err.startswith('Claude: cannot'): + break + if not saved: + failed += 1 + print('%s: no script saved' % t['name'], flush=True) + sys.exit(1 if failed else 0) + + +if __name__ == '__main__': + main() diff --git a/gui/manualtest/guitest.py b/gui/manualtest/guitest.py new file mode 100755 index 00000000000..0b28cd7031d --- /dev/null +++ b/gui/manualtest/guitest.py @@ -0,0 +1,715 @@ +#!/usr/bin/env python3 +"""GUI test helper for Qt applications, driven through AT-SPI. + +The application runs in its own Xvfb display with its own D-Bus and +accessibility bus, so the real desktop is never touched. Each subcommand is a +separate process; the session state (environment, pids) lives in a JSON file. + + guitest.py start APP [ARGS...] start the session and the application + guitest.py tree show the accessibility tree + guitest.py click --name Recheck click an element + guitest.py stop kill everything + +State file: $GUITEST_STATE, default /tmp/guitest-/state.json +""" + +import argparse +import contextlib +import io +import glob +import json +import os +import re +import shutil +import signal +import subprocess +import struct +import sys +import tempfile +import threading +import time +import warnings + +warnings.filterwarnings("ignore") + +STATE = os.environ.get('GUITEST_STATE', '/tmp/guitest-%d/state.json' % os.getuid()) + + +# ---------------------------------------------------------------- session + +def load_state(): + try: + with open(STATE) as f: + return json.load(f) + except FileNotFoundError: + sys.exit('error: no session, run "guitest.py start APP" first') + + +# the shell tells how the application ended in app.log: 139 is SIGSEGV, 134 SIGABRT, 143 killed by us +WRAPPER = '"$@" & echo $! > "$0"; wait $!; echo "application exited with status $?" >&2' + + +def launch(cmd, env, tmp, log): + """Start the application. Returns the shell that waits for it and the pid of the application.""" + pidfile = os.path.join(tmp, 'app.pid') + if os.path.exists(pidfile): + os.remove(pidfile) + p = subprocess.Popen(['sh', '-c', WRAPPER, pidfile] + cmd, env=env, start_new_session=True, + stdin=subprocess.DEVNULL, stdout=log, stderr=subprocess.STDOUT) + for _ in range(100): + try: + with open(pidfile) as f: + return p, int(f.read()) + except (OSError, ValueError): + time.sleep(0.05) + sys.exit('error: the application did not start') + + +def exit_info(state): + """' (exited with status 139, SIGSEGV)' when the log says how the application ended, else ''.""" + try: + with open(os.path.join(state['tmp'], 'app.log'), errors='replace') as f: + m = re.findall(r'application exited with status (\d+)', f.read()) + except OSError: + return '' + if not m: + return '' + code = int(m[-1]) + sig = ', ' + signal.Signals(code - 128).name if 128 < code < 160 else '' + return ' (exited with status %d%s)' % (code, sig) + + +def start(args): + if os.path.exists(STATE): + sys.exit('error: session already running (%s), run stop first' % STATE) + app = os.path.abspath(args.app) + if not os.access(app, os.X_OK): + sys.exit('error: not executable: ' + app) + for tool in ('Xvfb', 'xdotool', 'dbus-daemon', '/usr/libexec/at-spi-bus-launcher'): + if not shutil.which(tool) and not os.path.exists(tool): + sys.exit('error: missing ' + tool) + + tmp = tempfile.mkdtemp(prefix='guitest-') + runtime = os.path.join(tmp, 'run') + home = args.home or os.path.join(tmp, 'home') + os.makedirs(runtime, mode=0o700) + os.makedirs(home, exist_ok=True) + # Cppcheck Premium needs its license file in HOME + lic = os.path.join('.cppcheckpremium', 'cppcheck-premium.lic') + src_lic = os.path.join(os.path.expanduser('~'), lic) + dst_lic = os.path.join(home, lic) + if os.path.isfile(src_lic) and os.path.realpath(src_lic) != os.path.realpath(dst_lic): + os.makedirs(os.path.dirname(dst_lic), exist_ok=True) + shutil.copy2(src_lic, dst_lic) + pids = [] + + def spawn(cmd, env, **kw): + kw.setdefault('stdout', subprocess.DEVNULL) + kw.setdefault('stderr', subprocess.DEVNULL) + p = subprocess.Popen(cmd, env=env, start_new_session=True, stdin=subprocess.DEVNULL, **kw) + pids.append(p.pid) + return p + + env = dict(os.environ) + env.update(XDG_RUNTIME_DIR=runtime, HOME=home) + for k in ('XDG_CONFIG_HOME', 'XDG_DATA_HOME', 'XDG_CACHE_HOME', 'XDG_CONFIG_DIRS'): + env.pop(k, None) # QSettings must not find the real preferences + env.pop('WAYLAND_DISPLAY', None) + env.pop('DBUS_SESSION_BUS_ADDRESS', None) + + # session bus + out = subprocess.run(['dbus-daemon', '--session', '--fork', '--print-address', '--print-pid'], + env=env, capture_output=True, text=True, check=True).stdout.split() + env['DBUS_SESSION_BUS_ADDRESS'] = out[0] + pids.append(int(out[1])) + + # X server + r, w = os.pipe() + spawn(['Xvfb', '-displayfd', str(w), '-screen', '0', args.size + 'x24', '-fbdir', tmp], + env, pass_fds=(w,)) + os.close(w) + env['DISPLAY'] = ':' + os.read(r, 16).decode().strip() + os.close(r) + + # accessibility bus + spawn(['/usr/libexec/at-spi-bus-launcher', '--launch-immediately'], env) + for _ in range(100): + if glob.glob(os.path.join(runtime, 'at-spi', 'bus_*')): + break + time.sleep(0.1) + else: + kill(pids, tmp) + sys.exit('error: accessibility bus did not start') + + env.update(QT_QPA_PLATFORM='xcb', QT_ACCESSIBILITY='1', + QT_LINUX_ACCESSIBILITY_ALWAYS_ON='1', QT_NO_XDG_DESKTOP_PORTAL='1', + QT_QPA_PLATFORMTHEME='generic', # not the KDE file dialog, its KIO jobs crash in this session + LANG='C', LC_ALL='C') + log = open(os.path.join(tmp, 'app.log'), 'w') + appproc, app_pid = launch([app] + args.args, env, tmp, log) + pids.append(appproc.pid) + + state = dict(env={k: env[k] for k in ('XDG_RUNTIME_DIR', 'HOME', 'DBUS_SESSION_BUS_ADDRESS', 'DISPLAY', + 'QT_QPA_PLATFORM', 'QT_ACCESSIBILITY', + 'QT_LINUX_ACCESSIBILITY_ALWAYS_ON', 'QT_NO_XDG_DESKTOP_PORTAL', 'QT_QPA_PLATFORMTHEME', + 'LANG', 'LC_ALL')}, + pids=pids, app_pid=app_pid, app_pgid=appproc.pid, app_cmd=[app] + args.args, tmp=tmp) + os.makedirs(os.path.dirname(STATE), exist_ok=True) + with open(STATE, 'w') as f: + json.dump(state, f) + + # wait for the application to show up on the accessibility bus + os.environ.update(state['env']) + for _ in range(100): + if appproc.poll() is not None: + print(open(os.path.join(tmp, 'app.log')).read()) + kill(pids, tmp) + os.remove(STATE) + sys.exit('error: application exited with code %s' % appproc.returncode) + if find_app(state) is not None: + print('started, display %s, app log %s/app.log' % (env['DISPLAY'], tmp)) + return + time.sleep(0.2) + sys.exit('error: application did not appear on the accessibility bus (see %s/app.log)' % tmp) + + +def restart(args): + """Quit the application and start it again, same HOME so settings are kept.""" + state = load_state() + try: + os.killpg(state['app_pgid'], signal.SIGTERM) + except ProcessLookupError: + pass + time.sleep(1) + env = dict(os.environ) + env.update(state['env']) + log = open(os.path.join(state['tmp'], 'app.log'), 'a') + p, state['app_pid'] = launch(state['app_cmd'], env, state['tmp'], log) + state['app_pgid'] = p.pid + state['pids'].append(p.pid) + with open(STATE, 'w') as f: + json.dump(state, f) + for _ in range(100): + if find_app(state) is not None: + print('restarted') + return + time.sleep(0.2) + sys.exit('error: application did not come back') + + +def kill(pids, tmp): + for pid in reversed(pids): + try: + os.killpg(pid, signal.SIGTERM) + except (ProcessLookupError, PermissionError): + try: + os.kill(pid, signal.SIGTERM) + except (ProcessLookupError, PermissionError): + pass + time.sleep(0.3) + shutil.rmtree(tmp, ignore_errors=True) + + +def stop(args): + try: + state = load_state() + except SystemExit: + return + kill(state['pids'], state['tmp']) + os.remove(STATE) + print('stopped') + + +# ---------------------------------------------------------------- AT-SPI + +_atspi = None + + +def atspi(): + global _atspi + if _atspi is None: + os.environ.update(load_state()['env']) + import gi + gi.require_version('Atspi', '2.0') + from gi.repository import Atspi + _atspi = Atspi + return _atspi + + +def find_app(state): + A = atspi() + desktop = A.get_desktop(0) + for i in range(desktop.get_child_count()): + try: + app = desktop.get_child_at_index(i) + if app is not None and app.get_process_id() == state['app_pid']: + return app + except Exception: + pass + return None + + +def app_running(state): + """The process is there (a zombie is a crashed application nobody has collected).""" + try: + with open('/proc/%d/stat' % state['app_pid']) as f: + return f.read().rsplit(')', 1)[1].split()[0] != 'Z' + except OSError: + return False + + +def get_app(): + state = load_state() + for _ in range(25): # the application does not answer on the accessibility bus while a modal dialog opens + app = find_app(state) + if app is not None: + return app + if not app_running(state): + break + time.sleep(0.2) + if app_running(state): + sys.exit('error: the application is running but does not answer on the accessibility bus (busy or hung?)') + sys.exit('error: the application is not running%s. log: %s/app.log' % (exit_info(state) or ' (crashed?)', state['tmp'])) + + +def children(node): + try: + for i in range(node.get_child_count()): + c = node.get_child_at_index(i) + if c is not None: + yield i, c + except Exception: + return + + +def states(node): + A = atspi() + st = node.get_state_set() + names = [] + for label, s in (('showing', A.StateType.SHOWING), ('enabled', A.StateType.ENABLED), + ('focused', A.StateType.FOCUSED), ('checked', A.StateType.CHECKED), + ('selected', A.StateType.SELECTED), ('expanded', A.StateType.EXPANDED), + ('editable', A.StateType.EDITABLE), ('modal', A.StateType.MODAL)): + if st.contains(s): + names.append(label) + return names + + +def walk(node, path=(), depth=0): + yield path, node, depth + for i, c in children(node): + yield from walk(c, path + (i,), depth + 1) + + +def node_text(node): + # the interface functions take the accessible as first argument, node.get_text_iface() does not work here + A = atspi() + try: + if 'Text' in A.Accessible.get_interfaces(node): + return A.Text.get_text(node, 0, min(A.Text.get_character_count(node), 2000)) + except Exception: + pass + return None + + +def node_value(node): + A = atspi() + try: + if 'Value' in A.Accessible.get_interfaces(node): + return A.Value.get_current_value(node) + except Exception: + pass + return None + + +def extents(node): + A = atspi() + try: + e = node.get_component_iface().get_extents(A.CoordType.SCREEN) + return e.x, e.y, e.width, e.height + except Exception: + return None + + +def describe(path, node, with_geom=True): + name = node.get_name() or '' + s = '%s %s %r' % ('.'.join(map(str, path)) or '-', node.get_role_name(), name) + text = node_text(node) + if text and text != name: + s += ' text=%r' % (text if len(text) < 200 else text[:200] + '...') + val = node_value(node) + if val is not None: + s += ' value=%s' % val + st = states(node) + if st: + s += ' [%s]' % ','.join(st) + g = extents(node) + if with_geom and g and g[2] > 0: + s += ' @%d,%d %dx%d' % g + return s + + +def cmd_tree(args): + app = get_app() + root, rootpath = app, () + if args.path: + rootpath = tuple(int(x) for x in args.path.split('.')) + for i in rootpath: + root = root.get_child_at_index(i) + for path, node, depth in walk(root, rootpath): + d = len(path) - len(rootpath) + if d > args.depth: + continue + if args.showing and 'showing' not in states(node) and d > 0: + continue + print(' ' * d + describe(path, node)) + + +def cmd_windows(args): + app = get_app() + for i, c in children(app): + print(describe((i,), c)) + + +def cmd_texts(args): + """All visible text on screen, one line per element, for checking EXPECTED.""" + app = get_app() + for path, node, depth in walk(app): + if 'showing' not in states(node): + continue + role = node.get_role_name() + name = node.get_name() or '' + text = node_text(node) + if role in ('label', 'push button', 'check box', 'radio button', 'menu item', 'text', 'entry', + 'page tab', 'combo box', 'table cell', 'tree item', 'list item', 'status bar', + 'title bar', 'spin button', 'frame', 'dialog', 'alert') or text: + line = '%s %s %r' % ('.'.join(map(str, path)), role, name) + if text and text != name: + line += ' text=%r' % text + if 'checked' in states(node): + line += ' [checked]' + print(line) + + +# ---------------------------------------------------------------- finding + +def select(args, need_showing=True): + app = get_app() + if args.path: + node = app + for i in (int(x) for x in args.path.split('.')): + node = node.get_child_at_index(i) + if node is None: + sys.exit('error: no such path') + return node + matches = [] + for path, node, depth in walk(app): + role = node.get_role_name() + if args.role and args.role.lower() != role.lower(): + continue + name = node.get_name() or '' + text = '' # found by name only: the text of a panel would match the message of an error + if args.name is not None: + want = args.name.lower() + if args.exact: + if name.lower() != want and text.lower() != want: + continue + elif want not in name.lower() and want not in text.lower(): + continue + elif not args.role: + sys.exit('error: give --name, --role or --path') + if need_showing and 'showing' not in states(node): + continue + matches.append((path, node)) + if not matches: + sys.exit('error: no matching visible element (name=%r role=%r); try "tree --showing"' % (args.name, args.role)) + if args.nth is None and len(matches) > 1: + lines = '\n'.join(' ' + describe(p, n) for p, n in matches[:15]) + sys.exit('error: %d matches, narrow with --role/--exact or pick with --nth N (0-based):\n%s' % (len(matches), lines)) + nth = args.nth or 0 + if nth >= len(matches): + sys.exit('error: no matching visible element: only %d match(es) for name=%r role=%r, nth=%d is too high' + % (len(matches), args.name, args.role, nth)) + return matches[nth][1] + + +def xdo(*a): + env = dict(os.environ) + env.update(load_state()['env']) + subprocess.run(['xdotool'] + [str(x) for x in a], env=env, check=True) + + +def center(node): + g = extents(node) + if not g or g[2] <= 0: + sys.exit('error: element has no geometry') + return g[0] + g[2] // 2, g[1] + g[3] // 2 + + +def mouse(node, button=1, count=1): + x, y = center(node) + xdo('mousemove', x, y) + time.sleep(0.05) + xdo('click', '--repeat', count, '--delay', 80, button) + + +def do_action(node): + A = atspi() + try: + act = node.get_action_iface() + if act is None: + return False + for i in range(act.get_n_actions()): + if act.get_action_name(i).lower() in ('click', 'press', 'toggle', 'activate', 'check', 'uncheck'): + return act.do_action(i) + except Exception: + pass + return False + + +def cmd_click(args): + node = select(args) + desc = describe((), node, False) + if args.mouse: + mouse(node) + else: + # the action of a button that opens a modal dialog does not return until the dialog is closed + done = [] + t = threading.Thread(target=lambda: done.append(do_action(node)), daemon=True) + t.start() + t.join(args.settle + 1) + if done == [False]: + mouse(node) + elif not done: + time.sleep(args.settle) + print('clicked ' + desc, flush=True) + os._exit(0) + time.sleep(args.settle) + print('clicked ' + desc) + + +def cmd_rightclick(args): + node = select(args) + mouse(node, 3) + time.sleep(args.settle) + print('right-clicked ' + describe((), node, False)) + + +def cmd_doubleclick(args): + node = select(args) + mouse(node, 1, 2) + time.sleep(args.settle) + print('double-clicked ' + describe((), node, False)) + + +def cmd_focus(args): + node = select(args) + ok = node.get_component_iface().grab_focus() + if not ok: + mouse(node) + print('focused ' + describe((), node, False)) + + +def cmd_settext(args): + node = select(args) + et = node.get_editable_text_iface() + if et is None: + sys.exit('error: element is not editable text') + et.set_text_contents(args.value) + time.sleep(args.settle) + print('set text of ' + describe((), node, False)) + + +def cmd_type(args): + xdo('type', '--delay', 20, '--', args.value) + time.sleep(args.settle) + + +def cmd_key(args): + xdo('key', *args.keys) + time.sleep(args.settle) + + +def cmd_hover(args): + x, y = center(select(args)) + xdo('mousemove', x, y) + + +def cmd_wait(args): + deadline = time.time() + args.timeout + while True: + try: + node = select(args) + found = describe((), node, False) + except SystemExit as e: + if 'matches' in str(e): # ambiguous means it exists + found = str(e).split('\n')[0] + else: + found = None + if args.gone != (found is not None): + print('gone' if args.gone else 'found ' + found) + return + if time.time() > deadline: + sys.exit('error: timeout waiting for element %s' % ('to disappear' if args.gone else 'to appear')) + time.sleep(0.3) + + +def cmd_info(args): + """One line about the element: role, name, text, value and [states].""" + print(describe((), select(args), False)) + + +def cmd_suggest(args): + """Visible named elements, the ones most like --name first (for error messages).""" + import difflib + want = (args.name or '').lower() + items = [] + for path, node, depth in walk(get_app()): + name = (node.get_name() or '').strip() + if name and 'showing' in states(node): + items.append((node.get_role_name(), name)) + items.sort(key=lambda it: ((it[0].lower() != (args.role or '').lower()) if args.role else 0, + -difflib.SequenceMatcher(None, want, it[1].lower()).ratio())) + seen = [] + for it in items: + if it not in seen: + seen.append(it) + print('%s %r' % it) + if len(seen) >= args.limit: + break + + +def cmd_screenshot(args): + from PIL import Image + state = load_state() + src = os.path.join(state['tmp'], 'Xvfb_screen0') + data = open(src, 'rb').read() + (hdr, _ver, _fmt, _depth, w, h, _xoff, _bo, _bu, _bbo, _bp, bpp, bpl, _vc, + rm, gm, bm, _bpr, _ncm, ncolors) = struct.unpack('>20I', data[:80]) + off = hdr + ncolors * 12 + img = Image.frombuffer('RGB', (w, h), data[off:off + bpl * h], 'raw', 'BGRX', bpl, 1) + img.save(args.file) + print('saved %s (%dx%d)' % (args.file, w, h)) + + +def cmd_ocr(args): + """Text on the screen read by tesseract, also what AT-SPI does not show (some context menus).""" + import tempfile + png = os.path.join(tempfile.mkdtemp(prefix='guitest-ocr-'), 'screen.png') + args.file = png + with contextlib.redirect_stdout(io.StringIO()): + cmd_screenshot(args) + from PIL import Image + img = Image.open(png) + img = img.resize((img.width * 2, img.height * 2), Image.LANCZOS) + img.save(png) + p = subprocess.run(['tesseract', png, '-', '--psm', '11'], capture_output=True, text=True) + if p.returncode: + sys.exit('error: tesseract failed: ' + p.stderr.strip()) + shutil.rmtree(os.path.dirname(png), ignore_errors=True) + print(p.stdout) + + +def cmd_log(args): + print(open(os.path.join(load_state()['tmp'], 'app.log')).read()) + + +def cmd_alive(args): + sys.exit(0 if app_running(load_state()) else 'application is not running') + + +# ---------------------------------------------------------------- cli + +def selector(p): + p.add_argument('--name', help='substring of accessible name or text (case-insensitive)') + p.add_argument('--role', help='role, e.g. "push button", "menu item", "tree item", "check box"') + p.add_argument('--path', help='path from "tree", e.g. 0.1.2') + p.add_argument('--nth', type=int, help='pick the Nth match (0-based)') + p.add_argument('--exact', action='store_true', help='--name must match exactly') + + +def main(): + ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + sub = ap.add_subparsers(dest='cmd', required=True) + + p = sub.add_parser('start', help='start session and application') + p.add_argument('app') + p.add_argument('args', nargs='*') + p.add_argument('--home', help='HOME for the application (default: empty temporary dir)') + p.add_argument('--size', default='1280x900') + p.set_defaults(f=start) + + sub.add_parser('stop', help='stop session').set_defaults(f=stop) + sub.add_parser('restart', help='quit and start the application again (settings are kept)').set_defaults(f=restart) + sub.add_parser('alive', help='exit 0 if the application is running').set_defaults(f=cmd_alive) + sub.add_parser('log', help='application stdout/stderr').set_defaults(f=cmd_log) + sub.add_parser('windows', help='top level windows and dialogs').set_defaults(f=cmd_windows) + sub.add_parser('texts', help='all visible texts').set_defaults(f=cmd_texts) + + p = sub.add_parser('tree', help='accessibility tree') + p.add_argument('--depth', type=int, default=99) + p.add_argument('--path', help='only this subtree') + p.add_argument('--showing', action='store_true', help='only visible elements') + p.set_defaults(f=cmd_tree) + + for name, f, h in (('click', cmd_click, 'click (uses the accessibility action when there is one)'), + ('rightclick', cmd_rightclick, 'right click (opens context menu)'), + ('doubleclick', cmd_doubleclick, 'double click'), + ('focus', cmd_focus, 'give keyboard focus'), + ('hover', cmd_hover, 'move the mouse over the element')): + p = sub.add_parser(name, help=h) + selector(p) + p.add_argument('--settle', type=float, default=0.5, help='seconds to wait afterwards') + if name == 'click': + p.add_argument('--mouse', action='store_true', help='use a real mouse click') + p.set_defaults(f=f) + + p = sub.add_parser('settext', help='replace the text of an edit field') + selector(p) + p.add_argument('value') + p.add_argument('--settle', type=float, default=0.3) + p.set_defaults(f=cmd_settext) + + p = sub.add_parser('type', help='type text with the keyboard into the focused element') + p.add_argument('value') + p.add_argument('--settle', type=float, default=0.3) + p.set_defaults(f=cmd_type) + + p = sub.add_parser('key', help='press keys, xdotool syntax, e.g. Return ctrl+o Escape') + p.add_argument('keys', nargs='+') + p.add_argument('--settle', type=float, default=0.3) + p.set_defaults(f=cmd_key) + + p = sub.add_parser('wait', help='wait until a (visible) element exists') + selector(p) + p.add_argument('--timeout', type=float, default=10) + p.add_argument('--gone', action='store_true', help='wait until it disappears') + p.set_defaults(f=cmd_wait) + + p = sub.add_parser('info', help='role, name, text, value and states of one element') + selector(p) + p.set_defaults(f=cmd_info) + + p = sub.add_parser('suggest', help='visible elements most similar to --name/--role') + p.add_argument('--name') + p.add_argument('--role') + p.add_argument('--limit', type=int, default=8) + p.set_defaults(f=cmd_suggest) + + p = sub.add_parser('ocr', help='print the text on the screen as read by tesseract (for what "texts" does not show)') + p.set_defaults(f=cmd_ocr) + p = sub.add_parser('screenshot', help='save a PNG of the screen') + p.add_argument('file') + p.set_defaults(f=cmd_screenshot) + + args = ap.parse_args() + try: # command history next to the state file, so a watcher can see what a test is doing + with open(os.path.join(os.path.dirname(STATE), 'commands.log'), 'a') as f: + f.write(time.strftime('%H:%M:%S ') + ' '.join(sys.argv[1:]) + '\n') + except OSError: + pass + args.f(args) + + +if __name__ == '__main__': + main() diff --git a/gui/manualtest/mainwindow.md b/gui/manualtest/mainwindow.md deleted file mode 100644 index 563a538530f..00000000000 --- a/gui/manualtest/mainwindow.md +++ /dev/null @@ -1,21 +0,0 @@ - -# Main window - -Some manual testing in the main window interface - - -## Test: Recheck file - -1. Load a project with results. -1. Restart the GUI. -1. Right click on a file and click on "Recheck". - -EXPECTED: Results for that file should be refreshed. - - -## Test: Switch project - -1. Open project #1 -1. Open project #2 that has different results - -EXPECTED: Results for project #1 should go away, results for project #2 should be shown diff --git a/gui/manualtest/projectfiledialog.md b/gui/manualtest/projectfiledialog.md deleted file mode 100644 index eb76109e53e..00000000000 --- a/gui/manualtest/projectfiledialog.md +++ /dev/null @@ -1,31 +0,0 @@ - -# Project file dialog - -Some manual testing in the project file dialog interface - - - -## Test: Platform files - -Ticket: #14489 - -EXPECTED: In the project file dialog it should be possible to select xml files i.e. pic8.xml - -TODO: can this test be automated - - - -## Test: Misra C checkbox - -Ticket: #14488 - -Matrix: Use both open source and premium - -1. Load project file in trac ticket 14488 -1. Goto "Edit project" -1. Goto "Addons" tab - -EXPECTED: The misra c checkbox should be checked - -TODO: can this test be automated - diff --git a/gui/manualtest/report.py b/gui/manualtest/report.py new file mode 100644 index 00000000000..e84df758eb9 --- /dev/null +++ b/gui/manualtest/report.py @@ -0,0 +1,408 @@ +"""The HTML report of a run.py run: summary, results per test file, history over the earlier runs, +the failed tests with their screenshots and all tests. One file, no external resources. + +write(path, run, history) where run is what run.py saves as results.json and history is the +results.json of earlier runs of the same edition, oldest first. +""" + +import html +import os + +STATUSES = ('PASS', 'FAIL', 'SKIP') +ICON = {'PASS': '✓', 'FAIL': '✗', 'SKIP': '–', None: ''} +COLOR = {'PASS': 'var(--pass)', 'FAIL': 'var(--fail)', 'SKIP': 'var(--skip)'} +HISTORY_RUNS = 15 # columns in the history, including this run + +CSS = """ +:root { + --ink: #1b2330; --ink-2: #4a5568; --muted: #6b7686; --line: #e2e6ec; --line-2: #cfd6df; --paper: #fff; --band: #f7f8fa; + --accent: #2456c8; --accent-soft: #e8eefb; + --pass: #0ca30c; --pass-bg: #e4f5e4; --pass-ink: #0b5a0b; + --fail: #d03b3b; --fail-bg: #fbe6e6; --fail-ink: #9a1d1d; + --skip: #fab219; --skip-bg: #fdf2d6; --skip-ink: #6e4b00; + --none: #dfe3e9; + --sans: system-ui, -apple-system, "Segoe UI", Ubuntu, Cantarell, Roboto, sans-serif; + --mono: ui-monospace, "DejaVu Sans Mono", Menlo, Consolas, monospace; + color-scheme: light; +} +* { box-sizing: border-box; } +body { margin: 0; background: var(--paper); color: var(--ink); font: 14px/1.5 var(--sans); } +main { max-width: 72rem; margin: 0 auto; padding: 1.6rem max(16px, 2.2rem) 3rem; } +h1, h2, h3 { margin: 0; text-wrap: balance; } +a { color: var(--accent); } +section { margin-top: 2rem; } +h2 { font-size: 1.02rem; font-weight: 650; margin-bottom: .2rem; } +.sub { color: var(--muted); font-size: .85rem; margin: 0 0 .8rem; } +.rhead { display: flex; gap: 1rem 2rem; align-items: flex-start; justify-content: space-between; flex-wrap: wrap; } +.rhead h1 { font-size: 1.35rem; font-weight: 650; } +.eyebrow { text-transform: uppercase; letter-spacing: .08em; font-size: .72rem; color: var(--muted); font-weight: 600; } +.verdict { border-radius: 8px; padding: .45rem .85rem; font-weight: 650; } +.verdict.FAIL { background: var(--fail-bg); color: var(--fail-ink); } +.verdict.PASS { background: var(--pass-bg); color: var(--pass-ink); } +.meta { display: grid; grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr)); gap: .55rem 1.5rem; margin: 1.1rem 0 0; + padding-top: 1rem; border-top: 1px solid var(--line); } +.meta div { min-width: 0; } +.meta dt { font-size: .75rem; color: var(--muted); } +.meta dd { margin: 0; overflow-wrap: anywhere; } +.meta dd.mono { font-family: var(--mono); font-size: .85rem; } +.dirty, .aborted { color: var(--fail-ink); } +.dirty { font-size: .75rem; margin-left: .3rem; } +.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr)); gap: .8rem; margin-top: 1.4rem; } +.tile { border: 1px solid var(--line); border-radius: 8px; padding: .75rem .9rem; } +.tile .lbl { font-size: .8rem; color: var(--muted); display: flex; align-items: center; gap: .4rem; } +.tile .val { font-size: 1.7rem; font-weight: 650; line-height: 1.2; font-variant-numeric: tabular-nums; } +.tile .note { font-size: .78rem; color: var(--muted); } +.dot { width: .6rem; height: .6rem; border-radius: 50%; display: inline-block; flex: none; } +.d-PASS { background: var(--pass); } .d-FAIL { background: var(--fail); } .d-SKIP { background: var(--skip); } +.chip { display: inline-block; border-radius: 5px; padding: 0 .45rem; font-size: .75rem; font-weight: 650; line-height: 1.6; white-space: nowrap; } +.c-PASS { background: var(--pass-bg); color: var(--pass-ink); } +.c-FAIL { background: var(--fail-bg); color: var(--fail-ink); } +.c-SKIP { background: var(--skip-bg); color: var(--skip-ink); } +.c-CRASH { background: var(--fail-ink); color: #fff; } +.c-NEW { background: var(--accent-soft); color: var(--accent); } +.legend { display: flex; gap: 1rem; flex-wrap: wrap; font-size: .8rem; color: var(--ink-2); margin: .2rem 0 .5rem; } +.legend span { display: inline-flex; align-items: center; gap: .35rem; } +.chart { overflow-x: auto; } +.chart svg { display: block; width: 100%; height: auto; min-width: 34rem; } +.chart text { font-family: var(--sans); fill: var(--ink-2); font-size: 12px; } +.chart text.m { fill: var(--muted); font-size: 11px; } +.chart text.b { fill: var(--ink); font-weight: 600; } +.chart text.sha { font-family: var(--mono); font-size: 9.5px; fill: var(--muted); } +.chart .grid { stroke: var(--line); stroke-width: 1; } +.cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)); gap: 1.5rem 2.5rem; } +.cols > * { min-width: 0; } +.changes { display: grid; gap: .45rem; } +.changes div { display: flex; gap: .5rem; align-items: baseline; flex-wrap: wrap; } +.file { color: var(--muted); font-size: .8rem; } +.tbl { overflow-x: auto; } +table.tests { border-collapse: collapse; width: 100%; min-width: 40rem; } +.tests th { text-align: left; font-size: .75rem; color: var(--muted); font-weight: 600; padding: .35rem .6rem; border-bottom: 1px solid var(--line-2); } +.tests td { padding: .45rem .6rem; border-bottom: 1px solid var(--line); vertical-align: top; } +.tests tr.grp td { background: var(--band); font-weight: 600; font-size: .82rem; color: var(--ink-2); padding-top: .55rem; } +.tests tr.grp span { font-weight: 400; color: var(--muted); } +.tests td.t { white-space: nowrap; text-align: right; font-variant-numeric: tabular-nums; color: var(--ink-2); } +.tests td.links { white-space: nowrap; font-size: .82rem; } +.tests details { margin-top: .3rem; font-size: .82rem; } +.tests summary { color: var(--muted); cursor: pointer; } +.reason { font-family: var(--mono); font-size: .8rem; color: var(--ink-2); white-space: pre-wrap; overflow-wrap: anywhere; } +.strip { display: flex; gap: 2px; width: 11rem; margin-top: .35rem; } +.strip i { flex: 1; height: .5rem; background: var(--pass); } +.strip i:first-child { border-radius: 3px 0 0 3px; } .strip i:last-child { border-radius: 0 3px 3px 0; } +.strip i.f { background: var(--fail); } .strip i.n { background: var(--none); } +.stripnote { font-size: .75rem; color: var(--muted); } +.card { border: 1px solid var(--line); border-left: 4px solid var(--fail); border-radius: 8px; padding: .9rem 1rem; display: grid; gap: .5rem; } +.card + .card { margin-top: .8rem; } +.card.SKIP { border-left-color: var(--skip); } +.card-head { display: flex; gap: .5rem; align-items: baseline; flex-wrap: wrap; } +.card-head h3 { font-size: .98rem; } +.card-head .links { margin-left: auto; font-size: .82rem; } +.film { display: flex; gap: .6rem; overflow-x: auto; padding-bottom: .3rem; } +.shot { flex: none; width: 10.5rem; font-size: .75rem; color: var(--ink-2); text-decoration: none; } +.shot:hover b { text-decoration: underline; } +.shot img { display: block; width: 100%; aspect-ratio: 4/3; object-fit: cover; object-position: left top; + border: 1px solid var(--line-2); border-radius: 5px; background: var(--band); } +.shot.bad img { outline: 3px solid var(--fail); outline-offset: -1px; } +.shot b { color: var(--ink); } +.film.small .shot { width: 7.5rem; } +table.hgrid { border-collapse: separate; border-spacing: 3px; } +.hgrid th.name { text-align: right; padding-right: .6rem; font-size: .82rem; font-weight: 500; white-space: nowrap; } +.hgrid th.run { font-size: .68rem; color: var(--muted); font-weight: 500; text-align: center; } +.hgrid th.run.cur { color: var(--ink); font-weight: 700; } +.hgrid td.cell { width: 1.6rem; height: 1.35rem; border-radius: 3px; text-align: center; font-size: .72rem; font-weight: 700; color: #fff; background: var(--band); } +.hgrid td.PASS { background: var(--pass); } .hgrid td.FAIL { background: var(--fail); } +.hgrid td.SKIP { background: var(--skip); color: var(--skip-ink); } +.hgrid td.cur { box-shadow: 0 0 0 2px var(--paper), 0 0 0 3.5px var(--ink); } +.hgrid td.note { font-size: .78rem; color: var(--muted); padding-left: .7rem; white-space: nowrap; } +@media (max-width: 520px) { .card-head .links { margin-left: 0; width: 100%; } } +""" + +e = html.escape + + +def dur(s): + s = int(round(s)) + return '%d min %d s' % (s // 60, s % 60) if s >= 60 else '%d s' % s + + +def chip(status): + return '%s %s' % (status, ICON[status], status) + + +def crash_chip(r): + return ' CRASH' if r['crash'] else '' + + +def count(results, status): + return sum(1 for r in results if r['status'] == status) + + +def short_date(started): + return '%d/%d' % (int(started[5:7]), int(started[8:10])) + + +def links(r, outdir): + """Links to the files of a test that exist (a skipped test has none).""" + out = [(r['slug'] + ext, text) for ext, text in (('.txt', 'steps'), ('.app.log', 'app log'), ('.png', 'screenshot')) + if os.path.exists(os.path.join(outdir, r['slug'] + ext))] + return ' · '.join('%s' % (e(f), t) for f, t in out) + + +def strip(r): + """One cell per step: green passed, red failed, grey not run.""" + n, at = r.get('steps', 0), r.get('failed_step', 0) + if not n: + return '' + if r['status'] == 'SKIP': + cells, note = ['n'] * n, 'not run' + elif r['status'] == 'FAIL' and not at and not r['crash']: + cells, note = ['n'] * n, 'did not start' + else: + cells = ['' if not at or k < at else 'f' if k == at else 'n' for k in range(1, n + 1)] + note = 'failed at step %d of %d' % (at, n) if at else '%d of %d steps' % (n, n) + return '
%s
%s
' % ( + note, ''.join('' % c for c in cells), note) + + +def hbar(x, y, w, h, r, fill, tip): + """A horizontal bar, rounded at the data end only.""" + r = min(r, w / 2, h / 2) + return ('%s' + % (x, y, w - r, r, r, r, r, h - 2 * r, r, r, r, r, w - r, fill, e(tip))) + + +def vbar(x, base, w, h, r, fill, tip, top): + """A column segment; the top segment is rounded at the data end.""" + if not top: + return '%s' % (x, base - h, w, h, fill, e(tip)) + r = min(r, w / 2, h) + return ('%s' + % (x, base, h - r, r, r, r, r, w - 2 * r, r, r, r, r, h - r, fill, e(tip))) + + +def legend(): + return '
%s
' % ''.join('%s' % (k, k) for k in STATUSES) + + +def per_file_chart(run): + rows = [] + for f in run['files']: + rs = [r for r in run['results'] if r['file'] == f['file']] + if rs: + rows.append(dict({k: count(rs, k) for k in STATUSES}, title=f['title'] or f['file'], n=len(rs))) + big = max(r['n'] for r in rows) + step = 1 if big <= 10 else 5 if big <= 50 else 20 + top = -(-big // step) * step + left, width, rh, h = 210, 900, 30, 18 + bw = width - left - 70 + sx = lambda v: v / top * bw + s = [] + for v in range(0, top + 1, step): + s.append('%d' + % (left + sx(v), left + sx(v), len(rows) * rh + 4, left + sx(v), len(rows) * rh + 20, v)) + for i, r in enumerate(rows): + y = 8 + i * rh + title = r['title'] if len(r['title']) <= 30 else r['title'][:29] + '…' + s.append('%s%s' % (left - 10, y + h / 2 + 4, e(title), e(r['title']))) + x = left + segs = [k for k in STATUSES if r[k]] + for j, k in enumerate(segs): + last = j == len(segs) - 1 + w = sx(r[k]) - (0 if last else 2) # a 2px gap between the segments + tip = '%s\n%d %s of %d tests' % (r['title'], r[k], k, r['n']) + s.append(hbar(x, y, w, h, 4, COLOR[k], tip) if last else + '%s' % (x, y, w, h, COLOR[k], e(tip))) + x += sx(r[k]) + s.append('%d/%d' % (x + 8, y + h / 2 + 4, r['PASS'], r['n'])) + return ('
%s
' + % (width, len(rows) * rh + 28, ''.join(s))) + + +def trend_chart(runs): + width, left, base, top = 900, 34, 190, 14 + n = len(runs) + bw = min(30, (width - left - 30) / n * 0.6) + gap = min((width - left - 20 - n * bw) / max(n - 1, 1), 30) + big = max(len(r['results']) for r in runs) or 1 + step = 5 if big <= 25 else 10 if big <= 60 else 50 + ymax = -(-big // step) * step + sy = lambda v: v / ymax * (base - top) + s = [] + for v in range(0, ymax + 1, step): + s.append('%d' + % (left, width - 10, base - sy(v), base - sy(v), left - 6, base - sy(v) + 4, v)) + for i, run in enumerate(runs): + x, cur = left + 6 + i * (bw + gap), i == n - 1 + c = {k: count(run['results'], k) for k in STATUSES} + tip = 'Run %s · %s%s\n%d pass · %d fail · %d skip' % (run['started'][:16].replace('T', ' '), run['sha'][:8], + ' (aborted)' if run.get('aborted') else '', c['PASS'], c['FAIL'], c['SKIP']) + y0 = base + segs = [k for k in STATUSES if c[k]] + for j, k in enumerate(segs): + last = j == len(segs) - 1 + s.append(vbar(x, y0, bw, sy(c[k]) - (0 if last else 2), 4, COLOR[k], tip, last)) + y0 -= sy(c[k]) + if c['FAIL']: + s.append('%d' % (x + bw / 2, y0 - 6, c['FAIL'])) + s.append('%s' % ('b' if cur else 'm', x + bw / 2, base + 16, short_date(run['started']))) + s.append('%s' % (x + bw / 2, base + 30, e(run['sha'][:6]))) + if cur: + s.append('this run' % (x + bw / 2, base + 46)) + return ('
%s
' + % (width, base + 52, ''.join(s))) + + +def describe_history(h, runs): + """What a row of the status history says: flaky, failing since, fixed since...""" + seen = [x for x in h if x] + last = h[-1] + changes = sum(1 for a, b in zip(seen, seen[1:]) if a != b) + since = len(h) - 1 + while since > 0 and h[since - 1] == last: + since -= 1 + when = '%s %s (%s)' % (short_date(runs[since]['started']), runs[since]['started'][11:16], runs[since]['sha'][:6]) + if all(x == 'SKIP' for x in seen): + return 'never ran' + if changes >= 4: + return 'flaky: %d changes' % changes + if last == 'FAIL': + return 'new failure' if since == len(h) - 1 else 'failing since ' + when + if last == 'SKIP': + return 'skipped since ' + short_date(runs[since]['started']) + return 'fixed since ' + when if last == 'PASS' else '' + + +def history_sections(run, history): + runs = history[-(HISTORY_RUNS - 1):] + [run] + status = [{r['name']: r['status'] for r in x['results']} for x in runs] + prev = status[-2] + cur = run['results'] + new = [r for r in cur if r['status'] == 'FAIL' and prev.get(r['name']) != 'FAIL'] + fixed = [r for r in cur if r['status'] == 'PASS' and prev.get(r['name']) == 'FAIL'] + still = [r for r in cur if r['status'] == 'FAIL' and prev.get(r['name']) == 'FAIL'] + line = lambda r, tag: '
%s%s%s
' % (tag, e(r['name']), e(r['file'])) + changes = ''.join([line(r, 'NEW ' + chip('FAIL')) for r in new] + + [line(r, chip('PASS')) for r in fixed] + + [line(r, '✗ STILL FAILING') for r in still]) or \ + '

No test changed between PASS and FAIL.

' + p = runs[-2] + out = ['

The last %d runs

%s. The number on a column is the failed tests.

%s%s
' + % (len(runs), e(run['edition'] or 'All editions'), legend(), trend_chart(runs)), + '

Changed since the previous run

Run %s · %s

%s
' + % (e(p['started'][:16].replace('T', ' ')), e(p['sha'][:10]), changes)] + rows = [r for r in cur if any(s.get(r['name'], 'PASS') != 'PASS' for s in status)] + if rows: + head = '%s' % ''.join( + '%s' % (' cur' if i == len(runs) - 1 else '', short_date(x['started'])) for i, x in enumerate(runs)) + body = [] + for r in rows: + h = [s.get(r['name']) for s in status] + cells = ''.join('%s' % ( + x or '', ' cur' if i == len(runs) - 1 else '', + e('%s · %s %s: %s' % (r['name'], short_date(runs[i]['started']), runs[i]['sha'][:8], x or 'not in this run')), ICON[x]) + for i, x in enumerate(h)) + body.append('%s%s%s' % (e(r['name']), cells, describe_history(h, runs))) + out.append('

Status history

The tests that did not pass in every run. ' + 'The framed column is this run.

%s%s
' + % (head, ''.join(body))) + return out, len(new) - len(fixed), len(new), len(fixed) + + +def film(r, outdir): + """The screenshots after the last steps before the failure, and the final screenshot.""" + shots = [(s['file'], 'Step %d' % s['step'], s['label'], False) for s in r.get('shots', [])][-3:] + if os.path.exists(os.path.join(outdir, r['slug'] + '.png')): + at = r.get('failed_step', 0) + shots.append((r['slug'] + '.png', 'Step %d · failed here' % at if at else 'At the end', '', True)) + return '
%s
' % ''.join( + '%s%s
%s
' % ( + ' bad' if bad else '', e(f), e(f), e(title), e(title), e(label)) for f, title, label, bad in shots) if shots else '' + + +def problem_cards(run, outdir): + rs = [r for r in run['results'] if r['status'] != 'PASS'] + rs.sort(key=lambda r: r['status'] != 'FAIL') + return ''.join('
%s%s

%s

%s%s
' + '%s
%s
%s
' % ( + r['status'], chip(r['status']), crash_chip(r), e(r['name']), e(r['file']), links(r, outdir), + strip(r) if r['status'] == 'FAIL' else '', e(r['reason']), film(r, outdir) if r['status'] == 'FAIL' else '') + for r in rs) + + +def all_tests(run, outdir): + rows = [] + for f in run['files']: + rs = [r for r in run['results'] if r['file'] == f['file']] + if not rs: + continue + rows.append('%s · %s · %d of %d passed' % ( + e(f['title'] or f['file']), e(f['file']), count(rs, 'PASS'), len(rs))) + for r in rs: + shots = r.get('shots', []) + more = ('
screenshots after the steps (%d)
%s
' % ( + len(shots), ''.join('Step %d
%s
' % ( + e(s['file']), e(s['file']), s['step'], e(s['label'])) for s in shots))) if shots else '' + rows.append('%s%s
%s
%s%s%s%s' % ( + chip(r['status']), crash_chip(r), e(r['name']), strip(r), more, + dur(r['seconds']) if r['status'] != 'SKIP' else '–', links(r, outdir))) + return ('
' + '%s
ResultTestTimeFiles
' % ''.join(rows)) + + +def write(path, run, history): + outdir = os.path.dirname(path) + rs = run['results'] + c = {k: count(rs, k) for k in STATUSES} + ran = c['PASS'] + c['FAIL'] + total = sum(r['seconds'] for r in rs) + crashes = sum(1 for r in rs if r['crash']) + verdict = 'FAIL' if c['FAIL'] else 'PASS' + sha = run['sha'][:8] + text = ('✗ FAILED · %d of %d tests failed' % (c['FAIL'], len(rs)) if c['FAIL'] else + '✓ PASSED · all %d tests passed' % ran if not c['SKIP'] else '✓ PASSED · %d passed, %d skipped' % (ran, c['SKIP'])) + meta = [('Date', e(run['started'][:19].replace('T', ' ')), ''), + ('Edition', e(run['edition'] or 'not given, all tests'), ''), + ('Duration', dur(total), ''), + ('Git SHA', e(run['sha']) + ('+ uncommitted changes' if run['dirty'] else ''), 'mono'), + ('Branch', e(run['branch']), ''), + ('Commit', e(run['subject']), ''), + ('Application', e(run['app']), 'mono'), + ('Host', e(run['host']), ''), + ('Test files', e(', '.join(f['file'] for f in run['files'])), '')] + if run.get('aborted'): + meta.append(('Aborted', 'the run was stopped before all tests were done', '')) + hist, delta, new, fixed = history_sections(run, history) if history else ([], 0, 0, 0) + + def tile(label, value, note, status=''): + return '
%s%s
%s
%s
' % ( + '' % status if status else '', label, value, note) + tiles = [tile('Passed', c['PASS'], '%d %% of the tests' % round(100 * c['PASS'] / len(rs)) if rs else '', 'PASS'), + tile('Failed', c['FAIL'], '%d of them crashed the app' % crashes if crashes else 'no crashes', 'FAIL'), + tile('Skipped', c['SKIP'], 'no script or outdated script' if c['SKIP'] else ' ', 'SKIP'), + tile('Duration', dur(total), 'average %d s per test' % round(total / ran) if ran else ' ')] + if history: + tiles.append(tile('Compared to the previous run', '%+d' % delta if delta else '±0', + '%d new failure%s, %d fixed' % (new, '' if new == 1 else 's', fixed))) + + out = ['', '', + '', + 'GUI test report %s %s' % (e(run['edition'] or ''), e(sha)), '
', + '
GUI test report

%s %s · %s

%s
' % ( + e(os.path.basename(run['app'])), e(run['edition'] or ''), e(sha), verdict, text), + '
%s
' % ''.join('
%s
%s
' % (k, cls, v) for k, v, cls in meta), + '
%s
' % ''.join(tiles)] + if len([f for f in run['files'] if any(r['file'] == f['file'] for r in rs)]) > 1: + out.append('

Results per test file

Which part of the GUI has problems.

%s%s
' + % (legend(), per_file_chart(run))) + out += hist + if c['FAIL'] or c['SKIP']: + out.append('

Failed and skipped tests

The reason, how far the test got, and the screenshots ' + 'after the last steps. The red frame is the screen when the test failed.

%s
' % problem_cards(run, outdir)) + if rs: + out.append('

All tests

Grouped by test file. The strip shows the steps: green passed, ' + 'red failed, grey not run.

%s
' % all_tests(run, outdir)) + if not history: + out.append('

No earlier runs of this edition with results.json were found in %s, ' + 'the history is shown from the next run.

' % e(os.path.dirname(outdir))) + out.append('
') + with open(path, 'w', encoding='utf-8') as f: + f.write('\n'.join(out) + '\n') diff --git a/gui/manualtest/run.py b/gui/manualtest/run.py new file mode 100755 index 00000000000..1d7668cda27 --- /dev/null +++ b/gui/manualtest/run.py @@ -0,0 +1,230 @@ +#!/usr/bin/env python3 +"""Run the manual tests in a test file by letting Claude drive the GUI. + + run.py APP FILE.feature... [--edition opensource|premium] [--test SUBSTRING] [--id ID] [--src DIR] [--out DIR] + +FILE.feature is a Gherkin test file, edited with web.py or by hand, see testfile.py. The scripts +are in FILE.scripts.json. For each scenario (and each row of its Examples) the application is started +fresh (empty HOME) and the script of the test is run with guitest.py commands, see runner.py. +No AI is involved. With --edition APP is that edition and only the tests for it are run. +Tests without a script are SKIP. A step that fails gives PASS -> FAIL with the +step, the reason and the elements that are on the screen. +""" + +import argparse +import os +import re +import datetime +import json +import platform +import signal +import subprocess +import sys +import tempfile +import time + +import report +import runner +import testfile + +HERE = os.path.dirname(os.path.abspath(__file__)) +GUITEST = os.path.join(HERE, 'guitest.py') + +def parse(path, edition=None): + return [dict(t, file=os.path.basename(path)) for t in testfile.expand(path, edition)] + + +CRASH_RE = re.compile(r'exited with status (?!0\b|143\b)\d+|Segmentation fault|SIGSEGV|SIGABRT|Aborted|core dumped|terminate called|' + r'QFATAL|Fatal error|AddressSanitizer|runtime error:|Assertion .* failed', re.I) + + +def screenshot(sub, path): + """Save a screenshot without the empty (black) part of the screen right of and below the windows. + The top left corner stays, so positions in the image are screen positions.""" + if sub('screenshot', path, capture_output=True).returncode: + return False + try: + from PIL import Image + img = Image.open(path) + box = img.convert('RGB').getbbox() + if box and (box[2] < img.width or box[3] < img.height): + img.crop((0, 0, box[2], box[3])).save(path) + except (ImportError, OSError): + pass + return True + + +def detect_crash(sub, outdir, slug): + """Return a description when the application died or logged a fatal error, else ''.""" + log = sub('log', capture_output=True).stdout + with open(os.path.join(outdir, slug + '.app.log'), 'w') as f: + f.write(log) + hits = [l.strip() for l in log.splitlines() if CRASH_RE.search(l)] + dead = sub('alive', capture_output=True).returncode != 0 + if dead and re.search(r'application exited with status 0\b', log): + dead = False # the test closed the application (Quit) + if not dead and not hits: + return '' + why = 'application is not running at the end of the test' if dead else 'fatal message in application log' + return why + (': ' + hits[0] if hits else '') + + +def emit(kind, **data): + """One line of progress for the web interface, with --events.""" + if EVENTS: + print('@' + json.dumps(dict(data, kind=kind)), flush=True) + + +EVENTS = False + + +def run_test(app, test, outdir, on_step=None): + """Returns {status, reason, crash, steps, failed_step, shots}. failed_step is 0 when no step failed, + shots are the screenshots after the steps: [{step, label, file}].""" + slug = re.sub(r'\W+', '_', test['name']) + shots, reached = [], [0] + result = lambda status, reason, crash='', failed=0: {'status': status, 'reason': reason, 'crash': crash, + 'steps': len(test['script']), 'failed_step': failed, 'shots': shots} + if test.get('stale'): + return result('SKIP', 'OUTDATED SCRIPT: the test was changed after the script was made. ' + 'Generate the script again (gen.py or the web interface) or approve it there.') + if not test['script']: + return result('SKIP', 'no script, make one with gen.py or in the web interface') + work = tempfile.mkdtemp(prefix='guitest-work-') + state = os.path.join(work, 'state.json') + env = dict(os.environ, GUITEST_STATE=state) + sub = lambda *a, **kw: subprocess.run([sys.executable, GUITEST, *a], env=env, text=True, **kw) + try: + variables = testfile.materialize(test['root'], test['projects'], work) # same files every run + except FileNotFoundError as e: + return result('FAIL', str(e)) + r = sub('start', app, capture_output=True) + if r.returncode: + return result('FAIL', 'could not start: ' + (r.stdout + r.stderr).strip()) + try: + with open(state) as f: + home = json.load(f)['env']['HOME'] + def shot(i, label): + name = '%s.step%02d.png' % (slug, i) + if not screenshot(sub, os.path.join(outdir, name)): + return None + shots.append({'step': i, 'label': label, 'file': name}) + return name + def step(i, n, label): + reached[0] = i + if on_step: + on_step(i, n, label) + ok, reason, log = runner.run_script(sub, test['script'], work, home, variables, step, shot) + with open(os.path.join(outdir, slug + '.txt'), 'w') as f: + f.write(log) + screenshot(sub, os.path.join(outdir, slug + '.png')) + crash = detect_crash(sub, outdir, slug) + status = 'PASS' if ok else 'FAIL' + if crash: + # a crash is a failure even if all the steps passed + status, reason = 'FAIL', 'APPLICATION CRASHED: %s\n%s' % (crash, reason) + return result(status, reason, crash, 0 if ok else reached[0]) + finally: + sub('stop', capture_output=True) + + +def git(directory, *a): + r = subprocess.run(['git', '-C', directory, *a], text=True, capture_output=True) + return r.stdout.strip() if r.returncode == 0 else '' + + +def app_info(app, src): + """git info for the tested application, taken from SRC (default: the directory of the binary).""" + d = os.path.abspath(src) if src else os.path.dirname(app) + sha = git(d, 'rev-parse', 'HEAD') + return { + 'path': app, + 'sha': sha or 'unknown', + 'branch': git(d, 'rev-parse', '--abbrev-ref', 'HEAD') or 'unknown', + 'subject': git(d, 'log', '-1', '--format=%s') if sha else '', + 'dirty': bool(git(d, 'status', '--porcelain', '--untracked-files=no')) if sha else False, + } + + +def load_history(out, edition, current): + """results.json of the earlier runs of EDITION in OUT, oldest first.""" + runs = [] + for d in sorted(os.listdir(out)): + path = os.path.join(out, d, 'results.json') + if d == current or not os.path.isfile(path): + continue + try: + with open(path, encoding='utf-8') as f: + run = json.load(f) + except (OSError, ValueError): + continue + if run.get('edition') == edition: + runs.append(run) + return runs[-(report.HISTORY_RUNS - 1):] + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument('app') + ap.add_argument('file', nargs='+', help='one or more .feature test files') + ap.add_argument('--edition', choices=testfile.EDITIONS, help='the edition that APP is, only the tests for it') + ap.add_argument('--test', help='only tests whose name contains this') + ap.add_argument('--id', help='only the test with this id (all the rows of its Examples)') + ap.add_argument('--src', help='git checkout of the application (default: the directory of APP)') + ap.add_argument('--out', default=os.path.join(HERE, 'results')) + ap.add_argument('--events', action='store_true', help='progress lines for the web interface') + args = ap.parse_args() + global EVENTS + EVENTS = args.events + signal.signal(signal.SIGINT, signal.default_int_handler) # also when started in the background + + started = datetime.datetime.now() + info = app_info(os.path.abspath(args.app), args.src) + # each run gets its own directory: results/[-]-/ + parent = args.out + args.out = os.path.join(args.out, '%s%s-%s' % (started.strftime('%Y%m%d-%H%M%S'), + '-' + args.edition if args.edition else '', info['sha'][:8])) + os.makedirs(args.out, exist_ok=True) + failed = 0 + results = [] + aborted = False + try: + selected = [t for f in args.file for t in parse(f, args.edition) + if not (args.test and args.test.lower() not in t['name'].lower() or args.id and t['id'] != args.id)] + emit('start', total=len(selected), out=args.out, edition=args.edition) + for index, test in enumerate(selected, 1): + name = test['name'] + emit('test', index=index, name=name, file=test['file'], steps=len(test['script'])) + t0 = time.time() + try: + res = run_test(os.path.abspath(args.app), test, args.out, + lambda k, n, label: emit('step', step=k, steps=n, label=label)) + except KeyboardInterrupt: + results.append({'name': name, 'file': test['file'], 'status': 'SKIP', 'reason': 'aborted by user', 'crash': '', + 'seconds': time.time() - t0, 'slug': re.sub(r'\W+', '_', name), 'steps': 0, 'failed_step': 0, 'shots': []}) + emit('result', **results[-1]) + raise + results.append(dict(res, name=name, file=test['file'], seconds=time.time() - t0, slug=re.sub(r'\W+', '_', name))) + emit('result', **results[-1]) + print('%-4s %s: %s' % (res['status'], name, res['reason']), flush=True) + failed += res['status'] == 'FAIL' + except KeyboardInterrupt: + # the report is written with what is done, tests not started are left out + aborted = True + print('aborted') + run = {'started': started.isoformat(timespec='seconds'), 'edition': args.edition, 'app': info['path'], + 'sha': info['sha'], 'dirty': info['dirty'], 'branch': info['branch'], 'subject': info['subject'], + 'host': '%s, %s' % (platform.node(), platform.platform()), 'aborted': aborted, + 'files': [{'file': os.path.basename(f), 'title': testfile.load(f).get('title', '')} for f in args.file], + 'results': results} + with open(os.path.join(args.out, 'results.json'), 'w', encoding='utf-8') as f: + json.dump(run, f, indent=1) + path = os.path.join(args.out, 'report.html') + report.write(path, run, load_history(parent, args.edition, os.path.basename(args.out))) + print('report: ' + path) + emit('done', report=path) + sys.exit(1 if failed else 0) + + +if __name__ == '__main__': + main() diff --git a/gui/manualtest/runner.py b/gui/manualtest/runner.py new file mode 100644 index 00000000000..11634cdb1e4 --- /dev/null +++ b/gui/manualtest/runner.py @@ -0,0 +1,232 @@ +"""Executes a test script (see testfile.SCRIPT_DOC) with guitest.py commands, no AI involved. + +run_script() gets `sub`, a function that runs one guitest.py command in the session of the test: +sub(cmd, *args, capture_output=True) -> CompletedProcess with returncode, stdout, stderr. +""" + +import os +import re +import shutil +import time + +import testfile + +# actions that change the GUI, a screenshot is taken after each of them +GUI_ACTIONS = ('click', 'rightclick', 'doubleclick', 'focus', 'hover', 'settext', 'type', 'key', 'restart') +NO_ELEMENT = ('no matching visible element', 'does not answer on the accessibility bus') +DEFAULT_TIMEOUT = 5 + + +def selector_args(step): + a = [] + for k in ('name', 'role', 'path'): + if k in step: + a += ['--' + k, str(step[k])] + if 'nth' in step: + a += ['--nth', str(step['nth'])] + if step.get('exact'): + a.append('--exact') + return a + + +def describe(step): + return ' '.join('%s=%r' % (k, v) for k, v in step.items() if k != 'label') + + +def message(p): + text = (p.stderr.strip() or p.stdout.strip() or 'exit code %d' % p.returncode).removeprefix('error: ') + return text.replace('; try "tree --showing"', '') + + +class StepFailed(Exception): + pass + + +def check_path(path, allowed): + path = os.path.realpath(path) + if not any(path == d or path.startswith(d + os.sep) for d in (os.path.realpath(x) for x in allowed)): + raise StepFailed('path %s is outside the test directories' % path) + return path + + +def gui(sub, cmd, step, extra=()): + """Run a command that needs an element. The element may not have appeared yet, so retry.""" + deadline = time.time() + step.get('timeout', DEFAULT_TIMEOUT) + while True: + p = sub(cmd, *selector_args(step), *extra, capture_output=True) + if p.returncode == 0: + return p + if not any(m in message(p) for m in NO_ELEMENT) or time.time() > deadline: + raise StepFailed(message(p)) + time.sleep(0.4) + + +def run_step(sub, step, dirs, captured): + action = step.get('do') or step['expect'] + if 'do' in step: + if action in ('click', 'rightclick', 'doubleclick', 'focus', 'hover'): + gui(sub, action, step, ['--mouse'] if step.get('mouse') else []) + elif action == 'settext': + gui(sub, 'settext', step, [step['value']]) + elif action == 'type': + ok(sub('type', step['value'], capture_output=True)) + elif action == 'key': + keys = step['keys'].split() if isinstance(step['keys'], str) else step['keys'] + ok(sub('key', *keys, capture_output=True)) + elif action == 'wait': + ok(sub('wait', *selector_args(step), '--timeout', str(step.get('timeout', 10)), + *(['--gone'] if step.get('gone') else []), capture_output=True)) + elif action == 'restart': + ok(sub('restart', capture_output=True)) + elif action == 'sleep': + time.sleep(float(step['seconds'])) + elif action == 'copy': + src, dst = check_path(step['from'], dirs), check_path(step['to'], dirs) + if not os.path.isfile(src): + raise StepFailed('file %s does not exist' % src) + os.makedirs(os.path.dirname(dst), exist_ok=True) + shutil.copyfile(src, dst) + elif action == 'write': + path = check_path(step['path'], dirs) + os.makedirs(os.path.dirname(path), exist_ok=True) + with open(path, 'w', encoding='utf-8') as f: + f.write(step['content']) + return + if action == 'visible': + ok(sub('wait', *selector_args(step), '--timeout', str(step.get('timeout', DEFAULT_TIMEOUT)), + capture_output=True)) + elif action == 'gone': + ok(sub('wait', *selector_args(step), '--gone', '--timeout', str(step.get('timeout', DEFAULT_TIMEOUT)), + capture_output=True)) + elif action == 'state': + info = gui(sub, 'info', step).stdout + have = set((re.search(r'\[([\w,]*)\]\s*$', info.strip()) or [None, ''])[1].split(',')) + if 'state' in step and step['state'] not in have: + raise StepFailed('element is not %s, its states are [%s]: %s' % (step['state'], ','.join(sorted(have)), info.strip())) + if 'not_state' in step and step['not_state'] in have: + raise StepFailed('element is %s but should not be: %s' % (step['not_state'], info.strip())) + elif action == 'value': + info = gui(sub, 'info', step).stdout.strip() + found = re.findall(r" value=(\S+)", info) + re.findall(r" text='(.*?)'(?= value=| \[|$)", info) + if not any(same(v, step['equals']) for v in found): + raise StepFailed('the value is %s, not %r: %s' % (found or 'missing', step['equals'], info)) + elif action == 'exited': + deadline = time.time() + step.get('timeout', DEFAULT_TIMEOUT) + while sub('alive', capture_output=True).returncode == 0: + if time.time() > deadline: + raise StepFailed('the application is still running') + time.sleep(0.3) + status = re.findall(r'application exited with status (\d+)', sub('log', capture_output=True).stdout) + if status and status[-1] != '0': + raise StepFailed('the application ended with status %s, not 0' % status[-1]) + elif action == 'text': + deadline = time.time() + step.get('timeout', DEFAULT_TIMEOUT) # the screen may take a moment to change + while True: + texts = sub('texts', capture_output=True).stdout + problem = None + if 'contains' in step and step['contains'] not in texts: + problem = 'the text %r is not on the screen' % step['contains'] + elif 'not_contains' in step and step['not_contains'] in texts: + problem = 'the text %r is on the screen but should not be' % step['not_contains'] + if not problem: + break + if time.time() > deadline: + raise StepFailed(problem) + time.sleep(0.4) + elif action == 'ocr': + deadline = time.time() + step.get('timeout', DEFAULT_TIMEOUT) + norm = lambda x: re.sub(r'\s+', ' ', x).lower() + while True: + p = sub('ocr', capture_output=True) + if p.returncode: + raise StepFailed(message(p)) + seen = norm(p.stdout) + problem = None + if 'contains' in step and norm(step['contains']) not in seen: + problem = 'the text %r is not on the screen (OCR)' % step['contains'] + elif 'not_contains' in step and norm(step['not_contains']) in seen: + problem = 'the text %r is on the screen (OCR) but should not be' % step['not_contains'] + if not problem: + break + if time.time() > deadline: + raise StepFailed(problem + '. OCR read: ' + seen[:300]) + time.sleep(0.4) + elif action == 'file': + path = check_path(step['path'], dirs) + try: + with open(path, encoding='utf-8', errors='replace') as f: + text = f.read() + except FileNotFoundError: + raise StepFailed('file %s does not exist' % path) + if 'contains' in step and step['contains'] not in text: + raise StepFailed('%s does not contain %r' % (path, step['contains'])) + if 'not_contains' in step and step['not_contains'] in text: + raise StepFailed('%s contains %r but should not' % (path, step['not_contains'])) + if 'regex' in step and not re.search(step['regex'], text, re.M): + raise StepFailed('%s does not match /%s/' % (path, step['regex'])) + elif action == 'alive': + ok(sub('alive', capture_output=True)) + elif action == 'capture': + info = gui(sub, 'info', step).stdout.strip() + m = re.search(step['regex'], info) + if not m: + raise StepFailed('/%s/ does not match: %s' % (step['regex'], info)) + captured[step['as']] = m.group(1) if m.groups() else m.group(0) + + +def same(a, b): + try: + return float(a) == float(b) # a spin box says 1.0 + except ValueError: + return a == b + + +def ok(p): + if p.returncode: + raise StepFailed(message(p)) + + +def context(sub, step): + """What the screen looks like when a step failed, to make a renamed button easy to find.""" + out = [] + if {'name', 'role', 'path'} & set(step): + cmd = ['suggest'] + ['--%s=%s' % (k, step[k]) for k in ('name', 'role') if k in step] + s = sub(*cmd, capture_output=True).stdout.strip() + if s: + out.append('most similar visible elements:\n' + '\n'.join(' ' + l for l in s.splitlines())) + w = sub('windows', capture_output=True).stdout.strip() + if w: + out.append('windows:\n' + '\n'.join(' ' + l.split(' @')[0] for l in w.splitlines()[:6])) + return '\n '.join(out) + + + +def run_script(sub, script, work, home, variables=None, progress=None, shot=None): + """Returns (ok, message, log). On failure the message names the step and what was on screen. + shot(i, label) is called after each step i that is one of GUI_ACTIONS and returns the name of the + screenshot it saved, or None.""" + dirs = [work, home] + variables = dict(variables or {}, work=work, home=home) + captured = {} # {name} values of the capture steps so far + log = [] + for i, step in enumerate(script, 1): + step = testfile.subst_deep(step, dict(variables, **captured)) + t0 = time.time() + label = step.get('label') + if progress: + progress(i, len(script), label or describe(step)) + head = 'step %d/%d%s: %s' % (i, len(script), ' "%s"' % label if label else '', describe(step)) + try: + run_step(sub, step, dirs, captured) + except StepFailed as e: + log.append('FAIL %s (%.1f s)' % (head, time.time() - t0)) + msg = '%s\n %s' % (head, e) + ctx = context(sub, step) + return False, msg + ('\n ' + ctx if ctx else ''), '\n'.join(log) + '\n' + msg + '\n' + log.append('ok %s (%.1f s)' % (head, time.time() - t0)) + if shot and step.get('do') in GUI_ACTIONS: + time.sleep(0.2) # let the application paint + name = shot(i, label or describe(step)) + if name: + log[-1] += ' screenshot ' + name + return True, 'all %d steps passed' % len(script), '\n'.join(log) + '\n' diff --git a/gui/manualtest/testfile.py b/gui/manualtest/testfile.py new file mode 100644 index 00000000000..a915e7960fa --- /dev/null +++ b/gui/manualtest/testfile.py @@ -0,0 +1,743 @@ +#!/usr/bin/env python3 +"""Test files for run.py, written in Gherkin (FILE.feature), with the scripts in FILE.scripts.json. + + Feature: Project file dialog + + Background: + Given the application is started with no project loaded + + @id:t1a2b3c4d @ticket:14983 @project:relative-paths + Scenario: Relative paths + TODO: cannot be automated optional, a description line that starts with TODO: + When I open the project dialog + And I select the platform + Then relative paths are used in the XML + + Examples: + | platform | lang | + | native | C | + | unix32 | C++ | + +Feature: is the title of the file. Background: steps are done before every scenario of the file. +A scenario has steps that start with Given, When, Then, And, But or *, at least one Then (the +expected result). A step can be followed by a doc string (lines between \"\"\") or a table (| a | b |). +Lines that start with # are comments, they are not kept when the file is saved by web.py. + +Tags of a scenario (all optional): + @id:ID identifies a test when it is renamed, added when it is missing + @ticket:NUMBER the ticket of the test (#NUMBER) + @project:NAME a test project, see below; one tag per project + @edition:NAME an edition that the test is for, one of EDITIONS; one tag per edition, none: all + @disabled the test is not run +Other tags are kept but not used. + +A run is for one edition (run.py --edition, with the binary of that edition), the tests that are +not for it are left out. The script is the same for all editions, {edition} in the steps and the +script is the name of the edition of the run. + +Examples (Scenario Outline) are the test matrix: the test is run once for every row of the table. + in the steps and {column} in the script are replaced by the value of the row. + +"script" is what run.py executes, see SCRIPT_DOC below. It is written by gen.py (an AI explores +the GUI once) or by hand, and is then replayed without any AI. The scripts are kept in +FILE.scripts.json, {"ID": {"script": [...], "script_for": "..."}}, next to the feature file. + +@project:NAME are test projects, directories with files and code in DIR/projects/NAME, +shared by all tests next to them. Before every run they are copied to an empty temporary directory, +so a test always starts with the same files: {project:NAME} in steps and scripts is the copy of NAME, +and {project} when the test has exactly one project. + +A script is made for the steps (with the Background), todo, examples and test projects (the files in +them) of a test. "script_for" is a hash of those at the time, when they are changed afterwards the +script is OUTDATED: it is kept but not run until it is generated again, edited or approved. + +In Python a test file is a dict {"title", "background": [lines], "tests": [test]}, a test is +{"id", "name", "steps": [lines], "todo", "ticket", "enabled", "projects", "editions", "examples": +[{column: value}], "script", "script_for"}. The lines of the steps are Gherkin without indentation. + + testfile.py convert IN.md|IN.json OUT.feature import a markdown or an older JSON test file +""" + +import hashlib +import itertools +import json +import os +import re +import shutil +import sys + +AXIS_RE = re.compile(r'^\w+$') +PROJECT_RE = re.compile(r'^[\w-]+$') +PROJECTS = 'projects' # test projects: one directory per project, next to the test files +EDITIONS = ('opensource', 'premium') +EXT = '.feature' +SCRIPTS_EXT = '.scripts.json' + +SCRIPT_DOC = """A script is a list of steps. Every step is an object with "do" (an action) or "expect" (a check), +and an optional "label" that says what the step is for (shown when it fails). Elements are found +with name (substring of the accessible name/text, case-insensitive), role, path, nth, exact, like +the guitest.py commands. {work} is a temporary directory for files, {home} the HOME of the +application, {project:NAME} the copy of the test project NAME ({project} when the test has just one), +use those for the files of a test instead of write steps. {column} values of the Examples row and {edition} +(opensource or premium, the same script is used for both) are replaced before the run. + +actions + {"do": "click|rightclick|doubleclick|focus|hover", "name": "...", "role": "...", "mouse": true} + {"do": "settext", "name": "...", "value": "..."} set the text of an edit field + {"do": "type", "value": "..."} keyboard input to the focused element + {"do": "key", "keys": ["ctrl+o", "Return"]} xdotool key names + {"do": "wait", "name": "...", "gone": true, "timeout": 10} + {"do": "restart"} quit and start the application again + {"do": "sleep", "seconds": 1} + {"do": "write", "path": "{work}/a.c", "content": "..."} + {"do": "copy", "from": "{project:p}/a_v2.c", "to": "{project:p}/a.c"} change a file of the project +checks, the test fails when one is wrong + {"expect": "visible", "name": "...", "role": "...", "timeout": 5} + {"expect": "gone", "name": "..."} + {"expect": "state", "name": "...", "state": "checked"} or "not_state": "checked"; also + showing enabled focused selected expanded + {"expect": "value", "name": "...", "equals": "1"} value or text of a field, a spin box... + {"expect": "exited"} the application has ended by itself, status 0 (Quit) + {"expect": "text", "contains": "..."} or "not_contains": anywhere on screen + {"expect": "ocr", "contains": "Recheck"} text on the screen read by OCR (tesseract), or "not_contains"; + only for what "text" cannot see, e.g. a context menu that + is not in the accessibility tree. Case is ignored. + {"expect": "file", "path": "{home}/x.xml", "contains": "...", "not_contains": "...", "regex": "..."} + {"expect": "alive"} + {"expect": "capture", "name": "...", "role": "frame", "regex": "'Cppcheck (\\d[^']*)'", "as": "version"} + the regex is searched in the element's info line + (role 'name' text='...' value=...), group 1 (or the + whole match) is stored and {version} in the later + steps is replaced by it; use it instead of hard coding + text that changes, e.g. the version +A check can have "observed_fail": true when the application was seen not to do what the check says +(a known failure, only for the check that failed). Text checks wait up to "timeout" seconds (default 5). +Actions that need an element wait up to "timeout" seconds (default 5) for it to appear.""" + +SELECTOR = {'name', 'role', 'path', 'nth', 'exact'} +# step name -> (required keys, allowed optional keys) +STEPS = { + 'do': { + 'click': ((), SELECTOR | {'mouse'}), 'rightclick': ((), SELECTOR), 'doubleclick': ((), SELECTOR), + 'focus': ((), SELECTOR), 'hover': ((), SELECTOR), + 'settext': (('value',), SELECTOR), 'type': (('value',), set()), 'key': (('keys',), set()), + 'wait': ((), SELECTOR | {'gone'}), 'restart': ((), set()), + 'sleep': (('seconds',), set()), 'write': (('path', 'content'), set()), 'copy': (('from', 'to'), set()), + }, + 'expect': { + 'visible': ((), SELECTOR), 'gone': ((), SELECTOR), 'value': (('equals',), SELECTOR), 'exited': ((), set()), + 'state': ((), SELECTOR | {'state', 'not_state'}), + 'text': ((), {'contains', 'not_contains'}), 'file': (('path',), {'contains', 'not_contains', 'regex'}), + 'alive': ((), set()), 'ocr': ((), {'contains', 'not_contains'}), + 'capture': (('regex', 'as'), SELECTOR), + }, +} +NEEDS_SELECTOR = {'click', 'rightclick', 'doubleclick', 'focus', 'hover', 'settext', 'wait', 'visible', 'gone', 'state', 'value', + 'capture'} +STATES = {'showing', 'enabled', 'focused', 'selected', 'expanded', 'checked', 'editable', 'modal'} + + +def validate_script(script, where='script'): + if not isinstance(script, list): + return [where + ': must be a list of steps'] + errors = [] + for i, step in enumerate(script, 1): + w = '%s step %d' % (where, i) + if not isinstance(step, dict): + errors.append(w + ': not an object') + continue + kinds = [k for k in ('do', 'expect') if k in step] + if len(kinds) != 1: + errors.append(w + ': needs exactly one of "do" or "expect"') + continue + kind, action = kinds[0], step[kinds[0]] + if action not in STEPS[kind]: + errors.append('%s: unknown %s "%s", use one of %s' % (w, kind, action, ', '.join(STEPS[kind]))) + continue + required, optional = STEPS[kind][action] + allowed = set(required) | optional | {kind, 'label', 'timeout'} | ({'observed_fail'} if kind == 'expect' else set()) + for k in step: + if k not in allowed: + errors.append('%s: "%s" is not used by %s %s' % (w, k, kind, action)) + for k in required: + if k not in step: + errors.append('%s: %s %s needs "%s"' % (w, kind, action, k)) + if action in NEEDS_SELECTOR and not (SELECTOR - {'nth', 'exact'}) & set(step): + errors.append('%s: %s %s needs name, role or path' % (w, kind, action)) + if action in ('text', 'ocr') and not {'contains', 'not_contains'} & set(step): + errors.append(w + ': expect %s needs contains or not_contains' % action) + if action == 'state' and not {'state', 'not_state'} & set(step): + errors.append(w + ': expect state needs state or not_state') + for k in ('state', 'not_state'): + if k in step and step[k] not in STATES: + errors.append('%s: unknown state "%s", use one of %s' % (w, step[k], ', '.join(sorted(STATES)))) + if action == 'capture': + try: + re.compile(step['regex']) + except (re.error, TypeError) as e: + errors.append('%s: bad regex: %s' % (w, e)) + if not re.fullmatch(r'[\w-]+', str(step['as'])): + errors.append(w + ': "as" must be a name of letters, digits, _ and -') + if script and not any('expect' in s for s in script if isinstance(s, dict)): + errors.append(where + ': needs at least one "expect" step, otherwise nothing is tested') + return errors + + +STEP_RE = re.compile(r'^(Given|When|Then|And|But|\*)\s+\S') +SCENARIO_RE = re.compile(r'^(Scenario Outline|Scenario Template|Scenario|Example):\s*(.*)$') +TAG_RE = re.compile(r'^@[^\s@]+$') +DOCSTRING = ('"""', '```') + + +class ParseError(ValueError): + pass + + +def validate_steps(lines, where, need_then=True): + """Errors in the Gherkin lines of a scenario or background.""" + errors, fence, keyword = [], None, None + for n, line in enumerate(lines, 1): + s = line.strip() + if fence: + if s == fence: + fence = None + continue + if s.startswith(DOCSTRING) or s.startswith('|'): + if keyword is None: + errors.append('%s line %d: a doc string or table must come after a step' % (where, n)) + if s.startswith(DOCSTRING): + fence = s[:3] + continue + m = STEP_RE.match(s) + if not m: + errors.append('%s line %d: a step must start with Given, When, Then, And, But or *: %s' % (where, n, s)) + continue + if keyword is None and m.group(1) in ('And', 'But'): + errors.append('%s line %d: the first step can not start with %s' % (where, n, m.group(1))) + keyword = keyword if m.group(1) in ('And', 'But', '*') else m.group(1) + if fence: + errors.append(where + ': a doc string is not closed with ' + fence) + if need_then and not any(re.match(r'^Then\s', line.strip()) for line in lines): + errors.append(where + ': needs a Then step, the expected result') + return errors + + +def parse(text): + """A .feature file as a test file dict. Raises ParseError.""" + doc = {'title': '', 'tests': []} + tags, section, fence, indent, table = [], None, None, 0, None + lines = text.splitlines() + + def fail(n, msg): + raise ParseError('line %d: %s' % (n, msg)) + + for n, raw in enumerate(lines, 1): + s = raw.strip() + if fence: # inside a doc string, kept as it is + section['steps'].append(raw[indent:] if raw[:indent].strip() == '' else s) + if s == fence: + section['steps'][-1] = s + fence = None + continue + if not s or s.startswith('#'): + continue + if s.startswith('@'): + for tag in s.split('#')[0].split(): + if not TAG_RE.match(tag): + fail(n, 'bad tag ' + tag) + tags.append(tag[1:]) + continue + table = table if s.startswith('|') else None + if s.startswith('Feature:'): + if 'feature' in doc: + fail(n, 'only one Feature per file') + doc['feature'] = True + doc['title'] = s[len('Feature:'):].strip() + if tags: + doc['tags'], tags = tags, [] + section = doc + continue + if 'feature' not in doc: + fail(n, 'the file must start with Feature:') + if s.startswith('Background:'): + if doc['tests'] or 'background' in doc: + fail(n, 'the Background must come before the scenarios, once') + doc['background'] = [] + section = {'steps': doc['background']} + continue + m = SCENARIO_RE.match(s) + if m: + section = scenario(m.group(2).strip(), tags) + doc['tests'].append(section) + tags = [] + continue + if s.startswith(('Rule:', 'Scenarios:')) or (s.startswith('Examples:') and section is doc): + fail(n, s.split(':')[0] + ' is not supported here') + if s.startswith('Examples:'): + if 'name' not in section: + fail(n, 'Examples only belong to a scenario') + section['examples'] = section.get('examples', []) + table = [] + continue + if table is not None: # a row of Examples + cells = [c.strip() for c in s.strip('|').split('|')] + if not s.endswith('|') or len(s) < 2: + fail(n, 'a table row must start and end with |') + if not table: + table.append(cells) + section['_columns'] = cells + elif len(cells) != len(table[0]): + fail(n, 'the row has %d cells, the header has %d' % (len(cells), len(table[0]))) + else: + section['examples'].append(dict(zip(table[0], cells))) + continue + if section is doc: + doc.setdefault('description', []).append(s) + continue + if 'examples' in section: + fail(n, 'steps must come before the Examples') + if not section['steps'] and not STEP_RE.match(s) and 'name' in section: + if s.startswith('TODO:'): # description lines of a scenario + section['todo'] = (section.get('todo', '') + '\n' + s[5:].strip()).strip() + else: + section.setdefault('description', []).append(s) + continue + section['steps'].append(s) + if s.startswith(DOCSTRING): + fence, indent = s[:3], len(raw) - len(raw.lstrip()) + if fence: + fail(len(lines), 'the doc string is not closed') + if 'feature' not in doc: + fail(1, 'no Feature: in the file') + del doc['feature'] + for t in doc['tests']: + if 'examples' in t and not t['examples']: + raise ParseError('%s: Examples without rows' % t['name']) + t.pop('_columns', None) + return doc + + +def scenario(name, tags): + t = {'name': name, 'steps': []} + other = [] + for tag in tags: + key, _, value = tag.partition(':') + if key == 'id' and value: + t['id'] = value + elif key == 'ticket' and value: + t['ticket'] = '#' + value if value.isdigit() else value + elif key == 'project' and value: + t.setdefault('projects', []).append(value) + elif key == 'edition' and value: + t.setdefault('editions', []).append(value) + elif tag == 'disabled': + t['enabled'] = False + else: + other.append(tag) + if other: + t['tags'] = other + return t + + +def write_steps(out, lines, indent): + """Doc strings and tables are indented below their step.""" + fence = None + for line in lines: + s = line.strip() + inner = fence or s.startswith(DOCSTRING) or s.startswith('|') + out.append((indent + ' ' * bool(inner) + line) if line else '') + if fence and s == fence: + fence = None + elif not fence and s.startswith(DOCSTRING): + fence = s[:3] + + +def table(rows): + cols = list(rows[0]) + width = {c: max(len(c), *(len(r.get(c, '')) for r in rows)) for c in cols} + fmt = lambda cells: '| ' + ' | '.join(v.ljust(width[c]) for c, v in zip(cols, cells)) + ' |' + return [fmt(cols)] + [fmt([r.get(c, '') for c in cols]) for r in rows] + + +def to_gherkin(doc): + out = [] + if doc.get('tags'): + out.append(' '.join('@' + t for t in doc['tags'])) + out.append('Feature: ' + doc.get('title', '')) + for line in doc.get('description', []): + out.append(' ' + line) + if doc.get('background'): + out += ['', ' Background:'] + write_steps(out, doc['background'], ' ') + for t in doc['tests']: + tags = ['id:' + t['id']] if t.get('id') else [] + if t.get('ticket'): + tags.append('ticket:' + t['ticket'].lstrip('#')) + tags += ['project:' + p for p in t.get('projects', [])] + if t.get('editions') and len(t['editions']) < len(EDITIONS): + tags += ['edition:' + e for e in t['editions']] + if t.get('enabled') is False: + tags.append('disabled') + tags += t.get('tags', []) + out.append('') + if tags: + out.append(' ' + ' '.join('@' + x for x in tags)) + out.append(' %s: %s' % ('Scenario Outline' if t.get('examples') else 'Scenario', t['name'])) + for line in t.get('description', []): + out.append(' ' + line) + for line in (t.get('todo') or '').splitlines(): + out.append(' TODO: ' + line.strip()) + write_steps(out, t.get('steps', []), ' ') + if t.get('examples'): + out += ['', ' Examples:'] + out += [' ' + row for row in table(t['examples'])] + return '\n'.join(out) + '\n' + + +def legacy_steps(steps, expected): + """Steps and expected result in plain words (markdown and older JSON files) as Gherkin lines.""" + lines = [] + for s in steps: + if s.startswith(('* ', '- ')) and lines: + lines[-1] += ' ' + s[2:] + else: + lines.append(('Given ' if not lines else 'When ' if len(lines) == 1 else 'And ') + s) + if expected: + lines.append('Then ' + ' '.join(expected.split())) + return lines + + +def from_legacy_json(old): + """An older JSON test file: steps, expected and a matrix of axes.""" + doc = {'title': old.get('title', ''), 'tests': []} + for o in old.get('tests', []): + t = {k: o[k] for k in ('id', 'name', 'ticket', 'enabled', 'todo', 'projects', 'editions', 'script', 'script_for') + if k in o} + matrix = o.get('matrix') or {} + steps = legacy_steps(o.get('steps', []), o.get('expected', '')) + t['steps'] = [re.sub(r'\{(%s)\}' % '|'.join(map(re.escape, matrix)), r'<\1>', s) if matrix else s for s in steps] + if matrix: + t['examples'] = [dict(zip(matrix, v)) for v in itertools.product(*matrix.values())] + doc['tests'].append(t) + return doc + + +def from_markdown(text): + m = re.search(r'^#\s+(.+)$', text, re.M) + doc = {'title': m.group(1).strip() if m else '', 'tests': []} + for m in re.finditer(r'^## Test:\s*(.+?)\s*\n(.*?)(?=^## |\Z)', text, re.S | re.M): + test = {'name': m.group(1), 'steps': [], 'expected': '', 'todo': ''} + field = None + for line in m.group(2).splitlines(): + s = line.strip() + if s.startswith('Ticket:'): + test['ticket'] = s[len('Ticket:'):].strip() + field = None + elif s.startswith('EXPECTED:'): + test['expected'] = s[len('EXPECTED:'):].strip() + field = 'expected' + elif s.startswith('TODO:'): + test['todo'] = s[len('TODO:'):].strip() + field = 'todo' + elif not s: + field = None + elif field: + test[field] += '\n' + s + else: + test['steps'].append(re.sub(r'^\d+\.\s+', '', s)) + test['steps'] = legacy_steps(test['steps'], test.pop('expected')) + if not test['todo']: + del test['todo'] + doc['tests'].append(test) + return doc + + +def validate(doc, root=None): + """Return a list of error strings, empty when the document is fine. With ROOT (the directory of + the test file) the test projects must exist.""" + errors = [] + if not isinstance(doc, dict) or not isinstance(doc.get('tests'), list): + return ['the document needs a "tests" list'] + if not isinstance(doc.get('title', ''), str) or '\n' in doc.get('title', ''): + errors.append('the title must be one line') + background = doc.get('background', []) + if not isinstance(background, list) or not all(isinstance(s, str) for s in background): + errors.append('the background must be a list of lines') + else: + errors += validate_steps(background, 'Background', need_then=False) + names = set() + for i, t in enumerate(doc['tests'], 1): + where = 'test %d' % i + if not isinstance(t, dict): + errors.append(where + ': not an object') + continue + name = t.get('name') + if not isinstance(name, str) or not name.strip(): + errors.append(where + ': name is missing') + elif '\n' in name: + errors.append(where + ': the name must be one line') + elif name in names: + errors.append('%s: duplicate name "%s"' % (where, name)) + else: + names.add(name) + steps = t.get('steps', []) + if not isinstance(steps, list) or not all(isinstance(s, str) and '\n' not in s for s in steps): + errors.append(where + ': steps must be a list of lines') + else: + errors += validate_steps(steps, where) + for k in ('todo', 'ticket', 'id'): + if not isinstance(t.get(k, ''), str): + errors.append('%s: %s must be a string' % (where, k)) + if re.search(r'\s', t.get('ticket', '') or '') or re.search(r'\W', t.get('id', '') or ''): + errors.append(where + ': the ticket and the id can not have spaces, they are tags') + projects = t.get('projects', []) + if not isinstance(projects, list) or not all(isinstance(p, str) and PROJECT_RE.match(p) for p in projects): + errors.append(where + ': projects must be a list of project names (letters, digits, _ and -)') + elif root: + errors += ['%s: the test project "%s" does not exist' % (where, p) for p in projects + if not os.path.isdir(os.path.join(root, PROJECTS, p))] + editions = t.get('editions', list(EDITIONS)) + if (not isinstance(editions, list) or not editions or len(set(editions)) != len(editions) + or not all(e in EDITIONS for e in editions)): + errors.append('%s: editions must be a list of one or more of %s' % (where, ', '.join(EDITIONS))) + errors += validate_script(t.get('script', []), where + ' script') + examples = t.get('examples', []) + if not isinstance(examples, list) or not all(isinstance(r, dict) for r in examples): + errors.append(where + ': examples must be a list of rows') + continue + if examples: + cols = list(examples[0]) + for c in cols: + if not AXIS_RE.match(c): + errors.append('%s: Examples column "%s" must be letters, digits and _' % (where, c)) + if any(list(r) != cols for r in examples): + errors.append(where + ': every row of the Examples must have the same columns') + if not all(isinstance(v, str) and v and '|' not in v for r in examples for v in r.values()): + errors.append(where + ': every cell of the Examples needs a value without |') + if len({json.dumps(r, sort_keys=True) for r in examples}) != len(examples): + errors.append(where + ': a row of the Examples is there twice') + return errors + + +def scripts_path(path): + return path[:-len(EXT)] + SCRIPTS_EXT if path.endswith(EXT) else path + SCRIPTS_EXT + + +def load(path): + with open(path, encoding='utf-8') as f: + text = f.read() + if path.endswith('.md'): + return from_markdown(text) + if path.endswith('.json'): + return from_legacy_json(json.loads(text)) + doc = parse(text) + try: + with open(scripts_path(path), encoding='utf-8') as f: + scripts = json.load(f) + except FileNotFoundError: + scripts = {} + for t in doc['tests']: + s = scripts.get(t.get('id')) if t.get('id') else None + if isinstance(s, dict) and s.get('script'): + t['script'] = s['script'] + if s.get('script_for'): + t['script_for'] = s['script_for'] + return doc + + +def combinations(test): + """The rows of the Examples of a test as a list of {column: value}; [{}] when there are none.""" + return [dict(r) for r in test.get('examples') or []] or [{}] + + +def editions(test): + """The editions that a test is for, all of them when it does not say.""" + return test.get('editions') or list(EDITIONS) + + +def substitute(text, combo): + return re.sub(r'\{([\w:-]+)\}', lambda m: combo.get(m.group(1), m.group(0)), text) + + +def substitute_step(text, combo): + """ of the Examples and {edition} in a line of the steps.""" + text = re.sub(r'<(\w+)>', lambda m: combo.get(m.group(1), m.group(0)), text) + return substitute(text, {k: v for k, v in combo.items() if k == 'edition'}) + + +def body(test, combo, edition=None, background=()): + """The test as it is shown to Claude: the scenario in Gherkin, with the values of the run.""" + parts = [] + if test.get('ticket'): + parts.append('Ticket: ' + test['ticket']) + if edition: + parts.append('Edition of the application: ' + edition) + if combo: + parts.append('Examples row for this run: ' + ', '.join('%s=%s' % kv for kv in combo.items())) + combo = dict(combo, edition=edition) if edition else combo + lines = [] + if background: + lines += ['Background:'] + [' ' + substitute_step(s, combo) for s in background] + [''] + lines += ['Scenario: ' + test['name']] + [' ' + substitute_step(s, combo) for s in test.get('steps', [])] + parts.append('\n'.join(lines)) + if test.get('todo'): + parts.append('TODO: ' + substitute_step(test['todo'], combo)) + return '\n\n'.join(parts) + + +def project_files(root, name): + """{relative path: bytes} of a test project.""" + base = os.path.join(root, PROJECTS, name) + out = {} + for dirpath, _, files in os.walk(base): + for f in files: + p = os.path.join(dirpath, f) + with open(p, 'rb') as fh: + out[os.path.relpath(p, base)] = fh.read() + return out + + +def project_hash(root, name): + h = hashlib.sha1() + for path, data in sorted(project_files(root, name).items()): + h.update(path.encode() + b'\0' + data + b'\0') + return h.hexdigest()[:12] + + +def materialize(root, names, work): + """Copy the test projects to WORK/projects/NAME. Returns the variables for the script.""" + variables = {} + for name in names: + src = os.path.join(root, PROJECTS, name) + if not os.path.isdir(src): + raise FileNotFoundError('the test project "%s" does not exist' % name) + dst = os.path.join(work, PROJECTS, name) + shutil.copytree(src, dst) + variables['project:' + name] = dst + if len(names) == 1: + variables['project'] = variables['project:' + names[0]] + return variables + + +def definition_hash(test, root=None, background=()): + """What a script depends on: changing any of these, the Background of the file or a file of a + test project makes the script outdated.""" + d = {'steps': list(background or []) + list(test.get('steps') or []), 'todo': test.get('todo') or '', + 'examples': test.get('examples') or []} + if test.get('projects'): + d['projects'] = {p: project_hash(root, p) if root and os.path.isdir(os.path.join(root, PROJECTS, p)) else 'missing' + for p in test['projects']} + return hashlib.sha1(json.dumps(d, sort_keys=True, ensure_ascii=False).encode()).hexdigest()[:12] + + +def ensure_ids(doc): + for t in doc.get('tests', []): + if not t.get('id'): + t['id'] = 't' + hashlib.sha1(str(t.get('name', '')).encode()).hexdigest()[:8] + return doc + + +def is_stale(test, root=None, background=()): + """A script that was made for another version of the test. No script_for: old file, trusted.""" + return bool(test.get('script') and test.get('script_for') + and test['script_for'] != definition_hash(test, root, background)) + + +def stamp_scripts(doc, old, root=None): + """Before saving. A new, edited or approved ("_approve") script is current for the test as it is + now. An untouched script keeps what it was made for, so editing the test makes it outdated.""" + ensure_ids(doc) + old = ensure_ids(old or {}) + prev = {t['id']: t for t in old.get('tests', [])} + for t in doc['tests']: + t.pop('_stale', None) + approve = t.pop('_approve', False) + if not t.get('script'): + t.pop('script_for', None) + continue + p = prev.get(t['id']) + if approve or p is None or p.get('script') != t['script']: + t['script_for'] = definition_hash(t, root, doc.get('background')) + else: + t['script_for'] = p.get('script_for') or definition_hash(p, root, old.get('background')) + return doc + + +def subst_deep(obj, combo): + if isinstance(obj, str): + return substitute(obj, combo) + if isinstance(obj, list): + return [subst_deep(x, combo) for x in obj] + if isinstance(obj, dict): + return {k: subst_deep(v, combo) for k, v in obj.items()} + return obj + + +def expand(path, edition=None): + """One dict {name, id, body, script, stale, combo} per Examples row of every enabled test. + With EDITION only the tests for that edition, and {edition} is replaced.""" + doc = ensure_ids(load(path)) + root = os.path.dirname(os.path.abspath(path)) + errors = validate(doc, root) + if errors: + sys.exit('error: %s: %s' % (path, '; '.join(errors))) + background = doc.get('background', []) + out = [] + for t in doc['tests']: + if not t.get('enabled', True) or edition and edition not in editions(t): + continue + for combo in combinations(t): + name = t['name'] + (' [%s]' % ', '.join('%s=%s' % kv for kv in combo.items()) if combo else '') + values = dict(combo, edition=edition) if edition else combo + out.append({'name': name, 'id': t['id'], 'body': body(t, combo, edition, background), 'combo': combo, + 'root': root, 'projects': t.get('projects', []), 'stale': is_stale(t, root, background), + 'script': subst_deep(t.get('script', []), values)}) + return out + + +def write_atomic(path, text): + tmp = path + '.tmp' + with open(tmp, 'w', encoding='utf-8') as f: + f.write(text) + os.replace(tmp, path) + + +def save(path, doc): + """Write FILE.feature and the scripts in FILE.scripts.json.""" + if not path.endswith(EXT): + raise ValueError('a test file is saved as ' + EXT) + ensure_ids(doc) + scripts = {t['id']: {k: t[k] for k in ('script', 'script_for') if t.get(k)} for t in doc['tests'] if t.get('script')} + write_atomic(path, to_gherkin(doc)) + if scripts: + write_atomic(scripts_path(path), json.dumps(scripts, indent=2, ensure_ascii=False) + '\n') + elif os.path.exists(scripts_path(path)): + os.remove(scripts_path(path)) + + +def convert(src, dst): + """Import a markdown or an older JSON test file. Scripts that were up to date stay up to date.""" + root = os.path.dirname(os.path.abspath(src)) + current = set() + if src.endswith('.json'): + with open(src, encoding='utf-8') as f: + old = json.load(f) + for o in old.get('tests', []): + d = {k: o.get(k) or ([] if k == 'steps' else {} if k == 'matrix' else '') for k in ('steps', 'expected', 'todo', 'matrix')} + if o.get('projects'): + d['projects'] = {p: project_hash(root, p) if os.path.isdir(os.path.join(root, PROJECTS, p)) else 'missing' + for p in o['projects']} + h = hashlib.sha1(json.dumps(d, sort_keys=True, ensure_ascii=False).encode()).hexdigest()[:12] + if o.get('script') and o.get('script_for') in (None, h): + current.add(o.get('id')) + doc = ensure_ids(load(src)) + for t in doc['tests']: + if t['id'] in current: + t['script_for'] = definition_hash(t, os.path.dirname(os.path.abspath(dst)), doc.get('background')) + save(dst, doc) + + +if __name__ == '__main__': + if len(sys.argv) != 4 or sys.argv[1] != 'convert' or not sys.argv[3].endswith(EXT): + sys.exit(__doc__) + convert(sys.argv[2], sys.argv[3]) diff --git a/gui/manualtest/tests/analysis.feature b/gui/manualtest/tests/analysis.feature new file mode 100644 index 00000000000..fc43506dd98 --- /dev/null +++ b/gui/manualtest/tests/analysis.feature @@ -0,0 +1,55 @@ +Feature: Analysis and recheck + + Background: + Given the application is started with no project loaded + + @id:te65b1fa8 @project:basic-project + Scenario: Reanalyze all files + Given the project file {project:basic-project}/basic.cppcheck is opened from the File menu + And the analysis is done + When I change {project:basic-project}/src/a.c and {project:basic-project}/src/b.c so that they only contain ";" + And I click Reanalyze all files + And I wait until the analysis is done + Then there are no results for a.c + And there are no results for b.c + + @id:tc707b85b @ticket:14893 @project:recheck + Scenario: Recheck a changed file + Given the project file {project:recheck}/p.cppcheck is opened + And the analysis is done + And the results show a nullPointer error in a.c and an arrayIndexOutOfBounds error in b.c + When I copy {project:recheck}/a_v2.c over {project:recheck}/a.c (a_v2.c has an uninitvar error instead) + And I right click on the file a.c in the results and click Recheck + Then the results for a.c are refreshed + And the nullPointer error in a.c is gone + And the uninitvar error from the changed a.c is shown + And the arrayIndexOutOfBounds error in b.c is still shown, unchanged + + @id:tc550387d @project:basic-project + Scenario: Recheck a header file + Given the project file {project:basic-project}/basic.cppcheck is opened from the File menu + And the analysis is done + When I right click on the file util.h in the results + Then the context menu of util.h has a Recheck entry + When I click Recheck + And I wait until the analysis is done + Then the analysis is run again for the files that include util.h + And the nullPointer error in util.h is shown again + + @id:ta6f3b40b @project:syntax-error + Scenario Outline: Analyze a broken source file + Given the project file {project:syntax-error}/.cppcheck checks .c, a broken or unusual source file, and ok.c, which has a nullPointer error + When I open the project file {project:syntax-error}/.cppcheck + And I wait until the analysis is done + Then the analysis finishes + And the application does not crash + And the nullPointer error in ok.c is shown + + Examples: + | case | + | braces | + | empty | + | binary | + | latin1 | + | long_line | + | unclosed_nesting | diff --git a/gui/manualtest/tests/analysis.scripts.json b/gui/manualtest/tests/analysis.scripts.json new file mode 100644 index 00000000000..54d89a4c39d --- /dev/null +++ b/gui/manualtest/tests/analysis.scripts.json @@ -0,0 +1,399 @@ +{ + "te65b1fa8": { + "script": [ + { + "label": "Open the File menu", + "do": "click", + "name": "File", + "role": "menu item" + }, + { + "label": "Choose Open Project File", + "do": "click", + "name": "Open Project File", + "role": "menu item" + }, + { + "label": "Enter the project file path", + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck" + }, + { + "label": "Confirm the file dialog", + "do": "key", + "keys": [ + "Return" + ] + }, + { + "label": "Wait until the first analysis shows results for a.c", + "expect": "visible", + "name": "src/a.c", + "role": "table cell", + "timeout": 30 + }, + { + "label": "Results for b.c are shown too", + "expect": "visible", + "name": "src/b.c", + "role": "table cell" + }, + { + "label": "Edit a.c so it only contains ;", + "do": "write", + "path": "{project:basic-project}/src/a.c", + "content": ";" + }, + { + "label": "Edit b.c so it only contains ;", + "do": "write", + "path": "{project:basic-project}/src/b.c", + "content": ";" + }, + { + "label": "Click Reanalyze all files", + "do": "click", + "name": "Reanalyze all files", + "role": "button" + }, + { + "label": "Wait until the analysis is done (No errors found dialog)", + "expect": "visible", + "name": "No errors found", + "role": "label", + "timeout": 30 + }, + { + "label": "Close the dialog", + "do": "click", + "name": "OK", + "role": "button" + }, + { + "label": "No results for a.c", + "expect": "gone", + "name": "src/a.c", + "role": "table cell" + }, + { + "label": "No results for b.c", + "expect": "gone", + "name": "src/b.c", + "role": "table cell" + } + ], + "script_for": "c692e526a6cd" + }, + "tc707b85b": { + "script": [ + { + "do": "wait", + "name": "Cppcheck", + "role": "frame", + "timeout": 20, + "label": "Application started with no project" + }, + { + "do": "click", + "name": "Open Project File", + "role": "button", + "label": "Open the project file dialog" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:recheck}/p.cppcheck", + "label": "Enter the path of p.cppcheck" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Open the project" + }, + { + "expect": "visible", + "name": "b.c", + "role": "table cell", + "timeout": 30, + "label": "Analysis done, b.c in the results" + }, + { + "expect": "visible", + "name": "a.c", + "role": "table cell", + "timeout": 30, + "label": "a.c in the results" + }, + { + "do": "sleep", + "seconds": 3, + "label": "Let the analysis finish" + }, + { + "do": "copy", + "from": "{project:recheck}/a_v2.c", + "to": "{project:recheck}/a.c", + "label": "Change a.c by copying a_v2.c over it" + }, + { + "do": "rightclick", + "name": "a.c", + "role": "table cell", + "nth": 0, + "label": "Right click on a.c in the results" + }, + { + "expect": "ocr", + "contains": "Recheck", + "label": "Context menu shows Recheck" + }, + { + "do": "key", + "keys": [ + "Down", + "Return" + ], + "label": "Click Recheck" + }, + { + "do": "sleep", + "seconds": 4, + "label": "Wait for the recheck" + }, + { + "do": "click", + "name": "b.c", + "role": "table cell", + "nth": 0, + "label": "Select the b.c row" + }, + { + "do": "key", + "keys": [ + "Right", + "Down", + "Down", + "Down", + "Right" + ], + "label": "Expand b.c and a.c to show their results" + }, + { + "expect": "text", + "contains": "uninitvar", + "label": "The uninitvar error of the changed a.c is shown" + }, + { + "expect": "text", + "not_contains": "nullPointer", + "label": "The nullPointer error is gone" + }, + { + "expect": "text", + "contains": "arrayIndexOutOfBounds", + "label": "The arrayIndexOutOfBounds error in b.c is still shown" + } + ], + "script_for": "0c160519508a" + }, + "tc550387d": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the project file path" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the file dialog" + }, + { + "expect": "visible", + "name": "include/util.h", + "role": "table cell", + "timeout": 30, + "label": "Analysis is done and util.h is in the results" + }, + { + "expect": "visible", + "name": "src/b.c", + "role": "table cell", + "timeout": 30, + "label": "Analysis finished for all files" + }, + { + "do": "rightclick", + "name": "include/util.h", + "role": "table cell", + "label": "Right click on util.h in the results" + }, + { + "do": "sleep", + "seconds": 1, + "label": "Wait for the context menu" + }, + { + "do": "key", + "keys": [ + "Down", + "Return" + ], + "label": "Click the Recheck entry of the context menu" + }, + { + "expect": "text", + "contains": "Checking /tmp", + "timeout": 30, + "label": "The analysis was run again" + }, + { + "expect": "text", + "contains": "src/a.c ...", + "timeout": 30, + "label": "The file that includes util.h is checked again" + }, + { + "do": "sleep", + "seconds": 3, + "label": "Wait until the analysis is done" + }, + { + "do": "click", + "name": "include/util.h", + "role": "table cell", + "mouse": true, + "label": "Select util.h with the mouse so it has focus" + }, + { + "do": "key", + "keys": [ + "Right" + ], + "label": "Expand util.h" + }, + { + "expect": "visible", + "name": "nullPointer", + "role": "table cell", + "timeout": 10, + "label": "The nullPointer error in util.h is shown again" + }, + { + "expect": "visible", + "name": "Null pointer dereference", + "role": "table cell", + "timeout": 5, + "label": "The nullPointer message is shown" + } + ], + "script_for": "dc8fcd30e236" + }, + "ta6f3b40b": { + "script": [ + { + "label": "Start the application with no project loaded", + "expect": "visible", + "name": "Cppcheck", + "role": "frame" + }, + { + "label": "Open the project file", + "do": "click", + "name": "Open Project File", + "role": "button" + }, + { + "label": "Wait for the Select Project File dialog", + "do": "wait", + "name": "Select Project File", + "timeout": 10 + }, + { + "label": "Enter the project file {case}.cppcheck", + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:syntax-error}/{case}.cppcheck" + }, + { + "label": "Confirm the file dialog", + "do": "key", + "keys": [ + "Return" + ] + }, + { + "label": "The project is loaded", + "expect": "visible", + "name": "Project: {case}.cppcheck", + "role": "frame", + "timeout": 15 + }, + { + "label": "Wait until the analysis is done (ok.c is checked)", + "expect": "text", + "contains": "Checking ok.c", + "timeout": 60 + }, + { + "label": "Give the analysis time to finish", + "do": "sleep", + "seconds": 3 + }, + { + "label": "The analysis finishes: Stop analysis is disabled again", + "expect": "state", + "name": "Stop analysis", + "role": "button", + "not_state": "enabled", + "timeout": 60 + }, + { + "label": "The analysis finishes: Reanalyze all files is enabled again", + "expect": "state", + "name": "Reanalyze all files", + "role": "button", + "state": "enabled", + "timeout": 30 + }, + { + "label": "The broken source file {case}.c was analyzed", + "expect": "text", + "contains": "Checking {case}.c" + }, + { + "label": "The nullPointer error in ok.c is shown (ok.c has a result row)", + "expect": "visible", + "name": "ok.c", + "role": "table cell" + }, + { + "label": "The application does not crash", + "expect": "alive" + } + ], + "script_for": "3090dfe1cf47" + } +} diff --git a/gui/manualtest/tests/application.feature b/gui/manualtest/tests/application.feature new file mode 100644 index 00000000000..378e1a636cc --- /dev/null +++ b/gui/manualtest/tests/application.feature @@ -0,0 +1,59 @@ +Feature: Application, menus and help + + Background: + Given the application is started with no project loaded + + @id:t41f9102b + Scenario: All menus open + When I open each of the menus File, Edit, View, Analyze and Help in turn + And I look at its entries and close it with Escape + Then each of the menus File, Edit, View, Analyze and Help opens + And each menu shows at least one entry + + @id:t1cc2be0f + Scenario: Show and hide toolbars + Given all entries of View > Toolbar are checked + When I uncheck the entries of View > Toolbar one at a time + Then a toolbar that is unchecked is no longer shown in the main window + And the toolbars that are still checked are shown + + @id:t5fdeee58 @edition:opensource + Scenario: Same version in the title and in About + Given the title of the main window is "Cppcheck " + When I note the version in the title + And I open Help > About + Then the About dialog shows the same version as the title of the main window + + @id:t90321d9c + Scenario: Help contents + When I open Help > Contents + Then a help window opens + And the help window has contents, it is not empty + + @id:tc059b730 + Scenario: Other Help menu entries + When I open each of the other entries of the Help menu in turn (About and the rest) + And I close each window with Escape or its Close button + Then every entry opens a dialog or window with text in it + And none of them is empty + + @id:t5bf8f6ae @ticket:13982 + Scenario: Scratch pad + When I open View > Scratch Pad + And I enter this code, one line each: + """ + int main(void) + { + int *p = 0; + return *p; + } + """ + And I click Check + Then the code is analysed + And a nullPointer error is shown + And the application does not crash + + @id:t1a2285d8 + Scenario: Quit from the File menu + When I open File > Quit + Then the application closes diff --git a/gui/manualtest/tests/application.scripts.json b/gui/manualtest/tests/application.scripts.json new file mode 100644 index 00000000000..98f5b20a42d --- /dev/null +++ b/gui/manualtest/tests/application.scripts.json @@ -0,0 +1,548 @@ +{ + "t41f9102b": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "expect": "visible", + "name": "Open XML...", + "role": "menu item", + "label": "File menu shows entries" + }, + { + "do": "key", + "keys": [ + "Escape" + ], + "label": "Close the File menu" + }, + { + "do": "click", + "name": "Edit", + "role": "menu item", + "label": "Open the Edit menu" + }, + { + "expect": "visible", + "name": "Clear results", + "role": "menu item", + "label": "Edit menu shows entries" + }, + { + "do": "key", + "keys": [ + "Escape" + ], + "label": "Close the Edit menu" + }, + { + "do": "click", + "name": "View", + "role": "menu item", + "label": "Open the View menu" + }, + { + "expect": "visible", + "name": "Toolbars", + "role": "menu item", + "label": "View menu shows entries" + }, + { + "do": "key", + "keys": [ + "Escape" + ], + "label": "Close the View menu" + }, + { + "do": "click", + "name": "Analyze", + "role": "menu item", + "label": "Open the Analyze menu" + }, + { + "expect": "visible", + "name": "Files...", + "role": "menu item", + "label": "Analyze menu shows entries" + }, + { + "do": "key", + "keys": [ + "Escape" + ], + "label": "Close the Analyze menu" + }, + { + "do": "click", + "name": "Help", + "role": "menu item", + "label": "Open the Help menu" + }, + { + "expect": "visible", + "name": "Contents", + "role": "menu item", + "label": "Help menu shows entries" + }, + { + "do": "key", + "keys": [ + "Escape" + ], + "label": "Close the Help menu" + } + ], + "script_for": "06424398d415" + }, + "t1cc2be0f": { + "script": [ + { + "do": "click", + "name": "View", + "role": "menu item", + "label": "Open View menu" + }, + { + "do": "click", + "name": "Toolbars", + "role": "menu item", + "label": "Open Toolbars submenu" + }, + { + "expect": "state", + "name": "Standard", + "role": "menu item", + "state": "checked", + "label": "Standard toolbar entry is checked" + }, + { + "expect": "state", + "name": "Categories", + "role": "menu item", + "state": "checked", + "label": "Categories toolbar entry is checked" + }, + { + "expect": "state", + "name": "Filter", + "role": "menu item", + "state": "checked", + "label": "Filter toolbar entry is checked" + }, + { + "do": "click", + "name": "Standard", + "role": "menu item", + "label": "Uncheck Standard toolbar" + }, + { + "expect": "gone", + "name": "Standard", + "role": "tool bar", + "label": "Standard toolbar is no longer shown" + }, + { + "expect": "visible", + "name": "Categories", + "role": "tool bar", + "label": "Categories toolbar still shown" + }, + { + "expect": "visible", + "name": "Filter", + "role": "tool bar", + "label": "Filter toolbar still shown" + }, + { + "do": "click", + "name": "Categories", + "role": "menu item", + "label": "Uncheck Categories toolbar" + }, + { + "expect": "gone", + "name": "Categories", + "role": "tool bar", + "label": "Categories toolbar is no longer shown" + }, + { + "expect": "visible", + "name": "Filter", + "role": "tool bar", + "label": "Filter toolbar still shown" + }, + { + "do": "click", + "name": "Filter", + "role": "menu item", + "label": "Uncheck Filter toolbar" + }, + { + "expect": "gone", + "name": "Filter", + "role": "tool bar", + "label": "Filter toolbar is no longer shown" + } + ], + "script_for": "6a99d7841493" + }, + "t5fdeee58": { + "script": [ + { + "expect": "capture", + "name": "Cppcheck", + "role": "frame", + "regex": "'Cppcheck (?:Premium )?(\\d[^']*)'", + "as": "version", + "label": "Note the version in the title of the main window" + }, + { + "do": "click", + "name": "Help", + "role": "menu item", + "label": "Open the Help menu" + }, + { + "do": "click", + "name": "About...", + "role": "menu item", + "label": "Open Help > About" + }, + { + "expect": "visible", + "name": "About Cppcheck", + "role": "dialog", + "label": "About dialog is shown" + }, + { + "expect": "visible", + "name": "Version {version}", + "role": "label", + "label": "About dialog shows the same version as the main window title ({version})" + } + ], + "script_for": "5dadee22b5ec" + }, + "t90321d9c": { + "script": [ + { + "do": "click", + "name": "Help", + "role": "menu item", + "label": "Open the Help menu" + }, + { + "do": "click", + "name": "Contents", + "role": "menu item", + "label": "Choose Help > Contents" + }, + { + "expect": "visible", + "name": "Cppcheck GUI help", + "role": "dialog", + "label": "A help window opens", + "observed_fail": true + }, + { + "expect": "text", + "contains": "Contents", + "label": "The help window has contents (it is not empty)" + }, + { + "expect": "text", + "not_contains": "was not found", + "label": "No error that the help file was not found" + } + ], + "script_for": "7c97b218aa0f" + }, + "tc059b730": { + "script": [ + { + "do": "click", + "name": "Help", + "role": "menu item", + "label": "Open the Help menu" + }, + { + "do": "click", + "name": "License...", + "role": "menu item", + "label": "Open License" + }, + { + "expect": "visible", + "name": "License", + "role": "dialog", + "label": "License dialog opens" + }, + { + "expect": "text", + "contains": "GNU GENERAL PUBLIC LICENSE", + "label": "License dialog has text" + }, + { + "do": "key", + "keys": [ + "Escape" + ], + "label": "Close License with Escape" + }, + { + "expect": "gone", + "name": "License", + "label": "License dialog is closed" + }, + { + "do": "click", + "name": "Help", + "role": "menu item", + "label": "Open the Help menu" + }, + { + "do": "click", + "name": "Authors...", + "role": "menu item", + "label": "Open Authors" + }, + { + "expect": "visible", + "name": "Authors", + "role": "dialog", + "label": "Authors dialog opens" + }, + { + "expect": "text", + "contains": "The cppcheck team", + "label": "Authors dialog has text" + }, + { + "do": "click", + "name": "Close", + "role": "button", + "label": "Close Authors with its Close button" + }, + { + "expect": "gone", + "name": "Authors", + "role": "dialog", + "label": "Authors dialog is closed" + }, + { + "do": "click", + "name": "Help", + "role": "menu item", + "label": "Open the Help menu" + }, + { + "do": "click", + "name": "About...", + "role": "menu item", + "label": "Open About" + }, + { + "expect": "visible", + "name": "About Cppcheck", + "role": "dialog", + "label": "About dialog opens" + }, + { + "expect": "text", + "contains": "Cppcheck - A tool for static C/C++ code analysis.", + "label": "About dialog has text" + }, + { + "do": "key", + "keys": [ + "Escape" + ], + "label": "Close About with Escape" + }, + { + "expect": "gone", + "name": "About Cppcheck", + "role": "dialog", + "label": "About dialog is closed" + }, + { + "do": "click", + "name": "Help", + "role": "menu item", + "label": "Open the Help menu" + }, + { + "do": "click", + "name": "Contents", + "role": "menu item", + "label": "Open Contents" + }, + { + "expect": "visible", + "name": "Cppcheck", + "role": "alert", + "label": "Contents shows a message" + }, + { + "expect": "text", + "contains": "Helpfile", + "label": "Message has text" + }, + { + "do": "key", + "keys": [ + "Escape" + ], + "label": "Close the message with Escape" + }, + { + "expect": "visible", + "name": "Cppcheck GUI help", + "role": "dialog", + "label": "Help window opens" + }, + { + "expect": "text", + "contains": "Introduction", + "observed_fail": true, + "label": "Help window has text in it, not empty" + }, + { + "do": "key", + "keys": [ + "Escape" + ], + "label": "Close the help window with Escape" + } + ], + "script_for": "97511d2313db" + }, + "t5bf8f6ae": { + "script": [ + { + "do": "click", + "name": "View", + "role": "menu item", + "label": "Open the View menu" + }, + { + "do": "click", + "name": "Show Scratchpad", + "role": "menu item", + "label": "Open View > Scratch Pad" + }, + { + "expect": "visible", + "name": "Scratchpad", + "role": "dialog", + "label": "Scratchpad dialog is shown" + }, + { + "do": "focus", + "path": "1.1", + "label": "Focus the code editor of the scratchpad" + }, + { + "do": "type", + "value": "int main(void)", + "label": "Type line 1" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "New line" + }, + { + "do": "type", + "value": "{", + "label": "Type line 2" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "New line" + }, + { + "do": "type", + "value": " int *p = 0;", + "label": "Type line 3" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "New line" + }, + { + "do": "type", + "value": " return *p;", + "label": "Type line 4" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "New line" + }, + { + "do": "type", + "value": "}", + "label": "Type line 5" + }, + { + "do": "click", + "name": "Check", + "role": "button", + "label": "Click Check" + }, + { + "expect": "text", + "contains": "nullPointer", + "timeout": 15, + "label": "A nullPointer error is shown" + }, + { + "expect": "text", + "contains": "Null pointer dereference: p", + "label": "The null pointer message is shown" + }, + { + "expect": "alive", + "label": "The application did not crash" + } + ], + "script_for": "30d9d26803fd" + }, + "t1a2285d8": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Quit", + "role": "menu item", + "label": "Choose File > Quit" + }, + { + "expect": "exited", + "label": "The application closes" + } + ], + "script_for": "bc31dd4069bf" + } +} diff --git a/gui/manualtest/tests/preferences.feature b/gui/manualtest/tests/preferences.feature new file mode 100644 index 00000000000..70d0a82fa47 --- /dev/null +++ b/gui/manualtest/tests/preferences.feature @@ -0,0 +1,42 @@ +Feature: Preferences + + Background: + Given the application is started with no project loaded + + @id:t232a9783 + Scenario: Default number of threads + When I open Edit > Preferences + And I look at the General tab + Then the number of threads is 1 + + @id:tf5b5a7e1 + Scenario: Number of threads cannot be 0 + Given Edit > Preferences is open on the General tab + When I type 0 in the number of threads field + And I close the dialog with OK + And I open Edit > Preferences again + Then the field has not accepted 0 + And the number of threads is at least 1, it is never 0 + + @id:t841cca6c @project:basic-project + Scenario: Analysis with 100 threads + Given the number of threads is set to 100 in Edit > Preferences and the dialog is closed with OK + When I open the project file {project:basic-project}/basic.cppcheck from the File menu + And I wait until the analysis is done + Then the application does not crash + And the analysis is done and the results are shown + + @id:t28ce8afe @project:embedded @edition:opensource @disabled + Scenario Outline: Misra rule text file + Given Edit > Preferences is open on the Addons tab + When I select the rule text file {project:embedded}/ + And I close the dialog with OK + And I open the project file {project:embedded}/embedded.cppcheck + And I wait until the analysis is done + Then the Misra error for the unused parameter has the rule text from the file (TEST RULE TEXT: unused parameters are not allowed) + And there are no internalError results + + Examples: + | rulefile | + | rules.txt | + | rules with spaces.txt | diff --git a/gui/manualtest/tests/preferences.scripts.json b/gui/manualtest/tests/preferences.scripts.json new file mode 100644 index 00000000000..2da801e7e71 --- /dev/null +++ b/gui/manualtest/tests/preferences.scripts.json @@ -0,0 +1,306 @@ +{ + "t232a9783": { + "script": [ + { + "do": "click", + "name": "Edit", + "role": "menu item", + "label": "Open the Edit menu" + }, + { + "do": "click", + "name": "Preferences", + "role": "menu item", + "label": "Open Edit > Preferences" + }, + { + "expect": "visible", + "name": "Preferences", + "role": "dialog", + "label": "Preferences dialog is shown" + }, + { + "expect": "state", + "name": "General", + "role": "page tab", + "state": "selected", + "label": "General tab is shown" + }, + { + "expect": "value", + "name": "Number of threads", + "role": "text", + "equals": "1", + "label": "The number of threads is 1" + } + ], + "script_for": "3c6d225d76b6" + }, + "tf5b5a7e1": { + "script": [ + { + "do": "click", + "name": "Edit", + "role": "menu item", + "label": "Open the Edit menu" + }, + { + "do": "click", + "name": "Preferences", + "role": "menu item", + "label": "Open Edit > Preferences" + }, + { + "expect": "visible", + "name": "Preferences", + "role": "dialog", + "label": "Preferences dialog is shown" + }, + { + "do": "click", + "name": "General", + "role": "page tab", + "label": "Go to the General tab" + }, + { + "do": "settext", + "name": "Number of threads", + "role": "text", + "value": "0", + "label": "Type 0 in the number of threads field" + }, + { + "do": "click", + "name": "OK", + "role": "button", + "label": "Close the dialog with OK" + }, + { + "do": "click", + "name": "Edit", + "role": "menu item", + "label": "Open the Edit menu again" + }, + { + "do": "click", + "name": "Preferences", + "role": "menu item", + "label": "Open Edit > Preferences again" + }, + { + "expect": "visible", + "name": "Preferences", + "role": "dialog", + "label": "Preferences dialog is shown again" + }, + { + "do": "click", + "name": "General", + "role": "page tab", + "label": "Go to the General tab again" + }, + { + "expect": "value", + "name": "Number of threads", + "role": "text", + "equals": "1", + "label": "The number of threads is at least 1, never 0" + } + ], + "script_for": "6a7660844e11" + }, + "t841cca6c": { + "script": [ + { + "do": "click", + "name": "Edit", + "role": "menu item", + "label": "Open the Edit menu" + }, + { + "do": "click", + "name": "Preferences", + "role": "menu item", + "label": "Open Preferences from the Edit menu" + }, + { + "expect": "visible", + "name": "Preferences", + "role": "dialog", + "label": "Preferences dialog is shown" + }, + { + "do": "settext", + "name": "Number of threads", + "role": "text", + "value": "100", + "label": "Set the number of threads to 100" + }, + { + "expect": "value", + "name": "Number of threads", + "role": "text", + "equals": "100", + "label": "Thread count field holds 100" + }, + { + "do": "click", + "name": "OK", + "role": "button", + "label": "Close the Preferences dialog with OK" + }, + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "expect": "visible", + "name": "Select Project File", + "role": "dialog", + "label": "File dialog is shown" + }, + { + "do": "settext", + "name": "File name", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the path of basic.cppcheck" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Open the project file" + }, + { + "expect": "visible", + "name": "src/b.c", + "role": "table cell", + "timeout": 60, + "label": "Analysis is done and results for src/b.c are shown" + }, + { + "expect": "visible", + "name": "src/a.c", + "role": "table cell", + "label": "Results for src/a.c are shown" + }, + { + "expect": "text", + "contains": "Project: basic.cppcheck", + "label": "The project is loaded" + }, + { + "expect": "alive", + "label": "The application did not crash" + } + ], + "script_for": "3f99ba3a159c" + }, + "t28ce8afe": { + "script": [ + { + "do": "click", + "name": "Preferences", + "role": "button", + "label": "Open Preferences" + }, + { + "do": "click", + "name": "Addons", + "role": "page tab", + "label": "Go to the Addons tab" + }, + { + "do": "settext", + "name": "MISRA addon", + "role": "text", + "value": "{project:embedded}/{rulefile}", + "label": "Select the rule text file" + }, + { + "do": "click", + "name": "OK", + "role": "button", + "label": "Close the dialog with OK" + }, + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "settext", + "name": "File name", + "role": "text", + "value": "{project:embedded}/embedded.cppcheck", + "label": "Enter the project file name" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Open the project file" + }, + { + "do": "sleep", + "seconds": 8, + "label": "Wait until the analysis is done" + }, + { + "expect": "visible", + "name": "gpio.c", + "role": "table cell", + "timeout": 20, + "label": "Results for gpio.c are shown" + }, + { + "do": "doubleclick", + "name": "gpio.c", + "role": "table cell", + "label": "Select the gpio.c result row" + }, + { + "do": "key", + "keys": [ + "Right" + ], + "label": "Expand the result row" + }, + { + "expect": "text", + "contains": "TEST RULE TEXT: unused parameters are not allowed", + "timeout": 10, + "label": "Misra error has the rule text from the file" + }, + { + "expect": "text", + "contains": "misra-c2012-2.7", + "label": "The unused parameter Misra rule id is shown" + }, + { + "expect": "text", + "not_contains": "internalError", + "label": "There are no internalError results" + } + ], + "script_for": "755194f793e0" + } +} diff --git a/gui/manualtest/tests/project_dialog.feature b/gui/manualtest/tests/project_dialog.feature new file mode 100644 index 00000000000..2d28b7dbc0e --- /dev/null +++ b/gui/manualtest/tests/project_dialog.feature @@ -0,0 +1,40 @@ +Feature: Project file dialog + + Background: + Given the application is started with no project loaded + + @id:tcf7ddb76 @ticket:14983 @project:relative-paths + Scenario: Relative paths in the saved project + Given a new project file is created and the project file dialog is open + When I import the project {project:relative-paths}/compile_commands.json (Paths and defines, Import project) + And I add the include directory {project:relative-paths}/inc + And I exclude the file {project:relative-paths}/skip/b.c + And I save the project file as {project:relative-paths}/rel.cppcheck + Then in the saved rel.cppcheck the import project is written relative to the project file: compile_commands.json + And the include directory is written relative to the project file: inc + And the excluded file is written relative to the project file: skip/b.c + And the XML contains no absolute path to the project folder + + @id:t5afff002 @ticket:14489 + Scenario: Select the platform pic8.xml + Given a new project file is created and the project file dialog is open + When I open the Platform drop-down list + And I select pic8.xml in the list + Then pic8.xml is in the Platform list + And pic8.xml is the platform that the dialog shows + + @id:t91490557 @ticket:14672 @project:custom-cfg + Scenario: Custom cfg file in the library list + Given the folder of {project:custom-cfg}/p.cppcheck also holds aa.cfg, a copy of cfg/avr.cfg + When I open the project file {project:custom-cfg}/p.cppcheck + And I open the project file dialog (edit the project file) + And I look at the library list (Libraries) + Then aa is in the library list + And aa comes before avr, the list is in alphabetical order + + @id:t0ec0680b @ticket:14488 @project:embedded @edition:opensource + Scenario: Misra checkbox of a loaded project + When I open the project file {project:embedded}/embedded.cppcheck + And I open the project file dialog (edit the project file) + And I go to the Addons tab + Then the Misra C 2012 checkbox is checked diff --git a/gui/manualtest/tests/project_dialog.scripts.json b/gui/manualtest/tests/project_dialog.scripts.json new file mode 100644 index 00000000000..08dfc5965ce --- /dev/null +++ b/gui/manualtest/tests/project_dialog.scripts.json @@ -0,0 +1,371 @@ +{ + "tcf7ddb76": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "New Project File", + "role": "menu item", + "label": "Create a new project file" + }, + { + "do": "settext", + "name": "File name", + "role": "text", + "value": "{project:relative-paths}/rel.cppcheck", + "label": "Enter the project file name" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the project file name" + }, + { + "expect": "visible", + "name": "Project file: rel.cppcheck", + "role": "dialog", + "label": "Project file dialog is open" + }, + { + "do": "click", + "name": "Add...", + "role": "button", + "nth": 1, + "label": "Add an include directory" + }, + { + "do": "settext", + "name": "Directory:", + "role": "text", + "value": "{project:relative-paths}/inc", + "label": "Enter the include directory {project:relative-paths}/inc" + }, + { + "do": "click", + "name": "Choose", + "role": "button", + "label": "Choose the include directory" + }, + { + "do": "click", + "name": "Warning options", + "role": "page tab", + "label": "Open the tab with the excluded files" + }, + { + "do": "click", + "name": "Exclude file", + "role": "button", + "label": "Exclude a file" + }, + { + "do": "settext", + "name": "File name", + "role": "text", + "value": "{project:relative-paths}/skip/b.c", + "label": "Enter the excluded file {project:relative-paths}/skip/b.c" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the excluded file" + }, + { + "expect": "visible", + "name": "skip/b.c", + "role": "list item", + "label": "skip/b.c is in the exclude list" + }, + { + "do": "click", + "name": "Paths and Defines", + "role": "page tab", + "label": "Back to Paths and Defines" + }, + { + "do": "click", + "name": "Browse...", + "role": "button", + "nth": 0, + "label": "Browse for the import project" + }, + { + "do": "settext", + "name": "File name", + "role": "text", + "value": "{project:relative-paths}/compile_commands.json", + "label": "Enter the import project {project:relative-paths}/compile_commands.json" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the import project" + }, + { + "do": "click", + "name": "OK", + "role": "button", + "label": "Save the project file as rel.cppcheck" + }, + { + "do": "sleep", + "seconds": 1 + }, + { + "do": "key", + "keys": [ + "Escape" + ], + "label": "Dismiss the build dir question" + }, + { + "expect": "file", + "path": "{project:relative-paths}/rel.cppcheck", + "contains": "compile_commands.json", + "label": "Import project is relative" + }, + { + "expect": "file", + "path": "{project:relative-paths}/rel.cppcheck", + "contains": "", + "label": "Excluded file is relative (skip/b.c)" + }, + { + "expect": "file", + "path": "{project:relative-paths}/rel.cppcheck", + "not_contains": "{project:relative-paths}", + "label": "No absolute path to the project folder in the XML" + } + ], + "script_for": "edfc96b05beb" + }, + "t5afff002": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "New Project File", + "role": "menu item", + "label": "Create a new project file" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{work}/t.cppcheck", + "label": "Enter the project file name" + }, + { + "do": "click", + "name": "Save", + "role": "button", + "exact": true, + "label": "Save the new project file; the project file dialog opens" + }, + { + "expect": "visible", + "name": "Project file", + "role": "dialog", + "label": "Project file dialog is open" + }, + { + "do": "click", + "name": "Types and Functions", + "role": "page tab", + "label": "Go to the tab with the Platform setting" + }, + { + "do": "click", + "name": "Platform", + "role": "combo box", + "label": "Open the Platform drop-down list" + }, + { + "expect": "visible", + "name": "pic8.xml", + "role": "list item", + "label": "pic8.xml is in the Platform list" + }, + { + "do": "click", + "name": "pic8.xml", + "role": "list item", + "exact": true, + "mouse": true, + "label": "Select pic8.xml in the list" + }, + { + "expect": "visible", + "name": "pic8.xml", + "role": "combo box", + "label": "The dialog shows pic8.xml as the platform" + } + ], + "script_for": "619f376b22e1" + }, + "t91490557": { + "script": [ + { + "do": "click", + "name": "Open Project File", + "role": "button", + "label": "Open the project file dialog from the toolbar" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:custom-cfg}/p.cppcheck", + "label": "Enter the path of p.cppcheck" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Open the project file" + }, + { + "expect": "visible", + "name": "Cppcheck", + "role": "alert", + "label": "Analysis finishes and an alert appears" + }, + { + "do": "click", + "name": "OK", + "role": "button", + "label": "Dismiss the alert" + }, + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Edit Project File", + "role": "menu item", + "label": "Open the project file dialog (edit the project file)" + }, + { + "expect": "visible", + "name": "Project file: p.cppcheck", + "role": "dialog", + "label": "The project file dialog is open" + }, + { + "expect": "visible", + "name": "aa", + "role": "list item", + "exact": true, + "label": "aa is in the Libraries list" + }, + { + "expect": "visible", + "name": "avr", + "role": "list item", + "exact": true, + "label": "avr is in the Libraries list" + } + ], + "script_for": "b0bf245f7042" + }, + "t0ec0680b": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "wait", + "name": "Select Project File", + "role": "dialog", + "label": "File dialog is shown" + }, + { + "do": "settext", + "name": "File name", + "role": "text", + "value": "{project:embedded}/embedded.cppcheck", + "label": "Enter the project file path" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Open the project file" + }, + { + "do": "wait", + "name": "Project: embedded.cppcheck", + "role": "frame", + "label": "Project is loaded" + }, + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu again" + }, + { + "do": "click", + "name": "Edit Project File...", + "role": "menu item", + "label": "Open the project file dialog" + }, + { + "do": "wait", + "name": "Project file: embedded.cppcheck", + "role": "dialog", + "label": "Project file dialog is shown" + }, + { + "do": "click", + "name": "Addons", + "role": "page tab", + "label": "Go to the Addons tab" + }, + { + "expect": "state", + "name": "Misra C 2012", + "role": "check box", + "state": "checked", + "label": "The Misra C 2012 checkbox is checked" + } + ], + "script_for": "187bd0d82a72" + } +} diff --git a/gui/manualtest/tests/projects.feature b/gui/manualtest/tests/projects.feature new file mode 100644 index 00000000000..a4c2324551e --- /dev/null +++ b/gui/manualtest/tests/projects.feature @@ -0,0 +1,72 @@ +Feature: Open and create projects + + Background: + Given the application is started with no project loaded + + @id:tf9fc8bf6 @project:basic-project + Scenario: Open a project file + When I open the project file {project:basic-project}/basic.cppcheck with the open project file entry in the File menu + Then the project opens + And the title of the main window names basic.cppcheck + And the analysis results are shown: nullPointer errors in a.c and util.h, arrayIndexOutOfBounds in b.c + + @id:t6bb11424 @project:switch-project + Scenario: Open a second project + TODO: with and without build dir + Given the project file {project:switch-project}/one/p1.cppcheck is opened + And the analysis is done + And the results show a nullPointer error in a.c + When I open the project file {project:switch-project}/two/p2.cppcheck + And I wait until the analysis is done + Then the results of the first project (nullPointer in a.c) are gone + And the results of the second project (arrayIndexOutOfBounds in b.c) are shown + + @id:t176fd17f @project:switch-project + Scenario: Open a recent project + Given the project file {project:switch-project}/one/p1.cppcheck is opened and analysed + And the project file {project:switch-project}/two/p2.cppcheck is opened and analysed + When I open the File menu + And I click p1.cppcheck in the list of recent projects + Then the project p1.cppcheck is opened + And the title of the main window names p1.cppcheck, not p2.cppcheck or another file + + @id:t1e899c1c @project:basic-project + Scenario: New project file + When I open File > New Project File + And I enter the file name {project:basic-project}/OSgui.cppcheck + And I add the folder {project:basic-project}/src as a path to check in the project file dialog + And I save the project file without selecting an addon + And I wait until the analysis is done + Then the project file OSgui.cppcheck is saved without any addon in it + And the analysis is run + And the results are shown: nullPointer errors in a.c, arrayIndexOutOfBounds in b.c + And util.h is not checked, the new project has no include path to it + + @id:t202b9515 @project:basic-project + Scenario Outline: New project file with an addon + When I open File > New Project File + And I enter the file name {project:basic-project}/OSgui.cppcheck + And I add the folder {project:basic-project}/src as a path to check in the project file dialog + And I tick the checkbox of the addon whose name contains "" on the Addons tab (Misra C, Y2038 or Thread safety) + And I save the project file + And I wait until the analysis is done + Then the project file OSgui.cppcheck is saved with an addon entry that contains "" (misra, y2038 or threadsafety) + And the analysis is run and results are shown + And none of the results is an internalError about the addon + + Examples: + | addon | + | misra | + | y2038 | + | thread | + + @id:tf6da2cfd @project:basic-project @edition:opensource + Scenario: Add the Misra addon to a project + Given the project file {project:basic-project}/basic.cppcheck is opened from the File menu + And the analysis is done + When I open File > Edit Project File + And I select MISRA C 2012 on the Addons tab + And I save the project file + And I wait until the analysis is done + Then basic.cppcheck has the addon misra in it + And the analysis is run again diff --git a/gui/manualtest/tests/projects.scripts.json b/gui/manualtest/tests/projects.scripts.json new file mode 100644 index 00000000000..a78d2f6b793 --- /dev/null +++ b/gui/manualtest/tests/projects.scripts.json @@ -0,0 +1,771 @@ +{ + "tf9fc8bf6": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File in the File menu" + }, + { + "expect": "visible", + "name": "Select Project File", + "role": "dialog", + "label": "The project file dialog is shown" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the path of basic.cppcheck" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the dialog to open the project" + }, + { + "expect": "visible", + "name": "Project: basic.cppcheck", + "role": "frame", + "timeout": 15, + "label": "The title of the main window names basic.cppcheck" + }, + { + "do": "wait", + "name": "Select Project File", + "gone": true, + "label": "The dialog is closed" + }, + { + "do": "sleep", + "seconds": 4, + "label": "Wait for the analysis to finish" + }, + { + "do": "focus", + "role": "tree", + "label": "Focus the results tree" + }, + { + "do": "key", + "keys": [ + "Home", + "Down", + "Down", + "Right", + "Up", + "Right", + "Up", + "Right", + "Home" + ], + "label": "Expand the results of all three files and go to the top" + }, + { + "expect": "visible", + "name": "nullPointer", + "role": "table cell", + "timeout": 10, + "label": "nullPointer errors are shown for util.h and a.c" + }, + { + "expect": "text", + "contains": "src/a.c", + "label": "Results for a.c are shown" + }, + { + "expect": "text", + "contains": "include/util.h", + "label": "Results for util.h are shown" + }, + { + "do": "key", + "keys": [ + "End" + ], + "label": "Scroll to the results of b.c" + }, + { + "expect": "visible", + "name": "arrayIndexOutOfBounds", + "role": "table cell", + "timeout": 10, + "label": "arrayIndexOutOfBounds error is shown for b.c" + } + ], + "script_for": "7128457516b3" + }, + "t6bb11424": { + "script": [ + { + "do": "click", + "name": "Open Project File", + "role": "button", + "label": "Open the first project" + }, + { + "do": "type", + "value": "{project:switch-project}/one/p1.cppcheck", + "label": "Enter path of p1.cppcheck" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the file dialog" + }, + { + "expect": "visible", + "name": "Project: p1.cppcheck", + "role": "frame", + "timeout": 10, + "label": "First project is loaded" + }, + { + "expect": "visible", + "name": "a.c", + "role": "table cell", + "timeout": 20, + "label": "Results of the first project show a.c" + }, + { + "do": "click", + "name": "Open Project File", + "role": "button", + "label": "Open the second project" + }, + { + "do": "type", + "value": "{project:switch-project}/two/p2.cppcheck", + "label": "Enter path of p2.cppcheck" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the file dialog" + }, + { + "expect": "visible", + "name": "Project: p2.cppcheck", + "role": "frame", + "timeout": 10, + "label": "Second project is loaded" + }, + { + "expect": "visible", + "name": "b.c", + "role": "table cell", + "timeout": 20, + "label": "Results of the second project show b.c" + }, + { + "expect": "gone", + "name": "a.c", + "role": "table cell", + "label": "Results of the first project are gone" + } + ], + "script_for": "70097c80a6ed" + }, + "t176fd17f": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:switch-project}/one/p1.cppcheck", + "label": "Enter the path of p1.cppcheck" + }, + { + "do": "click", + "name": "Open", + "role": "button", + "exact": true, + "label": "Open p1.cppcheck" + }, + { + "expect": "text", + "contains": "Project: p1.cppcheck", + "timeout": 10, + "label": "Project p1 is open" + }, + { + "expect": "visible", + "name": "Checking a.c", + "timeout": 15, + "label": "Analysis of p1 is done" + }, + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:switch-project}/two/p2.cppcheck", + "label": "Enter the path of p2.cppcheck" + }, + { + "do": "click", + "name": "Open", + "role": "button", + "exact": true, + "label": "Open p2.cppcheck" + }, + { + "expect": "text", + "contains": "Project: p2.cppcheck", + "timeout": 10, + "label": "Project p2 is open" + }, + { + "do": "sleep", + "seconds": 3, + "label": "Wait until the analysis of p2 is done" + }, + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "p1.cppcheck", + "role": "menu item", + "label": "Click p1.cppcheck in the recent projects list" + }, + { + "expect": "text", + "contains": "Project: p1.cppcheck", + "timeout": 10, + "label": "Main window title names p1.cppcheck" + }, + { + "expect": "text", + "not_contains": "Project: p2.cppcheck", + "label": "Title does not name p2.cppcheck" + } + ], + "script_for": "2ea42621479d" + }, + "t1e899c1c": { + "script": [ + { + "label": "Open File menu", + "do": "click", + "name": "File", + "role": "menu item" + }, + { + "label": "Open File > New Project File", + "do": "click", + "name": "New Project File", + "role": "menu item" + }, + { + "label": "Wait for the file name dialog", + "do": "wait", + "name": "Select Project Filename", + "role": "dialog" + }, + { + "label": "Enter the file name OSgui.cppcheck", + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/OSgui.cppcheck" + }, + { + "label": "Confirm the file name", + "do": "key", + "keys": [ + "Return" + ] + }, + { + "label": "Project file dialog is shown", + "expect": "visible", + "name": "Project file: OSgui.cppcheck", + "role": "dialog" + }, + { + "label": "Add a path to check", + "do": "click", + "name": "Add...", + "role": "button", + "nth": 0 + }, + { + "label": "Wait for the directory dialog", + "do": "wait", + "name": "Select a directory to check", + "role": "dialog" + }, + { + "label": "Enter the src folder", + "do": "settext", + "name": "Directory:", + "role": "text", + "value": "{project:basic-project}/src" + }, + { + "label": "Choose the src folder", + "do": "click", + "name": "Choose", + "role": "button" + }, + { + "label": "src is in the paths list", + "expect": "visible", + "name": "src", + "role": "list item" + }, + { + "label": "Save the project file without selecting an addon", + "do": "click", + "name": "OK", + "role": "button" + }, + { + "label": "Create the build dir when asked", + "do": "click", + "name": "Yes", + "role": "button" + }, + { + "label": "Wait until the analysis has checked b.c", + "expect": "text", + "contains": "Checking src/b.c", + "timeout": 30 + }, + { + "label": "Wait for the results of a.c", + "expect": "visible", + "name": "src/a.c", + "role": "table cell", + "timeout": 30 + }, + { + "label": "Select a.c results", + "do": "click", + "name": "src/a.c", + "role": "table cell", + "nth": 0, + "mouse": true + }, + { + "label": "Collapse a.c results to get a known state", + "do": "key", + "keys": [ + "Left" + ] + }, + { + "label": "Expand a.c results", + "do": "key", + "keys": [ + "Right" + ] + }, + { + "label": "nullPointer is reported in a.c", + "expect": "visible", + "name": "nullPointer", + "role": "table cell", + "timeout": 10 + }, + { + "label": "Select b.c results", + "do": "click", + "name": "src/b.c", + "role": "table cell", + "nth": 0, + "mouse": true + }, + { + "label": "Collapse b.c results to get a known state", + "do": "key", + "keys": [ + "Left" + ] + }, + { + "label": "Expand b.c results", + "do": "key", + "keys": [ + "Right" + ] + }, + { + "label": "arrayIndexOutOfBounds is reported in b.c", + "expect": "visible", + "name": "arrayIndexOutOfBounds", + "role": "table cell", + "timeout": 10 + }, + { + "label": "util.h is not checked", + "expect": "text", + "not_contains": "Checking include/util.h" + }, + { + "label": "Project file contains the src path", + "expect": "file", + "path": "{project:basic-project}/OSgui.cppcheck", + "contains": "" + }, + { + "label": "Project file has no addon", + "expect": "file", + "path": "{project:basic-project}/OSgui.cppcheck", + "not_contains": "addon" + }, + { + "label": "Project file has no include path", + "expect": "file", + "path": "{project:basic-project}/OSgui.cppcheck", + "not_contains": "include" + } + ], + "script_for": "b3616d866fea" + }, + "t202b9515": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "New Project File...", + "role": "menu item", + "label": "Open File > New Project File" + }, + { + "do": "wait", + "name": "Select Project Filename", + "role": "dialog", + "label": "Wait for the file name dialog" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/OSgui.cppcheck", + "label": "Enter the file name OSgui.cppcheck" + }, + { + "do": "focus", + "name": "File name:", + "role": "text", + "label": "Focus the file name field" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the file name" + }, + { + "do": "wait", + "name": "Project file: OSgui.cppcheck", + "role": "dialog", + "timeout": 10, + "label": "Wait for the project file dialog" + }, + { + "do": "click", + "name": "Paths and Defines", + "role": "page tab", + "label": "Go to the Paths and Defines tab" + }, + { + "do": "click", + "name": "Add...", + "role": "button", + "nth": 0, + "label": "Add a path to check" + }, + { + "do": "wait", + "name": "Select a directory to check", + "role": "dialog", + "label": "Wait for the directory dialog" + }, + { + "do": "settext", + "name": "Directory:", + "role": "text", + "value": "{project:basic-project}/src", + "label": "Enter the folder src" + }, + { + "do": "click", + "name": "Choose", + "role": "button", + "label": "Choose the folder src" + }, + { + "do": "wait", + "name": "Select a directory to check", + "role": "dialog", + "gone": true, + "label": "Directory dialog closes" + }, + { + "expect": "visible", + "name": "src", + "role": "list item", + "exact": true, + "label": "src is in the paths to check" + }, + { + "do": "click", + "name": "Addons", + "role": "page tab", + "label": "Go to the Addons tab" + }, + { + "do": "click", + "name": "{addon}", + "role": "check box", + "nth": 0, + "label": "Tick the addon checkbox" + }, + { + "expect": "state", + "name": "{addon}", + "role": "check box", + "nth": 0, + "state": "checked", + "label": "The addon checkbox is ticked" + }, + { + "do": "click", + "name": "OK", + "role": "button", + "label": "Save the project file" + }, + { + "do": "wait", + "name": "Yes", + "role": "button", + "timeout": 10, + "label": "Question to create the build dir" + }, + { + "do": "click", + "name": "Yes", + "role": "button", + "label": "Create the build dir" + }, + { + "do": "wait", + "name": "src/b.c", + "role": "table cell", + "timeout": 120, + "label": "Wait for the analysis results" + }, + { + "do": "sleep", + "seconds": 3 + }, + { + "expect": "state", + "name": "Stop analysis", + "role": "button", + "not_state": "enabled", + "timeout": 120, + "label": "The analysis is done" + }, + { + "do": "focus", + "name": "src/a.c", + "role": "table cell", + "label": "Select the results of a.c" + }, + { + "do": "key", + "keys": [ + "asterisk", + "End", + "asterisk" + ], + "label": "Expand the results of all files" + }, + { + "do": "sleep", + "seconds": 1 + }, + { + "expect": "file", + "path": "{project:basic-project}/OSgui.cppcheck", + "contains": "{addon}", + "label": "Project file has the addon entry" + }, + { + "expect": "file", + "path": "{project:basic-project}/OSgui.cppcheck", + "contains": "", + "label": "Project file has the path src" + }, + { + "expect": "visible", + "name": "src/a.c", + "role": "table cell", + "label": "Analysis results are shown" + }, + { + "expect": "text", + "not_contains": "internalError", + "label": "No internalError about the addon in the results" + } + ], + "script_for": "60cd34c48191" + }, + "tf6da2cfd": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the project file path" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the file dialog" + }, + { + "do": "wait", + "name": "Checking src/b.c", + "timeout": 20, + "label": "Wait for the first analysis to run" + }, + { + "do": "sleep", + "seconds": 3, + "label": "Let the first analysis finish" + }, + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Edit Project File", + "role": "menu item", + "label": "Choose Edit Project File" + }, + { + "expect": "visible", + "name": "Project file: basic.cppcheck", + "role": "dialog", + "label": "Project file dialog is open" + }, + { + "do": "click", + "name": "Addons", + "role": "page tab", + "label": "Go to the Addons tab" + }, + { + "do": "click", + "name": "Misra C 2012", + "role": "check box", + "label": "Select MISRA C 2012" + }, + { + "expect": "state", + "name": "Misra C 2012", + "state": "checked", + "label": "MISRA C 2012 is checked" + }, + { + "do": "click", + "name": "OK", + "role": "button", + "label": "Save the project file" + }, + { + "expect": "gone", + "name": "Project file: basic.cppcheck", + "label": "Project file dialog is closed" + }, + { + "do": "sleep", + "seconds": 4, + "label": "Wait for the second analysis to finish" + }, + { + "expect": "file", + "path": "{project:basic-project}/basic.cppcheck", + "contains": "misra", + "label": "Project file has the misra addon" + }, + { + "expect": "visible", + "name": "Checking src/b.c", + "role": "list item", + "nth": 1, + "label": "The analysis was run again" + } + ], + "script_for": "452f351f0fb2" + } +} diff --git a/gui/manualtest/tests/projects/basic-project/basic.cppcheck b/gui/manualtest/tests/projects/basic-project/basic.cppcheck new file mode 100644 index 00000000000..c8805a32587 --- /dev/null +++ b/gui/manualtest/tests/projects/basic-project/basic.cppcheck @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/gui/manualtest/tests/projects/basic-project/include/util.h b/gui/manualtest/tests/projects/basic-project/include/util.h new file mode 100644 index 00000000000..48ae2d54a95 --- /dev/null +++ b/gui/manualtest/tests/projects/basic-project/include/util.h @@ -0,0 +1,5 @@ +static inline int helper(void) +{ + int *r = 0; + return *r; +} diff --git a/gui/manualtest/tests/projects/basic-project/src/a.c b/gui/manualtest/tests/projects/basic-project/src/a.c new file mode 100644 index 00000000000..3a84d86a5ff --- /dev/null +++ b/gui/manualtest/tests/projects/basic-project/src/a.c @@ -0,0 +1,18 @@ +#include "util.h" + +int first(void) +{ + int *p = 0; + return *p; +} + +int second(void) +{ + int *q = 0; + return *q; +} + +int main(void) +{ + return first() + second() + helper(); +} diff --git a/gui/manualtest/tests/projects/basic-project/src/b.c b/gui/manualtest/tests/projects/basic-project/src/b.c new file mode 100644 index 00000000000..20be2edc145 --- /dev/null +++ b/gui/manualtest/tests/projects/basic-project/src/b.c @@ -0,0 +1,5 @@ +void f(void) +{ + char a[4]; + a[10] = 0; +} diff --git a/gui/manualtest/tests/projects/custom-cfg/a.c b/gui/manualtest/tests/projects/custom-cfg/a.c new file mode 100644 index 00000000000..58fe69254d0 --- /dev/null +++ b/gui/manualtest/tests/projects/custom-cfg/a.c @@ -0,0 +1,4 @@ +int main(void) +{ + return 0; +} diff --git a/gui/manualtest/tests/projects/custom-cfg/aa.cfg b/gui/manualtest/tests/projects/custom-cfg/aa.cfg new file mode 100644 index 00000000000..bb21afac352 --- /dev/null +++ b/gui/manualtest/tests/projects/custom-cfg/aa.cfg @@ -0,0 +1,402 @@ + + + + + + + + + + + + + + + + + + + + + + + false + + + 0:255 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + false + + + + + + + + + + + false + + + + + + + + + + + false + + + + + + + + + + + false + + + + + + + + + + + false + + + + + + + + + + + false + + + + + + + + + + + + + + + false + + + + + + + + + + + + false + + + + + + 0: + + + + false + + + + + + + + + + + + + + false + + + + + + + + + + + + + + false + + + + + + + + false + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + false + + + + false + + + + + + + false + + + + + + + false + + + + + + + + + + false + + + + + + + + + + + + + + + + + + + + + false + + + + + + + false + + + + + + + false + + + + + + + false + + + + + + + + + + + + + + + diff --git a/gui/manualtest/tests/projects/custom-cfg/p.cppcheck b/gui/manualtest/tests/projects/custom-cfg/p.cppcheck new file mode 100644 index 00000000000..e8db10582ad --- /dev/null +++ b/gui/manualtest/tests/projects/custom-cfg/p.cppcheck @@ -0,0 +1,6 @@ + + + + + + diff --git a/gui/manualtest/tests/projects/embedded/board.h b/gui/manualtest/tests/projects/embedded/board.h new file mode 100644 index 00000000000..8a5bddc04a0 --- /dev/null +++ b/gui/manualtest/tests/projects/embedded/board.h @@ -0,0 +1,10 @@ +#ifndef BOARD_H +#define BOARD_H + +#define GPIOA_ODR (*(volatile unsigned int *)0x40020014U) +#define UART_DR (*(volatile unsigned int *)0x40011004U) + +void gpio_set(unsigned int pin, unsigned int mask); +void uart_putc(char c); + +#endif diff --git a/gui/manualtest/tests/projects/embedded/embedded.cppcheck b/gui/manualtest/tests/projects/embedded/embedded.cppcheck new file mode 100644 index 00000000000..679b87f6d90 --- /dev/null +++ b/gui/manualtest/tests/projects/embedded/embedded.cppcheck @@ -0,0 +1,9 @@ + + + + + + + misra + + diff --git a/gui/manualtest/tests/projects/embedded/gpio.c b/gui/manualtest/tests/projects/embedded/gpio.c new file mode 100644 index 00000000000..73968d86135 --- /dev/null +++ b/gui/manualtest/tests/projects/embedded/gpio.c @@ -0,0 +1,6 @@ +#include "board.h" + +void gpio_set(unsigned int pin, unsigned int mask) +{ + GPIOA_ODR |= mask; +} diff --git a/gui/manualtest/tests/projects/embedded/main.c b/gui/manualtest/tests/projects/embedded/main.c new file mode 100644 index 00000000000..6daafe949b4 --- /dev/null +++ b/gui/manualtest/tests/projects/embedded/main.c @@ -0,0 +1,10 @@ +#include "board.h" + +int main(void) +{ + gpio_set(5U, 0x20U); + uart_putc('A'); + for (;;) + { + } +} diff --git a/gui/manualtest/tests/projects/embedded/rules with spaces.txt b/gui/manualtest/tests/projects/embedded/rules with spaces.txt new file mode 100644 index 00000000000..a707cb8673d --- /dev/null +++ b/gui/manualtest/tests/projects/embedded/rules with spaces.txt @@ -0,0 +1,7 @@ +Appendix A Summary of guidelines + +Rule 2.7 +TEST RULE TEXT: unused parameters are not allowed +Category Advisory +Analysis Decidable, Single Translation Unit +Applies to C90, C99 diff --git a/gui/manualtest/tests/projects/embedded/rules.txt b/gui/manualtest/tests/projects/embedded/rules.txt new file mode 100644 index 00000000000..a707cb8673d --- /dev/null +++ b/gui/manualtest/tests/projects/embedded/rules.txt @@ -0,0 +1,7 @@ +Appendix A Summary of guidelines + +Rule 2.7 +TEST RULE TEXT: unused parameters are not allowed +Category Advisory +Analysis Decidable, Single Translation Unit +Applies to C90, C99 diff --git a/gui/manualtest/tests/projects/embedded/uart.c b/gui/manualtest/tests/projects/embedded/uart.c new file mode 100644 index 00000000000..3720b2fde22 --- /dev/null +++ b/gui/manualtest/tests/projects/embedded/uart.c @@ -0,0 +1,6 @@ +#include "board.h" + +void uart_putc(char c) +{ + UART_DR = (unsigned int)c; +} diff --git a/gui/manualtest/tests/projects/recheck/a.c b/gui/manualtest/tests/projects/recheck/a.c new file mode 100644 index 00000000000..a4f00f729cb --- /dev/null +++ b/gui/manualtest/tests/projects/recheck/a.c @@ -0,0 +1,5 @@ +int main(void) +{ + int *p = 0; + return *p; +} diff --git a/gui/manualtest/tests/projects/recheck/a_v2.c b/gui/manualtest/tests/projects/recheck/a_v2.c new file mode 100644 index 00000000000..229abfebb92 --- /dev/null +++ b/gui/manualtest/tests/projects/recheck/a_v2.c @@ -0,0 +1,5 @@ +int main(void) +{ + int x; + return x; +} diff --git a/gui/manualtest/tests/projects/recheck/b.c b/gui/manualtest/tests/projects/recheck/b.c new file mode 100644 index 00000000000..20be2edc145 --- /dev/null +++ b/gui/manualtest/tests/projects/recheck/b.c @@ -0,0 +1,5 @@ +void f(void) +{ + char a[4]; + a[10] = 0; +} diff --git a/gui/manualtest/tests/projects/recheck/p.cppcheck b/gui/manualtest/tests/projects/recheck/p.cppcheck new file mode 100644 index 00000000000..760eb8837f8 --- /dev/null +++ b/gui/manualtest/tests/projects/recheck/p.cppcheck @@ -0,0 +1,7 @@ + + + + + + + diff --git a/gui/manualtest/tests/projects/relative-paths/compile_commands.json b/gui/manualtest/tests/projects/relative-paths/compile_commands.json new file mode 100644 index 00000000000..130de6bca98 --- /dev/null +++ b/gui/manualtest/tests/projects/relative-paths/compile_commands.json @@ -0,0 +1,4 @@ +[ + {"directory": ".", "command": "gcc -c src/a.c", "file": "src/a.c"}, + {"directory": ".", "command": "gcc -c skip/b.c", "file": "skip/b.c"} +] diff --git a/gui/manualtest/tests/projects/relative-paths/inc/util.h b/gui/manualtest/tests/projects/relative-paths/inc/util.h new file mode 100644 index 00000000000..874a45c458c --- /dev/null +++ b/gui/manualtest/tests/projects/relative-paths/inc/util.h @@ -0,0 +1 @@ +int helper(int x); diff --git a/gui/manualtest/tests/projects/relative-paths/skip/b.c b/gui/manualtest/tests/projects/relative-paths/skip/b.c new file mode 100644 index 00000000000..5bb061143c0 --- /dev/null +++ b/gui/manualtest/tests/projects/relative-paths/skip/b.c @@ -0,0 +1,4 @@ +int unused(void) +{ + return 0; +} diff --git a/gui/manualtest/tests/projects/relative-paths/src/a.c b/gui/manualtest/tests/projects/relative-paths/src/a.c new file mode 100644 index 00000000000..db8654dea9f --- /dev/null +++ b/gui/manualtest/tests/projects/relative-paths/src/a.c @@ -0,0 +1,6 @@ +#include "util.h" + +int main(void) +{ + return helper(1); +} diff --git a/gui/manualtest/tests/projects/switch-project/one/a.c b/gui/manualtest/tests/projects/switch-project/one/a.c new file mode 100644 index 00000000000..a4f00f729cb --- /dev/null +++ b/gui/manualtest/tests/projects/switch-project/one/a.c @@ -0,0 +1,5 @@ +int main(void) +{ + int *p = 0; + return *p; +} diff --git a/gui/manualtest/tests/projects/switch-project/one/p1.cppcheck b/gui/manualtest/tests/projects/switch-project/one/p1.cppcheck new file mode 100644 index 00000000000..5eb66587401 --- /dev/null +++ b/gui/manualtest/tests/projects/switch-project/one/p1.cppcheck @@ -0,0 +1,6 @@ + + + + + + diff --git a/gui/manualtest/tests/projects/switch-project/two/b.c b/gui/manualtest/tests/projects/switch-project/two/b.c new file mode 100644 index 00000000000..20be2edc145 --- /dev/null +++ b/gui/manualtest/tests/projects/switch-project/two/b.c @@ -0,0 +1,5 @@ +void f(void) +{ + char a[4]; + a[10] = 0; +} diff --git a/gui/manualtest/tests/projects/switch-project/two/p2.cppcheck b/gui/manualtest/tests/projects/switch-project/two/p2.cppcheck new file mode 100644 index 00000000000..a79d287d385 --- /dev/null +++ b/gui/manualtest/tests/projects/switch-project/two/p2.cppcheck @@ -0,0 +1,6 @@ + + + + + + diff --git a/gui/manualtest/tests/projects/syntax-error/binary.c b/gui/manualtest/tests/projects/syntax-error/binary.c new file mode 100644 index 00000000000..c91312c3af0 Binary files /dev/null and b/gui/manualtest/tests/projects/syntax-error/binary.c differ diff --git a/gui/manualtest/tests/projects/syntax-error/binary.cppcheck b/gui/manualtest/tests/projects/syntax-error/binary.cppcheck new file mode 100644 index 00000000000..7804897af2d --- /dev/null +++ b/gui/manualtest/tests/projects/syntax-error/binary.cppcheck @@ -0,0 +1,7 @@ + + + + + + + diff --git a/gui/manualtest/tests/projects/syntax-error/braces.c b/gui/manualtest/tests/projects/syntax-error/braces.c new file mode 100644 index 00000000000..66d81f572c3 --- /dev/null +++ b/gui/manualtest/tests/projects/syntax-error/braces.c @@ -0,0 +1,3 @@ +int main( { + return 0 +} diff --git a/gui/manualtest/tests/projects/syntax-error/braces.cppcheck b/gui/manualtest/tests/projects/syntax-error/braces.cppcheck new file mode 100644 index 00000000000..94d206fd67c --- /dev/null +++ b/gui/manualtest/tests/projects/syntax-error/braces.cppcheck @@ -0,0 +1,7 @@ + + + + + + + diff --git a/gui/manualtest/tests/projects/syntax-error/empty.c b/gui/manualtest/tests/projects/syntax-error/empty.c new file mode 100644 index 00000000000..e69de29bb2d diff --git a/gui/manualtest/tests/projects/syntax-error/empty.cppcheck b/gui/manualtest/tests/projects/syntax-error/empty.cppcheck new file mode 100644 index 00000000000..eb36efbe086 --- /dev/null +++ b/gui/manualtest/tests/projects/syntax-error/empty.cppcheck @@ -0,0 +1,7 @@ + + + + + + + diff --git a/gui/manualtest/tests/projects/syntax-error/latin1.c b/gui/manualtest/tests/projects/syntax-error/latin1.c new file mode 100644 index 00000000000..7e730fd3a83 --- /dev/null +++ b/gui/manualtest/tests/projects/syntax-error/latin1.c @@ -0,0 +1,6 @@ +/* färg och smör */ +int färg(void) +{ + const char *s = "grön"; + return s[0]; +} diff --git a/gui/manualtest/tests/projects/syntax-error/latin1.cppcheck b/gui/manualtest/tests/projects/syntax-error/latin1.cppcheck new file mode 100644 index 00000000000..06ccf58dfd5 --- /dev/null +++ b/gui/manualtest/tests/projects/syntax-error/latin1.cppcheck @@ -0,0 +1,7 @@ + + + + + + + diff --git a/gui/manualtest/tests/projects/syntax-error/long_line.c b/gui/manualtest/tests/projects/syntax-error/long_line.c new file mode 100644 index 00000000000..14807c9e52b --- /dev/null +++ b/gui/manualtest/tests/projects/syntax-error/long_line.c @@ -0,0 +1 @@ +int x = a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + diff --git a/gui/manualtest/tests/projects/syntax-error/long_line.cppcheck b/gui/manualtest/tests/projects/syntax-error/long_line.cppcheck new file mode 100644 index 00000000000..f13ad9aa112 --- /dev/null +++ b/gui/manualtest/tests/projects/syntax-error/long_line.cppcheck @@ -0,0 +1,7 @@ + + + + + + + diff --git a/gui/manualtest/tests/projects/syntax-error/ok.c b/gui/manualtest/tests/projects/syntax-error/ok.c new file mode 100644 index 00000000000..8e1e58ce827 --- /dev/null +++ b/gui/manualtest/tests/projects/syntax-error/ok.c @@ -0,0 +1,5 @@ +int ok(void) +{ + int *p = 0; + return *p; +} diff --git a/gui/manualtest/tests/projects/syntax-error/unclosed_nesting.c b/gui/manualtest/tests/projects/syntax-error/unclosed_nesting.c new file mode 100644 index 00000000000..7d6ac74625a --- /dev/null +++ b/gui/manualtest/tests/projects/syntax-error/unclosed_nesting.c @@ -0,0 +1,4 @@ +int f(int a) +{ + return ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((a; +} diff --git a/gui/manualtest/tests/projects/syntax-error/unclosed_nesting.cppcheck b/gui/manualtest/tests/projects/syntax-error/unclosed_nesting.cppcheck new file mode 100644 index 00000000000..baeddf3ef2f --- /dev/null +++ b/gui/manualtest/tests/projects/syntax-error/unclosed_nesting.cppcheck @@ -0,0 +1,7 @@ + + + + + + + diff --git a/gui/manualtest/tests/projects/xml-results/a.c b/gui/manualtest/tests/projects/xml-results/a.c new file mode 100644 index 00000000000..38775adee5f --- /dev/null +++ b/gui/manualtest/tests/projects/xml-results/a.c @@ -0,0 +1,5 @@ +int first(void) +{ + int *p = 0; + return *p; +} diff --git a/gui/manualtest/tests/projects/xml-results/results.xml b/gui/manualtest/tests/projects/xml-results/results.xml new file mode 100644 index 00000000000..c6f4b14c0c1 --- /dev/null +++ b/gui/manualtest/tests/projects/xml-results/results.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/gui/manualtest/tests/result_list.feature b/gui/manualtest/tests/result_list.feature new file mode 100644 index 00000000000..f370d296fb0 --- /dev/null +++ b/gui/manualtest/tests/result_list.feature @@ -0,0 +1,91 @@ +Feature: Results list + + Background: + Given the application is started with no project loaded + + @id:t852206f1 + Scenario: Severity buttons toggle + When I click each of the toolbar buttons Error, Warning, Style, Portability, Performance, Information, Cppcheck and Clang once + Then every button can be clicked and changes between checked and unchecked + When I click each of them once more + Then all buttons are in the state they had at the start + + @id:td7decf1a @project:basic-project + Scenario: Filter the results + Given the project file {project:basic-project}/basic.cppcheck is opened from the File menu + And the analysis is done + When I type "arrayIndex" in the Filter field + Then only the arrayIndexOutOfBounds error in b.c is shown + When I clear the Filter field + Then all errors are shown again + + @id:t2af9d498 @project:basic-project + Scenario: Expand all + Given the project file {project:basic-project}/basic.cppcheck is opened from the File menu + And the analysis is done + When I open View > Collapse All + And I open View > Expand All + Then all files are expanded and show their errors + And the errors in a.c, util.h and b.c are visible + + @id:tec898362 @project:basic-project + Scenario: Collapse all + Given the project file {project:basic-project}/basic.cppcheck is opened from the File menu + And the analysis is done + When I open View > Expand All + And I open View > Collapse All + Then all files are collapsed + And the file entries are shown and their errors are not + + @id:t4c027507 @project:basic-project + Scenario: Context menu of a file entry + Given the project file {project:basic-project}/basic.cppcheck is opened from the File menu + And the analysis is done + When I right click on the file a.c in the results (not on one of its errors) + Then the context menu of the file entry has the entries Recheck and Open containing folder + But it has none of the entries Hide, Hide all with ID or Suppress selected id, they only apply to a single error + + @id:taacc3841 @project:basic-project + Scenario: Hide one error + Given the project file {project:basic-project}/basic.cppcheck is opened from the File menu + And the analysis is done + When I expand a.c in the results + And I right click on the first nullPointer error (line 6) and click Hide + Then that error is gone + And the other errors are still shown, including the second nullPointer error in a.c (line 12) + + @id:t2937b690 @project:basic-project + Scenario: Hide all errors with the same ID + Given the project file {project:basic-project}/basic.cppcheck is opened from the File menu + And the analysis is done + When I expand a.c in the results + And I right click on a nullPointer error and click Hide all with ID + Then all nullPointer errors are gone, in a.c and in util.h + And the arrayIndexOutOfBounds error in b.c is still shown + + @id:t313463d3 @project:basic-project + Scenario: Show hidden errors again + Given the project file {project:basic-project}/basic.cppcheck is opened from the File menu + And the analysis is done + When I expand a.c in the results + And I right click on the first nullPointer error and click Hide all with id + Then the a.c results are hidden + When I open View > Show Hidden + Then the error that was hidden is shown again + + @id:t90bf32e9 @project:basic-project + Scenario: Suppress selected ID + Given the project file {project:basic-project}/basic.cppcheck is opened from the File menu + And the analysis is done + When I expand a.c in the results + And I right click on a nullPointer error and click Suppress selected id + And I open File > Edit Project File and go to Warning options + Then no nullPointer error is shown in the results + And the id nullPointer is listed under Suppressions in the project file dialog + + @id:t389a5f3a @project:basic-project + Scenario: Clear results + Given the project file {project:basic-project}/basic.cppcheck is opened from the File menu + And the analysis is done + When I click Clear results + Then the results are cleared: the result tree is empty diff --git a/gui/manualtest/tests/result_list.scripts.json b/gui/manualtest/tests/result_list.scripts.json new file mode 100644 index 00000000000..739dd0bd32e --- /dev/null +++ b/gui/manualtest/tests/result_list.scripts.json @@ -0,0 +1,1127 @@ +{ + "t852206f1": { + "script": [ + { + "do": "click", + "name": "Show errors", + "role": "check box", + "label": "Click Error once" + }, + { + "do": "click", + "name": "Show warnings", + "role": "check box", + "label": "Click Warning once" + }, + { + "do": "click", + "name": "Show style warnings", + "role": "check box", + "label": "Click Style once" + }, + { + "do": "click", + "name": "Show portability", + "role": "check box", + "label": "Click Portability once" + }, + { + "do": "click", + "name": "Show performance warnings", + "role": "check box", + "label": "Click Performance once" + }, + { + "do": "click", + "name": "Show information", + "role": "check box", + "label": "Click Information once" + }, + { + "do": "click", + "name": "Cppcheck", + "role": "check box", + "label": "Click Cppcheck once" + }, + { + "do": "click", + "name": "Clang", + "role": "check box", + "label": "Click Clang once" + }, + { + "expect": "state", + "name": "Show errors", + "role": "check box", + "not_state": "checked", + "label": "Error changed to unchecked after first click" + }, + { + "expect": "state", + "name": "Show warnings", + "role": "check box", + "not_state": "checked", + "label": "Warning changed to unchecked after first click" + }, + { + "expect": "state", + "name": "Show style warnings", + "role": "check box", + "not_state": "checked", + "label": "Style changed to unchecked after first click" + }, + { + "expect": "state", + "name": "Show portability", + "role": "check box", + "not_state": "checked", + "label": "Portability changed to unchecked after first click" + }, + { + "expect": "state", + "name": "Show performance warnings", + "role": "check box", + "not_state": "checked", + "label": "Performance changed to unchecked after first click" + }, + { + "expect": "state", + "name": "Show information", + "role": "check box", + "not_state": "checked", + "label": "Information changed to unchecked after first click" + }, + { + "expect": "state", + "name": "Cppcheck", + "role": "check box", + "not_state": "checked", + "label": "Cppcheck changed to unchecked after first click" + }, + { + "expect": "state", + "name": "Clang", + "role": "check box", + "not_state": "checked", + "label": "Clang changed to unchecked after first click" + }, + { + "do": "click", + "name": "Show errors", + "role": "check box", + "label": "Click Error a second time" + }, + { + "do": "click", + "name": "Show warnings", + "role": "check box", + "label": "Click Warning a second time" + }, + { + "do": "click", + "name": "Show style warnings", + "role": "check box", + "label": "Click Style a second time" + }, + { + "do": "click", + "name": "Show portability", + "role": "check box", + "label": "Click Portability a second time" + }, + { + "do": "click", + "name": "Show performance warnings", + "role": "check box", + "label": "Click Performance a second time" + }, + { + "do": "click", + "name": "Show information", + "role": "check box", + "label": "Click Information a second time" + }, + { + "do": "click", + "name": "Cppcheck", + "role": "check box", + "label": "Click Cppcheck a second time" + }, + { + "do": "click", + "name": "Clang", + "role": "check box", + "label": "Click Clang a second time" + }, + { + "expect": "state", + "name": "Show errors", + "role": "check box", + "state": "checked", + "label": "Error is back to its start state (checked)" + }, + { + "expect": "state", + "name": "Show warnings", + "role": "check box", + "state": "checked", + "label": "Warning is back to its start state (checked)" + }, + { + "expect": "state", + "name": "Show style warnings", + "role": "check box", + "state": "checked", + "label": "Style is back to its start state (checked)" + }, + { + "expect": "state", + "name": "Show portability", + "role": "check box", + "state": "checked", + "label": "Portability is back to its start state (checked)" + }, + { + "expect": "state", + "name": "Show performance warnings", + "role": "check box", + "state": "checked", + "label": "Performance is back to its start state (checked)" + }, + { + "expect": "state", + "name": "Show information", + "role": "check box", + "state": "checked", + "label": "Information is back to its start state (checked)" + }, + { + "expect": "state", + "name": "Cppcheck", + "role": "check box", + "state": "checked", + "label": "Cppcheck is back to its start state (checked)" + }, + { + "expect": "state", + "name": "Clang", + "role": "check box", + "state": "checked", + "label": "Clang is back to its start state (checked)" + } + ], + "script_for": "e5baac140df9" + }, + "td7decf1a": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the project file path" + }, + { + "do": "click", + "name": "Open", + "role": "button", + "exact": true, + "label": "Confirm the file dialog" + }, + { + "expect": "visible", + "name": "src/b.c", + "role": "table cell", + "timeout": 30, + "label": "Analysis is done and results are shown" + }, + { + "expect": "visible", + "name": "src/a.c", + "role": "table cell", + "label": "All files are listed before filtering" + }, + { + "expect": "visible", + "name": "include/util.h", + "role": "table cell", + "label": "util.h is listed before filtering" + }, + { + "do": "focus", + "path": "0.4.1", + "role": "text", + "label": "Focus the Filter field" + }, + { + "do": "type", + "value": "arrayIndex", + "label": "Type arrayIndex in the Filter field" + }, + { + "expect": "visible", + "name": "src/b.c", + "role": "table cell", + "label": "Only the b.c error remains shown" + }, + { + "expect": "gone", + "name": "src/a.c", + "role": "table cell", + "label": "a.c error is filtered out" + }, + { + "expect": "gone", + "name": "include/util.h", + "role": "table cell", + "label": "util.h error is filtered out" + }, + { + "do": "settext", + "path": "0.4.1", + "role": "text", + "value": "", + "label": "Clear the Filter field" + }, + { + "expect": "visible", + "name": "src/a.c", + "role": "table cell", + "label": "a.c error is shown again" + }, + { + "expect": "visible", + "name": "src/b.c", + "role": "table cell", + "label": "b.c error is shown again" + }, + { + "expect": "visible", + "name": "include/util.h", + "role": "table cell", + "label": "util.h error is shown again" + } + ], + "script_for": "f86b9b8205a7" + }, + "t2af9d498": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the project file path" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the file dialog" + }, + { + "do": "wait", + "name": "Checking src/b.c", + "role": "list item", + "timeout": 30, + "label": "Wait until the analysis has run" + }, + { + "expect": "visible", + "name": "src/a.c", + "role": "table cell", + "timeout": 30, + "label": "Results are shown" + }, + { + "do": "click", + "name": "View", + "role": "menu item", + "label": "Open the View menu" + }, + { + "do": "click", + "name": "Collapse all", + "role": "menu item", + "label": "View > Collapse All" + }, + { + "do": "click", + "name": "Expand all", + "role": "menu item", + "label": "View > Expand All (menu is still open)" + }, + { + "expect": "visible", + "name": "Null pointer dereference: r", + "role": "table cell", + "label": "The error in util.h is visible" + }, + { + "expect": "visible", + "name": "Null pointer dereference: p", + "role": "table cell", + "label": "The error in a.c is visible" + }, + { + "expect": "state", + "name": "include/util.h", + "role": "table cell", + "state": "expanded", + "nth": 0, + "label": "util.h is expanded" + }, + { + "expect": "state", + "name": "src/a.c", + "role": "table cell", + "state": "expanded", + "nth": 0, + "label": "a.c is expanded" + } + ], + "script_for": "3d1da0542440" + }, + "tec898362": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "wait", + "name": "Select Project File", + "role": "dialog", + "label": "File dialog appears" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the project file path" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the file dialog" + }, + { + "expect": "visible", + "name": "src/b.c", + "role": "table cell", + "timeout": 60, + "label": "Analysis is done and the file entries are shown" + }, + { + "do": "click", + "name": "View", + "role": "menu item", + "label": "Open the View menu" + }, + { + "do": "click", + "name": "Expand all", + "role": "menu item", + "label": "View > Expand All" + }, + { + "expect": "visible", + "name": "nullPointer", + "role": "table cell", + "label": "Errors are visible after expanding" + }, + { + "do": "hover", + "name": "View", + "role": "menu item", + "label": "Open the View menu again" + }, + { + "do": "click", + "name": "Collapse all", + "role": "menu item", + "label": "View > Collapse All" + }, + { + "expect": "visible", + "name": "include/util.h", + "role": "table cell", + "label": "File entry util.h is shown" + }, + { + "expect": "visible", + "name": "src/a.c", + "role": "table cell", + "label": "File entry a.c is shown" + }, + { + "expect": "visible", + "name": "src/b.c", + "role": "table cell", + "label": "File entry b.c is shown" + }, + { + "expect": "gone", + "name": "nullPointer", + "label": "Errors are not shown" + }, + { + "expect": "gone", + "name": "constVariablePointer", + "label": "Style errors are not shown" + } + ], + "script_for": "405a34af72d2" + }, + "t4c027507": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File from the File menu" + }, + { + "do": "wait", + "name": "Select Project File", + "role": "dialog", + "label": "Wait for the file dialog" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the path of basic.cppcheck" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the file dialog" + }, + { + "do": "wait", + "name": "src/b.c", + "role": "table cell", + "timeout": 60, + "label": "Wait until the analysis is done and results are shown" + }, + { + "do": "wait", + "name": "Select Project File", + "gone": true, + "label": "File dialog is closed" + }, + { + "do": "rightclick", + "name": "src/a.c", + "role": "table cell", + "label": "Right click on the file a.c in the results" + }, + { + "expect": "ocr", + "contains": "Recheck", + "label": "Context menu has Recheck" + }, + { + "expect": "ocr", + "contains": "Open containing folder", + "label": "Context menu has Open containing folder" + }, + { + "expect": "ocr", + "not_contains": "Hide", + "label": "Context menu has no Hide entries" + }, + { + "expect": "ocr", + "not_contains": "Suppress selected id", + "label": "Context menu has no Suppress selected id" + } + ], + "script_for": "237d77caee36" + }, + "taacc3841": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the project file path" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Open the project file" + }, + { + "expect": "visible", + "name": "src/a.c", + "role": "table cell", + "timeout": 30, + "label": "Analysis is done and results show a.c" + }, + { + "do": "focus", + "name": "src/a.c", + "role": "table cell", + "label": "Focus the a.c row" + }, + { + "do": "key", + "keys": [ + "Right" + ], + "label": "Expand a.c in the results" + }, + { + "expect": "visible", + "name": "Null pointer dereference: p", + "role": "table cell", + "label": "First nullPointer error (line 6) is shown" + }, + { + "do": "rightclick", + "name": "Null pointer dereference: p", + "role": "table cell", + "label": "Right click on the first nullPointer error" + }, + { + "do": "key", + "keys": [ + "Down", + "Down", + "Down", + "Return" + ], + "label": "Click Hide (Hide 1 result(s)) in the context menu" + }, + { + "expect": "gone", + "name": "Null pointer dereference: p", + "label": "The hidden error is gone" + }, + { + "expect": "visible", + "name": "Null pointer dereference: q", + "role": "table cell", + "label": "Second nullPointer error (line 12) is still shown" + }, + { + "expect": "visible", + "name": "Variable 'p' can be declared as pointer to const", + "role": "table cell", + "label": "Other errors are still shown (line 5)" + }, + { + "expect": "visible", + "name": "Variable 'q' can be declared as pointer to const", + "role": "table cell", + "label": "Other errors are still shown (line 11)" + } + ], + "script_for": "13f9f46bd6a3" + }, + "t2937b690": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the project file path" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Open the project file" + }, + { + "expect": "visible", + "name": "src/a.c", + "role": "table cell", + "timeout": 30, + "label": "Wait until the analysis is done and results are shown" + }, + { + "do": "doubleclick", + "name": "src/a.c", + "role": "table cell", + "label": "Select a.c in the results" + }, + { + "do": "key", + "keys": [ + "Right" + ], + "label": "Expand a.c in the results" + }, + { + "expect": "visible", + "name": "nullPointer", + "role": "table cell", + "label": "nullPointer errors are listed before hiding" + }, + { + "do": "rightclick", + "name": "nullPointer", + "role": "table cell", + "nth": 0, + "label": "Right click on a nullPointer error" + }, + { + "do": "key", + "keys": [ + "Down", + "Down", + "Down", + "Down" + ], + "label": "Move to Hide all with id in the context menu" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Click Hide all with ID" + }, + { + "expect": "gone", + "name": "nullPointer", + "role": "table cell", + "label": "All nullPointer errors are gone from a.c and util.h" + }, + { + "expect": "visible", + "name": "constVariablePointer", + "role": "table cell", + "label": "Other errors in a.c are still shown" + }, + { + "do": "key", + "keys": [ + "End", + "Right" + ], + "label": "Expand b.c in the results" + }, + { + "expect": "visible", + "name": "arrayIndexOutOfBounds", + "role": "table cell", + "label": "The arrayIndexOutOfBounds error in b.c is still shown" + } + ], + "script_for": "0c3a81139be4" + }, + "t313463d3": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Given the project file is opened from the File menu: open File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Given the project file is opened from the File menu: Open Project File" + }, + { + "do": "wait", + "name": "Select Project File", + "role": "dialog", + "label": "Project file dialog is shown" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the project file basic.cppcheck" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Open the project file" + }, + { + "expect": "visible", + "name": "Project: basic.cppcheck", + "role": "frame", + "timeout": 10, + "label": "The project is loaded" + }, + { + "expect": "visible", + "name": "src/a.c", + "role": "table cell", + "timeout": 30, + "label": "And the analysis is done: a.c is in the results" + }, + { + "expect": "state", + "name": "Stop analysis", + "role": "button", + "not_state": "enabled", + "timeout": 30, + "label": "And the analysis is done" + }, + { + "do": "focus", + "name": "src/a.c", + "role": "table cell", + "label": "When I expand a.c in the results: select a.c" + }, + { + "do": "key", + "keys": [ + "Right" + ], + "label": "When I expand a.c in the results" + }, + { + "expect": "visible", + "name": "Null pointer dereference: p", + "role": "table cell", + "label": "The first nullPointer error of a.c is shown" + }, + { + "expect": "visible", + "name": "Null pointer dereference: q", + "role": "table cell", + "label": "The second nullPointer error of a.c is shown" + }, + { + "do": "rightclick", + "name": "Null pointer dereference: p", + "role": "table cell", + "label": "And I right click on the first nullPointer error" + }, + { + "expect": "ocr", + "contains": "Hide all with id", + "label": "The context menu has Hide all with id" + }, + { + "do": "key", + "keys": [ + "Up", + "Up", + "Up" + ], + "label": "Move to Hide all with id in the context menu" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "And click Hide all with id" + }, + { + "expect": "gone", + "name": "Null pointer dereference: p", + "label": "Then the a.c results are hidden: first nullPointer error" + }, + { + "expect": "gone", + "name": "Null pointer dereference: q", + "label": "Then the a.c results are hidden: second nullPointer error" + }, + { + "expect": "visible", + "name": "Variable 'p' can be declared as pointer to const", + "role": "table cell", + "label": "Other a.c results are still shown" + }, + { + "do": "click", + "name": "View", + "role": "menu item", + "label": "When I open View menu" + }, + { + "do": "click", + "name": "Show hidden", + "role": "menu item", + "label": "When I open View > Show Hidden" + }, + { + "expect": "visible", + "name": "Null pointer dereference: p", + "role": "table cell", + "label": "Then the error that was hidden is shown again" + }, + { + "expect": "visible", + "name": "Null pointer dereference: q", + "role": "table cell", + "label": "Then the other hidden nullPointer error is shown again" + } + ], + "script_for": "0b1c29212e8d" + }, + "t90bf32e9": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open File menu" + }, + { + "do": "click", + "name": "Open Project File", + "role": "menu item", + "label": "Click Open Project File" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter project file path" + }, + { + "do": "click", + "name": "Open", + "role": "button", + "exact": true, + "label": "Confirm the file dialog" + }, + { + "expect": "visible", + "name": "src/a.c", + "role": "table cell", + "timeout": 60, + "label": "Wait until the analysis is done" + }, + { + "do": "sleep", + "seconds": 3, + "label": "Let the analysis finish" + }, + { + "do": "focus", + "name": "src/a.c", + "role": "table cell", + "label": "Select a.c in the results" + }, + { + "do": "key", + "keys": [ + "Right" + ], + "label": "Expand a.c in the results" + }, + { + "expect": "visible", + "name": "nullPointer", + "role": "table cell", + "label": "nullPointer error is shown under a.c" + }, + { + "do": "rightclick", + "name": "nullPointer", + "role": "table cell", + "nth": 0, + "label": "Right click on a nullPointer error" + }, + { + "do": "key", + "keys": [ + "Down", + "Down", + "Down", + "Down", + "Down", + "Return" + ], + "label": "Click Suppress selected id" + }, + { + "expect": "gone", + "name": "nullPointer", + "role": "table cell", + "label": "No nullPointer error is shown in the results" + }, + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open File menu" + }, + { + "do": "click", + "name": "Edit Project File", + "role": "menu item", + "label": "Click Edit Project File" + }, + { + "do": "click", + "name": "Warning options", + "role": "page tab", + "label": "Go to Warning options" + }, + { + "expect": "visible", + "name": "nullPointer", + "role": "list item", + "label": "The id nullPointer is listed under Suppressions" + } + ], + "script_for": "fb50a62ef505" + }, + "t389a5f3a": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "Choose Open Project File" + }, + { + "do": "settext", + "name": "File name", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the project file path" + }, + { + "do": "click", + "name": "Open", + "role": "button", + "exact": true, + "label": "Confirm the file dialog" + }, + { + "expect": "visible", + "name": "src/b.c", + "role": "table cell", + "timeout": 30, + "label": "Analysis results are shown" + }, + { + "do": "sleep", + "seconds": 3, + "label": "Wait until the analysis is done" + }, + { + "expect": "state", + "name": "Clear results", + "role": "button", + "state": "enabled", + "label": "Analysis finished, Clear results is available" + }, + { + "do": "click", + "name": "Clear results", + "role": "button", + "label": "Click Clear results" + }, + { + "expect": "gone", + "name": "src/a.c", + "role": "table cell", + "label": "Result tree has no src/a.c entry" + }, + { + "expect": "gone", + "name": "src/b.c", + "role": "table cell", + "label": "Result tree has no src/b.c entry" + }, + { + "expect": "gone", + "name": "include/util.h", + "role": "table cell", + "label": "Result tree has no include/util.h entry" + } + ], + "script_for": "96a64734a43b" + } +} diff --git a/gui/manualtest/tests/xml_results.feature b/gui/manualtest/tests/xml_results.feature new file mode 100644 index 00000000000..2a7b48c5285 --- /dev/null +++ b/gui/manualtest/tests/xml_results.feature @@ -0,0 +1,45 @@ +Feature: XML results + + Background: + Given the application is started with no project loaded + + @id:tc1fc409e @project:basic-project + Scenario Outline: Save results to a file + Given the project file {project:basic-project}/basic.cppcheck is opened from the File menu + And the analysis is done + When I open File > Save results to file + And I choose the file type for files + And I save the results as results. in the work directory {work} + Then the file results. is saved in the format + And it has the nullPointer error in a.c ("Null pointer dereference") + And it has the arrayIndexOutOfBounds error in b.c ("out of bounds") + + Examples: + | format | + | xml | + | txt | + | csv | + + @id:tcdb051d4 @project:xml-results + Scenario: Open XML results + When I open File > Open XML + And I select {project:xml-results}/results.xml + Then the XML results are opened + And the nullPointer error in a.c is shown + + @id:t8d9a2076 @ticket:13984 @project:xml-results + Scenario: No Recheck in XML results + Given the XML results {project:xml-results}/results.xml are opened with File > Open XML + When I right click on the file a.c in the results + Then the context menu of the file has no Recheck entry and no Open containing folder entry, XML results can not be analysed again + And the application does not crash + + @id:t053b7c9c @project:xml-results @project:basic-project + Scenario: Reanalyze enabled after XML results + Given the XML results {project:xml-results}/results.xml are opened with File > Open XML + Then the button Reanalyze all files is disabled + And if I rightclick on a.c in the results I get a popup + And there is a option "Recheck 1 file(s)" in the popup that is disabled + When I open the project file {project:basic-project}/basic.cppcheck + And I wait until the analysis is done + Then the button Reanalyze all files is enabled and can be clicked diff --git a/gui/manualtest/tests/xml_results.scripts.json b/gui/manualtest/tests/xml_results.scripts.json new file mode 100644 index 00000000000..95fa6cebb97 --- /dev/null +++ b/gui/manualtest/tests/xml_results.scripts.json @@ -0,0 +1,466 @@ +{ + "tc1fc409e": { + "script": [ + { + "label": "Start the application with no project loaded", + "expect": "visible", + "name": "Cppcheck", + "role": "frame" + }, + { + "label": "Open the File menu", + "do": "click", + "name": "File", + "role": "menu item" + }, + { + "label": "Choose File > Open Project File", + "do": "click", + "name": "Open Project File...", + "role": "menu item" + }, + { + "label": "Wait for the project file dialog", + "do": "wait", + "name": "Select Project File", + "role": "dialog" + }, + { + "label": "Enter the project file basic.cppcheck", + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck" + }, + { + "label": "Open the project file", + "do": "key", + "keys": [ + "Return" + ] + }, + { + "label": "The project is loaded", + "expect": "visible", + "name": "Project: basic.cppcheck", + "role": "frame", + "timeout": 10 + }, + { + "label": "Analysis has produced results for b.c", + "expect": "visible", + "name": "src/b.c", + "role": "table cell", + "timeout": 30 + }, + { + "label": "Wait until the analysis is done", + "expect": "state", + "name": "Stop analysis", + "role": "button", + "not_state": "enabled", + "timeout": 30 + }, + { + "label": "Open the File menu", + "do": "click", + "name": "File", + "role": "menu item" + }, + { + "label": "Open File > Save results to file", + "do": "click", + "name": "Save results to file...", + "role": "menu item" + }, + { + "label": "Wait for the save dialog", + "do": "wait", + "name": "Save the report file", + "role": "dialog" + }, + { + "label": "Open the Files of type list", + "do": "click", + "name": "XML files", + "role": "combo box", + "mouse": true + }, + { + "label": "Choose the file type for {format} files", + "do": "click", + "name": "(*.{format})", + "role": "list item", + "mouse": true + }, + { + "label": "Enter results.{format} in the work directory as file name", + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{work}/results.{format}" + }, + { + "label": "Save the results", + "do": "click", + "name": "Save", + "role": "button", + "exact": true + }, + { + "label": "The save dialog is closed", + "expect": "gone", + "name": "Save the report file", + "role": "dialog" + }, + { + "label": "results.{format} is saved and names a.c", + "expect": "file", + "path": "{work}/results.{format}", + "contains": "src/a.c" + }, + { + "label": "results.{format} has the nullPointer error in a.c (Null pointer dereference)", + "expect": "file", + "path": "{work}/results.{format}", + "contains": "Null pointer dereference" + }, + { + "label": "results.{format} has the arrayIndexOutOfBounds error in b.c (out of bounds)", + "expect": "file", + "path": "{work}/results.{format}", + "regex": "src/b.c[\\s\\S]*out of bounds|out of bounds[\\s\\S]*src/b.c" + } + ], + "script_for": "dad1485bb95a" + }, + "tcdb051d4": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open XML", + "role": "menu item", + "label": "Choose File > Open XML" + }, + { + "expect": "visible", + "name": "Open the report file", + "role": "dialog", + "label": "The file dialog is shown" + }, + { + "do": "settext", + "name": "File name", + "role": "text", + "value": "{project:xml-results}/results.xml", + "label": "Select results.xml of the xml-results project" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Confirm the file dialog" + }, + { + "do": "wait", + "name": "Open the report file", + "gone": true, + "label": "The file dialog closes" + }, + { + "expect": "visible", + "name": "a.c", + "role": "table cell", + "label": "The file a.c is listed in the results" + }, + { + "do": "focus", + "name": "a.c", + "role": "table cell", + "label": "Give keyboard focus to the a.c entry" + }, + { + "do": "key", + "keys": [ + "Right" + ], + "label": "Expand the a.c entry to see its error" + }, + { + "expect": "visible", + "name": "nullPointer", + "role": "table cell", + "label": "The nullPointer error is shown" + }, + { + "expect": "visible", + "name": "Null pointer dereference: p", + "role": "table cell", + "label": "The nullPointer summary is shown" + }, + { + "expect": "visible", + "name": "error", + "role": "table cell", + "label": "The severity of the error is shown" + } + ], + "script_for": "35589d1420ec" + }, + "t8d9a2076": { + "script": [ + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Open the File menu" + }, + { + "do": "click", + "name": "Open XML", + "role": "menu item", + "label": "Choose File > Open XML" + }, + { + "do": "wait", + "name": "Open the report file", + "timeout": 10, + "label": "Wait for the open dialog" + }, + { + "do": "settext", + "name": "File name", + "role": "text", + "value": "{project:xml-results}/results.xml", + "label": "Enter the path of results.xml" + }, + { + "do": "click", + "name": "Open", + "role": "button", + "nth": 1, + "label": "Confirm the file dialog" + }, + { + "expect": "visible", + "name": "a.c", + "role": "table cell", + "timeout": 10, + "label": "The results show the file a.c" + }, + { + "do": "rightclick", + "name": "a.c", + "role": "table cell", + "label": "Right click on the file a.c in the results" + }, + { + "do": "key", + "keys": [ + "Menu" + ], + "label": "Make sure the context menu is open" + }, + { + "expect": "gone", + "name": "Recheck", + "label": "The context menu has no Recheck entry" + }, + { + "expect": "gone", + "name": "Open containing folder", + "label": "The context menu has no Open containing folder entry" + }, + { + "expect": "alive", + "label": "The application does not crash" + } + ], + "script_for": "a23ab5dd15e4" + }, + "t053b7c9c": { + "script": [ + { + "expect": "visible", + "name": "Cppcheck", + "role": "frame", + "label": "Given the application is started with no project loaded" + }, + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "Given the XML results are opened with File > Open XML: open the File menu" + }, + { + "do": "click", + "name": "Open XML...", + "role": "menu item", + "label": "Given the XML results are opened with File > Open XML: choose Open XML" + }, + { + "expect": "visible", + "name": "Open the report file", + "role": "dialog", + "label": "The Open XML file dialog is shown" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:xml-results}/results.xml", + "label": "Enter the XML results file name" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Open the XML results" + }, + { + "do": "wait", + "name": "Open the report file", + "gone": true, + "timeout": 10, + "label": "Wait until the file dialog is closed" + }, + { + "expect": "visible", + "name": "results.xml", + "role": "frame", + "timeout": 10, + "label": "The XML results are loaded" + }, + { + "expect": "visible", + "name": "a.c", + "role": "table cell", + "timeout": 10, + "label": "a.c is shown in the results" + }, + { + "expect": "state", + "name": "Reanalyze all files", + "role": "button", + "not_state": "enabled", + "label": "Then the button Reanalyze all files is disabled" + }, + { + "do": "rightclick", + "name": "a.c", + "role": "table cell", + "label": "And if I rightclick on a.c in the results" + }, + { + "do": "sleep", + "seconds": 1 + }, + { + "expect": "ocr", + "contains": "Copy", + "label": "I get a popup" + }, + { + "expect": "ocr", + "not_contains": "Recheck", + "label": "There is an option Recheck 1 file(s) in the popup that is disabled (greyed out, not read by OCR)" + }, + { + "do": "key", + "keys": [ + "Escape" + ], + "label": "Close the popup" + }, + { + "do": "click", + "name": "File", + "role": "menu item", + "label": "When I open the project file: open the File menu" + }, + { + "do": "click", + "name": "Open Project File...", + "role": "menu item", + "label": "When I open the project file: choose Open Project File" + }, + { + "expect": "visible", + "name": "Select Project File", + "role": "dialog", + "label": "The project file dialog is shown" + }, + { + "do": "settext", + "name": "File name:", + "role": "text", + "value": "{project:basic-project}/basic.cppcheck", + "label": "Enter the project file name" + }, + { + "do": "key", + "keys": [ + "Return" + ], + "label": "Open the project file" + }, + { + "do": "wait", + "name": "Select Project File", + "gone": true, + "timeout": 10, + "label": "Wait until the file dialog is closed" + }, + { + "expect": "visible", + "name": "Project: basic.cppcheck", + "role": "frame", + "timeout": 10, + "label": "The project is loaded" + }, + { + "expect": "state", + "name": "Stop analysis", + "role": "button", + "not_state": "enabled", + "timeout": 30, + "label": "And I wait until the analysis is done" + }, + { + "expect": "state", + "name": "Reanalyze all files", + "role": "button", + "state": "enabled", + "timeout": 30, + "label": "Then the button Reanalyze all files is enabled" + }, + { + "do": "click", + "name": "Reanalyze all files", + "role": "button", + "label": "And can be clicked" + }, + { + "expect": "alive", + "label": "The application still runs after clicking Reanalyze all files" + }, + { + "expect": "state", + "name": "Reanalyze all files", + "role": "button", + "state": "enabled", + "timeout": 30, + "label": "Reanalyze all files is enabled again after the reanalysis" + } + ], + "script_for": "299ae476b3db" + } +} diff --git a/gui/manualtest/web.py b/gui/manualtest/web.py new file mode 100644 index 00000000000..e538b00bdb8 --- /dev/null +++ b/gui/manualtest/web.py @@ -0,0 +1,464 @@ +#!/usr/bin/env python3 +"""Web interface for editing, generating and running the Gherkin tests. + + web.py [--opensource APP] [--premium APP] [--src DIR] [--dir tests] [--out results] [--port 8080] + +Serves on 127.0.0.1 only. Reads and writes DIR/*.feature, with the scripts in DIR/*.scripts.json, +and starts gen.py and run.py with the application that is tested, one binary per edition. SRC is the git checkout that both are built +from, for the git sha in the reports. --app is the same as --opensource. Without any APP the +tests can be edited but not generated or run. +""" + +import argparse +import json +import mimetypes +import os +import re +import signal +import subprocess +import sys +import shutil +import threading +import time +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from urllib.parse import parse_qs, quote, unquote, urlparse + +import testfile + +HERE = os.path.dirname(os.path.abspath(__file__)) +NAME_RE = re.compile(r'^[\w.-]+\.feature$') +ID_RE = re.compile(r'^\w+$') + + +class Job: + """gen.py or run.py in the background, one at a time. The output is shown in the web page. + A run of several editions is one run.py after the other, each with its own report.""" + + def __init__(self, kind, file, test_id, cmds, out_dir): + self.kind, self.file, self.test_id, self.out_dir = kind, file, test_id, out_dir + self.editions = [e for e, _ in cmds] + self.lines, self.reports, self.rc = [], [], None + self.started = time.time() + self.progress = {'total': 0, 'index': 0, 'test': '', 'testfile': '', 'step': 0, 'steps': 0, 'label': '', 'base': None, + 'edition': None, 'results': []} + self.proc, self.stopped, self.lock = None, False, threading.Lock() + threading.Thread(target=self.work, args=(cmds,), daemon=True).start() + + def work(self, cmds): + rc = 0 + for edition, cmd in cmds: + with self.lock: + if self.stopped: + break + self.edition = edition + self.proc = subprocess.Popen(cmd, cwd=HERE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, + env=dict(os.environ, PYTHONUNBUFFERED='1'), start_new_session=True) + if len(cmds) > 1: + self.lines.append('=== %s ===' % edition) + self.read() + rc = max(rc, self.proc.wait()) + self.rc = rc + + def stop(self): + with self.lock: + self.stopped = True + if self.proc and self.proc.poll() is None: + self.proc.send_signal(signal.SIGINT) # run.py writes its report, gen.py cleans up + + def read(self): + for line in self.proc.stdout: + line = line.rstrip('\n') + if line.startswith('@{') and self.event(line[1:]): + continue + self.lines.append(line) + m = re.match(r'report: (.+)$', line) + if m: + self.reports.append({'edition': self.edition, + 'url': '/results/' + os.path.relpath(m.group(1), self.out_dir)}) + + def event(self, text): + """A progress line from run.py --events. False when it is not one.""" + try: + e = json.loads(text) + kind, p = e['kind'], self.progress + except (ValueError, KeyError, TypeError): + return False + if kind == 'start': + p.update(total=e['total'], index=0, test='', testfile='', step=0, steps=0, label='', edition=e.get('edition'), + base='/results/' + os.path.relpath(e['out'], self.out_dir)) + elif kind == 'test': + p.update(index=e['index'], test=e['name'], testfile=e.get('file', ''), step=0, steps=e['steps'], label='') + elif kind == 'step': + p.update(step=e['step'], steps=e['steps'], label=e['label']) + elif kind == 'result': + p['results'].append(dict({k: e[k] for k in ('name', 'status', 'reason', 'seconds', 'crash', 'slug')}, + file=e.get('file', ''), + shots=e.get('shots', []), edition=p['edition'], base=p['base'])) + return True + + def info(self): + return {'kind': self.kind, 'file': self.file, 'id': self.test_id, 'running': self.rc is None, + 'rc': self.rc, 'output': '\n'.join(self.lines), 'reports': self.reports, 'editions': self.editions, + 'progress': self.progress, 'started': self.started, 'now': time.time()} + + +class Handler(BaseHTTPRequestHandler): + directory = None + out_dir = None + apps = {} # edition -> path of the binary + src = None + job = None + lock = threading.Lock() + + def log_message(self, fmt, *args): + pass + + def reply(self, code, data, ctype='application/json'): + if ctype == 'application/json': + data = json.dumps(data, ensure_ascii=False) + if isinstance(data, str): + data = data.encode() + self.send_response(code) + self.send_header('Content-Type', ctype + ('; charset=utf-8' if ctype.startswith(('text/', 'application/json')) else '')) + self.send_header('Content-Length', str(len(data))) + self.send_header('Cache-Control', 'no-store') + self.end_headers() + self.wfile.write(data) + + def allowed(self): + # only localhost names as Host (DNS rebinding) and JSON for writes (cross-site forms) + host = self.headers.get('Host', '').rsplit(':', 1)[0] + if host not in ('127.0.0.1', 'localhost'): + self.reply(403, {'error': 'bad host'}) + return False + if self.command in ('PUT', 'POST') and self.headers.get('Content-Type', '').split(';')[0] != 'application/json': + self.reply(415, {'error': 'application/json required'}) + return False + return True + + def path_of(self, query): + name = parse_qs(query).get('name', [''])[0] + if not NAME_RE.match(name): + self.reply(400, {'error': 'bad file name (letters, digits, _ - . and .feature)'}) + return None + return os.path.join(self.directory, name) + + def project_path(self, query): + name = parse_qs(query).get('name', [''])[0] + if not testfile.PROJECT_RE.match(name): + self.reply(400, {'error': 'bad project name (letters, digits, _ and -)'}) + return None + return os.path.join(self.directory, testfile.PROJECTS, name) + + def used_by(self, project): + used = [] + for f in sorted(os.listdir(self.directory)): + if NAME_RE.match(f): + try: + tests = testfile.load(os.path.join(self.directory, f)).get('tests', []) + except (ValueError, OSError): + continue + used += ['%s: %s' % (f, t.get('name')) for t in tests if project in t.get('projects', [])] + return used + + def reports(self): + """The earlier runs in the out directory, newest first.""" + found = [] + for d in os.listdir(self.out_dir) if os.path.isdir(self.out_dir) else []: + if not os.path.isfile(os.path.join(self.out_dir, d, 'report.html')): + continue + try: + with open(os.path.join(self.out_dir, d, 'results.json'), encoding='utf-8') as f: + run = json.load(f) + counts = {} + for r in run.get('results', []): + counts[r['status']] = counts.get(r['status'], 0) + 1 + except (ValueError, OSError, KeyError, TypeError): + run, counts = {}, {} + found.append({'id': d, 'url': '/results/%s/report.html' % quote(d), 'started': run.get('started', ''), + 'edition': run.get('edition'), 'branch': run.get('branch'), 'sha': (run.get('sha') or '')[:8], + 'dirty': run.get('dirty'), 'aborted': run.get('aborted'), 'counts': counts, + 'files': [f.get('title') or f.get('file') for f in run.get('files', [])]}) + return sorted(found, key=lambda r: r['id'], reverse=True) + + def annotate(self, doc): + """Add _stale (the script is outdated) and _errors (what is wrong with the test or its script) + to every test, for the web page. Not saved, see stamp_scripts.""" + tests = doc.get('tests') if isinstance(doc, dict) else None + background = doc.get('background') if isinstance(doc, dict) and isinstance(doc.get('background'), list) else [] + for t in tests if isinstance(tests, list) else []: + if not isinstance(t, dict): + continue + t['_stale'] = testfile.is_stale(t, self.directory, background) + t['_errors'] = testfile.validate({'tests': [t]}, self.directory) + + def file_status(self): + """{file: {invalid: n, stale: n}}: tests with errors and tests whose script must be generated again. + invalid is 1 for a file that is not valid Gherkin.""" + out = {} + for name in os.listdir(self.directory): + if not NAME_RE.match(name): + continue + try: + doc = testfile.ensure_ids(testfile.load(os.path.join(self.directory, name))) + self.annotate(doc) + tests = [t for t in doc['tests'] if isinstance(t, dict)] + bad = sum(1 for t in tests if t['_errors']) + (len(tests) != len(doc['tests'])) + bool( + testfile.validate(dict(doc, tests=[]), self.directory)) + out[name] = {'invalid': bad, 'stale': sum(1 for t in tests if t['_stale'])} + except (ValueError, OSError, KeyError, TypeError, AttributeError): + out[name] = {'invalid': 1, 'stale': 0} + return out + + def body(self): + return json.loads(self.rfile.read(int(self.headers.get('Content-Length', 0))) or b'null') + + def do_GET(self): + if not self.allowed(): + return + u = urlparse(self.path) + if u.path == '/': + with open(os.path.join(HERE, 'web', 'index.html'), 'rb') as f: + self.reply(200, f.read(), 'text/html') + elif u.path == '/api/files': + self.reply(200, sorted(f for f in os.listdir(self.directory) if NAME_RE.match(f))) + elif u.path == '/api/file-status': + self.reply(200, self.file_status()) + elif u.path == '/api/script-doc': + self.reply(200, testfile.SCRIPT_DOC, 'text/plain') + elif u.path == '/api/config': + self.reply(200, {'apps': self.apps, 'editions': list(testfile.EDITIONS), 'can_run': bool(self.apps)}) + elif u.path == '/api/job': + self.reply(200, self.job.info() if self.job else None) + elif u.path == '/api/projects': + base = os.path.join(self.directory, testfile.PROJECTS) + names = sorted(n for n in os.listdir(base) if os.path.isdir(os.path.join(base, n))) if os.path.isdir(base) else [] + self.reply(200, [{'name': n, 'used_by': self.used_by(n)} for n in names]) + elif u.path == '/api/project': + path = self.project_path(u.query) + if path: + if not os.path.isdir(path): + return self.reply(404, {'error': 'no such project'}) + files, binary = {}, [] + for rel, data in sorted(testfile.project_files(self.directory, os.path.basename(path)).items()): + try: + files[rel] = data.decode('utf-8') + except UnicodeDecodeError: + binary.append(rel) + self.reply(200, {'files': files, 'binary': binary, 'used_by': self.used_by(os.path.basename(path))}) + elif u.path == '/api/reports': + self.reply(200, self.reports()) + elif u.path.startswith('/results/'): + self.static_file(self.out_dir, unquote(u.path[len('/results/'):])) + elif u.path.startswith('/docs/'): + self.static_file(os.path.join(HERE, 'docs'), unquote(u.path[len('/docs/'):])) + elif u.path == '/api/file': + path = self.path_of(u.query) + if path: + try: + doc = testfile.ensure_ids(testfile.load(path)) + except FileNotFoundError: + return self.reply(404, {'error': 'no such file'}) + except ValueError as e: + return self.reply(400, {'error': '%s is not valid Gherkin (fix it in a text editor): %s' % ( + os.path.basename(path), e)}) + self.annotate(doc) + self.reply(200, doc) + else: + self.reply(404, {'error': 'not found'}) + + def static_file(self, base, rel): + """A file below BASE: the results of the runs or the handbook.""" + base = os.path.realpath(base) + path = os.path.realpath(os.path.join(base, rel)) + if not path.startswith(base + os.sep) or not os.path.isfile(path): + return self.reply(404, {'error': 'not found'}) + ctype = 'text/plain' if path.endswith('.log') else mimetypes.guess_type(path)[0] or 'text/plain' + with open(path, 'rb') as f: + self.reply(200, f.read(), ctype) + + def do_PUT(self): + if not self.allowed(): + return + u = urlparse(self.path) + if u.path == '/api/project': + return self.put_project(u.query) + path = self.path_of(u.query) if u.path == '/api/file' else None + if not path: + return + try: + doc = self.body() + except ValueError: + return self.reply(400, {'errors': ['not valid JSON']}) + errors = testfile.validate(doc, self.directory) + if errors: + return self.reply(400, {'errors': errors}) + try: + old = testfile.load(path) + except (FileNotFoundError, ValueError): + old = None + testfile.save(path, testfile.stamp_scripts(doc, old, self.directory)) + self.reply(200, {'saved': os.path.basename(path)}) + + def put_project(self, query): + path = self.project_path(query) + if not path: + return + try: + files = self.body()['files'] + assert isinstance(files, dict) and all(isinstance(v, str) for v in files.values()) + except (ValueError, KeyError, TypeError, AssertionError): + return self.reply(400, {'errors': ['expected {"files": {"path": "content"}}']}) + clean = {} + for rel, content in files.items(): + norm = os.path.normpath(rel) + if (not rel or os.path.isabs(norm) or norm.startswith('..') or '\0' in rel or norm == '.' + or norm.split(os.sep)[0] == '..'): + return self.reply(400, {'errors': ['bad file path: %r (relative, inside the project)' % rel]}) + clean[norm] = content + if sum(len(c) for c in clean.values()) > 4_000_000: + return self.reply(400, {'errors': ['the project is too big for this editor']}) + os.makedirs(path, exist_ok=True) + # text files that are not in the request were removed in the editor, binary files stay + for rel, data in testfile.project_files(self.directory, os.path.basename(path)).items(): + if rel not in clean: + try: + data.decode('utf-8') + except UnicodeDecodeError: + continue + os.remove(os.path.join(path, rel)) + for rel, content in clean.items(): + target = os.path.join(path, rel) + os.makedirs(os.path.dirname(target), exist_ok=True) + with open(target + '.tmp', 'w', encoding='utf-8', newline='') as f: + f.write(content) + os.replace(target + '.tmp', target) + for dirpath, dirs, fs in os.walk(path, topdown=False): # directories that became empty + if dirpath != path and not os.listdir(dirpath): + os.rmdir(dirpath) + self.reply(200, {'saved': os.path.basename(path), 'files': len(clean)}) + + def do_POST(self): + if not self.allowed(): + return + route = urlparse(self.path).path + try: + data = self.body() + if route == '/api/job': + self.start_job(data) + elif route == '/api/job/stop': + if self.job and self.job.rc is None: + self.job.stop() + self.reply(200, {'stopping': True}) + else: + self.reply(404, {'error': 'not found'}) + except (ValueError, KeyError, TypeError): + self.reply(400, {'errors': ['bad request']}) + + def start_job(self, data): + action, name, test_id, edition = data['action'], data['file'], data.get('id'), data.get('edition') + if not self.apps: + return self.reply(400, {'errors': ['web.py was started without --opensource or --premium']}) + everything = action == 'run' and name == '*' # every test file in the directory + if action not in ('generate', 'run') or not (everything or NAME_RE.match(name)) or ( + test_id and not ID_RE.match(test_id)): + return self.reply(400, {'errors': ['bad request']}) + if action == 'generate' and not test_id: + return self.reply(400, {'errors': ['generate needs a test id']}) + if everything: + paths = sorted(os.path.join(self.directory, n) for n in os.listdir(self.directory) if NAME_RE.match(n)) + if not paths or test_id: + return self.reply(404, {'errors': ['no test files']}) + else: + paths = [os.path.join(self.directory, name)] + if not os.path.isfile(paths[0]): + return self.reply(404, {'errors': ['no such file']}) + path = paths[0] + # the editions of the job: the chosen ones that web.py has a binary for and, for one test, that it is for + editions = [e for e in testfile.EDITIONS if e in self.apps and edition in (e, 'both')] + if test_id: + test = next((t for t in testfile.ensure_ids(testfile.load(path)).get('tests', []) if t['id'] == test_id), None) + if not test: + return self.reply(404, {'errors': ['no such test']}) + allowed = [e for e in testfile.editions(test) if e in self.apps] + if action == 'generate' and not set(editions) & set(allowed): + editions = allowed # the script is the same for all editions, make it with one that we have + editions = [e for e in editions if e in allowed] + if not editions: + return self.reply(400, {'errors': ['the test is not for the edition %s, or web.py has no application for it' % (edition or '')]}) + if action == 'generate': + editions = editions[:1] + cmds = [] + for e in editions: + app = self.apps[e] + if action == 'generate': + cmd = [sys.executable, 'gen.py', app, path, '--force', '--id', test_id, '--edition', e] + else: + cmd = [sys.executable, 'run.py', app, *paths, '--out', self.out_dir, '--events', '--edition', e] + cmd += ['--src', self.src] if self.src else [] + cmd += ['--id', test_id] if test_id else [] + cmds.append((e, cmd)) + with self.lock: + if self.job and self.job.rc is None: + return self.reply(409, {'errors': ['a job is already running']}) + Handler.job = Job(action, name, test_id, cmds, self.out_dir) + self.reply(200, self.job.info()) + + def do_DELETE(self): + if not self.allowed(): + return + u = urlparse(self.path) + if u.path == '/api/project': + path = self.project_path(u.query) + if path: + used = self.used_by(os.path.basename(path)) + if used: + return self.reply(409, {'errors': ['the project is used by: ' + '; '.join(used)]}) + if not os.path.isdir(path): + return self.reply(404, {'error': 'no such project'}) + shutil.rmtree(path) + self.reply(200, {'deleted': os.path.basename(path)}) + return + path = self.path_of(u.query) if u.path == '/api/file' else None + if path: + try: + os.remove(path) + if os.path.exists(testfile.scripts_path(path)): + os.remove(testfile.scripts_path(path)) + self.reply(200, {'deleted': os.path.basename(path)}) + except FileNotFoundError: + self.reply(404, {'error': 'no such file'}) + + +def main(): + ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + for e in testfile.EDITIONS: + ap.add_argument('--' + e, *(['--app'] if e == 'opensource' else []), metavar='APP', + help='the %s edition of the application to test' % e) + ap.add_argument('--src', help='git checkout of the application, the same for all editions (default: the directory of APP)') + ap.add_argument('--dir', default=os.path.join(HERE, 'tests')) + ap.add_argument('--out', default=os.path.join(HERE, 'results')) + ap.add_argument('--port', type=int, default=8080) + args = ap.parse_args() + for e in testfile.EDITIONS: + app = getattr(args, e) + if app: + if not os.access(app, os.X_OK): + sys.exit('error: not executable: ' + app) + Handler.apps[e] = os.path.abspath(app) + Handler.src = os.path.abspath(args.src) if args.src else None + Handler.directory = os.path.abspath(args.dir) + Handler.out_dir = os.path.realpath(args.out) + os.makedirs(Handler.directory, exist_ok=True) + os.makedirs(Handler.out_dir, exist_ok=True) + Handler.out_dir = os.path.realpath(Handler.out_dir) + server = ThreadingHTTPServer(('127.0.0.1', args.port), Handler) + print('http://127.0.0.1:%d/ (%s)' % (args.port, Handler.directory), flush=True) + try: + server.serve_forever() + except KeyboardInterrupt: + pass + + +if __name__ == '__main__': + main() diff --git a/gui/manualtest/web/index.html b/gui/manualtest/web/index.html new file mode 100644 index 00000000000..7cccc8e9b23 --- /dev/null +++ b/gui/manualtest/web/index.html @@ -0,0 +1,677 @@ + + + + + +GUI tester + + + +
+

GUI tester

+ no file selected + + + + + + Handbook +
+
+ +

Select a file on the left or create a new one.

+
+
+ + + diff --git a/gui/platforms.cpp b/gui/platforms.cpp index 77683a6ea09..a23b05b1ccd 100644 --- a/gui/platforms.cpp +++ b/gui/platforms.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -36,11 +36,11 @@ void Platforms::add(const QString &title, Platform::Type platform) void Platforms::init() { add(tr("Native"), Platform::Type::Native); - add(tr("Unix 32-bit"), Platform::Type::Unix32); - add(tr("Unix 64-bit"), Platform::Type::Unix64); - add(tr("Windows 32-bit ANSI"), Platform::Type::Win32A); - add(tr("Windows 32-bit Unicode"), Platform::Type::Win32W); - add(tr("Windows 64-bit"), Platform::Type::Win64); + add("Unix 32-bit", Platform::Type::Unix32); + add("Unix 64-bit", Platform::Type::Unix64); + add("Windows 32-bit ANSI", Platform::Type::Win32A); + add("Windows 32-bit Unicode", Platform::Type::Win32W); + add("Windows 64-bit", Platform::Type::Win64); } int Platforms::getCount() const diff --git a/gui/projectfile.cpp b/gui/projectfile.cpp index b807723ceeb..716288d9695 100644 --- a/gui/projectfile.cpp +++ b/gui/projectfile.cpp @@ -63,6 +63,7 @@ void ProjectFile::clear() mIncludeDirs.clear(); mDefines.clear(); mUndefines.clear(); + mUserInclude.clear(); mPaths.clear(); mExcludedPaths.clear(); mLibraries.clear(); @@ -87,7 +88,6 @@ void ProjectFile::clear() mBughunting = false; mCertIntPrecision = 0; mCodingStandards.clear(); - mPremiumLicenseFile.clear(); } bool ProjectFile::read(const QString &filename) @@ -166,6 +166,9 @@ bool ProjectFile::read(const QString &filename) if (xmlReader.name() == QString(CppcheckXml::UndefinesElementName)) readStringList(mUndefines, xmlReader, CppcheckXml::UndefineName); + if (xmlReader.name() == QString(CppcheckXml::UserIncludeElementName)) + mUserInclude = readString(xmlReader); + // Find exclude list from inside project element if (xmlReader.name() == QString(CppcheckXml::ExcludeElementName)) readExcludes(xmlReader); @@ -229,8 +232,6 @@ bool ProjectFile::read(const QString &filename) readStringList(mCodingStandards, xmlReader, CppcheckXml::CodingStandardElementName); if (xmlReader.name() == QString(CppcheckXml::CertIntPrecisionElementName)) mCertIntPrecision = readInt(xmlReader, 0); - if (xmlReader.name() == QString(CppcheckXml::LicenseFileElementName)) - mPremiumLicenseFile = readString(xmlReader); if (xmlReader.name() == QString(CppcheckXml::ProjectNameElementName)) mProjectName = readString(xmlReader); @@ -347,15 +348,13 @@ bool ProjectFile::readBool(QXmlStreamReader &reader) int ProjectFile::readInt(QXmlStreamReader &reader, int defaultValue) { - int ret = defaultValue; do { const QXmlStreamReader::TokenType type = reader.readNext(); switch (type) { case QXmlStreamReader::Characters: - ret = reader.text().toString().toInt(); - FALLTHROUGH; + return reader.text().toString().toInt(); case QXmlStreamReader::EndElement: - return ret; + return defaultValue; // Not handled case QXmlStreamReader::StartElement: case QXmlStreamReader::NoToken: @@ -373,15 +372,13 @@ int ProjectFile::readInt(QXmlStreamReader &reader, int defaultValue) QString ProjectFile::readString(QXmlStreamReader &reader) { - QString ret; do { const QXmlStreamReader::TokenType type = reader.readNext(); switch (type) { case QXmlStreamReader::Characters: - ret = reader.text().toString(); - FALLTHROUGH; + return reader.text().toString(); case QXmlStreamReader::EndElement: - return ret; + return {}; // Not handled case QXmlStreamReader::StartElement: case QXmlStreamReader::NoToken: @@ -910,6 +907,12 @@ bool ProjectFile::write(const QString &filename) xmlWriter.writeEndElement(); } + if (!mUserInclude.isEmpty()) { + xmlWriter.writeStartElement(CppcheckXml::UserIncludeElementName); + xmlWriter.writeCharacters(mUserInclude); + xmlWriter.writeEndElement(); + } + if (!mVsConfigurations.isEmpty()) { writeStringList(xmlWriter, mVsConfigurations, @@ -1047,12 +1050,6 @@ bool ProjectFile::write(const QString &filename) xmlWriter.writeEndElement(); } - if (!mPremiumLicenseFile.isEmpty()) { - xmlWriter.writeStartElement(CppcheckXml::LicenseFileElementName); - xmlWriter.writeCharacters(mPremiumLicenseFile); - xmlWriter.writeEndElement(); - } - xmlWriter.writeEndDocument(); file.close(); return true; @@ -1072,6 +1069,12 @@ void ProjectFile::writeStringList(QXmlStreamWriter &xmlWriter, const QStringList xmlWriter.writeEndElement(); } +void ProjectFile::setSettingsUserIncludes(Settings &settings) const +{ + if (!mUserInclude.isEmpty()) + settings.userIncludes.push_back(mUserInclude.toStdString()); +} + QStringList ProjectFile::fromNativeSeparators(const QStringList &paths) { QStringList ret; @@ -1190,10 +1193,19 @@ QStringList ProjectFile::getSearchPaths(const QString& projectPath, const QStrin return ret; } +QString ProjectFile::getRelativePath(const QString &absolutePath) const +{ + const QDir dir(QFileInfo(mFilename).absolutePath()); + const QString relativePath(dir.relativeFilePath(absolutePath)); + if (relativePath.startsWith("../../..") || absolutePath.length() < relativePath.length()) + return absolutePath; + return relativePath; +} + QStringList ProjectFile::getSearchPaths(const QString& dir) const { const QFileInfo inf(mFilename); const QString applicationFilePath = QCoreApplication::applicationFilePath(); const QString appPath = QFileInfo(applicationFilePath).canonicalPath(); const QString datadir = getDataDir(); - return getSearchPaths(inf.canonicalFilePath(), appPath, datadir, dir); + return getSearchPaths(inf.canonicalPath(), appPath, datadir, dir); } diff --git a/gui/projectfile.h b/gui/projectfile.h index e7f8d342180..e0b85582c99 100644 --- a/gui/projectfile.h +++ b/gui/projectfile.h @@ -141,6 +141,14 @@ class ProjectFile : public QObject { return mUndefines; } + const QString& getUserInclude() const { + return mUserInclude; + } + + void setUserInclude(const QString& userInclude) { + mUserInclude = userInclude.trimmed(); + } + /** * @brief Get list of paths to check. * @return list of paths. @@ -181,10 +189,6 @@ class ProjectFile : public QObject { return mPlatform; } - const QString& getProjectName() const { - return mProjectName; - } - void setProjectName(QString projectName) { mProjectName = std::move(projectName); } @@ -400,14 +404,6 @@ class ProjectFile : public QObject { return mCertIntPrecision; } - /** Cppcheck Premium: License file */ - void setLicenseFile(const QString& licenseFile) { - mPremiumLicenseFile = licenseFile; - } - const QString& getLicenseFile() const { - return mPremiumLicenseFile; - } - /** * @brief Write project file (to disk). * @param filename Filename to use. @@ -448,8 +444,21 @@ class ProjectFile : public QObject { /** Get paths where we should glob for certain files (dir="cfg"/"platforms"/etc */ QStringList getSearchPaths(const QString& dir) const; + static QStringList getSearchPaths(const QString& projectPath, const QString& appPath, const QString& datadir, const QString& dir); + /** + * @brief Convert an absolute path to a path relative to this project's directory. + * If the relative path would need to walk up more than 2 parent folders + * (i.e. "../../...") the absolute path is returned unchanged instead. + * @param absolutePath Absolute path to convert. + */ + QString getRelativePath(const QString &absolutePath) const; + + /** Set user includes in settings if non-empty */ + void setSettingsUserIncludes(Settings &settings) const; + + protected: /** @@ -601,6 +610,9 @@ class ProjectFile : public QObject { */ QStringList mUndefines; + /** @brief --include file */ + QString mUserInclude; + /** * @brief List of paths to check. */ @@ -641,9 +653,6 @@ class ProjectFile : public QObject { */ QStringList mCodingStandards; - /** @brief Cppcheck Premium: license file */ - QString mPremiumLicenseFile; - /** @brief Project name, used when generating compliance report */ QString mProjectName; diff --git a/gui/projectfile.ui b/gui/projectfile.ui index 4bd01339d83..9f7ad91fa8f 100644 --- a/gui/projectfile.ui +++ b/gui/projectfile.ui @@ -7,7 +7,7 @@ 0 0 940 - 701 + 741 @@ -80,7 +80,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -95,7 +95,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -117,7 +117,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -151,7 +151,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -199,7 +199,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -269,7 +269,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -284,7 +284,7 @@ - QAbstractItemView::SelectRows + QAbstractItemView::SelectionBehavior::SelectRows @@ -314,7 +314,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -342,10 +342,38 @@ + + + + + + Include file + + + mEditUserInclude + + + + + + + <html><head/><body><p><span style=" font-family:'monospace'; color:#000000; background-color:#ffffff;">Force inclusion of a header file</span></p></body></html> + + + + + + + Browse.. + + + + + - Qt::Vertical + Qt::Orientation::Vertical @@ -552,7 +580,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -586,7 +614,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -601,25 +629,6 @@ - - - - Premium License - - - - - - - - - Browse... - - - - - - @@ -701,7 +710,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -744,7 +753,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -769,7 +778,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -856,7 +865,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -919,7 +928,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1001,14 +1010,14 @@ - Clang-tidy + Clang-Tidy - Clang analyzer + Clang Static Analyzer @@ -1018,7 +1027,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -1035,10 +1044,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok diff --git a/gui/projectfiledialog.cpp b/gui/projectfiledialog.cpp index d46eb5c3489..abb7b343ec1 100644 --- a/gui/projectfiledialog.cpp +++ b/gui/projectfiledialog.cpp @@ -23,7 +23,6 @@ #include "importproject.h" #include "library.h" #include "newsuppressiondialog.h" -#include "path.h" #include "platform.h" #include "platforms.h" #include "projectfile.h" @@ -139,8 +138,6 @@ ProjectFileDialog::ProjectFileDialog(ProjectFile *projectFile, bool premium, QWi setWindowTitle(title); loadSettings(); - mUI->premiumLicense->setVisible(false); - QStringList libs; // Search the std.cfg first since other libraries could depend on it QString stdLibraryFilename; @@ -235,6 +232,7 @@ ProjectFileDialog::ProjectFileDialog(ProjectFile *projectFile, bool premium, QWi connect(mUI->mBtnBrowseBuildDir, &QPushButton::clicked, this, &ProjectFileDialog::browseBuildDir); connect(mUI->mBtnClearImportProject, &QPushButton::clicked, this, &ProjectFileDialog::clearImportProject); connect(mUI->mBtnBrowseImportProject, &QPushButton::clicked, this, &ProjectFileDialog::browseImportProject); + connect(mUI->mBtnBrowseUserInclude, &QPushButton::clicked, this, &ProjectFileDialog::browseUserInclude); connect(mUI->mBtnAddCheckPath, SIGNAL(clicked()), this, SLOT(addCheckPath())); connect(mUI->mBtnEditCheckPath, &QPushButton::clicked, this, &ProjectFileDialog::editCheckPath); connect(mUI->mBtnRemoveCheckPath, &QPushButton::clicked, this, &ProjectFileDialog::removeCheckPath); @@ -303,6 +301,7 @@ void ProjectFileDialog::loadFromProjectFile(const ProjectFile *projectFile) setIncludepaths(projectFile->getIncludeDirs()); setDefines(projectFile->getDefines()); setUndefines(projectFile->getUndefines()); + mUI->mEditUserInclude->setText(projectFile->getUserInclude()); setCheckPaths(projectFile->getCheckPaths()); setImportProject(projectFile->getImportProject()); mUI->mChkAllVsConfigs->setChecked(projectFile->getAnalyzeAllVsConfigs()); @@ -462,7 +461,6 @@ void ProjectFileDialog::loadFromProjectFile(const ProjectFile *projectFile) mUI->mToolClangTidy->setEnabled(false); } mUI->mEditTags->setText(projectFile->getTags().join(';')); - mUI->mEditLicenseFile->setText(projectFile->getLicenseFile()); updatePathsAndDefines(); } @@ -481,6 +479,7 @@ void ProjectFileDialog::saveToProjectFile(ProjectFile *projectFile) const projectFile->setIncludes(getIncludePaths()); projectFile->setDefines(getDefines()); projectFile->setUndefines(getUndefines()); + projectFile->setUserInclude(mUI->mEditUserInclude->text()); projectFile->setCheckPaths(getCheckPaths()); projectFile->setExcludedPaths(getExcludedPaths()); projectFile->setLibraries(getLibraries()); @@ -549,7 +548,6 @@ void ProjectFileDialog::saveToProjectFile(ProjectFile *projectFile) const projectFile->setBughunting(mUI->mBughunting->isChecked()); projectFile->setClangAnalyzer(mUI->mToolClangAnalyzer->isChecked()); projectFile->setClangTidy(mUI->mToolClangTidy->isChecked()); - projectFile->setLicenseFile(mUI->mEditLicenseFile->text()); projectFile->setTags(mUI->mEditTags->text().split(";", Qt::SkipEmptyParts)); } @@ -573,10 +571,7 @@ QString ProjectFileDialog::getExistingDirectory(const QString &caption, bool tra // Check if the path is relative to project file's path and if so // make it a relative path instead of absolute path. - const QDir dir(projectPath); - const QString relpath(dir.relativeFilePath(selectedDir)); - if (!relpath.startsWith("../..")) - selectedDir = relpath; + selectedDir = mProjectFile->getRelativePath(selectedDir); // Trailing slash.. if (trailingSlash && !selectedDir.endsWith('/')) @@ -626,14 +621,14 @@ void ProjectFileDialog::browseImportProject() const QFileInfo inf(mProjectFile->getFilename()); const QDir &dir = inf.absoluteDir(); QMap filters; - filters[tr("Visual Studio")] = "*.sln *.slnx *.vcxproj"; + filters["Visual Studio"] = "*.sln *.slnx *.vcxproj"; filters[tr("Compile database")] = "compile_commands.json"; - filters[tr("Borland C++ Builder 6")] = "*.bpr"; + filters["Borland C++ Builder 6"] = "*.bpr"; QString fileName = QFileDialog::getOpenFileName(this, tr("Import Project"), dir.canonicalPath(), toFilterString(filters)); if (!fileName.isEmpty()) { - mUI->mEditImportProject->setText(dir.relativeFilePath(fileName)); + mUI->mEditImportProject->setText(mProjectFile->getRelativePath(fileName)); updatePathsAndDefines(); setProjectConfigurations(getProjectConfigs(fileName)); for (int row = 0; row < mUI->mListVsConfigs->count(); ++row) { @@ -643,6 +638,21 @@ void ProjectFileDialog::browseImportProject() } } +void ProjectFileDialog::browseUserInclude() +{ + const QFileInfo inf(mProjectFile->getFilename()); + const QDir &dir = inf.absoluteDir(); + QMap filters; + filters[tr("C/C++ header")] = "*.h"; + filters[tr("All files")] = "*.*"; + QString fileName = QFileDialog::getOpenFileName(this, tr("Include file"), + dir.canonicalPath(), + toFilterString(filters)); + if (!fileName.isEmpty()) { + mUI->mEditUserInclude->setText(mProjectFile->getRelativePath(fileName)); + } +} + QStringList ProjectFileDialog::getProjectConfigurations() const { QStringList configs; @@ -878,7 +888,9 @@ void ProjectFileDialog::addExcludeFile() QMap filters; filters[tr("Source files")] = "*.c *.cpp"; filters[tr("All files")] = "*.*"; - addExcludePath(QFileDialog::getOpenFileName(this, tr("Exclude file"), dir.canonicalPath(), toFilterString(filters))); + QString fileName = QFileDialog::getOpenFileName(this, tr("Exclude file"), dir.canonicalPath(), toFilterString(filters)); + if (!fileName.isEmpty()) + addExcludePath(mProjectFile->getRelativePath(fileName)); } void ProjectFileDialog::editExcludePath() diff --git a/gui/projectfiledialog.h b/gui/projectfiledialog.h index 68f62ea83e5..a93d12dc2e2 100644 --- a/gui/projectfiledialog.h +++ b/gui/projectfiledialog.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -201,6 +201,11 @@ private slots: */ void browseImportProject(); + /** + * @brief Browse for include file + */ + void browseUserInclude(); + /** * @brief Add new path to check. */ @@ -275,7 +280,7 @@ private slots: /** * @brief Edit suppression (double clicking on suppression) */ - void editSuppression(const QModelIndex &index); + void editSuppression(const QModelIndex& /*index*/); /** * @brief Browse for misra file diff --git a/gui/readme.txt b/gui/readme.txt index 2137c6d174d..5983fe3658f 100644 --- a/gui/readme.txt +++ b/gui/readme.txt @@ -45,7 +45,11 @@ has several options to select tests etc. You can get the help by running Translations ------------ The GUI is translated to several languages. Qt comes with two tools to update -and compile the translations. lupdate updates translations files from the code -and lrelease compiles translation files use with the executable. - -The translations are automatically updated and compiled within CMake. \ No newline at end of file +and compile the translations. + +1. lupdate updates translations files from the code. + Updating the translation files from the code is a separate step: run + "lupdate gui.pro" + in the gui directory. +2. lrelease compiles translation files use with the executable. + The translations are automatically compiled within CMake. diff --git a/gui/resultitem.h b/gui/resultitem.h index e0c212b2138..9c9b0b82af3 100644 --- a/gui/resultitem.h +++ b/gui/resultitem.h @@ -53,8 +53,8 @@ class ResultItem : public QStandardItem return mIconFileName; } private: - const Type mType; - const int mErrorPathIndex; + Type mType; + int mErrorPathIndex; QString mIconFileName; }; diff --git a/gui/resultstree.cpp b/gui/resultstree.cpp index c553855e99e..2c99ecf3ed1 100644 --- a/gui/resultstree.cpp +++ b/gui/resultstree.cpp @@ -122,7 +122,7 @@ static QStringList getLabels() { QObject::tr("Rule"), QObject::tr("Since date"), QObject::tr("Tags"), - QObject::tr("CWE")}; + "CWE"}; } static Severity getSeverity(ReportType reportType, const ErrorItem& errorItem) { @@ -246,7 +246,7 @@ bool ResultsTree::addErrorItem(const ErrorItem& errorItem) const QString s = errorItem.toString(); if (mErrorList.contains(s)) return false; - mErrorList.append(s); + mErrorList.insert(s); QSharedPointer errorItemPtr{new ErrorItem(errorItem)}; @@ -427,8 +427,8 @@ void ResultsTree::clear(const QString &filename) if (stripped == fileItem->text() || filename == fileItem->errorItem->file0) { + removeFileErrorsFromErrorList(fileItem); mModel->removeRow(i); - mErrorList.removeAll(fileItem->errorItem->toString()); break; } } @@ -445,8 +445,8 @@ void ResultsTree::clearRecheckFile(const QString &filename) QString storedfile = fileItem->getErrorPathItem().file; storedfile = ((!mCheckPath.isEmpty() && storedfile.startsWith(mCheckPath)) ? storedfile.mid(mCheckPath.length() + 1) : storedfile); if (actualfile == storedfile) { + removeFileErrorsFromErrorList(fileItem); mModel->removeRow(i); - mErrorList.removeAll(fileItem->errorItem->toString()); break; } } @@ -741,7 +741,7 @@ void ResultsTree::startApplication(const ResultItem *target, int application) //If there are no applications specified, tell the user about it if (mApplications->getApplicationCount() == 0) { QMessageBox msg(QMessageBox::Critical, - tr("Cppcheck"), + "Cppcheck", tr("No editor application configured.\n\n" "Configure the editor application for Cppcheck in preferences/Applications."), QMessageBox::Ok, @@ -755,7 +755,7 @@ void ResultsTree::startApplication(const ResultItem *target, int application) if (application == -1) { QMessageBox msg(QMessageBox::Critical, - tr("Cppcheck"), + "Cppcheck", tr("No default editor application selected.\n\n" "Please select the default editor application in preferences/Applications."), QMessageBox::Ok, @@ -1132,6 +1132,15 @@ void ResultsTree::saveErrors(Report *report, const ResultItem *fileItem) const } } +void ResultsTree::removeFileErrorsFromErrorList(const ResultItem *fileItem) +{ + for (int i{0}; i < fileItem->rowCount(); ++i) { + const auto *errorItem = dynamic_cast(fileItem->child(i, COLUMN_FILE)); + if (errorItem && errorItem->errorItem) + mErrorList.remove(errorItem->errorItem->toString()); + } +} + void ResultsTree::updateFromOldReport(const QString &filename) { showColumn(COLUMN_SINCE_DATE); @@ -1194,7 +1203,15 @@ void ResultsTree::updateSettings(bool showFullPath, void ResultsTree::setCheckDirectory(const QString &dir) { + if (mCheckPath == dir) + return; + mCheckPath = dir; + + // A partial recheck keeps results whose displayed paths are relative to the check path, so + // they are recalculated. Not without a check path, QDir("") would turn absolute paths into "../..". + if (!mCheckPath.isEmpty()) + refreshFilePaths(); } const QString& ResultsTree::getCheckDirectory() const diff --git a/gui/resultstree.h b/gui/resultstree.h index 23fe5567ed9..c829c21fc6a 100644 --- a/gui/resultstree.h +++ b/gui/resultstree.h @@ -29,6 +29,7 @@ #include #include +#include #include #include #include @@ -356,6 +357,12 @@ protected slots: */ void saveErrors(Report *report, const ResultItem *fileItem) const; + /** + * @brief Remove all errors of a file item from the duplicate-check list + * @param fileItem Item whose errors to remove + */ + void removeFileErrorsFromErrorList(const ResultItem *fileItem); + /** * @brief Convert a severity string to a icon filename * @@ -566,8 +573,8 @@ protected slots: QStringList mHiddenMessageId; - // List of existing errors so we can avoid duplicates - QStringList mErrorList; + // Set of existing errors so we can avoid duplicates + QSet mErrorList; QItemSelectionModel* mSelectionModel{}; ThreadHandler *mThread{}; diff --git a/gui/resultsview.cpp b/gui/resultsview.cpp index c6c54a2fd85..d11cae35bc6 100644 --- a/gui/resultsview.cpp +++ b/gui/resultsview.cpp @@ -123,6 +123,7 @@ void ResultsView::clear(bool results) } mUI->mDetails->setText(QString()); + mUI->mCode->clear(); mStatistics->clear(); delete mCheckSettings; @@ -134,8 +135,6 @@ void ResultsView::clear(bool results) mUI->mProgress->setFormat("%p%"); mUI->mLabelCriticalErrors->setVisible(false); - - mSuccess = false; } void ResultsView::clear(const QString &filename) @@ -319,7 +318,6 @@ void ResultsView::setCheckSettings(const Settings &settings) void ResultsView::checkingStarted(int count) { - mSuccess = true; mUI->mProgress->setVisible(true); mUI->mProgress->setMaximum(PROGRESS_MAX); mUI->mProgress->setValue(0); @@ -347,7 +345,7 @@ void ResultsView::checkingFinished() //Tell user that we found no errors if (!hasResults()) { QMessageBox msg(QMessageBox::Information, - tr("Cppcheck"), + "Cppcheck", tr("No errors found."), QMessageBox::Ok, this); @@ -358,7 +356,7 @@ void ResultsView::checkingFinished() QString text = tr("Errors were found, but they are configured to be hidden.\n" \ "To toggle what kind of errors are shown, open view menu."); QMessageBox msg(QMessageBox::Information, - tr("Cppcheck"), + "Cppcheck", text, QMessageBox::Ok, this); @@ -394,8 +392,6 @@ void ResultsView::translate() void ResultsView::readErrorsXml(const QString &filename) { - mSuccess = false; // Don't know if results come from an aborted analysis - const int version = XmlReport::determineVersion(filename); if (version == 0) { QMessageBox msgBox; @@ -522,6 +518,7 @@ void ResultsView::logCopyComplete() const QListWidgetItem * item = mUI->mListLog->item(i); if (nullptr != item) { logText += item->text(); + logText += "\n"; } } QClipboard *clipboard = QApplication::clipboard(); @@ -545,7 +542,6 @@ void ResultsView::on_mListLog_customContextMenuRequested(const QPoint &pos) void ResultsView::stopAnalysis() { - mSuccess = false; mUI->mLabelCriticalErrors->setText(tr("Analysis was stopped")); mUI->mLabelCriticalErrors->setVisible(true); } @@ -568,10 +564,5 @@ void ResultsView::handleCriticalError(const ErrorItem &item) msg += ". " + tr("Analysis was aborted."); mUI->mLabelCriticalErrors->setText(msg); mUI->mLabelCriticalErrors->setVisible(true); - mSuccess = false; } } - -bool ResultsView::isSuccess() const { - return mSuccess; -} diff --git a/gui/resultsview.h b/gui/resultsview.h index 77b4610145c..9ef74666395 100644 --- a/gui/resultsview.h +++ b/gui/resultsview.h @@ -388,11 +388,6 @@ public slots: Settings* mCheckSettings = nullptr; - /** - * Set to true when checking finish successfully. Set to false whenever analysis starts. - */ - bool mSuccess = false; - /** Critical error ids */ QString mCriticalErrors; diff --git a/gui/statsdialog.cpp b/gui/statsdialog.cpp index f16b6cba3b9..bf46abcb2d4 100644 --- a/gui/statsdialog.cpp +++ b/gui/statsdialog.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -113,6 +113,7 @@ void StatsDialog::setProject(const ProjectFile* projectFile) if (!statsFile.isEmpty()) { QChartView *chartView = createChart(statsFile, "cppcheck"); mUI->mTabHistory->layout()->addWidget(chartView); + // cppcheck-suppress knownConditionTrueFalse - TODO in getClangAnalyzer() if (projectFile->getClangAnalyzer()) { chartView = createChart(statsFile, CLANG_ANALYZER); mUI->mTabHistory->layout()->addWidget(chartView); diff --git a/gui/test/cppchecklibrarydata/testcppchecklibrarydata.cpp b/gui/test/cppchecklibrarydata/testcppchecklibrarydata.cpp index c747f1b5093..7a91bd332c1 100644 --- a/gui/test/cppchecklibrarydata/testcppchecklibrarydata.cpp +++ b/gui/test/cppchecklibrarydata/testcppchecklibrarydata.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2021 Cppcheck team. + * Copyright (C) 2007-2025 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/gui/test/cppchecklibrarydata/testcppchecklibrarydata.h b/gui/test/cppchecklibrarydata/testcppchecklibrarydata.h index 625ad4046a9..31ddbb9eae0 100644 --- a/gui/test/cppchecklibrarydata/testcppchecklibrarydata.h +++ b/gui/test/cppchecklibrarydata/testcppchecklibrarydata.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2021 Cppcheck team. + * Copyright (C) 2007-2025 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/gui/test/data/xmlfiles/xmlreport_v2.xml b/gui/test/data/xmlfiles/xmlreport_v2.xml index 31af1a92cff..e8e03c3adbb 100644 --- a/gui/test/data/xmlfiles/xmlreport_v2.xml +++ b/gui/test/data/xmlfiles/xmlreport_v2.xml @@ -2,10 +2,10 @@ - + - + @@ -15,7 +15,7 @@ - + diff --git a/gui/test/filelist/testfilelist.cpp b/gui/test/filelist/testfilelist.cpp index d24b766bcc3..003c43c1ba0 100644 --- a/gui/test/filelist/testfilelist.cpp +++ b/gui/test/filelist/testfilelist.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2021 Cppcheck team. + * Copyright (C) 2007-2023 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/gui/test/filelist/testfilelist.h b/gui/test/filelist/testfilelist.h index 17a33d86e3e..f8d9e3a5b7b 100644 --- a/gui/test/filelist/testfilelist.h +++ b/gui/test/filelist/testfilelist.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2021 Cppcheck team. + * Copyright (C) 2007-2024 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/gui/test/projectfile/testprojectfile.cpp b/gui/test/projectfile/testprojectfile.cpp index b38de056ec3..1a66bd8de23 100644 --- a/gui/test/projectfile/testprojectfile.cpp +++ b/gui/test/projectfile/testprojectfile.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2021 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,12 +18,17 @@ #include "testprojectfile.h" +#include "addoninfo.h" #include "library.h" #include "platform.h" #include "projectfile.h" #include "settings.h" #include "suppressions.h" +#ifdef HAVE_RULES +#include "rule.h" +#endif + #include #include #include @@ -38,6 +43,7 @@ const char Settings::SafeChecks::XmlExternalFunctions[] = "external-functions"; const char Settings::SafeChecks::XmlInternalFunctions[] = "internal-functions"; const char Settings::SafeChecks::XmlExternalVariables[] = "external-variables"; Settings::Settings() : maxCtuDepth(10) {} +Settings::~Settings() = default; Platform::Platform() = default; Library::Library() = default; Library::~Library() = default; @@ -199,5 +205,54 @@ void TestProjectFile::getCheckingSuppressionsStar() const QCOMPARE(projectFile.getCheckingSuppressions()[0].fileName, "*.cpp"); } +void TestProjectFile::emptyUserInclude() const +{ + ProjectFile projectFile; + Settings settings; + projectFile.setUserInclude(" "); + projectFile.setSettingsUserIncludes(settings); + QCOMPARE(settings.userIncludes.size(), 0); +} + +// Absolute path is made relative when it does not require walking up more than 3 parent folders +void TestProjectFile::getRelativePathRelative() const +{ + ProjectFile projectFile; + projectFile.setFilename("/some/path/sub/123.cppcheck"); + QCOMPARE(projectFile.getRelativePath("/some/path/externals/foo.cpp"), QString("../externals/foo.cpp")); +} + +// Absolute path is made relative even when it requires walking up 2 parent folders +void TestProjectFile::getRelativePathTwoUp() const +{ + ProjectFile projectFile; + projectFile.setFilename("/some/path/sub/123.cppcheck"); + QCOMPARE(projectFile.getRelativePath("/some/externals/foo.cpp"), QString("../../externals/foo.cpp")); +} + +// Absolute path is kept as-is when making it relative would require walking up more than 3 parent folders +void TestProjectFile::getRelativePathTooFarUp() const +{ + ProjectFile projectFile; + projectFile.setFilename("/some/path/sub/123.cppcheck"); + QCOMPARE(projectFile.getRelativePath("/other/deep/foo.cpp"), QString("/other/deep/foo.cpp")); +} + +// Absolute path in a subfolder of the project path is made relative without walking up at all +void TestProjectFile::getRelativePathSubfolder() const +{ + ProjectFile projectFile; + projectFile.setFilename("/some/path/sub/123.cppcheck"); + QCOMPARE(projectFile.getRelativePath("/some/path/sub/src/file1.c"), QString("src/file1.c")); +} + +// Absolute path is kept as-is when it is shorter than the relative path, even if it does not require walking up 3 or more parent folders +void TestProjectFile::getRelativePathAbsoluteShorter() const +{ + ProjectFile projectFile; + projectFile.setFilename("/ab/path/sub/123.cppcheck"); + QCOMPARE(projectFile.getRelativePath("/ab/foo.cpp"), QString("/ab/foo.cpp")); +} + QTEST_MAIN(TestProjectFile) diff --git a/gui/test/projectfile/testprojectfile.h b/gui/test/projectfile/testprojectfile.h index 027c7069706..7203980eb7c 100644 --- a/gui/test/projectfile/testprojectfile.h +++ b/gui/test/projectfile/testprojectfile.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2021 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -36,4 +36,12 @@ private slots: void getCheckingSuppressionsRelative() const; void getCheckingSuppressionsAbsolute() const; void getCheckingSuppressionsStar() const; + + void emptyUserInclude() const; + + void getRelativePathRelative() const; + void getRelativePathTwoUp() const; + void getRelativePathTooFarUp() const; + void getRelativePathSubfolder() const; + void getRelativePathAbsoluteShorter() const; }; diff --git a/gui/test/resultstree/testresultstree.cpp b/gui/test/resultstree/testresultstree.cpp index afda38e0075..c4a7d3080c0 100644 --- a/gui/test/resultstree/testresultstree.cpp +++ b/gui/test/resultstree/testresultstree.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2021 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,7 +22,6 @@ // headers that declare mocked functions/variables #include "applicationlist.h" -#include "common.h" #include "filesettings.h" #include "projectfile.h" #include "threadhandler.h" @@ -107,12 +106,6 @@ Application& ApplicationList::getApplication(const int /*unused*/) { const Application& ApplicationList::getApplication(const int index) const { return mApplications.at(index); } -QString XmlReport::quoteMessage(const QString &message) { - return message; -} -QString XmlReport::unquoteMessage(const QString &message) { - return message; -} XmlReport::XmlReport(const QString& filename) : Report(filename) {} // NOLINTNEXTLINE(performance-unnecessary-value-param) void ThreadResult::finishCheck(CheckThread::Details /*unused*/) { @@ -266,6 +259,31 @@ void TestResultsTree::resultsInSameFile() const } } +void TestResultsTree::clearRecheckFile() const +{ + // #14893 - rechecking a file removes its results, the results of the other files are kept + ResultsTree tree(nullptr); + QVERIFY(tree.addErrorItem(createErrorItem("file1.c", 10, Severity::style, "test", "bugId"))); + QVERIFY(tree.addErrorItem(createErrorItem("file1.c", 20, Severity::style, "test", "bugId"))); + QVERIFY(tree.addErrorItem(createErrorItem("file2.c", 10, Severity::style, "test", "bugId"))); + + const auto* model = dynamic_cast(tree.model()); + QVERIFY(model != nullptr); + QCOMPARE(model->rowCount(), 2); + + tree.clearRecheckFile("file1.c"); + QCOMPARE(model->rowCount(), 1); + QCOMPARE(model->item(0,0)->text(), "file2.c"); + + // the recheck reports the results of the rechecked file again, they must not be treated as duplicates + QVERIFY(tree.addErrorItem(createErrorItem("file1.c", 10, Severity::style, "test", "bugId"))); + QVERIFY(tree.addErrorItem(createErrorItem("file1.c", 20, Severity::style, "test", "bugId"))); + QCOMPARE(model->rowCount(), 2); + + // the results of the other files are still known + QVERIFY(!tree.addErrorItem(createErrorItem("file2.c", 10, Severity::style, "test", "bugId"))); +} + void TestResultsTree::testReportType() const { TestReport report("{id},{classification},{guideline}"); diff --git a/gui/test/resultstree/testresultstree.h b/gui/test/resultstree/testresultstree.h index 8604dc81c6b..dcce568b4ba 100644 --- a/gui/test/resultstree/testresultstree.h +++ b/gui/test/resultstree/testresultstree.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2021 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,6 +26,7 @@ private slots: void duplicateResults() const; void multiLineResult() const; void resultsInSameFile() const; + void clearRecheckFile() const; void testReportType() const; void testReportTypeIcon() const; void testGetGuidelineError() const; diff --git a/gui/test/translationhandler/testtranslationhandler.cpp b/gui/test/translationhandler/testtranslationhandler.cpp index 7c093e3edfd..c827ad47b0c 100644 --- a/gui/test/translationhandler/testtranslationhandler.cpp +++ b/gui/test/translationhandler/testtranslationhandler.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2021 Cppcheck team. + * Copyright (C) 2007-2025 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/gui/test/translationhandler/testtranslationhandler.h b/gui/test/translationhandler/testtranslationhandler.h index 249bbaa5ddd..bdbf3c522e8 100644 --- a/gui/test/translationhandler/testtranslationhandler.h +++ b/gui/test/translationhandler/testtranslationhandler.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2021 Cppcheck team. + * Copyright (C) 2007-2024 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/gui/test/xmlreportv2/testxmlreportv2.cpp b/gui/test/xmlreportv2/testxmlreportv2.cpp index cede5842de0..d157a4bef6d 100644 --- a/gui/test/xmlreportv2/testxmlreportv2.cpp +++ b/gui/test/xmlreportv2/testxmlreportv2.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2021 Cppcheck team. + * Copyright (C) 2007-2024 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,7 +21,9 @@ #include "erroritem.h" #include "xmlreportv2.h" +#include #include +#include #include void TestXmlReportV2::readXml() const @@ -53,4 +55,39 @@ void TestXmlReportV2::readXml() const QCOMPARE(item2.message, QString("Mismatching allocation and deallocation: k")); } +void TestXmlReportV2::writeXmlEscaping() const +{ + QTemporaryDir dir; + const QString path = dir.path() + "/results.xml"; + + ErrorItem item; + item.errorId = "id"; + item.severity = Severity::style; + item.summary = "\"x\" & ""; + item.message = item.summary; + QErrorPathItem loc; + loc.file = "a&b.cpp"; + loc.line = 1; + item.errorPath << loc; + { + XmlReportV2 report(path, QString()); + QVERIFY(report.create()); + report.writeHeader(); + report.writeError(item); + report.writeFooter(); + } + + QFile file(path); + QVERIFY(file.open(QIODevice::ReadOnly)); + QVERIFY(file.readAll().contains("msg=\""x" & <y> &quot;\"")); + + XmlReportV2 report(path, QString()); + QVERIFY(report.open()); + const QList errors = report.read(); + QCOMPARE(errors.size(), 1); + QCOMPARE(errors[0].summary, item.summary); + QCOMPARE(errors[0].message, item.message); + QCOMPARE(errors[0].errorPath[0].file, loc.file); +} + QTEST_MAIN(TestXmlReportV2) diff --git a/gui/test/xmlreportv2/testxmlreportv2.h b/gui/test/xmlreportv2/testxmlreportv2.h index f4de863bff0..9662bd00b27 100644 --- a/gui/test/xmlreportv2/testxmlreportv2.h +++ b/gui/test/xmlreportv2/testxmlreportv2.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2021 Cppcheck team. + * Copyright (C) 2007-2024 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,4 +23,5 @@ class TestXmlReportV2 : public QObject { private slots: void readXml() const; + void writeXmlEscaping() const; }; diff --git a/gui/threadhandler.cpp b/gui/threadhandler.cpp index 3f89d58b558..577dcdc3375 100644 --- a/gui/threadhandler.cpp +++ b/gui/threadhandler.cpp @@ -21,6 +21,7 @@ #include "checkthread.h" #include "common.h" #include "filesettings.h" +#include "importproject.h" #include "resultsview.h" #include "settings.h" #include "utils.h" @@ -53,6 +54,7 @@ ThreadHandler::~ThreadHandler() void ThreadHandler::clearFiles() { mLastFiles.clear(); + mLastFileSettings.clear(); mResults.clearFiles(); mAnalyseWholeProgram = false; mCtuInfo.clear(); @@ -63,6 +65,7 @@ void ThreadHandler::clearFiles() void ThreadHandler::setFiles(std::list files) { mLastFiles = files; + mLastFileSettings.clear(); mResults.setFiles(std::move(files)); } @@ -70,6 +73,7 @@ void ThreadHandler::setProject(const ImportProject &prj) { mResults.setProject(prj); mLastFiles.clear(); + mLastFileSettings = prj.fileSettings; } void ThreadHandler::setCheckFiles(bool all) @@ -190,7 +194,7 @@ void ThreadHandler::threadDone() createThreads(1); mRunningThreadCount = 1; setupCheckThread(*mThreads[0]); - mThreads[0]->analyseWholeProgram(mLastFiles, mCtuInfo); + mThreads[0]->analyseWholeProgram(mLastFiles, mLastFileSettings, mCtuInfo); mAnalyseWholeProgram = false; mCtuInfo.clear(); return; diff --git a/gui/threadhandler.h b/gui/threadhandler.h index 17f011c32ac..879da0db8da 100644 --- a/gui/threadhandler.h +++ b/gui/threadhandler.h @@ -20,6 +20,7 @@ #ifndef THREADHANDLER_H #define THREADHANDLER_H +#include "filesettings.h" #include "settings.h" #include "suppressions.h" #include "threadresult.h" @@ -238,6 +239,12 @@ protected slots: */ std::list mLastFiles; + /** + * @brief Files of the project checked last time (used by the whole program analysis) + * + */ + std::list mLastFileSettings; + /** @brief date and time when current checking started */ QDateTime mCheckStartTime; diff --git a/gui/threadresult.h b/gui/threadresult.h index 1224b3c539d..87560e87d78 100644 --- a/gui/threadresult.h +++ b/gui/threadresult.h @@ -79,7 +79,7 @@ class ThreadResult : public QObject, public ErrorLogger { /** * ErrorLogger methods */ - void reportOut(const std::string &outmsg, Color c = Color::Reset) override; + void reportOut(const std::string &outmsg, Color /*c*/ = Color::Reset) override; void reportErr(const ErrorMessage &msg) override; void reportMetric(const std::string &metric) override { diff --git a/gui/translationhandler.cpp b/gui/translationhandler.cpp index d8160525856..29713c7c065 100644 --- a/gui/translationhandler.cpp +++ b/gui/translationhandler.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -133,7 +133,7 @@ bool TranslationHandler::setLanguage(const QString &code) "the Preferences-dialog to select any of the available " "languages.").arg(error)); QMessageBox msgBox(QMessageBox::Warning, - tr("Cppcheck"), + "Cppcheck", msg, QMessageBox::Ok); msgBox.exec(); diff --git a/gui/xmlreport.cpp b/gui/xmlreport.cpp index c5f00fc5bb7..8d184ca6658 100644 --- a/gui/xmlreport.cpp +++ b/gui/xmlreport.cpp @@ -32,28 +32,6 @@ XmlReport::XmlReport(const QString &filename) : Report(filename) {} -QString XmlReport::quoteMessage(const QString &message) -{ - QString quotedMessage(message); - quotedMessage.replace("&", "&"); - quotedMessage.replace("\"", """); - quotedMessage.replace("'", "'"); - quotedMessage.replace("<", "<"); - quotedMessage.replace(">", ">"); - return quotedMessage; -} - -QString XmlReport::unquoteMessage(const QString &message) -{ - QString quotedMessage(message); - quotedMessage.replace("&", "&"); - quotedMessage.replace(""", "\""); - quotedMessage.replace("'", "'"); - quotedMessage.replace("<", "<"); - quotedMessage.replace(">", ">"); - return quotedMessage; -} - int XmlReport::determineVersion(const QString &filename) { QFile file; diff --git a/gui/xmlreport.h b/gui/xmlreport.h index 2483b1b7471..83c222291ab 100644 --- a/gui/xmlreport.h +++ b/gui/xmlreport.h @@ -42,20 +42,6 @@ class XmlReport : public Report { */ virtual QList read() = 0; - /** - * @brief Quote the message. - * @param message Message to quote. - * @return quoted message. - */ - static QString quoteMessage(const QString &message); - - /** - * @brief Unquote the message. - * @param message Message to quote. - * @return quoted message. - */ - static QString unquoteMessage(const QString &message); - /** * @brief Get the XML report format version from the file. * @param filename Filename of the report file. diff --git a/gui/xmlreportv2.cpp b/gui/xmlreportv2.cpp index abf6aa361ba..b707b6d44dd 100644 --- a/gui/xmlreportv2.cpp +++ b/gui/xmlreportv2.cpp @@ -132,10 +132,8 @@ void XmlReportV2::writeError(const ErrorItem &error) // Don't localize severity so we can read these files mXmlWriter->writeAttribute(SeverityAttribute, GuiSeverity::toString(error.severity)); - const QString summary = XmlReport::quoteMessage(error.summary); - mXmlWriter->writeAttribute(MsgAttribute, summary); - const QString message = XmlReport::quoteMessage(error.message); - mXmlWriter->writeAttribute(VerboseAttribute, message); + mXmlWriter->writeAttribute(MsgAttribute, error.summary); + mXmlWriter->writeAttribute(VerboseAttribute, error.message); if (error.inconclusive) mXmlWriter->writeAttribute(InconclusiveAttribute, "true"); if (!error.remark.isEmpty()) @@ -145,7 +143,7 @@ void XmlReportV2::writeError(const ErrorItem &error) if (error.hash > 0) mXmlWriter->writeAttribute(HashAttribute, QString::number(error.hash)); if (!error.file0.isEmpty()) - mXmlWriter->writeAttribute(IncludedFromFilenameAttribute, quoteMessage(error.file0)); + mXmlWriter->writeAttribute(IncludedFromFilenameAttribute, error.file0); if (!error.sinceDate.isEmpty()) mXmlWriter->writeAttribute(SinceDateAttribute, error.sinceDate); if (!error.tags.isEmpty()) @@ -155,12 +153,12 @@ void XmlReportV2::writeError(const ErrorItem &error) mXmlWriter->writeStartElement(LocationElementName); QString file = QDir::toNativeSeparators(error.errorPath[i].file); - mXmlWriter->writeAttribute(FilenameAttribute, XmlReport::quoteMessage(file)); + mXmlWriter->writeAttribute(FilenameAttribute, file); mXmlWriter->writeAttribute(LineAttribute, QString::number(error.errorPath[i].line)); if (error.errorPath[i].column > 0) mXmlWriter->writeAttribute(ColumnAttribute, QString::number(error.errorPath[i].column)); if (error.errorPath.count() > 1) - mXmlWriter->writeAttribute(InfoAttribute, XmlReport::quoteMessage(error.errorPath[i].info)); + mXmlWriter->writeAttribute(InfoAttribute, error.errorPath[i].info); mXmlWriter->writeEndElement(); } @@ -228,10 +226,8 @@ ErrorItem XmlReportV2::readError(const QXmlStreamReader *reader) QXmlStreamAttributes attribs = reader->attributes(); item.errorId = attribs.value(QString(), IdAttribute).toString(); item.severity = GuiSeverity::fromString(attribs.value(QString(), SeverityAttribute).toString()); - const QString summary = attribs.value(QString(), MsgAttribute).toString(); - item.summary = XmlReport::unquoteMessage(summary); - const QString message = attribs.value(QString(), VerboseAttribute).toString(); - item.message = XmlReport::unquoteMessage(message); + item.summary = attribs.value(QString(), MsgAttribute).toString(); + item.message = attribs.value(QString(), VerboseAttribute).toString(); if (attribs.hasAttribute(QString(), InconclusiveAttribute)) item.inconclusive = true; if (attribs.hasAttribute(QString(), RemarkAttribute)) @@ -258,14 +254,14 @@ ErrorItem XmlReportV2::readError(const QXmlStreamReader *reader) QXmlStreamAttributes attribs = mXmlReader->attributes(); QString file0 = attribs.value(QString(), IncludedFromFilenameAttribute).toString(); if (!file0.isEmpty()) - item.file0 = XmlReport::unquoteMessage(file0); + item.file0 = file0; QErrorPathItem loc; - loc.file = XmlReport::unquoteMessage(attribs.value(QString(), FilenameAttribute).toString()); + loc.file = attribs.value(QString(), FilenameAttribute).toString(); loc.line = attribs.value(QString(), LineAttribute).toString().toUInt(); if (attribs.hasAttribute(QString(), ColumnAttribute)) loc.column = attribs.value(QString(), ColumnAttribute).toString().toInt(); if (attribs.hasAttribute(QString(), InfoAttribute)) - loc.info = XmlReport::unquoteMessage(attribs.value(QString(), InfoAttribute).toString()); + loc.info = attribs.value(QString(), InfoAttribute).toString(); item.errorPath.push_front(loc); } break; diff --git a/htmlreport/cppcheck-htmlreport b/htmlreport/cppcheck-htmlreport index 5044443a0a9..4de692ce3e9 100755 --- a/htmlreport/cppcheck-htmlreport +++ b/htmlreport/cppcheck-htmlreport @@ -278,7 +278,7 @@ HTML_HEAD = """ updateFileRows(); } - + function toggleClassification(cb) { cb.parentElement.classList.toggle("unchecked", !cb.checked); var elements = document.querySelectorAll(".class_" + cb.id); @@ -289,7 +289,7 @@ HTML_HEAD = """ updateFileRows(); } - + function toggleTool(cb) { cb.parentElement.classList.toggle("unchecked", !cb.checked); @@ -469,9 +469,9 @@ def filter_button(enabled_filters, id, function): def filter_bar(enabled): severity_bar = ''.join([filter_button(enabled, severity, 'toggleSeverity') for severity in ['error', 'warning', 'portability', 'performance', 'style', 'information']]) + '\n | ' classification_bar = ''.join([filter_button(enabled, _class, 'toggleClassification') for _class in ['Mandatory', 'Required', 'Advisory', 'Document', 'Disapplied', 'L1','L2','L3','']]) + '\n | ' - if "checked/>" not in severity_bar: + if "checked>" not in severity_bar: severity_bar = '' - if "checked/>" not in classification_bar: + if "checked>" not in classification_bar: classification_bar = '' return ''.join([ '
\n' @@ -620,15 +620,15 @@ class AnnotateCodeFormatter(HtmlFormatter): # from actual message if error.get('verbose') and (error['verbose'] != error['msg']): index = t.rfind('\n') - t = t[:index] + HTML_EXPANDABLE_INCONCLUSIVE % (error['msg'], html_escape(error['verbose'].replace("\\012", '\n'))) + t[index + 1:] + t = t[:index] + HTML_EXPANDABLE_INCONCLUSIVE % (html_escape(error['msg']), html_escape(error['verbose'].replace("\\012", '\n'))) + t[index + 1:] else: - t = t.replace('\n', HTML_INCONCLUSIVE % error['msg']) + t = t.replace('\n', HTML_INCONCLUSIVE % html_escape(error['msg'])) except KeyError: if error.get('verbose') and (error['verbose'] != error['msg']): index = t.rfind('\n') - t = t[:index] + HTML_EXPANDABLE_ERROR % (error['msg'], html_escape(error['verbose'].replace("\\012", '\n'))) + t[index + 1:] + t = t[:index] + HTML_EXPANDABLE_ERROR % (html_escape(error['msg']), html_escape(error['verbose'].replace("\\012", '\n'))) + t[index + 1:] else: - t = t.replace('\n', HTML_ERROR % error['msg']) + t = t.replace('\n', HTML_ERROR % html_escape(error['msg'])) line_no = line_no + 1 yield i, t @@ -709,6 +709,9 @@ class CppCheckHandler(XmlContentHandler): error = self.errors[-1] locations = error['locations'] file = attributes['file'] + # Cppcheck Premium reports license messages with this pseudo file name + if file == 'Cppcheck Premium': + file = '' line = int(attributes['line']) if not locations: error['file'] = file @@ -824,7 +827,7 @@ def main() -> None: if is_remote: # Construct remote URL for GitHub/GitLab # tr_str() will use the actual line number, so we can just start with line 1 - remote_url = source_dir.rstrip('/') + '/' + filename + '#L1' + remote_url = source_dir.rstrip('/') + '/' + html_escape(filename) + '#L1' files[filename] = {'errors': [], 'htmlfile': remote_url} else: files[filename] = {'errors': [], 'htmlfile': str(file_no) + '.html'} @@ -900,10 +903,10 @@ def main() -> None: (options.title, htmlFormatter.get_style_defs('.highlight'), options.title, - ': ' + filename)) + ': ' + html_escape(filename))) output_file.write(HTML_HEAD_END) - output_file.write(HTML_MENU % (filename.split('/')[-1])) + output_file.write(HTML_MENU % (html_escape(filename.split('/')[-1]))) for error in sorted(errors, key=lambda k: k['line']): output_file.write(" %s %s" % (data['htmlfile'], error['line'], error['id'], error['line'])) output_file.write(HTML_MENU_END) @@ -1006,7 +1009,7 @@ def main() -> None: for filename, data in sorted(files.items()): file_error = filename in decode_errors or filename.endswith('*') is_file = filename != '' and not file_error - row_content = filename if file_error else "%s" % (data['htmlfile'], filename) + row_content = html_escape(filename) if file_error else "%s" % (data['htmlfile'], html_escape(filename)) htmlfile = data.get('htmlfile') if is_file else None output_file.write("\n ") @@ -1122,7 +1125,7 @@ def main() -> None: if it == 0: LENGTH = len(str(i[1])) # <- length of longest number, now get the difference and try to make other numbers align to it - stats_file.write(" " * 3 + str(i[1]) + " " * (1 + LENGTH - len(str(i[1]))) + " " + i[0] + "
\n") + stats_file.write(" " * 3 + str(i[1]) + " " * (1 + LENGTH - len(str(i[1]))) + " " + html_escape(i[0]) + "
\n") it += 1 if it == 10: # print only the top 10 break diff --git a/htmlreport/setup.py b/htmlreport/setup.py index 3e049181ce6..9f85000a090 100755 --- a/htmlreport/setup.py +++ b/htmlreport/setup.py @@ -16,7 +16,7 @@ ), long_description=readme, author="Cppcheck Team", - url="https://github.com/danmar/cppcheck", + url="https://github.com/cppcheck-opensource/cppcheck", license="GPL", packages=find_packages(exclude=("tests", "docs")), use_scm_version={"root": ".."}, diff --git a/lib/analyzer.h b/lib/analyzer.h index a4546eb7dc4..f2b7460d7cb 100644 --- a/lib/analyzer.h +++ b/lib/analyzer.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -153,9 +153,13 @@ struct Analyzer { struct Assume { enum Flags : std::uint8_t { None = 0, - Quiet = (1 << 0), - Absolute = (1 << 1), - ContainerEmpty = (1 << 2), + Quiet = (1u << 0), + Absolute = (1u << 1), + ContainerEmpty = (1u << 2), + // The branch this condition guards is not traversed yet (a separate path walks it), so + // the assume must not record the program state at the branch boundaries - they would be + // premature. When unset, the branch has been traversed and control is leaving it. + Pending = (1u << 3), }; }; diff --git a/lib/analyzerinfo.cpp b/lib/analyzerinfo.cpp index 6f919ec3651..3bdc6875e63 100644 --- a/lib/analyzerinfo.cpp +++ b/lib/analyzerinfo.cpp @@ -21,7 +21,6 @@ #include "errorlogger.h" #include "filesettings.h" #include "path.h" -#include "utils.h" #include #include @@ -58,6 +57,68 @@ void AnalyzerInformation::writeFilesTxt(const std::string &buildDir, const std:: fout << getFilesTxt(sourcefiles, fileSettings); } +void AnalyzerInformation::writeIncludes(const std::set &files) +{ + if (!files.empty() && mOutputStream.is_open()) { + mOutputStream << " \n"; + for (const std::string &file : files) { + mOutputStream << " " << file << "\n"; + } + mOutputStream << " \n"; + } +} + +std::set AnalyzerInformation::getIncludes(const std::string &buildDir, const std::string &sourcefile, const std::string &cfg, std::size_t fsFileId) const +{ + if (mOutputStream.is_open()) + throw std::runtime_error("analyzer information file is already open"); + + std::set files; + + if (buildDir.empty() || sourcefile.empty()) + return files; + + const std::string analyzerInfoFile = AnalyzerInformation::getAnalyzerInfoFile(buildDir, sourcefile, cfg, fsFileId); + + tinyxml2::XMLDocument analyzerInfoDoc; + if (analyzerInfoDoc.LoadFile(analyzerInfoFile.c_str()) != tinyxml2::XML_SUCCESS) + return files; + + const tinyxml2::XMLElement *const rootNode = analyzerInfoDoc.FirstChildElement(); + if (rootNode == nullptr) + return files; + + if (strcmp(rootNode->Name(), "analyzerinfo") != 0) + return files; + + const tinyxml2::XMLElement *includesNode = nullptr; + for (const tinyxml2::XMLElement *e = rootNode->FirstChildElement(); e; e = e->NextSiblingElement()) { + if (strcmp(e->Name(), "includes") == 0) { + includesNode = e; + break; + } + } + + if (includesNode == nullptr) + return files; + + for (const tinyxml2::XMLElement *e = includesNode->FirstChildElement(); e; e = e->NextSiblingElement()) { + if (strcmp(e->Name(), "filename") != 0) + continue; + + files.insert(e->GetText()); + } + + return files; +} + +void AnalyzerInformation::writeHash(std::size_t hash) +{ + if (mOutputStream.is_open()) { + mOutputStream << " " << hash << "\n"; + } +} + std::string AnalyzerInformation::getFilesTxt(const std::list &sourcefiles, const std::list &fileSettings) { std::ostringstream ret; @@ -94,10 +155,17 @@ std::string AnalyzerInformation::skipAnalysis(const tinyxml2::XMLDocument &analy if (strcmp(rootNode->Name(), "analyzerinfo") != 0) return "unexpected root node"; - const char * const attr = rootNode->Attribute("hash"); - if (!attr) - return "no 'hash' attribute found"; - if (attr != std::to_string(hash)) + const tinyxml2::XMLElement *hashNode = nullptr; + for (const tinyxml2::XMLElement *e = rootNode->FirstChildElement(); e; e = e->NextSiblingElement()) { + if (strcmp(e->Name(), "hash") == 0) { + hashNode = e; + break; + } + } + + if (!hashNode) + return "no 'hash' node found"; + if (hashNode->GetText() != std::to_string(hash)) return "hash mismatch"; for (const tinyxml2::XMLElement *e = rootNode->FirstChildElement(); e; e = e->NextSiblingElement()) { @@ -126,14 +194,15 @@ std::string AnalyzerInformation::skipAnalysis(const tinyxml2::XMLDocument &analy return ""; } -std::string AnalyzerInformation::getAnalyzerInfoFileFromFilesTxt(std::istream& filesTxt, const std::string &sourcefile, const std::string &cfg, int fsFileId) +std::string AnalyzerInformation::getAnalyzerInfoFileFromFilesTxt(std::istream& filesTxt, const std::string &sourcefile, const std::string &cfg, size_t fsFileId) { + const std::string simplifiedSourcefile = Path::simplifyPath(sourcefile); std::string line; while (std::getline(filesTxt,line)) { AnalyzerInformation::Info filesTxtInfo; if (!filesTxtInfo.parse(line)) continue; // TODO: report error? - if (endsWith(sourcefile, filesTxtInfo.sourceFile) && filesTxtInfo.cfg == cfg && filesTxtInfo.fsFileId == fsFileId) + if (filesTxtInfo.sourceFile == simplifiedSourcefile && filesTxtInfo.cfg == cfg && filesTxtInfo.fsFileId == fsFileId) return filesTxtInfo.afile; } return ""; @@ -194,7 +263,7 @@ bool AnalyzerInformation::analyzeFile(const std::string &buildDir, const std::st if (!mOutputStream.is_open()) throw std::runtime_error("failed to open '" + analyzerInfoFile + "'"); mOutputStream << "\n"; - mOutputStream << "\n"; + mOutputStream << "\n"; return true; } diff --git a/lib/analyzerinfo.h b/lib/analyzerinfo.h index d2a83c747c3..75ff519db84 100644 --- a/lib/analyzerinfo.h +++ b/lib/analyzerinfo.h @@ -27,6 +27,7 @@ #include #include #include +#include #include class ErrorMessage; @@ -67,6 +68,9 @@ class CPPCHECKLIB AnalyzerInformation { bool analyzeFile(const std::string &buildDir, const std::string &sourcefile, const std::string &cfg, std::size_t fsFileId, std::size_t hash, std::list &errors, bool debug = false); void reportErr(const ErrorMessage &msg); void setFileInfo(const std::string &check, const std::string &fileInfo); + void writeIncludes(const std::set &files); + std::set getIncludes(const std::string &buildDir, const std::string &sourcefile, const std::string &cfg, std::size_t fsFileId) const; + void writeHash(std::size_t hash); static std::string getAnalyzerInfoFile(const std::string &buildDir, const std::string &sourcefile, const std::string &cfg, std::size_t fsFileId); void reopen(const std::string &buildDir, const std::string &sourcefile, const std::string &cfg, std::size_t fsFileId); @@ -87,7 +91,7 @@ class CPPCHECKLIB AnalyzerInformation { protected: static std::string getFilesTxt(const std::list &sourcefiles, const std::list &fileSettings); - static std::string getAnalyzerInfoFileFromFilesTxt(std::istream& filesTxt, const std::string &sourcefile, const std::string &cfg, int fsFileId); + static std::string getAnalyzerInfoFileFromFilesTxt(std::istream& filesTxt, const std::string &sourcefile, const std::string &cfg, size_t fsFileId); static std::string skipAnalysis(const tinyxml2::XMLDocument &analyzerInfoDoc, std::size_t hash, std::list &errors); diff --git a/lib/astutils.cpp b/lib/astutils.cpp index ac9795a8ff1..e2e19b6bd91 100644 --- a/lib/astutils.cpp +++ b/lib/astutils.cpp @@ -26,6 +26,7 @@ #include "infer.h" #include "library.h" #include "mathlib.h" +#include "refthunk.h" #include "settings.h" #include "symboldatabase.h" #include "token.h" @@ -320,12 +321,12 @@ Library::Container::Yield astContainerYield(const Token* tok, const Library& lib return ftokCont.second->getYield(ftokCont.first->str()); } -Library::Container::Yield astFunctionYield(const Token* tok, const Settings& settings, const Token** ftok) +Library::Container::Yield astFunctionYield(const Token* tok, const Library& library, const Token** ftok) { if (!tok) return Library::Container::Yield::NO_YIELD; - const auto* function = settings.library.getFunction(tok); + const auto* function = library.getFunction(tok); if (!function) return Library::Container::Yield::NO_YIELD; @@ -400,6 +401,8 @@ const Token * astIsVariableComparison(const Token *tok, const std::string &comp, } while (ret && ret->str() == ".") ret = ret->astOperand2(); + while (ret && ret->isCast()) + ret = ret->astOperand2() ? ret->astOperand2() : ret->astOperand1(); if (ret && ret->str() == "=" && ret->astOperand1() && ret->astOperand1()->varId()) ret = ret->astOperand1(); else if (ret && ret->varId() == 0U) @@ -493,8 +496,6 @@ bool isTemporary(const Token* tok, const Library* library, bool unknown) } return unknown; } - if (tok->isCast()) - return false; // Currying a function is unknown in cppcheck if (Token::simpleMatch(tok, "(") && Token::simpleMatch(tok->astOperand1(), "(")) return unknown; @@ -1069,6 +1070,17 @@ bool isAliasOf(const Token *tok, nonneg int varid, bool* inconclusive) return false; } +bool isIteratorOf(const Token* tok, nonneg int exprId) +{ + if (!astIsIterator(tok)) + return false; + // An iterator into a subcontainer (e.g. c[0].begin()) aliases the container but iterates + // an unrelated range, so require an iterator value recording the container itself + return std::any_of(tok->values().cbegin(), tok->values().cend(), [&](const ValueFlow::Value& v) { + return v.isIteratorValue() && v.container && v.container->exprId() == exprId; + }); +} + bool isAliasOf(const Token* tok, const Token* expr, nonneg int* indirect) { if (indirect) @@ -1148,6 +1160,8 @@ bool exprDependsOnThis(const Token* expr, bool onVar, nonneg int depth) } if (expr->isOperatorKeyword() && !Token::simpleMatch(expr->next()->astParent(), ".")) return true; + if (expr->variable() && expr->variable()->isArgument() && !expr->variable()->type() && expr->variable()->scope()->function && expr->variable()->scope()->function->templateDef) + return true; } if (onVar && expr->variable()) { const Variable* var = expr->variable(); @@ -1541,9 +1555,7 @@ bool isUsedAsBool(const Token* const tok, const Settings& settings) return true; if (parent->isCast()) return !Token::simpleMatch(parent->astOperand1(), "dynamic_cast") && isUsedAsBool(parent, settings); - if (parent->isUnaryOp("*")) - return isUsedAsBool(parent, settings); - if (Token::Match(parent, "==|!=") && tok->valueType() && tok->valueType()->pointer && + if (Token::Match(parent, "==|!=") && ((tok->valueType() && tok->valueType()->pointer) || tok->function()) && tok->astSibling()->hasKnownIntValue() && tok->astSibling()->getKnownIntValue() == 0) return true; if (parent->str() == "(" && astIsRHS(tok) && Token::Match(parent->astOperand1(), "if|while")) @@ -1702,7 +1714,7 @@ bool isSameExpression(bool macro, const Token *tok1, const Token *tok2, const Se compare = true; } } - if (compare && astIsBoolLike(varTok1, settings) && astIsBoolLike(varTok2, settings)) + if (compare && varTok1 != varTok2 && astIsBoolLike(varTok1, settings) && astIsBoolLike(varTok2, settings)) return isSameExpression(macro, varTok1, varTok2, settings, pure, followVar, errors); } @@ -1814,7 +1826,7 @@ static bool isZeroBoundCond(const Token * const cond, bool reverse) const Token* op = reverse ? cond->astOperand1() : cond->astOperand2(); if (!op->hasKnownIntValue()) - return false; + return true; // Assume unsigned const bool isZero = op->getKnownIntValue() == 0; @@ -2155,7 +2167,7 @@ bool isWithoutSideEffects(const Token* tok, bool checkArrayAccess, bool checkRef if (tok && tok->varId()) { const Variable* var = tok->variable(); return var && ((!var->isClass() && (checkReference || !var->isReference())) || var->isPointer() || - (checkArrayAccess ? var->isArray() || (var->isStlType() && !var->isStlType(CheckClass::stl_containers_not_const)) : var->isStlType())); + (checkArrayAccess ? var->isArray() || (var->isStlType() && !var->isStlType(CheckClassImpl::stl_containers_not_const)) : var->isStlType())); } return true; } @@ -2228,21 +2240,18 @@ static bool isEscapedOrJump(const Token* tok, bool functionsScope, const Library return Token::Match(tok, "return|goto|throw|continue|break"); } +static bool isNoreturnFunction(const Token* ftok, const Library& library) +{ + if (const Function* function = ftok->function()) + return function->isEscapeFunction() || function->isAttributeNoreturn(); + return library.isnoreturn(ftok); +} + bool isEscapeFunction(const Token* ftok, const Library& library) { if (!Token::Match(ftok, "%name% (")) return false; - const Function* function = ftok->function(); - if (function) { - if (function->isEscapeFunction()) - return true; - if (function->isAttributeNoreturn()) - return true; - } else { - if (library.isnoreturn(ftok)) - return true; - } - return false; + return isNoreturnFunction(ftok, library); } static bool hasNoreturnFunction(const Token* tok, const Library& library, const Token** unknownFunc) @@ -2253,18 +2262,9 @@ static bool hasNoreturnFunction(const Token* tok, const Library& library, const while (Token::simpleMatch(ftok, "(")) ftok = ftok->astOperand1(); if (ftok) { - const Function * function = ftok->function(); - if (function) { - if (function->isEscapeFunction()) - return true; - if (function->isAttributeNoreturn()) - return true; - } else if (library.isnoreturn(ftok)) { - return true; - } else if (Token::Match(ftok, "exit|abort")) { + if (isNoreturnFunction(ftok, library)) return true; - } - if (unknownFunc && !function && library.functions().count(library.getFunctionName(ftok)) == 0) + if (unknownFunc && !ftok->function() && library.functions().count(library.getFunctionName(ftok)) == 0) *unknownFunc = ftok; return false; } @@ -2336,14 +2336,14 @@ bool isWithinScope(const Token* tok, const Variable* var, ScopeType type) return false; } -bool isVariableChangedByFunctionCall(const Token *tok, int indirect, nonneg int varid, const Settings &settings, bool *inconclusive) +bool isVariableChangedByFunctionCall(const Token *tok, int indirect, nonneg int varid, const Library &library, bool *inconclusive) { if (!tok) return false; if (tok->varId() == varid) - return isVariableChangedByFunctionCall(tok, indirect, settings, inconclusive); - return isVariableChangedByFunctionCall(tok->astOperand1(), indirect, varid, settings, inconclusive) || - isVariableChangedByFunctionCall(tok->astOperand2(), indirect, varid, settings, inconclusive); + return isVariableChangedByFunctionCall(tok, indirect, library, inconclusive); + return isVariableChangedByFunctionCall(tok->astOperand1(), indirect, varid, library, inconclusive) || + isVariableChangedByFunctionCall(tok->astOperand2(), indirect, varid, library, inconclusive); } bool isScopeBracket(const Token* tok) @@ -2518,15 +2518,13 @@ bool isMutableExpression(const Token* tok) if (tok->astOperand1() && Token::simpleMatch(tok, "[")) return isMutableExpression(tok->astOperand1()); if (const Variable* var = tok->variable()) { - if (var->nameToken() == tok) - return false; if (var->isConst() && !var->isPointer() && (!var->isArray() || !var->isArgument())) return false; } return true; } -bool isVariableChangedByFunctionCall(const Token *tok, int indirect, const Settings &settings, bool *inconclusive) +bool isVariableChangedByFunctionCall(const Token *tok, int indirect, const Library &library, bool *inconclusive) { if (!tok) return false; @@ -2566,13 +2564,13 @@ bool isVariableChangedByFunctionCall(const Token *tok, int indirect, const Setti if (!tok->function() && !tok->variable() && tok->isName()) { // Check if direction (in, out, inout) is specified in the library configuration and use that - const Library::ArgumentChecks::Direction argDirection = settings.library.getArgDirection(tok, 1 + argnr, indirect); + const Library::ArgumentChecks::Direction argDirection = library.getArgDirection(tok, 1 + argnr, indirect); if (argDirection == Library::ArgumentChecks::Direction::DIR_IN) return false; if (argDirection == Library::ArgumentChecks::Direction::DIR_OUT || argDirection == Library::ArgumentChecks::Direction::DIR_INOUT) return true; - const bool requireNonNull = settings.library.isnullargbad(tok, 1 + argnr); + const bool requireNonNull = library.isnullargbad(tok, 1 + argnr); if (Token::simpleMatch(tok->tokAt(-2), "std :: tie")) return true; // if the library says 0 is invalid @@ -2592,6 +2590,8 @@ bool isVariableChangedByFunctionCall(const Token *tok, int indirect, const Setti if (const Variable* var = tok->variable()) { if (tok == var->nameToken() && (!var->isReference() || (var->isConst() && var->type() == tok1->type())) && (!var->isClass() || (var->valueType() && var->valueType()->container))) // const ref or passed to (copy) ctor return false; + if (var->isArray() && var->valueType() && var->valueType()->pointer == 0 && var->valueType()->isPrimitive()) + return false; } std::vector args = getArgumentVars(tok, argnr); @@ -2792,13 +2792,13 @@ bool isVariableChanged(const Token *tok, int indirect, const Settings &settings, if (ftok->str() == "(" && Token::simpleMatch(ftok->astOperand1(), "[")) // operator() on array element, bail out return true; const Token * ptok = tok2; - while (Token::Match(ptok->astParent(), ".|::|[")) + while (Token::Match(ptok->astParent(), ".|::")) ptok = ptok->astParent(); int pindirect = indirect; if (indirect == 0 && astIsLHS(tok2) && Token::Match(ptok, ". %var%") && astIsPointer(ptok->next())) pindirect = 1; bool inconclusive = false; - bool isChanged = isVariableChangedByFunctionCall(ptok, pindirect, settings, &inconclusive); + bool isChanged = isVariableChangedByFunctionCall(ptok, pindirect, settings.library, &inconclusive); isChanged |= inconclusive; if (isChanged) return true; @@ -3015,7 +3015,7 @@ bool isVariableChanged(const Variable * var, const Settings &settings, int depth const Token * start = var->declEndToken(); if (!start) return false; - if (Token::Match(start, "; %varid% =", var->declarationId())) + if (start->isSplittedVarDeclEq() && Token::Match(start, "; %varid% =", var->declarationId())) start = start->tokAt(2); if (Token::simpleMatch(start, "=")) { const Token* next = nextAfterAstRightmostLeafGeneric(start); @@ -3101,8 +3101,7 @@ static const Token* findExpressionChangedImpl(const Token* expr, } bool global = false; if (tok->variable()) { - global = !tok->variable()->isLocal() && !tok->variable()->isArgument() && - !(tok->variable()->isMember() && !tok->variable()->isStatic()); + global = !tok->variable()->isLocal() && !tok->variable()->isArgument(); } else if (tok->isIncompleteVar() && !tok->isIncompleteConstant()) { global = true; } @@ -3115,6 +3114,8 @@ static const Token* findExpressionChangedImpl(const Token* expr, if (vt->type == ValueType::ITERATOR) ++indirect; } + if (indirect == 0 && tok2->astParent() && tok2->astParent()->isUnaryOp("*")) + ++indirect; for (int i = 0; i <= indirect; ++i) { if (isExpressionChangedAt(tok, tok2, i, global, settings, depth)) return true; @@ -3141,7 +3142,7 @@ namespace { }; struct ExpressionChangedSkipDeadCode { - const Library& library; + RefThunk library; const std::function(const Token* tok)>* evaluate; ExpressionChangedSkipDeadCode(const Library& library, const std::function(const Token* tok)>& evaluate) @@ -3306,12 +3307,12 @@ static T* findLambdaEndTokenGeneric(T* first) return nullptr; if (!maybeLambda(first->previous())) return nullptr; - if (!Token::Match(first->link(), "] (|{|<")) + if (!Token::Match(first->link(), "] [({<.]")) return nullptr; const Token* roundOrCurly = first->link()->next(); if (roundOrCurly->link() && roundOrCurly->str() == "<") roundOrCurly = roundOrCurly->link()->next(); - if (first->astOperand1() != roundOrCurly) + if (first->astOperand1() != roundOrCurly && roundOrCurly->str() != ".") return nullptr; T * tok = first; @@ -3422,7 +3423,7 @@ bool isConstVarExpression(const Token *tok, const std::functionastParent() && tok->astParent()->isUnaryOp("&"); @@ -3484,14 +3485,14 @@ static ExprUsage getFunctionUsage(const Token* tok, int indirect, const Settings } else if (ftok->str() == "{") { return indirect == 0 ? ExprUsage::Used : ExprUsage::Inconclusive; } else { - const bool isnullbad = settings.library.isnullargbad(ftok, argnr + 1); + const bool isnullbad = library.isnullargbad(ftok, argnr + 1); if (indirect == 0 && astIsPointer(tok) && !addressOf && isnullbad) return ExprUsage::Used; bool hasIndirect = false; - const bool isuninitbad = settings.library.isuninitargbad(ftok, argnr + 1, indirect, &hasIndirect); + const bool isuninitbad = library.isuninitargbad(ftok, argnr + 1, indirect, &hasIndirect); if (isuninitbad && (!addressOf || isnullbad)) return ExprUsage::Used; - const Library::ArgumentChecks::Direction argDirection = settings.library.getArgDirection(ftok, argnr + 1, indirect); + const Library::ArgumentChecks::Direction argDirection = library.getArgDirection(ftok, argnr + 1, indirect); if (argDirection == Library::ArgumentChecks::Direction::DIR_IN) // TODO: DIR_INOUT? return ExprUsage::Used; if (argDirection == Library::ArgumentChecks::Direction::DIR_OUT) @@ -3561,7 +3562,7 @@ ExprUsage getExprUsage(const Token* tok, int indirect, const Settings& settings) (astIsLHS(tok) || Token::simpleMatch(parent, "( )"))) return ExprUsage::Used; } - return getFunctionUsage(tok, indirect, settings); + return getFunctionUsage(tok, indirect, settings.library); } static void getLHSVariablesRecursive(std::vector& vars, const Token* tok) @@ -3696,21 +3697,27 @@ bool isGlobalData(const Token *expr) globalData = true; return ChildrenToVisit::none; } - if (Token::Match(tok, "[*[]") && tok->astOperand1() && tok->astOperand1()->variable()) { + if (Token::Match(tok, "[*[]") && tok->astOperand1()) { // TODO check if pointer points at local data - const Variable *lhsvar = tok->astOperand1()->variable(); - const ValueType *lhstype = tok->astOperand1()->valueType(); - if (lhsvar->isPointer() || !lhstype || lhstype->type == ValueType::Type::ITERATOR) { - globalData = true; - return ChildrenToVisit::none; + const Token *lhs = tok->astOperand1(); + if (lhs->isCast()) { + lhs = lhs->astOperand2() ? lhs->astOperand2() : lhs->astOperand1(); } - if (lhsvar->isArgument() && lhsvar->isArray()) { - globalData = true; - return ChildrenToVisit::none; - } - if (lhsvar->isArgument() && lhstype->type <= ValueType::Type::VOID && !lhstype->container) { - globalData = true; - return ChildrenToVisit::none; + if (lhs && lhs->variable()) { + const Variable *lhsvar = lhs->variable(); + const ValueType *lhstype = lhs->valueType(); + if (lhsvar->isPointer() || !lhstype || lhstype->type == ValueType::Type::ITERATOR) { + globalData = true; + return ChildrenToVisit::none; + } + if (lhsvar->isArgument() && lhsvar->isArray()) { + globalData = true; + return ChildrenToVisit::none; + } + if (lhsvar->isArgument() && lhstype->type <= ValueType::Type::VOID && !lhstype->container) { + globalData = true; + return ChildrenToVisit::none; + } } } if (tok->varId() == 0 && tok->isName() && tok->strAt(-1) != ".") { @@ -3925,3 +3932,23 @@ const Token *skipUnreachableBranch(const Token *tok) return tok; } + +bool isEscapeKeyword(const Token *tok, const Settings &settings) +{ + if (!tok) + return false; + + if (tok->str() == "return") + return true; + + if (!tok->isCpp()) + return false; + + if (tok->str() == "throw") + return true; + + if (settings.standards.cpp < Standards::CPP20) + return false; + + return tok->str() == "co_return"; +} diff --git a/lib/astutils.h b/lib/astutils.h index 5b6b878f3ae..578763124ac 100644 --- a/lib/astutils.h +++ b/lib/astutils.h @@ -166,7 +166,7 @@ bool astIsContainerString(const Token* tok); Library::Container::Action astContainerAction(const Token* tok, const Library& library, const Token** ftok = nullptr); Library::Container::Yield astContainerYield(const Token* tok, const Library& library, const Token** ftok = nullptr); -Library::Container::Yield astFunctionYield(const Token* tok, const Settings& settings, const Token** ftok = nullptr); +Library::Container::Yield astFunctionYield(const Token* tok, const Library& library, const Token** ftok = nullptr); /** Is given token a range-declaration in a range-based for loop */ bool astIsRangeBasedForDecl(const Token* tok); @@ -327,20 +327,20 @@ bool isMutableExpression(const Token* tok); * * @param tok ast tree * @param varid Variable Id - * @param settings program settings + * @param library library configurations * @param inconclusive pointer to output variable which indicates that the answer of the question is inconclusive */ -bool isVariableChangedByFunctionCall(const Token *tok, int indirect, nonneg int varid, const Settings &settings, bool *inconclusive); +bool isVariableChangedByFunctionCall(const Token *tok, int indirect, nonneg int varid, const Library &library, bool *inconclusive); /** Is variable changed by function call? * In case the answer of the question is inconclusive, e.g. because the function declaration is not known * the return value is false and the output parameter inconclusive is set to true * - * @param tok token of variable in function call - * @param settings program settings + * @param tok token of variable in function call + * @param library library configurations * @param inconclusive pointer to output variable which indicates that the answer of the question is inconclusive */ -CPPCHECKLIB bool isVariableChangedByFunctionCall(const Token *tok, int indirect, const Settings &settings, bool *inconclusive); +CPPCHECKLIB bool isVariableChangedByFunctionCall(const Token *tok, int indirect, const Library &library, bool *inconclusive); /** Is variable changed in block of code? */ CPPCHECKLIB bool isVariableChanged(const Token *start, const Token *end, nonneg int exprid, bool globalvar, const Settings &settings, int depth = 20); @@ -386,6 +386,9 @@ bool isAliasOf(const Token *tok, nonneg int varid, bool* inconclusive = nullptr) bool isAliasOf(const Token* tok, const Token* expr, nonneg int* indirect = nullptr); +/// If token is an iterator into the container expression with the given expression id +bool isIteratorOf(const Token* tok, nonneg int exprId); + const Token* getArgumentStart(const Token* ftok); /** Determines the number of arguments - if token is a function call or macro @@ -463,4 +466,6 @@ bool isUnreachableOperand(const Token *tok); const Token *skipUnreachableBranch(const Token *tok); +bool isEscapeKeyword(const Token *tok, const Settings &settings); + #endif // astutilsH diff --git a/lib/check.cpp b/lib/check.cpp deleted file mode 100644 index 58b8dbfe0e1..00000000000 --- a/lib/check.cpp +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -//--------------------------------------------------------------------------- - -#include "check.h" - -#include "errorlogger.h" -#include "settings.h" -#include "token.h" -#include "tokenize.h" -#include "vfvalue.h" - -#include -#include -#include -#include -#include - -//--------------------------------------------------------------------------- - -Check::Check(const std::string &aname) - : mName(aname) -{ - { - const auto it = std::find_if(instances().begin(), instances().end(), [&](const Check *i) { - return i->name() == aname; - }); - if (it != instances().end()) - throw std::runtime_error("'" + aname + "' instance already exists"); - } - - // make sure the instances are sorted - const auto it = std::find_if(instances().begin(), instances().end(), [&](const Check* i) { - return i->name() > aname; - }); - if (it == instances().end()) - instances().push_back(this); - else - instances().insert(it, this); -} - -void Check::writeToErrorList(const ErrorMessage &errmsg) -{ - std::cout << errmsg.toXML() << std::endl; -} - - -void Check::reportError(const std::list &callstack, Severity severity, const std::string &id, const std::string &msg, const CWE &cwe, Certainty certainty) -{ - // TODO: report debug warning when error is for a disabled severity - const ErrorMessage errmsg(callstack, mTokenizer ? &mTokenizer->list : nullptr, severity, id, msg, cwe, certainty); - if (mErrorLogger) - mErrorLogger->reportErr(errmsg); - else - writeToErrorList(errmsg); -} - -void Check::reportError(ErrorPath errorPath, Severity severity, const char id[], const std::string &msg, const CWE &cwe, Certainty certainty) -{ - // TODO: report debug warning when error is for a disabled severity - const ErrorMessage errmsg(std::move(errorPath), mTokenizer ? &mTokenizer->list : nullptr, severity, id, msg, cwe, certainty); - if (mErrorLogger) - mErrorLogger->reportErr(errmsg); - else - writeToErrorList(errmsg); -} - -bool Check::wrongData(const Token *tok, const char *str) -{ - if (mSettings->daca) - reportError(tok, Severity::debug, "DacaWrongData", "Wrong data detected by condition " + std::string(str)); - return true; -} - -std::list &Check::instances() -{ -#ifdef __SVR4 - // Under Solaris, destructors are called in wrong order which causes a segmentation fault. - // This fix ensures pointer remains valid and reachable until program terminates. - static std::list *_instances= new std::list; - return *_instances; -#else - static std::list _instances; - return _instances; -#endif -} - -std::string Check::getMessageId(const ValueFlow::Value &value, const char id[]) -{ - if (value.condition != nullptr) - return id + std::string("Cond"); - if (value.safe) - return std::string("safe") + static_cast(std::toupper(id[0])) + (id + 1); - return id; -} - -ErrorPath Check::getErrorPath(const Token* errtok, const ValueFlow::Value* value, std::string bug) const -{ - ErrorPath errorPath; - if (!value) { - errorPath.emplace_back(errtok, std::move(bug)); - } else if (mSettings->verbose || mSettings->outputFormat == Settings::OutputFormat::xml || !mSettings->templateLocation.empty()) { - errorPath = value->errorPath; - errorPath.emplace_back(errtok, std::move(bug)); - } else { - if (value->condition) - errorPath.emplace_back(value->condition, "condition '" + value->condition->expressionString() + "'"); - //else if (!value->isKnown() || value->defaultArg) - // errorPath = value->callstack; - errorPath.emplace_back(errtok, std::move(bug)); - } - return errorPath; -} - -void Check::logChecker(const char id[]) -{ - reportError(nullptr, Severity::internal, "logChecker", id); -} - diff --git a/lib/check.h b/lib/check.h index 4e3c5b9a4e3..7adde886f7f 100644 --- a/lib/check.h +++ b/lib/check.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,7 +22,6 @@ //--------------------------------------------------------------------------- #include "config.h" -#include "errortypes.h" #include #include @@ -36,19 +35,10 @@ namespace CTU { class FileInfo; } -namespace ValueFlow { - class Value; -} - class Settings; -class Token; class ErrorLogger; -class ErrorMessage; class Tokenizer; -/** Use WRONG_DATA in checkers to mark conditions that check that data is correct */ -#define WRONG_DATA(COND, TOK) ((COND) && wrongData((TOK), #COND)) - /// @addtogroup Core /// @{ @@ -57,32 +47,22 @@ class Tokenizer; * All checking classes must inherit from this class */ class CPPCHECKLIB Check { -public: - /** This constructor is used when registering the CheckClass */ - explicit Check(const std::string &aname); - protected: - /** This constructor is used when running checks. */ - Check(std::string aname, const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) - : mTokenizer(tokenizer), mSettings(settings), mErrorLogger(errorLogger), mName(std::move(aname)) {} + /** This constructor is used when registering the check */ + explicit Check(std::string aname) + : mName(std::move(aname)) + {} + virtual ~Check() = default; public: - virtual ~Check() { - if (!mTokenizer) - instances().remove(this); - } - Check(const Check &) = delete; Check& operator=(const Check &) = delete; - /** List of registered check classes. This is used by Cppcheck to run checks and generate documentation */ - static std::list &instances(); - /** run checks, the token list is not simplified */ - virtual void runChecks(const Tokenizer &, ErrorLogger *) = 0; + virtual void runChecks(const Tokenizer &, ErrorLogger&) = 0; /** get error messages */ - virtual void getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const = 0; + virtual void getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const = 0; /** class name, used to generate documentation */ const std::string& name() const { @@ -92,72 +72,30 @@ class CPPCHECKLIB Check { /** get information about this class, used to generate documentation */ virtual std::string classInfo() const = 0; - /** - * Write given error to stdout in xml format. - * This is for for printout out the error list with --errorlist - * @param errmsg Error message to write - */ - static void writeToErrorList(const ErrorMessage &errmsg); - /** Base class used for whole-program analysis */ class CPPCHECKLIB FileInfo { public: - explicit FileInfo(std::string f0 = {}) : file0(std::move(f0)) {} + explicit FileInfo(std::string f0) : file0(std::move(f0)) {} virtual ~FileInfo() = default; - virtual std::string toString() const { - return std::string(); - } + virtual std::string toString() const = 0; std::string file0; }; - virtual FileInfo * getFileInfo(const Tokenizer& /*tokenizer*/, const Settings& /*settings*/, const std::string& /*currentConfig*/) const { + virtual const FileInfo * getFileInfo(const Tokenizer& /*tokenizer*/, const Settings& /*settings*/, const std::string& /*currentConfig*/) const { return nullptr; } - virtual FileInfo * loadFileInfoFromXml(const tinyxml2::XMLElement *xmlElement) const { + virtual const FileInfo * loadFileInfoFromXml(const tinyxml2::XMLElement *xmlElement, const std::string& file0) const { (void)xmlElement; + (void)file0; return nullptr; } // Return true if an error is reported. - virtual bool analyseWholeProgram(const CTU::FileInfo& /*ctu*/, const std::list& /*fileInfo*/, const Settings& /*settings*/, ErrorLogger & /*errorLogger*/) { + virtual bool analyseWholeProgram(const CTU::FileInfo& /*ctu*/, const std::list& /*fileInfo*/, const Settings& /*settings*/, ErrorLogger & /*errorLogger*/) { return false; } -protected: - static std::string getMessageId(const ValueFlow::Value &value, const char id[]); - - const Tokenizer* const mTokenizer{}; - const Settings* const mSettings{}; - ErrorLogger* const mErrorLogger{}; - - /** report an error */ - void reportError(const Token *tok, const Severity severity, const std::string &id, const std::string &msg) { - reportError(tok, severity, id, msg, CWE(0U), Certainty::normal); - } - - /** report an error */ - void reportError(const Token *tok, const Severity severity, const std::string &id, const std::string &msg, const CWE &cwe, Certainty certainty) { - const std::list callstack(1, tok); - reportError(callstack, severity, id, msg, cwe, certainty); - } - - /** report an error */ - void reportError(const std::list &callstack, Severity severity, const std::string &id, const std::string &msg, const CWE &cwe, Certainty certainty); - - void reportError(ErrorPath errorPath, Severity severity, const char id[], const std::string &msg, const CWE &cwe, Certainty certainty); - - /** log checker */ - void logChecker(const char id[]); - - ErrorPath getErrorPath(const Token* errtok, const ValueFlow::Value* value, std::string bug) const; - - /** - * Use WRONG_DATA in checkers when you check for wrong data. That - * will call this method - */ - bool wrongData(const Token *tok, const char *str); - private: const std::string mName; }; diff --git a/lib/check64bit.cpp b/lib/check64bit.cpp index d203eb5f279..174237eef5a 100644 --- a/lib/check64bit.cpp +++ b/lib/check64bit.cpp @@ -36,17 +36,12 @@ // CWE ids used static const CWE CWE758(758U); // Reliance on Undefined, Unspecified, or Implementation-Defined Behavior -// Register this check class (by creating a static instance of it) -namespace { - Check64BitPortability instance; -} - -static bool is32BitIntegerReturn(const Function* func, const Settings* settings) +static bool is32BitIntegerReturn(const Function* func, const Settings& settings) { - if (settings->platform.sizeof_pointer != 8) + if (settings.platform.sizeof_pointer != 8) return false; const ValueType* vt = func->arg->valueType(); - return vt && vt->pointer == 0 && vt->isIntegral() && vt->getSizeOf(*settings, ValueType::Accuracy::ExactOrZero, ValueType::SizeOf::Pointer) == 4; + return vt && vt->pointer == 0 && vt->isIntegral() && vt->getSizeOf(settings, ValueType::Accuracy::ExactOrZero, ValueType::SizeOf::Pointer) == 4; } static bool isFunctionPointer(const Token* tok) @@ -56,9 +51,9 @@ static bool isFunctionPointer(const Token* tok) return Tokenizer::isFunctionPointer(tok->variable()->nameToken()); } -void Check64BitPortability::pointerassignment() +void Check64BitPortabilityImpl::pointerassignment() { - if (!mSettings->severity.isEnabled(Severity::portability)) + if (!mSettings.severity.isEnabled(Severity::portability)) return; logChecker("Check64BitPortability::pointerassignment"); // portability @@ -95,11 +90,11 @@ void Check64BitPortability::pointerassignment() if (!returnType) continue; - if (retPointer && !returnType->typeScope && returnType->pointer == 0U) + if (retPointer && !returnType->typeScope && returnType->pointer == 0) returnIntegerError(tok); if (!retPointer) { - bool warn = returnType->pointer >= 1U; + bool warn = returnType->pointer >= 1; if (!warn) { const Token* tok2 = tok->astOperand1(); while (tok2 && tok2->isCast()) @@ -124,17 +119,17 @@ void Check64BitPortability::pointerassignment() continue; // Assign integer to pointer.. - if (lhstype->pointer >= 1U && + if (lhstype->pointer >= 1 && !tok->astOperand2()->isNumber() && - rhstype->pointer == 0U && + rhstype->pointer == 0 && rhstype->originalTypeName.empty() && rhstype->type == ValueType::Type::INT && !isFunctionPointer(tok->astOperand1())) assignmentIntegerToAddressError(tok); // Assign pointer to integer.. - if (rhstype->pointer >= 1U && - lhstype->pointer == 0U && + if (rhstype->pointer >= 1 && + lhstype->pointer == 0 && lhstype->originalTypeName.empty() && lhstype->isIntegral() && lhstype->type >= ValueType::Type::CHAR && @@ -144,7 +139,7 @@ void Check64BitPortability::pointerassignment() } } -void Check64BitPortability::assignmentAddressToIntegerError(const Token *tok) +void Check64BitPortabilityImpl::assignmentAddressToIntegerError(const Token *tok) { reportError(tok, Severity::portability, "AssignmentAddressToInteger", @@ -155,7 +150,7 @@ void Check64BitPortability::assignmentAddressToIntegerError(const Token *tok) "way is to store addresses only in pointer types (or typedefs like uintptr_t).", CWE758, Certainty::normal); } -void Check64BitPortability::assignmentIntegerToAddressError(const Token *tok) +void Check64BitPortabilityImpl::assignmentIntegerToAddressError(const Token *tok) { reportError(tok, Severity::portability, "AssignmentIntegerToAddress", @@ -166,7 +161,7 @@ void Check64BitPortability::assignmentIntegerToAddressError(const Token *tok) "way is to store addresses only in pointer types (or typedefs like uintptr_t).", CWE758, Certainty::normal); } -void Check64BitPortability::returnPointerError(const Token *tok) +void Check64BitPortabilityImpl::returnPointerError(const Token *tok) { reportError(tok, Severity::portability, "CastAddressToIntegerAtReturn", @@ -177,7 +172,7 @@ void Check64BitPortability::returnPointerError(const Token *tok) "to 32-bit integer. The safe way is to return a type such as intptr_t.", CWE758, Certainty::normal); } -void Check64BitPortability::returnIntegerError(const Token *tok) +void Check64BitPortabilityImpl::returnIntegerError(const Token *tok) { reportError(tok, Severity::portability, "CastIntegerToAddressAtReturn", @@ -188,15 +183,15 @@ void Check64BitPortability::returnIntegerError(const Token *tok) "The safe way is to always return a pointer.", CWE758, Certainty::normal); } -void Check64BitPortability::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) +void Check64BitPortability::runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) { - Check64BitPortability check64BitPortability(&tokenizer, &tokenizer.getSettings(), errorLogger); + Check64BitPortabilityImpl check64BitPortability(&tokenizer, tokenizer.getSettings(), errorLogger); check64BitPortability.pointerassignment(); } -void Check64BitPortability::getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const +void Check64BitPortability::getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const { - Check64BitPortability c(nullptr, settings, errorLogger); + Check64BitPortabilityImpl c(nullptr, settings, errorLogger); c.assignmentAddressToIntegerError(nullptr); c.assignmentIntegerToAddressError(nullptr); c.returnIntegerError(nullptr); diff --git a/lib/check64bit.h b/lib/check64bit.h index 79397e98e5f..cab82cf1148 100644 --- a/lib/check64bit.h +++ b/lib/check64bit.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,6 +23,7 @@ //--------------------------------------------------------------------------- #include "check.h" +#include "checkimpl.h" #include "config.h" #include @@ -40,19 +41,28 @@ class Tokenizer; */ class CPPCHECKLIB Check64BitPortability : public Check { - friend class Test64BitPortability; - public: /** This constructor is used when registering the Check64BitPortability */ - Check64BitPortability() : Check(myName()) {} + Check64BitPortability() : Check("64-bit portability") {} private: - /** This constructor is used when running checks. */ - Check64BitPortability(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) - : Check(myName(), tokenizer, settings, errorLogger) {} - /** @brief Run checks against the normal token list */ - void runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) override; + void runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) override; + + void getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const override; + + std::string classInfo() const override { + return "Check if there is 64-bit portability issues:\n" + "- assign address to/from int/long\n" + "- casting address from/to integer when returning from function\n"; + } +}; + +class CPPCHECKLIB Check64BitPortabilityImpl : public CheckImpl { +public: + /** This constructor is used when running checks. */ + Check64BitPortabilityImpl(const Tokenizer *tokenizer, const Settings &settings, ErrorLogger &errorLogger) + : CheckImpl(tokenizer, settings, errorLogger) {} /** Check for pointer assignment */ void pointerassignment(); @@ -61,18 +71,6 @@ class CPPCHECKLIB Check64BitPortability : public Check { void assignmentIntegerToAddressError(const Token *tok); void returnIntegerError(const Token *tok); void returnPointerError(const Token *tok); - - void getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const override; - - static std::string myName() { - return "64-bit portability"; - } - - std::string classInfo() const override { - return "Check if there is 64-bit portability issues:\n" - "- assign address to/from int/long\n" - "- casting address from/to integer when returning from function\n"; - } }; /// @} //--------------------------------------------------------------------------- diff --git a/lib/checkassert.cpp b/lib/checkassert.cpp index 3ab55967ede..00bb95e319a 100644 --- a/lib/checkassert.cpp +++ b/lib/checkassert.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -39,14 +39,9 @@ // CWE ids used static const CWE CWE398(398U); // Indicator of Poor Code Quality -// Register this check class (by creating a static instance of it) -namespace { - CheckAssert instance; -} - -void CheckAssert::assertWithSideEffects() +void CheckAssertImpl::assertWithSideEffects() { - if (!mSettings->severity.isEnabled(Severity::warning)) + if (!mSettings.severity.isEnabled(Severity::warning)) return; logChecker("CheckAssert::assertWithSideEffects"); // warning @@ -63,7 +58,7 @@ void CheckAssert::assertWithSideEffects() checkVariableAssignment(tmp, tok->scope()); if (tmp->tokType() != Token::eFunction) { - if (const Library::Function* f = mSettings->library.getFunction(tmp)) { + if (const Library::Function* f = mSettings.library.getFunction(tmp)) { if (f->isconst || f->ispure) continue; if (Library::getContainerYield(tmp->next()) != Library::Container::Yield::NO_YIELD) // bailout, assume read access @@ -78,17 +73,22 @@ void CheckAssert::assertWithSideEffects() f->containerYield == Library::Container::Yield::END_ITERATOR || f->containerYield == Library::Container::Yield::ITERATOR) continue; - sideEffectInAssertError(tmp, mSettings->library.getFunctionName(tmp)); + sideEffectInAssertError(tmp, mSettings.library.getFunctionName(tmp)); } continue; } + const Token* parent = tmp->astParent(); + while (Token::Match(parent, ".|::")) + parent = parent->astParent(); + if (!Token::simpleMatch(parent, "(")) + continue; const Function* f = tmp->function(); const Scope* scope = f->functionScope; if (!scope) { // guess that const method doesn't have side effects if (f->nestedIn->isClassOrStruct() && !f->isConst() && !f->isStatic()) - sideEffectInAssertError(tmp, f->name()); // Non-const member function called, assume it has side effects + sideEffectInAssertError(tmp, f->name(), " If there are no side effects, consider declaring the method const."); // Non-const member function called, assume it has side effects continue; } @@ -122,19 +122,19 @@ void CheckAssert::assertWithSideEffects() //--------------------------------------------------------------------------- -void CheckAssert::sideEffectInAssertError(const Token *tok, const std::string& functionName) +void CheckAssertImpl::sideEffectInAssertError(const Token *tok, const std::string& functionName, const std::string &extra) { reportError(tok, Severity::warning, "assertWithSideEffect", "$symbol:" + functionName + "\n" - "Assert statement calls a function which may have desired side effects: '$symbol'.\n" + "Assert statement calls a function which may have desired side effects: '$symbol'." + extra + "\n" "Non-pure function: '$symbol' is called inside assert statement. " "Assert statements are removed from release builds so the code inside " "assert statement is not executed. If the code is needed also in release " "builds, this is a bug.", CWE398, Certainty::normal); } -void CheckAssert::assignmentInAssertError(const Token *tok, const std::string& varname) +void CheckAssertImpl::assignmentInAssertError(const Token *tok, const std::string& varname) { reportError(tok, Severity::warning, "assignmentInAssert", @@ -147,7 +147,7 @@ void CheckAssert::assignmentInAssertError(const Token *tok, const std::string& v } // checks if side effects happen on the variable prior to tmp -void CheckAssert::checkVariableAssignment(const Token* assignTok, const Scope *assertionScope) +void CheckAssertImpl::checkVariableAssignment(const Token* assignTok, const Scope *assertionScope) { if (!assignTok->isAssignmentOp() && assignTok->tokType() != Token::eIncDecOp) return; @@ -177,21 +177,21 @@ void CheckAssert::checkVariableAssignment(const Token* assignTok, const Scope *a // TODO: function calls on var } -bool CheckAssert::inSameScope(const Token* returnTok, const Token* assignTok) +bool CheckAssertImpl::inSameScope(const Token* returnTok, const Token* assignTok) { // TODO: even if a return is in the same scope, the assignment might not affect it. return returnTok->scope() == assignTok->scope(); } -void CheckAssert::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) +void CheckAssert::runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) { - CheckAssert checkAssert(&tokenizer, &tokenizer.getSettings(), errorLogger); + CheckAssertImpl checkAssert(&tokenizer, tokenizer.getSettings(), errorLogger); checkAssert.assertWithSideEffects(); } -void CheckAssert::getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const +void CheckAssert::getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const { - CheckAssert c(nullptr, settings, errorLogger); + CheckAssertImpl c(nullptr, settings, errorLogger); c.sideEffectInAssertError(nullptr, "function"); c.assignmentInAssertError(nullptr, "var"); } diff --git a/lib/checkassert.h b/lib/checkassert.h index b01831a5eec..15f97fb1746 100644 --- a/lib/checkassert.h +++ b/lib/checkassert.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,6 +23,7 @@ //--------------------------------------------------------------------------- #include "check.h" +#include "checkimpl.h" #include "config.h" #include @@ -42,32 +43,30 @@ class Tokenizer; class CPPCHECKLIB CheckAssert : public Check { public: - CheckAssert() : Check(myName()) {} + CheckAssert() : Check("Assert") {} private: - CheckAssert(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) - : Check(myName(), tokenizer, settings, errorLogger) {} - /** run checks, the token list is not simplified */ - void runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) override; + void runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) override; + void getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const override; + + std::string classInfo() const override { + return "Warn if there are side effects in assert statements (since this cause different behaviour in debug/release builds).\n"; + } +}; + +class CPPCHECKLIB CheckAssertImpl : public CheckImpl { +public: + CheckAssertImpl(const Tokenizer *tokenizer, const Settings &settings, ErrorLogger &errorLogger) + : CheckImpl(tokenizer, settings, errorLogger) {} void assertWithSideEffects(); void checkVariableAssignment(const Token* assignTok, const Scope *assertionScope); static bool inSameScope(const Token* returnTok, const Token* assignTok); - void sideEffectInAssertError(const Token *tok, const std::string& functionName); + void sideEffectInAssertError(const Token *tok, const std::string& functionName, const std::string &extra = ""); void assignmentInAssertError(const Token *tok, const std::string &varname); - - void getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const override; - - static std::string myName() { - return "Assert"; - } - - std::string classInfo() const override { - return "Warn if there are side effects in assert statements (since this cause different behaviour in debug/release builds).\n"; - } }; /// @} //--------------------------------------------------------------------------- diff --git a/lib/checkautovariables.cpp b/lib/checkautovariables.cpp index e778f3d5959..de70772a279 100644 --- a/lib/checkautovariables.cpp +++ b/lib/checkautovariables.cpp @@ -39,12 +39,6 @@ //--------------------------------------------------------------------------- - -// Register this check class into cppcheck by creating a static instance of it.. -namespace { - CheckAutoVariables instance; -} - static const CWE CWE398(398U); // Indicator of Poor Code Quality static const CWE CWE562(562U); // Return of Stack Variable Address static const CWE CWE590(590U); // Free of Memory not on the Heap @@ -145,14 +139,14 @@ static bool isAutoVarArray(const Token *tok) return false; } -static bool isLocalContainerBuffer(const Token* tok, const Settings& settings) +static bool isLocalContainerBuffer(const Token* tok, const Library& library) { if (!tok) return false; // x+y if (tok->str() == "+") - return isLocalContainerBuffer(tok->astOperand1(), settings) || isLocalContainerBuffer(tok->astOperand2(), settings); + return isLocalContainerBuffer(tok->astOperand1(), library) || isLocalContainerBuffer(tok->astOperand2(), library); if (tok->str() != "(" || !Token::simpleMatch(tok->astOperand1(), ".")) return false; @@ -163,7 +157,7 @@ static bool isLocalContainerBuffer(const Token* tok, const Settings& settings) if (!var || !var->isLocal() || var->isStatic()) return false; - const Library::Container::Yield yield = astContainerYield(tok, settings.library); + const Library::Container::Yield yield = astContainerYield(tok, library); return yield == Library::Container::Yield::BUFFER || yield == Library::Container::Yield::BUFFER_NT; } @@ -208,11 +202,11 @@ static bool variableIsUsedInScope(const Token* start, nonneg int varId, const Sc return false; } -void CheckAutoVariables::assignFunctionArg() +void CheckAutoVariablesImpl::assignFunctionArg() { - const bool printStyle = mSettings->severity.isEnabled(Severity::style); - const bool printWarning = mSettings->severity.isEnabled(Severity::warning); - if (!printStyle && !printWarning && !mSettings->isPremiumEnabled("uselessAssignmentPtrArg")) + const bool printStyle = mSettings.severity.isEnabled(Severity::style); + const bool printWarning = mSettings.severity.isEnabled(Severity::warning); + if (!printStyle && !printWarning && !mSettings.isPremiumEnabled("uselessAssignmentPtrArg")) return; logChecker("CheckAutoVariables::assignFunctionArg"); // style,warning @@ -239,8 +233,8 @@ void CheckAutoVariables::assignFunctionArg() } } -static bool isAutoVariableRHS(const Token* tok, const Settings& settings) { - return isAddressOfLocalVariable(tok) || isAutoVarArray(tok) || isLocalContainerBuffer(tok, settings); +static bool isAutoVariableRHS(const Token* tok, const Library& library) { + return isAddressOfLocalVariable(tok) || isAutoVarArray(tok) || isLocalContainerBuffer(tok, library); } static bool hasOverloadedAssignment(const Token* tok, bool& inconclusive) @@ -263,7 +257,7 @@ static bool hasOverloadedAssignment(const Token* tok, bool& inconclusive) return true; } -static bool isMemberAssignment(const Token* tok, const Token*& rhs, const Settings& settings) +static bool isMemberAssignment(const Token* tok, const Token*& rhs, const Library& library) { const Token *endBracket = nullptr; if (!Token::Match(tok, "[;{}] %var% . %var%")) { @@ -280,17 +274,17 @@ static bool isMemberAssignment(const Token* tok, const Token*& rhs, const Settin assign = assign->astParent(); if (!Token::simpleMatch(assign, "=")) return false; - if (!isAutoVariableRHS(assign->astOperand2(), settings)) + if (!isAutoVariableRHS(assign->astOperand2(), library)) return false; rhs = assign->astOperand2(); return true; } -void CheckAutoVariables::autoVariables() +void CheckAutoVariablesImpl::autoVariables() { logChecker("CheckAutoVariables::autoVariables"); - const bool printInconclusive = mSettings->certainty.isEnabled(Certainty::inconclusive); + const bool printInconclusive = mSettings.certainty.isEnabled(Certainty::inconclusive); const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); for (const Scope * scope : symbolDatabase->functionScopes) { for (const Token *tok = scope->bodyStart; tok && tok != scope->bodyEnd; tok = tok->next()) { @@ -301,27 +295,27 @@ void CheckAutoVariables::autoVariables() } // Critical assignment const Token* rhs{}; - if (Token::Match(tok, "[;{}] %var% =") && isRefPtrArg(tok->next()) && isAutoVariableRHS(tok->tokAt(2)->astOperand2(), *mSettings)) { + if (Token::Match(tok, "[;{}] %var% =") && isRefPtrArg(tok->next()) && isAutoVariableRHS(tok->tokAt(2)->astOperand2(), mSettings.library)) { checkAutoVariableAssignment(tok->next(), false); - } else if (Token::Match(tok, "[;{}] * %var% =") && isPtrArg(tok->tokAt(2)) && isAutoVariableRHS(tok->tokAt(3)->astOperand2(), *mSettings)) { + } else if (Token::Match(tok, "[;{}] * %var% =") && isPtrArg(tok->tokAt(2)) && isAutoVariableRHS(tok->tokAt(3)->astOperand2(), mSettings.library)) { const Token* lhs = tok->tokAt(2); bool inconclusive = false; if (!hasOverloadedAssignment(lhs, inconclusive) || (printInconclusive && inconclusive)) checkAutoVariableAssignment(tok->next(), inconclusive); tok = tok->tokAt(4); - } else if (isMemberAssignment(tok, rhs, *mSettings)) { + } else if (isMemberAssignment(tok, rhs, mSettings.library)) { const Token* lhs = tok->tokAt(3); bool inconclusive = false; if (!hasOverloadedAssignment(lhs, inconclusive) || (printInconclusive && inconclusive)) checkAutoVariableAssignment(tok->next(), inconclusive); tok = rhs; } else if (Token::Match(tok, "[;{}] %var% [") && Token::simpleMatch(tok->linkAt(2), "] =") && - (isPtrArg(tok->next()) || isArrayArg(tok->next(), *mSettings)) && - isAutoVariableRHS(tok->linkAt(2)->next()->astOperand2(), *mSettings)) { + (isPtrArg(tok->next()) || isArrayArg(tok->next(), mSettings)) && + isAutoVariableRHS(tok->linkAt(2)->next()->astOperand2(), mSettings.library)) { errorAutoVariableAssignment(tok->next(), false); } // Invalid pointer deallocation - else if ((Token::Match(tok, "%name% ( %var%|%str% ) ;") && mSettings->library.getDeallocFuncInfo(tok)) || + else if ((Token::Match(tok, "%name% ( %var%|%str% ) ;") && mSettings.library.getDeallocFuncInfo(tok)) || (tok->isCpp() && Token::Match(tok, "delete [| ]| (| %var%|%str% !!["))) { tok = Token::findmatch(tok->next(), "%var%|%str%"); if (Token::simpleMatch(tok->astParent(), ".")) @@ -339,7 +333,7 @@ void CheckAutoVariables::autoVariables() } } } - } else if ((Token::Match(tok, "%name% ( & %var% ) ;") && mSettings->library.getDeallocFuncInfo(tok)) || + } else if ((Token::Match(tok, "%name% ( & %var% ) ;") && mSettings.library.getDeallocFuncInfo(tok)) || (tok->isCpp() && Token::Match(tok, "delete [| ]| (| & %var% !!["))) { tok = Token::findmatch(tok->next(), "%var%"); if (isAutoVar(tok)) @@ -349,7 +343,7 @@ void CheckAutoVariables::autoVariables() } } -bool CheckAutoVariables::checkAutoVariableAssignment(const Token *expr, bool inconclusive, const Token *startToken) +bool CheckAutoVariablesImpl::checkAutoVariableAssignment(const Token *expr, bool inconclusive, const Token *startToken) { if (!startToken) startToken = Token::findsimplematch(expr, "=")->next(); @@ -392,7 +386,7 @@ bool CheckAutoVariables::checkAutoVariableAssignment(const Token *expr, bool inc //--------------------------------------------------------------------------- -void CheckAutoVariables::errorAutoVariableAssignment(const Token *tok, bool inconclusive) +void CheckAutoVariablesImpl::errorAutoVariableAssignment(const Token *tok, bool inconclusive) { diag(tok); @@ -415,7 +409,7 @@ void CheckAutoVariables::errorAutoVariableAssignment(const Token *tok, bool inco } } -void CheckAutoVariables::errorUselessAssignmentArg(const Token *tok) +void CheckAutoVariablesImpl::errorUselessAssignmentArg(const Token *tok) { reportError(tok, Severity::style, @@ -423,7 +417,7 @@ void CheckAutoVariables::errorUselessAssignmentArg(const Token *tok) "Assignment of function parameter has no effect outside the function.", CWE398, Certainty::normal); } -void CheckAutoVariables::errorUselessAssignmentPtrArg(const Token *tok) +void CheckAutoVariablesImpl::errorUselessAssignmentPtrArg(const Token *tok) { reportError(tok, Severity::warning, @@ -431,7 +425,7 @@ void CheckAutoVariables::errorUselessAssignmentPtrArg(const Token *tok) "Assignment of function parameter has no effect outside the function. Did you forget dereferencing it?", CWE398, Certainty::normal); } -bool CheckAutoVariables::diag(const Token* tokvalue) +bool CheckAutoVariablesImpl::diag(const Token* tokvalue) { if (!tokvalue) return true; @@ -568,9 +562,9 @@ static bool isAssignedToNonLocal(const Token* tok) return !var->isLocal() || var->isStatic(); } -void CheckAutoVariables::checkVarLifetimeScope(const Token * start, const Token * end) +void CheckAutoVariablesImpl::checkVarLifetimeScope(const Token * start, const Token * end) { - const bool printInconclusive = mSettings->certainty.isEnabled(Certainty::inconclusive); + const bool printInconclusive = mSettings.certainty.isEnabled(Certainty::inconclusive); if (!start) return; const Scope * scope = start->scope(); @@ -583,7 +577,7 @@ void CheckAutoVariables::checkVarLifetimeScope(const Token * start, const Token for (const Token *tok = start; tok && tok != end; tok = tok->next()) { // Return reference from function if (returnRef && Token::simpleMatch(tok->astParent(), "return")) { - for (const ValueFlow::LifetimeToken& lt : ValueFlow::getLifetimeTokens(tok, *mSettings, true)) { + for (const ValueFlow::LifetimeToken& lt : ValueFlow::getLifetimeTokens(tok, mSettings, true)) { if (!printInconclusive && lt.inconclusive) continue; const Variable* var = lt.token->variable(); @@ -592,7 +586,7 @@ void CheckAutoVariables::checkVarLifetimeScope(const Token * start, const Token errorReturnReference(tok, lt.errorPath, lt.inconclusive); break; } - if (isDeadTemporary(lt.token, nullptr, mSettings->library)) { + if (isDeadTemporary(lt.token, nullptr, mSettings.library)) { errorReturnTempReference(tok, lt.errorPath, lt.inconclusive); break; } @@ -603,48 +597,51 @@ void CheckAutoVariables::checkVarLifetimeScope(const Token * start, const Token tok->variable()->declarationId() == tok->varId() && tok->variable()->isStatic() && !tok->variable()->isArgument()) { ErrorPath errorPath; - const Variable *var = ValueFlow::getLifetimeVariable(tok, errorPath, *mSettings); + const Variable *var = ValueFlow::getLifetimeVariable(tok, errorPath, mSettings); if (var && isInScope(var->nameToken(), tok->scope())) { errorDanglingReference(tok, var, std::move(errorPath)); continue; } // Reference to temporary } else if (tok->variable() && (tok->variable()->isReference() || tok->variable()->isRValueReference())) { - for (const ValueFlow::LifetimeToken& lt : ValueFlow::getLifetimeTokens(getParentLifetime(tok), *mSettings)) { + for (const ValueFlow::LifetimeToken& lt : ValueFlow::getLifetimeTokens(getParentLifetime(tok), mSettings)) { if (!printInconclusive && lt.inconclusive) continue; const Token * tokvalue = lt.token; - if (isDeadTemporary(tokvalue, tok, mSettings->library)) { + if (isDeadTemporary(tokvalue, tok, mSettings.library)) { errorDanglingTempReference(tok, lt.errorPath, lt.inconclusive); break; } } } - const bool escape = Token::simpleMatch(tok->astParent(), "throw") || - (Token::simpleMatch(tok->astParent(), "return") && !Function::returnsStandardType(scope->function)); + const Token* retThrow = tok->astParent(); + if (Token::simpleMatch(retThrow, "new")) + retThrow = retThrow->astParent(); + const bool escape = Token::simpleMatch(retThrow, "throw") || + (Token::simpleMatch(retThrow, "return") && !Function::returnsStandardType(scope->function)); std::unordered_set exprs; for (const ValueFlow::Value& val:tok->values()) { if (!val.isLocalLifetimeValue() && !val.isSubFunctionLifetimeValue()) continue; if (!printInconclusive && val.isInconclusive()) continue; - const Token* parent = getParentLifetime(val.tokvalue, mSettings->library); + const Token* parent = getParentLifetime(val.tokvalue, mSettings.library); if (!exprs.insert(parent).second) continue; - for (const ValueFlow::LifetimeToken& lt : ValueFlow::getLifetimeTokens(parent, *mSettings, escape || isAssignedToNonLocal(tok))) { + for (const ValueFlow::LifetimeToken& lt : ValueFlow::getLifetimeTokens(parent, mSettings, escape || isAssignedToNonLocal(tok))) { const Token * tokvalue = lt.token; if (val.isLocalLifetimeValue()) { if (escape) { if (getPointerDepth(tok) < getPointerDepth(tokvalue)) continue; - if (!ValueFlow::isLifetimeBorrowed(tok, *mSettings)) + if (!ValueFlow::isLifetimeBorrowed(tok, mSettings)) continue; if (tokvalue->exprId() == tok->exprId() && !(tok->variable() && tok->variable()->isArray()) && !astIsContainerView(tok->astParent())) continue; if ((tokvalue->variable() && !isEscapedReference(tokvalue->variable()) && isInScope(tokvalue->variable()->nameToken(), scope)) || - isDeadTemporary(tokvalue, nullptr, mSettings->library)) { + isDeadTemporary(tokvalue, nullptr, mSettings.library)) { if (!diag(tokvalue)) errorReturnDanglingLifetime(tok, &val); break; @@ -653,7 +650,7 @@ void CheckAutoVariables::checkVarLifetimeScope(const Token * start, const Token errorInvalidLifetime(tok, &val); break; } else if (!tokvalue->variable() && - isDeadTemporary(tokvalue, tok, mSettings->library)) { + isDeadTemporary(tokvalue, tok, mSettings.library)) { if (!diag(tokvalue)) errorDanglingTemporaryLifetime(tok, &val, tokvalue); break; @@ -671,7 +668,7 @@ void CheckAutoVariables::checkVarLifetimeScope(const Token * start, const Token } else if (tok->variable() && tok->variable()->declarationId() == tok->varId()) { var = tok->variable(); } - if (!ValueFlow::isLifetimeBorrowed(tok, *mSettings)) + if (!ValueFlow::isLifetimeBorrowed(tok, mSettings)) continue; const Token* nextTok = nextAfterAstRightmostLeaf(tok->astTop()); if (!nextTok) @@ -683,7 +680,7 @@ void CheckAutoVariables::checkVarLifetimeScope(const Token * start, const Token var->valueType() ? var->valueType()->pointer : 0, var->declarationId(), var->isGlobal(), - *mSettings)) { + mSettings)) { if (!diag(tok2)) errorDanglngLifetime(tok2, &val, var->isLocal()); break; @@ -711,7 +708,7 @@ void CheckAutoVariables::checkVarLifetimeScope(const Token * start, const Token } } -void CheckAutoVariables::checkVarLifetime() +void CheckAutoVariablesImpl::checkVarLifetime() { logChecker("CheckAutoVariables::checkVarLifetime"); const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); @@ -722,7 +719,7 @@ void CheckAutoVariables::checkVarLifetime() } } -void CheckAutoVariables::errorReturnDanglingLifetime(const Token *tok, const ValueFlow::Value *val) +void CheckAutoVariablesImpl::errorReturnDanglingLifetime(const Token *tok, const ValueFlow::Value *val) { const bool inconclusive = val ? val->isInconclusive() : false; ErrorPath errorPath = val ? val->errorPath : ErrorPath(); @@ -731,7 +728,7 @@ void CheckAutoVariables::errorReturnDanglingLifetime(const Token *tok, const Val reportError(std::move(errorPath), Severity::error, "returnDanglingLifetime", msg + " that will be invalid when returning.", CWE562, inconclusive ? Certainty::inconclusive : Certainty::normal); } -void CheckAutoVariables::errorInvalidLifetime(const Token *tok, const ValueFlow::Value* val) +void CheckAutoVariablesImpl::errorInvalidLifetime(const Token *tok, const ValueFlow::Value* val) { const bool inconclusive = val ? val->isInconclusive() : false; ErrorPath errorPath = val ? val->errorPath : ErrorPath(); @@ -740,7 +737,7 @@ void CheckAutoVariables::errorInvalidLifetime(const Token *tok, const ValueFlow: reportError(std::move(errorPath), Severity::error, "invalidLifetime", msg + " that is out of scope.", CWE562, inconclusive ? Certainty::inconclusive : Certainty::normal); } -void CheckAutoVariables::errorDanglingTemporaryLifetime(const Token* tok, const ValueFlow::Value* val, const Token* tempTok) +void CheckAutoVariablesImpl::errorDanglingTemporaryLifetime(const Token* tok, const ValueFlow::Value* val, const Token* tempTok) { const bool inconclusive = val ? val->isInconclusive() : false; ErrorPath errorPath = val ? val->errorPath : ErrorPath(); @@ -755,7 +752,7 @@ void CheckAutoVariables::errorDanglingTemporaryLifetime(const Token* tok, const inconclusive ? Certainty::inconclusive : Certainty::normal); } -void CheckAutoVariables::errorDanglngLifetime(const Token *tok, const ValueFlow::Value *val, bool isStatic) +void CheckAutoVariablesImpl::errorDanglngLifetime(const Token *tok, const ValueFlow::Value *val, bool isStatic) { const bool inconclusive = val ? val->isInconclusive() : false; ErrorPath errorPath = val ? val->errorPath : ErrorPath(); @@ -766,21 +763,21 @@ void CheckAutoVariables::errorDanglngLifetime(const Token *tok, const ValueFlow: reportError(std::move(errorPath), Severity::error, "danglingLifetime", msg + ".", CWE562, inconclusive ? Certainty::inconclusive : Certainty::normal); } -void CheckAutoVariables::errorDanglingTempReference(const Token* tok, ErrorPath errorPath, bool inconclusive) +void CheckAutoVariablesImpl::errorDanglingTempReference(const Token* tok, ErrorPath errorPath, bool inconclusive) { errorPath.emplace_back(tok, ""); reportError( std::move(errorPath), Severity::error, "danglingTempReference", "Using reference to dangling temporary.", CWE562, inconclusive ? Certainty::inconclusive : Certainty::normal); } -void CheckAutoVariables::errorReturnReference(const Token* tok, ErrorPath errorPath, bool inconclusive) +void CheckAutoVariablesImpl::errorReturnReference(const Token* tok, ErrorPath errorPath, bool inconclusive) { errorPath.emplace_back(tok, ""); reportError( std::move(errorPath), Severity::error, "returnReference", "Reference to local variable returned.", CWE562, inconclusive ? Certainty::inconclusive : Certainty::normal); } -void CheckAutoVariables::errorDanglingReference(const Token *tok, const Variable *var, ErrorPath errorPath) +void CheckAutoVariablesImpl::errorDanglingReference(const Token *tok, const Variable *var, ErrorPath errorPath) { std::string tokName = tok ? tok->str() : "x"; std::string varName = var ? var->name() : "y"; @@ -789,14 +786,14 @@ void CheckAutoVariables::errorDanglingReference(const Token *tok, const Variable reportError(std::move(errorPath), Severity::error, "danglingReference", msg, CWE562, Certainty::normal); } -void CheckAutoVariables::errorReturnTempReference(const Token* tok, ErrorPath errorPath, bool inconclusive) +void CheckAutoVariablesImpl::errorReturnTempReference(const Token* tok, ErrorPath errorPath, bool inconclusive) { errorPath.emplace_back(tok, ""); reportError( std::move(errorPath), Severity::error, "returnTempReference", "Reference to temporary returned.", CWE562, inconclusive ? Certainty::inconclusive : Certainty::normal); } -void CheckAutoVariables::errorInvalidDeallocation(const Token *tok, const ValueFlow::Value *val) +void CheckAutoVariablesImpl::errorInvalidDeallocation(const Token *tok, const ValueFlow::Value *val) { const Variable *var = val ? val->tokvalue->variable() : (tok ? tok->variable() : nullptr); @@ -823,17 +820,17 @@ void CheckAutoVariables::errorInvalidDeallocation(const Token *tok, const ValueF "that has been allocated dynamically.", CWE590, Certainty::normal); } -void CheckAutoVariables::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) +void CheckAutoVariables::runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) { - CheckAutoVariables checkAutoVariables(&tokenizer, &tokenizer.getSettings(), errorLogger); + CheckAutoVariablesImpl checkAutoVariables(&tokenizer, tokenizer.getSettings(), errorLogger); checkAutoVariables.assignFunctionArg(); checkAutoVariables.autoVariables(); checkAutoVariables.checkVarLifetime(); } -void CheckAutoVariables::getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const +void CheckAutoVariables::getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const { - CheckAutoVariables c(nullptr,settings,errorLogger); + CheckAutoVariablesImpl c(nullptr,settings,errorLogger); c.errorAutoVariableAssignment(nullptr, false); c.errorReturnReference(nullptr, ErrorPath{}, false); c.errorDanglingReference(nullptr, nullptr, ErrorPath{}); diff --git a/lib/checkautovariables.h b/lib/checkautovariables.h index d94f254508f..23d7a55a488 100644 --- a/lib/checkautovariables.h +++ b/lib/checkautovariables.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,6 +23,7 @@ //--------------------------------------------------------------------------- #include "check.h" +#include "checkimpl.h" #include "config.h" #include "errortypes.h" @@ -47,15 +48,32 @@ namespace ValueFlow { class CPPCHECKLIB CheckAutoVariables : public Check { public: /** This constructor is used when registering the CheckClass */ - CheckAutoVariables() : Check(myName()) {} + CheckAutoVariables() : Check("Auto Variables") {} private: - /** This constructor is used when running checks. */ - CheckAutoVariables(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) - : Check(myName(), tokenizer, settings, errorLogger) {} - /** @brief Run checks against the normal token list */ - void runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) override; + void runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) override; + + void getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const override; + + std::string classInfo() const override { + return "A pointer to a variable is only valid as long as the variable is in scope.\n" + "Check:\n" + "- returning a pointer to auto or temporary variable\n" + "- assigning address of an variable to an effective parameter of a function\n" + "- returning reference to local/temporary variable\n" + "- returning address of function parameter\n" + "- suspicious assignment of pointer argument\n" + "- useless assignment of function argument\n"; + } +}; + +class CPPCHECKLIB CheckAutoVariablesImpl : public CheckImpl +{ +public: + /** This constructor is used when running checks. */ + CheckAutoVariablesImpl(const Tokenizer *tokenizer, const Settings &settings, ErrorLogger &errorLogger) + : CheckImpl(tokenizer, settings, errorLogger) {} /** assign function argument */ void assignFunctionArg(); @@ -69,7 +87,6 @@ class CPPCHECKLIB CheckAutoVariables : public Check { bool checkAutoVariableAssignment(const Token *expr, bool inconclusive, const Token *startToken = nullptr); void checkVarLifetime(); - void checkVarLifetimeScope(const Token * start, const Token * end); void errorAutoVariableAssignment(const Token *tok, bool inconclusive); @@ -85,24 +102,8 @@ class CPPCHECKLIB CheckAutoVariables : public Check { void errorUselessAssignmentArg(const Token *tok); void errorUselessAssignmentPtrArg(const Token *tok); - void getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const override; - - static std::string myName() { - return "Auto Variables"; - } - - std::string classInfo() const override { - return "A pointer to a variable is only valid as long as the variable is in scope.\n" - "Check:\n" - "- returning a pointer to auto or temporary variable\n" - "- assigning address of an variable to an effective parameter of a function\n" - "- returning reference to local/temporary variable\n" - "- returning address of function parameter\n" - "- suspicious assignment of pointer argument\n" - "- useless assignment of function argument\n"; - } - /** returns true if tokvalue has already been diagnosed */ +private: bool diag(const Token* tokvalue); std::set mDiagDanglingTemp; diff --git a/lib/checkbool.cpp b/lib/checkbool.cpp index 93e501416aa..3d36c9936a1 100644 --- a/lib/checkbool.cpp +++ b/lib/checkbool.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -32,11 +32,6 @@ #include //--------------------------------------------------------------------------- -// Register this check class (by creating a static instance of it) -namespace { - CheckBool instance; -} - static const CWE CWE398(398U); // Indicator of Poor Code Quality static const CWE CWE571(571U); // Expression is Always True static const CWE CWE587(587U); // Assignment of a Fixed Address to a Pointer @@ -48,9 +43,9 @@ static bool isBool(const Variable* var) } //--------------------------------------------------------------------------- -void CheckBool::checkIncrementBoolean() +void CheckBoolImpl::checkIncrementBoolean() { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("incrementboolean")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("incrementboolean")) return; logChecker("CheckBool::checkIncrementBoolean"); // style @@ -65,7 +60,7 @@ void CheckBool::checkIncrementBoolean() } } -void CheckBool::incrementBooleanError(const Token *tok) +void CheckBoolImpl::incrementBooleanError(const Token *tok) { reportError( tok, @@ -88,13 +83,13 @@ static bool isConvertedToBool(const Token* tok) // if (bool & bool) -> if (bool && bool) // if (bool | bool) -> if (bool || bool) //--------------------------------------------------------------------------- -void CheckBool::checkBitwiseOnBoolean() +void CheckBoolImpl::checkBitwiseOnBoolean() { - if (!mSettings->isPremiumEnabled("bitwiseOnBoolean") && - !mSettings->severity.isEnabled(Severity::style) && + if (!mSettings.isPremiumEnabled("bitwiseOnBoolean") && + !mSettings.severity.isEnabled(Severity::style) && // danmar: this is inconclusive because I don't like that there are // warnings for calculations. Example: set_flag(a & b); - !mSettings->certainty.isEnabled(Certainty::inconclusive)) + !mSettings.certainty.isEnabled(Certainty::inconclusive)) return; logChecker("CheckBool::checkBitwiseOnBoolean"); // style,inconclusive @@ -121,7 +116,7 @@ void CheckBool::checkBitwiseOnBoolean() if (tok->str() == "|" && !isConvertedToBool(tok) && !(isBoolOp1 && isBoolOp2)) continue; // first operand will always be evaluated - if (!isConstExpression(tok->astOperand2(), mSettings->library)) + if (!isConstExpression(tok->astOperand2(), mSettings.library)) continue; if (tok->astOperand2()->variable() && tok->astOperand2()->variable()->nameToken() == tok->astOperand2()) continue; @@ -132,7 +127,7 @@ void CheckBool::checkBitwiseOnBoolean() } } -void CheckBool::bitwiseOnBooleanError(const Token* tok, const std::string& expression, const std::string& op, bool isCompound) +void CheckBoolImpl::bitwiseOnBooleanError(const Token* tok, const std::string& expression, const std::string& op, bool isCompound) { std::string msg = "Boolean expression '" + expression + "' is used in bitwise operation."; if (!isCompound) @@ -149,9 +144,9 @@ void CheckBool::bitwiseOnBooleanError(const Token* tok, const std::string& expre // if (!x==3) <- Probably meant to be "x!=3" //--------------------------------------------------------------------------- -void CheckBool::checkComparisonOfBoolWithInt() +void CheckBoolImpl::checkComparisonOfBoolWithInt() { - if (!mSettings->severity.isEnabled(Severity::warning) || !mTokenizer->isCPP()) + if (!mSettings.severity.isEnabled(Severity::warning) || !mTokenizer->isCPP()) return; logChecker("CheckBool::checkComparisonOfBoolWithInt"); // warning,c++ @@ -176,7 +171,7 @@ void CheckBool::checkComparisonOfBoolWithInt() } } -void CheckBool::comparisonOfBoolWithInvalidComparator(const Token *tok, const std::string &expression) +void CheckBoolImpl::comparisonOfBoolWithInvalidComparator(const Token *tok, const std::string &expression) { reportError(tok, Severity::warning, "comparisonOfBoolWithInvalidComparator", "Comparison of a boolean value using relational operator (<, >, <= or >=).\n" @@ -200,9 +195,9 @@ static bool tokenIsFunctionReturningBool(const Token* tok) return false; } -void CheckBool::checkComparisonOfFuncReturningBool() +void CheckBoolImpl::checkComparisonOfFuncReturningBool() { - if (!mSettings->severity.isEnabled(Severity::style)) + if (!mSettings.severity.isEnabled(Severity::style)) return; if (!mTokenizer->isCPP()) @@ -244,7 +239,7 @@ void CheckBool::checkComparisonOfFuncReturningBool() } } -void CheckBool::comparisonOfFuncReturningBoolError(const Token *tok, const std::string &expression) +void CheckBoolImpl::comparisonOfFuncReturningBoolError(const Token *tok, const std::string &expression) { reportError(tok, Severity::style, "comparisonOfFuncReturningBoolError", "Comparison of a function returning boolean value using relational (<, >, <= or >=) operator.\n" @@ -253,7 +248,7 @@ void CheckBool::comparisonOfFuncReturningBoolError(const Token *tok, const std:: " operator could cause unexpected results.", CWE398, Certainty::normal); } -void CheckBool::comparisonOfTwoFuncsReturningBoolError(const Token *tok, const std::string &expression1, const std::string &expression2) +void CheckBoolImpl::comparisonOfTwoFuncsReturningBoolError(const Token *tok, const std::string &expression1, const std::string &expression2) { reportError(tok, Severity::style, "comparisonOfTwoFuncsReturningBoolError", "Comparison of two functions returning boolean value using relational (<, >, <= or >=) operator.\n" @@ -266,9 +261,9 @@ void CheckBool::comparisonOfTwoFuncsReturningBoolError(const Token *tok, const s // Comparison of bool with bool //------------------------------------------------------------------------------- -void CheckBool::checkComparisonOfBoolWithBool() +void CheckBoolImpl::checkComparisonOfBoolWithBool() { - if (!mSettings->severity.isEnabled(Severity::style)) + if (!mSettings.severity.isEnabled(Severity::style)) return; if (!mTokenizer->isCPP()) @@ -307,7 +302,7 @@ void CheckBool::checkComparisonOfBoolWithBool() } } -void CheckBool::comparisonOfBoolWithBoolError(const Token *tok, const std::string &expression) +void CheckBoolImpl::comparisonOfBoolWithBoolError(const Token *tok, const std::string &expression) { reportError(tok, Severity::style, "comparisonOfBoolWithBoolError", "Comparison of a variable having boolean value using relational (<, >, <= or >=) operator.\n" @@ -317,7 +312,7 @@ void CheckBool::comparisonOfBoolWithBoolError(const Token *tok, const std::strin } //----------------------------------------------------------------------------- -void CheckBool::checkAssignBoolToPointer() +void CheckBoolImpl::checkAssignBoolToPointer() { logChecker("CheckBool::checkAssignBoolToPointer"); const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); @@ -330,7 +325,7 @@ void CheckBool::checkAssignBoolToPointer() } } -void CheckBool::assignBoolToPointerError(const Token *tok) +void CheckBoolImpl::assignBoolToPointerError(const Token *tok) { reportError(tok, Severity::error, "assignBoolToPointer", "Boolean value assigned to pointer.", CWE587, Certainty::normal); @@ -338,9 +333,9 @@ void CheckBool::assignBoolToPointerError(const Token *tok) //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- -void CheckBool::checkComparisonOfBoolExpressionWithInt() +void CheckBoolImpl::checkComparisonOfBoolExpressionWithInt() { - if (!mSettings->severity.isEnabled(Severity::warning) && !mSettings->isPremiumEnabled("compareBoolExpressionWithInt")) + if (!mSettings.severity.isEnabled(Severity::warning) && !mSettings.isPremiumEnabled("compareBoolExpressionWithInt")) return; logChecker("CheckBool::checkComparisonOfBoolExpressionWithInt"); // warning @@ -378,13 +373,13 @@ void CheckBool::checkComparisonOfBoolExpressionWithInt() if (astIsBool(numTok)) continue; - const ValueFlow::Value *minval = numTok->getValueLE(0, *mSettings); + const ValueFlow::Value *minval = numTok->getValueLE(0, mSettings); if (minval && minval->intvalue == 0 && (numInRhs ? Token::Match(tok, ">|==|!=") : Token::Match(tok, "<|==|!="))) minval = nullptr; - const ValueFlow::Value *maxval = numTok->getValueGE(1, *mSettings); + const ValueFlow::Value *maxval = numTok->getValueGE(1, mSettings); if (maxval && maxval->intvalue == 1 && (numInRhs ? Token::Match(tok, "<|==|!=") : Token::Match(tok, ">|==|!="))) @@ -398,7 +393,7 @@ void CheckBool::checkComparisonOfBoolExpressionWithInt() } } -void CheckBool::comparisonOfBoolExpressionWithIntError(const Token *tok, bool not0or1) +void CheckBoolImpl::comparisonOfBoolExpressionWithIntError(const Token *tok, bool not0or1) { if (not0or1) reportError(tok, Severity::warning, "compareBoolExpressionWithInt", @@ -409,7 +404,7 @@ void CheckBool::comparisonOfBoolExpressionWithIntError(const Token *tok, bool no } -void CheckBool::pointerArithBool() +void CheckBoolImpl::pointerArithBool() { logChecker("CheckBool::pointerArithBool"); @@ -432,7 +427,7 @@ void CheckBool::pointerArithBool() } } -void CheckBool::pointerArithBoolCond(const Token *tok) +void CheckBoolImpl::pointerArithBoolCond(const Token *tok) { if (!tok) return; @@ -452,7 +447,7 @@ void CheckBool::pointerArithBoolCond(const Token *tok) pointerArithBoolError(tok); } -void CheckBool::pointerArithBoolError(const Token *tok) +void CheckBoolImpl::pointerArithBoolError(const Token *tok) { reportError(tok, Severity::error, @@ -461,11 +456,11 @@ void CheckBool::pointerArithBoolError(const Token *tok) "Converting pointer arithmetic result to bool. The boolean result is always true unless there is pointer arithmetic overflow, and overflow is undefined behaviour. Probably a dereference is forgotten.", CWE571, Certainty::normal); } -void CheckBool::checkAssignBoolToFloat() +void CheckBoolImpl::checkAssignBoolToFloat() { if (!mTokenizer->isCPP()) return; - if (!mSettings->severity.isEnabled(Severity::style)) + if (!mSettings.severity.isEnabled(Severity::style)) return; logChecker("CheckBool::checkAssignBoolToFloat"); // style,c++ const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); @@ -478,15 +473,15 @@ void CheckBool::checkAssignBoolToFloat() } } -void CheckBool::assignBoolToFloatError(const Token *tok) +void CheckBoolImpl::assignBoolToFloatError(const Token *tok) { reportError(tok, Severity::style, "assignBoolToFloat", "Boolean value assigned to floating point variable.", CWE704, Certainty::normal); } -void CheckBool::returnValueOfFunctionReturningBool() +void CheckBoolImpl::returnValueOfFunctionReturningBool() { - if (!mSettings->severity.isEnabled(Severity::style)) + if (!mSettings.severity.isEnabled(Severity::style)) return; logChecker("CheckBool::returnValueOfFunctionReturningBool"); // style @@ -505,21 +500,21 @@ void CheckBool::returnValueOfFunctionReturningBool() else if (tok->scope() && tok->scope()->isClassOrStruct()) tok = tok->scope()->bodyEnd; else if (Token::simpleMatch(tok, "return") && tok->astOperand1() && - (tok->astOperand1()->getValueGE(2, *mSettings) || tok->astOperand1()->getValueLE(-1, *mSettings)) && + (tok->astOperand1()->getValueGE(2, mSettings) || tok->astOperand1()->getValueLE(-1, mSettings)) && !(tok->astOperand1()->astOperand1() && Token::Match(tok->astOperand1(), "&|%or%"))) returnValueBoolError(tok); } } } -void CheckBool::returnValueBoolError(const Token *tok) +void CheckBoolImpl::returnValueBoolError(const Token *tok) { reportError(tok, Severity::style, "returnNonBoolInBooleanFunction", "Non-boolean value returned from function returning bool"); } -void CheckBool::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) +void CheckBool::runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) { - CheckBool checkBool(&tokenizer, &tokenizer.getSettings(), errorLogger); + CheckBoolImpl checkBool(&tokenizer, tokenizer.getSettings(), errorLogger); // Checks checkBool.checkComparisonOfBoolExpressionWithInt(); @@ -534,9 +529,9 @@ void CheckBool::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) checkBool.checkBitwiseOnBoolean(); } -void CheckBool::getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const +void CheckBool::getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const { - CheckBool c(nullptr, settings, errorLogger); + CheckBoolImpl c(nullptr, settings, errorLogger); c.assignBoolToPointerError(nullptr); c.assignBoolToFloatError(nullptr); c.comparisonOfFuncReturningBoolError(nullptr, "func_name"); diff --git a/lib/checkbool.h b/lib/checkbool.h index 65e5d0254ea..53cc19c2486 100644 --- a/lib/checkbool.h +++ b/lib/checkbool.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,6 +23,7 @@ //--------------------------------------------------------------------------- #include "check.h" +#include "checkimpl.h" #include "config.h" #include @@ -41,15 +42,33 @@ class Tokenizer; class CPPCHECKLIB CheckBool : public Check { public: /** @brief This constructor is used when registering the CheckClass */ - CheckBool() : Check(myName()) {} + CheckBool() : Check("Boolean") {} private: - /** @brief This constructor is used when running checks. */ - CheckBool(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) - : Check(myName(), tokenizer, settings, errorLogger) {} - /** @brief Run checks against the normal token list */ - void runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) override; + void runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) override; + + void getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const override; + + std::string classInfo() const override { + return "Boolean type checks\n" + "- using increment on boolean\n" + "- comparison of a boolean expression with an integer other than 0 or 1\n" + "- comparison of a function returning boolean value using relational operator\n" + "- comparison of a boolean value with boolean value using relational operator\n" + "- using bool in bitwise expression\n" + "- pointer addition in condition (either dereference is forgot or pointer overflow is required to make the condition false)\n" + "- Assigning bool value to pointer or float\n" + "- Returning an integer other than 0 or 1 from a function with boolean return value\n"; + } +}; + +class CPPCHECKLIB CheckBoolImpl : public CheckImpl +{ +public: + /** @brief This constructor is used when running checks. */ + CheckBoolImpl(const Tokenizer *tokenizer, const Settings &settings, ErrorLogger &errorLogger) + : CheckImpl(tokenizer, settings, errorLogger) {} /** @brief %Check for comparison of function returning bool*/ void checkComparisonOfFuncReturningBool(); @@ -94,24 +113,6 @@ class CPPCHECKLIB CheckBool : public Check { void comparisonOfBoolExpressionWithIntError(const Token *tok, bool not0or1); void pointerArithBoolError(const Token *tok); void returnValueBoolError(const Token *tok); - - void getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const override; - - static std::string myName() { - return "Boolean"; - } - - std::string classInfo() const override { - return "Boolean type checks\n" - "- using increment on boolean\n" - "- comparison of a boolean expression with an integer other than 0 or 1\n" - "- comparison of a function returning boolean value using relational operator\n" - "- comparison of a boolean value with boolean value using relational operator\n" - "- using bool in bitwise expression\n" - "- pointer addition in condition (either dereference is forgot or pointer overflow is required to make the condition false)\n" - "- Assigning bool value to pointer or float\n" - "- Returning an integer other than 0 or 1 from a function with boolean return value\n"; - } }; /// @} //--------------------------------------------------------------------------- diff --git a/lib/checkbufferoverrun.cpp b/lib/checkbufferoverrun.cpp index a8f7efeabd6..a9c85802374 100644 --- a/lib/checkbufferoverrun.cpp +++ b/lib/checkbufferoverrun.cpp @@ -36,11 +36,13 @@ #include "utils.h" #include "valueflow.h" #include "vfvalue.h" +#include "vf_common.h" #include #include #include #include +#include #include // std::accumulate #include #include @@ -49,13 +51,6 @@ //--------------------------------------------------------------------------- -// Register this check class (by creating a static instance of it) -namespace { - CheckBufferOverrun instance; -} - -//--------------------------------------------------------------------------- - // CWE ids used: static const CWE CWE131(131U); // Incorrect Calculation of Buffer Size static const CWE CWE170(170U); // Improper Null Termination @@ -71,7 +66,12 @@ static const CWE CWE_BUFFER_OVERRUN(788U); // Access of Memory Location After static const ValueFlow::Value *getBufferSizeValue(const Token *tok) { const std::list &tokenValues = tok->values(); - const auto it = std::find_if(tokenValues.cbegin(), tokenValues.cend(), std::mem_fn(&ValueFlow::Value::isBufferSizeValue)); + auto it = std::find_if(tokenValues.cbegin(), tokenValues.cend(), std::mem_fn(&ValueFlow::Value::isBufferSizeValue)); + if (it != tokenValues.cend()) + return &*it; + it = std::find_if(tokenValues.cbegin(), tokenValues.cend(), [](const ValueFlow::Value& v) { + return v.isContainerSizeValue() && !v.isImpossible(); + }); return it == tokenValues.cend() ? nullptr : &*it; } @@ -83,7 +83,16 @@ static const Token* getRealBufferTok(const Token* tok) { return (op->valueType() && op->valueType()->pointer) ? op : tok; } -static int getMinFormatStringOutputLength(const std::vector ¶meters, nonneg int formatStringArgNr) +// Field width or precision of a format specifier. Values that do not fit in an int are clamped to INT_MAX. +static int getFormatDigits(const std::string &digits) +{ + const long long value = std::strtoll(digits.c_str(), nullptr, 10); + if (value < -std::numeric_limits::max() || value > std::numeric_limits::max()) + return std::numeric_limits::max(); + return std::abs(static_cast(value)); +} + +static MathLib::bigint getMinFormatStringOutputLength(const std::vector ¶meters, nonneg int formatStringArgNr, const Settings& settings) { if (formatStringArgNr <= 0 || formatStringArgNr > parameters.size()) return 0; @@ -91,7 +100,7 @@ static int getMinFormatStringOutputLength(const std::vector ¶m return 0; const std::string &formatString = parameters[formatStringArgNr - 1]->str(); bool percentCharFound = false; - int outputStringSize = 0; + MathLib::bigint outputStringSize = 0; bool handleNextParameter = false; std::string digits_string; bool i_d_x_f_found = false; @@ -138,8 +147,8 @@ static int getMinFormatStringOutputLength(const std::vector ¶m break; case 's': parameterLength = 0; - if (inputArgNr < parameters.size() && parameters[inputArgNr]->tokType() == Token::eString) - parameterLength = Token::getStrLength(parameters[inputArgNr]); + if (inputArgNr < parameters.size()) + parameterLength = ValueFlow::valueFlowGetStrLength(parameters[inputArgNr], settings.library); handleNextParameter = true; break; @@ -156,15 +165,13 @@ static int getMinFormatStringOutputLength(const std::vector ¶m outputStringSize++; if (handleNextParameter) { - // NOLINTNEXTLINE(bugprone-unchecked-string-to-number-conversion) - intentional use - int tempDigits = std::abs(std::atoi(digits_string.c_str())); + int tempDigits = getFormatDigits(digits_string); if (i_d_x_f_found) tempDigits = std::max(tempDigits, 1); if (digits_string.find('.') != std::string::npos) { const std::string endStr = digits_string.substr(digits_string.find('.') + 1); - // NOLINTNEXTLINE(bugprone-unchecked-string-to-number-conversion) - intentional use - const int maxLen = std::max(std::abs(std::atoi(endStr.c_str())), 1); + const int maxLen = std::max(getFormatDigits(endStr), 1); if (formatString[i] == 's') { // For strings, the length after the dot "%.2s" will limit @@ -233,7 +240,8 @@ static bool getDimensionsEtc(const Token * const arrayToken, const Settings &set const size_t typeSize = array->valueType()->getSizeOf(settings, ValueType::Accuracy::ExactOrZero, sizeOf); if (typeSize == 0) return false; - dim.num = value->intvalue / typeSize; + // a container size counts elements, a buffer size counts bytes + dim.num = value->isContainerSizeValue() ? value->intvalue : value->intvalue / typeSize; dimensions.emplace_back(dim); } return !dimensions.empty(); @@ -290,7 +298,7 @@ static std::vector getOverrunIndexValues(const Token* tok, return {}; } -void CheckBufferOverrun::arrayIndex() +void CheckBufferOverrunImpl::arrayIndex() { logChecker("CheckBufferOverrun::arrayIndex"); @@ -329,7 +337,7 @@ void CheckBufferOverrun::arrayIndex() ErrorPath errorPath; bool mightBeLarger = false; MathLib::bigint path = 0; - if (!getDimensionsEtc(tok->astOperand1(), *mSettings, dimensions, errorPath, mightBeLarger, path)) + if (!getDimensionsEtc(tok->astOperand1(), mSettings, dimensions, errorPath, mightBeLarger, path)) continue; const Variable* const var = array->variable(); @@ -337,7 +345,7 @@ void CheckBufferOverrun::arrayIndex() const Token* changeTok = var->scope()->bodyStart; bool isChanged = false; while ((changeTok = findVariableChanged(changeTok->next(), var->scope()->bodyEnd, /*indirect*/ 0, var->declarationId(), - /*globalvar*/ false, *mSettings))) { + /*globalvar*/ false, mSettings))) { if (!Token::simpleMatch(changeTok->astParent(), "[")) { isChanged = true; break; @@ -359,7 +367,7 @@ void CheckBufferOverrun::arrayIndex() bool neg = false; std::vector negativeIndexes; for (const Token * indexToken : indexTokens) { - const ValueFlow::Value *negativeValue = indexToken->getValueLE(-1, *mSettings); + const ValueFlow::Value *negativeValue = indexToken->getValueLE(-1, mSettings); if (negativeValue) { negativeIndexes.emplace_back(*negativeValue); neg = true; @@ -411,9 +419,9 @@ static std::string arrayIndexMessage(const Token* tok, return errmsg.str(); } -void CheckBufferOverrun::arrayIndexError(const Token* tok, - const std::vector& dimensions, - const std::vector& indexes) +void CheckBufferOverrunImpl::arrayIndexError(const Token* tok, + const std::vector& dimensions, + const std::vector& indexes) { if (!tok) { reportError(tok, Severity::error, "arrayIndexOutOfBounds", "Array 'arr[16]' accessed at index 16, which is out of bounds.", CWE_BUFFER_OVERRUN, Certainty::normal); @@ -424,7 +432,7 @@ void CheckBufferOverrun::arrayIndexError(const Token* tok, const Token *condition = nullptr; const ValueFlow::Value *index = nullptr; for (const ValueFlow::Value& indexValue : indexes) { - if (!indexValue.errorSeverity() && !mSettings->severity.isEnabled(Severity::warning)) + if (!indexValue.errorSeverity() && !mSettings.severity.isEnabled(Severity::warning)) return; if (indexValue.condition) condition = indexValue.condition; @@ -440,9 +448,9 @@ void CheckBufferOverrun::arrayIndexError(const Token* tok, index->isInconclusive() ? Certainty::inconclusive : Certainty::normal); } -void CheckBufferOverrun::negativeIndexError(const Token* tok, - const std::vector& dimensions, - const std::vector& indexes) +void CheckBufferOverrunImpl::negativeIndexError(const Token* tok, + const std::vector& dimensions, + const std::vector& indexes) { if (!tok) { reportError(tok, Severity::error, "negativeIndex", "Negative array index", CWE_BUFFER_UNDERRUN, Certainty::normal); @@ -452,7 +460,7 @@ void CheckBufferOverrun::negativeIndexError(const Token* tok, const Token *condition = nullptr; const ValueFlow::Value *negativeValue = nullptr; for (const ValueFlow::Value& indexValue : indexes) { - if (!indexValue.errorSeverity() && !mSettings->severity.isEnabled(Severity::warning)) + if (!indexValue.errorSeverity() && !mSettings.severity.isEnabled(Severity::warning)) return; if (indexValue.condition) condition = indexValue.condition; @@ -461,7 +469,7 @@ void CheckBufferOverrun::negativeIndexError(const Token* tok, } reportError(getErrorPath(tok, negativeValue, "Negative array index"), - negativeValue->errorSeverity() ? Severity::error : Severity::warning, + (negativeValue->errorSeverity() && !negativeValue->conditional) ? Severity::error : Severity::warning, "negativeIndex", arrayIndexMessage(tok, dimensions, indexes, condition), CWE_BUFFER_UNDERRUN, @@ -470,9 +478,9 @@ void CheckBufferOverrun::negativeIndexError(const Token* tok, //--------------------------------------------------------------------------- -void CheckBufferOverrun::pointerArithmetic() +void CheckBufferOverrunImpl::pointerArithmetic() { - if (!mSettings->severity.isEnabled(Severity::portability) && !mSettings->isPremiumEnabled("pointerOutOfBounds")) + if (!mSettings.severity.isEnabled(Severity::portability) && !mSettings.isPremiumEnabled("pointerOutOfBounds")) return; logChecker("CheckBufferOverrun::pointerArithmetic"); // portability @@ -503,7 +511,7 @@ void CheckBufferOverrun::pointerArithmetic() ErrorPath errorPath; bool mightBeLarger = false; MathLib::bigint path = 0; - if (!getDimensionsEtc(arrayToken, *mSettings, dimensions, errorPath, mightBeLarger, path)) + if (!getDimensionsEtc(arrayToken, mSettings, dimensions, errorPath, mightBeLarger, path)) continue; if (tok->str() == "+") { @@ -516,7 +524,7 @@ void CheckBufferOverrun::pointerArithmetic() pointerArithmeticError(tok, indexToken, &indexValues.front()); } - if (const ValueFlow::Value *neg = indexToken->getValueLE(-1, *mSettings)) + if (const ValueFlow::Value *neg = indexToken->getValueLE(-1, mSettings)) pointerArithmeticError(tok, indexToken, neg); } else if (tok->str() == "-") { if (arrayToken->variable() && arrayToken->variable()->isArgument()) @@ -526,7 +534,7 @@ void CheckBufferOverrun::pointerArithmetic() while (Token::Match(array, ".|::")) array = array->astOperand2(); if (array->variable() && array->variable()->isArray()) { - const ValueFlow::Value *v = indexToken->getValueGE(1, *mSettings); + const ValueFlow::Value *v = indexToken->getValueGE(1, mSettings); if (v) pointerArithmeticError(tok, indexToken, v); } @@ -534,7 +542,7 @@ void CheckBufferOverrun::pointerArithmetic() } } -void CheckBufferOverrun::pointerArithmeticError(const Token *tok, const Token *indexToken, const ValueFlow::Value *indexValue) +void CheckBufferOverrunImpl::pointerArithmeticError(const Token *tok, const Token *indexToken, const ValueFlow::Value *indexValue) { if (!tok) { reportError(tok, Severity::portability, "pointerOutOfBounds", "Pointer arithmetic overflow.", CWE_POINTER_ARITHMETIC_OVERFLOW, Certainty::normal); @@ -558,21 +566,65 @@ void CheckBufferOverrun::pointerArithmeticError(const Token *tok, const Token *i //--------------------------------------------------------------------------- -ValueFlow::Value CheckBufferOverrun::getBufferSize(const Token *bufTok) const +ValueFlow::Value CheckBufferOverrunImpl::getBufferSize(const Token *bufTok, const Settings& settings, bool checkStringLiteral) const { if (!bufTok->valueType()) return ValueFlow::Value(-1); - if (bufTok->isUnaryOp("&")) + + MathLib::bigint index = 0; + if (bufTok->isUnaryOp("&")) { bufTok = bufTok->astOperand1(); + if (Token::simpleMatch(bufTok, "[")) { + if (const Token* indexTok = bufTok->astOperand2()) { + if (indexTok->hasKnownIntValue()) + index = indexTok->getKnownIntValue(); + else if (const ValueFlow::Value* maxValue = indexTok->getMaxValue(false)) + index = maxValue->intvalue; + else + return ValueFlow::Value(-1); + } + bufTok = bufTok->astOperand1(); + } + } const Variable *var = bufTok->variable(); if (!var || var->dimensions().empty()) { - const ValueFlow::Value *value = getBufferSizeValue(bufTok); - if (value) - return *value; + if (const ValueFlow::Value *value = getBufferSizeValue(bufTok)) { + if (value->isBufferSizeValue()) + return *value; + if (value->isContainerSizeValue() && bufTok->valueType()) { + size_t elementSize = 0; + if (bufTok->valueType()->containerTypeToken) { + const ValueType vtElement = ValueType::parseDecl(bufTok->valueType()->containerTypeToken, settings); + elementSize = + vtElement.getSizeOf(settings, ValueType::Accuracy::ExactOrZero, ValueType::SizeOf::Pointer); + } else if (bufTok->valueType()->pointer == 1) { + elementSize = bufTok->valueType()->getSizeOf(settings, + ValueType::Accuracy::ExactOrZero, + ValueType::SizeOf::Pointee); + } + if (elementSize > 0) { + ValueFlow::Value bufSizeVal; + bufSizeVal.valueType = ValueFlow::Value::ValueType::BUFFER_SIZE; + bufSizeVal.intvalue = value->intvalue * elementSize; + bufSizeVal.valueKind = value->valueKind; + bufSizeVal.errorPath = value->errorPath; + return bufSizeVal; + } + } + } else if (checkStringLiteral && bufTok->tokType() == Token::Type::eString && bufTok->valueType()) { + const size_t charSize = bufTok->valueType()->getSizeOf(settings, ValueType::Accuracy::ExactOrZero, ValueType::SizeOf::Pointee); + if (charSize > 0) { + ValueFlow::Value strSizeVal; + strSizeVal.setKnown(); + strSizeVal.valueType = ValueFlow::Value::ValueType::BUFFER_SIZE; + strSizeVal.intvalue = (Token::getStrArraySize(bufTok) - index) * charSize; + return strSizeVal; + } + } } - if (!var || var->isPointer()) + if (!var || var->isPointer() || (astIsContainer(bufTok) && var->getTypeName() != "std::array")) return ValueFlow::Value(-1); const MathLib::bigint dim = std::accumulate(var->dimensions().cbegin(), var->dimensions().cend(), MathLib::bigint(1), [](MathLib::bigint i1, const Dimension &dim) { @@ -584,17 +636,29 @@ ValueFlow::Value CheckBufferOverrun::getBufferSize(const Token *bufTok) const v.valueType = ValueFlow::Value::ValueType::BUFFER_SIZE; if (var->isPointerArray()) - v.intvalue = dim * mSettings->platform.sizeof_pointer; + v.intvalue = (dim - index) * mSettings.platform.sizeof_pointer; else { - const size_t typeSize = bufTok->valueType()->getSizeOf(*mSettings, ValueType::Accuracy::ExactOrZero, ValueType::SizeOf::Pointee); - v.intvalue = dim * typeSize; + const size_t typeSize = bufTok->valueType()->getSizeOf(mSettings, ValueType::Accuracy::ExactOrZero, ValueType::SizeOf::Pointee); + v.intvalue = (dim - index) * typeSize; } return v; } //--------------------------------------------------------------------------- -static bool checkBufferSize(const Token *ftok, const Library::ArgumentChecks::MinSize &minsize, const std::vector &args, const MathLib::bigint bufferSize, const Settings &settings, const Tokenizer* tokenizer) +// Return true if a * b does not fit in a MathLib::bigint +static bool mulOverflows(MathLib::bigint a, MathLib::bigint b) +{ + const MathLib::bigint maxValue = std::numeric_limits::max(); + const MathLib::bigint minValue = std::numeric_limits::min(); + if (a > 0) + return b > 0 ? a > maxValue / b : b < minValue / a; + if (a < 0) + return b > 0 ? a < minValue / b : (b < 0 && a < maxValue / b); + return false; +} + +static bool checkBufferSize(const Token *ftok, const Library::ArgumentChecks::MinSize &minsize, const std::vector &args, ValueFlow::Value& bufferSize, const Settings &settings, const Tokenizer* tokenizer) { const Token * const arg = (minsize.arg > 0 && minsize.arg - 1 < args.size()) ? args[minsize.arg - 1] : nullptr; const Token * const arg2 = (minsize.arg2 > 0 && minsize.arg2 - 1 < args.size()) ? args[minsize.arg2 - 1] : nullptr; @@ -602,20 +666,32 @@ static bool checkBufferSize(const Token *ftok, const Library::ArgumentChecks::Mi switch (minsize.type) { case Library::ArgumentChecks::MinSize::Type::STRLEN: if (settings.library.isargformatstr(ftok, minsize.arg)) { - return getMinFormatStringOutputLength(args, minsize.arg) < bufferSize; + return getMinFormatStringOutputLength(args, minsize.arg, settings) < bufferSize.intvalue; } else if (arg) { const Token *strtoken = arg->getValueTokenMaxStrLength(); if (strtoken) - return Token::getStrLength(strtoken) < bufferSize; + return Token::getStrLength(strtoken) < bufferSize.intvalue; } break; case Library::ArgumentChecks::MinSize::Type::ARGVALUE: { - if (arg && arg->hasKnownIntValue()) { - MathLib::bigint myMinsize = arg->getKnownIntValue(); + if (arg) { + const ValueFlow::Value* argVal = arg->hasKnownIntValue() ? + arg->getKnownValue(ValueFlow::Value::ValueType::INT) : + arg->getMaxValue(/*condition*/ true); + if (!argVal) + break; + MathLib::bigint myMinsize = argVal->intvalue; const int baseSize = tokenizer->sizeOfType(minsize.baseType); - if (baseSize != 0) + const bool overflow = baseSize != 0 && mulOverflows(myMinsize, baseSize); + if (baseSize != 0 && !overflow) myMinsize *= baseSize; - return myMinsize <= bufferSize; + const bool ok = !overflow && myMinsize <= bufferSize.intvalue; + if (!ok) { + bufferSize.errorPath.insert(bufferSize.errorPath.end(), argVal->errorPath.begin(), argVal->errorPath.end()); + if (!bufferSize.condition) + bufferSize.condition = argVal->condition; + } + return ok; } break; } @@ -623,15 +699,21 @@ static bool checkBufferSize(const Token *ftok, const Library::ArgumentChecks::Mi // TODO break; case Library::ArgumentChecks::MinSize::Type::MUL: - if (arg && arg2 && arg->hasKnownIntValue() && arg2->hasKnownIntValue()) - return (arg->getKnownIntValue() * arg2->getKnownIntValue()) <= bufferSize; + if (arg && arg2 && arg->hasKnownIntValue() && arg2->hasKnownIntValue()) { + if (mulOverflows(arg->getKnownIntValue(), arg2->getKnownIntValue())) + return false; + return (arg->getKnownIntValue() * arg2->getKnownIntValue()) <= bufferSize.intvalue; + } break; case Library::ArgumentChecks::MinSize::Type::VALUE: { MathLib::bigint myMinsize = minsize.value; const int baseSize = tokenizer->sizeOfType(minsize.baseType); - if (baseSize != 0) + if (baseSize != 0) { + if (mulOverflows(myMinsize, baseSize)) + return false; myMinsize *= baseSize; - return myMinsize <= bufferSize; + } + return myMinsize <= bufferSize.intvalue; } case Library::ArgumentChecks::MinSize::Type::NONE: break; @@ -640,7 +722,7 @@ static bool checkBufferSize(const Token *ftok, const Library::ArgumentChecks::Mi } -void CheckBufferOverrun::bufferOverflow() +void CheckBufferOverrunImpl::bufferOverflow() { logChecker("CheckBufferOverrun::bufferOverflow"); @@ -649,13 +731,13 @@ void CheckBufferOverrun::bufferOverflow() for (const Token *tok = scope->bodyStart; tok != scope->bodyEnd; tok = tok->next()) { if (!Token::Match(tok, "%name% (") || Token::simpleMatch(tok, ") {")) continue; - if (!mSettings->library.hasminsize(tok)) + if (!mSettings.library.hasminsize(tok)) continue; const std::vector args = getArguments(tok); - for (int argnr = 0; argnr < args.size(); ++argnr) { + for (size_t argnr = 0; argnr < args.size(); ++argnr) { if (!args[argnr]->valueType() || args[argnr]->valueType()->pointer == 0) continue; - const std::vector *minsizes = mSettings->library.argminsizes(tok, argnr + 1); + const std::vector *minsizes = mSettings.library.argminsizes(tok, argnr + 1); if (!minsizes || minsizes->empty()) continue; // Get buffer size.. @@ -669,7 +751,8 @@ void CheckBufferOverrun::bufferOverflow() if (argtok->valueType() && argtok->valueType()->pointer == 0) continue; // TODO: strcpy(buf+10, "hello"); - const ValueFlow::Value bufferSize = getBufferSize(argtok); + const bool strz = mSettings.library.isargstrz(tok, argnr + 1); + ValueFlow::Value bufferSize = getBufferSize(argtok, mSettings, !strz); if (bufferSize.intvalue <= 0) continue; // buffer size == 1 => do not warn for dynamic memory @@ -688,7 +771,7 @@ void CheckBufferOverrun::bufferOverflow() } } const bool error = std::none_of(minsizes->begin(), minsizes->end(), [&](const Library::ArgumentChecks::MinSize &minsize) { - return checkBufferSize(tok, minsize, args, bufferSize.intvalue, *mSettings, mTokenizer); + return checkBufferSize(tok, minsize, args, bufferSize, mSettings, mTokenizer); }); if (error) bufferOverflowError(args[argnr], &bufferSize, Certainty::normal); @@ -697,16 +780,19 @@ void CheckBufferOverrun::bufferOverflow() } } -void CheckBufferOverrun::bufferOverflowError(const Token *tok, const ValueFlow::Value *value, Certainty certainty) +void CheckBufferOverrunImpl::bufferOverflowError(const Token *tok, const ValueFlow::Value *value, Certainty certainty) { - reportError(getErrorPath(tok, value, "Buffer overrun"), Severity::error, "bufferAccessOutOfBounds", "Buffer is accessed out of bounds: " + (tok ? getRealBufferTok(tok)->expressionString() : "buf"), CWE_BUFFER_OVERRUN, certainty); + const auto errorPath = getErrorPath(tok, value, "Buffer overrun"); + const auto severity = !value || (value->isKnown() && !value->condition) ? Severity::error : Severity::warning; + const std::string msg = "Buffer is accessed out of bounds: " + (tok ? getRealBufferTok(tok)->expressionString() : "buf"); + reportError(errorPath, severity, "bufferAccessOutOfBounds", msg, CWE_BUFFER_OVERRUN, certainty); } //--------------------------------------------------------------------------- -void CheckBufferOverrun::arrayIndexThenCheck() +void CheckBufferOverrunImpl::arrayIndexThenCheck() { - if (!mSettings->severity.isEnabled(Severity::style)) + if (!mSettings.severity.isEnabled(Severity::style)) return; logChecker("CheckBufferOverrun::arrayIndexThenCheck"); // style @@ -748,7 +834,7 @@ void CheckBufferOverrun::arrayIndexThenCheck() } } -void CheckBufferOverrun::arrayIndexThenCheckError(const Token *tok, const std::string &indexName) +void CheckBufferOverrunImpl::arrayIndexThenCheckError(const Token *tok, const std::string &indexName) { reportError(tok, Severity::style, "arrayIndexThenCheck", "$symbol:" + indexName + "\n" @@ -761,10 +847,10 @@ void CheckBufferOverrun::arrayIndexThenCheckError(const Token *tok, const std::s //--------------------------------------------------------------------------- -void CheckBufferOverrun::stringNotZeroTerminated() +void CheckBufferOverrunImpl::stringNotZeroTerminated() { // this is currently 'inconclusive'. See TestBufferOverrun::terminateStrncpy3 - if (!mSettings->severity.isEnabled(Severity::warning) || !mSettings->certainty.isEnabled(Certainty::inconclusive)) + if (!mSettings.severity.isEnabled(Severity::warning) || !mSettings.certainty.isEnabled(Certainty::inconclusive)) return; logChecker("CheckBufferOverrun::stringNotZeroTerminated"); // warning,inconclusive @@ -780,7 +866,7 @@ void CheckBufferOverrun::stringNotZeroTerminated() const Token *sizeToken = args[2]; if (!sizeToken->hasKnownIntValue()) continue; - const ValueFlow::Value &bufferSize = getBufferSize(args[0]); + const ValueFlow::Value &bufferSize = getBufferSize(args[0], mSettings); if (bufferSize.intvalue < 0 || sizeToken->getKnownIntValue() < bufferSize.intvalue) continue; if (Token::simpleMatch(args[1], "(") && Token::simpleMatch(args[1]->astOperand1(), ". c_str") && args[1]->astOperand1()->astOperand1()) { @@ -803,7 +889,7 @@ void CheckBufferOverrun::stringNotZeroTerminated() const Token *rhs = tok2->next()->astOperand2(); if (!rhs || !rhs->hasKnownIntValue() || rhs->getKnownIntValue() != 0) continue; - if (isSameExpression(false, args[0], tok2->link()->astOperand1(), *mSettings, false, false)) + if (isSameExpression(false, args[0], tok2->link()->astOperand1(), mSettings, false, false)) isZeroTerminated = true; } if (isZeroTerminated) @@ -814,7 +900,7 @@ void CheckBufferOverrun::stringNotZeroTerminated() } } -void CheckBufferOverrun::terminateStrncpyError(const Token *tok, const std::string &varname) +void CheckBufferOverrunImpl::terminateStrncpyError(const Token *tok, const std::string &varname) { const std::string shortMessage = "The buffer '$symbol' may not be null-terminated after the call to strncpy()."; reportError(tok, Severity::warning, "terminateStrncpy", @@ -827,10 +913,10 @@ void CheckBufferOverrun::terminateStrncpyError(const Token *tok, const std::stri } //--------------------------------------------------------------------------- -void CheckBufferOverrun::argumentSize() +void CheckBufferOverrunImpl::argumentSize() { // Check '%type% x[10]' arguments - if (!mSettings->severity.isEnabled(Severity::warning) && !mSettings->isPremiumEnabled("argumentSize")) + if (!mSettings.severity.isEnabled(Severity::warning) && !mSettings.isPremiumEnabled("argumentSize")) return; logChecker("CheckBufferOverrun::argumentSize"); // warning @@ -844,7 +930,7 @@ void CheckBufferOverrun::argumentSize() // If argument is '%type% a[num]' then check bounds against num const Function *callfunc = tok->function(); const std::vector callargs = getArguments(tok); - for (nonneg int paramIndex = 0; paramIndex < callargs.size() && paramIndex < callfunc->argCount(); ++paramIndex) { + for (size_t paramIndex = 0; paramIndex < callargs.size() && paramIndex < callfunc->argCount(); ++paramIndex) { const Variable* const argument = callfunc->getArgumentVar(paramIndex); if (!argument || !argument->nameToken() || !argument->isArray()) continue; @@ -875,7 +961,7 @@ void CheckBufferOverrun::argumentSize() } } -void CheckBufferOverrun::argumentSizeError(const Token *tok, const std::string &functionName, nonneg int paramIndex, const std::string ¶mExpression, const Variable *paramVar, const Variable *functionArg) +void CheckBufferOverrunImpl::argumentSizeError(const Token *tok, const std::string &functionName, nonneg int paramIndex, const std::string ¶mExpression, const Variable *paramVar, const Variable *functionArg) { const std::string strParamNum = std::to_string(paramIndex + 1) + getOrdinalText(paramIndex + 1); ErrorPath errorPath; @@ -906,6 +992,8 @@ namespace /** data for multifile checking */ class MyFileInfo : public Check::FileInfo { public: + explicit MyFileInfo(std::string f0) : Check::FileInfo(std::move(f0)) {} + using Check::FileInfo::FileInfo; /** unsafe array index usage */ std::list unsafeArrayIndex; @@ -926,7 +1014,7 @@ namespace }; } -bool CheckBufferOverrun::isCtuUnsafeBufferUsage(const Settings &settings, const Token *argtok, CTU::FileInfo::Value *offset, int type) +bool CheckBufferOverrunImpl::isCtuUnsafeBufferUsage(const Settings &settings, const Token *argtok, CTU::FileInfo::Value *offset, int type) { if (!offset) return false; @@ -947,21 +1035,21 @@ bool CheckBufferOverrun::isCtuUnsafeBufferUsage(const Settings &settings, const return true; } -bool CheckBufferOverrun::isCtuUnsafeArrayIndex(const Settings &settings, const Token *argtok, CTU::FileInfo::Value *offset) +bool CheckBufferOverrunImpl::isCtuUnsafeArrayIndex(const Settings &settings, const Token *argtok, CTU::FileInfo::Value *offset) { return isCtuUnsafeBufferUsage(settings, argtok, offset, 1); } -bool CheckBufferOverrun::isCtuUnsafePointerArith(const Settings &settings, const Token *argtok, CTU::FileInfo::Value* offset) +bool CheckBufferOverrunImpl::isCtuUnsafePointerArith(const Settings &settings, const Token *argtok, CTU::FileInfo::Value* offset) { return isCtuUnsafeBufferUsage(settings, argtok, offset, 2); } /** @brief Parse current TU and extract file info */ -Check::FileInfo *CheckBufferOverrun::getFileInfo(const Tokenizer &tokenizer, const Settings &settings, const std::string& /*currentConfig*/) const +const Check::FileInfo *CheckBufferOverrun::getFileInfo(const Tokenizer &tokenizer, const Settings &settings, const std::string& /*currentConfig*/) const { - const std::list &unsafeArrayIndex = CTU::getUnsafeUsage(tokenizer, settings, isCtuUnsafeArrayIndex); - const std::list &unsafePointerArith = CTU::getUnsafeUsage(tokenizer, settings, isCtuUnsafePointerArith); + const std::list &unsafeArrayIndex = CTU::getUnsafeUsage(tokenizer, settings, CheckBufferOverrunImpl::isCtuUnsafeArrayIndex); + const std::list &unsafePointerArith = CTU::getUnsafeUsage(tokenizer, settings, CheckBufferOverrunImpl::isCtuUnsafePointerArith); if (unsafeArrayIndex.empty() && unsafePointerArith.empty()) { return nullptr; } @@ -971,13 +1059,12 @@ Check::FileInfo *CheckBufferOverrun::getFileInfo(const Tokenizer &tokenizer, con return fileInfo; } -Check::FileInfo * CheckBufferOverrun::loadFileInfoFromXml(const tinyxml2::XMLElement *xmlElement) const +const Check::FileInfo * CheckBufferOverrun::loadFileInfoFromXml(const tinyxml2::XMLElement *xmlElement, const std::string& file0) const { - // cppcheck-suppress shadowFunction - TODO: fix this const std::string arrayIndex("array-index"); const std::string pointerArith("pointer-arith"); - auto *fileInfo = new MyFileInfo; + auto *fileInfo = new MyFileInfo(file0); for (const tinyxml2::XMLElement *e = xmlElement->FirstChildElement(); e; e = e->NextSiblingElement()) { const char* name = e->Name(); if (name == arrayIndex) @@ -995,9 +1082,9 @@ Check::FileInfo * CheckBufferOverrun::loadFileInfoFromXml(const tinyxml2::XMLEle } /** @brief Analyse all file infos for all TU */ -bool CheckBufferOverrun::analyseWholeProgram(const CTU::FileInfo &ctu, const std::list &fileInfo, const Settings& settings, ErrorLogger &errorLogger) +bool CheckBufferOverrun::analyseWholeProgram(const CTU::FileInfo &ctu, const std::list &fileInfo, const Settings& settings, ErrorLogger &errorLogger) { - CheckBufferOverrun dummy(nullptr, &settings, &errorLogger); + CheckBufferOverrunImpl dummy(nullptr, settings, errorLogger); dummy. logChecker("CheckBufferOverrun::analyseWholeProgram"); @@ -1064,7 +1151,7 @@ bool CheckBufferOverrun::analyseWholeProgram1(const std::mapgetSymbolDatabase(); @@ -1083,9 +1170,15 @@ void CheckBufferOverrun::objectIndex() std::vector values = ValueFlow::getLifetimeObjValues(obj, false, -1); for (const ValueFlow::Value& v:values) { - if (v.lifetimeKind != ValueFlow::Value::LifetimeKind::Address) + if (v.lifetimeKind != ValueFlow::Value::LifetimeKind::Address && v.lifetimeKind != ValueFlow::Value::LifetimeKind::Object) continue; - const Variable *var = v.tokvalue->variable(); + const Token* varTok = v.tokvalue; + if (Token::simpleMatch(varTok->astParent(), ".")) { + varTok = varTok->astParent(); + while (Token::simpleMatch(varTok, ".")) + varTok = varTok->astOperand2(); + } + const Variable *var = varTok ? varTok->variable() : nullptr; if (!var) continue; if (var->isReference()) @@ -1103,7 +1196,7 @@ void CheckBufferOverrun::objectIndex() continue; } if (obj->valueType() && var->valueType() && (obj->isCast() || (obj->isCpp() && isCPPCast(obj)) || obj->valueType()->pointer)) { // allow cast to a different type - const auto varSize = var->valueType()->getSizeOf(*mSettings, ValueType::Accuracy::ExactOrZero, ValueType::SizeOf::Pointee); + const auto varSize = var->valueType()->getSizeOf(mSettings, ValueType::Accuracy::ExactOrZero, ValueType::SizeOf::Pointee); if (varSize == 0) continue; if (obj->valueType()->type != var->valueType()->type) { @@ -1136,7 +1229,7 @@ void CheckBufferOverrun::objectIndex() } } -void CheckBufferOverrun::objectIndexError(const Token *tok, const ValueFlow::Value *v, bool known) +void CheckBufferOverrunImpl::objectIndexError(const Token *tok, const ValueFlow::Value *v, bool known) { ErrorPath errorPath; std::string name; @@ -1173,7 +1266,7 @@ static bool isVLAIndex(const Token* tok) return isVLAIndex(tok->astOperand1()) || isVLAIndex(tok->astOperand2()); } -void CheckBufferOverrun::negativeArraySize() +void CheckBufferOverrunImpl::negativeArraySize() { logChecker("CheckBufferOverrun::negativeArraySize"); const SymbolDatabase* symbolDatabase = mTokenizer->getSymbolDatabase(); @@ -1183,7 +1276,7 @@ void CheckBufferOverrun::negativeArraySize() const Token* const nameToken = var->nameToken(); if (!Token::Match(nameToken, "%var% [") || !nameToken->next()->astOperand2()) continue; - const ValueFlow::Value* sz = nameToken->next()->astOperand2()->getValueLE(-1, *mSettings); + const ValueFlow::Value* sz = nameToken->next()->astOperand2()->getValueLE(-1, mSettings); // don't warn about constant negative index because that is a compiler error if (sz && isVLAIndex(nameToken->next()->astOperand2())) negativeArraySizeError(nameToken); @@ -1196,14 +1289,14 @@ void CheckBufferOverrun::negativeArraySize() const Token* valOperand = tok->astOperand1()->astOperand2(); if (!valOperand) continue; - const ValueFlow::Value* sz = valOperand->getValueLE(-1, *mSettings); + const ValueFlow::Value* sz = valOperand->getValueLE(-1, mSettings); if (sz) negativeMemoryAllocationSizeError(tok, sz); } } } -void CheckBufferOverrun::negativeArraySizeError(const Token* tok) +void CheckBufferOverrunImpl::negativeArraySizeError(const Token* tok) { const std::string arrayName = tok ? tok->expressionString() : std::string(); const std::string line1 = arrayName.empty() ? std::string() : ("$symbol:" + arrayName + '\n'); @@ -1212,7 +1305,7 @@ void CheckBufferOverrun::negativeArraySizeError(const Token* tok) "Declaration of array '" + arrayName + "' with negative size is undefined behaviour", CWE758, Certainty::normal); } -void CheckBufferOverrun::negativeMemoryAllocationSizeError(const Token* tok, const ValueFlow::Value* value) +void CheckBufferOverrunImpl::negativeMemoryAllocationSizeError(const Token* tok, const ValueFlow::Value* value) { const std::string msg = "Memory allocation size is negative."; ErrorPath errorPath = getErrorPath(tok, value, msg); @@ -1221,9 +1314,9 @@ void CheckBufferOverrun::negativeMemoryAllocationSizeError(const Token* tok, con msg, CWE131, inconclusive ? Certainty::inconclusive : Certainty::normal); } -void CheckBufferOverrun::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) +void CheckBufferOverrun::runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) { - CheckBufferOverrun checkBufferOverrun(&tokenizer, &tokenizer.getSettings(), errorLogger); + CheckBufferOverrunImpl checkBufferOverrun(&tokenizer, tokenizer.getSettings(), errorLogger); checkBufferOverrun.arrayIndex(); checkBufferOverrun.pointerArithmetic(); checkBufferOverrun.bufferOverflow(); @@ -1234,9 +1327,9 @@ void CheckBufferOverrun::runChecks(const Tokenizer &tokenizer, ErrorLogger *erro checkBufferOverrun.negativeArraySize(); } -void CheckBufferOverrun::getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const +void CheckBufferOverrun::getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const { - CheckBufferOverrun c(nullptr, settings, errorLogger); + CheckBufferOverrunImpl c(nullptr, settings, errorLogger); c.arrayIndexError(nullptr, std::vector(), std::vector()); c.pointerArithmeticError(nullptr, nullptr, nullptr); c.negativeIndexError(nullptr, std::vector(), std::vector()); diff --git a/lib/checkbufferoverrun.h b/lib/checkbufferoverrun.h index 9c6e1f0386d..afac19d796c 100644 --- a/lib/checkbufferoverrun.h +++ b/lib/checkbufferoverrun.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,6 +23,7 @@ //--------------------------------------------------------------------------- #include "check.h" +#include "checkimpl.h" #include "config.h" #include "ctu.h" @@ -60,22 +61,43 @@ namespace ValueFlow class CPPCHECKLIB CheckBufferOverrun : public Check { public: /** This constructor is used when registering the CheckClass */ - CheckBufferOverrun() : Check(myName()) {} + CheckBufferOverrun() : Check("Bounds checking") {} private: - /** This constructor is used when running checks. */ - CheckBufferOverrun(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) - : Check(myName(), tokenizer, settings, errorLogger) {} - - void runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) override; - - void getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const override; + void runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) override; /** @brief Parse current TU and extract file info */ - Check::FileInfo *getFileInfo(const Tokenizer &tokenizer, const Settings &settings, const std::string& /*currentConfig*/) const override; + const Check::FileInfo *getFileInfo(const Tokenizer &tokenizer, const Settings &settings, const std::string& /*currentConfig*/) const override; /** @brief Analyse all file infos for all TU */ - bool analyseWholeProgram(const CTU::FileInfo &ctu, const std::list &fileInfo, const Settings& settings, ErrorLogger &errorLogger) override; + bool analyseWholeProgram(const CTU::FileInfo &ctu, const std::list &fileInfo, const Settings& settings, ErrorLogger &errorLogger) override; + + void getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const override; + + const Check::FileInfo * loadFileInfoFromXml(const tinyxml2::XMLElement *xmlElement, const std::string& file0) const override; + + std::string classInfo() const override { + return "Out of bounds checking:\n" + "- Array index out of bounds\n" + "- Pointer arithmetic overflow\n" + "- Buffer overflow\n" + "- Dangerous usage of strncat()\n" + "- Using array index before checking it\n" + "- Partial string write that leads to buffer that is not zero terminated.\n" + "- Check for large enough arrays being passed to functions\n" + "- Allocating memory with a negative size\n"; + } + + static bool analyseWholeProgram1(const std::map> &callsMap, const CTU::FileInfo::UnsafeUsage &unsafeUsage, + int type, ErrorLogger &errorLogger, int maxCtuDepth, const std::string& file0); +}; + +class CPPCHECKLIB CheckBufferOverrunImpl : public CheckImpl +{ +public: + /** This constructor is used when running checks. */ + CheckBufferOverrunImpl(const Tokenizer *tokenizer, const Settings &settings, ErrorLogger &errorLogger) + : CheckImpl(tokenizer, settings, errorLogger) {} void arrayIndex(); void arrayIndexError(const Token* tok, @@ -107,33 +129,12 @@ class CPPCHECKLIB CheckBufferOverrun : public Check { void objectIndex(); void objectIndexError(const Token *tok, const ValueFlow::Value *v, bool known); - ValueFlow::Value getBufferSize(const Token *bufTok) const; + ValueFlow::Value getBufferSize(const Token *bufTok, const Settings& settings, bool checkStringLiteral = false) const; // CTU static bool isCtuUnsafeBufferUsage(const Settings &settings, const Token *argtok, CTU::FileInfo::Value *offset, int type); static bool isCtuUnsafeArrayIndex(const Settings &settings, const Token *argtok, CTU::FileInfo::Value *offset); static bool isCtuUnsafePointerArith(const Settings &settings, const Token *argtok, CTU::FileInfo::Value *offset); - - Check::FileInfo * loadFileInfoFromXml(const tinyxml2::XMLElement *xmlElement) const override; - static bool analyseWholeProgram1(const std::map> &callsMap, const CTU::FileInfo::UnsafeUsage &unsafeUsage, - int type, ErrorLogger &errorLogger, int maxCtuDepth, const std::string& file0); - - - static std::string myName() { - return "Bounds checking"; - } - - std::string classInfo() const override { - return "Out of bounds checking:\n" - "- Array index out of bounds\n" - "- Pointer arithmetic overflow\n" - "- Buffer overflow\n" - "- Dangerous usage of strncat()\n" - "- Using array index before checking it\n" - "- Partial string write that leads to buffer that is not zero terminated.\n" - "- Check for large enough arrays being passed to functions\n" - "- Allocating memory with a negative size\n"; - } }; /// @} //--------------------------------------------------------------------------- diff --git a/lib/checkclass.cpp b/lib/checkclass.cpp index ebdb434bb24..a6fb1aecac4 100644 --- a/lib/checkclass.cpp +++ b/lib/checkclass.cpp @@ -43,11 +43,6 @@ //--------------------------------------------------------------------------- -// Register CheckClass.. -namespace { - CheckClass instance; -} - static const CWE CWE398(398U); // Indicator of Poor Code Quality static const CWE CWE404(404U); // Improper Resource Shutdown or Release static const CWE CWE665(665U); // Improper Initialization @@ -111,27 +106,177 @@ static bool isVclTypeInit(const Type *type) } //--------------------------------------------------------------------------- -CheckClass::CheckClass(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) - : Check(myName(), tokenizer, settings, errorLogger), +CheckClassImpl::CheckClassImpl(const Tokenizer *tokenizer, const Settings &settings, ErrorLogger &errorLogger) + : CheckImpl(tokenizer, settings, errorLogger), mSymbolDatabase(tokenizer?tokenizer->getSymbolDatabase():nullptr) {} +CheckClassImpl::Initialization CheckClassImpl::checkInitialization(const Usage& usage, FunctionType funcType, bool noConstructor) const +{ + const Variable& var = *usage.var; + + if (usage.assign || usage.init || var.isStatic()) + return Initialization::Initialized; + + if (!var.nameToken() || var.nameToken()->isAnonymous()) + return Initialization::None; + + if (var.valueType() && var.valueType()->pointer == 0 && var.type() && var.type()->needInitialization == Type::NeedInitialization::False && var.type()->derivedFrom.empty()) + return Initialization::Initialized; + + if (var.isConst() && (funcType == FunctionType::eOperatorEqual || // We can't set const members in assignment operator + (funcType == FunctionType::eConstructor && noConstructor))) // a const member of class type is default constructed or makes the class not default constructible + return Initialization::None; + + if (funcType == FunctionType::eConstructor && noConstructor && var.nameToken()->isBitfield() && mSettings.standards.cpp < Standards::CPP20) + return Initialization::None; + + if (var.isArray() && ((var.dimensionKnown(0) && var.dimensions()[0].num == 0) || Token::simpleMatch(var.nameToken()->next(), "[ ]"))) // HACK for unreliable dimensions + return Initialization::None; + + // Check if this is a class constructor + if (!var.isPointer() && !var.isPointerArray() && var.isClass() && funcType == FunctionType::eConstructor) { + // Unknown type so assume it is initialized + if (!var.type()) { + if (var.isStlType() && var.valueType() && var.valueType()->containerTypeToken) { + if (var.valueType()->type == ValueType::Type::ITERATOR) + { + // needs initialization + } + else if (var.getTypeName() == "std::array") { + const Token* ctt = var.valueType()->containerTypeToken; + if (!ctt->isStandardType() && + (!ctt->type() || ctt->type()->needInitialization != Type::NeedInitialization::True) && + !mSettings.library.podtype(ctt->str())) // TODO: handle complex type expression + return Initialization::Initialized; + } + else + return Initialization::Initialized; + } + else + return Initialization::Initialized; + } + + // Known type that doesn't need initialization or + // known type that has member variables of an unknown type + else if (var.type()->needInitialization != Type::NeedInitialization::True) + return Initialization::Initialized; + } + + // Check if type can't be copied + if (!var.isPointer() && !var.isPointerArray() && var.typeScope()) { + if (funcType == FunctionType::eMoveConstructor) { + if (canNotMove(var.typeScope())) + return Initialization::None; + } + else { + if (canNotCopy(var.typeScope())) + return Initialization::None; + } + } + return Initialization::Missing; +} + +void CheckClassImpl::handleUnionMembers(std::vector& usageList) +{ + // Assign 1 union member => assign all union members + for (const Usage& usage : usageList) { + const Variable& var = *usage.var; + if (!usage.assign && !usage.init) + continue; + const Scope* varScope1 = var.nameToken()->scope(); + while (varScope1->type == ScopeType::eStruct) + varScope1 = varScope1->nestedIn; + if (varScope1->type == ScopeType::eUnion) { + for (Usage& usage2 : usageList) { + const Variable& var2 = *usage2.var; + if (usage2.assign || usage2.init || var2.isStatic()) + continue; + const Scope* varScope2 = var2.nameToken()->scope(); + while (varScope2->type == ScopeType::eStruct) + varScope2 = varScope2->nestedIn; + if (varScope1 == varScope2) + usage2.assign = true; + } + } + } +} + +static std::vector getInheritedConstructors(const Scope *scope) +{ + std::vector result; + + if (!scope || !scope->bodyStart || !scope->definedType) + return result; + + const auto isInherited = [](const Function &baseCtor, const Scope *scope) { + if (baseCtor.type != FunctionType::eConstructor || baseCtor.argCount() == 0) + return false; + if (baseCtor.isDelete() || baseCtor.access == AccessControl::Private) + return false; + return std::none_of(scope->functionList.cbegin(), scope->functionList.cend(), [&](const Function &func) { + return func.isConstructor() && func.argCount() == baseCtor.argCount() && + func.argsMatch(baseCtor.nestedIn, baseCtor.argDef, func.argDef, "", 0); + }); + }; + + const std::vector &bases = scope->definedType->derivedFrom; + const Token *baseName = nullptr; + const std::list *baseFuncs = nullptr; + for (const Token *tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) { + if (Token::Match(tok, "using %name%")) { + const auto base = std::find_if( + bases.cbegin(), + bases.cend(), + [&tok](const Type::BaseInfo &info) { + return info.name == tok->strAt(1); + } + ); + if (base != bases.cend() && base->type && base->type->classScope) { + baseName = tok->next(); + baseFuncs = &base->type->classScope->functionList; + } + continue; + } + + if (tok->str() == ";") { + baseName = nullptr; + continue; + } + + if (tok->str() == "{") { + baseName = nullptr; + tok = tok->link(); + continue; + } + + if (baseName && Token::Match(tok, "%name% :: %name% ;") && + tok->str() == baseName->str() && tok->strAt(2) == baseName->str() && + std::any_of(baseFuncs->cbegin(), baseFuncs->cend(), [&](const Function &func) { + return isInherited(func, scope); + })) + result.push_back(tok->tokAt(2)); + } + + return result; +} + //--------------------------------------------------------------------------- // ClassCheck: Check that all class constructors are ok. //--------------------------------------------------------------------------- -void CheckClass::constructors() +void CheckClassImpl::constructors() { - const bool printStyle = mSettings->severity.isEnabled(Severity::style); - const bool printWarnings = mSettings->severity.isEnabled(Severity::warning); - if (!printStyle && !printWarnings && !mSettings->isPremiumEnabled("uninitMemberVar")) + const bool printStyle = mSettings.severity.isEnabled(Severity::style); + const bool printWarnings = mSettings.severity.isEnabled(Severity::warning); + if (!printStyle && !printWarnings && !mSettings.isPremiumEnabled("uninitMemberVar")) return; logChecker("CheckClass::checkConstructors"); // style,warning - const bool printInconclusive = mSettings->certainty.isEnabled(Certainty::inconclusive); + const bool printInconclusive = mSettings.certainty.isEnabled(Certainty::inconclusive); for (const Scope * scope : mSymbolDatabase->classAndStructScopes) { - if (mSettings->hasLib("vcl") && isVclTypeInit(scope->definedType)) + if (mSettings.hasLib("vcl") && isVclTypeInit(scope->definedType)) continue; const bool unusedTemplate = Token::simpleMatch(scope->classDef->previous(), ">"); @@ -144,8 +289,11 @@ void CheckClass::constructors() }); }); + const std::vector inheritedCtors = getInheritedConstructors(scope); + // There are no constructors. - if (scope->numConstructors == 0 && printStyle && !usedInUnion) { + std::set diagVars; + if (scope->numConstructors == 0 && inheritedCtors.empty() && printStyle && !usedInUnion) { // If there is a private variable, there should be a constructor.. int needInit = 0, haveInit = 0; std::vector uninitVars; @@ -163,8 +311,10 @@ void CheckClass::constructors() if (haveInit == 0) noConstructorError(scope->classDef, scope->className, scope->classDef->str() == "struct"); else - for (const Variable* uv : uninitVars) + for (const Variable* uv : uninitVars) { uninitVarError(uv->typeStartToken(), uv->scope()->className, uv->name()); + diagVars.emplace(uv); + } } } @@ -201,85 +351,15 @@ void CheckClass::constructors() std::list callstack; initializeVarList(func, callstack, scope, usageList); - // Assign 1 union member => assign all union members - for (const Usage &usage : usageList) { - const Variable& var = *usage.var; - if (!usage.assign && !usage.init) - continue; - const Scope* varScope1 = var.nameToken()->scope(); - while (varScope1->type == ScopeType::eStruct) - varScope1 = varScope1->nestedIn; - if (varScope1->type == ScopeType::eUnion) { - for (Usage &usage2 : usageList) { - const Variable& var2 = *usage2.var; - if (usage2.assign || usage2.init || var2.isStatic()) - continue; - const Scope* varScope2 = var2.nameToken()->scope(); - while (varScope2->type == ScopeType::eStruct) - varScope2 = varScope2->nestedIn; - if (varScope1 == varScope2) - usage2.assign = true; - } - } - } + handleUnionMembers(usageList); // Check if any variables are uninitialized for (const Usage &usage : usageList) { - const Variable& var = *usage.var; - - if (usage.assign || usage.init || var.isStatic()) - continue; - - if (!var.nameToken() || var.nameToken()->isAnonymous()) - continue; - - if (var.valueType() && var.valueType()->pointer == 0 && var.type() && var.type()->needInitialization == Type::NeedInitialization::False && var.type()->derivedFrom.empty()) - continue; - - if (var.isConst() && func.isOperator()) // We can't set const members in assignment operator + if (checkInitialization(usage, func.type) != Initialization::Missing) continue; - // Check if this is a class constructor - if (!var.isPointer() && !var.isPointerArray() && var.isClass() && func.type == FunctionType::eConstructor) { - // Unknown type so assume it is initialized - if (!var.type()) { - if (var.isStlType() && var.valueType() && var.valueType()->containerTypeToken) { - if (var.valueType()->type == ValueType::Type::ITERATOR) - { - // needs initialization - } - else if (var.getTypeName() == "std::array") { - const Token* ctt = var.valueType()->containerTypeToken; - if (!ctt->isStandardType() && - (!ctt->type() || ctt->type()->needInitialization != Type::NeedInitialization::True) && - !mSettings->library.podtype(ctt->str())) // TODO: handle complex type expression - continue; - } - else - continue; - } - else - continue; - } - - // Known type that doesn't need initialization or - // known type that has member variables of an unknown type - else if (var.type()->needInitialization != Type::NeedInitialization::True) - continue; - } - - // Check if type can't be copied - if (!var.isPointer() && !var.isPointerArray() && var.typeScope()) { - if (func.type == FunctionType::eMoveConstructor) { - if (canNotMove(var.typeScope())) - continue; - } else { - if (canNotCopy(var.typeScope())) - continue; - } - } - // Is there missing member copy in copy/move constructor or assignment operator? + const Variable& var = *usage.var; bool missingCopy = false; // Don't warn about unknown types in copy constructors since we @@ -303,9 +383,9 @@ void CheckClass::constructors() } } - if (classNameUsed && mSettings->library.getTypeCheck("operatorEqVarError", var.getTypeName()) != Library::TypeCheck::suppress) + if (classNameUsed && mSettings.library.getTypeCheck("operatorEqVarError", var.getTypeName()) != Library::TypeCheck::suppress) operatorEqVarError(func.token, scope->className, var.name(), missingCopy); - } else if (func.access != AccessControl::Private || mSettings->standards.cpp >= Standards::CPP11) { + } else if (func.access != AccessControl::Private || mSettings.standards.cpp >= Standards::CPP11) { // If constructor is not in scope then we maybe using a constructor from a different template specialization if (!precedes(scope->bodyStart, func.tokenDef)) continue; @@ -326,6 +406,72 @@ void CheckClass::constructors() } } } + + if (!inheritedCtors.empty()) { + clearAllVar(usageList); + + for (Usage &usage : usageList) { + if (usage.var->hasDefault()) + usage.init = true; + } + + handleUnionMembers(usageList); + + // Inherited constructors don't initialize members of the derived class + for (const Token *baseCtorTok : inheritedCtors) { + for (const Usage &usage : usageList) { + if (checkInitialization(usage, FunctionType::eConstructor) != Initialization::Missing) + continue; + const Variable& var = *usage.var; + if (var.typeScope() && var.typeScope()->numConstructors > 0) + continue; + uninitVarError(baseCtorTok, false, FunctionType::eConstructor, var.scope()->className, var.name(), scope != var.scope(), false, false, true); + diagVars.emplace(&var); + } + } + } + + if (scope->numConstructors == 0) { + + // Mark all variables not used + clearAllVar(usageList); + + // Variables with default initializers + bool hasAnyDefaultInit = false; + bool hasAnySelfInit = false; + bool isDefaultConstructible = true; + const bool cpp14OrLater = mSettings.standards.cpp >= Standards::CPP14; + for (Usage& usage : usageList) { + const Variable& var = *usage.var; + // check for C++11 initializer + if (var.hasDefault()) { + usage.init = true; + hasAnyDefaultInit = true; + } else if (!var.isStatic() && (var.isReference() || (var.isConst() && !var.isClass()))) { + // the default constructor is deleted, so all members are initialized by every instance + isDefaultConstructible = false; + } else if (cpp14OrLater && !hasAnySelfInit && checkInitialization(usage, FunctionType::eConstructor) == Initialization::Initialized) { + hasAnySelfInit = true; + } + } + if (!isDefaultConstructible || (!hasAnyDefaultInit && !hasAnySelfInit)) + continue; + + handleUnionMembers(usageList); + + // Check if any variables are uninitialized + for (const Usage& usage : usageList) { + if (checkInitialization(usage, FunctionType::eConstructor, /*noConstructor*/ true) != Initialization::Missing) + continue; + + const Variable& var = *usage.var; + if (var.typeScope() && var.typeScope()->numConstructors > 0) + continue; + + if (diagVars.count(&var) == 0) + uninitVarError(var.nameToken(), false, FunctionType::eConstructor, var.scope()->className, var.name(), false, false, true); + } + } } } @@ -334,9 +480,9 @@ static bool isPermissibleConversion(const std::string& type) return type == "std::initializer_list" || type == "std::nullptr_t"; } -void CheckClass::checkExplicitConstructors() +void CheckClassImpl::checkExplicitConstructors() { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("noExplicitConstructor")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("noExplicitConstructor")) return; logChecker("CheckClass::checkExplicitConstructors"); // style @@ -354,7 +500,7 @@ void CheckClass::checkExplicitConstructors() // Abstract classes can't be instantiated. But if there is C++11 // "misuse" by derived classes then these constructors must be explicit. - if (isAbstractClass && mSettings->standards.cpp >= Standards::CPP11) + if (isAbstractClass && mSettings.standards.cpp >= Standards::CPP11) continue; for (const Function &func : scope->functionList) { @@ -403,9 +549,9 @@ static bool hasNonCopyableBase(const Scope *scope, bool *unknown) return false; } -void CheckClass::copyconstructors() +void CheckClassImpl::copyconstructors() { - if (!mSettings->severity.isEnabled(Severity::warning)) + if (!mSettings.severity.isEnabled(Severity::warning)) return; logChecker("CheckClass::checkCopyConstructors"); // warning @@ -420,17 +566,17 @@ void CheckClass::copyconstructors() const Token* tok = func.token->linkAt(1); for (const Token* const end = func.functionScope->bodyStart; tok != end; tok = tok->next()) { if (Token::Match(tok, "%var% ( new") || - (Token::Match(tok, "%var% ( %name% (") && mSettings->library.getAllocFuncInfo(tok->tokAt(2)))) { + (Token::Match(tok, "%var% ( %name% (") && mSettings.library.getAllocFuncInfo(tok->tokAt(2)))) { const Variable* var = tok->variable(); - if (var && var->isPointer() && var->scope() == scope) + if (var && var->scope() == scope && var->valueType() && var->valueType()->type != ValueType::SMART_POINTER) allocatedVars[tok->varId()] = tok; } } for (const Token* const end = func.functionScope->bodyEnd; tok != end; tok = tok->next()) { if (Token::Match(tok, "%var% = new") || - (Token::Match(tok, "%var% = %name% (") && mSettings->library.getAllocFuncInfo(tok->tokAt(2)))) { + (Token::Match(tok, "%var% = %name% (") && mSettings.library.getAllocFuncInfo(tok->tokAt(2)))) { const Variable* var = tok->variable(); - if (var && var->isPointer() && var->scope() == scope && !var->isStatic()) + if (var && var->scope() == scope && !var->isStatic() && var->valueType() && var->valueType()->type != ValueType::SMART_POINTER) allocatedVars[tok->varId()] = tok; } } @@ -439,10 +585,13 @@ void CheckClass::copyconstructors() const Token* tok = func.functionScope->bodyStart; for (const Token* const end = func.functionScope->bodyEnd; tok != end; tok = tok->next()) { if (Token::Match(tok, "delete %var%") || - (Token::Match(tok, "%name% ( %var%") && mSettings->library.getDeallocFuncInfo(tok))) { + (Token::Match(tok, "%name% ( %var%") && mSettings.library.getDeallocFuncInfo(tok))) { const Token *vartok = tok->str() == "delete" ? tok->next() : tok->tokAt(2); const Variable* var = vartok->variable(); - if (var && var->isPointer() && var->scope() == scope && !var->isStatic()) + if (var && var->scope() == scope && !var->isStatic() && + var->valueType() && ((var->valueType()->type != ValueType::CONTAINER && + var->valueType()->type != ValueType::RECORD && + var->valueType()->type != ValueType::UNKNOWN_TYPE) || var->valueType()->pointer)) deallocatedVars[vartok->varId()] = vartok; } } @@ -529,7 +678,7 @@ void CheckClass::copyconstructors() } for (tok = func.functionScope->bodyStart; tok != func.functionScope->bodyEnd; tok = tok->next()) { if ((tok->isCpp() && Token::Match(tok, "%var% = new")) || - (Token::Match(tok, "%var% = %name% (") && (mSettings->library.getAllocFuncInfo(tok->tokAt(2)) || mSettings->library.getReallocFuncInfo(tok->tokAt(2))))) { + (Token::Match(tok, "%var% = %name% (") && (mSettings.library.getAllocFuncInfo(tok->tokAt(2)) || mSettings.library.getReallocFuncInfo(tok->tokAt(2))))) { allocatedVars.erase(tok->varId()); } else if (Token::Match(tok, "%var% = %name% . %name% ;") && allocatedVars.find(tok->varId()) != allocatedVars.end()) { copiedVars.insert(tok); @@ -560,7 +709,7 @@ void CheckClass::copyconstructors() } */ -void CheckClass::copyConstructorShallowCopyError(const Token *tok, const std::string& varname) +void CheckClassImpl::copyConstructorShallowCopyError(const Token *tok, const std::string& varname) { reportError(tok, Severity::warning, "copyCtorPointerCopying", "$symbol:" + varname + "\nValue of pointer '$symbol', which points to allocated memory, is copied in copy constructor instead of allocating new memory.", CWE398, Certainty::normal); @@ -586,22 +735,22 @@ static std::string noMemberErrorMessage(const Scope *scope, const char function[ return errmsg; } -void CheckClass::noCopyConstructorError(const Scope *scope, bool isdefault, const Token *alloc, bool inconclusive) +void CheckClassImpl::noCopyConstructorError(const Scope *scope, bool isdefault, const Token *alloc, bool inconclusive) { reportError(alloc, Severity::warning, "noCopyConstructor", noMemberErrorMessage(scope, "copy constructor", isdefault), CWE398, inconclusive ? Certainty::inconclusive : Certainty::normal); } -void CheckClass::noOperatorEqError(const Scope *scope, bool isdefault, const Token *alloc, bool inconclusive) +void CheckClassImpl::noOperatorEqError(const Scope *scope, bool isdefault, const Token *alloc, bool inconclusive) { reportError(alloc, Severity::warning, "noOperatorEq", noMemberErrorMessage(scope, "operator=", isdefault), CWE398, inconclusive ? Certainty::inconclusive : Certainty::normal); } -void CheckClass::noDestructorError(const Scope *scope, bool isdefault, const Token *alloc) +void CheckClassImpl::noDestructorError(const Scope *scope, bool isdefault, const Token *alloc) { reportError(alloc, Severity::warning, "noDestructor", noMemberErrorMessage(scope, "destructor", isdefault), CWE398, Certainty::normal); } -bool CheckClass::canNotCopy(const Scope *scope) +bool CheckClassImpl::canNotCopy(const Scope *scope) { bool constructor = false; bool publicAssign = false; @@ -625,7 +774,7 @@ bool CheckClass::canNotCopy(const Scope *scope) return constructor && !(publicAssign || publicCopy); } -bool CheckClass::canNotMove(const Scope *scope) +bool CheckClassImpl::canNotMove(const Scope *scope) { bool constructor = false; bool publicAssign = false; @@ -670,7 +819,7 @@ static void getAllVariableMembers(const Scope *scope, std::vector CheckClass::createUsageList(const Scope *scope) +std::vector CheckClassImpl::createUsageList(const Scope *scope) { std::vector ret; std::vector varlist; @@ -682,7 +831,7 @@ std::vector CheckClass::createUsageList(const Scope *scope) return ret; } -void CheckClass::assignVar(std::vector &usageList, nonneg int varid) +void CheckClassImpl::assignVar(std::vector &usageList, nonneg int varid) { auto it = std::find_if(usageList.begin(), usageList.end(), [varid](const Usage& usage) { return usage.var->declarationId() == varid; @@ -691,7 +840,7 @@ void CheckClass::assignVar(std::vector &usageList, nonneg int varid) it->assign = true; } -void CheckClass::assignVar(std::vector &usageList, const Token* vartok) +void CheckClassImpl::assignVar(std::vector &usageList, const Token* vartok) { if (vartok->varId() > 0) { assignVar(usageList, vartok->varId()); @@ -705,7 +854,7 @@ void CheckClass::assignVar(std::vector &usageList, const Token* vartok) it->assign = true; } -void CheckClass::initVar(std::vector &usageList, nonneg int varid) +void CheckClassImpl::initVar(std::vector &usageList, nonneg int varid) { auto it = std::find_if(usageList.begin(), usageList.end(), [varid](const Usage& usage) { return usage.var->declarationId() == varid; @@ -714,13 +863,13 @@ void CheckClass::initVar(std::vector &usageList, nonneg int varid) it->init = true; } -void CheckClass::assignAllVar(std::vector &usageList) +void CheckClassImpl::assignAllVar(std::vector &usageList) { for (Usage & i : usageList) i.assign = true; } -void CheckClass::assignAllVarsVisibleFromScope(std::vector& usageList, const Scope* scope) +void CheckClassImpl::assignAllVarsVisibleFromScope(std::vector& usageList, const Scope* scope) { for (Usage& usage : usageList) { if (usage.var->scope() == scope) @@ -736,7 +885,7 @@ void CheckClass::assignAllVarsVisibleFromScope(std::vector& usageList, co } } -void CheckClass::clearAllVar(std::vector &usageList) +void CheckClassImpl::clearAllVar(std::vector &usageList) { for (Usage & i : usageList) { i.assign = false; @@ -744,7 +893,7 @@ void CheckClass::clearAllVar(std::vector &usageList) } } -bool CheckClass::isBaseClassMutableMemberFunc(const Token *tok, const Scope *scope) +bool CheckClassImpl::isBaseClassMutableMemberFunc(const Token *tok, const Scope *scope) { // Iterate through each base class... for (const Type::BaseInfo & i : scope->definedType->derivedFrom) { @@ -771,7 +920,7 @@ bool CheckClass::isBaseClassMutableMemberFunc(const Token *tok, const Scope *sco return false; } -void CheckClass::initializeVarList(const Function &func, std::list &callstack, const Scope *scope, std::vector &usage) const +void CheckClassImpl::initializeVarList(const Function &func, std::list &callstack, const Scope *scope, std::vector &usage) const { if (!func.functionScope) return; @@ -1008,7 +1157,7 @@ void CheckClass::initializeVarList(const Function &func, std::listnext(); if (tok2->str() == "&") tok2 = tok2->next(); - if (isVariableChangedByFunctionCall(tok2, tok2->strAt(-1) == "&", tok2->varId(), *mSettings, nullptr)) + if (isVariableChangedByFunctionCall(tok2, tok2->strAt(-1) == "&", tok2->varId(), mSettings.library, nullptr)) assignVar(usage, tok2->varId()); } } @@ -1103,7 +1252,7 @@ void CheckClass::initializeVarList(const Function &func, std::liststr() + "'." : + "is not initialized in the inherited constructor."; + } else { + message += "is not initialized in the "; + if (functionType == FunctionType::eCopyConstructor) + message += "copy "; + else if (functionType == FunctionType::eMoveConstructor) + message += "move "; + message += "constructor."; + } if (derived) message += " Maybe it should be initialized directly in the class " + classname + "?"; - std::string id = std::string("uninit") + (derived ? "Derived" : "") + "MemberVar" + (isprivate ? "Private" : ""); + std::string id = std::string("uninit") + (derived ? "Derived" : "") + "MemberVar" + (isprivate ? "Private" : "") + (noCtor ? "NoCtor" : ""); const std::string verbose {message + " Member variables of native types, pointers, or references are left uninitialized when the class is instantiated. That may cause bugs or undefined behavior."}; reportError(tok, Severity::warning, id, "$symbol:" + classname + "::" + varname + '\n' + message + '\n' + verbose, CWE398, inconclusive ? Certainty::inconclusive : Certainty::normal); } -void CheckClass::uninitVarError(const Token *tok, const std::string &classname, const std::string &varname) +void CheckClassImpl::uninitVarError(const Token *tok, const std::string &classname, const std::string &varname) { const std::string message("Member variable '$symbol' is not initialized."); // report missing in-class initializer const std::string verbose {message + " Member variables of native types, pointers, or references are left uninitialized when the class is instantiated. That may cause bugs or undefined behavior."}; @@ -1141,7 +1298,7 @@ void CheckClass::uninitVarError(const Token *tok, const std::string &classname, reportError(tok, Severity::warning, id, "$symbol:" + classname + "::" + varname + '\n' + message + '\n' + verbose, CWE398, Certainty::normal); } -void CheckClass::missingMemberCopyError(const Token *tok, FunctionType functionType, const std::string& classname, const std::string& varname) +void CheckClassImpl::missingMemberCopyError(const Token *tok, FunctionType functionType, const std::string& classname, const std::string& varname) { const std::string ctor(functionType == FunctionType::eCopyConstructor ? "copy" : "move"); const std::string action(functionType == FunctionType::eCopyConstructor ? "copied?" : "moved?"); @@ -1151,7 +1308,7 @@ void CheckClass::missingMemberCopyError(const Token *tok, FunctionType functionT reportError(tok, Severity::warning, "missingMemberCopy", message, CWE398, Certainty::inconclusive); } -void CheckClass::operatorEqVarError(const Token *tok, const std::string &classname, const std::string &varname, bool inconclusive) +void CheckClassImpl::operatorEqVarError(const Token *tok, const std::string &classname, const std::string &varname, bool inconclusive) { reportError(tok, Severity::warning, "operatorEqVarError", "$symbol:" + classname + "::" + varname + "\nMember variable '$symbol' is not assigned a value in '" + classname + "::operator='.", CWE398, inconclusive ? Certainty::inconclusive : Certainty::normal); } @@ -1160,9 +1317,9 @@ void CheckClass::operatorEqVarError(const Token *tok, const std::string &classna // ClassCheck: Use initialization list instead of assignment //--------------------------------------------------------------------------- -void CheckClass::initializationListUsage() +void CheckClassImpl::initializationListUsage() { - if (!mSettings->severity.isEnabled(Severity::performance)) + if (!mSettings.severity.isEnabled(Severity::performance)) return; logChecker("CheckClass::initializationListUsage"); // performance @@ -1192,7 +1349,7 @@ void CheckClass::initializationListUsage() continue; if (var->isPointer() || var->isReference() || var->isEnumType()) continue; - if (!WRONG_DATA(!var->valueType(), tok) && var->valueType()->type > ValueType::Type::ITERATOR) + if (!var->valueType() || var->valueType()->type > ValueType::Type::ITERATOR) continue; // bailout: multi line lambda in rhs => do not warn @@ -1223,7 +1380,7 @@ void CheckClass::initializationListUsage() allowed = false; return ChildrenToVisit::done; } - if (var2->isLocal() && isVariableChanged(var2->nameToken(), previousBeforeAstLeftmostLeaf(tok), var2->declarationId(), /*globalvar*/ false, *mSettings)) { + if (var2->isLocal() && isVariableChanged(var2->nameToken(), previousBeforeAstLeftmostLeaf(tok), var2->declarationId(), /*globalvar*/ false, mSettings)) { allowed = false; return ChildrenToVisit::done; } @@ -1244,7 +1401,7 @@ void CheckClass::initializationListUsage() } } -void CheckClass::suggestInitializationList(const Token* tok, const std::string& varname) +void CheckClassImpl::suggestInitializationList(const Token* tok, const std::string& varname) { reportError(tok, Severity::performance, "useInitializationList", "$symbol:" + varname + "\nVariable '$symbol' is assigned in constructor body. Consider performing initialization in initialization list.\n" "When an object of a class is created, the constructors of all member variables are called consecutively " @@ -1306,9 +1463,9 @@ static bool checkFunctionUsage(const Function *privfunc, const Scope* scope) return false; // Unused in this scope } -void CheckClass::privateFunctions() +void CheckClassImpl::privateFunctions() { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("unusedPrivateFunction")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("unusedPrivateFunction")) return; logChecker("CheckClass::privateFunctions"); // style @@ -1366,11 +1523,11 @@ void CheckClass::privateFunctions() } } -void CheckClass::unusedPrivateFunctionError(const Token* tok1, const Token *tok2, const std::string &classname, const std::string &funcname) +void CheckClassImpl::unusedPrivateFunctionError(const Token* tokImpl, const Token *tokDef, const std::string &classname, const std::string &funcname) { - std::list toks{ tok1 }; - if (tok2) - toks.push_front(tok2); + std::list toks{ tokDef }; + if (tokDef != tokImpl) + toks.push_back(tokImpl); reportError(toks, Severity::style, "unusedPrivateFunction", "$symbol:" + classname + "::" + funcname + "\nUnused private function: '$symbol'", CWE398, Certainty::normal); } @@ -1388,10 +1545,10 @@ static const Scope* findFunctionOf(const Scope* scope) return nullptr; } -void CheckClass::checkMemset() +void CheckClassImpl::checkMemset() { logChecker("CheckClass::checkMemset"); - const bool printWarnings = mSettings->severity.isEnabled(Severity::warning); + const bool printWarnings = mSettings.severity.isEnabled(Severity::warning); for (const Scope *scope : mSymbolDatabase->functionScopes) { for (const Token *tok = scope->bodyStart; tok && tok != scope->bodyEnd; tok = tok->next()) { if (Token::Match(tok, "memset|memcpy|memmove (")) { @@ -1449,7 +1606,7 @@ void CheckClass::checkMemset() type = var->typeScope(); if (!type && !var->isPointer() && !Token::simpleMatch(var->typeStartToken(), "std :: array") && - mSettings->library.detectContainerOrIterator(var->typeStartToken())) { + mSettings.library.detectContainerOrIterator(var->typeStartToken())) { memsetError(tok, tok->str(), var->getTypeName(), {}, /*isContainer*/ true); } } @@ -1469,9 +1626,9 @@ void CheckClass::checkMemset() checkMemsetType(scope, tok, type, false, {}); } } else if (tok->variable() && tok->variable()->isPointer() && tok->variable()->typeScope() && Token::Match(tok, "%var% = %name% (")) { - const Library::AllocFunc* alloc = mSettings->library.getAllocFuncInfo(tok->tokAt(2)); + const Library::AllocFunc* alloc = mSettings.library.getAllocFuncInfo(tok->tokAt(2)); if (!alloc) - alloc = mSettings->library.getReallocFuncInfo(tok->tokAt(2)); + alloc = mSettings.library.getReallocFuncInfo(tok->tokAt(2)); if (!alloc || alloc->bufferSize == Library::AllocFunc::BufferSize::none) continue; std::set parsedTypes; @@ -1484,14 +1641,14 @@ void CheckClass::checkMemset() } } -void CheckClass::checkMemsetType(const Scope *start, const Token *tok, const Scope *type, bool allocation, std::set parsedTypes) +void CheckClassImpl::checkMemsetType(const Scope *start, const Token *tok, const Scope *type, bool allocation, std::set parsedTypes) { // If type has been checked there is no need to check it again if (parsedTypes.find(type) != parsedTypes.end()) return; parsedTypes.insert(type); - const bool printPortability = mSettings->severity.isEnabled(Severity::portability); + const bool printPortability = mSettings.severity.isEnabled(Severity::portability); // recursively check all parent classes for (const Type::BaseInfo & i : type->definedType->derivedFrom) { @@ -1518,21 +1675,12 @@ void CheckClass::checkMemsetType(const Scope *start, const Token *tok, const Sco } // don't warn if variable static or const, pointer or array of pointers if (!var.isStatic() && !var.isConst() && !var.isPointer() && (!var.isArray() || var.typeEndToken()->str() != "*")) { - const Token *tok1 = var.typeStartToken(); const Scope *typeScope = var.typeScope(); - std::string typeName; - if (Token::Match(tok1, "%type% ::")) { - const Token *typeTok = tok1; - while (Token::Match(typeTok, "%type% ::")) { - typeName += typeTok->str() + "::"; - typeTok = typeTok->tokAt(2); - } - typeName += typeTok->str(); - } + const std::string typeName = var.getTypeName(); // check for std:: type - if (var.isStlType() && typeName != "std::array" && !mSettings->library.podtype(typeName)) { + if (var.isStlType() && typeName != "std::array" && !mSettings.library.podtype(typeName)) { if (allocation) mallocOnClassError(tok, tok->str(), type->classDef, "'" + typeName + "'"); else @@ -1550,7 +1698,7 @@ void CheckClass::checkMemsetType(const Scope *start, const Token *tok, const Sco } } -void CheckClass::mallocOnClassWarning(const Token* tok, const std::string &memfunc, const Token* classTok) +void CheckClassImpl::mallocOnClassWarning(const Token* tok, const std::string &memfunc, const Token* classTok) { std::list toks = { tok, classTok }; reportError(toks, Severity::warning, "mallocOnClassWarning", @@ -1560,7 +1708,7 @@ void CheckClass::mallocOnClassWarning(const Token* tok, const std::string &memfu "since no constructor is called and class members remain uninitialized. Consider using 'new' instead.", CWE762, Certainty::normal); } -void CheckClass::mallocOnClassError(const Token* tok, const std::string &memfunc, const Token* classTok, const std::string &classname) +void CheckClassImpl::mallocOnClassError(const Token* tok, const std::string &memfunc, const Token* classTok, const std::string &classname) { std::list toks = { tok, classTok }; reportError(toks, Severity::error, "mallocOnClassError", @@ -1571,7 +1719,7 @@ void CheckClass::mallocOnClassError(const Token* tok, const std::string &memfunc "since no constructor is called and class members remain uninitialized. Consider using 'new' instead.", CWE665, Certainty::normal); } -void CheckClass::memsetError(const Token *tok, const std::string &memfunc, const std::string &classname, const std::string &type, bool isContainer) +void CheckClassImpl::memsetError(const Token *tok, const std::string &memfunc, const std::string &classname, const std::string &type, bool isContainer) { const std::string typeStr = isContainer ? std::string() : (type + " that contains a "); const std::string msg = "$symbol:" + memfunc + "\n" @@ -1583,14 +1731,14 @@ void CheckClass::memsetError(const Token *tok, const std::string &memfunc, const reportError(tok, Severity::error, "memsetClass", msg, CWE762, Certainty::normal); } -void CheckClass::memsetErrorReference(const Token *tok, const std::string &memfunc, const std::string &type) +void CheckClassImpl::memsetErrorReference(const Token *tok, const std::string &memfunc, const std::string &type) { reportError(tok, Severity::error, "memsetClassReference", "$symbol:" + memfunc +"\n" "Using '" + memfunc + "' on " + type + " that contains a reference.", CWE665, Certainty::normal); } -void CheckClass::memsetErrorFloat(const Token *tok, const std::string &type) +void CheckClassImpl::memsetErrorFloat(const Token *tok, const std::string &type) { reportError(tok, Severity::portability, "memsetClassFloat", "Using memset() on " + type + " which contains a floating point number.\n" "Using memset() on " + type + " which contains a floating point number." @@ -1605,9 +1753,9 @@ void CheckClass::memsetErrorFloat(const Token *tok, const std::string &type) // operator= should return a reference to *this //--------------------------------------------------------------------------- -void CheckClass::operatorEqRetRefThis() +void CheckClassImpl::operatorEqRetRefThis() { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("operatorEqRetRefThis")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("operatorEqRetRefThis")) return; logChecker("CheckClass::operatorEqRetRefThis"); // style @@ -1624,13 +1772,13 @@ void CheckClass::operatorEqRetRefThis() } } -void CheckClass::checkReturnPtrThis(const Scope *scope, const Function *func, const Token *tok, const Token *last) +void CheckClassImpl::checkReturnPtrThis(const Scope *scope, const Function *func, const Token *tok, const Token *last) { std::set analyzedFunctions; checkReturnPtrThis(scope, func, tok, last, analyzedFunctions); } -void CheckClass::checkReturnPtrThis(const Scope *scope, const Function *func, const Token *tok, const Token *last, std::set& analyzedFunctions) +void CheckClassImpl::checkReturnPtrThis(const Scope *scope, const Function *func, const Token *tok, const Token *last, std::set& analyzedFunctions) { bool foundReturn = false; @@ -1706,7 +1854,7 @@ void CheckClass::checkReturnPtrThis(const Scope *scope, const Function *func, co } return; } - if (mSettings->library.isScopeNoReturn(last, nullptr)) { + if (mSettings.library.isScopeNoReturn(last, nullptr)) { // Typical wrong way to prohibit default assignment operator // by always throwing an exception or calling a noreturn function operatorEqShouldBeLeftUnimplementedError(func->token); @@ -1716,17 +1864,17 @@ void CheckClass::checkReturnPtrThis(const Scope *scope, const Function *func, co operatorEqMissingReturnStatementError(func->token, func->access == AccessControl::Public); } -void CheckClass::operatorEqRetRefThisError(const Token *tok) +void CheckClassImpl::operatorEqRetRefThisError(const Token *tok) { reportError(tok, Severity::style, "operatorEqRetRefThis", "'operator=' should return reference to 'this' instance.", CWE398, Certainty::normal); } -void CheckClass::operatorEqShouldBeLeftUnimplementedError(const Token *tok) +void CheckClassImpl::operatorEqShouldBeLeftUnimplementedError(const Token *tok) { reportError(tok, Severity::style, "operatorEqShouldBeLeftUnimplemented", "'operator=' should either return reference to 'this' instance or be declared private and left unimplemented.", CWE398, Certainty::normal); } -void CheckClass::operatorEqMissingReturnStatementError(const Token *tok, bool error) +void CheckClassImpl::operatorEqMissingReturnStatementError(const Token *tok, bool error) { if (error) { reportError(tok, Severity::error, "operatorEqMissingReturnStatement", "No 'return' statement in non-void function causes undefined behavior.", CWE398, Certainty::normal); @@ -1749,9 +1897,9 @@ void CheckClass::operatorEqMissingReturnStatementError(const Token *tok, bool er // assignment to self. //--------------------------------------------------------------------------- -void CheckClass::operatorEqToSelf() +void CheckClassImpl::operatorEqToSelf() { - if (!mSettings->severity.isEnabled(Severity::warning) && !mSettings->isPremiumEnabled("operatorEqToSelf")) + if (!mSettings.severity.isEnabled(Severity::warning) && !mSettings.isPremiumEnabled("operatorEqToSelf")) return; logChecker("CheckClass::operatorEqToSelf"); // warning @@ -1767,7 +1915,7 @@ void CheckClass::operatorEqToSelf() if (func.argumentList.empty()) continue; const Token* typeTok = func.argumentList.front().typeEndToken(); - while (typeTok->str() == "const" || typeTok->str() == "&" || typeTok->str() == "*") + while (typeTok->str() == "const" || typeTok->str() == "&" || typeTok->str() == "&&" || typeTok->str() == "*") typeTok = typeTok->previous(); if (typeTok->str() != scope->className) continue; @@ -1790,7 +1938,7 @@ void CheckClass::operatorEqToSelf() } } -bool CheckClass::hasAllocationInIfScope(const Function *func, const Scope* scope, const Token *ifStatementScopeStart) const +bool CheckClassImpl::hasAllocationInIfScope(const Function *func, const Scope* scope, const Token *ifStatementScopeStart) const { const Token *end; if (ifStatementScopeStart->str() == "{") @@ -1800,24 +1948,24 @@ bool CheckClass::hasAllocationInIfScope(const Function *func, const Scope* scope return hasAllocation(func, scope, ifStatementScopeStart, end); } -bool CheckClass::hasAllocation(const Function *func, const Scope* scope) const +bool CheckClassImpl::hasAllocation(const Function *func, const Scope* scope) const { return hasAllocation(func, scope, func->functionScope->bodyStart, func->functionScope->bodyEnd); } -bool CheckClass::hasAllocation(const Function *func, const Scope* scope, const Token *start, const Token *end) const +bool CheckClassImpl::hasAllocation(const Function *func, const Scope* scope, const Token *start, const Token *end) const { if (!end) end = func->functionScope->bodyEnd; for (const Token *tok = start; tok && (tok != end); tok = tok->next()) { if (((tok->isCpp() && Token::Match(tok, "%var% = new")) || - (Token::Match(tok, "%var% = %name% (") && mSettings->library.getAllocFuncInfo(tok->tokAt(2)))) && + (Token::Match(tok, "%var% = %name% (") && mSettings.library.getAllocFuncInfo(tok->tokAt(2)))) && isMemberVar(scope, tok)) return true; // check for deallocating memory const Token *var; - if (Token::Match(tok, "%name% ( %var%") && mSettings->library.getDeallocFuncInfo(tok)) + if (Token::Match(tok, "%name% ( %var%") && mSettings.library.getDeallocFuncInfo(tok)) var = tok->tokAt(2); else if (tok->isCpp() && Token::Match(tok, "delete [ ] %var%")) var = tok->tokAt(3); @@ -1851,7 +1999,7 @@ static bool isFalseKeyword(const Token* tok) * Checks if self-assignment test is inverse * For example 'if (this == &rhs)' */ -CheckClass::Bool CheckClass::isInverted(const Token *tok, const Token *rhs) +CheckClassImpl::Bool CheckClassImpl::isInverted(const Token *tok, const Token *rhs) { bool res = true; for (const Token *itr = tok; itr && itr->str()!="("; itr=itr->astParent()) { @@ -1880,7 +2028,7 @@ CheckClass::Bool CheckClass::isInverted(const Token *tok, const Token *rhs) return Bool::False; } -const Token * CheckClass::getIfStmtBodyStart(const Token *tok, const Token *rhs) +const Token * CheckClassImpl::getIfStmtBodyStart(const Token *tok, const Token *rhs) { const Token *top = tok->astTop(); if (Token::simpleMatch(top->link(), ") {")) { @@ -1896,7 +2044,7 @@ const Token * CheckClass::getIfStmtBodyStart(const Token *tok, const Token *rhs) return nullptr; } -bool CheckClass::hasAssignSelf(const Function *func, const Token *rhs, const Token *&out_ifStatementScopeStart) +bool CheckClassImpl::hasAssignSelf(const Function *func, const Token *rhs, const Token *&out_ifStatementScopeStart) { if (!rhs) return false; @@ -1930,7 +2078,7 @@ bool CheckClass::hasAssignSelf(const Function *func, const Token *rhs, const Tok return false; } -void CheckClass::operatorEqToSelfError(const Token *tok) +void CheckClassImpl::operatorEqToSelfError(const Token *tok) { reportError(tok, Severity::warning, "operatorEqToSelf", "'operator=' should check for assignment to self to avoid problems with dynamic memory.\n" @@ -1942,7 +2090,7 @@ void CheckClass::operatorEqToSelfError(const Token *tok) // A destructor in a base class should be virtual //--------------------------------------------------------------------------- -void CheckClass::virtualDestructor() +void CheckClassImpl::virtualDestructor() { // This error should only be given if: // * base class doesn't have virtual destructor @@ -1950,12 +2098,39 @@ void CheckClass::virtualDestructor() // * base class is deleted // unless inconclusive in which case: // * A class with any virtual functions should have a destructor that is either public and virtual or protected - const bool printInconclusive = mSettings->certainty.isEnabled(Certainty::inconclusive); + const bool printInconclusive = mSettings.certainty.isEnabled(Certainty::inconclusive); std::list inconclusiveErrors; logChecker("CheckClass::virtualDestructor"); + // pointer variables of type 'Base *', for each type 'Base' + std::unordered_map> pointersByType; + + // for each pointer variable, the classes 'Derived' of a "ptr = new Derived" that is followed by a "delete ptr;" + std::unordered_map> deletedAfterNew; + + for (const Variable* var : mSymbolDatabase->variableList()) { + if (var && var->isPointer() && var->type() && var->nameToken()) + pointersByType[var->type()].push_back(var->declarationId()); + } + + { + // the classes 'Derived' of the "ptr = new Derived" seen so far + std::unordered_map> newClasses; + + for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) { + if (Token::Match(tok, "[;{}] %var% = new %name%")) + newClasses[tok->next()->varId()].insert(tok->strAt(4)); + + else if (Token::Match(tok, "delete %var% ;")) { + const auto it = newClasses.find(tok->next()->varId()); + if (it != newClasses.end()) + deletedAfterNew[it->first].insert(it->second.cbegin(), it->second.cend()); + } + } + } + for (const Scope * scope : mSymbolDatabase->classAndStructScopes) { // Skip base classes (unless inconclusive) @@ -1973,7 +2148,7 @@ void CheckClass::virtualDestructor() } // Check if destructor is empty and non-empty .. - if (mSettings->standards.cpp <= Standards::CPP03) { + if (mSettings.standards.cpp <= Standards::CPP03) { // Find the destructor const Function *destructor = scope->getDestructor(); @@ -2005,39 +2180,15 @@ void CheckClass::virtualDestructor() // If this pattern is not seen then bailout the checking of these base/derived classes { // pointer variables of type 'Base *' - std::set baseClassPointers; - - for (const Variable* var : mSymbolDatabase->variableList()) { - if (var && var->isPointer() && var->type() == derivedFrom) - baseClassPointers.insert(var->declarationId()); - } - - // pointer variables of type 'Base *' that should not be deleted - std::set dontDelete; - - // No deletion of derived class instance through base class pointer found => the code is ok - bool ok = true; - - for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) { - if (Token::Match(tok, "[;{}] %var% =") && - baseClassPointers.find(tok->next()->varId()) != baseClassPointers.end()) { - // new derived class.. - const std::string tmp("new " + derivedClass->str()); - if (Token::simpleMatch(tok->tokAt(3), tmp.c_str(), tmp.size())) { - dontDelete.insert(tok->next()->varId()); - } - } - - // Delete base class pointer that might point at derived class - else if (Token::Match(tok, "delete %var% ;") && - dontDelete.find(tok->next()->varId()) != dontDelete.end()) { - ok = false; - break; - } - } + const auto pointers = pointersByType.find(derivedFrom); + if (pointers == pointersByType.end()) + continue; // No base class pointer that points at a derived class is deleted - if (ok) + if (std::none_of(pointers->second.cbegin(), pointers->second.cend(), [&](int varId) { + const auto deleted = deletedAfterNew.find(varId); + return deleted != deletedAfterNew.end() && deleted->second.count(derivedClass->str()) > 0; + })) continue; } @@ -2077,10 +2228,10 @@ void CheckClass::virtualDestructor() virtualDestructorError(func->tokenDef, func->name(), "", true); } -void CheckClass::virtualDestructorError(const Token *tok, const std::string &Base, const std::string &Derived, bool inconclusive) +void CheckClassImpl::virtualDestructorError(const Token *tok, const std::string &Base, const std::string &Derived, bool inconclusive) { if (inconclusive) { - if (mSettings->severity.isEnabled(Severity::warning)) + if (mSettings.severity.isEnabled(Severity::warning)) reportError(tok, Severity::warning, "virtualDestructor", "$symbol:" + Base + "\nClass '$symbol' which has virtual members does not have a virtual destructor.", CWE404, Certainty::inconclusive); } else { reportError(tok, Severity::error, "virtualDestructor", @@ -2098,9 +2249,9 @@ void CheckClass::virtualDestructorError(const Token *tok, const std::string &Bas // warn for "this-x". The indented code may be "this->x" //--------------------------------------------------------------------------- -void CheckClass::thisSubtraction() +void CheckClassImpl::thisSubtraction() { - if (!mSettings->severity.isEnabled(Severity::warning)) + if (!mSettings.severity.isEnabled(Severity::warning)) return; logChecker("CheckClass::thisSubtraction"); // warning @@ -2118,7 +2269,7 @@ void CheckClass::thisSubtraction() } } -void CheckClass::thisSubtractionError(const Token *tok) +void CheckClassImpl::thisSubtractionError(const Token *tok) { reportError(tok, Severity::warning, "thisSubtraction", "Suspicious pointer subtraction. Did you intend to write '->'?", CWE398, Certainty::normal); } @@ -2127,11 +2278,11 @@ void CheckClass::thisSubtractionError(const Token *tok) // can member function be const? //--------------------------------------------------------------------------- -void CheckClass::checkConst() +void CheckClassImpl::checkConst() { - if (!mSettings->severity.isEnabled(Severity::style) && - !mSettings->isPremiumEnabled("functionConst") && - !mSettings->isPremiumEnabled("functionStatic")) + if (!mSettings.severity.isEnabled(Severity::style) && + !mSettings.isPremiumEnabled("functionConst") && + !mSettings.isPremiumEnabled("functionStatic")) return; logChecker("CheckClass::checkConst"); // style,inconclusive @@ -2192,7 +2343,7 @@ void CheckClass::checkConst() const std::string& opName = func.tokenDef->str(); if (opName.compare(8, 5, "const") != 0 && (endsWith(opName,'&') || endsWith(opName,'*'))) continue; - } else if (mSettings->library.isSmartPointer(func.retDef)) { + } else if (mSettings.library.isSmartPointer(func.retDef)) { // Don't warn if a std::shared_ptr etc is returned continue; } else { @@ -2215,7 +2366,7 @@ void CheckClass::checkConst() const bool suggestStatic = memberAccessed != MemberAccess::MEMBER && !func.isOperator(); if ((returnsPtrOrRef || func.isConst() || func.hasLvalRefQualifier()) && !suggestStatic) continue; - if (!suggestStatic && !mSettings->certainty.isEnabled(Certainty::inconclusive)) + if (!suggestStatic && !mSettings.certainty.isEnabled(Certainty::inconclusive)) // functionConst is inconclusive. False positives: #3322. continue; if (suggestStatic && func.isConst()) { @@ -2263,7 +2414,7 @@ static const Token* getFuncTokFromThis(const Token* tok) { return Token::Match(tok, "%name% (") ? tok : nullptr; } -bool CheckClass::isMemberVar(const Scope *scope, const Token *tok) const +bool CheckClassImpl::isMemberVar(const Scope *scope, const Token *tok) const { bool again = false; @@ -2350,7 +2501,7 @@ bool CheckClass::isMemberVar(const Scope *scope, const Token *tok) const return false; } -bool CheckClass::isMemberFunc(const Scope *scope, const Token *tok) +bool CheckClassImpl::isMemberFunc(const Scope *scope, const Token *tok) { if (!tok->function()) { if (Token::simpleMatch(tok->astParent(), ".") && Token::simpleMatch(tok->astParent()->astOperand1(), "this")) @@ -2358,7 +2509,7 @@ bool CheckClass::isMemberFunc(const Scope *scope, const Token *tok) for (const Function &func : scope->functionList) { if (func.name() == tok->str()) { const Token* tok2 = tok->tokAt(2); - int argsPassed = tok2->str() == ")" ? 0 : 1; + size_t argsPassed = tok2->str() == ")" ? 0 : 1; for (;;) { tok2 = tok2->nextArgument(); if (tok2) @@ -2393,7 +2544,7 @@ bool CheckClass::isMemberFunc(const Scope *scope, const Token *tok) return false; } -bool CheckClass::isConstMemberFunc(const Scope *scope, const Token *tok) +bool CheckClassImpl::isConstMemberFunc(const Scope *scope, const Token *tok) { if (!tok->function()) return false; @@ -2418,7 +2569,7 @@ bool CheckClass::isConstMemberFunc(const Scope *scope, const Token *tok) return false; } -const std::set CheckClass::stl_containers_not_const = { "map", "unordered_map", "std :: map|unordered_map <" }; // start pattern +const std::set CheckClassImpl::stl_containers_not_const = { "map", "unordered_map", "std :: map|unordered_map <" }; // start pattern static bool isNonConstPtrCast(const Token* tok) { @@ -2428,7 +2579,7 @@ static bool isNonConstPtrCast(const Token* tok) return !vt || (vt->pointer > 0 && !vt->isConst(vt->pointer)); } -bool CheckClass::checkConstFunc(const Scope *scope, const Function *func, MemberAccess& memberAccessed) const +bool CheckClassImpl::checkConstFunc(const Scope *scope, const Function *func, MemberAccess& memberAccessed) const { if (mTokenizer->hasIfdef(func->functionScope->bodyStart, func->functionScope->bodyEnd)) return false; @@ -2455,9 +2606,9 @@ bool CheckClass::checkConstFunc(const Scope *scope, const Function *func, Member if (const Function* f = funcTok->function()) { // check known function const std::vector args = getArguments(funcTok); - const auto argMax = std::min(args.size(), f->argCount()); + const auto argMax = std::min(args.size(), f->argCount()); - for (nonneg int argIndex = 0; argIndex < argMax; ++argIndex) { + for (size_t argIndex = 0; argIndex < argMax; ++argIndex) { const Variable* const argVar = f->getArgumentVar(argIndex); if (!argVar || ((argVar->isArrayOrPointer() || argVar->isReference()) && !(argVar->valueType() && argVar->valueType()->isConst(argVar->valueType()->pointer)))) { // argument might be modified @@ -2479,7 +2630,7 @@ bool CheckClass::checkConstFunc(const Scope *scope, const Function *func, Member return true; } - if (const Library::Function* fLib = mSettings->library.getFunction(funcTok)) + if (const Library::Function* fLib = mSettings.library.getFunction(funcTok)) if (fLib->isconst || fLib->ispure) return true; @@ -2513,7 +2664,7 @@ bool CheckClass::checkConstFunc(const Scope *scope, const Function *func, Member return false; if (Token::Match(tok1->previous(), "( this . * %var% )")) // call using ptr to member function TODO: check constness return false; - if (Token::simpleMatch(tok1->astParent(), "*") && tok1->astParent()->astParent() && tok1->astParent()->astParent()->isIncDecOp()) + if (Token::simpleMatch(tok1->astParent(), "*")) return false; } @@ -2552,9 +2703,12 @@ bool CheckClass::checkConstFunc(const Scope *scope, const Function *func, Member return false; } else { if (lhs->isAssignmentOp()) { - const Variable* lhsVar = lhs->previous()->variable(); - if (lhsVar && !lhsVar->isConst() && lhsVar->isReference() && lhs == lhsVar->nameToken()->next()) - return false; + if (const Variable* lhsVar = lhs->previous()->variable()) { + if (!lhsVar->isConst() && lhsVar->isReference() && lhs == lhsVar->nameToken()->next()) + return false; + if (lhsVar->isPointer() && v && v->isArray() && !(lhsVar->valueType() && lhsVar->valueType()->isConst(/*indirect*/ 1))) + return false; + } } } @@ -2640,12 +2794,12 @@ bool CheckClass::checkConstFunc(const Scope *scope, const Function *func, Member } else if (var->smartPointerType() && var->smartPointerType()->classScope && isConstMemberFunc(var->smartPointerType()->classScope, end)) { // empty body - } else if (var->isSmartPointer() && Token::simpleMatch(tok1->next(), ".") && tok1->next()->originalName().empty() && mSettings->library.isFunctionConst(end)) { + } else if (var->isSmartPointer() && Token::simpleMatch(tok1->next(), ".") && tok1->next()->originalName().empty() && mSettings.library.isFunctionConst(end)) { // empty body } else if (hasOverloadedMemberAccess(end, var->typeScope())) { // empty body } else if (!var->typeScope() || (end->function() != func && !isConstMemberFunc(var->typeScope(), end))) { - if (!mSettings->library.isFunctionConst(end)) + if (!mSettings.library.isFunctionConst(end)) return false; } } @@ -2702,12 +2856,12 @@ bool CheckClass::checkConstFunc(const Scope *scope, const Function *func, Member return true; } -void CheckClass::checkConstError(const Token *tok, const std::string &classname, const std::string &funcname, bool suggestStatic, bool foundAllBaseClasses) +void CheckClassImpl::checkConstError(const Token *tok, const std::string &classname, const std::string &funcname, bool suggestStatic, bool foundAllBaseClasses) { checkConstError2(tok, nullptr, classname, funcname, suggestStatic, foundAllBaseClasses); } -void CheckClass::checkConstError2(const Token *tok1, const Token *tok2, const std::string &classname, const std::string &funcname, bool suggestStatic, bool foundAllBaseClasses) +void CheckClassImpl::checkConstError2(const Token *tok1, const Token *tok2, const std::string &classname, const std::string &funcname, bool suggestStatic, bool foundAllBaseClasses) { std::list toks{ tok1 }; if (tok2) @@ -2748,17 +2902,17 @@ namespace { // avoid one-definition-rule violation }; } -void CheckClass::initializerListOrder() +void CheckClassImpl::initializerListOrder() { - if (!mSettings->isPremiumEnabled("initializerList")) { - if (!mSettings->severity.isEnabled(Severity::style)) + if (!mSettings.isPremiumEnabled("initializerList")) { + if (!mSettings.severity.isEnabled(Severity::style)) return; // This check is not inconclusive. However it only determines if the initialization // order is incorrect. It does not determine if being out of order causes // a real error. Out of order is not necessarily an error but you can never // have an error if the list is in order so this enforces defensive programming. - if (!mSettings->certainty.isEnabled(Certainty::inconclusive)) + if (!mSettings.certainty.isEnabled(Certainty::inconclusive)) return; } @@ -2827,7 +2981,7 @@ void CheckClass::initializerListOrder() } } -void CheckClass::initializerListError(const Token *tok1, const Token *tok2, const std::string &classname, const std::string &varname, const std::string& argname) +void CheckClassImpl::initializerListError(const Token *tok1, const Token *tok2, const std::string &classname, const std::string &varname, const std::string& argname) { std::list toks = { tok1, tok2 }; const std::string msg = argname.empty() ? @@ -2848,7 +3002,7 @@ void CheckClass::initializerListError(const Token *tok1, const Token *tok2, cons // Check for self initialization in initialization list //--------------------------------------------------------------------------- -void CheckClass::checkSelfInitialization() +void CheckClassImpl::checkSelfInitialization() { logChecker("CheckClass::checkSelfInitialization"); @@ -2877,7 +3031,7 @@ void CheckClass::checkSelfInitialization() } } -void CheckClass::selfInitializationError(const Token* tok, const std::string& varname) +void CheckClassImpl::selfInitializationError(const Token* tok, const std::string& varname) { reportError(tok, Severity::error, "selfInitialization", "$symbol:" + varname + "\nMember variable '$symbol' is initialized by itself.", CWE665, Certainty::normal); } @@ -2887,9 +3041,9 @@ void CheckClass::selfInitializationError(const Token* tok, const std::string& va // Check for virtual function calls in constructor/destructor //--------------------------------------------------------------------------- -void CheckClass::checkVirtualFunctionCallInConstructor() +void CheckClassImpl::checkVirtualFunctionCallInConstructor() { - if (!mSettings->severity.isEnabled(Severity::warning)) + if (!mSettings.severity.isEnabled(Severity::warning)) return; logChecker("CheckClass::checkVirtualFunctionCallInConstructor"); // warning std::map> virtualFunctionCallsMap; @@ -2917,8 +3071,8 @@ void CheckClass::checkVirtualFunctionCallInConstructor() } } -const std::list & CheckClass::getVirtualFunctionCalls(const Function & function, - std::map> & virtualFunctionCallsMap) +const std::list & CheckClassImpl::getVirtualFunctionCalls(const Function & function, + std::map> & virtualFunctionCallsMap) { const auto found = utils::as_const(virtualFunctionCallsMap).find(&function); if (found != virtualFunctionCallsMap.end()) @@ -2956,8 +3110,8 @@ const std::list & CheckClass::getVirtualFunctionCalls(const Funct tok->strAt(-1) == "(") { const Token * prev = tok->previous(); if (prev->previous() && - (mSettings->library.ignorefunction(tok->str()) - || mSettings->library.ignorefunction(prev->strAt(-1)))) + (mSettings.library.ignorefunction(tok->str()) + || mSettings.library.ignorefunction(prev->strAt(-1)))) continue; } @@ -2975,7 +3129,7 @@ const std::list & CheckClass::getVirtualFunctionCalls(const Funct return virtualFunctionCalls; } -void CheckClass::getFirstVirtualFunctionCallStack( +void CheckClassImpl::getFirstVirtualFunctionCallStack( std::map> & virtualFunctionCallsMap, const Token * callToken, std::list & pureFuncStack) @@ -2995,12 +3149,12 @@ void CheckClass::getFirstVirtualFunctionCallStack( getFirstVirtualFunctionCallStack(virtualFunctionCallsMap, firstCall, pureFuncStack); } -void CheckClass::virtualFunctionCallInConstructorError( +void CheckClassImpl::virtualFunctionCallInConstructorError( const Function * scopeFunction, const std::list & tokStack, const std::string &funcname) { - if (scopeFunction && !mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("virtualCallInConstructor")) + if (scopeFunction && !mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("virtualCallInConstructor")) return; const char * scopeFunctionTypeName = scopeFunction ? getFunctionTypeName(scopeFunction->type) : "constructor"; @@ -3033,7 +3187,7 @@ void CheckClass::virtualFunctionCallInConstructorError( "Virtual function '" + funcname + "' is called from " + scopeFunctionTypeName + " '" + constructorName + "' at line " + std::to_string(lineNumber) + ". Dynamic binding is not used.", CWE(0U), Certainty::normal); } -void CheckClass::pureVirtualFunctionCallInConstructorError( +void CheckClassImpl::pureVirtualFunctionCallInConstructorError( const Function * scopeFunction, const std::list & tokStack, const std::string &purefuncname) @@ -3058,9 +3212,9 @@ void CheckClass::pureVirtualFunctionCallInConstructorError( // Check for members hiding inherited members with the same name //--------------------------------------------------------------------------- -void CheckClass::checkDuplInheritedMembers() +void CheckClassImpl::checkDuplInheritedMembers() { - if (!mSettings->severity.isEnabled(Severity::warning) && !mSettings->isPremiumEnabled("duplInheritedMember")) + if (!mSettings.severity.isEnabled(Severity::warning) && !mSettings.isPremiumEnabled("duplInheritedMember")) return; logChecker("CheckClass::checkDuplInheritedMembers"); // warning @@ -3147,7 +3301,7 @@ static std::vector getDuplInheritedMemberFunctionsRecursive( return results; } -void CheckClass::checkDuplInheritedMembersRecursive(const Type* typeCurrent, const Type* typeBase) +void CheckClassImpl::checkDuplInheritedMembersRecursive(const Type* typeCurrent, const Type* typeBase) { const auto resultsVar = getDuplInheritedMembersRecursive(typeCurrent, typeBase); for (const auto& r : resultsVar) { @@ -3166,9 +3320,9 @@ void CheckClass::checkDuplInheritedMembersRecursive(const Type* typeCurrent, con } } -void CheckClass::duplInheritedMembersError(const Token *tok1, const Token* tok2, - const std::string &derivedName, const std::string &baseName, - const std::string &memberName, bool derivedIsStruct, bool baseIsStruct, bool isFunction) +void CheckClassImpl::duplInheritedMembersError(const Token *tok1, const Token* tok2, + const std::string &derivedName, const std::string &baseName, + const std::string &memberName, bool derivedIsStruct, bool baseIsStruct, bool isFunction) { ErrorPath errorPath; const std::string member = isFunction ? "function" : "variable"; @@ -3194,11 +3348,11 @@ enum class CtorType : std::uint8_t { WITH_BODY }; -void CheckClass::checkCopyCtorAndEqOperator() +void CheckClassImpl::checkCopyCtorAndEqOperator() { // TODO: This is disabled because of #8388 // The message must be clarified. How is the behaviour different? - if ((true) || !mSettings->severity.isEnabled(Severity::warning)) // NOLINT(readability-simplify-boolean-expr,readability-redundant-parentheses) + if ((true) || !mSettings.severity.isEnabled(Severity::warning)) // NOLINT(readability-simplify-boolean-expr,readability-redundant-parentheses) return; // logChecker @@ -3246,7 +3400,7 @@ void CheckClass::checkCopyCtorAndEqOperator() } } -void CheckClass::copyCtorAndEqOperatorError(const Token *tok, const std::string &classname, bool isStruct, bool hasCopyCtor) +void CheckClassImpl::copyCtorAndEqOperatorError(const Token *tok, const std::string &classname, bool isStruct, bool hasCopyCtor) { const std::string message = "$symbol:" + classname + "\n" "The " + std::string(isStruct ? "struct" : "class") + " '$symbol' has '" + @@ -3256,11 +3410,11 @@ void CheckClass::copyCtorAndEqOperatorError(const Token *tok, const std::string reportError(tok, Severity::warning, "copyCtorAndEqOperator", message); } -void CheckClass::checkOverride() +void CheckClassImpl::checkOverride() { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("missingOverride")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("missingOverride")) return; - if (mSettings->standards.cpp < Standards::CPP11) + if (mSettings.standards.cpp < Standards::CPP11) return; logChecker("CheckClass::checkMissingOverride"); // style,c++03 for (const Scope * classScope : mSymbolDatabase->classAndStructScopes) { @@ -3280,7 +3434,7 @@ void CheckClass::checkOverride() } } -void CheckClass::overrideError(const Function *funcInBase, const Function *funcInDerived) +void CheckClassImpl::overrideError(const Function *funcInBase, const Function *funcInDerived) { const std::string functionName = funcInDerived ? ((funcInDerived->isDestructor() ? "~" : "") + funcInDerived->name()) : ""; const std::string funcType = (funcInDerived && funcInDerived->isDestructor()) ? "destructor" : "function"; @@ -3298,7 +3452,7 @@ void CheckClass::overrideError(const Function *funcInBase, const Function *funcI Certainty::normal); } -void CheckClass::uselessOverrideError(const Function *funcInBase, const Function *funcInDerived, bool isSameCode) +void CheckClassImpl::uselessOverrideError(const Function *funcInBase, const Function *funcInDerived, bool isSameCode) { const std::string functionName = funcInDerived ? ((funcInDerived->isDestructor() ? "~" : "") + funcInDerived->name()) : ""; const std::string funcType = (funcInDerived && funcInDerived->isDestructor()) ? "destructor" : "function"; @@ -3362,9 +3516,9 @@ static bool compareTokenRanges(const Token* start1, const Token* end1, const Tok return isEqual; } -void CheckClass::checkUselessOverride() +void CheckClassImpl::checkUselessOverride() { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("uselessOverride")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("uselessOverride")) return; logChecker("CheckClass::checkUselessOverride"); // style @@ -3396,9 +3550,14 @@ void CheckClass::checkUselessOverride() if (isSameCode) { // bailout for shadowed members - if (!classScope->definedType || - !getDuplInheritedMembersRecursive(classScope->definedType, classScope->definedType, /*skipPrivate*/ false).empty() || - !getDuplInheritedMemberFunctionsRecursive(classScope->definedType, classScope->definedType, /*skipPrivate*/ false).empty()) + if (!getDuplInheritedMembersRecursive(classScope->definedType, + classScope->definedType, + /*skipPrivate*/ false) + .empty() || + !getDuplInheritedMemberFunctionsRecursive(classScope->definedType, + classScope->definedType, + /*skipPrivate*/ false) + .empty()) continue; uselessOverrideError(baseFunc, &func, true); continue; @@ -3443,9 +3602,9 @@ static const Variable* getSingleReturnVar(const Scope* scope) { return tok->variable(); } -void CheckClass::checkReturnByReference() +void CheckClassImpl::checkReturnByReference() { - if (!mSettings->severity.isEnabled(Severity::performance) && !mSettings->isPremiumEnabled("returnByReference")) + if (!mSettings.severity.isEnabled(Severity::performance) && !mSettings.isPremiumEnabled("returnByReference")) return; logChecker("CheckClass::checkReturnByReference"); // performance @@ -3460,7 +3619,7 @@ void CheckClass::checkReturnByReference() continue; if (func.functionPointerUsage) continue; - if (const Library::Container* container = mSettings->library.detectContainer(func.retDef)) + if (const Library::Container* container = mSettings.library.detectContainer(func.retDef)) if (container->view) continue; if (!func.isConst() && func.hasRvalRefQualifier()) @@ -3475,8 +3634,8 @@ void CheckClass::checkReturnByReference() const bool isView = isContainer && var->valueType()->container->view; bool warn = isContainer && !isView; if (!warn && !isView) { - const std::size_t size = var->valueType()->getSizeOf(*mSettings, ValueType::Accuracy::LowerBound, ValueType::SizeOf::Pointer); - if (size > 2 * mSettings->platform.sizeof_pointer) + const std::size_t size = var->valueType()->getSizeOf(mSettings, ValueType::Accuracy::LowerBound, ValueType::SizeOf::Pointer); + if (size > 2 * mSettings.platform.sizeof_pointer) warn = true; } if (warn) @@ -3486,16 +3645,16 @@ void CheckClass::checkReturnByReference() } } -void CheckClass::returnByReferenceError(const Function* func, const Variable* var) +void CheckClassImpl::returnByReferenceError(const Function* func, const Variable* var) { const Token* tok = func ? func->tokenDef : nullptr; const std::string message = "Function '" + (func ? func->name() : "func") + "()' should return member '" + (var ? var->name() : "var") + "' by const reference."; reportError(tok, Severity::performance, "returnByReference", message); } -void CheckClass::checkThisUseAfterFree() +void CheckClassImpl::checkThisUseAfterFree() { - if (!mSettings->severity.isEnabled(Severity::warning)) + if (!mSettings.severity.isEnabled(Severity::warning)) return; logChecker("CheckClass::checkThisUseAfterFree"); // warning @@ -3505,7 +3664,7 @@ void CheckClass::checkThisUseAfterFree() for (const Variable &var : classScope->varlist) { // Find possible "self pointer".. pointer/smartpointer member variable of "self" type. if (var.valueType() && var.valueType()->smartPointerType != classScope->definedType && var.valueType()->typeScope != classScope) { - const ValueType valueType = ValueType::parseDecl(var.typeStartToken(), *mSettings); + const ValueType valueType = ValueType::parseDecl(var.typeStartToken(), mSettings); if (valueType.smartPointerType != classScope->definedType) continue; } @@ -3542,7 +3701,7 @@ void CheckClass::checkThisUseAfterFree() } } -bool CheckClass::checkThisUseAfterFreeRecursive(const Scope *classScope, const Function *func, const Variable *selfPointer, std::set callstack, const Token *&freeToken) +bool CheckClassImpl::checkThisUseAfterFreeRecursive(const Scope *classScope, const Function *func, const Variable *selfPointer, std::set callstack, const Token *&freeToken) { if (!func || !func->functionScope) return false; @@ -3581,7 +3740,7 @@ bool CheckClass::checkThisUseAfterFreeRecursive(const Scope *classScope, const F return false; } -void CheckClass::thisUseAfterFree(const Token *self, const Token *free, const Token *use) +void CheckClassImpl::thisUseAfterFree(const Token *self, const Token *free, const Token *use) { std::string selfPointer = self ? self->str() : "ptr"; ErrorPath errorPath = { ErrorPathItem(self, "Assuming '" + selfPointer + "' is used as 'this'"), ErrorPathItem(free, "Delete '" + selfPointer + "', invalidating 'this'"), ErrorPathItem(use, "Call method when 'this' is invalid") }; @@ -3593,9 +3752,9 @@ void CheckClass::thisUseAfterFree(const Token *self, const Token *free, const To CWE(0), Certainty::normal); } -void CheckClass::checkUnsafeClassRefMember() +void CheckClassImpl::checkUnsafeClassRefMember() { - if (!mSettings->safeChecks.classes || !mSettings->severity.isEnabled(Severity::warning)) + if (!mSettings.safeChecks.classes || !mSettings.severity.isEnabled(Severity::warning)) return; logChecker("CheckClass::checkUnsafeClassRefMember"); // warning,safeChecks for (const Scope * classScope : mSymbolDatabase->classAndStructScopes) { @@ -3617,7 +3776,7 @@ void CheckClass::checkUnsafeClassRefMember() } } -void CheckClass::unsafeClassRefMemberError(const Token *tok, const std::string &varname) +void CheckClassImpl::unsafeClassRefMemberError(const Token *tok, const std::string &varname) { reportError(tok, Severity::warning, "unsafeClassRefMember", "$symbol:" + varname + "\n" @@ -3637,6 +3796,8 @@ namespace /* multifile checking; one definition rule violations */ class MyFileInfo : public Check::FileInfo { public: + explicit MyFileInfo(std::string f0) : Check::FileInfo(std::move(f0)) {} + using Check::FileInfo::FileInfo; struct NameLoc { std::string className; @@ -3672,7 +3833,7 @@ namespace }; } -Check::FileInfo *CheckClass::getFileInfo(const Tokenizer &tokenizer, const Settings& /*settings*/, const std::string& currentConfig) const +const Check::FileInfo *CheckClass::getFileInfo(const Tokenizer &tokenizer, const Settings& /*settings*/, const std::string& currentConfig) const { if (!tokenizer.isCPP()) return nullptr; @@ -3742,9 +3903,9 @@ Check::FileInfo *CheckClass::getFileInfo(const Tokenizer &tokenizer, const Setti return fileInfo; } -Check::FileInfo * CheckClass::loadFileInfoFromXml(const tinyxml2::XMLElement *xmlElement) const +const Check::FileInfo * CheckClass::loadFileInfoFromXml(const tinyxml2::XMLElement *xmlElement, const std::string& file0) const { - auto *fileInfo = new MyFileInfo; + auto *fileInfo = new MyFileInfo(file0); for (const tinyxml2::XMLElement *e = xmlElement->FirstChildElement(); e; e = e->NextSiblingElement()) { if (std::strcmp(e->Name(), "class") != 0) continue; @@ -3772,14 +3933,26 @@ Check::FileInfo * CheckClass::loadFileInfoFromXml(const tinyxml2::XMLElement *xm return fileInfo; } -bool CheckClass::analyseWholeProgram(const CTU::FileInfo &ctu, const std::list &fileInfo, const Settings& settings, ErrorLogger &errorLogger) +static ErrorMessage oneDefinitionRuleViolationErrorMessage(std::list locationList, const std::string &file0, const std::string &symbolName) +{ + return ErrorMessage(std::move(locationList), + file0, + Severity::error, + "$symbol:" + symbolName + + "\nThe one definition rule is violated, different classes/structs have the same name '$symbol'", + "ctuOneDefinitionRuleViolation", + CWE_ONE_DEFINITION_RULE, + Certainty::normal); +} + +bool CheckClass::analyseWholeProgram(const CTU::FileInfo &ctu, const std::list &fileInfo, const Settings& settings, ErrorLogger &errorLogger) { (void)ctu; (void)settings; - CheckClass dummy(nullptr, &settings, &errorLogger); + CheckClassImpl dummy(nullptr, settings, errorLogger); dummy. - logChecker("CheckClass::analyseWholeProgram"); + logChecker("CheckClass::checkCtuOneDefinitionRule"); if (fileInfo.empty()) return false; @@ -3810,15 +3983,7 @@ bool CheckClass::analyseWholeProgram(const CTU::FileInfo &ctu, const std::listsecond.fileName, it->second.lineNumber, it->second.column); - const ErrorMessage errmsg(std::move(locationList), - fi->file0, - Severity::error, - "$symbol:" + nameLoc.className + - "\nThe one definition rule is violated, different classes/structs have the same name '$symbol'", - "ctuOneDefinitionRuleViolation", - CWE_ONE_DEFINITION_RULE, - Certainty::normal); - errorLogger.reportErr(errmsg); + errorLogger.reportErr(oneDefinitionRuleViolationErrorMessage(std::move(locationList), fi->file0, nameLoc.className)); foundErrors = true; } @@ -3826,12 +3991,12 @@ bool CheckClass::analyseWholeProgram(const CTU::FileInfo &ctu, const std::list(), "f"); c.thisUseAfterFree(nullptr, nullptr, nullptr); c.unsafeClassRefMemberError(nullptr, "UnsafeClass::var"); - // TODO: ctuOneDefinitionRuleViolation + errorLogger.reportErr(oneDefinitionRuleViolationErrorMessage({}, "", "classname")); } diff --git a/lib/checkclass.h b/lib/checkclass.h index 1276b212518..376953ab6fd 100644 --- a/lib/checkclass.h +++ b/lib/checkclass.h @@ -22,6 +22,7 @@ //--------------------------------------------------------------------------- #include "check.h" +#include "checkimpl.h" #include "config.h" #include @@ -48,23 +49,57 @@ enum class FunctionType : std::uint8_t; /** @brief %Check classes. Uninitialized member variables, non-conforming operators, missing virtual destructor, etc */ class CPPCHECKLIB CheckClass : public Check { - friend class TestClass; - friend class TestConstructors; - friend class TestUnusedPrivateFunction; - public: /** @brief This constructor is used when registering the CheckClass */ - CheckClass() : Check(myName()) {} + CheckClass() : Check("Class") {} - /** @brief Set of the STL types whose operator[] is not const */ - static const std::set stl_containers_not_const; + /** @brief Run checks on the normal token list */ + void runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) override; + + /** @brief Parse current TU and extract file info */ + const Check::FileInfo *getFileInfo(const Tokenizer &tokenizer, const Settings& /*settings*/, const std::string& currentConfig) const override; -private: - /** @brief This constructor is used when running checks. */ - CheckClass(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger); + const Check::FileInfo * loadFileInfoFromXml(const tinyxml2::XMLElement *xmlElement, const std::string& file0) const override; - /** @brief Run checks on the normal token list */ - void runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) override; + /** @brief Analyse all file infos for all TU */ + bool analyseWholeProgram(const CTU::FileInfo &ctu, const std::list &fileInfo, const Settings& settings, ErrorLogger &errorLogger) override; + + void getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const override; + + std::string classInfo() const override { + return "Check the code for each class.\n" + "- Missing constructors and copy constructors\n" + //"- Missing allocation of memory in copy constructor\n" + "- Constructors which should be explicit\n" + "- Are all variables initialized by the constructors?\n" + "- Are all variables assigned by 'operator='?\n" + "- Warn if memset, memcpy etc are used on a class\n" + "- Warn if memory for classes is allocated with malloc()\n" + "- If it's a base class, check that the destructor is virtual\n" + "- Are there unused private functions?\n" + "- 'operator=' should check for assignment to self\n" + "- Constness for member functions\n" + "- Order of initializations\n" + "- Suggest usage of initialization list\n" + "- Initialization of a member with itself\n" + "- Suspicious subtraction from 'this'\n" + "- Call of pure virtual function in constructor/destructor\n" + "- Duplicated inherited data members\n" + // disabled for now "- If 'copy constructor' defined, 'operator=' also should be defined and vice versa\n" + "- Check that arbitrary usage of public interface does not result in division by zero\n" + "- Delete \"self pointer\" and then access 'this'\n" + "- Check that the 'override' keyword is used when overriding virtual functions\n" + "- Check that the 'one definition rule' is not violated\n"; + } +}; + +class CPPCHECKLIB CheckClassImpl : public CheckImpl { +public: + /** This constructor is used when running checks. */ + CheckClassImpl(const Tokenizer *tokenizer, const Settings &settings, ErrorLogger &errorLogger); + + /** @brief Set of the STL types whose operator[] is not const */ + static const std::set stl_containers_not_const; /** @brief %Check that all class constructors are ok */ void constructors(); @@ -137,14 +172,6 @@ class CPPCHECKLIB CheckClass : public Check { /** @brief Unsafe class check - const reference member */ void checkUnsafeClassRefMember(); - /** @brief Parse current TU and extract file info */ - Check::FileInfo *getFileInfo(const Tokenizer &tokenizer, const Settings &settings, const std::string& currentConfig) const override; - - Check::FileInfo * loadFileInfoFromXml(const tinyxml2::XMLElement *xmlElement) const override; - - /** @brief Analyse all file infos for all TU */ - bool analyseWholeProgram(const CTU::FileInfo &ctu, const std::list &fileInfo, const Settings& settings, ErrorLogger &errorLogger) override; - const SymbolDatabase* mSymbolDatabase{}; // Reporting errors.. @@ -155,11 +182,11 @@ class CPPCHECKLIB CheckClass : public Check { void noCopyConstructorError(const Scope *scope, bool isdefault, const Token *alloc, bool inconclusive); void noOperatorEqError(const Scope *scope, bool isdefault, const Token *alloc, bool inconclusive); void noDestructorError(const Scope *scope, bool isdefault, const Token *alloc); - void uninitVarError(const Token *tok, bool isprivate, FunctionType functionType, const std::string &classname, const std::string &varname, bool derived, bool inconclusive); + void uninitVarError(const Token *tok, bool isprivate, FunctionType functionType, const std::string &classname, const std::string &varname, bool derived, bool inconclusive, bool noCtor = false, bool inheritedCtor = false); void uninitVarError(const Token *tok, const std::string &classname, const std::string &varname); void missingMemberCopyError(const Token *tok, FunctionType functionType, const std::string& classname, const std::string& varname); void operatorEqVarError(const Token *tok, const std::string &classname, const std::string &varname, bool inconclusive); - void unusedPrivateFunctionError(const Token *tok1, const Token *tok2, const std::string &classname, const std::string &funcname); + void unusedPrivateFunctionError(const Token *tokImpl, const Token *tokDef, const std::string &classname, const std::string &funcname); void memsetError(const Token *tok, const std::string &memfunc, const std::string &classname, const std::string &type, bool isContainer = false); void memsetErrorReference(const Token *tok, const std::string &memfunc, const std::string &type); void memsetErrorFloat(const Token *tok, const std::string &type); @@ -187,38 +214,6 @@ class CPPCHECKLIB CheckClass : public Check { void unsafeClassRefMemberError(const Token *tok, const std::string &varname); void checkDuplInheritedMembersRecursive(const Type* typeCurrent, const Type* typeBase); - void getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const override; - - static std::string myName() { - return "Class"; - } - - std::string classInfo() const override { - return "Check the code for each class.\n" - "- Missing constructors and copy constructors\n" - //"- Missing allocation of memory in copy constructor\n" - "- Constructors which should be explicit\n" - "- Are all variables initialized by the constructors?\n" - "- Are all variables assigned by 'operator='?\n" - "- Warn if memset, memcpy etc are used on a class\n" - "- Warn if memory for classes is allocated with malloc()\n" - "- If it's a base class, check that the destructor is virtual\n" - "- Are there unused private functions?\n" - "- 'operator=' should check for assignment to self\n" - "- Constness for member functions\n" - "- Order of initializations\n" - "- Suggest usage of initialization list\n" - "- Initialization of a member with itself\n" - "- Suspicious subtraction from 'this'\n" - "- Call of pure virtual function in constructor/destructor\n" - "- Duplicated inherited data members\n" - // disabled for now "- If 'copy constructor' defined, 'operator=' also should be defined and vice versa\n" - "- Check that arbitrary usage of public interface does not result in division by zero\n" - "- Delete \"self pointer\" and then access 'this'\n" - "- Check that the 'override' keyword is used when overriding virtual functions\n" - "- Check that the 'one definition rule' is not violated\n"; - } - // operatorEqRetRefThis helper functions void checkReturnPtrThis(const Scope *scope, const Function *func, const Token *tok, const Token *last); void checkReturnPtrThis(const Scope *scope, const Function *func, const Token *tok, const Token *last, std::set& analyzedFunctions); @@ -254,6 +249,15 @@ class CPPCHECKLIB CheckClass : public Check { bool init{}; }; + static void handleUnionMembers(std::vector& usageList); + + enum class Initialization : std::uint8_t { + Missing, // a member that is missing initialization + Initialized, // a member that has a constructor or an explicit initializer + None // a member that cannot be initialized (does not trigger a warning for other uninitialized members) + }; + Initialization checkInitialization(const Usage& usage, FunctionType funcType, bool noConstructor = false) const; + static bool isBaseClassMutableMemberFunc(const Token *tok, const Scope *scope); /** diff --git a/lib/checkcondition.cpp b/lib/checkcondition.cpp index cf54c882f34..ad547cf543e 100644 --- a/lib/checkcondition.cpp +++ b/lib/checkcondition.cpp @@ -52,12 +52,7 @@ static const CWE CWE571(571U); // Expression is Always True //--------------------------------------------------------------------------- -// Register this check class (by creating a static instance of it) -namespace { - CheckCondition instance; -} - -bool CheckCondition::diag(const Token* tok, bool insert) +bool CheckConditionImpl::diag(const Token* tok, bool insert) { if (!tok) return false; @@ -78,14 +73,14 @@ bool CheckCondition::diag(const Token* tok, bool insert) return true; } -bool CheckCondition::diag(const Token* tok1, const Token* tok2) +bool CheckConditionImpl::diag(const Token* tok1, const Token* tok2) { const bool b1 = diag(tok1); const bool b2 = diag(tok2); return b1 && b2; } -bool CheckCondition::isAliased(const std::set &vars) const +bool CheckConditionImpl::isAliased(const std::set &vars) const { for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) { if (Token::Match(tok, "= & %var% ;") && vars.find(tok->tokAt(2)->varId()) != vars.end()) @@ -94,9 +89,9 @@ bool CheckCondition::isAliased(const std::set &vars) const return false; } -void CheckCondition::assignIf() +void CheckConditionImpl::assignIf() { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("assignIfError")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("assignIfError")) return; logChecker("CheckCondition::assignIf"); // style @@ -167,12 +162,12 @@ static bool isParameterChanged(const Token *partok) } /** parse scopes recursively */ -bool CheckCondition::assignIfParseScope(const Token * const assignTok, - const Token * const startTok, - const nonneg int varid, - const bool islocal, - const char bitop, - const MathLib::bigint num) +bool CheckConditionImpl::assignIfParseScope(const Token * const assignTok, + const Token * const startTok, + const nonneg int varid, + const bool islocal, + const char bitop, + const MathLib::bigint num) { bool ret = false; @@ -209,7 +204,7 @@ bool CheckCondition::assignIfParseScope(const Token * const assignTok, // is variable changed in loop? const Token *bodyStart = tok2->linkAt(1)->next(); const Token *bodyEnd = bodyStart ? bodyStart->link() : nullptr; - if (!bodyEnd || bodyEnd->str() != "}" || isVariableChanged(bodyStart, bodyEnd, varid, !islocal, *mSettings)) + if (!bodyEnd || bodyEnd->str() != "}" || isVariableChanged(bodyStart, bodyEnd, varid, !islocal, mSettings)) continue; } @@ -245,7 +240,7 @@ bool CheckCondition::assignIfParseScope(const Token * const assignTok, return false; } -void CheckCondition::assignIfError(const Token *tok1, const Token *tok2, const std::string &condition, bool result) +void CheckConditionImpl::assignIfError(const Token *tok1, const Token *tok2, const std::string &condition, bool result) { if (tok2 && diag(tok2->tokAt(2))) return; @@ -257,7 +252,7 @@ void CheckCondition::assignIfError(const Token *tok1, const Token *tok2, const s } -void CheckCondition::mismatchingBitAndError(const Token *tok1, const MathLib::bigint num1, const Token *tok2, const MathLib::bigint num2) +void CheckConditionImpl::mismatchingBitAndError(const Token *tok1, const MathLib::bigint num1, const Token *tok2, const MathLib::bigint num2) { std::list locations = { tok1, tok2 }; @@ -313,9 +308,9 @@ static bool isOperandExpanded(const Token *tok) return false; } -void CheckCondition::checkBadBitmaskCheck() +void CheckConditionImpl::checkBadBitmaskCheck() { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("badBitmaskCheck")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("badBitmaskCheck")) return; logChecker("CheckCondition::checkBadBitmaskCheck"); // style @@ -353,7 +348,7 @@ void CheckCondition::checkBadBitmaskCheck() } } -void CheckCondition::badBitmaskCheckError(const Token *tok, bool isNoOp) +void CheckConditionImpl::badBitmaskCheckError(const Token *tok, bool isNoOp) { if (isNoOp) reportError(tok, Severity::style, "badBitmaskCheck", "Operator '|' with one operand equal to zero is redundant.", CWE571, Certainty::normal); @@ -361,9 +356,9 @@ void CheckCondition::badBitmaskCheckError(const Token *tok, bool isNoOp) reportError(tok, Severity::warning, "badBitmaskCheck", "Result of operator '|' is always true if one operand is non-zero. Did you intend to use '&'?", CWE571, Certainty::normal); } -void CheckCondition::comparison() +void CheckConditionImpl::comparison() { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("comparisonError")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("comparisonError")) return; logChecker("CheckCondition::comparison"); // style @@ -426,7 +421,7 @@ void CheckCondition::comparison() } } -void CheckCondition::comparisonError(const Token *tok, const std::string &bitop, MathLib::bigint value1, const std::string &op, MathLib::bigint value2, bool result) +void CheckConditionImpl::comparisonError(const Token *tok, const std::string &bitop, MathLib::bigint value1, const std::string &op, MathLib::bigint value2, bool result) { std::ostringstream expression; expression << std::hex << "(X " << bitop << " 0x" << value1 << ") " << op << " 0x" << value2; @@ -440,13 +435,13 @@ void CheckCondition::comparisonError(const Token *tok, const std::string &bitop, reportError(tok, Severity::style, "comparisonError", errmsg, CWE398, Certainty::normal); } -bool CheckCondition::isOverlappingCond(const Token * const cond1, const Token * const cond2, bool pure) const +bool CheckConditionImpl::isOverlappingCond(const Token * const cond1, const Token * const cond2, bool pure) const { if (!cond1 || !cond2) return false; // same expressions - if (isSameExpression(true, cond1, cond2, *mSettings, pure, false)) + if (isSameExpression(true, cond1, cond2, mSettings, pure, false)) return true; // bitwise overlap for example 'x&7' and 'x==1' @@ -460,7 +455,7 @@ bool CheckCondition::isOverlappingCond(const Token * const cond1, const Token * if (!num1->isNumber() || MathLib::isNegative(num1->str())) return false; - if (!Token::Match(cond2, "&|==") || !cond2->astOperand1() || !cond2->astOperand2()) + if (!Token::Match(cond2, "&|==") || !cond2->astOperand1()) return false; const Token *expr2 = cond2->astOperand1(); const Token *num2 = cond2->astOperand2(); @@ -469,7 +464,7 @@ bool CheckCondition::isOverlappingCond(const Token * const cond1, const Token * if (!num2->isNumber() || MathLib::isNegative(num2->str())) return false; - if (!isSameExpression(true, expr1, expr2, *mSettings, pure, false)) + if (!isSameExpression(true, expr1, expr2, mSettings, pure, false)) return false; const MathLib::bigint value1 = MathLib::toBigNumber(num1); @@ -481,9 +476,9 @@ bool CheckCondition::isOverlappingCond(const Token * const cond1, const Token * return false; } -void CheckCondition::duplicateCondition() +void CheckConditionImpl::duplicateCondition() { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("duplicateCondition")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("duplicateCondition")) return; logChecker("CheckCondition::duplicateCondition"); // style @@ -514,13 +509,13 @@ void CheckCondition::duplicateCondition() continue; ErrorPath errorPath; - if (!findExpressionChanged(cond1, scope.classDef->next(), cond2, *mSettings) && - isSameExpression(true, cond1, cond2, *mSettings, true, true, &errorPath)) + if (!findExpressionChanged(cond1, scope.classDef->next(), cond2, mSettings) && + isSameExpression(true, cond1, cond2, mSettings, true, true, &errorPath)) duplicateConditionError(cond1, cond2, std::move(errorPath)); } } -void CheckCondition::duplicateConditionError(const Token *tok1, const Token *tok2, ErrorPath errorPath) +void CheckConditionImpl::duplicateConditionError(const Token *tok1, const Token *tok2, ErrorPath errorPath) { if (diag(tok1, tok2)) return; @@ -532,9 +527,9 @@ void CheckCondition::duplicateConditionError(const Token *tok1, const Token *tok reportError(std::move(errorPath), Severity::style, "duplicateCondition", msg, CWE398, Certainty::normal); } -void CheckCondition::multiCondition() +void CheckConditionImpl::multiCondition() { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("multiCondition")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("multiCondition")) return; logChecker("CheckCondition::multiCondition"); // style @@ -564,18 +559,18 @@ void CheckCondition::multiCondition() if (tok2->astOperand2()) { ErrorPath errorPath; if (isOverlappingCond(cond1, tok2->astOperand2(), true) && - !findExpressionChanged(cond1, cond1, tok2->astOperand2(), *mSettings)) + !findExpressionChanged(cond1, cond1, tok2->astOperand2(), mSettings)) overlappingElseIfConditionError(tok2->astOperand2(), cond1->linenr()); else if (isOppositeCond( - true, cond1, tok2->astOperand2(), *mSettings, true, true, &errorPath) && - !findExpressionChanged(cond1, cond1, tok2->astOperand2(), *mSettings)) + true, cond1, tok2->astOperand2(), mSettings, true, true, &errorPath) && + !findExpressionChanged(cond1, cond1, tok2->astOperand2(), mSettings)) oppositeElseIfConditionError(cond1, tok2->astOperand2(), std::move(errorPath)); } } } } -void CheckCondition::overlappingElseIfConditionError(const Token *tok, nonneg int line1) +void CheckConditionImpl::overlappingElseIfConditionError(const Token *tok, nonneg int line1) { if (diag(tok)) return; @@ -586,7 +581,7 @@ void CheckCondition::overlappingElseIfConditionError(const Token *tok, nonneg in reportError(tok, Severity::style, "multiCondition", errmsg.str(), CWE398, Certainty::normal); } -void CheckCondition::oppositeElseIfConditionError(const Token *ifCond, const Token *elseIfCond, ErrorPath errorPath) +void CheckConditionImpl::oppositeElseIfConditionError(const Token *ifCond, const Token *elseIfCond, ErrorPath errorPath) { if (diag(ifCond, elseIfCond)) return; @@ -602,7 +597,7 @@ void CheckCondition::oppositeElseIfConditionError(const Token *ifCond, const Tok //--------------------------------------------------------------------------- // - Opposite inner conditions => always false -// - (TODO) Same/Overlapping inner condition => always true +// - Same/Overlapping inner condition => always true // - same condition after early exit => always false //--------------------------------------------------------------------------- @@ -634,11 +629,11 @@ static bool isNestedInLambda(const Scope* inner, const Scope* outer) return false; } -void CheckCondition::multiCondition2() +void CheckConditionImpl::multiCondition2() { - if (!mSettings->severity.isEnabled(Severity::warning) && - !mSettings->isPremiumEnabled("identicalConditionAfterEarlyExit") && - !mSettings->isPremiumEnabled("identicalInnerCondition")) + if (!mSettings.severity.isEnabled(Severity::warning) && + !mSettings.isPremiumEnabled("identicalConditionAfterEarlyExit") && + !mSettings.isPremiumEnabled("identicalInnerCondition")) return; logChecker("CheckCondition::multiCondition2"); // warning @@ -673,7 +668,7 @@ void CheckCondition::multiCondition2() [&](const Token *cond) { if (Token::Match(cond, "%name% (")) { functionCall = true; - nonConstFunctionCall = isNonConstFunctionCall(cond, mSettings->library); + nonConstFunctionCall = isNonConstFunctionCall(cond, mSettings.library); if (nonConstFunctionCall) return ChildrenToVisit::done; } @@ -684,7 +679,7 @@ void CheckCondition::multiCondition2() if (!nonlocal && var) { if (!(var->isLocal() || var->isArgument())) nonlocal = true; - else if ((var->isPointer() || var->isReference()) && !Token::Match(cond->astParent(), "%oror%|&&|!")) + else if ((var->isPointer() || var->isReference() || var->isArray()) && !Token::Match(cond->astParent(), "%oror%|&&|!")) // TODO: if var is pointer check what it points at nonlocal = true; } @@ -730,18 +725,18 @@ void CheckCondition::multiCondition2() for (; tok && tok != endToken; tok = tok->next()) { if (isNestedInLambda(tok->scope(), cond1->scope())) continue; - if (isExpressionChangedAt(cond1, tok, 0, false, *mSettings)) + if (isExpressionChangedAt(cond1, tok, 0, false, mSettings)) break; if (Token::Match(tok, "if|return")) { const Token * condStartToken = tok->str() == "if" ? tok->next() : tok; const Token * condEndToken = tok->str() == "if" ? condStartToken->link() : Token::findsimplematch(condStartToken, ";"); // Does condition modify tracked variables? - if (findExpressionChanged(cond1, condStartToken, condEndToken, *mSettings)) + if (findExpressionChanged(cond1, condStartToken, condEndToken, mSettings)) break; // Condition.. const Token *cond2 = tok->str() == "if" ? condStartToken->astOperand2() : condStartToken->astOperand1(); - const bool isReturnVar = (tok->str() == "return" && (!Token::Match(cond2, "%cop%") || (cond2 && cond2->isUnaryOp("!")))); + const bool isReturnVar = (tok->str() == "return" && (!Token::Match(cond2, "%cop%") || cond2->isUnaryOp("!") || cond2->isUnaryOp("*"))); ErrorPath errorPath; @@ -750,15 +745,17 @@ void CheckCondition::multiCondition2() if (!firstCondition) return ChildrenToVisit::none; if (firstCondition->str() == "&&") { - if (!isOppositeCond(false, firstCondition, cond2, *mSettings, true, true)) + if (!isOppositeCond(false, firstCondition, cond2, mSettings, true, true)) return ChildrenToVisit::op1_and_op2; } if (!firstCondition->hasKnownIntValue()) { - if (!isReturnVar && isOppositeCond(false, firstCondition, cond2, *mSettings, true, true, &errorPath)) { + if (!isReturnVar && isOppositeCond(false, firstCondition, cond2, mSettings, true, true, &errorPath)) { if (!isAliased(vars)) oppositeInnerConditionError(firstCondition, cond2, errorPath); - } else if (!isReturnVar && isSameExpression(true, firstCondition, cond2, *mSettings, true, true, &errorPath)) { + } else if (!isReturnVar && isSameExpression(true, firstCondition, cond2, mSettings, true, true, &errorPath)) { identicalInnerConditionError(firstCondition, cond2, errorPath); + } else if (!isReturnVar && isOverlappingCond(cond2, firstCondition, true)) { + overlappingInnerConditionError(firstCondition, cond2, errorPath); } } return ChildrenToVisit::none; @@ -769,7 +766,7 @@ void CheckCondition::multiCondition2() return ChildrenToVisit::op1_and_op2; if ((!cond1->hasKnownIntValue() || !secondCondition->hasKnownIntValue()) && - isSameExpression(true, cond1, secondCondition, *mSettings, true, true, &errorPath)) { + isSameExpression(true, cond1, secondCondition, mSettings, true, true, &errorPath)) { if (!isAliased(vars) && !mTokenizer->hasIfdef(cond1, secondCondition)) { identicalConditionAfterEarlyExitError(cond1, secondCondition, errorPath); return ChildrenToVisit::done; @@ -780,10 +777,10 @@ void CheckCondition::multiCondition2() } } if (Token::Match(tok, "%name% (") && - isVariablesChanged(tok, tok->linkAt(1), 0, varsInCond, *mSettings)) { + isVariablesChanged(tok, tok->linkAt(1), 0, varsInCond, mSettings)) { break; } - if (Token::Match(tok, "%type% (") && nonlocal && isNonConstFunctionCall(tok, mSettings->library)) // non const function call -> bailout if there are nonlocal variables + if (Token::Match(tok, "%type% (") && nonlocal && isNonConstFunctionCall(tok, mSettings.library)) // non const function call -> bailout if there are nonlocal variables break; if (Token::Match(tok, "case|break|continue|return|throw") && tok->scope() == endToken->scope()) break; @@ -809,7 +806,7 @@ void CheckCondition::multiCondition2() break; } const bool changed = std::any_of(vars.cbegin(), vars.cend(), [&](int varid) { - return isVariableChanged(tok1, tok2, varid, nonlocal, *mSettings); + return isVariableChanged(tok1, tok2, varid, nonlocal, mSettings); }); if (changed) break; @@ -864,7 +861,7 @@ static std::string innerSmtString(const Token * tok) return top->str(); } -void CheckCondition::oppositeInnerConditionError(const Token *tok1, const Token* tok2, ErrorPath errorPath) +void CheckConditionImpl::oppositeInnerConditionError(const Token *tok1, const Token* tok2, ErrorPath errorPath) { if (diag(tok1, tok2)) return; @@ -879,7 +876,22 @@ void CheckCondition::oppositeInnerConditionError(const Token *tok1, const Token* reportError(std::move(errorPath), Severity::warning, "oppositeInnerCondition", msg, CWE398, Certainty::normal); } -void CheckCondition::identicalInnerConditionError(const Token *tok1, const Token* tok2, ErrorPath errorPath) +void CheckConditionImpl::overlappingInnerConditionError(const Token *tok1, const Token* tok2, ErrorPath errorPath) +{ + if (diag(tok1, tok2)) + return; + const std::string s1(tok1 ? tok1->expressionString() : "x"); + const std::string s2(tok2 ? tok2->expressionString() : "x"); + const std::string innerSmt = innerSmtString(tok2); + errorPath.emplace_back(tok1, "outer condition: " + s1); + errorPath.emplace_back(tok2, "overlapping inner condition: " + s2); + + const std::string msg("Overlapping inner '" + innerSmt + "' condition is always true.\n" + "Overlapping inner '" + innerSmt + "' condition is always true (outer condition is '" + s1 + "' and inner condition is '" + s2 + "')."); + reportError(std::move(errorPath), Severity::warning, "overlappingInnerCondition", msg, CWE398, Certainty::normal); +} + +void CheckConditionImpl::identicalInnerConditionError(const Token *tok1, const Token* tok2, ErrorPath errorPath) { if (diag(tok1, tok2)) return; @@ -894,7 +906,7 @@ void CheckCondition::identicalInnerConditionError(const Token *tok1, const Token reportError(std::move(errorPath), Severity::warning, "identicalInnerCondition", msg, CWE398, Certainty::normal); } -void CheckCondition::identicalConditionAfterEarlyExitError(const Token *cond1, const Token* cond2, ErrorPath errorPath) +void CheckConditionImpl::identicalConditionAfterEarlyExitError(const Token *cond1, const Token* cond2, ErrorPath errorPath) { if (diag(cond1, cond2)) return; @@ -1144,13 +1156,13 @@ static bool isIfConstexpr(const Token* tok) { return Token::simpleMatch(top->astOperand1(), "if") && top->astOperand1()->isConstexpr(); } -void CheckCondition::checkIncorrectLogicOperator() +void CheckConditionImpl::checkIncorrectLogicOperator() { - const bool printStyle = mSettings->severity.isEnabled(Severity::style); - const bool printWarning = mSettings->severity.isEnabled(Severity::warning); - if (!printWarning && !printStyle && !mSettings->isPremiumEnabled("incorrectLogicOperator")) + const bool printStyle = mSettings.severity.isEnabled(Severity::style); + const bool printWarning = mSettings.severity.isEnabled(Severity::warning); + if (!printWarning && !printStyle && !mSettings.isPremiumEnabled("incorrectLogicOperator")) return; - const bool printInconclusive = mSettings->certainty.isEnabled(Certainty::inconclusive); + const bool printInconclusive = mSettings.certainty.isEnabled(Certainty::inconclusive); logChecker("CheckCondition::checkIncorrectLogicOperator"); // style,warning @@ -1170,7 +1182,7 @@ void CheckCondition::checkIncorrectLogicOperator() ((tok->str() == "||" && tok->astOperand2()->str() == "&&") || (tok->str() == "&&" && tok->astOperand2()->str() == "||"))) { const Token* tok2 = tok->astOperand2()->astOperand1(); - if (isOppositeCond(true, tok->astOperand1(), tok2, *mSettings, true, false)) { + if (isOppositeCond(true, tok->astOperand1(), tok2, mSettings, true, false)) { std::string expr1(tok->astOperand1()->expressionString()); std::string expr2(tok->astOperand2()->astOperand1()->expressionString()); std::string expr3(tok->astOperand2()->astOperand2()->expressionString()); @@ -1202,7 +1214,7 @@ void CheckCondition::checkIncorrectLogicOperator() redundantConditionError(tok, msg, false); continue; } - if (isSameExpression(false, tok->astOperand1(), tok2, *mSettings, true, true)) { + if (isSameExpression(false, tok->astOperand1(), tok2, mSettings, true, true)) { std::string expr1(tok->astOperand1()->expressionString()); std::string expr2(tok->astOperand2()->astOperand1()->expressionString()); std::string expr3(tok->astOperand2()->astOperand2()->expressionString()); @@ -1267,7 +1279,7 @@ void CheckCondition::checkIncorrectLogicOperator() // Opposite comparisons around || or && => always true or always false const bool isLogicalOr(tok->str() == "||"); - if (!isfloat && isOppositeCond(isLogicalOr, tok->astOperand1(), tok->astOperand2(), *mSettings, true, true, &errorPath)) { + if (!isfloat && isOppositeCond(isLogicalOr, tok->astOperand1(), tok->astOperand2(), mSettings, true, true, &errorPath)) { if (!isIfConstexpr(tok)) { const bool alwaysTrue(isLogicalOr); incorrectLogicOperatorError(tok, conditionString(tok), alwaysTrue, inconclusive, std::move(errorPath)); @@ -1278,9 +1290,9 @@ void CheckCondition::checkIncorrectLogicOperator() if (!parseable) continue; - if (isSameExpression(true, comp1, comp2, *mSettings, true, true)) + if (isSameExpression(true, comp1, comp2, mSettings, true, true)) continue; // same expressions => only report that there are same expressions - if (!isSameExpression(true, expr1, expr2, *mSettings, true, true)) + if (!isSameExpression(true, expr1, expr2, mSettings, true, true)) continue; @@ -1289,6 +1301,10 @@ void CheckCondition::checkIncorrectLogicOperator() if (isfloat && (op1 == "==" || op1 == "!=" || op2 == "==" || op2 == "!=")) continue; + // no double value to compare (1e400L would become infinity) + if (isfloat && (MathLib::isLongDoubleOutOfDoubleRange(value1) || MathLib::isLongDoubleOutOfDoubleRange(value2))) + continue; + // the expr are not the token of the value but they provide better context const double d1 = isfloat ? MathLib::toDoubleNumber(value1) : 0; const double d2 = isfloat ? MathLib::toDoubleNumber(value2) : 0; @@ -1355,7 +1371,7 @@ void CheckCondition::checkIncorrectLogicOperator() } } -void CheckCondition::incorrectLogicOperatorError(const Token *tok, const std::string &condition, bool always, bool inconclusive, ErrorPath errors) +void CheckConditionImpl::incorrectLogicOperatorError(const Token *tok, const std::string &condition, bool always, bool inconclusive, ErrorPath errors) { if (diag(tok)) return; @@ -1372,7 +1388,7 @@ void CheckCondition::incorrectLogicOperatorError(const Token *tok, const std::st "Are these conditions necessary? Did you intend to use || instead? Are the numbers correct? Are you comparing the correct variables?", CWE570, inconclusive ? Certainty::inconclusive : Certainty::normal); } -void CheckCondition::redundantConditionError(const Token *tok, const std::string &text, bool inconclusive) +void CheckConditionImpl::redundantConditionError(const Token *tok, const std::string &text, bool inconclusive) { if (diag(tok)) return; @@ -1382,9 +1398,9 @@ void CheckCondition::redundantConditionError(const Token *tok, const std::string //----------------------------------------------------------------------------- // Detect "(var % val1) > val2" where val2 is >= val1. //----------------------------------------------------------------------------- -void CheckCondition::checkModuloAlwaysTrueFalse() +void CheckConditionImpl::checkModuloAlwaysTrueFalse() { - if (!mSettings->severity.isEnabled(Severity::warning)) + if (!mSettings.severity.isEnabled(Severity::warning)) return; logChecker("CheckCondition::checkModuloAlwaysTrueFalse"); // warning @@ -1412,7 +1428,7 @@ void CheckCondition::checkModuloAlwaysTrueFalse() } } -void CheckCondition::moduloAlwaysTrueFalseError(const Token* tok, const std::string& maxVal) +void CheckConditionImpl::moduloAlwaysTrueFalseError(const Token* tok, const std::string& maxVal) { if (diag(tok)) return; @@ -1438,9 +1454,9 @@ static int countPar(const Token *tok1, const Token *tok2) // Clarify condition '(x = a < 0)' into '((x = a) < 0)' or '(x = (a < 0))' // Clarify condition '(a & b == c)' into '((a & b) == c)' or '(a & (b == c))' //--------------------------------------------------------------------------- -void CheckCondition::clarifyCondition() +void CheckConditionImpl::clarifyCondition() { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("clarifyCondition")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("clarifyCondition")) return; logChecker("CheckCondition::clarifyCondition"); // style @@ -1479,7 +1495,7 @@ void CheckCondition::clarifyCondition() } } -void CheckCondition::clarifyConditionError(const Token *tok, bool assign, bool boolop) +void CheckConditionImpl::clarifyConditionError(const Token *tok, bool assign, bool boolop) { std::string errmsg; @@ -1502,13 +1518,11 @@ void CheckCondition::clarifyConditionError(const Token *tok, bool assign, bool b errmsg, CWE398, Certainty::normal); } -void CheckCondition::alwaysTrueFalse() +void CheckConditionImpl::alwaysTrueFalse() { - const bool pedantic = mSettings->isPremiumEnabled("alwaysTrue") || - mSettings->isPremiumEnabled("alwaysFalse") || - mSettings->isPremiumEnabled("knownConditionTrueFalse"); + const bool pedantic = mSettings.isPremiumEnabled("knownConditionTrueFalse"); - if (!pedantic && !mSettings->severity.isEnabled(Severity::style)) + if (!pedantic && !mSettings.severity.isEnabled(Severity::style)) return; logChecker("CheckCondition::alwaysTrueFalse"); // style @@ -1522,52 +1536,58 @@ void CheckCondition::alwaysTrueFalse() tok = tok->link(); continue; } - if (!tok->hasKnownIntValue()) + if (!tok->hasKnownIntValue() || !isConstExpression(tok, mSettings.library)) continue; - if (Token::Match(tok->previous(), "%name% (") && tok->previous()->function()) { - const Function* f = tok->previous()->function(); - if (f->functionScope && Token::Match(f->functionScope->bodyStart, "{ return true|false ;")) - continue; - } const Token* condition = nullptr; { // is this a condition.. const Token *parent = tok->astParent(); - while (Token::Match(parent, "%oror%|&&")) + bool hasComp = false; + while (Token::Match(parent, "%oror%|&&")) { + hasComp = true; parent = parent->astParent(); + } if (!parent) continue; if (parent->str() == "?" && precedes(tok, parent)) condition = parent; else if (Token::Match(parent->previous(), "if|while (")) condition = parent->previous(); - else if (Token::simpleMatch(parent, "return")) - condition = parent; else if (parent->str() == ";" && parent->astParent() && parent->astParent()->astParent() && Token::simpleMatch(parent->astParent()->astParent()->previous(), "for (")) condition = parent->astParent()->astParent()->previous(); - else if (Token::Match(tok, "%comp%")) + else if ((Token::Match(tok, "%comp%|!") || isConstFunctionCall(tok->previous(), mSettings.library)) && Token::Match(tok->astParent(), "%oror%|&&")) condition = tok; - else if (tok->str() == "(" && astIsBool(parent) && Token::Match(parent, "%assign%")) + else if (hasComp && Token::Match(tok, "!|%var%") && astIsBool(parent) && Token::Match(parent, "%assign%")) condition = tok; else continue; + } // Skip already diagnosed values if (diag(tok, false)) continue; if (condition->isConstexpr()) continue; - if (!isUsedAsBool(tok, *mSettings)) + if (!isUsedAsBool(tok, mSettings)) continue; if (Token::simpleMatch(condition, "return") && Token::Match(tok, "%assign%")) continue; if (Token::simpleMatch(tok->astParent(), "return") && Token::Match(tok, ".|%var%")) continue; - if (Token::Match(tok, "%num%|%bool%|%char%")) - continue; - if (Token::Match(tok, "! %num%|%bool%|%char%")) - continue; + bool warnForNumber = false; + if (Token::Match(tok, "%num%|%bool%|%char%")) { + const bool isZeroOrOne = (tok->getKnownIntValue() >> 1) == 0; + warnForNumber = !isZeroOrOne && tok->tokType() == Token::eNumber && tok->astParent() == condition->astParent(); + if (!warnForNumber) + continue; + } + if (Token::Match(tok, "! %num%|%bool%|%char%")) { + const bool isZeroOrOne = tok->next()->hasKnownIntValue() && (tok->next()->getKnownIntValue() >> 1) == 0; + warnForNumber = !isZeroOrOne && tok->next()->tokType() == Token::eNumber && tok->astParent() == condition->astParent(); + if (!warnForNumber) + continue; + } if (Token::Match(tok, "%oror%|&&")) { bool bail = false; for (const Token* op : { tok->astOperand1(), tok->astOperand2() }) { @@ -1587,12 +1607,12 @@ void CheckCondition::alwaysTrueFalse() isSameExpression(true, tok->astOperand1(), tok->astOperand2(), - *mSettings, + mSettings, true, true)) continue; - if (!pedantic && isConstVarExpression(tok, [](const Token* tok) { + if (!pedantic && !warnForNumber && isConstVarExpression(tok, [](const Token* tok) { return Token::Match(tok, "[|(|&|+|-|*|/|%|^|>>|<<") && !Token::simpleMatch(tok, "( )"); })) continue; @@ -1602,8 +1622,8 @@ void CheckCondition::alwaysTrueFalse() { const ValueFlow::Value *zeroValue = nullptr; const Token *nonZeroExpr = nullptr; - if (CheckOther::comparisonNonZeroExpressionLessThanZero(tok, zeroValue, nonZeroExpr, /*suppress*/ true) || - CheckOther::testIfNonZeroExpressionIsPositive(tok, zeroValue, nonZeroExpr)) + if (CheckOtherImpl::comparisonNonZeroExpressionLessThanZero(tok, zeroValue, nonZeroExpr, /*suppress*/ true) || + CheckOtherImpl::testIfNonZeroExpressionIsPositive(tok, zeroValue, nonZeroExpr)) continue; } @@ -1662,7 +1682,7 @@ static std::string getConditionString(const Token* condition) return "Condition"; } -void CheckCondition::alwaysTrueFalseError(const Token* tok, const Token* condition, const ValueFlow::Value* value) +void CheckConditionImpl::alwaysTrueFalseError(const Token* tok, const Token* condition, const ValueFlow::Value* value) { const bool alwaysTrue = value && (value->intvalue != 0 || value->isImpossible()); const std::string expr = tok ? tok->expressionString() : std::string("x"); @@ -1676,7 +1696,7 @@ void CheckCondition::alwaysTrueFalseError(const Token* tok, const Token* conditi (alwaysTrue ? CWE571 : CWE570), Certainty::normal); } -void CheckCondition::checkInvalidTestForOverflow() +void CheckConditionImpl::checkInvalidTestForOverflow() { // Interesting blogs: // https://www.airs.com/blog/archives/120 @@ -1691,7 +1711,7 @@ void CheckCondition::checkInvalidTestForOverflow() // x + y < x -> y < 0 - if (!mSettings->severity.isEnabled(Severity::warning)) + if (!mSettings.severity.isEnabled(Severity::warning)) return; logChecker("CheckCondition::checkInvalidTestForOverflow"); // warning @@ -1725,7 +1745,7 @@ void CheckCondition::checkInvalidTestForOverflow() if (!isSameExpression(true, expr, lhs->astSibling(), - *mSettings, + mSettings, true, false)) continue; @@ -1764,7 +1784,7 @@ void CheckCondition::checkInvalidTestForOverflow() } } -void CheckCondition::invalidTestForOverflow(const Token* tok, const ValueType *valueType, const std::string &replace) +void CheckConditionImpl::invalidTestForOverflow(const Token* tok, const ValueType *valueType, const std::string &replace) { const std::string expr = (tok ? tok->expressionString() : std::string("x + c < x")); const std::string overflow = (valueType && valueType->pointer) ? "pointer overflow" : "signed integer overflow"; @@ -1779,9 +1799,9 @@ void CheckCondition::invalidTestForOverflow(const Token* tok, const ValueType *v } -void CheckCondition::checkPointerAdditionResultNotNull() +void CheckConditionImpl::checkPointerAdditionResultNotNull() { - if (!mSettings->severity.isEnabled(Severity::warning)) + if (!mSettings.severity.isEnabled(Severity::warning)) return; logChecker("CheckCondition::checkPointerAdditionResultNotNull"); // warning @@ -1820,15 +1840,51 @@ void CheckCondition::checkPointerAdditionResultNotNull() } } -void CheckCondition::pointerAdditionResultNotNullError(const Token *tok, const Token *calc) +void CheckConditionImpl::pointerAdditionResultNotNullError(const Token *tok, const Token *calc) { const std::string s = calc ? calc->expressionString() : "ptr+1"; reportError(tok, Severity::warning, "pointerAdditionResultNotNull", "Comparison is wrong. Result of '" + s + "' can't be 0 unless there is pointer overflow, and pointer overflow is undefined behaviour."); } -void CheckCondition::checkDuplicateConditionalAssign() +static bool checkBoolConditionalAssign(const Token* condTok, const Token* assignTok, bool& isRedundant) { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("duplicateConditionalAssign")) + bool isNegation = false; + const Token* varTok = condTok; + if (condTok->isUnaryOp("!")) { + isNegation = true; + varTok = varTok->astOperand1(); + } else if (condTok->isBinaryOp()) { + varTok = condTok->astOperand1(); + if (varTok->hasKnownIntValue()) + varTok = condTok->astOperand2(); + } + + const ValueType* vt = varTok->variable() ? varTok->variable()->valueType() : nullptr; + if (!(vt && vt->type == ValueType::Type::BOOL && !vt->pointer)) + return false; + + if (!(assignTok->astOperand1() && assignTok->astOperand1()->varId() == varTok->varId())) + return false; + if (!(assignTok->astOperand2() && assignTok->astOperand2()->hasKnownIntValue())) + return false; + const MathLib::bigint val = assignTok->astOperand2()->getKnownIntValue(); + if (val < 0 || val > 1) + return false; + if (condTok->isBinaryOp()) { + if (!varTok->astSibling()->hasKnownIntValue()) + return false; + const MathLib::bigint compVal = varTok->astSibling()->getKnownIntValue(); + if (compVal < 0 || compVal > 1) + return false; + isNegation = (condTok->str() == "!=") == (compVal == 1); + } + isRedundant = (isNegation && val == 0) || (!isNegation && val == 1); + return true; +} + +void CheckConditionImpl::checkDuplicateConditionalAssign() +{ + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("duplicateConditionalAssign")) return; logChecker("CheckCondition::checkDuplicateConditionalAssign"); // style @@ -1842,7 +1898,7 @@ void CheckCondition::checkDuplicateConditionalAssign() continue; const Token *blockTok = tok->linkAt(1)->next(); const Token *condTok = tok->next()->astOperand2(); - const bool isBoolVar = Token::Match(condTok, "!| %var%"); + bool isBoolVar = Token::Match(condTok, "!| %var%"); if (!isBoolVar && !Token::Match(condTok, "==|!=")) continue; if ((isBoolVar || condTok->str() == "!=") && Token::simpleMatch(blockTok->link(), "} else {")) @@ -1855,40 +1911,27 @@ void CheckCondition::checkDuplicateConditionalAssign() if (nextAfterAstRightmostLeaf(assignTok) != blockTok->link()->previous()) continue; bool isRedundant = false; - if (isBoolVar) { - const bool isNegation = condTok->str() == "!"; - const Token* const varTok = isNegation ? condTok->next() : condTok; - const ValueType* vt = varTok->variable() ? varTok->variable()->valueType() : nullptr; - if (!(vt && vt->type == ValueType::Type::BOOL && !vt->pointer)) - continue; - - if (!(assignTok->astOperand1() && assignTok->astOperand1()->varId() == varTok->varId())) - continue; - if (!(assignTok->astOperand2() && assignTok->astOperand2()->hasKnownIntValue())) - continue; - const MathLib::bigint val = assignTok->astOperand2()->getKnownIntValue(); - if (val < 0 || val > 1) - continue; - isRedundant = (isNegation && val == 0) || (!isNegation && val == 1); + if (checkBoolConditionalAssign(condTok, assignTok, isRedundant)) { + isBoolVar = true; } else { // comparison if (!isSameExpression( - true, condTok->astOperand1(), assignTok->astOperand1(), *mSettings, true, true)) + true, condTok->astOperand1(), assignTok->astOperand1(), mSettings, true, true)) continue; if (!isSameExpression( - true, condTok->astOperand2(), assignTok->astOperand2(), *mSettings, true, true)) + true, condTok->astOperand2(), assignTok->astOperand2(), mSettings, true, true)) continue; } - duplicateConditionalAssignError(condTok, assignTok, isRedundant); + duplicateConditionalAssignError(condTok, assignTok, isRedundant, isBoolVar); } } } -void CheckCondition::duplicateConditionalAssignError(const Token *condTok, const Token* assignTok, bool isRedundant) +void CheckConditionImpl::duplicateConditionalAssignError(const Token *condTok, const Token* assignTok, bool isRedundant, bool isBoolVar) { ErrorPath errors; std::string msg = "Duplicate expression for the condition and assignment."; if (condTok && assignTok) { - if (condTok->str() == "==") { + if (condTok->str() == "==" && !isBoolVar) { msg = "Assignment '" + assignTok->expressionString() + "' is redundant with condition '" + condTok->expressionString() + "'."; errors.emplace_back(condTok, "Condition '" + condTok->expressionString() + "'"); errors.emplace_back(assignTok, "Assignment '" + assignTok->expressionString() + "' is redundant"); @@ -1904,10 +1947,9 @@ void CheckCondition::duplicateConditionalAssignError(const Token *condTok, const std::move(errors), Severity::style, "duplicateConditionalAssign", msg, CWE398, Certainty::normal); } - -void CheckCondition::checkAssignmentInCondition() +void CheckConditionImpl::checkAssignmentInCondition() { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("assignmentInCondition")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("assignmentInCondition")) return; logChecker("CheckCondition::checkAssignmentInCondition"); // style @@ -1939,7 +1981,7 @@ void CheckCondition::checkAssignmentInCondition() } } -void CheckCondition::assignmentInCondition(const Token *eq) +void CheckConditionImpl::assignmentInCondition(const Token *eq) { std::string expr = eq ? eq->expressionString() : "x=y"; @@ -1952,12 +1994,12 @@ void CheckCondition::assignmentInCondition(const Token *eq) Certainty::normal); } -void CheckCondition::checkCompareValueOutOfTypeRange() +void CheckConditionImpl::checkCompareValueOutOfTypeRange() { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("compareValueOutOfTypeRangeError")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("compareValueOutOfTypeRangeError")) return; - if (mSettings->platform.type == Platform::Type::Unspecified) + if (mSettings.platform.type == Platform::Type::Unspecified) return; logChecker("CheckCondition::checkCompareValueOutOfTypeRange"); // style,platform @@ -1971,29 +2013,32 @@ void CheckCondition::checkCompareValueOutOfTypeRange() for (int i = 0; i < 2; ++i) { const Token * const valueTok = (i == 0) ? tok->astOperand1() : tok->astOperand2(); const Token * const typeTok = valueTok->astSibling(); - if (!valueTok->hasKnownIntValue() || !typeTok->valueType() || typeTok->valueType()->pointer) + const ValueType* const typeVt = typeTok->valueType(); + if (!valueTok->hasKnownIntValue() || !typeVt || typeVt->pointer) continue; if (valueTok->getKnownIntValue() < 0 && valueTok->valueType() && valueTok->valueType()->sign != ValueType::Sign::SIGNED) continue; + if (typeTok->isLiteral()) + continue; std::uint8_t bits = 0; - switch (typeTok->valueType()->type) { + switch (typeVt->type) { case ValueType::Type::BOOL: bits = 1; break; case ValueType::Type::CHAR: - bits = mSettings->platform.char_bit; + bits = mSettings.platform.char_bit; break; case ValueType::Type::SHORT: - bits = mSettings->platform.short_bit; + bits = mSettings.platform.short_bit; break; case ValueType::Type::INT: - bits = mSettings->platform.int_bit; + bits = mSettings.platform.int_bit; break; case ValueType::Type::LONG: - bits = mSettings->platform.long_bit; + bits = mSettings.platform.long_bit; break; case ValueType::Type::LONGLONG: - bits = mSettings->platform.long_long_bit; + bits = mSettings.platform.long_long_bit; break; default: break; @@ -2001,12 +2046,12 @@ void CheckCondition::checkCompareValueOutOfTypeRange() if (bits == 0 || bits >= 63) continue; - const auto typeMinValue = (typeTok->valueType()->sign == ValueType::Sign::UNSIGNED) ? 0 : (-(1LL << (bits-1))); + const auto typeMinValue = (typeVt->sign == ValueType::Sign::UNSIGNED) ? 0 : (-(1LL << (bits-1))); const auto unsignedTypeMaxValue = (1LL << bits) - 1LL; long long typeMaxValue; - if (typeTok->valueType()->sign != ValueType::Sign::SIGNED) + if (typeVt->sign != ValueType::Sign::SIGNED && !(typeVt->sign == ValueType::Sign::UNKNOWN_SIGN && typeVt->type == ValueType::Type::CHAR && mSettings.platform.defaultSign == 's')) typeMaxValue = unsignedTypeMaxValue; - else if (bits >= mSettings->platform.int_bit && (!valueTok->valueType() || valueTok->valueType()->sign != ValueType::Sign::SIGNED)) + else if (bits >= mSettings.platform.int_bit && (!valueTok->valueType() || valueTok->valueType()->sign != ValueType::Sign::SIGNED)) typeMaxValue = unsignedTypeMaxValue; else typeMaxValue = unsignedTypeMaxValue / 2; @@ -2065,16 +2110,16 @@ void CheckCondition::checkCompareValueOutOfTypeRange() } if (!error || diag(tok)) continue; - compareValueOutOfTypeRangeError(valueTok, typeTok->valueType()->str(), kiv, result); + compareValueOutOfTypeRangeError(valueTok, typeVt->str(), kiv, result); } } } } -void CheckCondition::compareValueOutOfTypeRangeError(const Token *comparison, const std::string &type, MathLib::bigint value, bool result) +void CheckConditionImpl::compareValueOutOfTypeRangeError(const Token *comparisonTok, const std::string &type, MathLib::bigint value, bool result) { reportError( - comparison, + comparisonTok, Severity::style, "compareValueOutOfTypeRangeError", "Comparing expression of type '" + type + "' against value " + MathLib::toString(value) + ". Condition is always " + bool_to_string(result) + ".", @@ -2082,9 +2127,9 @@ void CheckCondition::compareValueOutOfTypeRangeError(const Token *comparison, co Certainty::normal); } -void CheckCondition::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) +void CheckCondition::runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) { - CheckCondition checkCondition(&tokenizer, &tokenizer.getSettings(), errorLogger); + CheckConditionImpl checkCondition(&tokenizer, tokenizer.getSettings(), errorLogger); checkCondition.multiCondition(); checkCondition.clarifyCondition(); // not simplified because ifAssign checkCondition.multiCondition2(); @@ -2102,15 +2147,16 @@ void CheckCondition::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLog checkCondition.alwaysTrueFalse(); } -void CheckCondition::getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const +void CheckCondition::getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const { - CheckCondition c(nullptr, settings, errorLogger); + CheckConditionImpl c(nullptr, settings, errorLogger); c.assignIfError(nullptr, nullptr, "", false); c.badBitmaskCheckError(nullptr); c.comparisonError(nullptr, "&", 6, "==", 1, false); c.duplicateConditionError(nullptr, nullptr, ErrorPath{}); c.overlappingElseIfConditionError(nullptr, 1); + c.overlappingInnerConditionError(nullptr, nullptr, ErrorPath()); c.mismatchingBitAndError(nullptr, 0xf0, nullptr, 1); c.oppositeInnerConditionError(nullptr, nullptr, ErrorPath{}); c.identicalInnerConditionError(nullptr, nullptr, ErrorPath{}); diff --git a/lib/checkcondition.h b/lib/checkcondition.h index dac3ba8d0de..4a4e1f22db1 100644 --- a/lib/checkcondition.h +++ b/lib/checkcondition.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,6 +23,7 @@ //--------------------------------------------------------------------------- #include "check.h" +#include "checkimpl.h" #include "config.h" #include "mathlib.h" #include "errortypes.h" @@ -50,14 +51,38 @@ namespace ValueFlow { class CPPCHECKLIB CheckCondition : public Check { public: /** This constructor is used when registering the CheckAssignIf */ - CheckCondition() : Check(myName()) {} + CheckCondition() : Check("Condition") {} private: - /** This constructor is used when running checks. */ - CheckCondition(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) - : Check(myName(), tokenizer, settings, errorLogger) {} + void runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) override; - void runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) override; + void getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const override; + + std::string classInfo() const override { + return "Match conditions with assignments and other conditions:\n" + "- Mismatching assignment and comparison => comparison is always true/false\n" + "- Mismatching lhs and rhs in comparison => comparison is always true/false\n" + "- Detect usage of | where & should be used\n" + "- Duplicate condition and assignment\n" + "- Detect matching 'if' and 'else if' conditions\n" + "- Mismatching bitand (a &= 0xf0; a &= 1; => a = 0)\n" + "- Opposite inner condition is always false\n" + "- Identical condition after early exit is always false\n" + "- Condition that is always true/false\n" + "- Mutual exclusion over || always evaluating to true\n" + "- Comparisons of modulo results that are always true/false.\n" + "- Known variable values => condition is always true/false\n" + "- Invalid test for overflow. Some mainstream compilers remove such overflow tests when optimising code.\n" + "- Suspicious assignment of container/iterator in condition => condition is always true.\n"; + } +}; + + +class CPPCHECKLIB CheckConditionImpl : public CheckImpl { +public: + /** This constructor is used when running checks. */ + CheckConditionImpl(const Tokenizer *tokenizer, const Settings &settings, ErrorLogger &errorLogger) + : CheckImpl(tokenizer, settings, errorLogger) {} /** mismatching assignment / comparison */ void assignIf(); @@ -84,7 +109,7 @@ class CPPCHECKLIB CheckCondition : public Check { /** * multiconditions #2 * - Opposite inner conditions => always false - * - (TODO) Same/Overlapping inner condition => always true + * - Same/Overlapping inner condition => always true * - same condition after early exit => always false **/ void multiCondition2(); @@ -130,6 +155,7 @@ class CPPCHECKLIB CheckCondition : public Check { bool result); void duplicateConditionError(const Token *tok1, const Token *tok2, ErrorPath errorPath); void overlappingElseIfConditionError(const Token *tok, nonneg int line1); + void overlappingInnerConditionError(const Token *tok1, const Token *tok2, ErrorPath errorPath); void oppositeElseIfConditionError(const Token *ifCond, const Token *elseIfCond, ErrorPath errorPath); void oppositeInnerConditionError(const Token *tok1, const Token* tok2, ErrorPath errorPath); @@ -150,36 +176,12 @@ class CPPCHECKLIB CheckCondition : public Check { void invalidTestForOverflow(const Token* tok, const ValueType *valueType, const std::string &replace); void pointerAdditionResultNotNullError(const Token *tok, const Token *calc); - void duplicateConditionalAssignError(const Token *condTok, const Token* assignTok, bool isRedundant = false); + void duplicateConditionalAssignError(const Token *condTok, const Token* assignTok, bool isRedundant = false, bool isBoolVar = false); void assignmentInCondition(const Token *eq); void checkCompareValueOutOfTypeRange(); - void compareValueOutOfTypeRangeError(const Token *comparison, const std::string &type, MathLib::bigint value, bool result); - - void getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const override; - - static std::string myName() { - return "Condition"; - } - - std::string classInfo() const override { - return "Match conditions with assignments and other conditions:\n" - "- Mismatching assignment and comparison => comparison is always true/false\n" - "- Mismatching lhs and rhs in comparison => comparison is always true/false\n" - "- Detect usage of | where & should be used\n" - "- Duplicate condition and assignment\n" - "- Detect matching 'if' and 'else if' conditions\n" - "- Mismatching bitand (a &= 0xf0; a &= 1; => a = 0)\n" - "- Opposite inner condition is always false\n" - "- Identical condition after early exit is always false\n" - "- Condition that is always true/false\n" - "- Mutual exclusion over || always evaluating to true\n" - "- Comparisons of modulo results that are always true/false.\n" - "- Known variable values => condition is always true/false\n" - "- Invalid test for overflow. Some mainstream compilers remove such overflow tests when optimising code.\n" - "- Suspicious assignment of container/iterator in condition => condition is always true.\n"; - } + void compareValueOutOfTypeRangeError(const Token *comparisonTok, const std::string &type, MathLib::bigint value, bool result); }; /// @} //--------------------------------------------------------------------------- diff --git a/lib/checkers.cpp b/lib/checkers.cpp index f7d7f913da6..fef33042472 100644 --- a/lib/checkers.cpp +++ b/lib/checkers.cpp @@ -47,10 +47,10 @@ namespace checkers { {"CheckBufferOverrun::objectIndex",""}, {"CheckBufferOverrun::pointerArithmetic","portability"}, {"CheckBufferOverrun::stringNotZeroTerminated","warning,inconclusive"}, - {"CheckClass::analyseWholeProgram",""}, {"CheckClass::checkConst","style,inconclusive"}, {"CheckClass::checkConstructors","style,warning"}, {"CheckClass::checkCopyConstructors","warning"}, + {"CheckClass::checkCtuOneDefinitionRule",""}, {"CheckClass::checkDuplInheritedMembers","warning"}, {"CheckClass::checkExplicitConstructors","style"}, {"CheckClass::checkMemset",""}, @@ -102,6 +102,7 @@ namespace checkers { {"CheckIO::checkCoutCerrMisusage","c"}, {"CheckIO::checkFileUsage",""}, {"CheckIO::checkWrongPrintfScanfArguments",""}, + {"CheckIO::checkWrongfeofUsage",""}, {"CheckIO::invalidScanf",""}, {"CheckLeakAutoVar::check","notclang"}, {"CheckMemoryLeakInClass::check",""}, @@ -109,7 +110,7 @@ namespace checkers { {"CheckMemoryLeakNoVar::check",""}, {"CheckMemoryLeakNoVar::checkForUnsafeArgAlloc",""}, {"CheckMemoryLeakStructMember::check",""}, - {"CheckNullPointer::analyseWholeProgram","unusedfunctions"}, + {"CheckNullPointer::analyseWholeProgram",""}, {"CheckNullPointer::arithmetic",""}, {"CheckNullPointer::nullConstantDereference",""}, {"CheckNullPointer::nullPointer",""}, @@ -145,6 +146,7 @@ namespace checkers { {"CheckOther::checkSuspiciousSemicolon","warning,inconclusive"}, {"CheckOther::checkUnionZeroInit","portability"}, {"CheckOther::checkUnreachableCode","style"}, + {"CheckOther::checkUnreachableSwitchCase","style"}, {"CheckOther::checkUnusedLabel","style,warning"}, {"CheckOther::checkVarFuncNullUB","portability"}, {"CheckOther::checkVariableScope","style,notclang"}, @@ -166,6 +168,7 @@ namespace checkers { {"CheckSizeof::sizeofVoid","portability"}, {"CheckSizeof::sizeofsizeof","warning"}, {"CheckSizeof::suspiciousSizeofCalculation","warning,inconclusive"}, + {"CheckStl::algorithmOutOfBounds",""}, {"CheckStl::checkDereferenceInvalidIterator","warning"}, {"CheckStl::checkDereferenceInvalidIterator2",""}, {"CheckStl::checkFindInsert","performance"}, @@ -1034,6 +1037,8 @@ namespace checkers { {7,5,2,Req}, {7,5,3,Req}, {7,5,4,Adv}, + {8,0,1,Req}, + {8,3,1,Req}, {8,4,1,Req}, {8,4,2,Req}, {8,4,3,Req}, @@ -1071,9 +1076,9 @@ namespace checkers { {14,7,2,Req}, {14,7,3,Req}, {14,8,1,Req}, - {14,8,2,Req}, - {15,0,1,Req}, - {15,0,2,Req}, + {14,8,2,Adv}, + {15,0,1,Doc}, + {15,0,2,Adv}, {15,0,3,Req}, {15,1,1,Req}, {15,1,2,Req}, @@ -1216,8 +1221,8 @@ namespace checkers { {9,3,1,Req}, {9,4,1,Req}, {9,4,2,Req}, - {9,5,2,Adv}, - {9,5,3,Req}, + {9,5,1,Adv}, + {9,5,2,Req}, {9,6,1,Adv}, {9,6,2,Req}, {9,6,3,Req}, @@ -1254,6 +1259,7 @@ namespace checkers { {15,1,3,Req}, {15,1,4,Adv}, {15,1,5,Req}, + {16,5,1,Req}, {16,5,2,Req}, {16,6,1,Adv}, {17,8,1,Req}, @@ -1677,9 +1683,25 @@ std::vector checkers::autosarInfo{ }; std::vector checkers::certCInfo{ - {"PRE30-C", "L3"}, - {"PRE31-C", "L3"}, - {"PRE32-C", "L3"}, + {"ARR30-C", "L2"}, + {"ARR32-C", "L2"}, + {"ARR36-C", "L3"}, + {"ARR37-C", "L2"}, + {"ARR38-C", "L2"}, + {"ARR39-C", "L2"}, + {"CON30-C", "L3"}, + {"CON31-C", "L3"}, + {"CON32-C", "L3"}, + {"CON33-C", "L3"}, + {"CON34-C", "L3"}, + {"CON35-C", "L3"}, + {"CON36-C", "L3"}, + {"CON37-C", "L3"}, + {"CON38-C", "L3"}, + {"CON39-C", "L3"}, + {"CON40-C", "L2"}, + {"CON41-C", "L3"}, + {"CON43-C", "L3"}, {"DCL30-C", "L2"}, {"DCL31-C", "L3"}, {"DCL36-C", "L2"}, @@ -1688,6 +1710,16 @@ std::vector checkers::certCInfo{ {"DCL39-C", "L3"}, {"DCL40-C", "L3"}, {"DCL41-C", "L2"}, + {"DCL42-C", "L3"}, + {"ENV30-C", "L3"}, + {"ENV31-C", "L3"}, + {"ENV32-C", "L1"}, + {"ENV33-C", "L1"}, + {"ENV34-C", "L3"}, + {"ERR30-C", "L1"}, + {"ERR32-C", "L3"}, + {"ERR33-C", "L1"}, + {"ERR34-C", "L2"}, {"EXP30-C", "L2"}, {"EXP32-C", "L2"}, {"EXP33-C", "L1"}, @@ -1695,7 +1727,7 @@ std::vector checkers::certCInfo{ {"EXP35-C", "L2"}, {"EXP36-C", "L3"}, {"EXP37-C", "L3"}, - {"EXP39-C", "L2"}, + {"EXP39-C", "L3"}, {"EXP40-C", "L3"}, {"EXP42-C", "L1"}, {"EXP43-C", "L3"}, @@ -1703,36 +1735,6 @@ std::vector checkers::certCInfo{ {"EXP45-C", "L2"}, {"EXP46-C", "L2"}, {"EXP47-C", "L2"}, - {"INT30-C", "L2"}, - {"INT31-C", "L1"}, - {"INT32-C", "L1"}, - {"INT33-C", "L2"}, - {"INT34-C", "L3"}, - {"INT35-C", "L3"}, - {"INT36-C", "L3"}, - {"FLP30-C", "L2"}, - {"FLP32-C", "L1"}, - {"FLP34-C", "L3"}, - {"FLP36-C", "L3"}, - {"FLP37-C", "L3"}, - {"ARR30-C", "L2"}, - {"ARR32-C", "L2"}, - {"ARR36-C", "L3"}, - {"ARR37-C", "L2"}, - {"ARR38-C", "L2"}, - {"ARR39-C", "L2"}, - {"STR30-C", "L2"}, - {"STR31-C", "L2"}, - {"STR32-C", "L1"}, - {"STR34-C", "L2"}, - {"STR37-C", "L3"}, - {"STR38-C", "L1"}, - {"MEM30-C", "L2"}, - {"MEM31-C", "L3"}, - {"MEM33-C", "L3"}, - {"MEM34-C", "L2"}, - {"MEM35-C", "L2"}, - {"MEM36-C", "L3"}, {"FIO30-C", "L1"}, {"FIO32-C", "L3"}, {"FIO34-C", "L1"}, @@ -1746,32 +1748,25 @@ std::vector checkers::certCInfo{ {"FIO45-C", "L2"}, {"FIO46-C", "L3"}, {"FIO47-C", "L2"}, - {"ENV30-C", "L3"}, - {"ENV31-C", "L3"}, - {"ENV32-C", "L1"}, - {"ENV33-C", "L1"}, - {"ENV34-C", "L3"}, - {"SIG30-C", "L1"}, - {"SIG31-C", "L1"}, - {"SIG34-C", "L3"}, - {"SIG35-C", "L3"}, - {"ERR30-C", "L1"}, - {"ERR32-C", "L3"}, - {"ERR33-C", "L1"}, - {"ERR34-C", "L2"}, - {"CON30-C", "L3"}, - {"CON31-C", "L3"}, - {"CON32-C", "L3"}, - {"CON33-C", "L3"}, - {"CON34-C", "L3"}, - {"CON35-C", "L3"}, - {"CON36-C", "L3"}, - {"CON37-C", "L3"}, - {"CON38-C", "L3"}, - {"CON39-C", "L3"}, - {"CON40-C", "L2"}, - {"CON41-C", "L3"}, - {"CON43-C", "L3"}, + {"FLP30-C", "L2"}, + {"FLP32-C", "L1"}, + {"FLP34-C", "L3"}, + {"FLP36-C", "L3"}, + {"FLP37-C", "L3"}, + {"FLP38-C", "L3"}, + {"INT30-C", "L2"}, + {"INT31-C", "L1"}, + {"INT32-C", "L1"}, + {"INT33-C", "L2"}, + {"INT34-C", "L3"}, + {"INT35-C", "L3"}, + {"INT36-C", "L3"}, + {"MEM30-C", "L2"}, + {"MEM31-C", "L3"}, + {"MEM33-C", "L3"}, + {"MEM34-C", "L2"}, + {"MEM35-C", "L2"}, + {"MEM36-C", "L3"}, {"MSC30-C", "L3"}, {"MSC32-C", "L1"}, {"MSC33-C", "L2"}, @@ -1796,21 +1791,42 @@ std::vector checkers::certCInfo{ {"POS52-C", "L3"}, {"POS53-C", "L2"}, {"POS54-C", "L1"}, + {"PRE30-C", "L3"}, + {"PRE31-C", "L3"}, + {"PRE32-C", "L3"}, + {"SIG30-C", "L1"}, + {"SIG31-C", "L1"}, + {"SIG34-C", "L3"}, + {"SIG35-C", "L3"}, + {"STR30-C", "L2"}, + {"STR31-C", "L2"}, + {"STR32-C", "L1"}, + {"STR34-C", "L2"}, + {"STR37-C", "L3"}, + {"STR38-C", "L1"}, {"WIN30-C", "L3"}, // Recommendations - {"PRE00-C", "L3"}, - {"PRE01-C", "L1"}, - {"PRE02-C", "L1"}, - {"PRE04-C", "L3"}, - {"PRE05-C", "L3"}, - {"PRE06-C", "L3"}, - {"PRE07-C", "L3"}, - {"PRE08-C", "L3"}, - {"PRE09-C", "L1"}, - {"PRE10-C", "L1"}, - {"PRE11-C", "L2"}, - {"PRE12-C", "L3"}, - {"PRE13-C", "L3"}, + {"API00-C", "L3"}, + {"API01-C", "L1"}, + {"API02-C", "L1"}, + {"API03-C", "L3"}, + {"API04-C", "L3"}, + {"API05-C", "L1"}, + {"API07-C", "L3"}, + {"API09-C", "L3"}, + {"API10-C", "L3"}, + {"ARR00-C", "L2"}, + {"ARR01-C", "L1"}, + {"ARR02-C", "L2"}, + {"CON01-C", "L3"}, + {"CON02-C", "L3"}, + {"CON03-C", "L3"}, + {"CON04-C", "L3"}, + {"CON05-C", "L3"}, + {"CON06-C", "L3"}, + {"CON07-C", "L2"}, + {"CON08-C", "L3"}, + {"CON09-C", "L3"}, {"DCL00-C", "L3"}, {"DCL01-C", "L3"}, {"DCL02-C", "L3"}, @@ -1834,6 +1850,16 @@ std::vector checkers::certCInfo{ {"DCL21-C", "L3"}, {"DCL22-C", "L3"}, {"DCL23-C", "L2"}, + {"ENV01-C", "L2"}, + {"ENV02-C", "L3"}, + {"ENV03-C", "L2"}, + {"ERR00-C", "L3"}, + {"ERR01-C", "L2"}, + {"ERR02-C", "L3"}, + {"ERR04-C", "L3"}, + {"ERR05-C", "L2"}, + {"ERR06-C", "L3"}, + {"ERR07-C", "L1"}, {"EXP00-C", "L2"}, {"EXP02-C", "L3"}, {"EXP03-C", "L3"}, @@ -1850,44 +1876,6 @@ std::vector checkers::certCInfo{ {"EXP16-C", "L2"}, {"EXP19-C", "L1"}, {"EXP20-C", "L1"}, - {"INT00-C", "L3"}, - {"INT01-C", "L2"}, - {"INT02-C", "L3"}, - {"INT04-C", "L1"}, - {"INT05-C", "L2"}, - {"INT07-C", "L1"}, - {"INT08-C", "L3"}, - {"INT09-C", "L3"}, - {"INT10-C", "L3"}, - {"INT12-C", "L3"}, - {"INT13-C", "L2"}, - {"INT14-C", "L3"}, - {"INT15-C", "L2"}, - {"INT16-C", "L3"}, - {"INT17-C", "L3"}, - {"INT18-C", "L1"}, - {"FLP00-C", "L3"}, - {"FLP01-C", "L3"}, - {"FLP02-C", "L3"}, - {"FLP03-C", "L3"}, - {"FLP04-C", "L3"}, - {"FLP05-C", "L3"}, - {"FLP06-C", "L3"}, - {"FLP07-C", "L3"}, - {"ARR00-C", "L2"}, - {"ARR01-C", "L1"}, - {"ARR02-C", "L2"}, - {"STR00-C", "L3"}, - {"STR01-C", "L3"}, - {"STR02-C", "L2"}, - {"STR03-C", "L3"}, - {"STR04-C", "L3"}, - {"STR05-C", "L3"}, - {"STR06-C", "L2"}, - {"STR08-C", "L2"}, - {"STR09-C", "L3"}, - {"STR10-C", "L3"}, - {"STR11-C", "L2"}, {"FIO01-C", "L1"}, {"FIO02-C", "L3"}, {"FIO03-C", "L3"}, @@ -1908,36 +1896,41 @@ std::vector checkers::certCInfo{ {"FIO22-C", "L3"}, {"FIO23-C", "L3"}, {"FIO24-C", "L3"}, - {"ENV01-C", "L2"}, - {"ENV02-C", "L3"}, - {"ENV03-C", "L2"}, - {"SIG00-C", "L2"}, - {"SIG01-C", "L3"}, - {"SIG02-C", "L2"}, - {"ERR00-C", "L3"}, - {"ERR01-C", "L2"}, - {"ERR02-C", "L3"}, - {"ERR04-C", "L3"}, - {"ERR05-C", "L2"}, - {"ERR06-C", "L3"}, - {"ERR07-C", "L1"}, - {"API00-C", "L3"}, - {"API01-C", "L1"}, - {"API02-C", "L1"}, - {"API03-C", "L3"}, - {"API04-C", "L3"}, - {"API05-C", "L1"}, - {"API07-C", "L3"}, - {"API09-C", "L3"}, - {"API10-C", "L3"}, - {"CON01-C", "L3"}, - {"CON02-C", "L3"}, - {"CON04-C", "L3"}, - {"CON05-C", "L3"}, - {"CON06-C", "L3"}, - {"CON07-C", "L2"}, - {"CON08-C", "L3"}, - {"CON09-C", "L3"}, + {"FLP00-C", "L3"}, + {"FLP01-C", "L3"}, + {"FLP02-C", "L3"}, + {"FLP03-C", "L3"}, + {"FLP04-C", "L3"}, + {"FLP05-C", "L3"}, + {"FLP06-C", "L3"}, + {"FLP07-C", "L3"}, + {"INT00-C", "L3"}, + {"INT01-C", "L2"}, + {"INT02-C", "L3"}, + {"INT04-C", "L1"}, + {"INT05-C", "L2"}, + {"INT07-C", "L1"}, + {"INT08-C", "L3"}, + {"INT09-C", "L3"}, + {"INT10-C", "L3"}, + {"INT12-C", "L3"}, + {"INT13-C", "L2"}, + {"INT14-C", "L3"}, + {"INT15-C", "L2"}, + {"INT16-C", "L3"}, + {"INT17-C", "L3"}, + {"INT18-C", "L1"}, + {"MEM00-C", "L1"}, + {"MEM01-C", "L2"}, + {"MEM02-C", "L3"}, + {"MEM03-C", "L3"}, + {"MEM04-C", "L2"}, + {"MEM05-C", "L2"}, + {"MEM06-C", "L3"}, + {"MEM07-C", "L2"}, + {"MEM10-C", "L3"}, + {"MEM11-C", "L3"}, + {"MEM12-C", "L3"}, {"MSC00-C", "L3"}, {"MSC01-C", "L3"}, {"MSC04-C", "L3"}, @@ -1964,6 +1957,32 @@ std::vector checkers::certCInfo{ {"POS02-C", "L2"}, {"POS04-C", "L3"}, {"POS05-C", "L3"}, + {"PRE00-C", "L3"}, + {"PRE01-C", "L1"}, + {"PRE02-C", "L1"}, + {"PRE04-C", "L3"}, + {"PRE05-C", "L3"}, + {"PRE06-C", "L3"}, + {"PRE07-C", "L3"}, + {"PRE08-C", "L3"}, + {"PRE09-C", "L1"}, + {"PRE10-C", "L1"}, + {"PRE11-C", "L2"}, + {"PRE12-C", "L3"}, + {"PRE13-C", "L3"}, + {"SIG00-C", "L2"}, + {"SIG01-C", "L3"}, + {"SIG02-C", "L2"}, + {"STR00-C", "L3"}, + {"STR01-C", "L3"}, + {"STR02-C", "L2"}, + {"STR03-C", "L3"}, + {"STR04-C", "L3"}, + {"STR05-C", "L3"}, + {"STR06-C", "L2"}, + {"STR09-C", "L3"}, + {"STR10-C", "L3"}, + {"STR11-C", "L2"}, {"WIN00-C", "L2"}, {"WIN01-C", "L1"}, {"WIN02-C", "L1"}, @@ -1972,6 +1991,22 @@ std::vector checkers::certCInfo{ }; std::vector checkers::certCppInfo{ + {"CON50-CPP", "L3"}, + {"CON51-CPP", "L2"}, + {"CON52-CPP", "L3"}, + {"CON53-CPP", "L3"}, + {"CON54-CPP", "L3"}, + {"CON55-CPP", "L3"}, + {"CON56-CPP", "L3"}, + {"CTR50-CPP", "L2"}, + {"CTR51-CPP", "L2"}, + {"CTR52-CPP", "L2"}, + {"CTR53-CPP", "L2"}, + {"CTR54-CPP", "L3"}, + {"CTR55-CPP", "L2"}, + {"CTR56-CPP", "L2"}, + {"CTR57-CPP", "L3"}, + {"CTR58-CPP", "L2"}, {"DCL50-CPP", "L1"}, {"DCL51-CPP", "L3"}, {"DCL52-CPP", "L3"}, @@ -1983,6 +2018,19 @@ std::vector checkers::certCppInfo{ {"DCL58-CPP", "L2"}, {"DCL59-CPP", "L3"}, {"DCL60-CPP", "L2"}, + {"ERR50-CPP", "L3"}, + {"ERR51-CPP", "L2"}, + {"ERR52-CPP", "L3"}, + {"ERR53-CPP", "L3"}, + {"ERR54-CPP", "L1"}, + {"ERR55-CPP", "L2"}, + {"ERR56-CPP", "L2"}, + {"ERR57-CPP", "L3"}, + {"ERR58-CPP", "L2"}, + {"ERR59-CPP", "L2"}, + {"ERR60-CPP", "L3"}, + {"ERR61-CPP", "L3"}, + {"ERR62-CPP", "L3"}, {"EXP50-CPP", "L2"}, {"EXP51-CPP", "L3"}, {"EXP52-CPP", "L3"}, @@ -1997,20 +2045,9 @@ std::vector checkers::certCppInfo{ {"EXP61-CPP", "L2"}, {"EXP62-CPP", "L1"}, {"EXP63-CPP", "L2"}, + {"FIO50-CPP", "L2"}, + {"FIO51-CPP", "L3"}, {"INT50-CPP", "L3"}, - {"CTR50-CPP", "L2"}, - {"CTR51-CPP", "L2"}, - {"CTR52-CPP", "L2"}, - {"CTR53-CPP", "L2"}, - {"CTR54-CPP", "L3"}, - {"CTR55-CPP", "L2"}, - {"CTR56-CPP", "L2"}, - {"CTR57-CPP", "L3"}, - {"CTR58-CPP", "L2"}, - {"STR50-CPP", "L2"}, - {"STR51-CPP", "L1"}, - {"STR52-CPP", "L2"}, - {"STR53-CPP", "L3"}, {"MEM50-CPP", "L2"}, {"MEM51-CPP", "L2"}, {"MEM52-CPP", "L1"}, @@ -2019,21 +2056,11 @@ std::vector checkers::certCppInfo{ {"MEM55-CPP", "L2"}, {"MEM56-CPP", "L2"}, {"MEM57-CPP", "L3"}, - {"FIO50-CPP", "L2"}, - {"FIO51-CPP", "L3"}, - {"ERR50-CPP", "L3"}, - {"ERR51-CPP", "L2"}, - {"ERR52-CPP", "L3"}, - {"ERR53-CPP", "L3"}, - {"ERR54-CPP", "L1"}, - {"ERR55-CPP", "L2"}, - {"ERR56-CPP", "L2"}, - {"ERR57-CPP", "L3"}, - {"ERR58-CPP", "L2"}, - {"ERR59-CPP", "L2"}, - {"ERR60-CPP", "L3"}, - {"ERR61-CPP", "L3"}, - {"ERR62-CPP", "L3"}, + {"MSC50-CPP", "L3"}, + {"MSC51-CPP", "L1"}, + {"MSC52-CPP", "L2"}, + {"MSC53-CPP", "L3"}, + {"MSC54-CPP", "L2"}, {"OOP50-CPP", "L3"}, {"OOP51-CPP", "L3"}, {"OOP52-CPP", "L3"}, @@ -2043,17 +2070,9 @@ std::vector checkers::certCppInfo{ {"OOP56-CPP", "L3"}, {"OOP57-CPP", "L1"}, {"OOP58-CPP", "L2"}, - {"CON50-CPP", "L3"}, - {"CON51-CPP", "L2"}, - {"CON52-CPP", "L3"}, - {"CON53-CPP", "L3"}, - {"CON54-CPP", "L3"}, - {"CON55-CPP", "L3"}, - {"CON56-CPP", "L3"}, - {"MSC50-CPP", "L3"}, - {"MSC51-CPP", "L1"}, - {"MSC52-CPP", "L2"}, - {"MSC53-CPP", "L3"}, - {"MSC54-CPP", "L2"}, + {"STR50-CPP", "L2"}, + {"STR51-CPP", "L1"}, + {"STR52-CPP", "L2"}, + {"STR53-CPP", "L3"}, }; diff --git a/lib/checkersidmapping.cpp b/lib/checkersidmapping.cpp index e9636843853..7021a5c12ce 100644 --- a/lib/checkersidmapping.cpp +++ b/lib/checkersidmapping.cpp @@ -22,7 +22,7 @@ std::vector checkers::idMappingAutosar{ {"m0-1-1", "unreachableCode,duplicateBreak"}, - {"m0-1-2", "unsignedLessThanZero"}, + {"m0-1-2", "unsignedLessThanZero,compareValueOutOfTypeRangeError"}, {"m0-1-3", "unusedVariable,unusedStructMember"}, {"a0-1-1", "unreadVariable,redundantAssignment"}, {"m0-1-9", "redundantAssignment,redundantInitialization"}, @@ -95,6 +95,8 @@ std::vector checkers::idMappingCertC{ }; std::vector checkers::idMappingCertCpp{ + {"STR52", "invalidContainer"}, + {"STR51", "nullPointer"}, {"CTR51", "eraseDereference"}, {"CTR54", "comparePointers"}, {"CTR55", "containerOutOfBounds"}, @@ -104,8 +106,8 @@ std::vector checkers::idMappingCertCpp{ {"EXP52", "sizeofCalculation"}, {"EXP53", "uninitvar,uninitdata,uninitStructMember"}, {"EXP54", "uninitvar,danglingLifetime,danglingReference,danglingTemporaryLifetime,danglingTempReference,returnDanglingLifetime"}, - {"EXP61", "danglingLifetime,danglingReference,danglingTemporaryLifetime,danglingTempReference,returnDanglingLifetime,deallocuse,deallocret"}, - {"EXP63", "accessMoved"}, + {"EXP61", "danglingLifetime,danglingReference,danglingTemporaryLifetime,danglingTempReference,deallocuse,deallocret,returnDanglingLifetime"}, + {"EXP63", "accessMoved,uselessCallsRemove"}, {"FIO50", "IOWithoutPositioning"}, {"MEM50", "deallocuse"}, {"MEM51", "mismatchAllocDealloc"}, @@ -115,8 +117,6 @@ std::vector checkers::idMappingCertCpp{ {"OOP52", "virtualDestructor"}, {"OOP53", "initializerList"}, {"OOP54", "operatorEqToSelf"}, - {"STR51", "nullPointer"}, - {"STR52", "invalidContainer"}, }; std::vector checkers::idMappingMisraC{ @@ -128,22 +128,23 @@ std::vector checkers::idMappingMisraC{ {"2.8", "unusedVariable"}, {"5.3", "shadowVariable"}, {"8.3", "funcArgNamesDifferent"}, - {"8.13", "constParameterPointer"}, - {"9.1", "uninitvar"}, + {"8.13", "constParameter,constParameterPointer,constVariablePointer,constParameterCallback"}, + {"9.1", "uninitvar,legacyUninitvar"}, + {"12.2", "shiftNegative"}, {"12.5", "sizeofwithsilentarraypointer"}, {"13.2", "unknownEvaluationOrder"}, {"13.6", "sizeofCalculation"}, - {"14.3", "compareValueOutOfTypeRangeError,knownConditionTrueFalse"}, + {"14.3", "compareValueOutOfTypeRangeError,knownConditionTrueFalse,incorrectLogicOperator"}, {"17.4", "missingReturn"}, {"17.5", "argumentSize"}, - {"18.1", "pointerOutOfBounds"}, + {"18.1", "arrayIndexOutOfBounds,arrayIndexOutOfBoundsCond,pointerOutOfBounds,negativeIndex"}, {"18.2", "comparePointers"}, {"18.3", "comparePointers"}, - {"18.6", "danglingLifetime,danglingTemporaryLifetime,returnDanglingLifetime"}, + {"18.6", "autoVariables,danglingLifetime,danglingTemporaryLifetime,returnDanglingLifetime"}, {"19.1", "overlappingWriteUnion,overlappingWriteFunction"}, - {"20.6", "preprocessorErrorDirective"}, + {"20.6", "directiveAsMacroParameter"}, {"21.13", "invalidFunctionArg"}, - {"21.17", "bufferAccessOutOfBounds"}, + {"21.17", "invalidFunctionArgStr"}, {"21.18", "bufferAccessOutOfBounds"}, {"22.1", "memleak,resourceLeak,memleakOnRealloc,leakReturnValNotUsed,leakNoVarFunctionCall"}, {"22.2", "autovarInvalidDeallocation"}, @@ -154,7 +155,7 @@ std::vector checkers::idMappingMisraC{ std::vector checkers::idMappingMisraCpp2008{ {"0-1-1", "unreachableCode,duplicateBreak"}, - {"0-1-2", "unsignedLessThanZero"}, + {"0-1-2", "unsignedLessThanZero,compareValueOutOfTypeRangeError"}, {"0-1-3", "unusedVariable,unusedStructMember"}, {"0-1-6", "redundantAssignment,unreadVariable,variableScope"}, {"0-1-9", "redundantAssignment,redundantInitialization"}, diff --git a/lib/checkersreport.cpp b/lib/checkersreport.cpp index f2c4a84653f..9779bdd93c6 100644 --- a/lib/checkersreport.cpp +++ b/lib/checkersreport.cpp @@ -129,19 +129,19 @@ void CheckersReport::countCheckers() mActiveCheckersCount = mAllCheckersCount = 0; for (const auto& checkReq: checkers::allCheckers) { - if (mActiveCheckers.count(checkReq.first) > 0) + if (mActiveCheckers().count(checkReq.first) > 0) ++mActiveCheckersCount; ++mAllCheckersCount; } - for (const auto& addonInfo: mSettings.addonInfos) { + for (const auto& addonInfo: mSettings().addonInfos) { for (const auto& checkReq: addonInfo.checkers) { - if (mActiveCheckers.count(checkReq.first) > 0) + if (mActiveCheckers().count(checkReq.first) > 0) ++mActiveCheckersCount; ++mAllCheckersCount; } } - if (mSettings.addons.count("misra")) { + if (mSettings().addons.count("misra")) { const bool doUnusedFunctionOnly = Settings::unusedFunctionOnly(); for (const checkers::MisraInfo& info: checkers::misraC2012Rules) { const std::string rule = std::to_string(info.a) + "." + std::to_string(info.b); @@ -185,7 +185,7 @@ std::string CheckersReport::getReport(const std::string& criticalErrors) const } for (const auto& checkReq: checkers::allCheckers) { const std::string& checker = checkReq.first; - const bool active = mActiveCheckers.count(checkReq.first) > 0; + const bool active = mActiveCheckers().count(checkReq.first) > 0; const std::string& req = checkReq.second; fout << (active ? "Yes " : "No ") << checker; if (!active && !req.empty()) @@ -193,12 +193,12 @@ std::string CheckersReport::getReport(const std::string& criticalErrors) const fout << std::endl; } - for (const auto& addonInfo: mSettings.addonInfos) { + for (const auto& addonInfo: mSettings().addonInfos) { if (addonInfo.checkers.empty()) continue; fout << std::endl << std::endl; std::string title; - if (mSettings.premium && addonInfo.name == "premiumaddon.json") + if (mSettings().premium && addonInfo.name == "premiumaddon.json") title = "Cppcheck Premium"; else { title = addonInfo.name; @@ -217,7 +217,7 @@ std::string CheckersReport::getReport(const std::string& criticalErrors) const for (const auto& checkReq: addonInfo.checkers) { const std::string& checker = checkReq.first; - const bool active = mActiveCheckers.count(checkReq.first) > 0; + const bool active = mActiveCheckers().count(checkReq.first) > 0; const std::string& req = checkReq.second; fout << (active ? "Yes " : "No ") << checker; if (!active && !req.empty()) @@ -238,7 +238,7 @@ std::string CheckersReport::getXmlReport(const std::string& criticalErrors) cons ret += " \n"; ret += " \n"; const int misraCVersion = getMisraCVersion(mSettings); - for (std::string checker: mActiveCheckers) { + for (std::string checker: mActiveCheckers()) { if (checker.compare(0,8,"Misra C:") == 0) checker = "Misra C " + std::to_string(misraCVersion) + ":" + checker.substr(8); ret += " \n"; diff --git a/lib/checkersreport.h b/lib/checkersreport.h index b189a7f1ebd..179653d3d51 100644 --- a/lib/checkersreport.h +++ b/lib/checkersreport.h @@ -20,6 +20,7 @@ #define checkersReportH #include "config.h" +#include "refthunk.h" #include #include @@ -37,8 +38,8 @@ class CPPCHECKLIB CheckersReport { std::string getXmlReport(const std::string& criticalErrors) const; private: - const Settings& mSettings; - const std::set& mActiveCheckers; + RefThunk mSettings; + RefThunk> mActiveCheckers; void countCheckers(); diff --git a/lib/checkexceptionsafety.cpp b/lib/checkexceptionsafety.cpp index 34303cc0f4a..29342fb4dbc 100644 --- a/lib/checkexceptionsafety.cpp +++ b/lib/checkexceptionsafety.cpp @@ -34,20 +34,15 @@ //--------------------------------------------------------------------------- -// Register CheckExceptionSafety.. -namespace { - CheckExceptionSafety instance; -} - static const CWE CWE398(398U); // Indicator of Poor Code Quality static const CWE CWE703(703U); // Improper Check or Handling of Exceptional Conditions static const CWE CWE480(480U); // Use of Incorrect Operator //--------------------------------------------------------------------------- -void CheckExceptionSafety::destructors() +void CheckExceptionSafetyImpl::destructors() { - if (!mSettings->severity.isEnabled(Severity::warning)) + if (!mSettings.severity.isEnabled(Severity::warning)) return; logChecker("CheckExceptionSafety::destructors"); // warning @@ -83,7 +78,7 @@ void CheckExceptionSafety::destructors() } } -void CheckExceptionSafety::destructorsError(const Token * const tok, const std::string &className) +void CheckExceptionSafetyImpl::destructorsError(const Token * const tok, const std::string &className) { reportError(tok, Severity::warning, "exceptThrowInDestructor", "Class " + className + " is not safe, destructor throws exception\n" @@ -93,14 +88,14 @@ void CheckExceptionSafety::destructorsError(const Token * const tok, const std:: } -void CheckExceptionSafety::deallocThrow() +void CheckExceptionSafetyImpl::deallocThrow() { - if (!mSettings->severity.isEnabled(Severity::warning) && !mSettings->isPremiumEnabled("exceptDeallocThrow")) + if (!mSettings.severity.isEnabled(Severity::warning) && !mSettings.isPremiumEnabled("exceptDeallocThrow")) return; logChecker("CheckExceptionSafety::deallocThrow"); // warning - const bool printInconclusive = mSettings->certainty.isEnabled(Certainty::inconclusive); + const bool printInconclusive = mSettings.certainty.isEnabled(Certainty::inconclusive); const SymbolDatabase* const symbolDatabase = mTokenizer->getSymbolDatabase(); // Deallocate a global/member pointer and then throw exception @@ -156,7 +151,7 @@ void CheckExceptionSafety::deallocThrow() } } -void CheckExceptionSafety::deallocThrowError(const Token * const tok, const std::string &varname) +void CheckExceptionSafetyImpl::deallocThrowError(const Token * const tok, const std::string &varname) { reportError(tok, Severity::warning, "exceptDeallocThrow", "Exception thrown in invalid state, '" + varname + "' points at deallocated memory.", CWE398, Certainty::normal); @@ -168,9 +163,9 @@ void CheckExceptionSafety::deallocThrowError(const Token * const tok, const std: // throw err; // <- should be just "throw;" // } //--------------------------------------------------------------------------- -void CheckExceptionSafety::checkRethrowCopy() +void CheckExceptionSafetyImpl::checkRethrowCopy() { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("exceptRethrowCopy")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("exceptRethrowCopy")) return; logChecker("CheckExceptionSafety::checkRethrowCopy"); // style @@ -201,7 +196,7 @@ void CheckExceptionSafety::checkRethrowCopy() } } -void CheckExceptionSafety::rethrowCopyError(const Token * const tok, const std::string &varname) +void CheckExceptionSafetyImpl::rethrowCopyError(const Token * const tok, const std::string &varname) { reportError(tok, Severity::style, "exceptRethrowCopy", "Throwing a copy of the caught exception instead of rethrowing the original exception.\n" @@ -212,9 +207,9 @@ void CheckExceptionSafety::rethrowCopyError(const Token * const tok, const std:: //--------------------------------------------------------------------------- // try {} catch (std::exception err) {} <- Should be "std::exception& err" //--------------------------------------------------------------------------- -void CheckExceptionSafety::checkCatchExceptionByValue() +void CheckExceptionSafetyImpl::checkCatchExceptionByValue() { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("catchExceptionByValue")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("catchExceptionByValue")) return; logChecker("CheckExceptionSafety::checkCatchExceptionByValue"); // style @@ -233,7 +228,7 @@ void CheckExceptionSafety::checkCatchExceptionByValue() } } -void CheckExceptionSafety::catchExceptionByValueError(const Token *tok) +void CheckExceptionSafetyImpl::catchExceptionByValueError(const Token *tok) { reportError(tok, Severity::style, "catchExceptionByValue", "Exception should be caught by reference.\n" @@ -286,7 +281,7 @@ static const Token * functionThrows(const Function * function) // void func() throw() { throw x; } // void func() __attribute__((nothrow)); void func() { throw x; } //-------------------------------------------------------------------------- -void CheckExceptionSafety::nothrowThrows() +void CheckExceptionSafetyImpl::nothrowThrows() { logChecker("CheckExceptionSafety::nothrowThrows"); @@ -303,7 +298,7 @@ void CheckExceptionSafety::nothrowThrows() function->isAttributeNothrow()) { // __attribute__((nothrow)) or __declspec(nothrow) functions isNoExcept = true; } - else if (mSettings->library.isentrypoint(function->name())) { + else if (mSettings.library.isentrypoint(function->name())) { isEntryPoint = true; } if (!isNoExcept && !isEntryPoint) @@ -318,12 +313,12 @@ void CheckExceptionSafety::nothrowThrows() } } -void CheckExceptionSafety::noexceptThrowError(const Token * const tok) +void CheckExceptionSafetyImpl::noexceptThrowError(const Token * const tok) { reportError(tok, Severity::error, "throwInNoexceptFunction", "Unhandled exception thrown in function declared not to throw exceptions.", CWE398, Certainty::normal); } -void CheckExceptionSafety::entryPointThrowError(const Token * const tok) +void CheckExceptionSafetyImpl::entryPointThrowError(const Token * const tok) { reportError(tok, Severity::error, "throwInEntryPoint", "Unhandled exception thrown in function that is an entry point.", CWE398, Certainty::normal); } @@ -331,10 +326,10 @@ void CheckExceptionSafety::entryPointThrowError(const Token * const tok) //-------------------------------------------------------------------------- // void func() { functionWithExceptionSpecification(); } //-------------------------------------------------------------------------- -void CheckExceptionSafety::unhandledExceptionSpecification() +void CheckExceptionSafetyImpl::unhandledExceptionSpecification() { - if ((!mSettings->severity.isEnabled(Severity::style) || !mSettings->certainty.isEnabled(Certainty::inconclusive)) && - !mSettings->isPremiumEnabled("unhandledExceptionSpecification")) + if ((!mSettings.severity.isEnabled(Severity::style) || !mSettings.certainty.isEnabled(Certainty::inconclusive)) && + !mSettings.isPremiumEnabled("unhandledExceptionSpecification")) return; logChecker("CheckExceptionSafety::unhandledExceptionSpecification"); // style,inconclusive @@ -343,7 +338,7 @@ void CheckExceptionSafety::unhandledExceptionSpecification() for (const Scope * scope : symbolDatabase->functionScopes) { // only check functions without exception specification - if (scope->function && !scope->function->isThrow() && !mSettings->library.isentrypoint(scope->className)) { + if (scope->function && !scope->function->isThrow() && !mSettings.library.isentrypoint(scope->className)) { for (const Token *tok = scope->function->functionScope->bodyStart->next(); tok != scope->function->functionScope->bodyEnd; tok = tok->next()) { if (tok->str() == "try") @@ -361,7 +356,7 @@ void CheckExceptionSafety::unhandledExceptionSpecification() } } -void CheckExceptionSafety::unhandledExceptionSpecificationError(const Token * const tok1, const Token * const tok2, const std::string & funcname) +void CheckExceptionSafetyImpl::unhandledExceptionSpecificationError(const Token * const tok1, const Token * const tok2, const std::string & funcname) { const std::string str1(tok1 ? tok1->str() : "foo"); const std::list locationList = { tok1, tok2 }; @@ -374,7 +369,7 @@ void CheckExceptionSafety::unhandledExceptionSpecificationError(const Token * co //-------------------------------------------------------------------------- // 7.6.18.4 If no exception is presently being handled, evaluating a throw-expression with no operand calls std​::​​terminate(). //-------------------------------------------------------------------------- -void CheckExceptionSafety::rethrowNoCurrentException() +void CheckExceptionSafetyImpl::rethrowNoCurrentException() { logChecker("CheckExceptionSafety::rethrowNoCurrentException"); const SymbolDatabase* const symbolDatabase = mTokenizer->getSymbolDatabase(); @@ -405,7 +400,7 @@ void CheckExceptionSafety::rethrowNoCurrentException() } } -void CheckExceptionSafety::rethrowNoCurrentExceptionError(const Token *tok) +void CheckExceptionSafetyImpl::rethrowNoCurrentExceptionError(const Token *tok) { reportError(tok, Severity::error, "rethrowNoCurrentException", "Rethrowing current exception with 'throw;', it seems there is no current exception to rethrow." @@ -414,12 +409,12 @@ void CheckExceptionSafety::rethrowNoCurrentExceptionError(const Token *tok) CWE480, Certainty::normal); } -void CheckExceptionSafety::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) +void CheckExceptionSafety::runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) { if (tokenizer.isC()) return; - CheckExceptionSafety checkExceptionSafety(&tokenizer, &tokenizer.getSettings(), errorLogger); + CheckExceptionSafetyImpl checkExceptionSafety(&tokenizer, tokenizer.getSettings(), errorLogger); checkExceptionSafety.destructors(); checkExceptionSafety.deallocThrow(); checkExceptionSafety.checkRethrowCopy(); @@ -429,9 +424,9 @@ void CheckExceptionSafety::runChecks(const Tokenizer &tokenizer, ErrorLogger *er checkExceptionSafety.rethrowNoCurrentException(); } -void CheckExceptionSafety::getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const +void CheckExceptionSafety::getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const { - CheckExceptionSafety c(nullptr, settings, errorLogger); + CheckExceptionSafetyImpl c(nullptr, settings, errorLogger); c.destructorsError(nullptr, "Class"); c.deallocThrowError(nullptr, "p"); c.rethrowCopyError(nullptr, "varname"); diff --git a/lib/checkexceptionsafety.h b/lib/checkexceptionsafety.h index 7c5a52b5605..8b52d773a93 100644 --- a/lib/checkexceptionsafety.h +++ b/lib/checkexceptionsafety.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,6 +22,7 @@ //--------------------------------------------------------------------------- #include "check.h" +#include "checkimpl.h" #include "config.h" #include @@ -46,14 +47,32 @@ class Tokenizer; class CPPCHECKLIB CheckExceptionSafety : public Check { public: /** This constructor is used when registering the CheckClass */ - CheckExceptionSafety() : Check(myName()) {} + CheckExceptionSafety() : Check("Exception Safety") {} private: - /** This constructor is used when running checks. */ - CheckExceptionSafety(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) - : Check(myName(), tokenizer, settings, errorLogger) {} + void runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) override; + + /** Generate all possible errors (for --errorlist) */ + void getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const override; - void runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) override; + /** wiki formatted description of the class (for --doc) */ + std::string classInfo() const override { + return "Checking exception safety\n" + "- Throwing exceptions in destructors\n" + "- Throwing exception during invalid state\n" + "- Throwing a copy of a caught exception instead of rethrowing the original exception\n" + "- Exception caught by value instead of by reference\n" + "- Throwing exception in noexcept, nothrow(), __attribute__((nothrow)) or __declspec(nothrow) function\n" + "- Unhandled exception specification when calling function foo()\n" + "- Rethrow without currently handled exception\n"; + } +}; + +class CPPCHECKLIB CheckExceptionSafetyImpl : public CheckImpl { +public: + /** This constructor is used when running checks. */ + CheckExceptionSafetyImpl(const Tokenizer *tokenizer, const Settings &settings, ErrorLogger &errorLogger) + : CheckImpl(tokenizer, settings, errorLogger) {} /** Don't throw exceptions in destructors */ void destructors(); @@ -87,26 +106,6 @@ class CPPCHECKLIB CheckExceptionSafety : public Check { void unhandledExceptionSpecificationError(const Token * tok1, const Token * tok2, const std::string & funcname); /** Rethrow without currently handled exception */ void rethrowNoCurrentExceptionError(const Token *tok); - - /** Generate all possible errors (for --errorlist) */ - void getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const override; - - /** Short description of class (for --doc) */ - static std::string myName() { - return "Exception Safety"; - } - - /** wiki formatted description of the class (for --doc) */ - std::string classInfo() const override { - return "Checking exception safety\n" - "- Throwing exceptions in destructors\n" - "- Throwing exception during invalid state\n" - "- Throwing a copy of a caught exception instead of rethrowing the original exception\n" - "- Exception caught by value instead of by reference\n" - "- Throwing exception in noexcept, nothrow(), __attribute__((nothrow)) or __declspec(nothrow) function\n" - "- Unhandled exception specification when calling function foo()\n" - "- Rethrow without currently handled exception\n"; - } }; /// @} //--------------------------------------------------------------------------- diff --git a/lib/checkfunctions.cpp b/lib/checkfunctions.cpp index f6799d199e7..89094b6bf4c 100644 --- a/lib/checkfunctions.cpp +++ b/lib/checkfunctions.cpp @@ -44,12 +44,6 @@ //--------------------------------------------------------------------------- - -// Register this check class (by creating a static instance of it) -namespace { - CheckFunctions instance; -} - static const CWE CWE252(252U); // Unchecked Return Value static const CWE CWE477(477U); // Use of Obsolete Functions static const CWE CWE758(758U); // Reliance on Undefined, Unspecified, or Implementation-Defined Behavior @@ -58,9 +52,9 @@ static const CWE CWE686(686U); // Function Call With Incorrect Argument Type static const CWE CWE687(687U); // Function Call With Incorrectly Specified Argument Value static const CWE CWE688(688U); // Function Call With Incorrect Variable or Reference as Argument -void CheckFunctions::checkProhibitedFunctions() +void CheckFunctionsImpl::checkProhibitedFunctions() { - const bool checkAlloca = mSettings->severity.isEnabled(Severity::warning) && ((mTokenizer->isC() && mSettings->standards.c >= Standards::C99) || mSettings->standards.cpp >= Standards::CPP11); + const bool checkAlloca = mSettings.severity.isEnabled(Severity::warning) && ((mTokenizer->isC() && mSettings.standards.c >= Standards::C99) || mSettings.standards.cpp >= Standards::CPP11); logChecker("CheckFunctions::checkProhibitedFunctions"); @@ -72,7 +66,7 @@ void CheckFunctions::checkProhibitedFunctions() // alloca() is special as it depends on the code being C or C++, so it is not in Library if (checkAlloca && Token::simpleMatch(tok, "alloca (") && (!tok->function() || tok->function()->nestedIn->type == ScopeType::eGlobal)) { if (tok->isC()) { - if (mSettings->standards.c > Standards::C89) + if (mSettings.standards.c > Standards::C89) reportError(tok, Severity::warning, "allocaCalled", "$symbol:alloca\n" "Obsolete function 'alloca' called. In C99 and later it is recommended to use a variable length array instead.\n" @@ -90,11 +84,12 @@ void CheckFunctions::checkProhibitedFunctions() if (tok->function() && tok->function()->hasBody()) continue; - const Library::WarnInfo* wi = mSettings->library.getWarnInfo(tok); + const Library::WarnInfo* wi = mSettings.library.getWarnInfo(tok); if (wi) { - if (mSettings->severity.isEnabled(wi->severity) && ((tok->isC() && mSettings->standards.c >= wi->standards.c) || (tok->isCpp() && mSettings->standards.cpp >= wi->standards.cpp))) { - const std::string daca = mSettings->daca ? "prohibited" : ""; - reportError(tok, wi->severity, daca + tok->str() + "Called", wi->message, CWE477, Certainty::normal); + if (mSettings.severity.isEnabled(wi->severity) && ((tok->isC() && mSettings.standards.c >= wi->standards.c) || (tok->isCpp() && mSettings.standards.cpp >= wi->standards.cpp))) { + const std::string daca = mSettings.daca ? "prohibited" : ""; + const std::string prefix = daca + tok->str(); + functionCalledError(tok, wi->severity, prefix, wi->message); } } } @@ -102,10 +97,27 @@ void CheckFunctions::checkProhibitedFunctions() } } +void CheckFunctionsImpl::functionCalledError(const Token* tok, Severity severity, const std::string& prefix, const std::string& msg) +{ + reportError(tok, severity, prefix + "Called", msg, CWE477, Certainty::normal); +} + +static bool hasArgMinReadSize(const Token *ftok, int argnr, const Library &library) +{ + if (library.getArgDirection(ftok, argnr) == Library::ArgumentChecks::Direction::DIR_IN) { + if (const std::vector *minsizes = library.argminsizes(ftok, argnr)) { + return std::any_of(minsizes->begin(), minsizes->end(), [](const Library::ArgumentChecks::MinSize &ms) { + return ms.type == Library::ArgumentChecks::MinSize::Type::ARGVALUE; + }); + } + } + return false; +} + //--------------------------------------------------------------------------- // Check , and //--------------------------------------------------------------------------- -void CheckFunctions::invalidFunctionUsage() +void CheckFunctionsImpl::invalidFunctionUsage() { logChecker("CheckFunctions::invalidFunctionUsage"); const SymbolDatabase* symbolDatabase = mTokenizer->getSymbolDatabase(); @@ -116,28 +128,28 @@ void CheckFunctions::invalidFunctionUsage() continue; const Token * const functionToken = tok; const std::vector arguments = getArguments(tok); - for (int argnr = 1; argnr <= arguments.size(); ++argnr) { + for (size_t argnr = 1; argnr <= arguments.size(); ++argnr) { const Token * const argtok = arguments[argnr-1]; // check ... - const ValueFlow::Value *invalidValue = argtok->getInvalidValue(functionToken,argnr,*mSettings); + const ValueFlow::Value *invalidValue = argtok->getInvalidValue(functionToken,argnr,mSettings); if (invalidValue) { - invalidFunctionArgError(argtok, functionToken->next()->astOperand1()->expressionString(), argnr, invalidValue, mSettings->library.validarg(functionToken, argnr)); + invalidFunctionArgError(argtok, functionToken->next()->astOperand1()->expressionString(), argnr, invalidValue, mSettings.library.validarg(functionToken, argnr)); } if (astIsBool(argtok)) { // check - if (mSettings->library.isboolargbad(functionToken, argnr)) + if (mSettings.library.isboolargbad(functionToken, argnr)) invalidFunctionArgBoolError(argtok, functionToken->str(), argnr); // Are the values 0 and 1 valid? - else if (!mSettings->library.isIntArgValid(functionToken, argnr, 0, *mSettings)) - invalidFunctionArgError(argtok, functionToken->str(), argnr, nullptr, mSettings->library.validarg(functionToken, argnr)); - else if (!mSettings->library.isIntArgValid(functionToken, argnr, 1, *mSettings)) - invalidFunctionArgError(argtok, functionToken->str(), argnr, nullptr, mSettings->library.validarg(functionToken, argnr)); + else if (!mSettings.library.isIntArgValid(functionToken, argnr, 0, mSettings)) + invalidFunctionArgError(argtok, functionToken->str(), argnr, nullptr, mSettings.library.validarg(functionToken, argnr)); + else if (!mSettings.library.isIntArgValid(functionToken, argnr, 1, mSettings)) + invalidFunctionArgError(argtok, functionToken->str(), argnr, nullptr, mSettings.library.validarg(functionToken, argnr)); } // check - if (mSettings->library.isargstrz(functionToken, argnr)) { + if (mSettings.library.isargstrz(functionToken, argnr) && !hasArgMinReadSize(functionToken, argnr, mSettings.library)) { if (Token::Match(argtok, "& %var% !![") && argtok->next() && argtok->next()->valueType()) { const ValueType * valueType = argtok->next()->valueType(); const Variable * variable = argtok->next()->variable(); @@ -153,7 +165,7 @@ void CheckFunctions::invalidFunctionUsage() // Is non-null terminated local variable of type char (e.g. char buf[] = {'x'};) ? if (variable && variable->isLocal() && valueType && (valueType->type == ValueType::Type::CHAR || valueType->type == ValueType::Type::WCHAR_T) - && !isVariablesChanged(variable->declEndToken(), functionToken, 0 /*indirect*/, { variable }, *mSettings)) { + && !isVariablesChanged(variable->declEndToken(), functionToken, valueType->pointer, { variable }, mSettings)) { const Token* varTok = variable->declEndToken(); MathLib::bigint count = -1; // Find out explicitly set count, e.g.: char buf[3] = {...}. Variable 'count' is set to 3 then. if (varTok && Token::simpleMatch(varTok->astOperand1(), "[")) @@ -183,7 +195,7 @@ void CheckFunctions::invalidFunctionUsage() invalidFunctionArgStrError(argtok, functionToken->str(), argnr); } } else if (count > -1 && Token::Match(varTok, "= %str%")) { - const Token* strTok = varTok->getValueTokenMinStrSize(*mSettings); + const Token* strTok = varTok->getValueTokenMinStrSize(mSettings); if (strTok) { const int strSize = Token::getStrArraySize(strTok); if (strSize > count && strTok->str().find('\0') == std::string::npos) @@ -197,7 +209,7 @@ void CheckFunctions::invalidFunctionUsage() } } -void CheckFunctions::invalidFunctionArgError(const Token *tok, const std::string &functionName, int argnr, const ValueFlow::Value *invalidValue, const std::string &validstr) +void CheckFunctionsImpl::invalidFunctionArgError(const Token *tok, const std::string &functionName, int argnr, const ValueFlow::Value *invalidValue, const std::string &validstr) { std::ostringstream errmsg; errmsg << "$symbol:" << functionName << '\n'; @@ -210,14 +222,15 @@ void CheckFunctions::invalidFunctionArgError(const Token *tok, const std::string errmsg << " The value is " << std::setprecision(10) << (invalidValue->isIntValue() ? invalidValue->intvalue : invalidValue->floatValue) << " but the valid values are '" << validstr << "'."; else errmsg << " The value is 0 or 1 (boolean) but the valid values are '" << validstr << "'."; - if (invalidValue) + if (invalidValue) { + const auto severity = invalidValue->errorSeverity() && invalidValue->isKnown() && !invalidValue->conditional ? Severity::error : Severity::warning; reportError(getErrorPath(tok, invalidValue, "Invalid argument"), - invalidValue->errorSeverity() && invalidValue->isKnown() ? Severity::error : Severity::warning, + severity, "invalidFunctionArg", errmsg.str(), CWE628, invalidValue->isInconclusive() ? Certainty::inconclusive : Certainty::normal); - else + } else reportError(tok, Severity::error, "invalidFunctionArg", @@ -226,7 +239,7 @@ void CheckFunctions::invalidFunctionArgError(const Token *tok, const std::string Certainty::normal); } -void CheckFunctions::invalidFunctionArgBoolError(const Token *tok, const std::string &functionName, int argnr) +void CheckFunctionsImpl::invalidFunctionArgBoolError(const Token *tok, const std::string &functionName, int argnr) { std::ostringstream errmsg; errmsg << "$symbol:" << functionName << '\n'; @@ -234,7 +247,7 @@ void CheckFunctions::invalidFunctionArgBoolError(const Token *tok, const std::st reportError(tok, Severity::error, "invalidFunctionArgBool", errmsg.str(), CWE628, Certainty::normal); } -void CheckFunctions::invalidFunctionArgStrError(const Token *tok, const std::string &functionName, nonneg int argnr) +void CheckFunctionsImpl::invalidFunctionArgStrError(const Token *tok, const std::string &functionName, nonneg int argnr) { std::ostringstream errmsg; errmsg << "$symbol:" << functionName << '\n'; @@ -245,11 +258,11 @@ void CheckFunctions::invalidFunctionArgStrError(const Token *tok, const std::str //--------------------------------------------------------------------------- // Check for ignored return values. //--------------------------------------------------------------------------- -void CheckFunctions::checkIgnoredReturnValue() +void CheckFunctionsImpl::checkIgnoredReturnValue() { - if (!mSettings->severity.isEnabled(Severity::warning) && - !mSettings->severity.isEnabled(Severity::style) && - !mSettings->isPremiumEnabled("ignoredReturnValue")) + if (!mSettings.severity.isEnabled(Severity::warning) && + !mSettings.severity.isEnabled(Severity::style) && + !mSettings.isPremiumEnabled("ignoredReturnValue")) return; logChecker("CheckFunctions::checkIgnoredReturnValue"); // style,warning @@ -282,13 +295,13 @@ void CheckFunctions::checkIgnoredReturnValue() if ((!tok->function() || !Token::Match(tok->function()->retDef, "void %name%")) && tok->next()->astOperand1()) { - const Library::UseRetValType retvalTy = mSettings->library.getUseRetValType(tok); + const Library::UseRetValType retvalTy = mSettings.library.getUseRetValType(tok); const bool warn = (tok->function() && (tok->function()->isAttributeNodiscard() || tok->function()->isAttributePure() || tok->function()->isAttributeConst())) || // avoid duplicate warnings for resource-allocating functions - (retvalTy == Library::UseRetValType::DEFAULT && mSettings->library.getAllocFuncInfo(tok) == nullptr); - if (mSettings->severity.isEnabled(Severity::warning) && warn) + (retvalTy == Library::UseRetValType::DEFAULT && mSettings.library.getAllocFuncInfo(tok) == nullptr); + if (mSettings.severity.isEnabled(Severity::warning) && warn) ignoredReturnValueError(tok, tok->next()->astOperand1()->expressionString()); - else if (mSettings->severity.isEnabled(Severity::style) && + else if (mSettings.severity.isEnabled(Severity::style) && retvalTy == Library::UseRetValType::ERROR_CODE) ignoredReturnErrorCode(tok, tok->next()->astOperand1()->expressionString()); } @@ -296,13 +309,13 @@ void CheckFunctions::checkIgnoredReturnValue() } } -void CheckFunctions::ignoredReturnValueError(const Token* tok, const std::string& function) +void CheckFunctionsImpl::ignoredReturnValueError(const Token* tok, const std::string& function) { reportError(tok, Severity::warning, "ignoredReturnValue", "$symbol:" + function + "\nReturn value of function $symbol() is not used.", CWE252, Certainty::normal); } -void CheckFunctions::ignoredReturnErrorCode(const Token* tok, const std::string& function) +void CheckFunctionsImpl::ignoredReturnErrorCode(const Token* tok, const std::string& function) { reportError(tok, Severity::style, "ignoredReturnErrorCode", "$symbol:" + function + "\nError code from the return value of function $symbol() is not used.", CWE252, Certainty::normal); @@ -313,7 +326,7 @@ void CheckFunctions::ignoredReturnErrorCode(const Token* tok, const std::string& //--------------------------------------------------------------------------- static const Token *checkMissingReturnScope(const Token *tok, const Library &library); -void CheckFunctions::checkMissingReturn() +void CheckFunctionsImpl::checkMissingReturn() { logChecker("CheckFunctions::checkMissingReturn"); const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); @@ -321,7 +334,7 @@ void CheckFunctions::checkMissingReturn() const Function *function = scope->function; if (!function || !function->hasBody()) continue; - if (function->name() == "main" && !(mTokenizer->isC() && mSettings->standards.c < Standards::C99)) + if (function->name() == "main" && !(mTokenizer->isC() && mSettings.standards.c < Standards::C99)) continue; if (function->type != FunctionType::eFunction && function->type != FunctionType::eOperatorEqual) continue; @@ -329,7 +342,9 @@ void CheckFunctions::checkMissingReturn() continue; if (Function::returnsVoid(function, true)) continue; - const Token *errorToken = checkMissingReturnScope(scope->bodyEnd, mSettings->library); + if (Function::isCoroutine(function, mSettings.standards, *mTokenizer)) + continue; + const Token *errorToken = checkMissingReturnScope(scope->bodyEnd, mSettings.library); if (errorToken) missingReturnError(errorToken); } @@ -384,7 +399,12 @@ static const Token *checkMissingReturnScope(const Token *tok, const Library &lib if (!isExhaustiveSwitch(tok->link())) return tok->link(); } else if (tok->scope()->type == ScopeType::eIf) { - const Token *condition = tok->scope()->classDef->next()->astOperand2(); + const Token *paren = tok->link()->linkAt(-1); + if (!paren || !Token::simpleMatch(paren->astOperand1(), "if")) { + tok = tok->link(); + continue; + } + const Token *condition = paren->astOperand2(); if (condition && condition->hasKnownIntValue() && condition->getKnownIntValue() == 1) return checkMissingReturnScope(tok, library); return tok; @@ -431,7 +451,7 @@ static const Token *checkMissingReturnScope(const Token *tok, const Library &lib return nullptr; } -void CheckFunctions::missingReturnError(const Token* tok) +void CheckFunctionsImpl::missingReturnError(const Token* tok) { reportError(tok, Severity::error, "missingReturn", "Found an exit path from function with non-void return type that has missing return statement", CWE758, Certainty::normal); @@ -439,12 +459,12 @@ void CheckFunctions::missingReturnError(const Token* tok) //--------------------------------------------------------------------------- // Detect passing wrong values to functions like atan(0, x); //--------------------------------------------------------------------------- -void CheckFunctions::checkMathFunctions() +void CheckFunctionsImpl::checkMathFunctions() { - const bool styleC99 = mSettings->severity.isEnabled(Severity::style) && ((mTokenizer->isC() && mSettings->standards.c != Standards::C89) || (mTokenizer->isCPP() && mSettings->standards.cpp != Standards::CPP03)); - const bool printWarnings = mSettings->severity.isEnabled(Severity::warning); + const bool styleC99 = mSettings.severity.isEnabled(Severity::style) && ((mTokenizer->isC() && mSettings.standards.c != Standards::C89) || (mTokenizer->isCPP() && mSettings.standards.cpp != Standards::CPP03)); + const bool printWarnings = mSettings.severity.isEnabled(Severity::warning); - if (!styleC99 && !printWarnings && !mSettings->isPremiumEnabled("wrongmathcall")) + if (!styleC99 && !printWarnings && !mSettings.isPremiumEnabled("wrongmathcall")) return; logChecker("CheckFunctions::checkMathFunctions"); // style,warning,c99,c++11 @@ -500,7 +520,7 @@ void CheckFunctions::checkMathFunctions() } } -void CheckFunctions::mathfunctionCallWarning(const Token *tok, const nonneg int numParam) +void CheckFunctionsImpl::mathfunctionCallWarning(const Token *tok, const nonneg int numParam) { if (tok) { if (numParam == 1) @@ -511,7 +531,7 @@ void CheckFunctions::mathfunctionCallWarning(const Token *tok, const nonneg int reportError(tok, Severity::warning, "wrongmathcall", "Passing value '#' to #() leads to implementation-defined result.", CWE758, Certainty::normal); } -void CheckFunctions::mathfunctionCallWarning(const Token *tok, const std::string& oldexp, const std::string& newexp) +void CheckFunctionsImpl::mathfunctionCallWarning(const Token *tok, const std::string& oldexp, const std::string& newexp) { reportError(tok, Severity::style, "unpreciseMathCall", "Expression '" + oldexp + "' can be replaced by '" + newexp + "' to avoid loss of precision.", CWE758, Certainty::normal); } @@ -519,7 +539,7 @@ void CheckFunctions::mathfunctionCallWarning(const Token *tok, const std::string //--------------------------------------------------------------------------- // memset(p, y, 0 /* bytes to fill */) <- 2nd and 3rd arguments inverted //--------------------------------------------------------------------------- -void CheckFunctions::memsetZeroBytes() +void CheckFunctionsImpl::memsetZeroBytes() { // FIXME: // Replace this with library configuration. @@ -528,7 +548,7 @@ void CheckFunctions::memsetZeroBytes() // // - if (!mSettings->severity.isEnabled(Severity::warning)) + if (!mSettings.severity.isEnabled(Severity::warning)) return; logChecker("CheckFunctions::memsetZeroBytes"); // warning @@ -536,9 +556,9 @@ void CheckFunctions::memsetZeroBytes() const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); for (const Scope *scope : symbolDatabase->functionScopes) { for (const Token* tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) { - if (Token::Match(tok, "memset|wmemset (") && (numberOfArguments(tok)==3)) { + if (Token::Match(tok, "memset|wmemset (")) { const std::vector &arguments = getArguments(tok); - if (WRONG_DATA(arguments.size() != 3U, tok)) + if (arguments.size() != 3U) continue; const Token* lastParamTok = arguments[2]; if (MathLib::isNullValue(lastParamTok->str())) @@ -548,7 +568,7 @@ void CheckFunctions::memsetZeroBytes() } } -void CheckFunctions::memsetZeroBytesError(const Token *tok) +void CheckFunctionsImpl::memsetZeroBytesError(const Token *tok) { const std::string summary("memset() called to fill 0 bytes."); const std::string verbose(summary + " The second and third arguments might be inverted." @@ -557,7 +577,7 @@ void CheckFunctions::memsetZeroBytesError(const Token *tok) reportError(tok, Severity::warning, "memsetZeroBytes", summary + "\n" + verbose, CWE687, Certainty::normal); } -void CheckFunctions::memsetInvalid2ndParam() +void CheckFunctionsImpl::memsetInvalid2ndParam() { // FIXME: // Replace this with library configuration. @@ -567,8 +587,8 @@ void CheckFunctions::memsetInvalid2ndParam() // // - const bool printPortability = mSettings->severity.isEnabled(Severity::portability); - const bool printWarning = mSettings->severity.isEnabled(Severity::warning); + const bool printPortability = mSettings.severity.isEnabled(Severity::portability); + const bool printWarning = mSettings.severity.isEnabled(Severity::warning); if (!printWarning && !printPortability) return; @@ -596,8 +616,8 @@ void CheckFunctions::memsetInvalid2ndParam() if (printWarning && secondParamTok->isNumber()) { // Check if the second parameter is a literal and is out of range const MathLib::bigint value = MathLib::toBigNumber(secondParamTok); - const long long sCharMin = mSettings->platform.signedCharMin(); - const long long uCharMax = mSettings->platform.unsignedCharMax(); + const long long sCharMin = mSettings.platform.signedCharMin(); + const long long uCharMax = mSettings.platform.unsignedCharMax(); if (value < sCharMin || value > uCharMax) memsetValueOutOfRangeError(secondParamTok, secondParamTok->str()); } @@ -605,7 +625,7 @@ void CheckFunctions::memsetInvalid2ndParam() } } -void CheckFunctions::memsetFloatError(const Token *tok, const std::string &var_value) +void CheckFunctionsImpl::memsetFloatError(const Token *tok, const std::string &var_value) { const std::string message("The 2nd memset() argument '" + var_value + "' is a float, its representation is implementation defined."); @@ -614,7 +634,7 @@ void CheckFunctions::memsetFloatError(const Token *tok, const std::string &var_v reportError(tok, Severity::portability, "memsetFloat", message + "\n" + verbose, CWE688, Certainty::normal); } -void CheckFunctions::memsetValueOutOfRangeError(const Token *tok, const std::string &value) +void CheckFunctionsImpl::memsetValueOutOfRangeError(const Token *tok, const std::string &value) { const std::string message("The 2nd memset() argument '" + value + "' doesn't fit into an 'unsigned char'."); const std::string verbose(message + " The 2nd parameter is passed as an 'int', but the function fills the block of memory using the 'unsigned char' conversion of this value."); @@ -625,9 +645,9 @@ void CheckFunctions::memsetValueOutOfRangeError(const Token *tok, const std::str // --check-library => warn for unconfigured functions //--------------------------------------------------------------------------- -void CheckFunctions::checkLibraryMatchFunctions() +void CheckFunctionsImpl::checkLibraryMatchFunctions() { - if (!mSettings->checkLibrary) + if (!mSettings.checkLibrary) return; bool insideNew = false; @@ -665,32 +685,32 @@ void CheckFunctions::checkLibraryMatchFunctions() if (Token::simpleMatch(tok->astParent(), ".")) { const Token* contTok = tok->astParent()->astOperand1(); - if (astContainerAction(contTok, mSettings->library) != Library::Container::Action::NO_ACTION) + if (astContainerAction(contTok, mSettings.library) != Library::Container::Action::NO_ACTION) continue; - if (astContainerYield(contTok, mSettings->library) != Library::Container::Yield::NO_YIELD) + if (astContainerYield(contTok, mSettings.library) != Library::Container::Yield::NO_YIELD) continue; } - if (!mSettings->library.isNotLibraryFunction(tok)) + if (!mSettings.library.isNotLibraryFunction(tok)) continue; - const std::string &functionName = mSettings->library.getFunctionName(tok); + const std::string &functionName = mSettings.library.getFunctionName(tok); if (functionName.empty()) continue; - if (mSettings->library.functions().find(functionName) != mSettings->library.functions().end()) + if (mSettings.library.functions().find(functionName) != mSettings.library.functions().end()) continue; - if (mSettings->library.podtype(tok->expressionString())) + if (mSettings.library.podtype(tok->expressionString())) continue; - if (mSettings->library.getTypeCheck("unusedvar", functionName) != Library::TypeCheck::def) + if (mSettings.library.getTypeCheck("unusedvar", functionName) != Library::TypeCheck::def) continue; const Token* start = tok; while (Token::Match(start->tokAt(-2), "%name% ::") && !start->tokAt(-2)->isKeyword()) start = start->tokAt(-2); - if (mSettings->library.detectContainerOrIterator(start)) + if (mSettings.library.detectContainerOrIterator(start)) continue; reportError(tok, @@ -703,12 +723,12 @@ void CheckFunctions::checkLibraryMatchFunctions() // Check for problems to compiler apply (Named) Return Value Optimization for local variable // Technically we have different guarantees between standard versions // details: https://en.cppreference.com/w/cpp/language/copy_elision -void CheckFunctions::returnLocalStdMove() +void CheckFunctionsImpl::returnLocalStdMove() { - if (!mTokenizer->isCPP() || mSettings->standards.cpp < Standards::CPP11) + if (!mTokenizer->isCPP() || mSettings.standards.cpp < Standards::CPP11) return; - if (!mSettings->severity.isEnabled(Severity::performance)) + if (!mSettings.severity.isEnabled(Severity::performance)) return; logChecker("CheckFunctions::returnLocalStdMove"); // performance,c++11 @@ -733,7 +753,7 @@ void CheckFunctions::returnLocalStdMove() } } -void CheckFunctions::copyElisionError(const Token *tok) +void CheckFunctionsImpl::copyElisionError(const Token *tok) { reportError(tok, Severity::performance, @@ -742,9 +762,9 @@ void CheckFunctions::copyElisionError(const Token *tok) " More: https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rf-return-move-local"); } -void CheckFunctions::useStandardLibrary() +void CheckFunctionsImpl::useStandardLibrary() { - if (!mSettings->severity.isEnabled(Severity::style)) + if (!mSettings.severity.isEnabled(Severity::style)) return; logChecker("CheckFunctions::useStandardLibrary"); // style @@ -780,10 +800,10 @@ void CheckFunctions::useStandardLibrary() const auto& secondOp = condToken->str(); const bool isLess = "<" == secondOp && - isConstExpression(condToken->astOperand2(), mSettings->library) && + isConstExpression(condToken->astOperand2(), mSettings.library) && condToken->astOperand1()->varId() == idxVarId; const bool isMore = ">" == secondOp && - isConstExpression(condToken->astOperand1(), mSettings->library) && + isConstExpression(condToken->astOperand1(), mSettings.library) && condToken->astOperand2()->varId() == idxVarId; if (!(isLess || isMore)) @@ -846,16 +866,16 @@ void CheckFunctions::useStandardLibrary() } } -void CheckFunctions::useStandardLibraryError(const Token *tok, const std::string& expected) +void CheckFunctionsImpl::useStandardLibraryError(const Token *tok, const std::string& expected) { reportError(tok, Severity::style, "useStandardLibrary", "Consider using " + expected + " instead of loop."); } -void CheckFunctions::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) +void CheckFunctions::runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) { - CheckFunctions checkFunctions(&tokenizer, &tokenizer.getSettings(), errorLogger); + CheckFunctionsImpl checkFunctions(&tokenizer, tokenizer.getSettings(), errorLogger); checkFunctions.checkIgnoredReturnValue(); checkFunctions.checkMissingReturn(); // Missing "return" in exit path @@ -872,12 +892,12 @@ void CheckFunctions::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLog checkFunctions.useStandardLibrary(); } -void CheckFunctions::getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const +void CheckFunctions::getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const { - CheckFunctions c(nullptr, settings, errorLogger); + CheckFunctionsImpl c(nullptr, settings, errorLogger); - for (auto i = settings->library.functionwarn().cbegin(); i != settings->library.functionwarn().cend(); ++i) { - c.reportError(nullptr, Severity::style, i->first+"Called", i->second.message); + for (auto i = settings.library.functionwarn().cbegin(); i != settings.library.functionwarn().cend(); ++i) { + c.functionCalledError(nullptr, Severity::style, i->first, i->second.message); } c.invalidFunctionArgError(nullptr, "func_name", 1, nullptr,"1:4"); diff --git a/lib/checkfunctions.h b/lib/checkfunctions.h index f5954d14043..ea7278bf0c8 100644 --- a/lib/checkfunctions.h +++ b/lib/checkfunctions.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,6 +23,7 @@ //--------------------------------------------------------------------------- #include "check.h" +#include "checkimpl.h" #include "config.h" #include @@ -47,15 +48,33 @@ namespace ValueFlow { class CPPCHECKLIB CheckFunctions : public Check { public: /** This constructor is used when registering the CheckFunctions */ - CheckFunctions() : Check(myName()) {} + CheckFunctions() : Check("Check function usage") {} private: - /** This constructor is used when running checks. */ - CheckFunctions(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) - : Check(myName(), tokenizer, settings, errorLogger) {} - /** @brief Run checks against the normal token list */ - void runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) override; + void runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) override; + + void getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const override; + + std::string classInfo() const override { + return "Check function usage:\n" + "- missing 'return' in non-void function\n" + "- return value of certain functions not used\n" + "- invalid input values for functions\n" + "- Warn if a function is called whose usage is discouraged\n" + "- memset() third argument is zero\n" + "- memset() with a value out of range as the 2nd parameter\n" + "- memset() with a float as the 2nd parameter\n" + "- copy elision optimization for returning value affected by std::move\n" + "- use memcpy()/memset() instead of for loop\n"; + } +}; + +class CPPCHECKLIB CheckFunctionsImpl : public CheckImpl { +public: + /** This constructor is used when running checks. */ + CheckFunctionsImpl(const Tokenizer *tokenizer, const Settings &settings, ErrorLogger &errorLogger) + : CheckImpl(tokenizer, settings, errorLogger) {} /** Check for functions that should not be used */ void checkProhibitedFunctions(); @@ -106,25 +125,7 @@ class CPPCHECKLIB CheckFunctions : public Check { void missingReturnError(const Token *tok); void copyElisionError(const Token *tok); void useStandardLibraryError(const Token *tok, const std::string& expected); - - void getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const override; - - static std::string myName() { - return "Check function usage"; - } - - std::string classInfo() const override { - return "Check function usage:\n" - "- missing 'return' in non-void function\n" - "- return value of certain functions not used\n" - "- invalid input values for functions\n" - "- Warn if a function is called whose usage is discouraged\n" - "- memset() third argument is zero\n" - "- memset() with a value out of range as the 2nd parameter\n" - "- memset() with a float as the 2nd parameter\n" - "- copy elision optimization for returning value affected by std::move\n" - "- use memcpy()/memset() instead of for loop\n"; - } + void functionCalledError(const Token* tok, Severity severity, const std::string& prefix, const std::string& msg); }; /// @} //--------------------------------------------------------------------------- diff --git a/lib/checkimpl.cpp b/lib/checkimpl.cpp new file mode 100644 index 00000000000..99954315d26 --- /dev/null +++ b/lib/checkimpl.cpp @@ -0,0 +1,65 @@ +/* + * Cppcheck - A tool for static C/C++ code analysis + * Copyright (C) 2007-2026 Cppcheck team. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "checkimpl.h" + +#include "errorlogger.h" +#include "settings.h" +#include "token.h" +#include "tokenize.h" +#include "vfvalue.h" + +#include + +void CheckImpl::reportError(const std::list &callstack, Severity severity, const std::string &id, const std::string &msg, const CWE &cwe, Certainty certainty) +{ + // TODO: report debug warning when error is for a disabled severity + const ErrorMessage errmsg(callstack, mTokenizer ? &mTokenizer->list : nullptr, severity, id, msg, cwe, certainty); + mErrorLogger.reportErr(errmsg); +} + +void CheckImpl::reportError(ErrorPath errorPath, Severity severity, const char id[], const std::string &msg, const CWE &cwe, Certainty certainty) +{ + // TODO: report debug warning when error is for a disabled severity + const ErrorMessage errmsg(std::move(errorPath), mTokenizer ? &mTokenizer->list : nullptr, severity, id, msg, cwe, certainty); + mErrorLogger.reportErr(errmsg); +} + +ErrorPath CheckImpl::getErrorPath(const Token* errtok, const ValueFlow::Value* value, std::string bug) const +{ + ErrorPath errorPath; + if (!value) { + errorPath.emplace_back(errtok, std::move(bug)); + } else if (mSettings.verbose || mSettings.outputFormat == Settings::OutputFormat::xml || !mSettings.templateLocation.empty()) { + errorPath = value->errorPath; + errorPath.emplace_back(errtok, std::move(bug)); + } else { + if (value->condition) + errorPath.emplace_back(value->condition, "condition '" + value->condition->expressionString() + "'"); + //else if (!value->isKnown() || value->defaultArg) + // errorPath = value->callstack; + errorPath.emplace_back(errtok, std::move(bug)); + } + return errorPath; +} + +void CheckImpl::logChecker(const char id[]) +{ + if (!mSettings.buildDir.empty() || mSettings.collectLogCheckers()) + reportError(nullptr, Severity::internal, "logChecker", id); +} diff --git a/lib/checkimpl.h b/lib/checkimpl.h new file mode 100644 index 00000000000..aeff9bfca21 --- /dev/null +++ b/lib/checkimpl.h @@ -0,0 +1,79 @@ +/* + * Cppcheck - A tool for static C/C++ code analysis + * Copyright (C) 2007-2026 Cppcheck team. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef checkimplH +#define checkimplH + +#include "config.h" +#include "errortypes.h" + +#include +#include + +class Settings; +class ErrorLogger; +class Tokenizer; +class Token; +namespace ValueFlow { + class Value; +} + +class CPPCHECKLIB CheckImpl +{ +protected: + /** This constructor is used when running checks. */ + CheckImpl(const Tokenizer *tokenizer, const Settings &settings, ErrorLogger &errorLogger) + : mTokenizer(tokenizer), mSettings(settings), mErrorLogger(errorLogger) {} + +public: + CheckImpl(const CheckImpl &) = delete; + CheckImpl& operator=(const CheckImpl &) = delete; + +protected: + const Tokenizer* const mTokenizer{}; + const Settings& mSettings; + ErrorLogger& mErrorLogger; + + /** report an error */ + void reportError(const Token *tok, const Severity severity, const std::string &id, const std::string &msg) { + reportError(tok, severity, id, msg, CWE(0U), Certainty::normal); + } + + /** report an error */ + void reportError(const Token *tok, const Severity severity, const std::string &id, const std::string &msg, const CWE &cwe, Certainty certainty) { + const std::list callstack(1, tok); + reportError(callstack, severity, id, msg, cwe, certainty); + } + + /** report an error */ + void reportError(const std::list &callstack, Severity severity, const std::string &id, const std::string &msg) { + reportError(callstack, severity, id, msg, CWE(0U), Certainty::normal); + } + + /** report an error */ + void reportError(const std::list &callstack, Severity severity, const std::string &id, const std::string &msg, const CWE &cwe, Certainty certainty); + + void reportError(ErrorPath errorPath, Severity severity, const char id[], const std::string &msg, const CWE &cwe, Certainty certainty); + + ErrorPath getErrorPath(const Token* errtok, const ValueFlow::Value* value, std::string bug) const; + +public: // TODO: should be protected + void logChecker(const char id[]); +}; + +#endif // checkimplH diff --git a/lib/checkinternal.cpp b/lib/checkinternal.cpp index f289044b102..93b10210404 100644 --- a/lib/checkinternal.cpp +++ b/lib/checkinternal.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,13 +31,7 @@ #include #include -// Register this check class (by creating a static instance of it). -// Disabled in release builds -namespace { - CheckInternal instance; -} - -void CheckInternal::checkTokenMatchPatterns() +void CheckInternalImpl::checkTokenMatchPatterns() { const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); for (const Scope *scope : symbolDatabase->functionScopes) { @@ -88,7 +82,7 @@ void CheckInternal::checkTokenMatchPatterns() } } -void CheckInternal::checkRedundantTokCheck() +void CheckInternalImpl::checkRedundantTokCheck() { for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) { if (Token::Match(tok, "&& Token :: simpleMatch|Match|findsimplematch|findmatch (")) { @@ -123,13 +117,13 @@ void CheckInternal::checkRedundantTokCheck() } -void CheckInternal::checkRedundantTokCheckError(const Token* tok) +void CheckInternalImpl::checkRedundantTokCheckError(const Token* tok) { reportError(tok, Severity::style, "redundantTokCheck", "Unnecessary check of \"" + (tok? tok->expressionString(): "") + "\", match-function already checks if it is null."); } -void CheckInternal::checkTokenSimpleMatchPatterns() +void CheckInternalImpl::checkTokenSimpleMatchPatterns() { const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); for (const Scope* scope : symbolDatabase->functionScopes) { @@ -213,7 +207,7 @@ namespace { }; } -void CheckInternal::checkMissingPercentCharacter() +void CheckInternalImpl::checkMissingPercentCharacter() { const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); for (const Scope* scope : symbolDatabase->functionScopes) { @@ -254,7 +248,7 @@ void CheckInternal::checkMissingPercentCharacter() } } -void CheckInternal::checkUnknownPattern() +void CheckInternalImpl::checkUnknownPattern() { const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); for (const Scope* scope : symbolDatabase->functionScopes) { @@ -288,7 +282,7 @@ void CheckInternal::checkUnknownPattern() } } -void CheckInternal::checkRedundantNextPrevious() +void CheckInternalImpl::checkRedundantNextPrevious() { const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); for (const Scope* scope : symbolDatabase->functionScopes) { @@ -311,7 +305,7 @@ void CheckInternal::checkRedundantNextPrevious() } } -void CheckInternal::checkExtraWhitespace() +void CheckInternalImpl::checkExtraWhitespace() { const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); for (const Scope* scope : symbolDatabase->functionScopes) { @@ -337,58 +331,58 @@ void CheckInternal::checkExtraWhitespace() } } -void CheckInternal::simplePatternError(const Token* tok, const std::string& pattern, const std::string &funcname) +void CheckInternalImpl::simplePatternError(const Token* tok, const std::string& pattern, const std::string &funcname) { reportError(tok, Severity::warning, "simplePatternError", "Found simple pattern inside Token::" + funcname + "() call: \"" + pattern + "\"" ); } -void CheckInternal::complexPatternError(const Token* tok, const std::string& pattern, const std::string &funcname) +void CheckInternalImpl::complexPatternError(const Token* tok, const std::string& pattern, const std::string &funcname) { reportError(tok, Severity::error, "complexPatternError", "Found complex pattern inside Token::" + funcname + "() call: \"" + pattern + "\"" ); } -void CheckInternal::missingPercentCharacterError(const Token* tok, const std::string& pattern, const std::string& funcname) +void CheckInternalImpl::missingPercentCharacterError(const Token* tok, const std::string& pattern, const std::string& funcname) { reportError(tok, Severity::error, "missingPercentCharacter", "Missing percent end character in Token::" + funcname + "() pattern: \"" + pattern + "\"" ); } -void CheckInternal::unknownPatternError(const Token* tok, const std::string& pattern) +void CheckInternalImpl::unknownPatternError(const Token* tok, const std::string& pattern) { reportError(tok, Severity::error, "unknownPattern", "Unknown pattern used: \"" + pattern + "\""); } -void CheckInternal::redundantNextPreviousError(const Token* tok, const std::string& func1, const std::string& func2) +void CheckInternalImpl::redundantNextPreviousError(const Token* tok, const std::string& func1, const std::string& func2) { reportError(tok, Severity::style, "redundantNextPrevious", "Call to 'Token::" + func1 + "()' followed by 'Token::" + func2 + "()' can be simplified."); } -void CheckInternal::orInComplexPattern(const Token* tok, const std::string& pattern, const std::string &funcname) +void CheckInternalImpl::orInComplexPattern(const Token* tok, const std::string& pattern, const std::string &funcname) { reportError(tok, Severity::error, "orInComplexPattern", "Token::" + funcname + "() pattern \"" + pattern + "\" contains \"||\" or \"|\". Replace it by \"%oror%\" or \"%or%\"."); } -void CheckInternal::extraWhitespaceError(const Token* tok, const std::string& pattern, const std::string &funcname) +void CheckInternalImpl::extraWhitespaceError(const Token* tok, const std::string& pattern, const std::string &funcname) { reportError(tok, Severity::warning, "extraWhitespaceError", "Found extra whitespace inside Token::" + funcname + "() call: \"" + pattern + "\"" ); } -void CheckInternal::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) +void CheckInternal::runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) { if (!tokenizer.getSettings().checks.isEnabled(Checks::internalCheck)) return; - CheckInternal checkInternal(&tokenizer, &tokenizer.getSettings(), errorLogger); + CheckInternalImpl checkInternal(&tokenizer, tokenizer.getSettings(), errorLogger); checkInternal.checkTokenMatchPatterns(); checkInternal.checkTokenSimpleMatchPatterns(); @@ -399,9 +393,9 @@ void CheckInternal::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogg checkInternal.checkRedundantTokCheck(); } -void CheckInternal::getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const +void CheckInternal::getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const { - CheckInternal c(nullptr, settings, errorLogger); + CheckInternalImpl c(nullptr, settings, errorLogger); c.simplePatternError(nullptr, "class {", "Match"); c.complexPatternError(nullptr, "%type% ( )", "Match"); c.missingPercentCharacterError(nullptr, "%num", "Match"); diff --git a/lib/checkinternal.h b/lib/checkinternal.h index 1fc97bcb247..18a1535a73f 100644 --- a/lib/checkinternal.h +++ b/lib/checkinternal.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,7 +22,10 @@ #define checkinternalH //--------------------------------------------------------------------------- +#ifdef CHECK_INTERNAL + #include "check.h" +#include "checkimpl.h" #include "config.h" #include @@ -40,14 +43,25 @@ class Settings; class CPPCHECKLIB CheckInternal : public Check { public: /** This constructor is used when registering the CheckClass */ - CheckInternal() : Check(myName()) {} + CheckInternal() : Check("cppcheck internal API usage") {} private: - /** This constructor is used when running checks. */ - CheckInternal(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) - : Check(myName(), tokenizer, settings, errorLogger) {} + void runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) override; + + void getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const override; + + std::string classInfo() const override { + // Don't include these checks on the WIKI where people can read what + // checks there are. These checks are not intended for users. + return ""; + } +}; - void runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) override; +class CPPCHECKLIB CheckInternalImpl : public CheckImpl { +public: + /** This constructor is used when running checks. */ + CheckInternalImpl(const Tokenizer *tokenizer, const Settings &settings, ErrorLogger &errorLogger) + : CheckImpl(tokenizer, settings, errorLogger) {} /** @brief %Check if a simple pattern is used inside Token::Match or Token::findmatch */ void checkTokenMatchPatterns(); @@ -78,19 +92,10 @@ class CPPCHECKLIB CheckInternal : public Check { void orInComplexPattern(const Token *tok, const std::string &pattern, const std::string &funcname); void extraWhitespaceError(const Token *tok, const std::string &pattern, const std::string &funcname); void checkRedundantTokCheckError(const Token *tok); - - void getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const override; - - static std::string myName() { - return "cppcheck internal API usage"; - } - - std::string classInfo() const override { - // Don't include these checks on the WIKI where people can read what - // checks there are. These checks are not intended for users. - return ""; - } }; /// @} //--------------------------------------------------------------------------- + +#endif // CHECK_INTERNAL + #endif // checkinternalH diff --git a/lib/checkio.cpp b/lib/checkio.cpp index 0e1d0395146..4fff7d46282 100644 --- a/lib/checkio.cpp +++ b/lib/checkio.cpp @@ -45,14 +45,10 @@ //--------------------------------------------------------------------------- -// Register CheckIO.. -namespace { - CheckIO instance; -} - // CVE ID used: static const CWE CWE119(119U); // Improper Restriction of Operations within the Bounds of a Memory Buffer static const CWE CWE398(398U); // Indicator of Poor Code Quality +static const CWE CWE474(474U); // Use of Function with Inconsistent Implementations static const CWE CWE664(664U); // Improper Control of a Resource Through its Lifetime static const CWE CWE685(685U); // Function Call With Incorrect Number of Arguments static const CWE CWE686(686U); // Function Call With Incorrect Argument Type @@ -63,7 +59,7 @@ static const CWE CWE910(910U); // Use of Expired File Descriptor //--------------------------------------------------------------------------- // std::cout << std::cout; //--------------------------------------------------------------------------- -void CheckIO::checkCoutCerrMisusage() +void CheckIOImpl::checkCoutCerrMisusage() { if (mTokenizer->isC()) return; @@ -85,7 +81,7 @@ void CheckIO::checkCoutCerrMisusage() } } -void CheckIO::coutCerrMisusageError(const Token* tok, const std::string& streamName) +void CheckIOImpl::coutCerrMisusageError(const Token* tok, const std::string& streamName) { reportError(tok, Severity::error, "coutCerrMisusage", "Invalid usage of output stream: '<< std::" + streamName + "'.", CWE398, Certainty::normal); } @@ -116,6 +112,8 @@ namespace { nonneg int op_indent{}; enum class AppendMode : std::uint8_t { UNKNOWN_AM, APPEND, APPEND_EX }; AppendMode append_mode = AppendMode::UNKNOWN_AM; + enum class ReadMode : std::uint8_t { READ_TEXT, READ_BIN }; + ReadMode read_mode = ReadMode::READ_BIN; std::string filename; explicit Filepointer(OpenMode mode_ = OpenMode::UNKNOWN_OM) : mode(mode_) {} @@ -124,11 +122,11 @@ namespace { const std::unordered_set whitelist = { "clearerr", "feof", "ferror", "fgetpos", "ftell", "setbuf", "setvbuf", "ungetc", "ungetwc" }; } -void CheckIO::checkFileUsage() +void CheckIOImpl::checkFileUsage() { - const bool windows = mSettings->platform.isWindows(); - const bool printPortability = mSettings->severity.isEnabled(Severity::portability); - const bool printWarnings = mSettings->severity.isEnabled(Severity::warning); + const bool windows = mSettings.platform.isWindows(); + const bool printPortability = mSettings.severity.isEnabled(Severity::portability); + const bool printWarnings = mSettings.severity.isEnabled(Severity::warning); std::map filepointers; @@ -157,6 +155,8 @@ void CheckIO::checkFileUsage() tok = tok->linkAt(1); continue; } + if (tok->function() && tok->function()->nestedIn) + continue; if (tok->str() == "{") indent++; else if (tok->str() == "}") { @@ -171,7 +171,7 @@ void CheckIO::checkFileUsage() filepointer.second.lastOperation = Filepointer::Operation::UNKNOWN_OP; } } - } else if (tok->str() == "return" || tok->str() == "continue" || tok->str() == "break" || mSettings->library.isnoreturn(tok)) { // Reset upon return, continue or break + } else if (tok->str() == "return" || tok->str() == "continue" || tok->str() == "break" || mSettings.library.isnoreturn(tok)) { // Reset upon return, continue or break for (std::pair& filepointer : filepointers) { filepointer.second.mode_indent = 0; filepointer.second.mode = OpenMode::UNKNOWN_OM; @@ -188,6 +188,7 @@ void CheckIO::checkFileUsage() } } else if (Token::Match(tok, "%name% (") && tok->previous() && (!tok->previous()->isName() || Token::Match(tok->previous(), "return|throw"))) { std::string mode; + bool isftell = false; const Token* fileTok = nullptr; const Token* fileNameTok = nullptr; Filepointer::Operation operation = Filepointer::Operation::NONE; @@ -245,12 +246,37 @@ void CheckIO::checkFileUsage() } else if (tok->str() == "fclose") { fileTok = tok->tokAt(2); operation = Filepointer::Operation::CLOSE; + + // #1473 Check if fclose is in a while loop condition + if (fileTok && fileTok->isVariable()) { + const Token* loopTok = tok->astTop()->previous(); + + if (loopTok && loopTok->str() == "while") { + const Token* bodyEnd = nullptr; + const Token* bodyStart = nullptr; + + if (Token::simpleMatch(loopTok->previous(), "}") && loopTok->previous()->scope()->type == ScopeType::eDo) { // Handle do-while loops + bodyEnd = loopTok->previous(); + bodyStart = bodyEnd->link(); + } else { + bodyStart = loopTok->linkAt(1)->next(); + bodyEnd = bodyStart->link(); + } + + // Do not trigger a warning if the loop always exits or if the file is opened again in the loop. + if (!isReturnScope(bodyEnd, mSettings.library) && Token::findmatch(bodyStart, "%var% =", bodyEnd, fileTok->varId()) == nullptr) + fcloseInLoopConditionError(tok, fileTok->str()); + } + } } else if (whitelist.find(tok->str()) != whitelist.end()) { fileTok = tok->tokAt(2); if ((tok->str() == "ungetc" || tok->str() == "ungetwc") && fileTok) fileTok = fileTok->nextArgument(); + else if (tok->str() == "ftell") { + isftell = true; + } operation = Filepointer::Operation::UNIMPORTANT; - } else if (!Token::Match(tok, "if|for|while|catch|switch") && !mSettings->library.isFunctionConst(tok->str(), true)) { + } else if (!Token::Match(tok, "if|for|while|catch|switch") && !mSettings.library.isFunctionConst(tok->str(), true)) { const Token* const end2 = tok->linkAt(1); if (scope->functionOf && scope->functionOf->isClassOrStruct() && !scope->function->isStatic() && ((tok->strAt(-1) != "::" && tok->strAt(-1) != ".") || tok->strAt(-2) == "this")) { if (!tok->function() || (tok->function()->nestedIn && tok->function()->nestedIn->isClassOrStruct())) { @@ -304,10 +330,15 @@ void CheckIO::checkFileUsage() f.append_mode = Filepointer::AppendMode::APPEND_EX; else f.append_mode = Filepointer::AppendMode::APPEND; + } + else if (mode.find('r') != std::string::npos && + mode.find('t') != std::string::npos) { + f.read_mode = Filepointer::ReadMode::READ_TEXT; } else f.append_mode = Filepointer::AppendMode::UNKNOWN_AM; f.mode_indent = indent; break; + case Filepointer::Operation::POSITIONING: if (f.mode == OpenMode::CLOSED) useClosedFileError(tok); @@ -340,6 +371,8 @@ void CheckIO::checkFileUsage() case Filepointer::Operation::UNIMPORTANT: if (f.mode == OpenMode::CLOSED) useClosedFileError(tok); + if (isftell && f.read_mode == Filepointer::ReadMode::READ_TEXT && printPortability) + ftellFileError(tok); break; case Filepointer::Operation::UNKNOWN_OP: f.mode = OpenMode::UNKNOWN_OM; @@ -362,43 +395,58 @@ void CheckIO::checkFileUsage() } } -void CheckIO::fflushOnInputStreamError(const Token *tok, const std::string &varname) +void CheckIOImpl::fflushOnInputStreamError(const Token *tok, const std::string &varname) { reportError(tok, Severity::portability, "fflushOnInputStream", "fflush() called on input stream '" + varname + "' may result in undefined behaviour on non-linux systems.", CWE398, Certainty::normal); } -void CheckIO::ioWithoutPositioningError(const Token *tok) +void CheckIOImpl::ioWithoutPositioningError(const Token *tok) { reportError(tok, Severity::error, "IOWithoutPositioning", "Read and write operations without a call to a positioning function (fseek, fsetpos or rewind) or fflush in between result in undefined behaviour.", CWE664, Certainty::normal); } -void CheckIO::readWriteOnlyFileError(const Token *tok) +void CheckIOImpl::readWriteOnlyFileError(const Token *tok) { reportError(tok, Severity::error, "readWriteOnlyFile", "Read operation on a file that was opened only for writing.", CWE664, Certainty::normal); } -void CheckIO::writeReadOnlyFileError(const Token *tok) +void CheckIOImpl::writeReadOnlyFileError(const Token *tok) { reportError(tok, Severity::error, "writeReadOnlyFile", "Write operation on a file that was opened only for reading.", CWE664, Certainty::normal); } -void CheckIO::useClosedFileError(const Token *tok) +void CheckIOImpl::useClosedFileError(const Token *tok) { reportError(tok, Severity::error, "useClosedFile", "Used file that is not opened.", CWE910, Certainty::normal); } -void CheckIO::seekOnAppendedFileError(const Token *tok) +void CheckIOImpl::fcloseInLoopConditionError(const Token *tok, const std::string &varname) +{ + reportError(tok, Severity::warning, + "fcloseInLoopCondition", + "fclose() used as loop condition may skip loop body or double-close file handle.\n" + "fclose() closes '" + varname + "' each time it is evaluated. On success the loop body might never execute, on failure fclose() might be called again on the already-closed file handle.", + CWE910, Certainty::normal); +} + +void CheckIOImpl::seekOnAppendedFileError(const Token *tok) { reportError(tok, Severity::warning, "seekOnAppendedFile", "Repositioning operation performed on a file opened in append mode has no effect.", CWE398, Certainty::normal); } -void CheckIO::incompatibleFileOpenError(const Token *tok, const std::string &filename) +void CheckIOImpl::ftellFileError(const Token *tok) +{ + reportError(tok, Severity::portability, + "ftellTextModeFile", "ftell() result is unspecified when file is opened in mode \"t\"", CWE474, Certainty::normal); +} + +void CheckIOImpl::incompatibleFileOpenError(const Token *tok, const std::string &filename) { reportError(tok, Severity::warning, "incompatibleFileOpen", "The file '" + filename + "' is opened for read and write access at the same time on different streams", CWE664, Certainty::normal); @@ -408,9 +456,9 @@ void CheckIO::incompatibleFileOpenError(const Token *tok, const std::string &fil //--------------------------------------------------------------------------- // scanf without field width limits can crash with huge input data //--------------------------------------------------------------------------- -void CheckIO::invalidScanf() +void CheckIOImpl::invalidScanf() { - if (!mSettings->severity.isEnabled(Severity::warning) && !mSettings->isPremiumEnabled("invalidscanf")) + if (!mSettings.severity.isEnabled(Severity::warning) && !mSettings.isPremiumEnabled("invalidscanf")) return; logChecker("CheckIO::invalidScanf"); @@ -455,7 +503,7 @@ void CheckIO::invalidScanf() } } -void CheckIO::invalidScanfError(const Token *tok) +void CheckIOImpl::invalidScanfError(const Token *tok) { const std::string fname = (tok ? tok->str() : std::string("scanf")); reportError(tok, Severity::warning, @@ -481,22 +529,157 @@ void CheckIO::invalidScanfError(const Token *tok) CWE119, Certainty::normal); } +static const Token* findFileReadCall(const Token *start, const Token *end, int varid) +{ + const Token* found = Token::findmatch(start, "fgets|fgetc|getc|fread|fscanf (", end); + while (found) { + const std::vector args = getArguments(found); + if (!args.empty()) { + const bool match = (found->str() == "fscanf") + ? args.front()->varId() == varid + : args.back()->varId() == varid; + if (match) + return found; + } + found = Token::findmatch(found->next(), "fgets|fgetc|getc|fread|fscanf (", end); + } + return nullptr; +} + +void CheckIOImpl::checkWrongfeofUsage() +{ + const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); + + logChecker("CheckIO::checkWrongfeofUsage"); + + for (const Scope * scope : symbolDatabase->functionScopes) { + for (const Token *tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) { + // TODO: Handle for loops + if (!Token::Match(tok, "while ( ! feof ( %var% )")) + continue; + + // Bail out if we cannot identify file pointer + const int fpVarId = tok->tokAt(5)->varId(); + if (fpVarId == 0) + continue; + + const Token *endCond = tok->linkAt(1); + const Token *bodyStart; + const Token *bodyEnd; + + if (Token::simpleMatch(tok->previous(), "}") && tok->previous()->scope()->type == ScopeType::eDo) { + bodyEnd = tok->previous(); + bodyStart = bodyEnd->link(); + } else { + if (!Token::simpleMatch(endCond, ") {")) + continue; + bodyEnd = endCond->linkAt(1); + bodyStart = endCond->next(); + } + + // Bail out if the loop contains control flow (too complex to analyze) + if (Token::findmatch(bodyStart, "return|break|goto|continue|throw", bodyEnd)) + continue; + + // Bail out if fp is used outside of known file I/O functions. + // If it is passed to an unknown function, reads may occur there. + bool fpUsedElsewhere = false; + for (const Token *t = bodyStart->next(); t && t != bodyEnd; t = t->next()) { + if (t->varId() != fpVarId) + continue; + const Token *p = t->astParent(); + while (p && p->str() == ",") + p = p->astParent(); + if (!p || !Token::Match(p->astOperand1(), "fgets|fgetc|getc|fread|fscanf|fprintf|fwrite|fputs|fputc|putc")) { + fpUsedElsewhere = true; + break; + } + } + if (fpUsedElsewhere) + continue; + + // No file read call in the loop: feof can never become true inside it + const Token *loopFileReadCallTok = findFileReadCall(bodyStart, bodyEnd, fpVarId); + if (!loopFileReadCallTok) { + // TODO: Warn about infinite loop + continue; + } + + // Find last file read + const Token *lastLoopFileReadCallTok = loopFileReadCallTok; + while (loopFileReadCallTok) { + lastLoopFileReadCallTok = loopFileReadCallTok; + loopFileReadCallTok = findFileReadCall(lastLoopFileReadCallTok->next(), bodyEnd, fpVarId); + } + + // Warn if the destination of the last file read is used after the call before bodyEnd. + // If it is not, the stale buffer is never accessed on the extra iteration at EOF. + + if (lastLoopFileReadCallTok->str() == "fgetc" || lastLoopFileReadCallTok->str() == "getc") { + // Warn if the return value feeds into an expression (astParent of the call node) + if (lastLoopFileReadCallTok->astParent() && lastLoopFileReadCallTok->astParent()->astParent()) + wrongfeofUsage(getCondTok(tok)); + } else { + const std::vector args = getArguments(lastLoopFileReadCallTok); + // Collect destination varIds + std::vector destVarIds; + if (lastLoopFileReadCallTok->str() == "fscanf") { + // args[0]=fp, args[1]=format, args[2+]=destinations (typically &var) + for (std::size_t i = 2; i < args.size(); ++i) { + const Token *destTok = Token::Match(args[i], "& %var%") ? args[i]->next() : args[i]; + if (destTok->varId() != 0) + destVarIds.push_back(destTok->varId()); + } + } else { + // Handle fgets, fread + // First argument is the destination buffer + if (!args.empty() && args.front()->varId() != 0) + destVarIds.push_back(args.front()->varId()); + } + + // Search for any destination use between this call's ';' and endBody + const Token *semiColonTok = lastLoopFileReadCallTok->linkAt(1)->next(); + for (const Token *t = semiColonTok; t && t != bodyEnd; t = t->next()) { + if (std::find(destVarIds.begin(), destVarIds.end(), t->varId()) != destVarIds.end()) { + wrongfeofUsage(getCondTok(tok)); + break; + } + } + } + } + } +} + +void CheckIOImpl::wrongfeofUsage(const Token * tok) +{ + reportError(tok, Severity::warning, + "wrongfeofUsage", + "Using feof() as a loop condition causes the last line to be processed twice.\n" + "feof() returns true only after a read has failed due to end-of-file, so the loop " + "body executes once more after the last successful read. Check the return value of " + "the read function instead (e.g. fgets, fread, fscanf)."); +} + //--------------------------------------------------------------------------- // printf("%u", "xyz"); // Wrong argument type // printf("%u%s", 1); // Too few arguments // printf("", 1); // Too much arguments //--------------------------------------------------------------------------- -static bool findFormat(nonneg int arg, const Token *firstArg, +static bool findFormat(nonneg int arg, nonneg int argc, const Token *firstArg, const Token *&formatStringTok, const Token *&formatArgTok) { + formatArgTok = firstArg; + + for (int i = 0; i < argc && formatArgTok; ++i) + formatArgTok = formatArgTok->nextArgument(); + const Token* argTok = firstArg; for (int i = 0; i < arg && argTok; ++i) argTok = argTok->nextArgument(); if (Token::Match(argTok, "%str% [,)]")) { - formatArgTok = argTok->nextArgument(); formatStringTok = argTok; return true; } @@ -507,7 +690,6 @@ static bool findFormat(nonneg int arg, const Token *firstArg, (argTok->variable()->dimensions().size() == 1 && argTok->variable()->dimensionKnown(0) && argTok->variable()->dimension(0) != 0))) { - formatArgTok = argTok->nextArgument(); if (!argTok->values().empty()) { const auto value = std::find_if( argTok->values().cbegin(), argTok->values().cend(), std::mem_fn(&ValueFlow::Value::isTokValue)); @@ -527,10 +709,21 @@ static inline bool typesMatch(const std::string& iToTest, const std::string& iTy return (iToTest == iTypename) || (iToTest == iOptionalPrefix + iTypename); } -void CheckIO::checkWrongPrintfScanfArguments() +static int getMaxArgNo(const Library::Function *func) +{ + const auto &checks = func->argumentChecks; + return std::max_element(checks.cbegin(), checks.cend(), + [](const std::pair &lhs, + const std::pair &rhs) { + return lhs.first < rhs.first; + } + )->first; +} + +void CheckIOImpl::checkWrongPrintfScanfArguments() { const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); - const bool isWindows = mSettings->platform.isWindows(); + const bool isWindows = mSettings.platform.isWindows(); logChecker("CheckIO::checkWrongPrintfScanfArguments"); @@ -544,46 +737,48 @@ void CheckIO::checkWrongPrintfScanfArguments() bool scan = false; bool scanf_s = false; int formatStringArgNo = -1; + int argc = -1; - if (tok->strAt(1) == "(" && mSettings->library.formatstr_function(tok)) { - formatStringArgNo = mSettings->library.formatstr_argno(tok); - scan = mSettings->library.formatstr_scan(tok); - scanf_s = mSettings->library.formatstr_secure(tok); + if (tok->strAt(1) == "(" && mSettings.library.formatstr_function(tok)) { + argc = getMaxArgNo(mSettings.library.getFunction(tok)); + formatStringArgNo = mSettings.library.formatstr_argno(tok); + scan = mSettings.library.formatstr_scan(tok); + scanf_s = mSettings.library.formatstr_secure(tok); } if (formatStringArgNo >= 0) { // formatstring found in library. Find format string and first argument belonging to format string. - if (!findFormat(formatStringArgNo, tok->tokAt(2), formatStringTok, argListTok)) + if (!findFormat(formatStringArgNo, argc, tok->tokAt(2), formatStringTok, argListTok)) continue; } else if (Token::simpleMatch(tok, "swprintf (")) { if (Token::Match(tok->tokAt(2)->nextArgument(), "%str%")) { // Find third parameter and format string - if (!findFormat(1, tok->tokAt(2), formatStringTok, argListTok)) + if (!findFormat(1, 2, tok->tokAt(2), formatStringTok, argListTok)) continue; } else { // Find fourth parameter and format string - if (!findFormat(2, tok->tokAt(2), formatStringTok, argListTok)) + if (!findFormat(2, 3, tok->tokAt(2), formatStringTok, argListTok)) continue; } } else if (isWindows && Token::Match(tok, "sprintf_s|swprintf_s (")) { // template int sprintf_s(char (&buffer)[size], const char *format, ...); - if (findFormat(1, tok->tokAt(2), formatStringTok, argListTok)) { + if (findFormat(1, 2, tok->tokAt(2), formatStringTok, argListTok)) { if (!formatStringTok) continue; } // int sprintf_s(char *buffer, size_t sizeOfBuffer, const char *format, ...); - else if (findFormat(2, tok->tokAt(2), formatStringTok, argListTok)) { + else if (findFormat(2, 3, tok->tokAt(2), formatStringTok, argListTok)) { if (!formatStringTok) continue; } } else if (isWindows && Token::Match(tok, "_snprintf_s|_snwprintf_s (")) { // template int _snprintf_s(char (&buffer)[size], size_t count, const char *format, ...); - if (findFormat(2, tok->tokAt(2), formatStringTok, argListTok)) { + if (findFormat(2, 3, tok->tokAt(2), formatStringTok, argListTok)) { if (!formatStringTok) continue; } // int _snprintf_s(char *buffer, size_t sizeOfBuffer, size_t count, const char *format, ...); - else if (findFormat(3, tok->tokAt(2), formatStringTok, argListTok)) { + else if (findFormat(3, 4, tok->tokAt(2), formatStringTok, argListTok)) { if (!formatStringTok) continue; } @@ -599,14 +794,14 @@ void CheckIO::checkWrongPrintfScanfArguments() } } -void CheckIO::checkFormatString(const Token * const tok, - const Token * const formatStringTok, - const Token * argListTok, - const bool scan, - const bool scanf_s) +void CheckIOImpl::checkFormatString(const Token * const tok, + const Token * const formatStringTok, + const Token * argListTok, + const bool scan, + const bool scanf_s) { - const bool isWindows = mSettings->platform.isWindows(); - const bool printWarning = mSettings->severity.isEnabled(Severity::warning); + const bool isWindows = mSettings.platform.isWindows(); + const bool printWarning = mSettings.severity.isEnabled(Severity::warning); const std::string &formatString = formatStringTok->str(); // Count format string parameters.. @@ -664,6 +859,8 @@ void CheckIO::checkFormatString(const Token * const tok, } ++i; } + while (!width.empty() && width[0] == '0') + width = width.substr(1); auto bracketBeg = formatString.cend(); if (i != formatString.cend() && *i == '[') { bracketBeg = i; @@ -696,9 +893,9 @@ void CheckIO::checkFormatString(const Token * const tok, } // Perform type checks - ArgumentInfo argInfo(argListTok, *mSettings, mTokenizer->isCPP()); + ArgumentInfo argInfo(argListTok, mSettings, mTokenizer->isCPP()); - if ((argInfo.typeToken && !argInfo.isLibraryType(*mSettings)) || *i == ']') { + if ((argInfo.typeToken && !argInfo.isLibraryType(mSettings.library)) || *i == ']') { if (scan) { std::string specifier; bool done = false; @@ -718,7 +915,7 @@ void CheckIO::checkFormatString(const Token * const tok, argInfo.isKnownType() && argInfo.isArrayOrPointer() && (!Token::Match(argInfo.typeToken, "char|wchar_t") || argInfo.typeToken->strAt(-1) == "const")) { - if (!(argInfo.isArrayOrPointer() && argInfo.element && !argInfo.typeToken->isStandardType())) + if (!argInfo.element || argInfo.typeToken->isStandardType()) invalidScanfArgTypeError_s(tok, numFormat, specifier, &argInfo); } if (scanf_s) { @@ -1342,7 +1539,7 @@ void CheckIO::checkFormatString(const Token * const tok, // We currently only support string literals, variables, and functions. /// @todo add non-string literals, and generic expressions -CheckIO::ArgumentInfo::ArgumentInfo(const Token * arg, const Settings &settings, bool _isCPP) +CheckIOImpl::ArgumentInfo::ArgumentInfo(const Token * arg, const Settings &settings, bool _isCPP) : isCPP(_isCPP) { if (!arg) @@ -1539,9 +1736,10 @@ CheckIO::ArgumentInfo::ArgumentInfo(const Token * arg, const Settings &settings, if (element && isStdVectorOrString()) { // isStdVectorOrString sets type token if true element = false; // not really an array element } else if (variableInfo->isEnumType()) { - if (variableInfo->type() && variableInfo->type()->classScope && variableInfo->type()->classScope->enumType) + const bool hasEnumType = variableInfo->type() && variableInfo->type()->classScope && variableInfo->type()->classScope->enumType; + if (hasEnumType && variableInfo->type()->classScope->enumType->isStandardType()) typeToken = variableInfo->type()->classScope->enumType; - else { + else if (!hasEnumType) { tempToken = new Token(tok1); tempToken->str("int"); typeToken = tempToken; @@ -1555,7 +1753,7 @@ CheckIO::ArgumentInfo::ArgumentInfo(const Token * arg, const Settings &settings, } } -CheckIO::ArgumentInfo::~ArgumentInfo() +CheckIOImpl::ArgumentInfo::~ArgumentInfo() { if (tempToken) { while (tempToken->next()) @@ -1570,7 +1768,7 @@ namespace { const std::set stl_string = { "string", "u16string", "u32string", "wstring" }; } -bool CheckIO::ArgumentInfo::isStdVectorOrString() +bool CheckIOImpl::ArgumentInfo::isStdVectorOrString() { if (!isCPP) return false; @@ -1632,7 +1830,7 @@ static const std::set stl_container = { "unordered_map", "unordered_multimap", "unordered_multiset", "unordered_set", "vector" }; -bool CheckIO::ArgumentInfo::isStdContainer(const Token *tok) +bool CheckIOImpl::ArgumentInfo::isStdContainer(const Token *tok) { if (!isCPP) return false; @@ -1664,7 +1862,7 @@ bool CheckIO::ArgumentInfo::isStdContainer(const Token *tok) return false; } -bool CheckIO::ArgumentInfo::isArrayOrPointer() const +bool CheckIOImpl::ArgumentInfo::isArrayOrPointer() const { if (address) return true; @@ -1677,7 +1875,7 @@ bool CheckIO::ArgumentInfo::isArrayOrPointer() const return tok && tok->strAt(1) == "*"; } -bool CheckIO::ArgumentInfo::isComplexType() const +bool CheckIOImpl::ArgumentInfo::isComplexType() const { if (variableInfo->type()) return true; @@ -1689,7 +1887,7 @@ bool CheckIO::ArgumentInfo::isComplexType() const return ((variableInfo->isStlStringType() || (varTypeTok->strAt(1) == "<" && varTypeTok->linkAt(1) && varTypeTok->linkAt(1)->strAt(1) != "::")) && !variableInfo->isArrayOrPointer()); } -bool CheckIO::ArgumentInfo::isKnownType() const +bool CheckIOImpl::ArgumentInfo::isKnownType() const { if (variableInfo) return (typeToken->isStandardType() || typeToken->next()->isStandardType() || isComplexType()); @@ -1699,18 +1897,18 @@ bool CheckIO::ArgumentInfo::isKnownType() const return typeToken->isStandardType() || Token::Match(typeToken, "std :: string|wstring"); } -bool CheckIO::ArgumentInfo::isLibraryType(const Settings &settings) const +bool CheckIOImpl::ArgumentInfo::isLibraryType(const Library &library) const { - return typeToken && typeToken->isStandardType() && settings.library.podtype(typeToken->str()); + return typeToken && typeToken->isStandardType() && library.podtype(typeToken->str()); } -void CheckIO::wrongPrintfScanfArgumentsError(const Token* tok, - const std::string &functionName, - nonneg int numFormat, - nonneg int numFunction) +void CheckIOImpl::wrongPrintfScanfArgumentsError(const Token* tok, + const std::string &functionName, + nonneg int numFormat, + nonneg int numFunction) { const Severity severity = numFormat > numFunction ? Severity::error : Severity::warning; - if (severity != Severity::error && !mSettings->severity.isEnabled(Severity::warning) && !mSettings->isPremiumEnabled("wrongPrintfScanfArgNum")) + if (severity != Severity::error && !mSettings.severity.isEnabled(Severity::warning) && !mSettings.isPremiumEnabled("wrongPrintfScanfArgNum")) return; std::ostringstream errmsg; @@ -1726,10 +1924,10 @@ void CheckIO::wrongPrintfScanfArgumentsError(const Token* tok, reportError(tok, severity, "wrongPrintfScanfArgNum", errmsg.str(), CWE685, Certainty::normal); } -void CheckIO::wrongPrintfScanfPosixParameterPositionError(const Token* tok, const std::string& functionName, - nonneg int index, nonneg int numFunction) +void CheckIOImpl::wrongPrintfScanfPosixParameterPositionError(const Token* tok, const std::string& functionName, + nonneg int index, nonneg int numFunction) { - if (!mSettings->severity.isEnabled(Severity::warning) && !mSettings->isPremiumEnabled("wrongPrintfScanfParameterPositionError")) + if (!mSettings.severity.isEnabled(Severity::warning) && !mSettings.isPremiumEnabled("wrongPrintfScanfParameterPositionError")) return; std::ostringstream errmsg; errmsg << functionName << ": "; @@ -1741,10 +1939,10 @@ void CheckIO::wrongPrintfScanfPosixParameterPositionError(const Token* tok, cons reportError(tok, Severity::warning, "wrongPrintfScanfParameterPositionError", errmsg.str(), CWE685, Certainty::normal); } -void CheckIO::invalidScanfArgTypeError_s(const Token* tok, nonneg int numFormat, const std::string& specifier, const ArgumentInfo* argInfo) +void CheckIOImpl::invalidScanfArgTypeError_s(const Token* tok, nonneg int numFormat, const std::string& specifier, const ArgumentInfo* argInfo) { const Severity severity = getSeverity(argInfo); - if (!mSettings->severity.isEnabled(severity)) + if (!mSettings.severity.isEnabled(severity)) return; std::ostringstream errmsg; errmsg << "%" << specifier << " in format string (no. " << numFormat << ") requires a \'"; @@ -1757,10 +1955,10 @@ void CheckIO::invalidScanfArgTypeError_s(const Token* tok, nonneg int numFormat, errmsg << "."; reportError(tok, severity, "invalidScanfArgType_s", errmsg.str(), CWE686, Certainty::normal); } -void CheckIO::invalidScanfArgTypeError_int(const Token* tok, nonneg int numFormat, const std::string& specifier, const ArgumentInfo* argInfo, bool isUnsigned) +void CheckIOImpl::invalidScanfArgTypeError_int(const Token* tok, nonneg int numFormat, const std::string& specifier, const ArgumentInfo* argInfo, bool isUnsigned) { const Severity severity = getSeverity(argInfo); - if (!mSettings->severity.isEnabled(severity)) + if (!mSettings.severity.isEnabled(severity)) return; std::ostringstream errmsg; errmsg << "%" << specifier << " in format string (no. " << numFormat << ") requires \'"; @@ -1802,10 +2000,10 @@ void CheckIO::invalidScanfArgTypeError_int(const Token* tok, nonneg int numForma errmsg << "."; reportError(tok, severity, "invalidScanfArgType_int", errmsg.str(), CWE686, Certainty::normal); } -void CheckIO::invalidScanfArgTypeError_float(const Token* tok, nonneg int numFormat, const std::string& specifier, const ArgumentInfo* argInfo) +void CheckIOImpl::invalidScanfArgTypeError_float(const Token* tok, nonneg int numFormat, const std::string& specifier, const ArgumentInfo* argInfo) { const Severity severity = getSeverity(argInfo); - if (!mSettings->severity.isEnabled(severity)) + if (!mSettings.severity.isEnabled(severity)) return; std::ostringstream errmsg; errmsg << "%" << specifier << " in format string (no. " << numFormat << ") requires \'"; @@ -1821,10 +2019,10 @@ void CheckIO::invalidScanfArgTypeError_float(const Token* tok, nonneg int numFor reportError(tok, severity, "invalidScanfArgType_float", errmsg.str(), CWE686, Certainty::normal); } -void CheckIO::invalidPrintfArgTypeError_s(const Token* tok, nonneg int numFormat, const ArgumentInfo* argInfo) +void CheckIOImpl::invalidPrintfArgTypeError_s(const Token* tok, nonneg int numFormat, const ArgumentInfo* argInfo) { const Severity severity = getSeverity(argInfo); - if (!mSettings->severity.isEnabled(severity)) + if (!mSettings.severity.isEnabled(severity)) return; std::ostringstream errmsg; errmsg << "%s in format string (no. " << numFormat << ") requires \'char *\' but the argument type is "; @@ -1832,10 +2030,10 @@ void CheckIO::invalidPrintfArgTypeError_s(const Token* tok, nonneg int numFormat errmsg << "."; reportError(tok, severity, "invalidPrintfArgType_s", errmsg.str(), CWE686, Certainty::normal); } -void CheckIO::invalidPrintfArgTypeError_n(const Token* tok, nonneg int numFormat, const ArgumentInfo* argInfo) +void CheckIOImpl::invalidPrintfArgTypeError_n(const Token* tok, nonneg int numFormat, const ArgumentInfo* argInfo) { const Severity severity = getSeverity(argInfo); - if (!mSettings->severity.isEnabled(severity)) + if (!mSettings.severity.isEnabled(severity)) return; std::ostringstream errmsg; errmsg << "%n in format string (no. " << numFormat << ") requires \'int *\' but the argument type is "; @@ -1843,10 +2041,10 @@ void CheckIO::invalidPrintfArgTypeError_n(const Token* tok, nonneg int numFormat errmsg << "."; reportError(tok, severity, "invalidPrintfArgType_n", errmsg.str(), CWE686, Certainty::normal); } -void CheckIO::invalidPrintfArgTypeError_p(const Token* tok, nonneg int numFormat, const ArgumentInfo* argInfo) +void CheckIOImpl::invalidPrintfArgTypeError_p(const Token* tok, nonneg int numFormat, const ArgumentInfo* argInfo) { const Severity severity = getSeverity(argInfo); - if (!mSettings->severity.isEnabled(severity)) + if (!mSettings.severity.isEnabled(severity)) return; std::ostringstream errmsg; errmsg << "%p in format string (no. " << numFormat << ") requires an address but the argument type is "; @@ -1893,10 +2091,10 @@ static void printfFormatType(std::ostream& os, const std::string& specifier, boo os << "\'"; } -void CheckIO::invalidPrintfArgTypeError_uint(const Token* tok, nonneg int numFormat, const std::string& specifier, const ArgumentInfo* argInfo) +void CheckIOImpl::invalidPrintfArgTypeError_uint(const Token* tok, nonneg int numFormat, const std::string& specifier, const ArgumentInfo* argInfo) { const Severity severity = getSeverity(argInfo); - if (!mSettings->severity.isEnabled(severity)) + if (!mSettings.severity.isEnabled(severity)) return; std::ostringstream errmsg; errmsg << "%" << specifier << " in format string (no. " << numFormat << ") requires "; @@ -1907,10 +2105,10 @@ void CheckIO::invalidPrintfArgTypeError_uint(const Token* tok, nonneg int numFor reportError(tok, severity, "invalidPrintfArgType_uint", errmsg.str(), CWE686, Certainty::normal); } -void CheckIO::invalidPrintfArgTypeError_sint(const Token* tok, nonneg int numFormat, const std::string& specifier, const ArgumentInfo* argInfo) +void CheckIOImpl::invalidPrintfArgTypeError_sint(const Token* tok, nonneg int numFormat, const std::string& specifier, const ArgumentInfo* argInfo) { const Severity severity = getSeverity(argInfo); - if (!mSettings->severity.isEnabled(severity)) + if (!mSettings.severity.isEnabled(severity)) return; std::ostringstream errmsg; errmsg << "%" << specifier << " in format string (no. " << numFormat << ") requires "; @@ -1920,10 +2118,10 @@ void CheckIO::invalidPrintfArgTypeError_sint(const Token* tok, nonneg int numFor errmsg << "."; reportError(tok, severity, "invalidPrintfArgType_sint", errmsg.str(), CWE686, Certainty::normal); } -void CheckIO::invalidPrintfArgTypeError_float(const Token* tok, nonneg int numFormat, const std::string& specifier, const ArgumentInfo* argInfo) +void CheckIOImpl::invalidPrintfArgTypeError_float(const Token* tok, nonneg int numFormat, const std::string& specifier, const ArgumentInfo* argInfo) { const Severity severity = getSeverity(argInfo); - if (!mSettings->severity.isEnabled(severity)) + if (!mSettings.severity.isEnabled(severity)) return; std::ostringstream errmsg; errmsg << "%" << specifier << " in format string (no. " << numFormat << ") requires \'"; @@ -1935,12 +2133,12 @@ void CheckIO::invalidPrintfArgTypeError_float(const Token* tok, nonneg int numFo reportError(tok, severity, "invalidPrintfArgType_float", errmsg.str(), CWE686, Certainty::normal); } -Severity CheckIO::getSeverity(const CheckIO::ArgumentInfo *argInfo) +Severity CheckIOImpl::getSeverity(const ArgumentInfo *argInfo) { return (argInfo && argInfo->typeToken && !argInfo->typeToken->originalName().empty()) ? Severity::portability : Severity::warning; } -void CheckIO::argumentType(std::ostream& os, const ArgumentInfo * argInfo) +void CheckIOImpl::argumentType(std::ostream& os, const ArgumentInfo * argInfo) { if (argInfo) { os << "\'"; @@ -1990,16 +2188,16 @@ void CheckIO::argumentType(std::ostream& os, const ArgumentInfo * argInfo) os << "Unknown"; } -void CheckIO::invalidLengthModifierError(const Token* tok, nonneg int numFormat, const std::string& modifier) +void CheckIOImpl::invalidLengthModifierError(const Token* tok, nonneg int numFormat, const std::string& modifier) { - if (!mSettings->severity.isEnabled(Severity::warning) && !mSettings->isPremiumEnabled("invalidLengthModifierError")) + if (!mSettings.severity.isEnabled(Severity::warning) && !mSettings.isPremiumEnabled("invalidLengthModifierError")) return; std::ostringstream errmsg; errmsg << "'" << modifier << "' in format string (no. " << numFormat << ") is a length modifier and cannot be used without a conversion specifier."; reportError(tok, Severity::warning, "invalidLengthModifierError", errmsg.str(), CWE704, Certainty::normal); } -void CheckIO::invalidScanfFormatWidthError(const Token* tok, nonneg int numFormat, int width, const Variable *var, const std::string& specifier) +void CheckIOImpl::invalidScanfFormatWidthError(const Token* tok, nonneg int numFormat, int width, const Variable *var, const std::string& specifier) { MathLib::bigint arrlen = 0; std::string varname; @@ -2011,7 +2209,7 @@ void CheckIO::invalidScanfFormatWidthError(const Token* tok, nonneg int numForma std::ostringstream errmsg; if (arrlen > width) { - if (tok != nullptr && (!mSettings->certainty.isEnabled(Certainty::inconclusive) || !mSettings->severity.isEnabled(Severity::warning))) + if (tok != nullptr && (!mSettings.certainty.isEnabled(Certainty::inconclusive) || !mSettings.severity.isEnabled(Severity::warning))) return; errmsg << "Width " << width << " given in format string (no. " << numFormat << ") is smaller than destination buffer" << " '" << varname << "[" << arrlen << "]'."; @@ -2023,27 +2221,30 @@ void CheckIO::invalidScanfFormatWidthError(const Token* tok, nonneg int numForma } } -void CheckIO::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) +void CheckIO::runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) { - CheckIO checkIO(&tokenizer, &tokenizer.getSettings(), errorLogger); + CheckIOImpl checkIO(&tokenizer, tokenizer.getSettings(), errorLogger); checkIO.checkWrongPrintfScanfArguments(); checkIO.checkCoutCerrMisusage(); checkIO.checkFileUsage(); + checkIO.checkWrongfeofUsage(); checkIO.invalidScanf(); } -void CheckIO::getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const +void CheckIO::getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const { - CheckIO c(nullptr, settings, errorLogger); + CheckIOImpl c(nullptr, settings, errorLogger); c.coutCerrMisusageError(nullptr, "cout"); c.fflushOnInputStreamError(nullptr, "stdin"); c.ioWithoutPositioningError(nullptr); c.readWriteOnlyFileError(nullptr); c.writeReadOnlyFileError(nullptr); c.useClosedFileError(nullptr); + c.fcloseInLoopConditionError(nullptr, "fp"); c.seekOnAppendedFileError(nullptr); c.incompatibleFileOpenError(nullptr, "tmp"); + c.wrongfeofUsage(nullptr); c.invalidScanfError(nullptr); c.wrongPrintfScanfArgumentsError(nullptr, "printf",3,2); c.invalidScanfArgTypeError_s(nullptr, 1, "s", nullptr); diff --git a/lib/checkio.h b/lib/checkio.h index e37a942770b..06c6d8d973b 100644 --- a/lib/checkio.h +++ b/lib/checkio.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,6 +22,7 @@ //--------------------------------------------------------------------------- #include "check.h" +#include "checkimpl.h" #include "config.h" #include @@ -34,6 +35,7 @@ class Token; class Variable; class ErrorLogger; class Tokenizer; +class Library; enum class Severity : std::uint8_t; /// @addtogroup Checks @@ -41,19 +43,36 @@ enum class Severity : std::uint8_t; /** @brief %Check input output operations. */ class CPPCHECKLIB CheckIO : public Check { - friend class TestIO; - public: /** @brief This constructor is used when registering CheckIO */ - CheckIO() : Check(myName()) {} + CheckIO() : Check("IO using format string") {} private: - /** @brief This constructor is used when running checks. */ - CheckIO(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) - : Check(myName(), tokenizer, settings, errorLogger) {} - /** @brief Run checks on the normal token list */ - void runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) override; + void runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) override; + + void getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const override; + + std::string classInfo() const override { + return "Check format string input/output operations.\n" + "- Bad usage of the function 'sprintf' (overlapping data)\n" + "- Missing or wrong width specifiers in 'scanf' format string\n" + "- Use a file that has been closed\n" + "- File input/output without positioning results in undefined behaviour\n" + "- Read to a file that has only been opened for writing (or vice versa)\n" + "- Repositioning operation on a file opened in append mode\n" + "- The same file can't be open for read and write at the same time on different streams\n" + "- Using fflush() on an input stream\n" + "- Invalid usage of output stream. For example: 'std::cout << std::cout;'\n" + "- Wrong number of arguments given to 'printf' or 'scanf;'\n"; + } +}; + +class CPPCHECKLIB CheckIOImpl : public CheckImpl { +public: + /** @brief This constructor is used when running checks. */ + CheckIOImpl(const Tokenizer *tokenizer, const Settings &settings, ErrorLogger &errorLogger) + : CheckImpl(tokenizer, settings, errorLogger) {} /** @brief %Check for missusage of std::cout */ void checkCoutCerrMisusage(); @@ -64,6 +83,9 @@ class CPPCHECKLIB CheckIO : public Check { /** @brief scanf can crash if width specifiers are not used */ void invalidScanf(); + /** @brief %Check wrong usage of feof */ + void checkWrongfeofUsage(); + /** @brief %Checks type and number of arguments given to functions like printf or scanf*/ void checkWrongPrintfScanfArguments(); @@ -80,7 +102,7 @@ class CPPCHECKLIB CheckIO : public Check { bool isKnownType() const; bool isStdVectorOrString(); bool isStdContainer(const Token *tok); - bool isLibraryType(const Settings &settings) const; + bool isLibraryType(const Library &library) const; const Variable* variableInfo{}; const Token* typeToken{}; @@ -105,9 +127,12 @@ class CPPCHECKLIB CheckIO : public Check { void readWriteOnlyFileError(const Token *tok); void writeReadOnlyFileError(const Token *tok); void useClosedFileError(const Token *tok); + void fcloseInLoopConditionError(const Token *tok, const std::string &varname); void seekOnAppendedFileError(const Token *tok); + void ftellFileError(const Token *tok); void incompatibleFileOpenError(const Token *tok, const std::string &filename); void invalidScanfError(const Token *tok); + void wrongfeofUsage(const Token *tok); void wrongPrintfScanfArgumentsError(const Token* tok, const std::string &functionName, nonneg int numFormat, @@ -127,26 +152,6 @@ class CPPCHECKLIB CheckIO : public Check { void invalidScanfFormatWidthError(const Token* tok, nonneg int numFormat, int width, const Variable *var, const std::string& specifier); static void argumentType(std::ostream & os, const ArgumentInfo * argInfo); static Severity getSeverity(const ArgumentInfo *argInfo); - - void getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const override; - - static std::string myName() { - return "IO using format string"; - } - - std::string classInfo() const override { - return "Check format string input/output operations.\n" - "- Bad usage of the function 'sprintf' (overlapping data)\n" - "- Missing or wrong width specifiers in 'scanf' format string\n" - "- Use a file that has been closed\n" - "- File input/output without positioning results in undefined behaviour\n" - "- Read to a file that has only been opened for writing (or vice versa)\n" - "- Repositioning operation on a file opened in append mode\n" - "- The same file can't be open for read and write at the same time on different streams\n" - "- Using fflush() on an input stream\n" - "- Invalid usage of output stream. For example: 'std::cout << std::cout;'\n" - "- Wrong number of arguments given to 'printf' or 'scanf;'\n"; - } }; /// @} //--------------------------------------------------------------------------- diff --git a/lib/checkleakautovar.cpp b/lib/checkleakautovar.cpp index 803ea9967a3..6dc760618c8 100644 --- a/lib/checkleakautovar.cpp +++ b/lib/checkleakautovar.cpp @@ -45,11 +45,6 @@ //--------------------------------------------------------------------------- -// Register this check class (by creating a static instance of it) -namespace { - CheckLeakAutoVar instance; -} - static const CWE CWE672(672U); static const CWE CWE415(415U); @@ -112,39 +107,40 @@ void VarInfo::possibleUsageAll(const std::pair& functionUsa } -void CheckLeakAutoVar::leakError(const Token *tok, const std::string &varname, int type) const +void CheckLeakAutoVarImpl::leakError(const Token *tok, const std::string &varname, int type) const { - const CheckMemoryLeak checkmemleak(mTokenizer, mErrorLogger, mSettings); + const CheckMemoryLeakImpl checkmemleak(mTokenizer, mSettings, mErrorLogger); if (Library::isresource(type)) checkmemleak.resourceLeakError(tok, varname); else checkmemleak.memleakError(tok, varname); } -void CheckLeakAutoVar::mismatchError(const Token *deallocTok, const Token *allocTok, const std::string &varname) const +void CheckLeakAutoVarImpl::mismatchError(const Token *deallocTok, const Token *allocTok, const std::string &varname) const { - const CheckMemoryLeak c(mTokenizer, mErrorLogger, mSettings); + const CheckMemoryLeakImpl c(mTokenizer, mSettings, mErrorLogger); const std::list callstack = { allocTok, deallocTok }; c.mismatchAllocDealloc(callstack, varname); } -void CheckLeakAutoVar::deallocUseError(const Token *tok, const std::string &varname) const +void CheckLeakAutoVarImpl::deallocUseError(const Token *tok, const std::string &varname) const { - const CheckMemoryLeak c(mTokenizer, mErrorLogger, mSettings); + const CheckMemoryLeakImpl c(mTokenizer, mSettings, mErrorLogger); c.deallocuseError(tok, varname); } -void CheckLeakAutoVar::deallocReturnError(const Token *tok, const Token *deallocTok, const std::string &varname) +void CheckLeakAutoVarImpl::deallocReturnError(const Token *tok, const Token *deallocTok, const std::string &varname) { const std::list locations = { deallocTok, tok }; reportError(locations, Severity::error, "deallocret", "$symbol:" + varname + "\nReturning/dereferencing '$symbol' after it is deallocated / released", CWE672, Certainty::normal); } -void CheckLeakAutoVar::configurationInfo(const Token* tok, const std::pair& functionUsage) +void CheckLeakAutoVarImpl::configurationInfo(const Token* tok, const std::pair& functionUsage) { - if (mSettings->checkLibrary && functionUsage.second == VarInfo::USED && - (!functionUsage.first || !functionUsage.first->function() || !functionUsage.first->function()->hasBody())) { - std::string funcStr = functionUsage.first ? mSettings->library.getFunctionName(functionUsage.first) : "f"; + if (mSettings.checkLibrary && functionUsage.second == VarInfo::USED && + (!functionUsage.first || !functionUsage.first->function() || !functionUsage.first->function()->hasBody()) && + !(functionUsage.first && mSettings.library.isUse(mSettings.library.getFunctionName(functionUsage.first)))) { + std::string funcStr = functionUsage.first ? mSettings.library.getFunctionName(functionUsage.first) : "f"; if (funcStr.empty()) funcStr = "unknown::" + functionUsage.first->str(); reportError(tok, @@ -154,7 +150,7 @@ void CheckLeakAutoVar::configurationInfo(const Token* tok, const std::pair locations = { prevFreeTok, tok }; @@ -165,9 +161,9 @@ void CheckLeakAutoVar::doubleFreeError(const Token *tok, const Token *prevFreeTo } -void CheckLeakAutoVar::check() +void CheckLeakAutoVarImpl::check() { - if (mSettings->clang) + if (mSettings.clang) return; logChecker("CheckLeakAutoVar::check"); // notclang @@ -272,7 +268,7 @@ static const Token * isFunctionCall(const Token * nameToken) return nullptr; } -static const Token* getOutparamAllocation(const Token* tok, const Settings& settings) +static const Token* getOutparamAllocation(const Token* tok, const Library& library) { if (!tok) return nullptr; @@ -280,16 +276,16 @@ static const Token* getOutparamAllocation(const Token* tok, const Settings& sett const Token* ftok = getTokenArgumentFunction(tok, argn); if (!ftok) return nullptr; - if (const Library::AllocFunc* allocFunc = settings.library.getAllocFuncInfo(ftok)) { + if (const Library::AllocFunc* allocFunc = library.getAllocFuncInfo(ftok)) { if (allocFunc->arg == argn + 1) return ftok; } return nullptr; } -static const Token* getReturnValueFromOutparamAlloc(const Token* alloc, const Settings& settings) +static const Token* getReturnValueFromOutparamAlloc(const Token* alloc, const Library& library) { - if (const Token* ftok = getOutparamAllocation(alloc, settings)) { + if (const Token* ftok = getOutparamAllocation(alloc, library)) { if (Token::simpleMatch(ftok->astParent()->astParent(), "=")) return ftok->next()->astParent()->astOperand1(); } @@ -308,10 +304,10 @@ static std::vector getComparisonTokens(const Token* tok) return result; } -bool CheckLeakAutoVar::checkScope(const Token * const startToken, - VarInfo &varInfo, - std::set notzero, - nonneg int recursiveCount) +bool CheckLeakAutoVarImpl::checkScope(const Token * const startToken, + VarInfo &varInfo, + std::set notzero, + nonneg int recursiveCount) { #if ASAN static const nonneg int recursiveLimit = 300; @@ -347,6 +343,8 @@ bool CheckLeakAutoVar::checkScope(const Token * const startToken, } } + if (tok == tok->scope()->bodyEnd && tok->scope()->type == ScopeType::eUnconditional) + ret(tok, varInfo, /*isEndOfScope*/ true); // look for end of statement const bool isInit = Token::Match(tok->tokAt(-1), "%var% {|(") && tok->tokAt(-1)->variable() && tok->tokAt(-1) == tok->tokAt(-1)->variable()->nameToken(); @@ -386,7 +384,7 @@ bool CheckLeakAutoVar::checkScope(const Token * const startToken, ftok = ftok->tokAt(2); // bailout for variable passed to library function with out parameter - if (const Library::Function *libFunc = mSettings->library.getFunction(ftok)) { + if (const Library::Function *libFunc = mSettings.library.getFunction(ftok)) { using ArgumentChecks = Library::ArgumentChecks; using Direction = ArgumentChecks::Direction; const std::vector args = getArguments(ftok); @@ -397,7 +395,7 @@ bool CheckLeakAutoVar::checkScope(const Token * const startToken, }); }); if (hasOutParam) { - for (int i = 0; i < args.size(); i++) { + for (size_t i = 0; i < args.size(); i++) { if (!argChecks.count(i + 1)) continue; const ArgumentChecks argCheck = argChecks.at(i + 1); @@ -484,7 +482,7 @@ bool CheckLeakAutoVar::checkScope(const Token * const startToken, // allocation? const Token *const fTok = tokRightAstOperand ? tokRightAstOperand->previous() : nullptr; if (Token::Match(fTok, "%type% (")) { - const Library::AllocFunc* f = mSettings->library.getAllocFuncInfo(fTok); + const Library::AllocFunc* f = mSettings.library.getAllocFuncInfo(fTok); if (f && f->arg == -1) { VarInfo::AllocInfo& varAlloc = alloctype[varTok->varId()]; varAlloc.type = f->groupId; @@ -550,7 +548,7 @@ bool CheckLeakAutoVar::checkScope(const Token * const startToken, tokRightAstOperand = tokRightAstOperand->astOperand2() ? tokRightAstOperand->astOperand2() : tokRightAstOperand->astOperand1(); if (tokRightAstOperand && Token::Match(tokRightAstOperand->previous(), "%type% (")) { const Token * fTok = tokRightAstOperand->previous(); - const Library::AllocFunc* f = mSettings->library.getAllocFuncInfo(fTok); + const Library::AllocFunc* f = mSettings.library.getAllocFuncInfo(fTok); if (f && f->arg == -1) { VarInfo::AllocInfo& varAlloc = alloctype[innerTok->varId()]; varAlloc.type = f->groupId; @@ -573,7 +571,7 @@ bool CheckLeakAutoVar::checkScope(const Token * const startToken, // check for function call if (openingPar) { - const Library::AllocFunc* allocFunc = mSettings->library.getDeallocFuncInfo(innerTok); + const Library::AllocFunc* allocFunc = mSettings.library.getDeallocFuncInfo(innerTok); // innerTok is a function name const VarInfo::AllocInfo allocation(0, VarInfo::NOALLOC); functionCall(innerTok, openingPar, varInfo, allocation, allocFunc); @@ -628,7 +626,7 @@ bool CheckLeakAutoVar::checkScope(const Token * const startToken, if (std::any_of(varInfo1.alloctype.begin(), varInfo1.alloctype.end(), [&](const std::pair& info) { if (info.second.status != VarInfo::ALLOC) return false; - const Token* ret = getReturnValueFromOutparamAlloc(info.second.allocTok, *mSettings); + const Token* ret = getReturnValueFromOutparamAlloc(info.second.allocTok, mSettings.library); return ret && vartok && ret->varId() && ret->varId() == vartok->varId(); })) { varInfo1.clear(); @@ -760,7 +758,7 @@ bool CheckLeakAutoVar::checkScope(const Token * const startToken, // Function call.. else if (const Token* openingPar = isFunctionCall(ftok)) { - const Library::AllocFunc* af = mSettings->library.getDeallocFuncInfo(ftok); + const Library::AllocFunc* af = mSettings.library.getDeallocFuncInfo(ftok); VarInfo::AllocInfo allocation(af ? af->groupId : 0, VarInfo::DEALLOC, ftok); if (allocation.type == 0) allocation.status = VarInfo::NOALLOC; @@ -781,8 +779,8 @@ bool CheckLeakAutoVar::checkScope(const Token * const startToken, if (ftok->function() && !ftok->function()->isAttributeNoreturn() && !(ftok->function()->functionScope && mTokenizer->isScopeNoReturn(ftok->function()->functionScope->bodyEnd))) // check function scope continue; - const std::string functionName(mSettings->library.getFunctionName(ftok)); - if (!mSettings->library.isLeakIgnore(functionName) && !mSettings->library.isUse(functionName)) { + const std::string functionName(mSettings.library.getFunctionName(ftok)); + if (!mSettings.library.isLeakIgnore(functionName) && !mSettings.library.isUse(functionName)) { const VarInfo::Usage usage = Token::simpleMatch(openingPar, "( )") ? VarInfo::NORET : VarInfo::USED; // TODO: check parameters passed to function varInfo.possibleUsageAll({ ftok, usage }); } @@ -805,7 +803,7 @@ bool CheckLeakAutoVar::checkScope(const Token * const startToken, } // Check smart pointer - else if (Token::Match(ftok, "%name% <") && mSettings->library.isSmartPointer(tok)) { + else if (Token::Match(ftok, "%name% <") && mSettings.library.isSmartPointer(tok)) { const Token * typeEndTok = ftok->linkAt(1); if (!Token::Match(typeEndTok, "> %var% {|( %var% ,|)|}")) continue; @@ -840,7 +838,7 @@ bool CheckLeakAutoVar::checkScope(const Token * const startToken, const Token * dtok = Token::findmatch(deleterToken, "& %name%", endDeleterToken); if (dtok) { dtok = dtok->next(); - af = mSettings->library.getDeallocFuncInfo(dtok); + af = mSettings.library.getDeallocFuncInfo(dtok); } if (!dtok || !af) { const Token * tscopeStart = nullptr; @@ -870,7 +868,7 @@ bool CheckLeakAutoVar::checkScope(const Token * const startToken, if (tscopeStart && tscopeEnd) { for (const Token *tok2 = tscopeStart; tok2 != tscopeEnd; tok2 = tok2->next()) { - af = mSettings->library.getDeallocFuncInfo(tok2); + af = mSettings.library.getDeallocFuncInfo(tok2); if (af) break; } @@ -892,7 +890,7 @@ bool CheckLeakAutoVar::checkScope(const Token * const startToken, } -const Token * CheckLeakAutoVar::checkTokenInsideExpression(const Token * const tok, VarInfo &varInfo, bool inFuncCall) +const Token * CheckLeakAutoVarImpl::checkTokenInsideExpression(const Token * const tok, VarInfo &varInfo, bool inFuncCall) { // Deallocation and then dereferencing pointer.. if (tok->varId() > 0) { @@ -901,7 +899,7 @@ const Token * CheckLeakAutoVar::checkTokenInsideExpression(const Token * const t if (var != varInfo.alloctype.end()) { bool unknown = false; if (var->second.status == VarInfo::DEALLOC && tok->valueType() && tok->valueType()->pointer && - CheckNullPointer::isPointerDeRef(tok, unknown, *mSettings, /*checkNullArg*/ false) && !unknown) { + CheckNullPointerImpl::isPointerDeRef(tok, unknown, mSettings.library, /*checkNullArg*/ false) && !unknown) { deallocUseError(tok, tok->str()); } else if (Token::simpleMatch(tok->tokAt(-2), "= &")) { varInfo.erase(tok->varId()); @@ -922,9 +920,9 @@ const Token * CheckLeakAutoVar::checkTokenInsideExpression(const Token * const t if ((rhs->str() == "." || rhs->varId() == tok->varId()) && isAssignment) { // simple assignment varInfo.erase(tok->varId()); - } else if (rhs->astParent() && rhs->str() == "(" && !mSettings->library.returnValue(rhs->astOperand1()).empty()) { + } else if (rhs->astParent() && rhs->str() == "(" && !mSettings.library.returnValue(rhs->astOperand1()).empty()) { // #9298, assignment through return value of a function - const std::string &returnValue = mSettings->library.returnValue(rhs->astOperand1()); + const std::string &returnValue = mSettings.library.returnValue(rhs->astOperand1()); if (startsWith(returnValue, "arg")) { int argn; const Token *func = getTokenArgumentFunction(tok, argn); @@ -945,12 +943,12 @@ const Token * CheckLeakAutoVar::checkTokenInsideExpression(const Token * const t // check for function call const Token * const openingPar = inFuncCall ? nullptr : isFunctionCall(tok); if (openingPar) { - const Library::AllocFunc* allocFunc = mSettings->library.getDeallocFuncInfo(tok); + const Library::AllocFunc* allocFunc = mSettings.library.getDeallocFuncInfo(tok); VarInfo::AllocInfo alloc(allocFunc ? allocFunc->groupId : 0, VarInfo::DEALLOC, tok); if (alloc.type == 0) alloc.status = VarInfo::NOALLOC; functionCall(tok, openingPar, varInfo, alloc, nullptr); - const std::string &returnValue = mSettings->library.returnValue(tok); + const std::string &returnValue = mSettings.library.returnValue(tok); if (startsWith(returnValue, "arg")) // the function returns one of its argument, we need to process a potential assignment return openingPar; @@ -961,9 +959,9 @@ const Token * CheckLeakAutoVar::checkTokenInsideExpression(const Token * const t } -void CheckLeakAutoVar::changeAllocStatusIfRealloc(std::map &alloctype, const Token *fTok, const Token *retTok) const +void CheckLeakAutoVarImpl::changeAllocStatusIfRealloc(std::map &alloctype, const Token *fTok, const Token *retTok) const { - const Library::AllocFunc* f = mSettings->library.getReallocFuncInfo(fTok); + const Library::AllocFunc* f = mSettings.library.getReallocFuncInfo(fTok); if (f && f->arg == -1 && f->reallocArg > 0 && f->reallocArg <= numberOfArguments(fTok)) { const Token* argTok = getArguments(fTok).at(f->reallocArg - 1); if (alloctype.find(argTok->varId()) != alloctype.end()) { @@ -982,14 +980,14 @@ void CheckLeakAutoVar::changeAllocStatusIfRealloc(std::map &alloctype = varInfo.alloctype; const auto var = alloctype.find(arg->varId()); if (var != alloctype.end()) { // bailout if function is also allocating, since the argument might be moved // to the return value, such as in fdopen - if (allocation.allocTok && mSettings->library.getAllocFuncInfo(allocation.allocTok)) { + if (allocation.allocTok && mSettings.library.getAllocFuncInfo(allocation.allocTok)) { varInfo.erase(arg->varId()); return; } @@ -1019,11 +1017,11 @@ void CheckLeakAutoVar::changeAllocStatus(VarInfo &varInfo, const VarInfo::AllocI } } -void CheckLeakAutoVar::functionCall(const Token *tokName, const Token *tokOpeningPar, VarInfo &varInfo, const VarInfo::AllocInfo& allocation, const Library::AllocFunc* af) +void CheckLeakAutoVarImpl::functionCall(const Token *tokName, const Token *tokOpeningPar, VarInfo &varInfo, const VarInfo::AllocInfo& allocation, const Library::AllocFunc* af) { // Ignore function call? - const bool isLeakIgnore = mSettings->library.isLeakIgnore(mSettings->library.getFunctionName(tokName)); - if (mSettings->library.getReallocFuncInfo(tokName)) + const bool isLeakIgnore = mSettings.library.isLeakIgnore(mSettings.library.getFunctionName(tokName)); + if (mSettings.library.getReallocFuncInfo(tokName)) return; if (tokName->next()->valueType() && tokName->next()->valueType()->container && tokName->next()->valueType()->container->stdStringLike) return; @@ -1074,10 +1072,10 @@ void CheckLeakAutoVar::functionCall(const Token *tokName, const Token *tokOpenin // Is variable allocated? if (!isnull && (!af || af->arg == argNr)) { - const Library::AllocFunc* deallocFunc = mSettings->library.getDeallocFuncInfo(tokName); + const Library::AllocFunc* deallocFunc = mSettings.library.getDeallocFuncInfo(tokName); VarInfo::AllocInfo dealloc(deallocFunc ? deallocFunc->groupId : 0, VarInfo::DEALLOC, tokName); - if (const Library::AllocFunc* allocFunc = mSettings->library.getAllocFuncInfo(tokName)) { - if (mSettings->library.getDeallocFuncInfo(tokName)) { + if (const Library::AllocFunc* allocFunc = mSettings.library.getAllocFuncInfo(tokName)) { + if (mSettings.library.getDeallocFuncInfo(tokName)) { changeAllocStatus(varInfo, dealloc.type == 0 ? allocation : dealloc, tokName, arg); } if (allocFunc->arg == argNr && @@ -1097,7 +1095,7 @@ void CheckLeakAutoVar::functionCall(const Token *tokName, const Token *tokOpenin } } // Check smart pointer - else if (Token::Match(arg, "%name% < %type%") && mSettings->library.isSmartPointer(argTypeStartTok)) { + else if (Token::Match(arg, "%name% < %type%") && mSettings.library.isSmartPointer(argTypeStartTok)) { const Token * typeEndTok = arg->linkAt(1); const Token * allocTok = nullptr; if (!Token::Match(typeEndTok, "> {|( %var% ,|)|}")) @@ -1122,14 +1120,14 @@ void CheckLeakAutoVar::functionCall(const Token *tokName, const Token *tokOpenin // Check if its a pointer to a function const Token * dtok = Token::findmatch(deleterToken, "& %name%", endDeleterToken); if (dtok) { - sp_af = mSettings->library.getDeallocFuncInfo(dtok->tokAt(1)); + sp_af = mSettings.library.getDeallocFuncInfo(dtok->tokAt(1)); } else { // If the deleter is a class, check if class calls the dealloc function dtok = Token::findmatch(deleterToken, "%type%", endDeleterToken); if (dtok && dtok->type()) { const Scope * tscope = dtok->type()->classScope; for (const Token *tok2 = tscope->bodyStart; tok2 != tscope->bodyEnd; tok2 = tok2->next()) { - sp_af = mSettings->library.getDeallocFuncInfo(tok2); + sp_af = mSettings.library.getDeallocFuncInfo(tok2); if (sp_af) { allocTok = tok2; break; @@ -1158,8 +1156,8 @@ void CheckLeakAutoVar::functionCall(const Token *tokName, const Token *tokOpenin } -void CheckLeakAutoVar::leakIfAllocated(const Token *vartok, - const VarInfo &varInfo) +void CheckLeakAutoVarImpl::leakIfAllocated(const Token *vartok, + const VarInfo &varInfo) { const std::map &alloctype = varInfo.alloctype; const auto& possibleUsage = varInfo.possibleUsage; @@ -1181,7 +1179,7 @@ static bool isSafeCast(const ValueType* vt, const Settings& settings) return sizeOf == 0 || sizeOf >= settings.platform.sizeof_pointer; } -void CheckLeakAutoVar::ret(const Token *tok, VarInfo &varInfo, const bool isEndOfScope) +void CheckLeakAutoVarImpl::ret(const Token *tok, VarInfo &varInfo, const bool isEndOfScope) { const std::map &alloctype = varInfo.alloctype; const auto& possibleUsage = varInfo.possibleUsage; @@ -1214,7 +1212,7 @@ void CheckLeakAutoVar::ret(const Token *tok, VarInfo &varInfo, const bool isEndO while (tok3 && tok3->isCast() && (!tok3->valueType() || tok3->valueType()->pointer || - isSafeCast(tok3->valueType(), *mSettings))) + isSafeCast(tok3->valueType(), mSettings))) tok3 = tok3->astOperand2() ? tok3->astOperand2() : tok3->astOperand1(); if (tok3 && tok3->varId() == varid) tok2 = tok3->next(); @@ -1237,7 +1235,7 @@ void CheckLeakAutoVar::ret(const Token *tok, VarInfo &varInfo, const bool isEndO // don't warn when returning after checking return value of outparam allocation const Token* outparamFunc{}; if ((tok->scope()->type == ScopeType::eIf || tok->scope()->type== ScopeType::eElse) && - (outparamFunc = getOutparamAllocation(it->second.allocTok, *mSettings))) { + (outparamFunc = getOutparamAllocation(it->second.allocTok, mSettings.library))) { const Scope* scope = tok->scope(); if (scope->type == ScopeType::eElse) { scope = scope->bodyStart->tokAt(-2)->scope(); @@ -1283,15 +1281,15 @@ void CheckLeakAutoVar::ret(const Token *tok, VarInfo &varInfo, const bool isEndO varInfo.erase(varId); } -void CheckLeakAutoVar::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) +void CheckLeakAutoVar::runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) { - CheckLeakAutoVar checkLeakAutoVar(&tokenizer, &tokenizer.getSettings(), errorLogger); + CheckLeakAutoVarImpl checkLeakAutoVar(&tokenizer, tokenizer.getSettings(), errorLogger); checkLeakAutoVar.check(); } -void CheckLeakAutoVar::getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const +void CheckLeakAutoVar::getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const { - CheckLeakAutoVar c(nullptr, settings, errorLogger); + CheckLeakAutoVarImpl c(nullptr, settings, errorLogger); c.deallocReturnError(nullptr, nullptr, "p"); c.configurationInfo(nullptr, { nullptr, VarInfo::USED }); // user configuration is needed to complete analysis c.doubleFreeError(nullptr, nullptr, "varname", 0); diff --git a/lib/checkleakautovar.h b/lib/checkleakautovar.h index d7d8d390b3d..b613b357119 100644 --- a/lib/checkleakautovar.h +++ b/lib/checkleakautovar.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,6 +23,7 @@ //--------------------------------------------------------------------------- #include "check.h" +#include "checkimpl.h" #include "config.h" #include "library.h" @@ -107,14 +108,23 @@ class CPPCHECKLIB VarInfo { class CPPCHECKLIB CheckLeakAutoVar : public Check { public: /** This constructor is used when registering the CheckLeakAutoVar */ - CheckLeakAutoVar() : Check(myName()) {} + CheckLeakAutoVar() : Check("Leaks (auto variables)") {} private: - /** This constructor is used when running checks. */ - CheckLeakAutoVar(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) - : Check(myName(), tokenizer, settings, errorLogger) {} + void runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) override; + + void getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const override; + + std::string classInfo() const override { + return "Detect when a auto variable is allocated but not deallocated or deallocated twice.\n"; + } +}; - void runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) override; +class CPPCHECKLIB CheckLeakAutoVarImpl : public CheckImpl { +public: + /** This constructor is used when running checks. */ + CheckLeakAutoVarImpl(const Tokenizer *tokenizer, const Settings &settings, ErrorLogger &errorLogger) + : CheckImpl(tokenizer, settings, errorLogger) {} /** check for leaks in all scopes */ void check(); @@ -157,16 +167,6 @@ class CPPCHECKLIB CheckLeakAutoVar : public Check { /** message: user configuration is needed to complete analysis */ void configurationInfo(const Token* tok, const std::pair& functionUsage); - - void getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const override; - - static std::string myName() { - return "Leaks (auto variables)"; - } - - std::string classInfo() const override { - return "Detect when a auto variable is allocated but not deallocated or deallocated twice.\n"; - } }; /// @} //--------------------------------------------------------------------------- diff --git a/lib/checkmemoryleak.cpp b/lib/checkmemoryleak.cpp index 7ce49292ffd..398240b06ab 100644 --- a/lib/checkmemoryleak.cpp +++ b/lib/checkmemoryleak.cpp @@ -37,14 +37,6 @@ //--------------------------------------------------------------------------- -// Register this check class (by creating a static instance of it) -namespace { - CheckMemoryLeakInFunction instance1; - CheckMemoryLeakInClass instance2; - CheckMemoryLeakStructMember instance3; - CheckMemoryLeakNoVar instance4; -} - // CWE ID used: static const CWE CWE398(398U); // Indicator of Poor Code Quality static const CWE CWE401(401U); // Improper Release of Memory Before Removing Last Reference ('Memory Leak') @@ -53,7 +45,8 @@ static const CWE CWE772(772U); // Missing Release of Resource after Effective L //--------------------------------------------------------------------------- -CheckMemoryLeak::AllocType CheckMemoryLeak::getAllocationType(const Token *tok2, nonneg int varid, std::list *callstack) const + +CheckMemoryLeakImpl::AllocType CheckMemoryLeakImpl::getAllocationType(const Token *tok2, nonneg int varid, std::list *callstack) const { // What we may have... // * var = (char *)malloc(10); @@ -101,7 +94,7 @@ CheckMemoryLeak::AllocType CheckMemoryLeak::getAllocationType(const Token *tok2, return New; } - if (mSettings_->hasLib("posix")) { + if (mSettings.hasLib("posix")) { if (Token::Match(tok2, "open|openat|creat|mkstemp|mkostemp|socket (")) { // simple sanity check of function parameters.. // TODO: Make such check for all these functions @@ -120,11 +113,11 @@ CheckMemoryLeak::AllocType CheckMemoryLeak::getAllocationType(const Token *tok2, } // Does tok2 point on a Library allocation function? - const int alloctype = mSettings_->library.getAllocId(tok2, -1); + const int alloctype = mSettings.library.getAllocId(tok2, -1); if (alloctype > 0) { - if (alloctype == mSettings_->library.deallocId("free")) + if (alloctype == mSettings.library.deallocId("free")) return Malloc; - if (alloctype == mSettings_->library.deallocId("fclose")) + if (alloctype == mSettings.library.deallocId("fclose")) return File; return Library::ismemory(alloctype) ? OtherMem : OtherRes; } @@ -151,7 +144,7 @@ CheckMemoryLeak::AllocType CheckMemoryLeak::getAllocationType(const Token *tok2, } -CheckMemoryLeak::AllocType CheckMemoryLeak::getReallocationType(const Token *tok2, nonneg int varid) const +CheckMemoryLeakImpl::AllocType CheckMemoryLeakImpl::getReallocationType(const Token *tok2, nonneg int varid) const { // What we may have... // * var = (char *)realloc(..; @@ -165,7 +158,7 @@ CheckMemoryLeak::AllocType CheckMemoryLeak::getReallocationType(const Token *tok if (!Token::Match(tok2, "%name% (")) return No; - const Library::AllocFunc *f = mSettings_->library.getReallocFuncInfo(tok2); + const Library::AllocFunc *f = mSettings.library.getReallocFuncInfo(tok2); if (!(f && f->reallocArg > 0 && f->reallocArg <= numberOfArguments(tok2))) return No; const auto args = getArguments(tok2); @@ -179,11 +172,11 @@ CheckMemoryLeak::AllocType CheckMemoryLeak::getReallocationType(const Token *tok if (varid > 0 && !Token::Match(arg, "%varid% [,)]", varid)) return No; - const int realloctype = mSettings_->library.getReallocId(tok2, -1); + const int realloctype = mSettings.library.getReallocId(tok2, -1); if (realloctype > 0) { - if (realloctype == mSettings_->library.deallocId("free")) + if (realloctype == mSettings.library.deallocId("free")) return Malloc; - if (realloctype == mSettings_->library.deallocId("fclose")) + if (realloctype == mSettings.library.deallocId("fclose")) return File; return Library::ismemory(realloctype) ? OtherMem : OtherRes; } @@ -191,7 +184,7 @@ CheckMemoryLeak::AllocType CheckMemoryLeak::getReallocationType(const Token *tok } -CheckMemoryLeak::AllocType CheckMemoryLeak::getDeallocationType(const Token *tok, nonneg int varid) const +CheckMemoryLeakImpl::AllocType CheckMemoryLeakImpl::getDeallocationType(const Token *tok, nonneg int varid) const { if (tok->isCpp() && tok->str() == "delete" && tok->astOperand1()) { const Token* vartok = tok->astOperand1(); @@ -222,7 +215,7 @@ CheckMemoryLeak::AllocType CheckMemoryLeak::getDeallocationType(const Token *tok if (tok->str() == "realloc" && Token::simpleMatch(vartok->next(), ", 0 )")) return Malloc; - if (mSettings_->hasLib("posix")) { + if (mSettings.hasLib("posix")) { if (tok->str() == "close") return Fd; if (tok->str() == "pclose") @@ -230,11 +223,11 @@ CheckMemoryLeak::AllocType CheckMemoryLeak::getDeallocationType(const Token *tok } // Does tok point on a Library deallocation function? - const int dealloctype = mSettings_->library.getDeallocId(tok, argNr); + const int dealloctype = mSettings.library.getDeallocId(tok, argNr); if (dealloctype > 0) { - if (dealloctype == mSettings_->library.deallocId("free")) + if (dealloctype == mSettings.library.deallocId("free")) return Malloc; - if (dealloctype == mSettings_->library.deallocId("fclose")) + if (dealloctype == mSettings.library.deallocId("fclose")) return File; return Library::ismemory(dealloctype) ? OtherMem : OtherRes; } @@ -246,10 +239,10 @@ CheckMemoryLeak::AllocType CheckMemoryLeak::getDeallocationType(const Token *tok return No; } -bool CheckMemoryLeak::isReopenStandardStream(const Token *tok) const +bool CheckMemoryLeakImpl::isReopenStandardStream(const Token *tok) const { if (getReallocationType(tok, 0) == File) { - const Library::AllocFunc *f = mSettings_->library.getReallocFuncInfo(tok); + const Library::AllocFunc *f = mSettings.library.getReallocFuncInfo(tok); if (f && f->reallocArg > 0 && f->reallocArg <= numberOfArguments(tok)) { const Token* arg = getArguments(tok).at(f->reallocArg - 1); if (Token::Match(arg, "stdin|stdout|stderr")) @@ -259,9 +252,9 @@ bool CheckMemoryLeak::isReopenStandardStream(const Token *tok) const return false; } -bool CheckMemoryLeak::isOpenDevNull(const Token *tok) const +bool CheckMemoryLeakImpl::isOpenDevNull(const Token *tok) const { - if (mSettings_->hasLib("posix") && tok->str() == "open" && numberOfArguments(tok) == 2) { + if (mSettings.hasLib("posix") && tok->str() == "open" && numberOfArguments(tok) == 2) { const Token* arg = getArguments(tok).at(0); if (Token::simpleMatch(arg, "\"/dev/null\"")) return true; @@ -269,24 +262,18 @@ bool CheckMemoryLeak::isOpenDevNull(const Token *tok) const return false; } -//-------------------------------------------------------------------------- - - -//-------------------------------------------------------------------------- - -void CheckMemoryLeak::memoryLeak(const Token *tok, const std::string &varname, AllocType alloctype) const +void CheckMemoryLeakImpl::memoryLeak(const Token *tok, const std::string &varname, AllocType alloctype) const { - if (alloctype == CheckMemoryLeak::File || - alloctype == CheckMemoryLeak::Pipe || - alloctype == CheckMemoryLeak::Fd || - alloctype == CheckMemoryLeak::OtherRes) + if (alloctype == File || + alloctype == Pipe || + alloctype == Fd || + alloctype == OtherRes) resourceLeakError(tok, varname); else memleakError(tok, varname); } -//--------------------------------------------------------------------------- -void CheckMemoryLeak::reportErr(const Token *tok, Severity severity, const std::string &id, const std::string &msg, const CWE &cwe) const +void CheckMemoryLeakImpl::reportErr(const Token *tok, Severity severity, const std::string &id, const std::string &msg, const CWE &cwe) const { std::list callstack; @@ -296,26 +283,23 @@ void CheckMemoryLeak::reportErr(const Token *tok, Severity severity, const std:: reportErr(callstack, severity, id, msg, cwe); } -void CheckMemoryLeak::reportErr(const std::list &callstack, Severity severity, const std::string &id, const std::string &msg, const CWE &cwe) const +void CheckMemoryLeakImpl::reportErr(const std::list &callstack, Severity severity, const std::string &id, const std::string &msg, const CWE &cwe) const { - const ErrorMessage errmsg(callstack, mTokenizer_ ? &mTokenizer_->list : nullptr, severity, id, msg, cwe, Certainty::normal); - if (mErrorLogger_) - mErrorLogger_->reportErr(errmsg); - else - Check::writeToErrorList(errmsg); + const ErrorMessage errmsg(callstack, mTokenizer ? &mTokenizer->list : nullptr, severity, id, msg, cwe, Certainty::normal); + mErrorLogger.reportErr(errmsg); } -void CheckMemoryLeak::memleakError(const Token *tok, const std::string &varname) const +void CheckMemoryLeakImpl::memleakError(const Token *tok, const std::string &varname) const { reportErr(tok, Severity::error, "memleak", "$symbol:" + varname + "\nMemory leak: $symbol", CWE(401U)); } -void CheckMemoryLeak::memleakUponReallocFailureError(const Token *tok, const std::string &reallocfunction, const std::string &varname) const +void CheckMemoryLeakImpl::memleakUponReallocFailureError(const Token *tok, const std::string &reallocfunction, const std::string &varname) const { reportErr(tok, Severity::error, "memleakOnRealloc", "$symbol:" + varname + "\nCommon " + reallocfunction + " mistake: \'$symbol\' nulled but not freed upon failure", CWE(401U)); } -void CheckMemoryLeak::resourceLeakError(const Token *tok, const std::string &varname) const +void CheckMemoryLeakImpl::resourceLeakError(const Token *tok, const std::string &varname) const { std::string errmsg("Resource leak"); if (!varname.empty()) @@ -323,17 +307,17 @@ void CheckMemoryLeak::resourceLeakError(const Token *tok, const std::string &var reportErr(tok, Severity::error, "resourceLeak", errmsg, CWE(775U)); } -void CheckMemoryLeak::deallocuseError(const Token *tok, const std::string &varname) const +void CheckMemoryLeakImpl::deallocuseError(const Token *tok, const std::string &varname) const { reportErr(tok, Severity::error, "deallocuse", "$symbol:" + varname + "\nDereferencing '$symbol' after it is deallocated / released", CWE(416U)); } -void CheckMemoryLeak::mismatchAllocDealloc(const std::list &callstack, const std::string &varname) const +void CheckMemoryLeakImpl::mismatchAllocDealloc(const std::list &callstack, const std::string &varname) const { reportErr(callstack, Severity::error, "mismatchAllocDealloc", "$symbol:" + varname + "\nMismatching allocation and deallocation: $symbol", CWE(762U)); } -CheckMemoryLeak::AllocType CheckMemoryLeak::functionReturnType(const Function* func, std::list *callstack) const +CheckMemoryLeakImpl::AllocType CheckMemoryLeakImpl::functionReturnType(const Function* func, std::list *callstack) const { if (!func || !func->hasBody() || !func->functionScope) return No; @@ -431,7 +415,7 @@ static bool ifvar(const Token *tok, nonneg int varid, const std::string &comp, c // a = malloc(10); a = realloc(a, 100); //--------------------------------------------------------------------------- -void CheckMemoryLeakInFunction::checkReallocUsage() +void CheckMemoryLeakInFunctionImpl::checkReallocUsage() { logChecker("CheckMemoryLeakInFunction::checkReallocUsage"); @@ -453,8 +437,8 @@ void CheckMemoryLeakInFunction::checkReallocUsage() const Token *const reallocTok = parTok->astOperand1(); if (!reallocTok) continue; - const Library::AllocFunc* f = mSettings->library.getReallocFuncInfo(reallocTok); - if (!(f && f->arg == -1 && mSettings->library.isnotnoreturn(reallocTok))) + const Library::AllocFunc* f = mSettings.library.getReallocFuncInfo(reallocTok); + if (!(f && f->arg == -1 && mSettings.library.isnotnoreturn(reallocTok))) continue; const AllocType allocType = getReallocationType(reallocTok, tok->varId()); @@ -477,8 +461,11 @@ void CheckMemoryLeakInFunction::checkReallocUsage() // Check that another copy of the pointer wasn't saved earlier in the function if (Token::findmatch(scope->bodyStart, "%name% = %varid% ;", tok, tok->varId()) || - Token::findmatch(scope->bodyStart, "[{};] %varid% = *| %var% .| %var%| [;=]", tok, tok->varId())) + Token::findmatch(scope->bodyStart, "[{};] %varid% = *| %var%", tok, tok->varId())) continue; + if (const Token* storeTok = Token::findmatch(scope->bodyStart, "[{};] %varid% = %name% (", tok, tok->varId())) + if (storeTok->tokAt(3) != reallocTok && !mSettings.library.getAllocFuncInfo(storeTok->tokAt(3))) + continue; // Check if the argument is known to be null, which means it is not a memory leak if (arg->hasKnownIntValue() && arg->getKnownIntValue() == 0) { @@ -499,17 +486,16 @@ void CheckMemoryLeakInFunction::checkReallocUsage() } } } -//--------------------------------------------------------------------------- -void CheckMemoryLeakInFunction::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) +void CheckMemoryLeakInFunction::runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) { - CheckMemoryLeakInFunction checkMemoryLeak(&tokenizer, &tokenizer.getSettings(), errorLogger); + CheckMemoryLeakInFunctionImpl checkMemoryLeak(&tokenizer, tokenizer.getSettings(), errorLogger); checkMemoryLeak.checkReallocUsage(); } -void CheckMemoryLeakInFunction::getErrorMessages(ErrorLogger *e, const Settings *settings) const +void CheckMemoryLeakInFunction::getErrorMessages(ErrorLogger& e, const Settings &settings) const { - CheckMemoryLeakInFunction c(nullptr, settings, e); + CheckMemoryLeakInFunctionImpl c(nullptr, settings, e); c.memleakError(nullptr, "varname"); c.resourceLeakError(nullptr, "varname"); c.deallocuseError(nullptr, "varname"); @@ -522,8 +508,7 @@ void CheckMemoryLeakInFunction::getErrorMessages(ErrorLogger *e, const Settings // Checks for memory leaks in classes.. //--------------------------------------------------------------------------- - -void CheckMemoryLeakInClass::check() +void CheckMemoryLeakInClassImpl::check() { logChecker("CheckMemoryLeakInClass::check"); @@ -532,7 +517,7 @@ void CheckMemoryLeakInClass::check() // only check classes and structures for (const Scope * scope : symbolDatabase->classAndStructScopes) { for (const Variable &var : scope->varlist) { - if (!var.isStatic() && (var.isPointer() || var.isPointerArray())) { + if (!var.isStatic()) { // allocation but no deallocation of private variables in public function.. const Token *tok = var.typeStartToken(); // Either it is of standard type or a non-derived type @@ -548,15 +533,15 @@ void CheckMemoryLeakInClass::check() } -void CheckMemoryLeakInClass::variable(const Scope *scope, const Token *tokVarname) +void CheckMemoryLeakInClassImpl::variable(const Scope *scope, const Token *tokVarname) { const std::string& varname = tokVarname->str(); const int varid = tokVarname->varId(); const std::string& classname = scope->className; // Check if member variable has been allocated and deallocated.. - CheckMemoryLeak::AllocType memberAlloc = CheckMemoryLeak::No; - CheckMemoryLeak::AllocType memberDealloc = CheckMemoryLeak::No; + AllocType memberAlloc = AllocType::No; + AllocType memberDealloc = AllocType::No; bool allocInConstructor = false; bool deallocInDestructor = false; @@ -568,7 +553,7 @@ void CheckMemoryLeakInClass::variable(const Scope *scope, const Token *tokVarnam if (!func.hasBody()) { if (destructor && !func.isDefault()) { // implementation for destructor is not seen and not defaulted => assume it deallocates all variables properly deallocInDestructor = true; - memberDealloc = CheckMemoryLeak::Many; + memberDealloc = AllocType::Many; } continue; } @@ -601,14 +586,14 @@ void CheckMemoryLeakInClass::variable(const Scope *scope, const Token *tokVarnam allocTok = tok->astParent()->astParent()->astOperand2(); AllocType alloc = getAllocationType(allocTok, 0); - if (alloc != CheckMemoryLeak::No) { + if (alloc != AllocType::No) { if (constructor) allocInConstructor = true; - if (memberAlloc != No && memberAlloc != alloc) - alloc = CheckMemoryLeak::Many; + if (memberAlloc != AllocType::No && memberAlloc != alloc) + alloc = AllocType::Many; - if (alloc != CheckMemoryLeak::Many && memberDealloc != CheckMemoryLeak::No && memberDealloc != CheckMemoryLeak::Many && memberDealloc != alloc) { + if (alloc != AllocType::Many && memberDealloc != AllocType::No && memberDealloc != AllocType::Many && memberDealloc != alloc) { mismatchAllocDealloc({tok}, classname + "::" + varname); } @@ -624,24 +609,24 @@ void CheckMemoryLeakInClass::variable(const Scope *scope, const Token *tokVarnam // some usage in the destructor => assume it's related // to deallocation if (destructor && tok->str() == varname) - dealloc = CheckMemoryLeak::Many; - if (dealloc != CheckMemoryLeak::No) { + dealloc = AllocType::Many; + if (dealloc != AllocType::No) { if (destructor) deallocInDestructor = true; - if (dealloc != CheckMemoryLeak::Many && memberAlloc != CheckMemoryLeak::No && memberAlloc != Many && memberAlloc != dealloc) { + if (dealloc != AllocType::Many && memberAlloc != AllocType::No && memberAlloc != Many && memberAlloc != dealloc) { mismatchAllocDealloc({tok}, classname + "::" + varname); } // several types of allocation/deallocation? - if (memberDealloc != CheckMemoryLeak::No && memberDealloc != dealloc) - dealloc = CheckMemoryLeak::Many; + if (memberDealloc != AllocType::No && memberDealloc != dealloc) + dealloc = AllocType::Many; memberDealloc = dealloc; } // Function call .. possible deallocation - else if (Token::Match(tok->previous(), "[{};] %name% (") && !tok->isKeyword() && !mSettings->library.isLeakIgnore(tok->str())) { + else if (Token::Match(tok->previous(), "[{};] %name% (") && !tok->isKeyword() && !mSettings.library.isLeakIgnore(tok->str())) { return; } } @@ -650,14 +635,14 @@ void CheckMemoryLeakInClass::variable(const Scope *scope, const Token *tokVarnam if (allocInConstructor && !deallocInDestructor) { unsafeClassError(tokVarname, classname, classname + "::" + varname /*, memberAlloc*/); - } else if (memberAlloc != CheckMemoryLeak::No && memberDealloc == CheckMemoryLeak::No) { + } else if (memberAlloc != AllocType::No && memberDealloc == AllocType::No) { unsafeClassError(tokVarname, classname, classname + "::" + varname /*, memberAlloc*/); } } -void CheckMemoryLeakInClass::unsafeClassError(const Token *tok, const std::string &classname, const std::string &varname) +void CheckMemoryLeakInClassImpl::unsafeClassError(const Token *tok, const std::string &classname, const std::string &varname) { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("unsafeClassCanLeak")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("unsafeClassCanLeak")) return; reportError(tok, Severity::style, "unsafeClassCanLeak", @@ -668,12 +653,12 @@ void CheckMemoryLeakInClass::unsafeClassError(const Token *tok, const std::strin } -void CheckMemoryLeakInClass::checkPublicFunctions(const Scope *scope, const Token *classtok) +void CheckMemoryLeakInClassImpl::checkPublicFunctions(const Scope *scope, const Token *classtok) { // Check that public functions deallocate the pointers that they allocate. // There is no checking how these functions are used and therefore it // isn't established if there is real leaks or not. - if (!mSettings->severity.isEnabled(Severity::warning)) + if (!mSettings.severity.isEnabled(Severity::warning)) return; const int varid = classtok->varId(); @@ -685,44 +670,43 @@ void CheckMemoryLeakInClass::checkPublicFunctions(const Scope *scope, const Toke func.access == AccessControl::Public && func.hasBody()) { const Token *tok2 = func.functionScope->bodyStart->next(); if (Token::Match(tok2, "%varid% =", varid)) { - const CheckMemoryLeak::AllocType alloc = getAllocationType(tok2->tokAt(2), varid); - if (alloc != CheckMemoryLeak::No) + const AllocType alloc = getAllocationType(tok2->tokAt(2), varid); + if (alloc != AllocType::No) publicAllocationError(tok2, tok2->str()); } else if (Token::Match(tok2, "%type% :: %varid% =", varid) && tok2->str() == scope->className) { - const CheckMemoryLeak::AllocType alloc = getAllocationType(tok2->tokAt(4), varid); - if (alloc != CheckMemoryLeak::No) + const AllocType alloc = getAllocationType(tok2->tokAt(4), varid); + if (alloc != AllocType::No) publicAllocationError(tok2, tok2->strAt(2)); } } } } -void CheckMemoryLeakInClass::publicAllocationError(const Token *tok, const std::string &varname) +void CheckMemoryLeakInClassImpl::publicAllocationError(const Token *tok, const std::string &varname) { reportError(tok, Severity::warning, "publicAllocationError", "$symbol:" + varname + "\nPossible leak in public function. The pointer '$symbol' is not deallocated before it is allocated.", CWE398, Certainty::normal); } -void CheckMemoryLeakInClass::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) +void CheckMemoryLeakInClass::runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) { if (!tokenizer.isCPP()) return; - CheckMemoryLeakInClass checkMemoryLeak(&tokenizer, &tokenizer.getSettings(), errorLogger); + CheckMemoryLeakInClassImpl checkMemoryLeak(&tokenizer, tokenizer.getSettings(), errorLogger); checkMemoryLeak.check(); } -void CheckMemoryLeakInClass::getErrorMessages(ErrorLogger *e, const Settings *settings) const +void CheckMemoryLeakInClass::getErrorMessages(ErrorLogger& e, const Settings &settings) const { - CheckMemoryLeakInClass c(nullptr, settings, e); + CheckMemoryLeakInClassImpl c(nullptr, settings, e); c.publicAllocationError(nullptr, "varname"); c.unsafeClassError(nullptr, "class", "class::varname"); } - -void CheckMemoryLeakStructMember::check() +void CheckMemoryLeakStructMemberImpl::check() { - if (mSettings->clang) + if (mSettings.clang) return; logChecker("CheckMemoryLeakStructMember::check"); @@ -739,7 +723,7 @@ void CheckMemoryLeakStructMember::check() } } -bool CheckMemoryLeakStructMember::isMalloc(const Variable *variable) const +bool CheckMemoryLeakStructMemberImpl::isMalloc(const Variable *variable) const { if (!variable) return false; @@ -752,7 +736,7 @@ bool CheckMemoryLeakStructMember::isMalloc(const Variable *variable) const const Token* tok3 = tok2->tokAt(1)->astOperand2(); while (tok3 && tok3->isCast()) tok3 = tok3->astOperand2() ? tok3->astOperand2() : tok3->astOperand1(); - if ((tok3 && Token::Match(tok3->tokAt(-1), "%name% (") && mSettings->library.getAllocFuncInfo(tok3->tokAt(-1))) || + if ((tok3 && Token::Match(tok3->tokAt(-1), "%name% (") && mSettings.library.getAllocFuncInfo(tok3->tokAt(-1))) || (Token::simpleMatch(tok3, "new") && tok3->isCpp())) { alloc = true; } @@ -761,7 +745,7 @@ bool CheckMemoryLeakStructMember::isMalloc(const Variable *variable) const return alloc; } -void CheckMemoryLeakStructMember::checkStructVariable(const Variable* const variable) const +void CheckMemoryLeakStructMemberImpl::checkStructVariable(const Variable* const variable) const { if (!variable) return; @@ -780,14 +764,14 @@ void CheckMemoryLeakStructMember::checkStructVariable(const Variable* const vari auto deallocInFunction = [this](const Token* tok, int structid) -> bool { // Calling non-function / function that doesn't deallocate? - if (tok->isKeyword() || mSettings->library.isLeakIgnore(tok->str())) + if (tok->isKeyword() || mSettings.library.isLeakIgnore(tok->str())) return false; // Check if the struct is used.. bool deallocated = false; const Token* const end = tok->linkAt(1); for (const Token* tok2 = tok; tok2 != end; tok2 = tok2->next()) { - if (Token::Match(tok2, "[(,] &| %varid% [,)]", structid)) { + if (Token::Match(tok2, "%varid%", structid)) { /** @todo check if the function deallocates the memory */ deallocated = true; break; @@ -902,7 +886,7 @@ void CheckMemoryLeakStructMember::checkStructVariable(const Variable* const vari } // Deallocating the struct.. - else if (Token::Match(tok3, "%name% ( %varid% )", structid) && mSettings->library.getDeallocFuncInfo(tok3)) { + else if (Token::Match(tok3, "%name% ( %varid% )", structid) && mSettings.library.getDeallocFuncInfo(tok3)) { if (indentlevel2 == 0) memoryLeak(tok3, variable->name() + "." + tok2->strAt(2), allocType); break; @@ -936,7 +920,7 @@ void CheckMemoryLeakStructMember::checkStructVariable(const Variable* const vari --indentlevel4; if (indentlevel4 == 0) break; - } else if (Token::Match(tok4, "%name% ( %var% . %varid% )", structmemberid) && mSettings->library.getDeallocFuncInfo(tok4)) { + } else if (Token::Match(tok4, "%name% ( %var% . %varid% )", structmemberid) && mSettings.library.getDeallocFuncInfo(tok4)) { break; } } @@ -979,17 +963,19 @@ void CheckMemoryLeakStructMember::checkStructVariable(const Variable* const vari } } -void CheckMemoryLeakStructMember::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) +void CheckMemoryLeakStructMember::runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) { - CheckMemoryLeakStructMember checkMemoryLeak(&tokenizer, &tokenizer.getSettings(), errorLogger); + CheckMemoryLeakStructMemberImpl checkMemoryLeak(&tokenizer, tokenizer.getSettings(), errorLogger); checkMemoryLeak.check(); } -void CheckMemoryLeakStructMember::getErrorMessages(ErrorLogger * /*errorLogger*/, const Settings * /*settings*/) const -{} - +void CheckMemoryLeakStructMember::getErrorMessages(ErrorLogger& errorLogger, const Settings & settings) const +{ + (void)&errorLogger; + (void)settings; +} -void CheckMemoryLeakNoVar::check() +void CheckMemoryLeakNoVarImpl::check() { logChecker("CheckMemoryLeakNoVar::check"); @@ -1015,7 +1001,7 @@ void CheckMemoryLeakNoVar::check() // Checks if an input argument to a function is the return value of an allocation function // like malloc(), and the function does not release it. //--------------------------------------------------------------------------- -void CheckMemoryLeakNoVar::checkForUnreleasedInputArgument(const Scope *scope) +void CheckMemoryLeakNoVarImpl::checkForUnreleasedInputArgument(const Scope *scope) { // parse the executable scope until tok is reached... for (const Token *tok = scope->bodyStart; tok != scope->bodyEnd; tok = tok->next()) { @@ -1027,10 +1013,8 @@ void CheckMemoryLeakNoVar::checkForUnreleasedInputArgument(const Scope *scope) const Token* tok2 = tok->next()->astParent(); while (tok2 && (tok2->isCast() || Token::Match(tok2, "?|:"))) tok2 = tok2->astParent(); - if (Token::Match(tok2, "%assign%")) // TODO: check if function returns allocated resource - continue; - if (Token::simpleMatch(tok->astTop(), "return")) - continue; + const bool hasAssign = Token::Match(tok2, "%assign%"); + const bool hasReturn = Token::simpleMatch(tok->astTop(), "return"); const std::string& functionName = tok->str(); if ((tok->isCpp() && functionName == "delete") || @@ -1039,9 +1023,15 @@ void CheckMemoryLeakNoVar::checkForUnreleasedInputArgument(const Scope *scope) if (Token::simpleMatch(tok->next()->astParent(), "(")) // passed to another function continue; - if (!tok->isKeyword() && !tok->function() && !mSettings->library.isLeakIgnore(functionName)) + if (!tok->isKeyword() && !tok->function() && !mSettings.library.isLeakIgnore(functionName)) continue; + if ((hasAssign || hasReturn) && !tok->function()) { + const std::string& ret = mSettings.library.returnValueType(tok); + if (ret.empty() || endsWith(ret, "*")) + continue; + } + const std::vector args = getArguments(tok); int argnr = -1; for (const Token* arg : args) { @@ -1059,8 +1049,11 @@ void CheckMemoryLeakNoVar::checkForUnreleasedInputArgument(const Scope *scope) if (alloc == New || alloc == NewArray) { const Token* typeTok = arg->next(); bool bail = !typeTok->isStandardType() && - !mSettings->library.detectContainerOrIterator(typeTok) && - !mSettings->library.podtype(typeTok->expressionString()); + (!typeTok->valueType() || + (typeTok->valueType()->type < ValueType::Type::SMART_POINTER && + typeTok->valueType()->type != ValueType::Type::POD)) && + !mSettings.library.detectContainerOrIterator(typeTok) && + !mSettings.library.podtype(typeTok->expressionString()); if (bail && typeTok->type() && typeTok->type()->classScope && typeTok->type()->classScope->numConstructors == 0 && typeTok->type()->classScope->getDestructor() == nullptr) { @@ -1075,8 +1068,8 @@ void CheckMemoryLeakNoVar::checkForUnreleasedInputArgument(const Scope *scope) const Variable* argvar = tok->function()->getArgumentVar(argnr); if (!argvar || !argvar->valueType()) continue; - const size_t argSize = argvar->valueType()->getSizeOf(*mSettings, ValueType::Accuracy::ExactOrZero, ValueType::SizeOf::Pointer); - if (argSize == 0 || argSize >= mSettings->platform.sizeof_pointer) + const size_t argSize = argvar->valueType()->getSizeOf(mSettings, ValueType::Accuracy::ExactOrZero, ValueType::SizeOf::Pointer); + if (argSize == 0 || argSize >= mSettings.platform.sizeof_pointer) continue; } functionCallLeak(arg, arg->str(), functionName); @@ -1088,7 +1081,7 @@ void CheckMemoryLeakNoVar::checkForUnreleasedInputArgument(const Scope *scope) //--------------------------------------------------------------------------- // Checks if a call to an allocation function like malloc() is made and its return value is not assigned. //--------------------------------------------------------------------------- -void CheckMemoryLeakNoVar::checkForUnusedReturnValue(const Scope *scope) +void CheckMemoryLeakNoVarImpl::checkForUnusedReturnValue(const Scope *scope) { for (const Token *tok = scope->bodyStart; tok != scope->bodyEnd; tok = tok->next()) { const bool isNew = tok->isCpp() && tok->str() == "new"; @@ -1121,7 +1114,7 @@ void CheckMemoryLeakNoVar::checkForUnusedReturnValue(const Scope *scope) bool warn = true; if (isNew) { const Token* typeTok = tok->next(); - warn = typeTok && (typeTok->isStandardType() || mSettings->library.detectContainer(typeTok)); + warn = typeTok && (typeTok->isStandardType() || mSettings.library.detectContainer(typeTok)); } if (!parent && warn) { @@ -1155,13 +1148,13 @@ void CheckMemoryLeakNoVar::checkForUnusedReturnValue(const Scope *scope) // f(shared_ptr(new int(42)), g()); // } //--------------------------------------------------------------------------- -void CheckMemoryLeakNoVar::checkForUnsafeArgAlloc(const Scope *scope) +void CheckMemoryLeakNoVarImpl::checkForUnsafeArgAlloc(const Scope *scope) { // This test only applies to C++ source if (!mTokenizer->isCPP()) return; - if (!mSettings->isPremiumEnabled("leakUnsafeArgAlloc") && (!mSettings->certainty.isEnabled(Certainty::inconclusive) || !mSettings->severity.isEnabled(Severity::warning))) + if (!mSettings.isPremiumEnabled("leakUnsafeArgAlloc") && (!mSettings.certainty.isEnabled(Certainty::inconclusive) || !mSettings.severity.isEnabled(Severity::warning))) return; logChecker("CheckMemoryLeakNoVar::checkForUnsafeArgAlloc"); @@ -1205,17 +1198,17 @@ void CheckMemoryLeakNoVar::checkForUnsafeArgAlloc(const Scope *scope) } } -void CheckMemoryLeakNoVar::functionCallLeak(const Token *loc, const std::string &alloc, const std::string &functionCall) +void CheckMemoryLeakNoVarImpl::functionCallLeak(const Token *loc, const std::string &alloc, const std::string &functionCall) { reportError(loc, Severity::error, "leakNoVarFunctionCall", "Allocation with " + alloc + ", " + functionCall + " doesn't release it.", CWE772, Certainty::normal); } -void CheckMemoryLeakNoVar::returnValueNotUsedError(const Token *tok, const std::string &alloc) +void CheckMemoryLeakNoVarImpl::returnValueNotUsedError(const Token *tok, const std::string &alloc) { reportError(tok, Severity::error, "leakReturnValNotUsed", "$symbol:" + alloc + "\nReturn value of allocation function '$symbol' is not stored.", CWE771, Certainty::normal); } -void CheckMemoryLeakNoVar::unsafeArgAllocError(const Token *tok, const std::string &funcName, const std::string &ptrType, const std::string& objType) +void CheckMemoryLeakNoVarImpl::unsafeArgAllocError(const Token *tok, const std::string &funcName, const std::string &ptrType, const std::string& objType) { const std::string factoryFunc = ptrType == "shared_ptr" ? "make_shared" : "make_unique"; reportError(tok, Severity::warning, "leakUnsafeArgAlloc", @@ -1225,15 +1218,15 @@ void CheckMemoryLeakNoVar::unsafeArgAllocError(const Token *tok, const std::stri Certainty::inconclusive); // Inconclusive because funcName may never throw } -void CheckMemoryLeakNoVar::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) +void CheckMemoryLeakNoVar::runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) { - CheckMemoryLeakNoVar checkMemoryLeak(&tokenizer, &tokenizer.getSettings(), errorLogger); + CheckMemoryLeakNoVarImpl checkMemoryLeak(&tokenizer, tokenizer.getSettings(), errorLogger); checkMemoryLeak.check(); } -void CheckMemoryLeakNoVar::getErrorMessages(ErrorLogger *e, const Settings *settings) const +void CheckMemoryLeakNoVar::getErrorMessages(ErrorLogger& e, const Settings &settings) const { - CheckMemoryLeakNoVar c(nullptr, settings, e); + CheckMemoryLeakNoVarImpl c(nullptr, settings, e); c.functionCallLeak(nullptr, "funcName", "funcName"); c.returnValueNotUsedError(nullptr, "funcName"); c.unsafeArgAllocError(nullptr, "funcName", "shared_ptr", "int"); diff --git a/lib/checkmemoryleak.h b/lib/checkmemoryleak.h index cf8cc4c11a2..fdb7748f9ed 100644 --- a/lib/checkmemoryleak.h +++ b/lib/checkmemoryleak.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -33,6 +33,7 @@ */ #include "check.h" +#include "checkimpl.h" #include "config.h" #include @@ -52,18 +53,95 @@ enum class Severity : std::uint8_t; /// @addtogroup Core /// @{ -/** @brief Base class for memory leaks checking */ -class CPPCHECKLIB CheckMemoryLeak { + +/** + * @brief %CheckMemoryLeakInFunction detects when a function variable is allocated but not deallocated properly. + * + * The checking is done by looking at each function variable separately. By repeating these 4 steps over and over: + * -# locate a function variable + * -# create a simple token list that describes the usage of the function variable. + * -# simplify the token list. + * -# finally, check if the simplified token list contain any leaks. + */ + +class CPPCHECKLIB CheckMemoryLeakInFunction : public Check { +public: + /** @brief This constructor is used when registering this class */ + CheckMemoryLeakInFunction() : Check("Memory leaks (function variables)") {} + +private: + void runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) override; + + /** Report all possible errors (for the --errorlist) */ + void getErrorMessages(ErrorLogger& e, const Settings &settings) const override; + + /** + * Get class information (--doc) + * @return Wiki formatted information about this class + */ + std::string classInfo() const override { + return "Is there any allocated memory when a function goes out of scope\n"; + } +}; + + +/** + * @brief %Check class variables, variables that are allocated in the constructor should be deallocated in the destructor + */ + +class CPPCHECKLIB CheckMemoryLeakInClass : public Check { +public: + CheckMemoryLeakInClass() : Check("Memory leaks (class variables)") {} + +private: + void runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) override; + + void getErrorMessages(ErrorLogger& e, const Settings &settings) const override; + + std::string classInfo() const override { + return "If the constructor allocate memory then the destructor must deallocate it.\n"; + } +}; + + + +/** @brief detect simple memory leaks for struct members */ + +class CPPCHECKLIB CheckMemoryLeakStructMember : public Check { +public: + CheckMemoryLeakStructMember() : Check("Memory leaks (struct members)") {} + +private: + void runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) override; + + void getErrorMessages(ErrorLogger& errorLogger, const Settings & settings) const override; + + std::string classInfo() const override { + return "Don't forget to deallocate struct members\n"; + } +}; + + + +/** @brief detect simple memory leaks (address not taken) */ + +class CPPCHECKLIB CheckMemoryLeakNoVar : public Check { +public: + CheckMemoryLeakNoVar() : Check("Memory leaks (address not taken)") {} + private: - /** For access to the tokens */ - const Tokenizer * const mTokenizer_; + void runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) override; - /** ErrorLogger used to report errors */ - ErrorLogger * const mErrorLogger_; + void getErrorMessages(ErrorLogger& e, const Settings &settings) const override; - /** Enabled standards */ - const Settings * const mSettings_; + std::string classInfo() const override { + return "Not taking the address to allocated memory\n"; + } +}; +/** @brief Base class for memory leaks checking */ +class CPPCHECKLIB CheckMemoryLeakImpl : public CheckImpl { +private: /** * Report error. Similar with the function Check::reportError * @param tok the token where the error occurs @@ -85,12 +163,12 @@ class CPPCHECKLIB CheckMemoryLeak { void reportErr(const std::list &callstack, Severity severity, const std::string &id, const std::string &msg, const CWE &cwe) const; public: - CheckMemoryLeak() = delete; - CheckMemoryLeak(const CheckMemoryLeak &) = delete; - CheckMemoryLeak& operator=(const CheckMemoryLeak &) = delete; + CheckMemoryLeakImpl() = delete; + CheckMemoryLeakImpl(const CheckMemoryLeakImpl &) = delete; + CheckMemoryLeakImpl& operator=(const CheckMemoryLeakImpl &) = delete; - CheckMemoryLeak(const Tokenizer *t, ErrorLogger *e, const Settings *s) - : mTokenizer_(t), mErrorLogger_(e), mSettings_(s) {} + CheckMemoryLeakImpl(const Tokenizer *t, const Settings &s, ErrorLogger &e) + : CheckImpl(t, s, e) {} /** @brief What type of allocation are used.. the "Many" means that several types of allocation and deallocation are used */ enum AllocType : std::uint8_t { No, Malloc, New, NewArray, File, Fd, Pipe, OtherMem, OtherRes, Many }; @@ -165,43 +243,16 @@ class CPPCHECKLIB CheckMemoryLeak { * -# finally, check if the simplified token list contain any leaks. */ -class CPPCHECKLIB CheckMemoryLeakInFunction : public Check, public CheckMemoryLeak { - friend class TestMemleakInFunction; - +class CPPCHECKLIB CheckMemoryLeakInFunctionImpl : public CheckMemoryLeakImpl { public: - /** @brief This constructor is used when registering this class */ - CheckMemoryLeakInFunction() : Check(myName()), CheckMemoryLeak(nullptr, nullptr, nullptr) {} - -private: /** @brief This constructor is used when running checks */ - CheckMemoryLeakInFunction(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) - : Check(myName(), tokenizer, settings, errorLogger), CheckMemoryLeak(tokenizer, errorLogger, settings) {} - - void runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) override; + CheckMemoryLeakInFunctionImpl(const Tokenizer *tokenizer, const Settings &settings, ErrorLogger &errorLogger) + : CheckMemoryLeakImpl(tokenizer, settings, errorLogger) {} /** * Checking for a memory leak caused by improper realloc usage. */ void checkReallocUsage(); - - /** Report all possible errors (for the --errorlist) */ - void getErrorMessages(ErrorLogger *e, const Settings *settings) const override; - - /** - * Get name of class (--doc) - * @return name of class - */ - static std::string myName() { - return "Memory leaks (function variables)"; - } - - /** - * Get class information (--doc) - * @return Wiki formatted information about this class - */ - std::string classInfo() const override { - return "Is there any allocated memory when a function goes out of scope\n"; - } }; @@ -210,17 +261,10 @@ class CPPCHECKLIB CheckMemoryLeakInFunction : public Check, public CheckMemoryLe * @brief %Check class variables, variables that are allocated in the constructor should be deallocated in the destructor */ -class CPPCHECKLIB CheckMemoryLeakInClass : public Check, private CheckMemoryLeak { - friend class TestMemleakInClass; - +class CPPCHECKLIB CheckMemoryLeakInClassImpl : private CheckMemoryLeakImpl { public: - CheckMemoryLeakInClass() : Check(myName()), CheckMemoryLeak(nullptr, nullptr, nullptr) {} - -private: - CheckMemoryLeakInClass(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) - : Check(myName(), tokenizer, settings, errorLogger), CheckMemoryLeak(tokenizer, errorLogger, settings) {} - - void runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) override; + CheckMemoryLeakInClassImpl(const Tokenizer *tokenizer, const Settings &settings, ErrorLogger &errorLogger) + : CheckMemoryLeakImpl(tokenizer, settings, errorLogger) {} void check(); @@ -231,33 +275,17 @@ class CPPCHECKLIB CheckMemoryLeakInClass : public Check, private CheckMemoryLeak void publicAllocationError(const Token *tok, const std::string &varname); void unsafeClassError(const Token *tok, const std::string &classname, const std::string &varname); - - void getErrorMessages(ErrorLogger *e, const Settings *settings) const override; - - static std::string myName() { - return "Memory leaks (class variables)"; - } - - std::string classInfo() const override { - return "If the constructor allocate memory then the destructor must deallocate it.\n"; - } }; /** @brief detect simple memory leaks for struct members */ -class CPPCHECKLIB CheckMemoryLeakStructMember : public Check, private CheckMemoryLeak { - friend class TestMemleakStructMember; - +class CPPCHECKLIB CheckMemoryLeakStructMemberImpl : private CheckMemoryLeakImpl { public: - CheckMemoryLeakStructMember() : Check(myName()), CheckMemoryLeak(nullptr, nullptr, nullptr) {} - -private: - CheckMemoryLeakStructMember(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) - : Check(myName(), tokenizer, settings, errorLogger), CheckMemoryLeak(tokenizer, errorLogger, settings) {} - void runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) override; + CheckMemoryLeakStructMemberImpl(const Tokenizer *tokenizer, const Settings &settings, ErrorLogger &errorLogger) + : CheckMemoryLeakImpl(tokenizer, settings, errorLogger) {} void check(); @@ -265,33 +293,16 @@ class CPPCHECKLIB CheckMemoryLeakStructMember : public Check, private CheckMemor bool isMalloc(const Variable *variable) const; void checkStructVariable(const Variable* variable) const; - - void getErrorMessages(ErrorLogger * errorLogger, const Settings * settings) const override; - - static std::string myName() { - return "Memory leaks (struct members)"; - } - - std::string classInfo() const override { - return "Don't forget to deallocate struct members\n"; - } }; /** @brief detect simple memory leaks (address not taken) */ -class CPPCHECKLIB CheckMemoryLeakNoVar : public Check, private CheckMemoryLeak { - friend class TestMemleakNoVar; - +class CPPCHECKLIB CheckMemoryLeakNoVarImpl : private CheckMemoryLeakImpl { public: - CheckMemoryLeakNoVar() : Check(myName()), CheckMemoryLeak(nullptr, nullptr, nullptr) {} - -private: - CheckMemoryLeakNoVar(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) - : Check(myName(), tokenizer, settings, errorLogger), CheckMemoryLeak(tokenizer, errorLogger, settings) {} - - void runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) override; + CheckMemoryLeakNoVarImpl(const Tokenizer *tokenizer, const Settings &settings, ErrorLogger &errorLogger) + : CheckMemoryLeakImpl(tokenizer, settings, errorLogger) {} void check(); @@ -317,16 +328,6 @@ class CPPCHECKLIB CheckMemoryLeakNoVar : public Check, private CheckMemoryLeak { void functionCallLeak(const Token *loc, const std::string &alloc, const std::string &functionCall); void returnValueNotUsedError(const Token* tok, const std::string &alloc); void unsafeArgAllocError(const Token *tok, const std::string &funcName, const std::string &ptrType, const std::string &objType); - - void getErrorMessages(ErrorLogger *e, const Settings *settings) const override; - - static std::string myName() { - return "Memory leaks (address not taken)"; - } - - std::string classInfo() const override { - return "Not taking the address to allocated memory\n"; - } }; /// @} //--------------------------------------------------------------------------- diff --git a/lib/checknullpointer.cpp b/lib/checknullpointer.cpp index 02c5cd8c812..5c2e64e9a6c 100644 --- a/lib/checknullpointer.cpp +++ b/lib/checknullpointer.cpp @@ -47,26 +47,21 @@ static const CWE CWE_NULL_POINTER_DEREFERENCE(476U); static const CWE CWE_INCORRECT_CALCULATION(682U); -// Register this check class (by creating a static instance of it) -namespace { - CheckNullPointer instance; -} - //--------------------------------------------------------------------------- -static bool checkNullpointerFunctionCallPlausibility(const Function* func, unsigned int arg) +static bool checkNullpointerFunctionCallPlausibility(const Function* func, size_t arg) { return !func || (func->argCount() >= arg && func->getArgumentVar(arg - 1) && func->getArgumentVar(arg - 1)->isPointer()); } -std::list CheckNullPointer::parseFunctionCall(const Token &tok, const Library &library, bool checkNullArg) +std::list CheckNullPointerImpl::parseFunctionCall(const Token &tok, const Library &library, bool checkNullArg) { if (Token::Match(&tok, "%name% ( )") || !tok.tokAt(2)) return {}; const std::vector args = getArguments(&tok); std::list var; - for (int argnr = 1; argnr <= args.size(); ++argnr) { + for (size_t argnr = 1; argnr <= args.size(); ++argnr) { const Token *param = args[argnr - 1]; if ((!checkNullArg || library.isnullargbad(&tok, argnr)) && checkNullpointerFunctionCallPlausibility(tok.function(), argnr)) var.push_back(param); @@ -133,21 +128,12 @@ namespace { }; } -/** - * Is there a pointer dereference? Everything that should result in - * a nullpointer dereference error message will result in a true - * return value. If it's unknown if the pointer is dereferenced false - * is returned. - * @param tok token for the pointer - * @param unknown it is not known if there is a pointer dereference (could be reported as a debug message) - * @return true => there is a dereference - */ -bool CheckNullPointer::isPointerDeRef(const Token *tok, bool &unknown) const +bool CheckNullPointerImpl::isPointerDeRef(const Token *tok, bool &unknown) const { - return isPointerDeRef(tok, unknown, *mSettings); + return isPointerDeRef(tok, unknown, mSettings.library); } -bool CheckNullPointer::isPointerDeRef(const Token *tok, bool &unknown, const Settings &settings, bool checkNullArg) +bool CheckNullPointerImpl::isPointerDeRef(const Token *tok, bool &unknown, const Library &library, bool checkNullArg) { unknown = false; @@ -160,7 +146,7 @@ bool CheckNullPointer::isPointerDeRef(const Token *tok, bool &unknown, const Set ftok = ftok->previous(); } if (ftok && ftok->previous()) { - const std::list varlist = parseFunctionCall(*ftok->previous(), settings.library, checkNullArg); + const std::list varlist = CheckNullPointerImpl::parseFunctionCall(*ftok->previous(), library, checkNullArg); if (std::find(varlist.cbegin(), varlist.cend(), tok) != varlist.cend()) { return true; } @@ -175,7 +161,7 @@ bool CheckNullPointer::isPointerDeRef(const Token *tok, bool &unknown, const Set return false; const bool addressOf = parent->astParent() && parent->astParent()->str() == "&"; if (parent->str() == "." && astIsRHS(tok)) - return isPointerDeRef(parent, unknown, settings); + return isPointerDeRef(parent, unknown, library); const bool firstOperand = parent->astOperand1() == tok; parent = astParentSkipParens(tok); if (!parent) @@ -187,6 +173,8 @@ bool CheckNullPointer::isPointerDeRef(const Token *tok, bool &unknown, const Set // declaration of function pointer if (tok->variable() && tok->variable()->nameToken() == tok) return false; + if ((tok->valueType() && tok->valueType()->pointer == 0) && !astIsIterator(tok) && !astIsSmartPointer(tok)) + return false; if (!addressOf) return true; } @@ -276,9 +264,9 @@ static bool isNullablePointer(const Token* tok) return false; } -void CheckNullPointer::nullPointerByDeRefAndCheck() +void CheckNullPointerImpl::nullPointerByDeRefAndCheck() { - const bool printInconclusive = (mSettings->certainty.isEnabled(Certainty::inconclusive)); + const bool printInconclusive = (mSettings.certainty.isEnabled(Certainty::inconclusive)); const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); for (const Scope * scope : symbolDatabase->functionScopes) { @@ -304,13 +292,13 @@ void CheckNullPointer::nullPointerByDeRefAndCheck() return true; }; const Token* const start = (scope->function && scope->function->isConstructor()) ? scope->function->token : scope->bodyStart; // Check initialization list - std::vector tokens = findTokensSkipDeadAndUnevaluatedCode(mSettings->library, start, scope->bodyEnd, pred); + std::vector tokens = findTokensSkipDeadAndUnevaluatedCode(mSettings.library, start, scope->bodyEnd, pred); for (const Token *tok : tokens) { const ValueFlow::Value *value = tok->getValue(0); // Pointer dereference. bool unknown = false; - if (!isPointerDeRef(tok, unknown)) { + if (!CheckNullPointerImpl::isPointerDeRef(tok, unknown, mSettings.library)) { if (unknown) nullPointerError(tok, tok->expressionString(), value, true); continue; @@ -321,7 +309,7 @@ void CheckNullPointer::nullPointerByDeRefAndCheck() } } -void CheckNullPointer::nullPointer() +void CheckNullPointerImpl::nullPointer() { logChecker("CheckNullPointer::nullPointer"); nullPointerByDeRefAndCheck(); @@ -335,7 +323,7 @@ namespace { } /** Dereferencing null constant (simplified token list) */ -void CheckNullPointer::nullConstantDereference() +void CheckNullPointerImpl::nullConstantDereference() { logChecker("CheckNullPointer::nullConstantDereference"); @@ -369,7 +357,7 @@ void CheckNullPointer::nullConstantDereference() if (var && !var->isPointer() && !var->isArray() && var->isStlStringType()) nullPointerError(tok); } else { // function call - const std::list var = parseFunctionCall(*tok, mSettings->library); + const std::list var = parseFunctionCall(*tok, mSettings.library); // is one of the var items a NULL pointer? for (const Token *vartok : var) { @@ -382,13 +370,13 @@ void CheckNullPointer::nullConstantDereference() else if (Token::Match(tok->previous(), "::|. %name% (")) { const std::vector &args = getArguments(tok); - for (int argnr = 0; argnr < args.size(); ++argnr) { + for (size_t argnr = 0; argnr < args.size(); ++argnr) { const Token *argtok = args[argnr]; if (!argtok->hasKnownIntValue()) continue; if (argtok->getKnownIntValue() != 0) continue; - if (mSettings->library.isnullargbad(tok, argnr+1)) + if (mSettings.library.isnullargbad(tok, argnr+1)) nullPointerError(argtok); } } @@ -428,14 +416,14 @@ void CheckNullPointer::nullConstantDereference() } } -void CheckNullPointer::nullPointerError(const Token *tok) +void CheckNullPointerImpl::nullPointerError(const Token *tok) { ValueFlow::Value v(0); v.setKnown(); nullPointerError(tok, "", &v, false); } -void CheckNullPointer::nullPointerError(const Token *tok, const std::string &varname, const ValueFlow::Value *value, bool inconclusive) +void CheckNullPointerImpl::nullPointerError(const Token *tok, const std::string &varname, const ValueFlow::Value *value, bool inconclusive) { const std::string errmsgcond("$symbol:" + varname + '\n' + ValueFlow::eitherTheConditionIsRedundant(value ? value->condition : nullptr) + " or there is possible null pointer dereference: $symbol."); const std::string errmsgdefarg("$symbol:" + varname + "\nPossible null pointer dereference if the default parameter value is used: $symbol"); @@ -456,7 +444,7 @@ void CheckNullPointer::nullPointerError(const Token *tok, const std::string &var return; } - if (!mSettings->isEnabled(value, inconclusive) && !mSettings->isPremiumEnabled("nullPointer")) + if (!mSettings.isEnabled(value, inconclusive) && !mSettings.isPremiumEnabled("nullPointer")) return; ErrorPath errorPath = getErrorPath(tok, value, "Null pointer dereference"); @@ -489,7 +477,7 @@ void CheckNullPointer::nullPointerError(const Token *tok, const std::string &var } } -void CheckNullPointer::arithmetic() +void CheckNullPointerImpl::arithmetic() { logChecker("CheckNullPointer::arithmetic"); const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); @@ -515,9 +503,9 @@ void CheckNullPointer::arithmetic() const ValueFlow::Value* value = pointerOperand->getValue(0); if (!value) continue; - if (!mSettings->certainty.isEnabled(Certainty::inconclusive) && value->isInconclusive()) + if (!mSettings.certainty.isEnabled(Certainty::inconclusive) && value->isInconclusive()) continue; - if (value->condition && !mSettings->severity.isEnabled(Severity::warning)) + if (value->condition && !mSettings.severity.isEnabled(Severity::warning)) continue; if (value->condition) redundantConditionWarning(tok, value, value->condition, value->isInconclusive()); @@ -536,7 +524,7 @@ static std::string arithmeticTypeString(const Token *tok) return "arithmetic"; } -void CheckNullPointer::pointerArithmeticError(const Token* tok, const ValueFlow::Value *value, bool inconclusive) +void CheckNullPointerImpl::pointerArithmeticError(const Token* tok, const ValueFlow::Value *value, bool inconclusive) { // cppcheck-suppress shadowFunction - TODO: fix this std::string arithmetic = arithmeticTypeString(tok); @@ -566,7 +554,7 @@ void CheckNullPointer::pointerArithmeticError(const Token* tok, const ValueFlow: inconclusive ? Certainty::inconclusive : Certainty::normal); } -void CheckNullPointer::redundantConditionWarning(const Token* tok, const ValueFlow::Value *value, const Token *condition, bool inconclusive) +void CheckNullPointerImpl::redundantConditionWarning(const Token* tok, const ValueFlow::Value *value, const Token *condition, bool inconclusive) { // cppcheck-suppress shadowFunction - TODO: fix this std::string arithmetic = arithmeticTypeString(tok); @@ -590,7 +578,7 @@ static bool isUnsafeUsage(const Settings &settings, const Token *vartok, CTU::Fi { (void)value; bool unknown = false; - return CheckNullPointer::isPointerDeRef(vartok, unknown, settings); + return CheckNullPointerImpl::isPointerDeRef(vartok, unknown, settings.library); } // a Clang-built executable will crash when using the anonymous MyFileInfo later on - so put it in a unique namespace for now @@ -616,9 +604,11 @@ namespace }; } -Check::FileInfo *CheckNullPointer::getFileInfo(const Tokenizer &tokenizer, const Settings &settings, const std::string& /*currentConfig*/) const +const Check::FileInfo *CheckNullPointer::getFileInfo(const Tokenizer &tokenizer, const Settings &settings, const std::string& currentConfig) const { - const std::list &unsafeUsage = CTU::getUnsafeUsage(tokenizer, settings, isUnsafeUsage); + (void)currentConfig; + + const std::list &unsafeUsage = CTU::getUnsafeUsage(tokenizer, settings, ::isUnsafeUsage); if (unsafeUsage.empty()) return nullptr; @@ -627,24 +617,24 @@ Check::FileInfo *CheckNullPointer::getFileInfo(const Tokenizer &tokenizer, const return fileInfo; } -Check::FileInfo * CheckNullPointer::loadFileInfoFromXml(const tinyxml2::XMLElement *xmlElement) const +const Check::FileInfo * CheckNullPointer::loadFileInfoFromXml(const tinyxml2::XMLElement *xmlElement, const std::string& file0) const { const std::list &unsafeUsage = CTU::loadUnsafeUsageListFromXml(xmlElement); if (unsafeUsage.empty()) return nullptr; - auto *fileInfo = new MyFileInfo; + auto *fileInfo = new MyFileInfo(file0); fileInfo->unsafeUsage = unsafeUsage; return fileInfo; } -bool CheckNullPointer::analyseWholeProgram(const CTU::FileInfo &ctu, const std::list &fileInfo, const Settings& settings, ErrorLogger &errorLogger) +bool CheckNullPointer::analyseWholeProgram(const CTU::FileInfo &ctu, const std::list &fileInfo, const Settings& settings, ErrorLogger &errorLogger) { (void)settings; - CheckNullPointer dummy(nullptr, &settings, &errorLogger); + CheckNullPointerImpl dummy(nullptr, settings, errorLogger); dummy. - logChecker("CheckNullPointer::analyseWholeProgram"); // unusedfunctions + logChecker("CheckNullPointer::analyseWholeProgram"); if (fileInfo.empty()) return false; @@ -702,17 +692,17 @@ bool CheckNullPointer::analyseWholeProgram(const CTU::FileInfo &ctu, const std:: return foundErrors; } -void CheckNullPointer::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) +void CheckNullPointer::runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) { - CheckNullPointer checkNullPointer(&tokenizer, &tokenizer.getSettings(), errorLogger); + CheckNullPointerImpl checkNullPointer(&tokenizer, tokenizer.getSettings(), errorLogger); checkNullPointer.nullPointer(); checkNullPointer.arithmetic(); checkNullPointer.nullConstantDereference(); } -void CheckNullPointer::getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const +void CheckNullPointer::getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const { - CheckNullPointer c(nullptr, settings, errorLogger); + CheckNullPointerImpl c(nullptr, settings, errorLogger); c.nullPointerError(nullptr, "pointer", nullptr, false); c.pointerArithmeticError(nullptr, nullptr, false); // TODO: nullPointerArithmeticOutOfMemory diff --git a/lib/checknullpointer.h b/lib/checknullpointer.h index c02feae303e..ed6a617d81a 100644 --- a/lib/checknullpointer.h +++ b/lib/checknullpointer.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,6 +23,7 @@ //--------------------------------------------------------------------------- #include "check.h" +#include "checkimpl.h" #include "config.h" #include @@ -47,11 +48,38 @@ namespace ValueFlow /** @brief check for null pointer dereferencing */ class CPPCHECKLIB CheckNullPointer : public Check { - friend class TestNullPointer; - public: /** @brief This constructor is used when registering the CheckNullPointer */ - CheckNullPointer() : Check(myName()) {} + CheckNullPointer() : Check("Null pointer") {} + +private: + /** @brief Run checks against the normal token list */ + void runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) override; + + /** @brief Parse current TU and extract file info */ + const Check::FileInfo *getFileInfo(const Tokenizer &tokenizer, const Settings &settings, const std::string& currentConfig) const override; + + const Check::FileInfo * loadFileInfoFromXml(const tinyxml2::XMLElement *xmlElement, const std::string& file0) const override; + + /** @brief Analyse all file infos for all TU */ + bool analyseWholeProgram(const CTU::FileInfo &ctu, const std::list &fileInfo, const Settings& settings, ErrorLogger &errorLogger) override; + + /** Get error messages. Used by --errorlist */ + void getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const override; + + /** class info in WIKI format. Used by --doc */ + std::string classInfo() const override { + return "Null pointers\n" + "- null pointer dereferencing\n" + "- undefined null pointer arithmetic\n"; + } +}; + +class CPPCHECKLIB CheckNullPointerImpl : public CheckImpl { +public: + /** @brief This constructor is used when running checks. */ + CheckNullPointerImpl(const Tokenizer *tokenizer, const Settings &settings, ErrorLogger &errorLogger) + : CheckImpl(tokenizer, settings, errorLogger) {} /** * Is there a pointer dereference? Everything that should result in @@ -64,9 +92,8 @@ class CPPCHECKLIB CheckNullPointer : public Check { */ bool isPointerDeRef(const Token *tok, bool &unknown) const; - static bool isPointerDeRef(const Token *tok, bool &unknown, const Settings &settings, bool checkNullArg = true); + static bool isPointerDeRef(const Token *tok, bool &unknown, const Library &library, bool checkNullArg = true); -private: /** * @brief parse a function call and extract information about variable usage * @param tok first token @@ -76,13 +103,6 @@ class CPPCHECKLIB CheckNullPointer : public Check { */ static std::list parseFunctionCall(const Token &tok, const Library &library, bool checkNullArg = true); - /** @brief This constructor is used when running checks. */ - CheckNullPointer(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) - : Check(myName(), tokenizer, settings, errorLogger) {} - - /** @brief Run checks against the normal token list */ - void runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) override; - /** @brief possible null pointer dereference */ void nullPointer(); @@ -92,29 +112,6 @@ class CPPCHECKLIB CheckNullPointer : public Check { void nullPointerError(const Token *tok); void nullPointerError(const Token *tok, const std::string &varname, const ValueFlow::Value* value, bool inconclusive); - /** @brief Parse current TU and extract file info */ - Check::FileInfo *getFileInfo(const Tokenizer &tokenizer, const Settings &settings, const std::string& /*currentConfig*/) const override; - - Check::FileInfo * loadFileInfoFromXml(const tinyxml2::XMLElement *xmlElement) const override; - - /** @brief Analyse all file infos for all TU */ - bool analyseWholeProgram(const CTU::FileInfo &ctu, const std::list &fileInfo, const Settings& settings, ErrorLogger &errorLogger) override; - - /** Get error messages. Used by --errorlist */ - void getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const override; - - /** Name of check */ - static std::string myName() { - return "Null pointer"; - } - - /** class info in WIKI format. Used by --doc */ - std::string classInfo() const override { - return "Null pointers\n" - "- null pointer dereferencing\n" - "- undefined null pointer arithmetic\n"; - } - /** * @brief Does one part of the check for nullPointer(). * Dereferencing a pointer and then checking if it's NULL.. diff --git a/lib/checkother.cpp b/lib/checkother.cpp index 13ad6d29577..79697a2e6d7 100644 --- a/lib/checkother.cpp +++ b/lib/checkother.cpp @@ -24,6 +24,7 @@ #include "fwdanalysis.h" #include "library.h" #include "mathlib.h" +#include "refthunk.h" #include "platform.h" #include "settings.h" #include "standards.h" @@ -49,11 +50,6 @@ //--------------------------------------------------------------------------- -// Register this check class (by creating a static instance of it) -namespace { - CheckOther instance; -} - static const CWE CWE128(128U); // Wrap-around Error static const CWE CWE131(131U); // Incorrect Calculation of Buffer Size static const CWE CWE197(197U); // Numeric Truncation Error @@ -85,9 +81,9 @@ static const CWE CWE783(783U); // Operator Precedence Logic Error // - http://www.cplusplus.com/reference/cstdio/getchar/ // - http://www.cplusplus.com/reference/cstdio/ungetc/ ... //---------------------------------------------------------------------------------- -void CheckOther::checkCastIntToCharAndBack() +void CheckOtherImpl::checkCastIntToCharAndBack() { - if (!mSettings->severity.isEnabled(Severity::warning)) + if (!mSettings.severity.isEnabled(Severity::warning)) return; logChecker("CheckOther::checkCastIntToCharAndBack"); // warning @@ -135,7 +131,7 @@ void CheckOther::checkCastIntToCharAndBack() } } -void CheckOther::checkCastIntToCharAndBackError(const Token *tok, const std::string &strFunctionName) +void CheckOtherImpl::checkCastIntToCharAndBackError(const Token *tok, const std::string &strFunctionName) { reportError( tok, @@ -155,9 +151,9 @@ void CheckOther::checkCastIntToCharAndBackError(const Token *tok, const std::str //--------------------------------------------------------------------------- // Clarify calculation precedence for ternary operators. //--------------------------------------------------------------------------- -void CheckOther::clarifyCalculation() +void CheckOtherImpl::clarifyCalculation() { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("clarifyCalculation")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("clarifyCalculation")) return; logChecker("CheckOther::clarifyCalculation"); // style @@ -204,7 +200,7 @@ void CheckOther::clarifyCalculation() } } -void CheckOther::clarifyCalculationError(const Token *tok, const std::string &op) +void CheckOtherImpl::clarifyCalculationError(const Token *tok, const std::string &op) { // suspicious calculation const std::string calc("'a" + op + "b?c:d'"); @@ -226,9 +222,9 @@ void CheckOther::clarifyCalculationError(const Token *tok, const std::string &op //--------------------------------------------------------------------------- // Clarify (meaningless) statements like *foo++; with parentheses. //--------------------------------------------------------------------------- -void CheckOther::clarifyStatement() +void CheckOtherImpl::clarifyStatement() { - if (!mSettings->severity.isEnabled(Severity::warning)) + if (!mSettings.severity.isEnabled(Severity::warning)) return; logChecker("CheckOther::clarifyStatement"); // warning @@ -252,7 +248,7 @@ void CheckOther::clarifyStatement() } } -void CheckOther::clarifyStatementError(const Token *tok) +void CheckOtherImpl::clarifyStatementError(const Token *tok) { reportError(tok, Severity::warning, "clarifyStatement", "In expression like '*A++' the result of '*' is unused. Did you intend to write '(*A)++;'?\n" "A statement like '*A++;' might not do what you intended. Postfix 'operator++' is executed before 'operator*'. " @@ -262,9 +258,9 @@ void CheckOther::clarifyStatementError(const Token *tok) //--------------------------------------------------------------------------- // Check for suspicious occurrences of 'if(); {}'. //--------------------------------------------------------------------------- -void CheckOther::checkSuspiciousSemicolon() +void CheckOtherImpl::checkSuspiciousSemicolon() { - if (!mSettings->certainty.isEnabled(Certainty::inconclusive) || !mSettings->severity.isEnabled(Severity::warning)) + if (!mSettings.certainty.isEnabled(Certainty::inconclusive) || !mSettings.severity.isEnabled(Severity::warning)) return; const SymbolDatabase* const symbolDatabase = mTokenizer->getSymbolDatabase(); @@ -286,7 +282,7 @@ void CheckOther::checkSuspiciousSemicolon() } } -void CheckOther::suspiciousSemicolonError(const Token* tok) +void CheckOtherImpl::suspiciousSemicolonError(const Token* tok) { reportError(tok, Severity::warning, "suspiciousSemicolon", "Suspicious use of ; at the end of '" + (tok ? tok->str() : std::string()) + "' statement.", CWE398, Certainty::normal); @@ -334,13 +330,13 @@ static bool isDangerousTypeConversion(const Token* const tok) //--------------------------------------------------------------------------- // For C++ code, warn if C-style casts are used on pointer types //--------------------------------------------------------------------------- -void CheckOther::warningOldStylePointerCast() +void CheckOtherImpl::warningOldStylePointerCast() { // Only valid on C++ code if (!mTokenizer->isCPP()) return; - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("cstyleCast")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("cstyleCast")) return; logChecker("CheckOther::warningOldStylePointerCast"); // style,c++ @@ -399,7 +395,7 @@ void CheckOther::warningOldStylePointerCast() } } -void CheckOther::cstyleCastError(const Token *tok, bool isPtr) +void CheckOtherImpl::cstyleCastError(const Token *tok, bool isPtr) { const std::string type = isPtr ? "pointer" : "reference"; reportError(tok, Severity::style, "cstyleCast", @@ -410,12 +406,12 @@ void CheckOther::cstyleCastError(const Token *tok, bool isPtr) "which kind of cast is expected.", CWE398, Certainty::normal); } -void CheckOther::warningDangerousTypeCast() +void CheckOtherImpl::warningDangerousTypeCast() { // Only valid on C++ code if (!mTokenizer->isCPP()) return; - if (!mSettings->severity.isEnabled(Severity::warning) && !mSettings->isPremiumEnabled("dangerousTypeCast")) + if (!mSettings.severity.isEnabled(Severity::warning) && !mSettings.isPremiumEnabled("dangerousTypeCast")) return; logChecker("CheckOther::warningDangerousTypeCast"); // warning,c++ @@ -438,7 +434,7 @@ void CheckOther::warningDangerousTypeCast() } } -void CheckOther::dangerousTypeCastError(const Token *tok, bool isPtr) +void CheckOtherImpl::dangerousTypeCastError(const Token *tok, bool isPtr) { //const std::string type = isPtr ? "pointer" : "reference"; (void)isPtr; @@ -447,9 +443,9 @@ void CheckOther::dangerousTypeCastError(const Token *tok, bool isPtr) CWE398, Certainty::normal); } -void CheckOther::warningIntToPointerCast() +void CheckOtherImpl::warningIntToPointerCast() { - if (!mSettings->severity.isEnabled(Severity::portability) && !mSettings->isPremiumEnabled("cstyleCast")) + if (!mSettings.severity.isEnabled(Severity::portability) && !mSettings.isPremiumEnabled("cstyleCast")) return; logChecker("CheckOther::warningIntToPointerCast"); // portability @@ -469,6 +465,8 @@ void CheckOther::warningIntToPointerCast() format = "decimal"; else if (MathLib::isOct(from->str())) format = "octal"; + else if (MathLib::isBin(from->str())) + format = "binary"; else continue; intToPointerCastError(tok, format); @@ -476,16 +474,16 @@ void CheckOther::warningIntToPointerCast() } } -void CheckOther::intToPointerCastError(const Token *tok, const std::string& format) +void CheckOtherImpl::intToPointerCastError(const Token *tok, const std::string& format) { reportError(tok, Severity::portability, "intToPointerCast", "Casting non-zero " + format + " integer literal to pointer.", CWE398, Certainty::normal); } -void CheckOther::suspiciousFloatingPointCast() +void CheckOtherImpl::suspiciousFloatingPointCast() { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("suspiciousFloatingPointCast")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("suspiciousFloatingPointCast")) return; logChecker("CheckOther::suspiciousFloatingPointCast"); // style @@ -533,7 +531,7 @@ void CheckOther::suspiciousFloatingPointCast() } } -void CheckOther::suspiciousFloatingPointCastError(const Token* tok) +void CheckOtherImpl::suspiciousFloatingPointCastError(const Token* tok) { reportError(tok, Severity::style, "suspiciousFloatingPointCast", "Floating-point cast causes loss of precision.\n" @@ -544,14 +542,14 @@ void CheckOther::suspiciousFloatingPointCastError(const Token* tok) // float* f; double* d = (double*)f; <-- Pointer cast to a type with an incompatible binary data representation //--------------------------------------------------------------------------- -void CheckOther::invalidPointerCast() +void CheckOtherImpl::invalidPointerCast() { - if (!mSettings->severity.isEnabled(Severity::portability)) + if (!mSettings.severity.isEnabled(Severity::portability)) return; logChecker("CheckOther::invalidPointerCast"); // portability - const bool printInconclusive = mSettings->certainty.isEnabled(Certainty::inconclusive); + const bool printInconclusive = mSettings.certainty.isEnabled(Certainty::inconclusive); const SymbolDatabase* const symbolDatabase = mTokenizer->getSymbolDatabase(); for (const Scope * scope : symbolDatabase->functionScopes) { for (const Token* tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) { @@ -584,7 +582,7 @@ void CheckOther::invalidPointerCast() } -void CheckOther::invalidPointerCastError(const Token* tok, const std::string& from, const std::string& to, bool inconclusive, bool toIsInt) +void CheckOtherImpl::invalidPointerCastError(const Token* tok, const std::string& from, const std::string& to, bool inconclusive, bool toIsInt) { if (toIsInt) { // If we cast something to int*, this can be useful to play with its binary data representation reportError(tok, Severity::portability, "invalidPointerCast", "Casting from " + from + " to " + to + " is not portable due to different binary data representations on different platforms.", CWE704, inconclusive ? Certainty::inconclusive : Certainty::normal); @@ -597,11 +595,19 @@ void CheckOther::invalidPointerCastError(const Token* tok, const std::string& fr // Detect redundant assignments: x = 0; x = 4; //--------------------------------------------------------------------------- -void CheckOther::checkRedundantAssignment() +static bool isAssignmentOrInit(const Token* tok) { + if (tok->astParent() || !tok->astOperand1()) + return false; + if (tok->isAssignmentOp() || tok->tokType() == Token::eIncDecOp) + return true; + return Token::Match(tok, "[{(]") && tok->astOperand1()->variable() && tok->astOperand1() == tok->astOperand1()->variable()->nameToken(); +} + +void CheckOtherImpl::checkRedundantAssignment() { - if (!mSettings->severity.isEnabled(Severity::style) && - !mSettings->isPremiumEnabled("redundantAssignment") && - !mSettings->isPremiumEnabled("redundantAssignInSwitch")) + if (!mSettings.severity.isEnabled(Severity::style) && + !mSettings.isPremiumEnabled("redundantAssignment") && + !mSettings.isPremiumEnabled("redundantAssignInSwitch")) return; logChecker("CheckOther::checkRedundantAssignment"); // style @@ -617,75 +623,80 @@ void CheckOther::checkRedundantAssignment() if (Token::simpleMatch(tok, "try {")) // todo: check try blocks tok = tok->linkAt(1); - if ((tok->isAssignmentOp() || tok->tokType() == Token::eIncDecOp) && tok->astOperand1()) { - if (tok->astParent()) - continue; - - // Do not warn about redundant initialization when rhs is trivial - // TODO : do not simplify the variable declarations - bool isInitialization = false; - if (Token::Match(tok->tokAt(-2), "; %var% =") && tok->tokAt(-2)->isSplittedVarDeclEq()) { - isInitialization = true; - bool trivial = true; - visitAstNodes(tok->astOperand2(), - [&](const Token *rhs) { - if (Token::simpleMatch(rhs, "{ 0 }")) - return ChildrenToVisit::none; - if (Token::Match(rhs, "%num%|%name%") && !rhs->varId()) - return ChildrenToVisit::none; - if (Token::Match(rhs, ":: %name%") && rhs->hasKnownIntValue()) - return ChildrenToVisit::none; - if (rhs->isCast()) - return ChildrenToVisit::op2; - trivial = false; - return ChildrenToVisit::done; - }); - if (trivial) - continue; - } + if (!isAssignmentOrInit(tok)) + continue; - const Token* rhs = tok->astOperand2(); - // Do not warn about assignment with 0 / NULL - if ((rhs && MathLib::isNullValue(rhs->str())) || isNullOperand(rhs)) + // Do not warn about redundant initialization when rhs is trivial + // TODO : do not simplify the variable declarations + bool isInitialization = false; + if ((Token::Match(tok->tokAt(-2), "; %var% =") && tok->tokAt(-2)->isSplittedVarDeclEq()) || Token::Match(tok, "[{(]")) { + isInitialization = true; + bool trivial = true; + visitAstNodes(tok->astOperand2(), + [&](const Token *rhs) { + if (Token::simpleMatch(rhs, "{ 0 }")) + return ChildrenToVisit::none; + if (Token::Match(rhs, "%num%|%name%") && !rhs->varId()) + return ChildrenToVisit::none; + if (Token::Match(rhs, ":: %name%") && rhs->hasKnownIntValue()) + return ChildrenToVisit::none; + if (rhs->isCast()) + return rhs->astOperand2() ? ChildrenToVisit::op2 : ChildrenToVisit::op1; + trivial = false; + return ChildrenToVisit::done; + }); + if (trivial) continue; + } - if (tok->astOperand1()->variable() && tok->astOperand1()->variable()->isReference()) - // todo: check references - continue; + const Token* rhs = tok->astOperand2(); + // Do not warn about assignment with 0 / NULL + if ((rhs && MathLib::isNullValue(rhs->str())) || isNullOperand(rhs)) + continue; - if (tok->astOperand1()->variable() && tok->astOperand1()->variable()->isStatic()) - // todo: check static variables - continue; + if (tok->astOperand1()->variable() && tok->astOperand1()->variable()->isReference()) + // todo: check references + continue; - bool inconclusive = false; - if (tok->isCpp() && tok->astOperand1()->valueType()) { - // If there is a custom assignment operator => this is inconclusive - if (tok->astOperand1()->valueType()->typeScope) { - const std::string op = "operator" + tok->str(); - const std::list& fList = tok->astOperand1()->valueType()->typeScope->functionList; - inconclusive = std::any_of(fList.cbegin(), fList.cend(), [&](const Function& f) { - return f.name() == op; - }); - } - // assigning a smart pointer has side effects - if (tok->astOperand1()->valueType()->type == ValueType::SMART_POINTER) - break; + if (tok->astOperand1()->variable() && tok->astOperand1()->variable()->isStatic()) + // todo: check static variables + continue; + + bool inconclusive = false; + if (tok->isCpp() && tok->astOperand1()->valueType()) { + // If there is a custom assignment operator => this is inconclusive + if (tok->astOperand1()->valueType()->typeScope) { + const std::string op = "operator" + tok->str(); + const std::list& fList = tok->astOperand1()->valueType()->typeScope->functionList; + inconclusive = std::any_of(fList.cbegin(), fList.cend(), [&](const Function& f) { + return f.name() == op; + }); } - if (inconclusive && !mSettings->certainty.isEnabled(Certainty::inconclusive)) - continue; + // assigning a smart pointer has side effects + if (tok->astOperand1()->valueType()->type == ValueType::SMART_POINTER) + break; + } + if (inconclusive && !mSettings.certainty.isEnabled(Certainty::inconclusive)) + continue; - FwdAnalysis fwdAnalysis(*mSettings); - if (fwdAnalysis.hasOperand(tok->astOperand2(), tok->astOperand1())) - continue; + FwdAnalysis fwdAnalysis(mSettings); + if (fwdAnalysis.hasOperand(tok->astOperand2(), tok->astOperand1())) + continue; - // Is there a redundant assignment? - const Token *start; - if (tok->isAssignmentOp()) - start = tok->astOperand2(); - else - start = tok->findExpressionStartEndTokens().second->next(); + // Is there a redundant assignment? + const Token *start; + if (tok->isAssignmentOp()) + start = tok->astOperand2(); + else + start = tok->findExpressionStartEndTokens().second->next(); - const Token * tokenToCheck = tok->astOperand1(); + std::vector tokensToCheck{ tok->astOperand1() }; + if (Token::simpleMatch(tok->astOperand1(), "[") && Token::simpleMatch(tok->astOperand1()->astOperand1(), "auto")) { // structured binding + tokensToCheck = astFlatten(tok->astOperand1()->astOperand2(), ","); + isInitialization = true; + } + + for (const Token* tokenToCheck : tokensToCheck) { // Check if we are working with union for (const Token* tempToken = tokenToCheck; Token::simpleMatch(tempToken, ".");) { @@ -701,17 +712,22 @@ void CheckOther::checkRedundantAssignment() } // Get next assignment.. - const Token *nextAssign = fwdAnalysis.reassign(tokenToCheck, start, scope->bodyEnd); + const Token* nextAssign = fwdAnalysis.reassign(tokenToCheck, start, scope->bodyEnd); // extra check for union - if (nextAssign && tokenToCheck != tok->astOperand1()) + const bool isUnion = tokenToCheck != tok->astOperand1() && !isStructuredBindingVariable(tokenToCheck->variable()); + if (nextAssign && isUnion) { nextAssign = fwdAnalysis.reassign(tok->astOperand1(), start, scope->bodyEnd); + // reading another member of the same union in the rhs is a use through aliasing + if (nextAssign && fwdAnalysis.hasOperand(nextAssign->astOperand2(), tokenToCheck)) + nextAssign = nullptr; + } if (!nextAssign) continue; // there is redundant assignment. Is there a case between the assignments? bool hasCase = false; - for (const Token *tok2 = tok; tok2 != nextAssign; tok2 = tok2->next()) { + for (const Token* tok2 = tok; tok2 != nextAssign; tok2 = tok2->next()) { if (tok2->str() == "break" || tok2->str() == "return") break; if (tok2->str() == "case") { @@ -721,20 +737,21 @@ void CheckOther::checkRedundantAssignment() } // warn + const Token* exprTok = isUnion ? tok->astOperand1() : tokenToCheck; if (hasCase) - redundantAssignmentInSwitchError(tok, nextAssign, tok->astOperand1()->expressionString()); + redundantAssignmentInSwitchError(tok, nextAssign, exprTok->expressionString()); else if (isInitialization) - redundantInitializationError(tok, nextAssign, tok->astOperand1()->expressionString(), inconclusive); + redundantInitializationError(tok, nextAssign, exprTok->expressionString(), inconclusive); else { diag(nextAssign); - redundantAssignmentError(tok, nextAssign, tok->astOperand1()->expressionString(), inconclusive); + redundantAssignmentError(tok, nextAssign, exprTok->expressionString(), inconclusive); } } } } } -void CheckOther::redundantCopyError(const Token *tok1, const Token* tok2, const std::string& var) +void CheckOtherImpl::redundantCopyError(const Token *tok1, const Token* tok2, const std::string& var) { const std::list callstack = { tok1, tok2 }; reportError(callstack, Severity::performance, "redundantCopy", @@ -742,7 +759,7 @@ void CheckOther::redundantCopyError(const Token *tok1, const Token* tok2, const "Buffer '$symbol' is being written before its old content has been used.", CWE563, Certainty::normal); } -void CheckOther::redundantAssignmentError(const Token *tok1, const Token* tok2, const std::string& var, bool inconclusive) +void CheckOtherImpl::redundantAssignmentError(const Token *tok1, const Token* tok2, const std::string& var, bool inconclusive) { ErrorPath errorPath = { ErrorPathItem(tok1, var + " is assigned"), ErrorPathItem(tok2, var + " is overwritten") }; if (inconclusive) @@ -756,7 +773,7 @@ void CheckOther::redundantAssignmentError(const Token *tok1, const Token* tok2, "Variable '$symbol' is reassigned a value before the old one has been used.", CWE563, Certainty::normal); } -void CheckOther::redundantInitializationError(const Token *tok1, const Token* tok2, const std::string& var, bool inconclusive) +void CheckOtherImpl::redundantInitializationError(const Token *tok1, const Token* tok2, const std::string& var, bool inconclusive) { ErrorPath errorPath = { ErrorPathItem(tok1, var + " is initialized"), ErrorPathItem(tok2, var + " is overwritten") }; reportError(std::move(errorPath), Severity::style, "redundantInitialization", @@ -765,7 +782,7 @@ void CheckOther::redundantInitializationError(const Token *tok1, const Token* to inconclusive ? Certainty::inconclusive : Certainty::normal); } -void CheckOther::redundantAssignmentInSwitchError(const Token *tok1, const Token* tok2, const std::string &var) +void CheckOtherImpl::redundantAssignmentInSwitchError(const Token *tok1, const Token* tok2, const std::string &var) { ErrorPath errorPath = { ErrorPathItem(tok1, "$symbol is assigned"), ErrorPathItem(tok2, "$symbol is overwritten") }; reportError(std::move(errorPath), Severity::style, "redundantAssignInSwitch", @@ -773,7 +790,7 @@ void CheckOther::redundantAssignmentInSwitchError(const Token *tok1, const Token "Variable '$symbol' is reassigned a value before the old one has been used. 'break;' missing?", CWE563, Certainty::normal); } -void CheckOther::redundantAssignmentSameValueError(const Token *tok, const ValueFlow::Value* val, const std::string &var) +void CheckOtherImpl::redundantAssignmentSameValueError(const Token *tok, const ValueFlow::Value* val, const std::string &var) { auto errorPath = val->errorPath; errorPath.emplace_back(tok, ""); @@ -794,12 +811,12 @@ void CheckOther::redundantAssignmentSameValueError(const Token *tok, const Value //--------------------------------------------------------------------------- static inline bool isFunctionOrBreakPattern(const Token *tok) { - return Token::Match(tok, "%name% (") || Token::Match(tok, "break|continue|return|exit|goto|throw"); + return Token::Match(tok, "%name% (") || (tok->isKeyword() && Token::Match(tok, "break|continue|return|goto|throw")); } -void CheckOther::redundantBitwiseOperationInSwitchError() +void CheckOtherImpl::redundantBitwiseOperationInSwitchError() { - if (!mSettings->severity.isEnabled(Severity::warning)) + if (!mSettings.severity.isEnabled(Severity::warning)) return; logChecker("CheckOther::redundantBitwiseOperationInSwitch"); // warning @@ -914,7 +931,7 @@ void CheckOther::redundantBitwiseOperationInSwitchError() } } -void CheckOther::redundantBitwiseOperationInSwitchError(const Token *tok, const std::string &varname) +void CheckOtherImpl::redundantBitwiseOperationInSwitchError(const Token *tok, const std::string &varname) { reportError(tok, Severity::style, "redundantBitwiseOperationInSwitch", @@ -926,9 +943,9 @@ void CheckOther::redundantBitwiseOperationInSwitchError(const Token *tok, const //--------------------------------------------------------------------------- // Check for statements like case A||B: in switch() //--------------------------------------------------------------------------- -void CheckOther::checkSuspiciousCaseInSwitch() +void CheckOtherImpl::checkSuspiciousCaseInSwitch() { - if (!mSettings->certainty.isEnabled(Certainty::inconclusive) || !mSettings->severity.isEnabled(Severity::warning)) + if (!mSettings.certainty.isEnabled(Certainty::inconclusive) || !mSettings.severity.isEnabled(Severity::warning)) return; logChecker("CheckOther::checkSuspiciousCaseInSwitch"); // warning,inconclusive @@ -960,13 +977,73 @@ void CheckOther::checkSuspiciousCaseInSwitch() } } -void CheckOther::suspiciousCaseInSwitchError(const Token* tok, const std::string& operatorString) +void CheckOtherImpl::suspiciousCaseInSwitchError(const Token* tok, const std::string& operatorString) { reportError(tok, Severity::warning, "suspiciousCase", "Found suspicious case label in switch(). Operator '" + operatorString + "' probably doesn't work as intended.\n" "Using an operator like '" + operatorString + "' in a case label is suspicious. Did you intend to use a bitwise operator, multiple case labels or if/else instead?", CWE398, Certainty::inconclusive); } +void CheckOtherImpl::checkUnreachableSwitchCase() +{ + if (!mSettings.severity.isEnabled(Severity::style)) + return; + + logChecker("CheckOther::checkUnreachableSwitchCase"); // style + + const SymbolDatabase* symbolDatabase = mTokenizer->getSymbolDatabase(); + + for (const Scope& scope : symbolDatabase->scopeList) { + if (scope.type != ScopeType::eSwitch || !scope.bodyStart) + continue; + const Token* rpar = scope.bodyStart->previous(); + if (!Token::simpleMatch(rpar, ")")) + continue; + const Token* lpar = rpar->link(); + if (!lpar) + continue; + const Token* condition = lpar->astOperand2(); + if (!condition) + continue; + const ValueFlow::Value* switchValue = + condition->getKnownValue(ValueFlow::Value::ValueType::INT); + if (!switchValue) + continue; + + for (const Token* tok = scope.bodyStart->next(); + tok && tok != scope.bodyEnd; + tok = tok->next()) { + + // Do not inspect cases belonging to a nested switch. + if (Token::simpleMatch(tok, "{") && + tok->scope()->type == ScopeType::eSwitch) { + tok = tok->link(); + continue; + } + if (!Token::simpleMatch(tok, "case")) + continue; + const Token* caseExpression = tok->astOperand1(); + if (!caseExpression) + continue; + const ValueFlow::Value* caseValue = + caseExpression->getKnownValue(ValueFlow::Value::ValueType::INT); + if (!caseValue) + continue; + if (switchValue->intvalue == caseValue->intvalue) + continue; + unreachableSwitchCaseError(tok, caseExpression->expressionString(), MathLib::toString(switchValue->intvalue)); + } + } +} + +void CheckOtherImpl::unreachableSwitchCaseError(const Token* tok, const std::string& caseExpression, const std::string& switchValue) +{ + reportError(tok, Severity::style, "unreachableSwitchCase", + "Switch case '" + caseExpression + + "' can never be selected because the switch condition is known to be " + switchValue + ".", + CWE561, Certainty::normal); +} + static bool isNestedInSwitch(const Scope* scope) { while (scope) { @@ -1010,18 +1087,18 @@ static bool isVardeclInSwitch(const Token* tok) // Detect dead code, that follows such a statement. e.g.: // return(0); foo(); //--------------------------------------------------------------------------- -void CheckOther::checkUnreachableCode() +void CheckOtherImpl::checkUnreachableCode() { // misra-c-2012-2.1 // misra-c-2023-2.1 // misra-cpp-2008-0-1-1 // autosar - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("duplicateBreak") && !mSettings->isPremiumEnabled("unreachableCode")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("duplicateBreak") && !mSettings.isPremiumEnabled("unreachableCode")) return; logChecker("CheckOther::checkUnreachableCode"); // style - const bool printInconclusive = mSettings->certainty.isEnabled(Certainty::inconclusive); + const bool printInconclusive = mSettings.certainty.isEnabled(Certainty::inconclusive); const SymbolDatabase* symbolDatabase = mTokenizer->getSymbolDatabase(); for (const Scope * scope : symbolDatabase->functionScopes) { if (scope->hasInlineOrLambdaFunction(nullptr, /*onlyInline*/ true)) @@ -1048,7 +1125,7 @@ void CheckOther::checkUnreachableCode() } else if (Token::Match(tok, "goto %any% ;")) { secondBreak = tok->tokAt(3); labelName = tok->next(); - } else if (Token::Match(tok, "%name% (") && mSettings->library.isnoreturn(tok) && !Token::Match(tok->next()->astParent(), "?|:")) { + } else if (Token::Match(tok, "%name% (") && mSettings.library.isnoreturn(tok) && !Token::Match(tok->next()->astParent(), "?|:")) { if ((!tok->function() || (tok->function()->token != tok && tok->function()->tokenDef != tok)) && tok->linkAt(1)->strAt(1) != "{") secondBreak = tok->linkAt(1)->tokAt(2); if (Token::simpleMatch(secondBreak, "return")) { @@ -1130,7 +1207,7 @@ void CheckOther::checkUnreachableCode() } } -void CheckOther::duplicateBreakError(const Token *tok, bool inconclusive) +void CheckOtherImpl::duplicateBreakError(const Token *tok, bool inconclusive) { reportError(tok, Severity::style, "duplicateBreak", "Consecutive return, break, continue, goto or throw statements are unnecessary.\n" @@ -1138,10 +1215,10 @@ void CheckOther::duplicateBreakError(const Token *tok, bool inconclusive) "The second statement can never be executed, and so should be removed.", CWE561, inconclusive ? Certainty::inconclusive : Certainty::normal); } -void CheckOther::unreachableCodeError(const Token *tok, const Token* noreturn, bool inconclusive) +void CheckOtherImpl::unreachableCodeError(const Token *tok, const Token* noreturn, bool inconclusive) { std::string msg = "Statements following "; - if (noreturn && (noreturn->function() || mSettings->library.isnoreturn(noreturn))) + if (noreturn && (noreturn->function() || mSettings.library.isnoreturn(noreturn))) msg += "noreturn function '" + noreturn->str() + "()'"; else if (noreturn && noreturn->isKeyword()) msg += "'" + noreturn->str() + "'"; @@ -1152,7 +1229,7 @@ void CheckOther::unreachableCodeError(const Token *tok, const Token* noreturn, b msg, CWE561, inconclusive ? Certainty::inconclusive : Certainty::normal); } -void CheckOther::redundantContinueError(const Token *tok) +void CheckOtherImpl::redundantContinueError(const Token *tok) { reportError(tok, Severity::style, "redundantContinue", "'continue' is redundant since it is the last statement in a loop.", CWE561, Certainty::normal); @@ -1188,19 +1265,19 @@ static bool isSimpleExpr(const Token* tok, const Variable* var, const Settings& //--------------------------------------------------------------------------- // Check scope of variables.. //--------------------------------------------------------------------------- -void CheckOther::checkVariableScope() +void CheckOtherImpl::checkVariableScope() { - if (mSettings->clang) + if (mSettings.clang) return; - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("variableScope")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("variableScope")) return; const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); // In C it is common practice to declare local variables at the // start of functions. - if (mSettings->daca && mTokenizer->isC()) + if (mSettings.daca && mTokenizer->isC()) return; logChecker("CheckOther::checkVariableScope"); // style,notclang @@ -1249,7 +1326,7 @@ void CheckOther::checkVariableScope() bool isConstructor = false; if (Token::Match(tok, "; %varid% =", var->declarationId())) { // bailout for assignment tok = tok->tokAt(2)->astOperand2(); - if (!isSimpleExpr(tok, var, *mSettings)) + if (!isSimpleExpr(tok, var, mSettings)) continue; } else if (Token::Match(tok, "{|(")) { // bailout for constructor @@ -1258,11 +1335,11 @@ void CheckOther::checkVariableScope() bool bail = false; while (argTok) { if (Token::simpleMatch(argTok, ",")) { - if (!isSimpleExpr(argTok->astOperand2(), var, *mSettings)) { + if (!isSimpleExpr(argTok->astOperand2(), var, mSettings)) { bail = true; break; } - } else if (argTok->str() != "." && !isSimpleExpr(argTok, var, *mSettings)) { + } else if (argTok->str() != "." && !isSimpleExpr(argTok, var, mSettings)) { bail = true; break; } @@ -1292,7 +1369,7 @@ void CheckOther::checkVariableScope() tok = tok->link(); // parse else if blocks.. - } else if (Token::simpleMatch(tok, "else { if (") && tok->next()->isSimplifiedScope() && Token::simpleMatch(tok->linkAt(3), ") {")) { + } else if (Token::simpleMatch(tok, "else { if (") && tok->next()->isInsertedBrace() && Token::simpleMatch(tok->linkAt(3), ") {")) { tok = tok->next(); } else if (tok->varId() == var->declarationId() || tok->str() == "goto") { reduce = false; @@ -1340,7 +1417,7 @@ static bool isOnlyUsedInCurrentScope(const Variable* var, const Token *tok, cons return !Token::findmatch(tok->scope()->bodyEnd, "%varid%", var->scope()->bodyEnd, var->declarationId()); } -bool CheckOther::checkInnerScope(const Token *tok, const Variable* var, bool& used) const +bool CheckOtherImpl::checkInnerScope(const Token *tok, const Variable* var, bool& used) const { const Scope* scope = tok->next()->scope(); bool loopVariable = scope->isLoopScope(); @@ -1418,7 +1495,7 @@ bool CheckOther::checkInnerScope(const Token *tok, const Variable* var, bool& us if (scope->type == ScopeType::eSwitch) return false; // Used in outer switch scope - unsafe or impossible to reduce scope - if (scope->bodyStart && scope->bodyStart->isSimplifiedScope()) + if (scope->bodyStart && scope->bodyStart->isSimplifiedIfInitStmt()) return false; // simplified if/for/switch init statement } if (var->isArrayOrPointer()) { @@ -1427,7 +1504,7 @@ bool CheckOther::checkInnerScope(const Token *tok, const Variable* var, bool& us if (ftok->next()->astParent()) { // return value used? if (ftok->function() && Function::returnsPointer(ftok->function())) return false; - const std::string ret = mSettings->library.returnValueType(ftok); // assume that var is returned + const std::string ret = mSettings.library.returnValueType(ftok); // assume that var is returned if (!ret.empty() && ret.back() == '*') return false; } @@ -1442,7 +1519,7 @@ bool CheckOther::checkInnerScope(const Token *tok, const Variable* var, bool& us } } } - const auto yield = astContainerYield(tok, mSettings->library); + const auto yield = astContainerYield(tok, mSettings.library); if (yield == Library::Container::Yield::BUFFER || yield == Library::Container::Yield::BUFFER_NT) return false; } @@ -1451,7 +1528,7 @@ bool CheckOther::checkInnerScope(const Token *tok, const Variable* var, bool& us return true; } -void CheckOther::variableScopeError(const Token *tok, const std::string &varname) +void CheckOtherImpl::variableScopeError(const Token *tok, const std::string &varname) { reportError(tok, Severity::style, @@ -1478,10 +1555,10 @@ void CheckOther::variableScopeError(const Token *tok, const std::string &varname //--------------------------------------------------------------------------- // Comma in return statement: return a+1, b++;. (experimental) //--------------------------------------------------------------------------- -void CheckOther::checkCommaSeparatedReturn() +void CheckOtherImpl::checkCommaSeparatedReturn() { // TODO: This is experimental for now. See #5076 - if ((true) || !mSettings->severity.isEnabled(Severity::style)) // NOLINT(readability-simplify-boolean-expr,readability-redundant-parentheses) + if ((true) || !mSettings.severity.isEnabled(Severity::style)) // NOLINT(readability-simplify-boolean-expr,readability-redundant-parentheses) return; // logChecker @@ -1505,7 +1582,7 @@ void CheckOther::checkCommaSeparatedReturn() } } -void CheckOther::commaSeparatedReturnError(const Token *tok) +void CheckOtherImpl::commaSeparatedReturnError(const Token *tok) { reportError(tok, Severity::style, @@ -1543,9 +1620,9 @@ static bool isLargeContainer(const Variable* var, const Settings& settings) return arraySize > maxByValueSize; } -void CheckOther::checkPassByReference() +void CheckOtherImpl::checkPassByReference() { - if (!mSettings->severity.isEnabled(Severity::performance) || mTokenizer->isC()) + if (!mSettings.severity.isEnabled(Severity::performance) || mTokenizer->isC()) return; logChecker("CheckOther::checkPassByReference"); // performance,c++ @@ -1571,26 +1648,41 @@ void CheckOther::checkPassByReference() bool inconclusive = false; const bool isContainer = var->valueType() && var->valueType()->type == ValueType::Type::CONTAINER && var->valueType()->container && !var->valueType()->container->view; - if (isContainer && !isLargeContainer(var, *mSettings)) + if (isContainer && !isLargeContainer(var, mSettings)) continue; if (!isContainer) { if (var->type() && !var->type()->isEnumType()) { // Check if type is a struct or class. // Ensure that it is a large object. if (!var->type()->classScope) inconclusive = true; - else if (!var->valueType() || var->valueType()->getSizeOf(*mSettings, ValueType::Accuracy::LowerBound, ValueType::SizeOf::Pointer) <= 2 * mSettings->platform.sizeof_pointer) + else if (!var->valueType() || var->valueType()->getSizeOf(mSettings, ValueType::Accuracy::LowerBound, ValueType::SizeOf::Pointer) <= 2 * mSettings.platform.sizeof_pointer) continue; } else continue; } - if (inconclusive && !mSettings->certainty.isEnabled(Certainty::inconclusive)) + if (inconclusive && !mSettings.certainty.isEnabled(Certainty::inconclusive)) continue; if (var->isArray() && (!var->isStlType() || Token::simpleMatch(var->nameToken()->next(), "["))) continue; + if (var->isArgument()) { + const Token *tok = var->typeStartToken(); + for (; tok; tok = tok->next()) { + if (Token::simpleMatch(tok, "(")) { + tok = tok->link(); + continue; + } + if (Token::simpleMatch(tok, ")")) + break; + } + + if (Token::simpleMatch(tok, ") ;")) + continue; + } + const bool isConst = var->isConst(); if (isConst) { passedByValueError(var, inconclusive, isRangeBasedFor); @@ -1601,13 +1693,13 @@ void CheckOther::checkPassByReference() if (!isRangeBasedFor && (!var->scope() || var->scope()->function->isImplicitlyVirtual())) continue; - if (!isVariableChanged(var, *mSettings)) { + if (!isVariableChanged(var, mSettings)) { passedByValueError(var, inconclusive, isRangeBasedFor); } } } -void CheckOther::passedByValueError(const Variable* var, bool inconclusive, bool isRangeBasedFor) +void CheckOtherImpl::passedByValueError(const Variable* var, bool inconclusive, bool isRangeBasedFor) { std::string id = isRangeBasedFor ? "iterateByValue" : "passedByValue"; const std::string action = isRangeBasedFor ? "declared as": "passed by"; @@ -1672,9 +1764,9 @@ static bool isCastToVoid(const Variable* var) return false; } -void CheckOther::checkConstVariable() +void CheckOtherImpl::checkConstVariable() { - if ((!mSettings->severity.isEnabled(Severity::style) || mTokenizer->isC()) && !mSettings->isPremiumEnabled("constVariable")) + if ((!mSettings.severity.isEnabled(Severity::style) || mTokenizer->isC()) && !mSettings.isPremiumEnabled("constVariable") && !mSettings.isPremiumEnabled("constParameterReference")) return; logChecker("CheckOther::checkConstVariable"); // style,c++ @@ -1692,6 +1784,8 @@ void CheckOther::checkConstVariable() continue; if (var->isConst()) continue; + if (Tokenizer::isFunctionPointer(var->nameToken())) // reference to function + continue; const Scope* scope = var->scope(); if (!scope) continue; @@ -1722,7 +1816,7 @@ void CheckOther::checkConstVariable() continue; if (isCastToVoid(var)) continue; - if (isVariableChanged(var, *mSettings)) + if (isVariableChanged(var, mSettings)) continue; const bool hasFunction = function != nullptr; if (!hasFunction) { @@ -1734,18 +1828,31 @@ void CheckOther::checkConstVariable() if (function && (Function::returnsReference(function) || Function::returnsPointer(function)) && !Function::returnsConst(function)) { std::vector returns = Function::findReturns(function); if (std::any_of(returns.cbegin(), returns.cend(), [&](const Token* retTok) { - if (retTok->varId() == var->declarationId()) - return true; - while (retTok && retTok->isCast()) - retTok = retTok->astOperand2() ? retTok->astOperand2() : retTok->astOperand1(); - while (Token::simpleMatch(retTok, ".")) - retTok = retTok->astOperand2(); - if (Token::simpleMatch(retTok, "&")) - retTok = retTok->astOperand1(); - ValueFlow::Value ltVal = ValueFlow::getLifetimeObjValue(retTok); - if (ltVal.isLifetimeValue() && ltVal.tokvalue->varId() == var->declarationId()) - return true; - return ValueFlow::hasLifetimeToken(getParentLifetime(retTok), var->nameToken(), *mSettings); + bool result = false; + visitAstNodes(retTok, [&result, &var, this](const Token* tok) { + if (tok->varId() == var->declarationId()) { + result = true; + return ChildrenToVisit::done; + } + ValueFlow::Value ltVal = ValueFlow::getLifetimeObjValue(tok); + if ((ltVal.isLifetimeValue() && ltVal.tokvalue->varId() == var->declarationId()) || + ValueFlow::hasLifetimeToken(getParentLifetime(tok), var->nameToken(), mSettings)) { + result = true; + return ChildrenToVisit::done; + } + if (tok->isCast()) + return tok->astOperand2() ? ChildrenToVisit::op2 : ChildrenToVisit::op1; + if (tok->str() == ".") + return ChildrenToVisit::op2; + if (tok->str() == "&") + return ChildrenToVisit::op1; + if (tok->str() == "?") + return ChildrenToVisit::op2; + if (tok->str() == ":") + return ChildrenToVisit::op1_and_op2; + return ChildrenToVisit::none; + }); + return result; })) continue; } @@ -1754,9 +1861,13 @@ void CheckOther::checkConstVariable() //Is it the right side of an initialization of a non-const reference bool usedInAssignment = false; for (const Token* tok = var->nameToken(); tok != scope->bodyEnd && tok != nullptr; tok = tok->next()) { - if (Token::Match(tok, "& %var% = %varid%", var->declarationId())) { - const Variable* refvar = tok->next()->variable(); - if (refvar && !refvar->isConst() && refvar->nameToken() == tok->next()) { + if (Token::Match(tok, "%name% = %varid%", var->declarationId())) { + const Variable* refvar = tok->variable(); + if (tok->strAt(-1) == "&" && refvar && !refvar->isConst() && refvar->nameToken() == tok) { + usedInAssignment = true; + break; + } + if (!tok->valueType() || tok->valueType()->type == ValueType::Type::RECORD) { usedInAssignment = true; break; } @@ -1777,7 +1888,7 @@ void CheckOther::checkConstVariable() continue; } else if (const Token* ftok = getTokenArgumentFunction(tok, argn)) { bool inconclusive{}; - if (var->valueType() && !isVariableChangedByFunctionCall(ftok, var->valueType()->pointer, var->declarationId(), *mSettings, &inconclusive) && !inconclusive) + if (var->valueType() && !isVariableChangedByFunctionCall(ftok, var->valueType()->pointer, var->declarationId(), mSettings.library, &inconclusive) && !inconclusive) continue; } usedInAssignment = true; @@ -1861,13 +1972,13 @@ static const Function* getEnclosingFunction(const Variable* var) return scope ? scope->function : nullptr; } -void CheckOther::checkConstPointer() +void CheckOtherImpl::checkConstPointer() { - if (!mSettings->severity.isEnabled(Severity::style) && - !mSettings->isPremiumEnabled("constParameter") && - !mSettings->isPremiumEnabled("constParameterPointer") && - !mSettings->isPremiumEnabled("constParameterReference") && - !mSettings->isPremiumEnabled("constVariablePointer")) + if (!mSettings.severity.isEnabled(Severity::style) && + !mSettings.isPremiumEnabled("constParameter") && + !mSettings.isPremiumEnabled("constParameterPointer") && + !mSettings.isPremiumEnabled("constParameterReference") && + !mSettings.isPremiumEnabled("constVariablePointer")) return; logChecker("CheckOther::checkConstPointer"); // style @@ -1922,16 +2033,25 @@ void CheckOther::checkConstPointer() continue; if (deref != NONE) { const Token* gparent = parent->astParent(); - while (Token::simpleMatch(gparent, "[") && parent != gparent->astOperand2() && parent->str() == gparent->str()) + while (Token::simpleMatch(gparent, "[") && parent != gparent->astOperand2()) + gparent = gparent->astParent(); + while (Token::Match(gparent, "[?:]")) gparent = gparent->astParent(); if (deref == MEMBER) { if (!gparent) continue; - if (parent->astOperand2()) { - if (parent->astOperand2()->function() && parent->astOperand2()->function()->isConst()) + const Token* funcParent = parent; + while (Token::simpleMatch(funcParent->astParent(), ".")) + funcParent = funcParent->astParent(); + if (funcParent->astOperand2()) { + if (funcParent->astOperand2()->function() && funcParent->astOperand2()->function()->isConst()) continue; - if (mSettings->library.isFunctionConst(parent->astOperand2())) + if (mSettings.library.isFunctionConst(funcParent->astOperand2())) continue; + if (funcParent->astOperand2()->varId()) { + if (gparent->str() == "?" && astIsLHS(funcParent)) + continue; + } } } if (hasIncDecPlus) { @@ -1964,7 +2084,7 @@ void CheckOther::checkConstPointer() continue; else if (const Token* ftok = getTokenArgumentFunction(parent, argn)) { bool inconclusive{}; - if (!isVariableChangedByFunctionCall(ftok->next(), vt->pointer, var->declarationId(), *mSettings, &inconclusive) && !inconclusive) + if (!isVariableChangedByFunctionCall(ftok->next(), vt->pointer, var->declarationId(), mSettings.library, &inconclusive) && !inconclusive) continue; } } else { @@ -1989,12 +2109,12 @@ void CheckOther::checkConstPointer() const Variable* argVar = ftok->function()->getArgumentVar(argn); if (argVar && argVar->valueType() && argVar->valueType()->isConst(vt->pointer)) { bool inconclusive{}; - if (!isVariableChangedByFunctionCall(ftok, vt->pointer, var->declarationId(), *mSettings, &inconclusive) && !inconclusive) + if (!isVariableChangedByFunctionCall(ftok, vt->pointer, var->declarationId(), mSettings.library, &inconclusive) && !inconclusive) continue; } } } else { - const auto dir = mSettings->library.getArgDirection(ftok, argn + 1); + const auto dir = mSettings.library.getArgDirection(ftok, argn + 1); if (dir == Library::ArgumentChecks::Direction::DIR_IN) continue; } @@ -2008,8 +2128,11 @@ void CheckOther::checkConstPointer() nonConstPointers.emplace(var); } for (const Variable *p: pointers) { + bool foundAllBaseClasses = true; if (p->isArgument()) { - if (!p->scope() || !p->scope()->function || p->scope()->function->isImplicitlyVirtual(true) || p->scope()->function->hasVirtualSpecifier()) + if (!p->scope() || !p->scope()->function || p->scope()->function->hasVirtualSpecifier()) + continue; + if (p->scope()->function->isImplicitlyVirtual(true, &foundAllBaseClasses) && foundAllBaseClasses) continue; if (p->isMaybeUnused()) continue; @@ -2022,16 +2145,16 @@ void CheckOther::checkConstPointer() // const int indirect = p->isArray() ? p->dimensions().size() : 1; // if (isVariableChanged(start, p->scope()->bodyEnd, indirect, p->declarationId(), false, *mSettings)) // continue; - if (!isConstPointerVariable(p, *mSettings)) + if (!isConstPointerVariable(p, mSettings)) continue; - if (p->typeStartToken() && p->typeStartToken()->isSimplifiedTypedef() && !(Token::simpleMatch(p->typeEndToken(), "*") && !p->typeEndToken()->isSimplifiedTypedef())) + if (p->typeStartToken() && p->typeStartToken()->isSimplifiedTypedef() && Token::simpleMatch(p->typeEndToken(), "*") && p->typeEndToken()->isSimplifiedTypedef()) continue; - constVariableError(p, p->isArgument() ? p->scope()->function : nullptr); + constVariableError(p, p->isArgument() ? p->scope()->function : nullptr, foundAllBaseClasses); } } } -void CheckOther::constVariableError(const Variable *var, const Function *function) +void CheckOtherImpl::constVariableError(const Variable *var, const Function *function, bool foundAllBaseClasses) { if (!var) { reportError(nullptr, Severity::style, "constParameter", "Parameter 'x' can be declared with const"); @@ -2044,13 +2167,18 @@ void CheckOther::constVariableError(const Variable *var, const Function *functio return; } - const std::string vartype(var->isArgument() ? "Parameter" : "Variable"); + std::string vartype(var->isArgument() ? "Parameter" : "Variable"); const std::string& varname(var->name()); const std::string ptrRefArray = var->isArray() ? "const array" : (var->isPointer() ? "pointer to const" : "reference to const"); ErrorPath errorPath; std::string id = "const" + vartype; - std::string message = "$symbol:" + varname + "\n" + vartype + " '$symbol' can be declared as " + ptrRefArray; + std::string message = "$symbol:" + varname + "\n"; + if (!foundAllBaseClasses) { + message += "Either there is a missing override/final keyword, or the "; + vartype[0] = std::tolower(vartype[0]); + } + message += vartype + " '$symbol' can be declared as " + ptrRefArray; errorPath.emplace_back(var->nameToken(), message); if (var->isArgument() && function && function->functionPointerUsage) { errorPath.emplace_front(function->functionPointerUsage, "You might need to cast the function pointer here"); @@ -2069,10 +2197,10 @@ void CheckOther::constVariableError(const Variable *var, const Function *functio // Check usage of char variables.. //--------------------------------------------------------------------------- -void CheckOther::checkCharVariable() +void CheckOtherImpl::checkCharVariable() { - const bool warning = mSettings->severity.isEnabled(Severity::warning); - const bool portability = mSettings->severity.isEnabled(Severity::portability); + const bool warning = mSettings.severity.isEnabled(Severity::warning); + const bool portability = mSettings.severity.isEnabled(Severity::portability); if (!warning && !portability) return; @@ -2087,24 +2215,24 @@ void CheckOther::checkCharVariable() if (!tok->variable()->isArray() && !tok->variable()->isPointer()) continue; const Token *index = tok->next()->astOperand2(); - if (warning && tok->variable()->isArray() && astIsSignedChar(index) && index->getValueGE(0x80, *mSettings)) + if (warning && tok->variable()->isArray() && astIsSignedChar(index) && index->getValueGE(0x80, mSettings)) signedCharArrayIndexError(tok); - if (portability && astIsUnknownSignChar(index) && index->getValueGE(0x80, *mSettings)) + if (portability && astIsUnknownSignChar(index) && index->getValueGE(0x80, mSettings)) unknownSignCharArrayIndexError(tok); } else if (warning && Token::Match(tok, "[&|^]") && tok->isBinaryOp()) { bool warn = false; if (astIsSignedChar(tok->astOperand1())) { - const ValueFlow::Value *v1 = tok->astOperand1()->getValueLE(-1, *mSettings); + const ValueFlow::Value *v1 = tok->astOperand1()->getValueLE(-1, mSettings); const ValueFlow::Value *v2 = tok->astOperand2()->getMaxValue(false); if (!v1) - v1 = tok->astOperand1()->getValueGE(0x80, *mSettings); + v1 = tok->astOperand1()->getValueGE(0x80, mSettings); if (v1 && !(tok->str() == "&" && v2 && v2->isKnown() && v2->intvalue >= 0 && v2->intvalue < 0x100)) warn = true; } else if (astIsSignedChar(tok->astOperand2())) { - const ValueFlow::Value *v1 = tok->astOperand2()->getValueLE(-1, *mSettings); + const ValueFlow::Value *v1 = tok->astOperand2()->getValueLE(-1, mSettings); const ValueFlow::Value *v2 = tok->astOperand1()->getMaxValue(false); if (!v1) - v1 = tok->astOperand2()->getValueGE(0x80, *mSettings); + v1 = tok->astOperand2()->getValueGE(0x80, mSettings); if (v1 && !(tok->str() == "&" && v2 && v2->isKnown() && v2->intvalue >= 0 && v2->intvalue < 0x100)) warn = true; } @@ -2120,7 +2248,7 @@ void CheckOther::checkCharVariable() } } -void CheckOther::signedCharArrayIndexError(const Token *tok) +void CheckOtherImpl::signedCharArrayIndexError(const Token *tok) { reportError(tok, Severity::warning, @@ -2131,7 +2259,7 @@ void CheckOther::signedCharArrayIndexError(const Token *tok) "because of sign extension.", CWE128, Certainty::normal); } -void CheckOther::unknownSignCharArrayIndexError(const Token *tok) +void CheckOtherImpl::unknownSignCharArrayIndexError(const Token *tok) { reportError(tok, Severity::portability, @@ -2141,7 +2269,7 @@ void CheckOther::unknownSignCharArrayIndexError(const Token *tok) "treated depending on whether 'char' is signed or unsigned on target platform.", CWE758, Certainty::normal); } -void CheckOther::charBitOpError(const Token *tok) +void CheckOtherImpl::charBitOpError(const Token *tok) { reportError(tok, Severity::warning, @@ -2290,18 +2418,12 @@ static bool isConstStatement(const Token *tok, const Library& library, bool plat static bool isVoidStmt(const Token *tok) { - if (Token::simpleMatch(tok, "( void")) - return true; - if (isCPPCast(tok) && tok->astOperand1() && Token::Match(tok->astOperand1()->next(), "< void *| >")) + if (Token::simpleMatch(tok, "( void") && !(tok->astOperand1() && (tok->astOperand1()->isLiteral() || isNullOperand(tok->astOperand1())))) return true; - const Token *tok2 = tok; - while (tok2->astOperand1()) - tok2 = tok2->astOperand1(); - if (Token::simpleMatch(tok2->previous(), ")") && Token::simpleMatch(tok2->linkAt(-1), "( void")) + if (isCPPCast(tok) && tok->astOperand1() && Token::Match(tok->astOperand1()->next(), "< void *| >") && + !(tok->astOperand2() && (tok->astOperand2()->isLiteral() || isNullOperand(tok->astOperand2())))) return true; - if (Token::simpleMatch(tok2, "( void")) - return true; - return Token::Match(tok2->previous(), "delete|throw|return"); + return false; } static bool isConstTop(const Token *tok) @@ -2328,10 +2450,10 @@ static bool isConstTop(const Token *tok) return false; } -void CheckOther::checkIncompleteStatement() +void CheckOtherImpl::checkIncompleteStatement() { - if (!mSettings->severity.isEnabled(Severity::warning) && - !mSettings->isPremiumEnabled("constStatement")) + if (!mSettings.severity.isEnabled(Severity::warning) && + !mSettings.isPremiumEnabled("constStatement")) return; logChecker("CheckOther::checkIncompleteStatement"); // warning @@ -2375,27 +2497,23 @@ void CheckOther::checkIncompleteStatement() !(tok->str() == "," && tok->astParent() && tok->astParent()->isAssignmentOp())) continue; // Skip statement expressions - if (Token::simpleMatch(rtok, "; } )")) + if (Token::simpleMatch(rtok, "; } )") || Token::simpleMatch(tok->next(), "; } )")) continue; - if (!isConstStatement(tok, mSettings->library, false)) + if (!isConstStatement(tok, mSettings.library, false)) continue; if (isVoidStmt(tok)) continue; if (tok->isCpp() && tok->str() == "&" && !(tok->astOperand1() && tok->astOperand1()->valueType() && tok->astOperand1()->valueType()->isIntegral())) // Possible archive continue; - const bool inconclusive = tok->isConstOp() && !mSettings->isPremiumEnabled("constStatement"); - if (mSettings->certainty.isEnabled(Certainty::inconclusive) || !inconclusive) + const bool inconclusive = tok->isConstOp() && !mSettings.isPremiumEnabled("constStatement"); + if (mSettings.certainty.isEnabled(Certainty::inconclusive) || !inconclusive) constStatementError(tok, tok->isNumber() ? "numeric" : "string", inconclusive); } } -void CheckOther::constStatementError(const Token *tok, const std::string &type, bool inconclusive) +void CheckOtherImpl::constStatementError(const Token *tok, const std::string &type, bool inconclusive) { - const Token *valueTok = tok; - while (valueTok && valueTok->isCast()) - valueTok = valueTok->astOperand2() ? valueTok->astOperand2() : valueTok->astOperand1(); - std::string msg; if (Token::simpleMatch(tok, "==")) msg = "Found suspicious equality comparison. Did you intend to assign a value instead?"; @@ -2403,26 +2521,24 @@ void CheckOther::constStatementError(const Token *tok, const std::string &type, msg = "Found suspicious operator '" + tok->str() + "', result is not used."; else if (Token::Match(tok, "%var%")) msg = "Unused variable value '" + tok->str() + "'"; - else if (isConstant(valueTok)) { + else if (isConstant(tok)) { std::string typeStr("string"); - if (valueTok->isNumber()) + if (tok->isNumber()) typeStr = "numeric"; - else if (valueTok->isBoolean()) + else if (tok->isBoolean()) typeStr = "bool"; - else if (valueTok->tokType() == Token::eChar) + else if (tok->tokType() == Token::eChar) typeStr = "character"; - else if (isNullOperand(valueTok)) - typeStr = "NULL"; - else if (valueTok->isEnumerator()) + else if (isNullOperand(tok)) + typeStr = "null"; + else if (tok->isEnumerator()) typeStr = "enumerator"; msg = "Redundant code: Found a statement that begins with " + typeStr + " constant."; } else if (!tok) msg = "Redundant code: Found a statement that begins with " + type + " constant."; - else if (tok->isCast() && tok->tokType() == Token::Type::eExtendedOp) { - msg = "Redundant code: Found unused cast "; - msg += valueTok ? "of expression '" + valueTok->expressionString() + "'." : "expression."; - } + else if (tok->isCast() && tok->tokType() == Token::Type::eExtendedOp) + msg = "Redundant code: Found unused cast in expression '" + tok->expressionString() + "'."; else if (tok->str() == "?" && tok->tokType() == Token::Type::eExtendedOp) msg = "Redundant code: Found unused result of ternary operator."; else if (tok->str() == "." && tok->tokType() == Token::Type::eOther) @@ -2435,7 +2551,7 @@ void CheckOther::constStatementError(const Token *tok, const std::string &type, msg = "Redundant code: Found unused function."; else if (Token::Match(tok->previous(), "%name% (")) msg = "Redundant code: Found unused '" + tok->strAt(-1) + "' expression."; - else if (mSettings->debugwarnings) { + else if (mSettings.debugwarnings) { reportError(tok, Severity::debug, "debug", "constStatementError not handled."); return; } @@ -2445,7 +2561,7 @@ void CheckOther::constStatementError(const Token *tok, const std::string &type, //--------------------------------------------------------------------------- // Detect division by zero. //--------------------------------------------------------------------------- -void CheckOther::checkZeroDivision() +void CheckOtherImpl::checkZeroDivision() { logChecker("CheckOther::checkZeroDivision"); @@ -2461,12 +2577,12 @@ void CheckOther::checkZeroDivision() // Value flow.. const ValueFlow::Value *value = tok->astOperand2()->getValue(0LL); - if (value && mSettings->isEnabled(value, false)) + if (value && mSettings.isEnabled(value, false)) zerodivError(tok, value); } } -void CheckOther::zerodivError(const Token *tok, const ValueFlow::Value *value) +void CheckOtherImpl::zerodivError(const Token *tok, const ValueFlow::Value *value) { if (!tok && !value) { reportError(tok, Severity::error, "zerodiv", "Division by zero.", CWE369, Certainty::normal); @@ -2485,8 +2601,8 @@ void CheckOther::zerodivError(const Token *tok, const ValueFlow::Value *value) errmsg << "Division by zero."; reportError(std::move(errorPath), - value->errorSeverity() ? Severity::error : Severity::warning, - value->condition ? "zerodivcond" : "zerodiv", + (value->errorSeverity() && !value->conditional) ? Severity::error : Severity::warning, + (value->condition || value->conditional) ? "zerodivcond" : "zerodiv", errmsg.str(), CWE369, value->isInconclusive() ? Certainty::inconclusive : Certainty::normal); } @@ -2495,9 +2611,9 @@ void CheckOther::zerodivError(const Token *tok, const ValueFlow::Value *value) // double d = 1.0 / 0.0 + 100.0; //--------------------------------------------------------------------------- -void CheckOther::checkNanInArithmeticExpression() +void CheckOtherImpl::checkNanInArithmeticExpression() { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("nanInArithmeticExpression")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("nanInArithmeticExpression")) return; logChecker("CheckOther::checkNanInArithmeticExpression"); // style for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) { @@ -2510,7 +2626,7 @@ void CheckOther::checkNanInArithmeticExpression() } } -void CheckOther::nanInArithmeticExpressionError(const Token *tok) +void CheckOtherImpl::nanInArithmeticExpressionError(const Token *tok) { reportError(tok, Severity::style, "nanInArithmeticExpression", "Using NaN/Inf in a computation.\n" @@ -2521,13 +2637,13 @@ void CheckOther::nanInArithmeticExpressionError(const Token *tok) //--------------------------------------------------------------------------- // Creating instance of classes which are destroyed immediately //--------------------------------------------------------------------------- -void CheckOther::checkMisusedScopedObject() +void CheckOtherImpl::checkMisusedScopedObject() { // Skip this check for .c files if (mTokenizer->isC()) return; - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("unusedScopedObject")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("unusedScopedObject")) return; logChecker("CheckOther::checkMisusedScopedObject"); // style,c++ @@ -2554,10 +2670,10 @@ void CheckOther::checkMisusedScopedObject() }; auto isLibraryConstructor = [&](const Token* tok, const std::string& typeStr) -> bool { - const Library::TypeCheck typeCheck = mSettings->library.getTypeCheck("unusedvar", typeStr); + const Library::TypeCheck typeCheck = mSettings.library.getTypeCheck("unusedvar", typeStr); if (typeCheck == Library::TypeCheck::check || typeCheck == Library::TypeCheck::checkFiniteLifetime) return true; - return mSettings->library.detectContainerOrIterator(tok); + return mSettings.library.detectContainerOrIterator(tok); }; const SymbolDatabase* const symbolDatabase = mTokenizer->getSymbolDatabase(); @@ -2572,7 +2688,7 @@ void CheckOther::checkMisusedScopedObject() if (Token::simpleMatch(parTok, "<") && parTok->link()) parTok = parTok->link()->next(); if (const Token* arg = parTok->astOperand2()) { - if (!isConstStatement(arg, mSettings->library, false)) + if (!isConstStatement(arg, mSettings.library, false)) continue; if (parTok->str() == "(") { if (arg->varId() && !(arg->variable() && arg->variable()->nameToken() != arg)) @@ -2596,7 +2712,7 @@ void CheckOther::checkMisusedScopedObject() } } -void CheckOther::misusedScopeObjectError(const Token *tok, const std::string& varname, bool isAssignment) +void CheckOtherImpl::misusedScopeObjectError(const Token *tok, const std::string& varname, bool isAssignment) { std::string msg = "Instance of '$symbol' object is destroyed immediately"; msg += isAssignment ? ", assignment has no effect." : "."; @@ -2621,7 +2737,7 @@ static const Token * getSingleExpressionInBlock(const Token * tok) // check for duplicate code in if and else branches // if (a) { b = true; } else { b = true; } //----------------------------------------------------------------------------- -void CheckOther::checkDuplicateBranch() +void CheckOtherImpl::checkDuplicateBranch() { // This is inconclusive since in practice most warnings are noise: // * There can be unfixed low-priority todos. The code is fine as it @@ -2631,7 +2747,7 @@ void CheckOther::checkDuplicateBranch() // and their conditional code is a duplicate of the condition that // is always true just in case it would be false. See for instance // abiword. - if (!mSettings->severity.isEnabled(Severity::style) || !mSettings->certainty.isEnabled(Certainty::inconclusive)) + if (!mSettings.severity.isEnabled(Severity::style) || !mSettings.certainty.isEnabled(Certainty::inconclusive)) return; logChecker("CheckOther::checkDuplicateBranch"); // style,inconclusive @@ -2677,7 +2793,7 @@ void CheckOther::checkDuplicateBranch() const std::string branch2 = tokElse->stringifyList(scope.bodyEnd->linkAt(2)); // check for duplicates - if (branch1 == branch2) { + if (branch1 == branch2 && branch1 != ";") { duplicateBranchError(scope.classDef, scope.bodyEnd->next(), ErrorPath{}); continue; } @@ -2693,14 +2809,14 @@ void CheckOther::checkDuplicateBranch() if (branchTop1->str() != branchTop2->str()) continue; ErrorPath errorPath; - if (isSameExpression(false, branchTop1->astOperand1(), branchTop2->astOperand1(), *mSettings, true, true, &errorPath) && - isSameExpression(false, branchTop1->astOperand2(), branchTop2->astOperand2(), *mSettings, true, true, &errorPath)) + if (isSameExpression(false, branchTop1->astOperand1(), branchTop2->astOperand1(), mSettings, true, true, &errorPath) && + isSameExpression(false, branchTop1->astOperand2(), branchTop2->astOperand2(), mSettings, true, true, &errorPath)) duplicateBranchError(scope.classDef, scope.bodyEnd->next(), std::move(errorPath)); } } } -void CheckOther::duplicateBranchError(const Token *tok1, const Token *tok2, ErrorPath errors) +void CheckOtherImpl::duplicateBranchError(const Token *tok1, const Token *tok2, ErrorPath errors) { errors.emplace_back(tok2, ""); errors.emplace_back(tok1, ""); @@ -2717,21 +2833,21 @@ void CheckOther::duplicateBranchError(const Token *tok1, const Token *tok2, Erro // char* p = malloc(100); // free(p + 10); //----------------------------------------------------------------------------- -void CheckOther::checkInvalidFree() +void CheckOtherImpl::checkInvalidFree() { std::map inconclusive; std::map allocation; logChecker("CheckOther::checkInvalidFree"); - const bool printInconclusive = mSettings->certainty.isEnabled(Certainty::inconclusive); + const bool printInconclusive = mSettings.certainty.isEnabled(Certainty::inconclusive); const SymbolDatabase* symbolDatabase = mTokenizer->getSymbolDatabase(); for (const Scope * scope : symbolDatabase->functionScopes) { for (const Token* tok = scope->bodyStart->next(); tok && tok != scope->bodyEnd; tok = tok->next()) { // Keep track of which variables were assigned addresses to newly-allocated memory if ((tok->isCpp() && Token::Match(tok, "%var% = new")) || - (Token::Match(tok, "%var% = %name% (") && mSettings->library.getAllocFuncInfo(tok->tokAt(2)))) { + (Token::Match(tok, "%var% = %name% (") && mSettings.library.getAllocFuncInfo(tok->tokAt(2)))) { allocation.emplace(tok->varId(), tok->strAt(2)); inconclusive.emplace(tok->varId(), false); } @@ -2759,7 +2875,7 @@ void CheckOther::checkInvalidFree() // If a variable that was previously assigned a newly-allocated memory location is // added or subtracted from when used to free the memory, report an error. - else if ((Token::Match(tok, "%name% ( %any% +|-") && mSettings->library.getDeallocFuncInfo(tok)) || + else if ((Token::Match(tok, "%name% ( %any% +|-") && mSettings.library.getDeallocFuncInfo(tok)) || Token::Match(tok, "delete [ ] ( %any% +|-") || Token::Match(tok, "delete %any% +|- %any%")) { @@ -2779,7 +2895,7 @@ void CheckOther::checkInvalidFree() // If the previously-allocated variable is passed in to another function // as a parameter, it might be modified, so we shouldn't report an error // if it is later used to free memory - else if (Token::Match(tok, "%name% (") && !mSettings->library.isFunctionConst(tok->str(), true)) { + else if (Token::Match(tok, "%name% (") && !mSettings.library.isFunctionConst(tok->str(), true)) { const Token* tok2 = Token::findmatch(tok->next(), "%var%", tok->linkAt(1)); while (tok2 != nullptr) { allocation.erase(tok->varId()); @@ -2791,7 +2907,7 @@ void CheckOther::checkInvalidFree() } } -void CheckOther::invalidFreeError(const Token *tok, const std::string &allocation, bool inconclusive) +void CheckOtherImpl::invalidFreeError(const Token *tok, const std::string &allocation, bool inconclusive) { std::string alloc = allocation; if (alloc != "new") @@ -2842,25 +2958,27 @@ isStaticAssert(const Settings &settings, const Token *tok) return true; } - if (tok->isC() && settings.standards.c >= Standards::C11 && - Token::simpleMatch(tok, "_Static_assert")) { - return true; + if (tok->isC()) { + if (settings.standards.c >= Standards::C11 && Token::simpleMatch(tok, "_Static_assert")) + return true; + if (settings.standards.c >= Standards::C23 && Token::simpleMatch(tok, "static_assert")) + return true; } return false; } -void CheckOther::checkDuplicateExpression() +void CheckOtherImpl::checkDuplicateExpression() { { - const bool styleEnabled = mSettings->severity.isEnabled(Severity::style); - const bool premiumEnabled = mSettings->isPremiumEnabled("oppositeExpression") || - mSettings->isPremiumEnabled("duplicateExpression") || - mSettings->isPremiumEnabled("duplicateAssignExpression") || - mSettings->isPremiumEnabled("duplicateExpressionTernary") || - mSettings->isPremiumEnabled("duplicateValueTernary") || - mSettings->isPremiumEnabled("selfAssignment") || - mSettings->isPremiumEnabled("knownConditionTrueFalse"); + const bool styleEnabled = mSettings.severity.isEnabled(Severity::style); + const bool premiumEnabled = mSettings.isPremiumEnabled("oppositeExpression") || + mSettings.isPremiumEnabled("duplicateExpression") || + mSettings.isPremiumEnabled("duplicateAssignExpression") || + mSettings.isPremiumEnabled("duplicateExpressionTernary") || + mSettings.isPremiumEnabled("duplicateValueTernary") || + mSettings.isPremiumEnabled("selfAssignment") || + mSettings.isPremiumEnabled("knownConditionTrueFalse"); if (!styleEnabled && !premiumEnabled) return; @@ -2898,8 +3016,8 @@ void CheckOther::checkDuplicateExpression() Token::Match(tok->astOperand2()->previous(), "%name% (") ) && tok->next()->tokType() != Token::eType && - isSameExpression(true, tok->next(), nextAssign->next(), *mSettings, true, false) && - isSameExpression(true, tok->astOperand2(), nextAssign->astOperand2(), *mSettings, true, false) && + isSameExpression(true, tok->next(), nextAssign->next(), mSettings, true, false) && + isSameExpression(true, tok->astOperand2(), nextAssign->astOperand2(), mSettings, true, false) && tok->astOperand2()->expressionString() == nextAssign->astOperand2()->expressionString()) { bool differentDomain = false; const Scope * varScope = var1->scope() ? var1->scope() : scope; @@ -2917,7 +3035,7 @@ void CheckOther::checkDuplicateExpression() !isSameExpression(true, tok->astOperand2(), assignTok->astOperand1(), - *mSettings, + mSettings, true, true)) continue; @@ -2926,7 +3044,7 @@ void CheckOther::checkDuplicateExpression() !isSameExpression(true, tok->astOperand2(), assignTok->astOperand2(), - *mSettings, + mSettings, true, true)) continue; @@ -2935,7 +3053,7 @@ void CheckOther::checkDuplicateExpression() } if (!differentDomain && !isUniqueExpression(tok->astOperand2())) duplicateAssignExpressionError(var1, var2, false); - else if (mSettings->certainty.isEnabled(Certainty::inconclusive)) { + else if (mSettings.certainty.isEnabled(Certainty::inconclusive)) { diag(assignTok); duplicateAssignExpressionError(var1, var2, true); } @@ -2961,14 +3079,14 @@ void CheckOther::checkDuplicateExpression() if (isSameExpression(true, tok->astOperand1(), tok->astOperand2(), - *mSettings, + mSettings, true, followVar, &errorPath)) { if (isWithoutSideEffects(tok->astOperand1())) { const Token* loopTok = isInLoopCondition(tok); if (!loopTok || - !findExpressionChanged(tok, tok, loopTok->link()->linkAt(1), *mSettings)) { + !findExpressionChanged(tok, tok, loopTok->link()->linkAt(1), mSettings)) { const bool isEnum = tok->scope()->type == ScopeType::eEnum; const bool assignment = !isEnum && tok->str() == "="; if (assignment) @@ -2980,7 +3098,7 @@ void CheckOther::checkDuplicateExpression() parent = parent->astParent(); } if (parent && parent->previous() && - (isStaticAssert(*mSettings, parent->previous()) || + (isStaticAssert(mSettings, parent->previous()) || Token::simpleMatch(parent->previous(), "assert"))) { continue; } @@ -2993,7 +3111,7 @@ void CheckOther::checkDuplicateExpression() isSameExpression(false, tok->astOperand1(), tok->astOperand2()->astOperand1(), - *mSettings, + mSettings, true, false)) { if (isWithoutSideEffects(tok->astOperand1())) { @@ -3001,7 +3119,7 @@ void CheckOther::checkDuplicateExpression() } } else if (isOppositeExpression(tok->astOperand1(), tok->astOperand2(), - *mSettings, + mSettings, false, true, &errorPath) && @@ -3013,15 +3131,15 @@ void CheckOther::checkDuplicateExpression() isSameExpression(true, tok->astOperand2(), tok->astOperand1()->astOperand2(), - *mSettings, + mSettings, true, followVar, &errorPath) && isWithoutSideEffects(tok->astOperand2())) duplicateExpressionError(tok->astOperand2(), tok->astOperand1()->astOperand2(), tok, std::move(errorPath)); - else if (tok->astOperand2() && isConstExpression(tok->astOperand1(), mSettings->library)) { + else if (tok->astOperand2() && isConstExpression(tok->astOperand1(), mSettings.library)) { auto checkDuplicate = [&](const Token* exp1, const Token* exp2, const Token* ast1) { - if (isSameExpression(true, exp1, exp2, *mSettings, true, true, &errorPath) && + if (isSameExpression(true, exp1, exp2, mSettings, true, true, &errorPath) && isWithoutSideEffects(exp1) && isWithoutSideEffects(ast1->astOperand2())) duplicateExpressionError(exp1, exp2, tok, errorPath, /*hasMultipleExpr*/ true); @@ -3033,22 +3151,32 @@ void CheckOther::checkDuplicateExpression() checkDuplicate(ast1->astOperand1(), tok->astOperand2(), ast1); ast1 = ast1->astOperand1(); } + if (tok->str() != "=") { + const Token* par = tok->astParent(); + while (par && tok->str() == par->str() && precedes(par->astOperand1(), tok)) { // chain of identical operators with parentheses + checkDuplicate(par->astOperand1(), tok->astOperand1(), par); + checkDuplicate(par->astOperand1(), tok->astOperand2(), par); + checkDuplicate(par->astOperand2(), tok->astOperand1(), par); + checkDuplicate(par->astOperand2(), tok->astOperand2(), par); + par = par->astParent(); + } + } } } } else if (tok->astOperand1() && tok->astOperand2() && tok->str() == ":" && tok->astParent() && tok->astParent()->str() == "?") { - if (isSameExpression(true, tok->astOperand1(), tok->astOperand2(), *mSettings, false, true, &errorPath)) + if (isSameExpression(true, tok->astOperand1(), tok->astOperand2(), mSettings, false, true, &errorPath)) duplicateExpressionTernaryError(tok, std::move(errorPath)); else if (!tok->astOperand1()->values().empty() && !tok->astOperand2()->values().empty() && isEqualKnownValue(tok->astOperand1(), tok->astOperand2()) && - !isVariableChanged(tok->astParent(), /*indirect*/ 0, *mSettings) && - isConstStatement(tok->astOperand1(), mSettings->library, true) && isConstStatement(tok->astOperand2(), mSettings->library, true)) + !isVariableChanged(tok->astParent(), /*indirect*/ 0, mSettings) && + isConstStatement(tok->astOperand1(), mSettings.library, true) && isConstStatement(tok->astOperand2(), mSettings.library, true)) duplicateValueTernaryError(tok); } } } } -void CheckOther::oppositeExpressionError(const Token *opTok, ErrorPath errors) +void CheckOtherImpl::oppositeExpressionError(const Token *opTok, ErrorPath errors) { errors.emplace_back(opTok, ""); @@ -3060,7 +3188,7 @@ void CheckOther::oppositeExpressionError(const Token *opTok, ErrorPath errors) "determine if it is correct.", CWE398, Certainty::normal); } -void CheckOther::duplicateExpressionError(const Token *tok1, const Token *tok2, const Token *opTok, ErrorPath errors, bool hasMultipleExpr) +void CheckOtherImpl::duplicateExpressionError(const Token *tok1, const Token *tok2, const Token *opTok, ErrorPath errors, bool hasMultipleExpr) { errors.emplace_back(opTok, ""); @@ -3088,7 +3216,7 @@ void CheckOther::duplicateExpressionError(const Token *tok1, const Token *tok2, "determine if it is correct.", CWE398, Certainty::normal); } -void CheckOther::duplicateAssignExpressionError(const Token *tok1, const Token *tok2, bool inconclusive) +void CheckOtherImpl::duplicateAssignExpressionError(const Token *tok1, const Token *tok2, bool inconclusive) { const std::list toks = { tok2, tok1 }; @@ -3102,7 +3230,7 @@ void CheckOther::duplicateAssignExpressionError(const Token *tok1, const Token * "determine if it is correct.", CWE398, inconclusive ? Certainty::inconclusive : Certainty::normal); } -void CheckOther::duplicateExpressionTernaryError(const Token *tok, ErrorPath errors) +void CheckOtherImpl::duplicateExpressionTernaryError(const Token *tok, ErrorPath errors) { errors.emplace_back(tok, ""); reportError(std::move(errors), Severity::style, "duplicateExpressionTernary", "Same expression in both branches of ternary operator.\n" @@ -3110,14 +3238,14 @@ void CheckOther::duplicateExpressionTernaryError(const Token *tok, ErrorPath err "the same code is executed regardless of the condition.", CWE398, Certainty::normal); } -void CheckOther::duplicateValueTernaryError(const Token *tok) +void CheckOtherImpl::duplicateValueTernaryError(const Token *tok) { reportError(tok, Severity::style, "duplicateValueTernary", "Same value in both branches of ternary operator.\n" "Finding the same value in both branches of ternary operator is suspicious as " "the same code is executed regardless of the condition.", CWE398, Certainty::normal); } -void CheckOther::selfAssignmentError(const Token *tok, const std::string &varname) +void CheckOtherImpl::selfAssignmentError(const Token *tok, const std::string &varname) { reportError(tok, Severity::style, "selfAssignment", @@ -3134,9 +3262,9 @@ void CheckOther::selfAssignmentError(const Token *tok, const std::string &varnam // Reference: // - http://www.cplusplus.com/reference/cmath/ //----------------------------------------------------------------------------- -void CheckOther::checkComparisonFunctionIsAlwaysTrueOrFalse() +void CheckOtherImpl::checkComparisonFunctionIsAlwaysTrueOrFalse() { - if (!mSettings->severity.isEnabled(Severity::warning)) + if (!mSettings.severity.isEnabled(Severity::warning)) return; logChecker("CheckOther::checkComparisonFunctionIsAlwaysTrueOrFalse"); // warning @@ -3164,7 +3292,7 @@ void CheckOther::checkComparisonFunctionIsAlwaysTrueOrFalse() } } } -void CheckOther::checkComparisonFunctionIsAlwaysTrueOrFalseError(const Token* tok, const std::string &functionName, const std::string &varName, const bool result) +void CheckOtherImpl::checkComparisonFunctionIsAlwaysTrueOrFalseError(const Token* tok, const std::string &functionName, const std::string &varName, const bool result) { const std::string strResult = bool_to_string(result); const CWE cweResult = result ? CWE571 : CWE570; @@ -3179,9 +3307,9 @@ void CheckOther::checkComparisonFunctionIsAlwaysTrueOrFalseError(const Token* to //--------------------------------------------------------------------------- // Check testing sign of unsigned variables and pointers. //--------------------------------------------------------------------------- -void CheckOther::checkSignOfUnsignedVariable() +void CheckOtherImpl::checkSignOfUnsignedVariable() { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("unsignedLessThanZero")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("unsignedLessThanZero")) return; logChecker("CheckOther::checkSignOfUnsignedVariable"); // style @@ -3210,7 +3338,7 @@ void CheckOther::checkSignOfUnsignedVariable() } } -bool CheckOther::comparisonNonZeroExpressionLessThanZero(const Token *tok, const ValueFlow::Value *&zeroValue, const Token *&nonZeroExpr, bool suppress) +bool CheckOtherImpl::comparisonNonZeroExpressionLessThanZero(const Token *tok, const ValueFlow::Value *&zeroValue, const Token *&nonZeroExpr, bool suppress) { if (!tok->isComparisonOp() || !tok->astOperand1() || !tok->astOperand2()) return false; @@ -3236,7 +3364,7 @@ bool CheckOther::comparisonNonZeroExpressionLessThanZero(const Token *tok, const return vt && (vt->pointer || vt->sign == ValueType::UNSIGNED); } -bool CheckOther::testIfNonZeroExpressionIsPositive(const Token *tok, const ValueFlow::Value *&zeroValue, const Token *&nonZeroExpr) +bool CheckOtherImpl::testIfNonZeroExpressionIsPositive(const Token *tok, const ValueFlow::Value *&zeroValue, const Token *&nonZeroExpr) { if (!tok->isComparisonOp() || !tok->astOperand1() || !tok->astOperand2()) return false; @@ -3258,7 +3386,7 @@ bool CheckOther::testIfNonZeroExpressionIsPositive(const Token *tok, const Value return vt && (vt->pointer || vt->sign == ValueType::UNSIGNED); } -void CheckOther::unsignedLessThanZeroError(const Token *tok, const ValueFlow::Value * v, const std::string &varname) +void CheckOtherImpl::unsignedLessThanZeroError(const Token *tok, const ValueFlow::Value * v, const std::string &varname) { reportError(getErrorPath(tok, v, "Unsigned less than zero"), Severity::style, "unsignedLessThanZero", "$symbol:" + varname + "\n" @@ -3267,20 +3395,20 @@ void CheckOther::unsignedLessThanZeroError(const Token *tok, const ValueFlow::Va "is either pointless or an error to check if it is.", CWE570, Certainty::normal); } -void CheckOther::pointerLessThanZeroError(const Token *tok, const ValueFlow::Value *v) +void CheckOtherImpl::pointerLessThanZeroError(const Token *tok, const ValueFlow::Value *v) { reportError(getErrorPath(tok, v, "Pointer less than zero"), Severity::style, "pointerLessThanZero", "A pointer can not be negative so it is either pointless or an error to check if it is.", CWE570, Certainty::normal); } -void CheckOther::unsignedPositiveError(const Token *tok, const ValueFlow::Value * v, const std::string &varname) +void CheckOtherImpl::unsignedPositiveError(const Token *tok, const ValueFlow::Value * v, const std::string &varname) { reportError(getErrorPath(tok, v, "Unsigned positive"), Severity::style, "unsignedPositive", "$symbol:" + varname + "\n" "Unsigned expression '$symbol' can't be negative so it is unnecessary to test it.", CWE570, Certainty::normal); } -void CheckOther::pointerPositiveError(const Token *tok, const ValueFlow::Value * v) +void CheckOtherImpl::pointerPositiveError(const Token *tok, const ValueFlow::Value * v) { reportError(getErrorPath(tok, v, "Pointer positive"), Severity::style, "pointerPositive", "A pointer can not be negative so it is either pointless or an error to check if it is not.", CWE570, Certainty::normal); @@ -3291,7 +3419,7 @@ static bool constructorTakesReference(const Scope * const classScope) { return std::any_of(classScope->functionList.begin(), classScope->functionList.end(), [&](const Function& constructor) { if (constructor.isConstructor()) { - for (int argnr = 0U; argnr < constructor.argCount(); argnr++) { + for (size_t argnr = 0U; argnr < constructor.argCount(); argnr++) { const Variable * const argVar = constructor.getArgumentVar(argnr); if (argVar && argVar->isReference()) { return true; @@ -3363,9 +3491,9 @@ static bool checkVariableAssignment(const Token* tok, const ValueType* vtLhs, co return scope && scope->function && (!scope->functionOf || scope->function->isConst() || scope->function->isStatic()); } -void CheckOther::checkRedundantCopy() +void CheckOtherImpl::checkRedundantCopy() { - if (!mSettings->severity.isEnabled(Severity::performance) || mTokenizer->isC() || !mSettings->certainty.isEnabled(Certainty::inconclusive)) + if (!mSettings.severity.isEnabled(Severity::performance) || mTokenizer->isC() || !mSettings.certainty.isEnabled(Certainty::inconclusive)) return; logChecker("CheckOther::checkRedundantCopy"); // c++,performance,inconclusive @@ -3375,7 +3503,7 @@ void CheckOther::checkRedundantCopy() for (const Variable* var : symbolDatabase->variableList()) { if (!var || var->isReference() || var->isPointer() || (!var->type() && !var->isStlType() && !(var->valueType() && var->valueType()->container)) || // bailout if var is of standard type, if it is a pointer or non-const - (!var->isConst() && isVariableChanged(var, *mSettings))) + (!var->isConst() && isVariableChanged(var, mSettings))) continue; const Token* startTok = var->nameToken(); @@ -3395,13 +3523,13 @@ void CheckOther::checkRedundantCopy() const Token* tok = startTok->next()->astOperand2(); if (!tok) continue; - if (!checkFunctionReturnsRef(tok, *mSettings) && !checkVariableAssignment(tok, var->valueType(), *mSettings)) + if (!checkFunctionReturnsRef(tok, mSettings) && !checkVariableAssignment(tok, var->valueType(), mSettings)) continue; redundantCopyError(startTok, startTok->str()); } } -void CheckOther::redundantCopyError(const Token *tok,const std::string& varname) +void CheckOtherImpl::redundantCopyError(const Token *tok,const std::string& varname) { reportError(tok, Severity::performance, "redundantCopyLocalConst", "$symbol:" + varname + "\n" @@ -3416,14 +3544,16 @@ void CheckOther::redundantCopyError(const Token *tok,const std::string& varname) // Checking for shift by negative values //--------------------------------------------------------------------------- -static bool isNegative(const Token *tok, const Settings &settings) +static const ValueFlow::Value* isNegative(const Token *tok, const Settings &settings) { - return tok->valueType() && tok->valueType()->sign == ValueType::SIGNED && tok->getValueLE(-1LL, settings); + if (!tok->valueType() || tok->valueType()->sign != ValueType::SIGNED) + return nullptr; + return tok->getValueLE(-1LL, settings); } -void CheckOther::checkNegativeBitwiseShift() +void CheckOtherImpl::checkNegativeBitwiseShift() { - const bool portability = mSettings->severity.isEnabled(Severity::portability); + const bool portability = mSettings.severity.isEnabled(Severity::portability); logChecker("CheckOther::checkNegativeBitwiseShift"); @@ -3454,35 +3584,39 @@ void CheckOther::checkNegativeBitwiseShift() if (ternary) continue; - // Get negative rhs value. preferably a value which doesn't have 'condition'. - if (portability && isNegative(tok->astOperand1(), *mSettings)) - negativeBitwiseShiftError(tok, 1); - else if (isNegative(tok->astOperand2(), *mSettings)) - negativeBitwiseShiftError(tok, 2); + const ValueFlow::Value* value = isNegative(tok->astOperand1(), mSettings); // lhs + if (portability && value) + negativeBitwiseShiftError(tok, true, value); + else { + value = isNegative(tok->astOperand2(), mSettings); // rhs + if (value) + negativeBitwiseShiftError(tok, false, value); + } } } -void CheckOther::negativeBitwiseShiftError(const Token *tok, int op) +void CheckOtherImpl::negativeBitwiseShiftError(const Token *tok, bool isLHS, const ValueFlow::Value* v) { - if (op == 1) - // LHS - this is used by intention in various software, if it - // is used often in a project and works as expected then this is - // a portability issue - reportError(tok, Severity::portability, "shiftNegativeLHS", "Shifting a negative value is technically undefined behaviour", CWE758, Certainty::normal); - else // RHS - reportError(tok, Severity::error, "shiftNegative", "Shifting by a negative value is undefined behaviour", CWE758, Certainty::normal); + // LHS - this is used by intention in various software, if it + // is used often in a project and works as expected then this is + // a portability issue + const char* id = isLHS ? "shiftNegativeLHS" : "shiftNegative"; + const std::string msg = isLHS ? "Shifting a negative value is technically undefined behaviour" : "Shifting by a negative value is undefined behaviour"; + const Severity severity = isLHS ? Severity::portability : (v && v->errorSeverity() && !v->conditional ? Severity::error : Severity::warning); + const ErrorPath errorPath = getErrorPath(tok, v, msg); + reportError(errorPath, severity, id, msg, CWE758, Certainty::normal); } //--------------------------------------------------------------------------- // Check for incompletely filled buffers. //--------------------------------------------------------------------------- -void CheckOther::checkIncompleteArrayFill() +void CheckOtherImpl::checkIncompleteArrayFill() { - if (!mSettings->certainty.isEnabled(Certainty::inconclusive)) + if (!mSettings.certainty.isEnabled(Certainty::inconclusive)) return; - const bool printWarning = mSettings->severity.isEnabled(Severity::warning); - const bool printPortability = mSettings->severity.isEnabled(Severity::portability); + const bool printWarning = mSettings.severity.isEnabled(Severity::warning); + const bool printPortability = mSettings.severity.isEnabled(Severity::portability); if (!printPortability && !printWarning) return; @@ -3512,9 +3646,9 @@ void CheckOther::checkIncompleteArrayFill() continue; int size = mTokenizer->sizeOfType(var->typeStartToken()); if (size == 0 && var->valueType()->pointer) - size = mSettings->platform.sizeof_pointer; + size = mSettings.platform.sizeof_pointer; else if (size == 0 && var->valueType()) - size = var->valueType()->getSizeOf(*mSettings, ValueType::Accuracy::LowerBound, ValueType::SizeOf::Pointer); + size = var->valueType()->getSizeOf(mSettings, ValueType::Accuracy::LowerBound, ValueType::SizeOf::Pointer); const Token* tok3 = tok->next()->astOperand2()->astOperand1()->astOperand1(); if ((size != 1 && size != 100 && size != 0) || var->isPointer()) { if (printWarning) @@ -3526,7 +3660,7 @@ void CheckOther::checkIncompleteArrayFill() } } -void CheckOther::incompleteArrayFillError(const Token* tok, const std::string& buffer, const std::string& function, bool boolean) +void CheckOtherImpl::incompleteArrayFillError(const Token* tok, const std::string& buffer, const std::string& function, bool boolean) { if (boolean) reportError(tok, Severity::portability, "incompleteArrayFill", @@ -3546,9 +3680,9 @@ void CheckOther::incompleteArrayFillError(const Token* tok, const std::string& b // Detect NULL being passed to variadic function. //--------------------------------------------------------------------------- -void CheckOther::checkVarFuncNullUB() +void CheckOtherImpl::checkVarFuncNullUB() { - if (!mSettings->severity.isEnabled(Severity::portability)) + if (!mSettings.severity.isEnabled(Severity::portability)) return; logChecker("CheckOther::checkVarFuncNullUB"); // portability @@ -3584,7 +3718,7 @@ void CheckOther::checkVarFuncNullUB() } } -void CheckOther::varFuncNullUBError(const Token *tok) +void CheckOtherImpl::varFuncNullUBError(const Token *tok) { reportError(tok, Severity::portability, @@ -3631,9 +3765,9 @@ void CheckOther::varFuncNullUBError(const Token *tok) "}", CWE475, Certainty::normal); } -void CheckOther::checkRedundantPointerOp() +void CheckOtherImpl::checkRedundantPointerOp() { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("redundantPointerOp")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("redundantPointerOp")) return; logChecker("CheckOther::checkRedundantPointerOp"); // style @@ -3670,16 +3804,16 @@ void CheckOther::checkRedundantPointerOp() } } -void CheckOther::redundantPointerOpError(const Token* tok, const std::string &varname, bool inconclusive, bool addressOfDeref) +void CheckOtherImpl::redundantPointerOpError(const Token* tok, const std::string &varname, bool inconclusive, bool addressOfDeref) { std::string msg = "$symbol:" + varname + "\nRedundant pointer operation on '$symbol' - it's already a "; msg += addressOfDeref ? "pointer." : "variable."; reportError(tok, Severity::style, "redundantPointerOp", msg, CWE398, inconclusive ? Certainty::inconclusive : Certainty::normal); } -void CheckOther::checkInterlockedDecrement() +void CheckOtherImpl::checkInterlockedDecrement() { - if (!mSettings->platform.isWindows()) { + if (!mSettings.platform.isWindows()) { return; } @@ -3717,15 +3851,15 @@ void CheckOther::checkInterlockedDecrement() } } -void CheckOther::raceAfterInterlockedDecrementError(const Token* tok) +void CheckOtherImpl::raceAfterInterlockedDecrementError(const Token* tok) { reportError(tok, Severity::error, "raceAfterInterlockedDecrement", "Race condition: non-interlocked access after InterlockedDecrement(). Use InterlockedDecrement() return value instead.", CWE362, Certainty::normal); } -void CheckOther::checkUnusedLabel() +void CheckOtherImpl::checkUnusedLabel() { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->severity.isEnabled(Severity::warning) && !mSettings->isPremiumEnabled("unusedLabel")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.severity.isEnabled(Severity::warning) && !mSettings.isPremiumEnabled("unusedLabel")) return; logChecker("CheckOther::checkUnusedLabel"); // style,warning @@ -3746,9 +3880,9 @@ void CheckOther::checkUnusedLabel() } } -void CheckOther::unusedLabelError(const Token* tok, bool inSwitch, bool hasIfdef) +void CheckOtherImpl::unusedLabelError(const Token* tok, bool inSwitch, bool hasIfdef) { - if (tok && !mSettings->severity.isEnabled(inSwitch ? Severity::warning : Severity::style) && !mSettings->isPremiumEnabled("unusedLabel")) + if (tok && !mSettings.severity.isEnabled(inSwitch ? Severity::warning : Severity::style) && !mSettings.isPremiumEnabled("unusedLabel")) return; std::string id = "unusedLabel"; @@ -3840,7 +3974,7 @@ static bool checkEvaluationOrderCpp17(const Token * tok, const Token * tok2, con return foundUndefined || foundUnspecified; } -void CheckOther::checkEvaluationOrder() +void CheckOtherImpl::checkEvaluationOrder() { logChecker("CheckOther::checkEvaluationOrder"); const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); @@ -3880,14 +4014,14 @@ void CheckOther::checkEvaluationOrder() break; bool foundError{false}, foundUnspecified{false}, bSelfAssignmentError{false}; - if (mTokenizer->isCPP() && mSettings->standards.cpp >= Standards::CPP11) { - if (mSettings->standards.cpp >= Standards::CPP17) - foundError = checkEvaluationOrderCpp17(tok, tok2, parent, *mSettings, foundUnspecified); + if (mTokenizer->isCPP() && mSettings.standards.cpp >= Standards::CPP11) { + if (mSettings.standards.cpp >= Standards::CPP17) + foundError = checkEvaluationOrderCpp17(tok, tok2, parent, mSettings, foundUnspecified); else - foundError = checkEvaluationOrderCpp11(tok, tok2, parent, *mSettings); + foundError = checkEvaluationOrderCpp11(tok, tok2, parent, mSettings); } else - foundError = checkEvaluationOrderC(tok, tok2, parent, *mSettings, bSelfAssignmentError); + foundError = checkEvaluationOrderC(tok, tok2, parent, mSettings, bSelfAssignmentError); if (foundError) { unknownEvaluationOrder(parent, foundUnspecified); @@ -3902,7 +4036,7 @@ void CheckOther::checkEvaluationOrder() } } -void CheckOther::unknownEvaluationOrder(const Token* tok, bool isUnspecifiedBehavior) +void CheckOtherImpl::unknownEvaluationOrder(const Token* tok, bool isUnspecifiedBehavior) { isUnspecifiedBehavior ? reportError(tok, Severity::portability, "unknownEvaluationOrder", @@ -3911,14 +4045,14 @@ void CheckOther::unknownEvaluationOrder(const Token* tok, bool isUnspecifiedBeha "Expression '" + (tok ? tok->expressionString() : std::string("x = x++;")) + "' depends on order of evaluation of side effects", CWE768, Certainty::normal); } -void CheckOther::checkAccessOfMovedVariable() +void CheckOtherImpl::checkAccessOfMovedVariable() { - if (!mTokenizer->isCPP() || mSettings->standards.cpp < Standards::CPP11) + if (!mTokenizer->isCPP() || mSettings.standards.cpp < Standards::CPP11) return; - if (!mSettings->isPremiumEnabled("accessMoved") && !mSettings->severity.isEnabled(Severity::warning)) + if (!mSettings.isPremiumEnabled("accessMoved") && !mSettings.severity.isEnabled(Severity::warning)) return; logChecker("CheckOther::checkAccessOfMovedVariable"); // c++11,warning - const bool reportInconclusive = mSettings->certainty.isEnabled(Certainty::inconclusive); + const bool reportInconclusive = mSettings.certainty.isEnabled(Certainty::inconclusive); const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); for (const Scope * scope : symbolDatabase->functionScopes) { const Token * scopeStart = scope->bodyStart; @@ -3944,11 +4078,11 @@ void CheckOther::checkAccessOfMovedVariable() else inconclusive = true; } else { - const ExprUsage usage = getExprUsage(tok, 0, *mSettings); + const ExprUsage usage = getExprUsage(tok, 0, mSettings); if (usage == ExprUsage::Used) accessOfMoved = true; if (usage == ExprUsage::PassedByReference) - accessOfMoved = !isVariableChangedByFunctionCall(tok, 0, *mSettings, &inconclusive); + accessOfMoved = !isVariableChangedByFunctionCall(tok, 0, mSettings.library, &inconclusive); else if (usage == ExprUsage::Inconclusive) inconclusive = true; } @@ -3958,7 +4092,7 @@ void CheckOther::checkAccessOfMovedVariable() } } -void CheckOther::accessMovedError(const Token *tok, const std::string &varname, const ValueFlow::Value *value, bool inconclusive) +void CheckOtherImpl::accessMovedError(const Token *tok, const std::string &varname, const ValueFlow::Value *value, bool inconclusive) { if (!tok) { reportError(tok, Severity::warning, "accessMoved", "Access of moved variable 'v'.", CWE672, Certainty::normal); @@ -3987,13 +4121,13 @@ void CheckOther::accessMovedError(const Token *tok, const std::string &varname, -void CheckOther::checkFuncArgNamesDifferent() +void CheckOtherImpl::checkFuncArgNamesDifferent() { - const bool style = mSettings->severity.isEnabled(Severity::style); - const bool inconclusive = mSettings->certainty.isEnabled(Certainty::inconclusive); - const bool warning = mSettings->severity.isEnabled(Severity::warning); + const bool style = mSettings.severity.isEnabled(Severity::style); + const bool inconclusive = mSettings.certainty.isEnabled(Certainty::inconclusive); + const bool warning = mSettings.severity.isEnabled(Severity::warning); - if (!(warning || (style && inconclusive)) && !mSettings->isPremiumEnabled("funcArgNamesDifferent")) + if (!(warning || (style && inconclusive)) && !mSettings.isPremiumEnabled("funcArgNamesDifferent")) return; logChecker("CheckOther::checkFuncArgNamesDifferent"); // style,warning,inconclusive @@ -4014,14 +4148,14 @@ void CheckOther::checkFuncArgNamesDifferent() std::vector declarations(function->argCount()); std::vector definitions(function->argCount()); const Token * decl = function->argDef->next(); - for (int j = 0; j < function->argCount(); ++j) { + for (size_t j = 0; j < function->argCount(); ++j) { // get the definition const Variable * variable = function->getArgumentVar(j); if (variable) { definitions[j] = variable->nameToken(); } // get the declaration (search for first token with varId) - while (decl && !Token::Match(decl, ",|)|;")) { + while (decl && !Token::Match(decl, "[,;]")) { // skip everything after the assignment because // it could also have a varId or be the first // token with a varId if there is no name token @@ -4029,8 +4163,8 @@ void CheckOther::checkFuncArgNamesDifferent() decl = decl->nextArgument(); break; } - // skip over template - if (decl->link() && decl->str() == "<") + // skip over templates and arrays + if (decl->link() && precedes(decl, decl->link()) && !Token::Match(decl, "( [*&]")) decl = decl->link(); else if (decl->varId()) declarations[j] = decl; @@ -4042,11 +4176,11 @@ void CheckOther::checkFuncArgNamesDifferent() // check for different argument order if (warning) { bool order_different = false; - for (int j = 0; j < function->argCount(); ++j) { + for (size_t j = 0; j < function->argCount(); ++j) { if (!declarations[j] || !definitions[j] || declarations[j]->str() == definitions[j]->str()) continue; - for (int k = 0; k < function->argCount(); ++k) { + for (size_t k = 0; k < function->argCount(); ++k) { if (j != k && definitions[k] && declarations[j]->str() == definitions[k]->str()) { order_different = true; break; @@ -4060,29 +4194,32 @@ void CheckOther::checkFuncArgNamesDifferent() } // check for different argument names if (style && inconclusive) { - for (int j = 0; j < function->argCount(); ++j) { - if (declarations[j] && definitions[j] && declarations[j]->str() != definitions[j]->str()) + for (size_t j = 0; j < function->argCount(); ++j) { + const bool warn = (declarations[j] != nullptr) != (definitions[j] != nullptr) || + (declarations[j] && definitions[j] && declarations[j]->str() != definitions[j]->str()); + if (warn) funcArgNamesDifferent(function->name(), j, declarations[j], definitions[j]); } } } } -void CheckOther::funcArgNamesDifferent(const std::string & functionName, nonneg int index, - const Token* declaration, const Token* definition) +void CheckOtherImpl::funcArgNamesDifferent(const std::string & functionName, size_t index, + const Token* declaration, const Token* definition) { std::list tokens = { declaration,definition }; - reportError(tokens, Severity::style, "funcArgNamesDifferent", + const std::string id = (declaration != nullptr) == (definition != nullptr) ? "funcArgNamesDifferent" : "funcArgNamesDifferentUnnamed"; + reportError(tokens, Severity::style, id, "$symbol:" + functionName + "\n" "Function '$symbol' argument " + std::to_string(index + 1) + " names different: declaration '" + - (declaration ? declaration->str() : std::string("A")) + "' definition '" + - (definition ? definition->str() : std::string("B")) + "'.", CWE628, Certainty::inconclusive); + (declaration ? declaration->str() : "") + "' definition '" + + (definition ? definition->str() : "") + "'.", CWE628, Certainty::inconclusive); } -void CheckOther::funcArgOrderDifferent(const std::string & functionName, - const Token* declaration, const Token* definition, - const std::vector & declarations, - const std::vector & definitions) +void CheckOtherImpl::funcArgOrderDifferent(const std::string & functionName, + const Token* declaration, const Token* definition, + const std::vector & declarations, + const std::vector & definitions) { std::list tokens = { !declarations.empty() ? declarations[0] ? declarations[0] : declaration : nullptr, @@ -4106,33 +4243,34 @@ void CheckOther::funcArgOrderDifferent(const std::string & functionName, reportError(tokens, Severity::warning, "funcArgOrderDifferent", msg, CWE683, Certainty::normal); } -static const Token *findShadowed(const Scope *scope, const Variable& var, int linenr) +static const Token *findShadowed(const Scope *scope, const Variable& var) { if (!scope) return nullptr; for (const Variable &v : scope->varlist) { - if (scope->isExecutable() && v.nameToken()->linenr() > linenr) + if ((scope->isExecutable() || scope->type == ScopeType::eGlobal) && succeeds(v.nameToken(), var.nameToken())) continue; if (v.name() == var.name()) return v.nameToken(); } auto it = std::find_if(scope->functionList.cbegin(), scope->functionList.cend(), [&](const Function& f) { - return f.type == FunctionType::eFunction && f.name() == var.name() && precedes(f.tokenDef, var.nameToken()); + return f.type == FunctionType::eFunction && f.name() == var.name() + && (scope->isClassOrStructOrUnion() || precedes(f.tokenDef, var.nameToken())); }); if (it != scope->functionList.end()) return it->tokenDef; if (scope->type == ScopeType::eLambda) return nullptr; - const Token* shadowed = findShadowed(scope->nestedIn, var, linenr); + const Token* shadowed = findShadowed(scope->nestedIn, var); if (!shadowed) - shadowed = findShadowed(scope->functionOf, var, linenr); + shadowed = findShadowed(scope->functionOf, var); return shadowed; } -void CheckOther::checkShadowVariables() +void CheckOtherImpl::checkShadowVariables() { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("shadowVariable")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("shadowVariable")) return; logChecker("CheckOther::checkShadowVariables"); // style const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); @@ -4142,46 +4280,69 @@ void CheckOther::checkShadowVariables() const Scope *functionScope = &scope; while (functionScope && functionScope->type != ScopeType::eFunction && functionScope->type != ScopeType::eLambda) functionScope = functionScope->nestedIn; - for (const Variable &var : scope.varlist) { - if (var.nameToken() && var.nameToken()->isExpandedMacro()) // #8903 - continue; + const auto checkVar = [&](const Variable &var) { + if (!var.nameToken()) + return; - if (functionScope && functionScope->type == ScopeType::eFunction && functionScope->function) { + if (var.nameToken()->isExpandedMacro()) // #8903 + return; + + if (!var.isArgument() && functionScope && functionScope->type == ScopeType::eFunction && functionScope->function) { const auto & argList = functionScope->function->argumentList; auto it = std::find_if(argList.cbegin(), argList.cend(), [&](const Variable& arg) { return arg.nameToken() && var.name() == arg.name(); }); if (it != argList.end()) { - shadowError(var.nameToken(), it->nameToken(), "argument"); - continue; + shadowError(var.nameToken(), "local variable", it->nameToken(), "argument"); + return; } } - const Token *shadowed = findShadowed(scope.nestedIn, var, var.nameToken()->linenr()); + const Token *shadowed = findShadowed(scope.nestedIn, var); if (!shadowed) - shadowed = findShadowed(scope.functionOf, var, var.nameToken()->linenr()); + shadowed = findShadowed(scope.functionOf, var); if (!shadowed) - continue; + return; if (scope.type == ScopeType::eFunction && scope.className == var.name()) - continue; + return; if (functionScope->functionOf && functionScope->functionOf->isClassOrStructOrUnion() && functionScope->function && (functionScope->function->isStatic() || functionScope->function->isFriend()) && shadowed->variable() && !shadowed->variable()->isLocal()) - continue; - shadowError(var.nameToken(), shadowed, (shadowed->varId() != 0) ? "variable" : "function"); - } + return; + if (functionScope->functionOf && functionScope->functionOf->isClassOrStructOrUnion() && functionScope->function && + functionScope->function->isStatic() && shadowed->function() && !shadowed->function()->isStatic()) + return; + if (var.scope() && var.scope()->function && var.scope()->function->isConstructor()) { + if (shadowed->variable() && shadowed->variable()->isMember()) + return; + if (shadowed->function() && shadowed->function()->nestedIn && + shadowed->function()->nestedIn->isClassOrStruct()) + return; + } + shadowError(var.nameToken(), var.isArgument() ? "argument" : "local variable", + shadowed, (shadowed->varId() != 0) ? + (shadowed->variable()->isMember() ? "member" : "variable") : "function"); + }; + for (const Variable &var : scope.varlist) + checkVar(var); + if (functionScope && functionScope->type == ScopeType::eFunction && functionScope->function) + for (const Variable &arg: functionScope->function->argumentList) + checkVar(arg); } } -void CheckOther::shadowError(const Token *var, const Token *shadowed, const std::string& type) +void CheckOtherImpl::shadowError(const Token *shadows, const std::string &shadowsType, + const Token *shadowed, const std::string &shadowedType) { - ErrorPath errorPath; - errorPath.emplace_back(shadowed, "Shadowed declaration"); - errorPath.emplace_back(var, "Shadow variable"); - const std::string &varname = var ? var->str() : type; - const std::string Type = char(std::toupper(type[0])) + type.substr(1); - const std::string id = "shadow" + Type; - const std::string message = "$symbol:" + varname + "\nLocal variable \'$symbol\' shadows outer " + type; + ErrorPath errorPath{ + {shadowed, "Shadowed " + shadowedType}, + {shadows, "Shadow " + shadowsType} + }; + const std::string &varname = shadows ? shadows->str() : shadowsType; + const std::string ShadowsType = char(std::toupper(shadowsType[0])) + shadowsType.substr(1); + const std::string ShadowedType = char(std::toupper(shadowedType[0])) + shadowedType.substr(1); + const std::string id = "shadow" + ShadowedType; + const std::string message = "$symbol:" + varname + "\n" + ShadowsType + " \'$symbol\' shadows outer " + shadowedType; reportError(std::move(errorPath), Severity::style, id.c_str(), message, CWE398, Certainty::normal); } @@ -4212,9 +4373,9 @@ static bool isVariableExprHidden(const Token* tok) return false; } -void CheckOther::checkKnownArgument() +void CheckOtherImpl::checkKnownArgument() { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("knownArgument")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("knownArgument")) return; logChecker("CheckOther::checkKnownArgument"); // style const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); @@ -4249,7 +4410,7 @@ void CheckOther::checkKnownArgument() continue; if (tok->isComparisonOp() && isSameExpression( - true, tok->astOperand1(), tok->astOperand2(), *mSettings, true, true)) + true, tok->astOperand1(), tok->astOperand2(), mSettings, true, true)) continue; // ensure that there is a integer variable in expression with unknown value const Token* vartok = nullptr; @@ -4281,7 +4442,7 @@ void CheckOther::checkKnownArgument() } } -void CheckOther::knownArgumentError(const Token *tok, const Token *ftok, const ValueFlow::Value *value, const std::string &varexpr, bool isVariableExpressionHidden) +void CheckOtherImpl::knownArgumentError(const Token *tok, const Token *ftok, const ValueFlow::Value *value, const std::string &varexpr, bool isVariableExpressionHidden) { if (!tok) { reportError(tok, Severity::style, "knownArgument", "Argument 'x-x' to function 'func' is always 0. It does not matter what value 'x' has."); @@ -4313,9 +4474,9 @@ void CheckOther::knownArgumentError(const Token *tok, const Token *ftok, const V reportError(std::move(errorPath), Severity::style, id, errmsg, CWE570, Certainty::normal); } -void CheckOther::checkKnownPointerToBool() +void CheckOtherImpl::checkKnownPointerToBool() { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("knownPointerToBool")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("knownPointerToBool")) return; logChecker("CheckOther::checkKnownPointerToBool"); // style const SymbolDatabase* symbolDatabase = mTokenizer->getSymbolDatabase(); @@ -4323,7 +4484,7 @@ void CheckOther::checkKnownPointerToBool() for (const Token* tok = functionScope->bodyStart; tok != functionScope->bodyEnd; tok = tok->next()) { if (!tok->hasKnownIntValue()) continue; - if (!astIsPointer(tok)) + if (!astIsPointer(tok) && !tok->function()) continue; if (Token::Match(tok->astParent(), "?|!|&&|%oror%|%comp%")) continue; @@ -4335,7 +4496,7 @@ void CheckOther::checkKnownPointerToBool() return parent->isExpandedMacro(); })) continue; - if (!isUsedAsBool(tok, *mSettings)) + if (!isUsedAsBool(tok, mSettings)) continue; const ValueFlow::Value& value = tok->values().front(); knownPointerToBoolError(tok, &value); @@ -4343,7 +4504,7 @@ void CheckOther::checkKnownPointerToBool() } } -void CheckOther::knownPointerToBoolError(const Token* tok, const ValueFlow::Value* value) +void CheckOtherImpl::knownPointerToBoolError(const Token* tok, const ValueFlow::Value* value) { if (!tok) { reportError(tok, Severity::style, "knownPointerToBool", "Pointer expression 'p' converted to bool is always true."); @@ -4356,7 +4517,7 @@ void CheckOther::knownPointerToBoolError(const Token* tok, const ValueFlow::Valu reportError(std::move(errorPath), Severity::style, "knownPointerToBool", errmsg, CWE570, Certainty::normal); } -void CheckOther::checkComparePointers() +void CheckOtherImpl::checkComparePointers() { logChecker("CheckOther::checkComparePointers"); const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); @@ -4386,24 +4547,22 @@ void CheckOther::checkComparePointers() continue; if (var1->isRValueReference() || var2->isRValueReference()) continue; - if (const Token* parent2 = getParentLifetime(v2.tokvalue, mSettings->library)) + if (const Token* parent2 = getParentLifetime(v2.tokvalue, mSettings.library)) if (var1 == parent2->variable()) continue; - if (const Token* parent1 = getParentLifetime(v1.tokvalue, mSettings->library)) + if (const Token* parent1 = getParentLifetime(v1.tokvalue, mSettings.library)) if (var2 == parent1->variable()) continue; - comparePointersError(tok, &v1, &v2); + comparePointersError(tok, &v1, &v2, Token::simpleMatch(tok, "-")); } } } -void CheckOther::comparePointersError(const Token *tok, const ValueFlow::Value *v1, const ValueFlow::Value *v2) +void CheckOtherImpl::comparePointersError(const Token *tok, const ValueFlow::Value *v1, const ValueFlow::Value *v2, bool subtract) { ErrorPath errorPath; - std::string verb = "Comparing"; - if (Token::simpleMatch(tok, "-")) - verb = "Subtracting"; - const char * const id = (verb[0] == 'C') ? "comparePointers" : "subtractPointers"; + const std::string verb = subtract ? "Subtracting" : "Comparing"; + const char * const id = subtract ? "subtractPointers" : "comparePointers"; if (v1) { errorPath.emplace_back(v1->tokvalue->variable()->nameToken(), "Variable declared here."); errorPath.insert(errorPath.end(), v1->errorPath.cbegin(), v1->errorPath.cend()); @@ -4417,9 +4576,9 @@ void CheckOther::comparePointersError(const Token *tok, const ValueFlow::Value * std::move(errorPath), Severity::error, id, verb + " pointers that point to different objects", CWE758, Certainty::normal); } -void CheckOther::checkModuloOfOne() +void CheckOtherImpl::checkModuloOfOne() { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("moduloofone")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("moduloofone")) return; logChecker("CheckOther::checkModuloOfOne"); // style @@ -4439,7 +4598,7 @@ void CheckOther::checkModuloOfOne() } } -void CheckOther::checkModuloOfOneError(const Token *tok) +void CheckOtherImpl::checkModuloOfOneError(const Token *tok) { reportError(tok, Severity::style, "moduloofone", "Modulo of one is always equal to zero"); } @@ -4455,7 +4614,7 @@ struct UnionMember { : name(name) , size(size) {} - const std::string &name; + RefThunk name; size_t size; }; @@ -4465,7 +4624,7 @@ struct Union { , name(scope.className) {} const Scope *scope; - const std::string &name; + RefThunk name; std::vector members; const UnionMember *largestMember() const { @@ -4527,9 +4686,9 @@ static bool isZeroInitializer(const Token *tok) } -void CheckOther::checkUnionZeroInit() +void CheckOtherImpl::checkUnionZeroInit() { - if (!mSettings->severity.isEnabled(Severity::portability)) + if (!mSettings.severity.isEnabled(Severity::portability)) return; logChecker("CheckOther::checkUnionZeroInit"); // portability @@ -4537,8 +4696,9 @@ void CheckOther::checkUnionZeroInit() const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); std::unordered_map unionsByScopeId; - const std::vector unions = parseUnions(*symbolDatabase, *mSettings); + const std::vector unions = parseUnions(*symbolDatabase, mSettings); for (const Union &u : unions) { + // cppcheck-suppress useStlAlgorithm - std::transform is cumbersome unionsByScopeId.emplace(u.scope, u); } @@ -4565,15 +4725,15 @@ void CheckOther::checkUnionZeroInit() } } -void CheckOther::unionZeroInitError(const Token *tok, - const UnionMember& largestMember) +void CheckOtherImpl::unionZeroInitError(const Token *tok, + const UnionMember& largestMember) { reportError(tok, Severity::portability, "UnionZeroInit", (tok != nullptr ? "$symbol:" + tok->str() + "\n" : "") + "Zero initializing union '$symbol' does not guarantee " + "its complete storage to be zero initialized as its largest member " + "is not declared as the first member. Consider making " + - largestMember.name + " the first member or favor memset()."); + largestMember.name() + " the first member or favor memset()."); } //----------------------------------------------------------------------------- @@ -4637,7 +4797,7 @@ static bool getBufAndOffset(const Token *expr, const Token *&buf, MathLib::bigin return true; } -void CheckOther::checkOverlappingWrite() +void CheckOtherImpl::checkOverlappingWrite() { logChecker("CheckOther::checkOverlappingWrite"); const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); @@ -4676,7 +4836,7 @@ void CheckOther::checkOverlappingWrite() if (errorToken) overlappingWriteUnion(tok); } else if (Token::Match(tok, "%name% (")) { - const Library::NonOverlappingData *nonOverlappingData = mSettings->library.getNonOverlappingData(tok); + const Library::NonOverlappingData *nonOverlappingData = mSettings.library.getNonOverlappingData(tok); if (!nonOverlappingData) continue; const std::vector args = getArguments(tok); @@ -4701,7 +4861,7 @@ void CheckOther::checkOverlappingWrite() constexpr bool macro = true; constexpr bool pure = true; constexpr bool follow = true; - if (!isSameExpression(macro, ptr1, ptr2, *mSettings, pure, follow, &errorPath)) + if (!isSameExpression(macro, ptr1, ptr2, mSettings, pure, follow, &errorPath)) continue; overlappingWriteFunction(tok, tok->str()); } @@ -4713,9 +4873,9 @@ void CheckOther::checkOverlappingWrite() MathLib::bigint sizeValue = args[sizeArg-1]->getKnownIntValue(); const Token *buf1, *buf2; MathLib::bigint offset1, offset2; - if (!getBufAndOffset(ptr1, buf1, &offset1, *mSettings, isCountArg ? &sizeValue : nullptr)) + if (!getBufAndOffset(ptr1, buf1, &offset1, mSettings, isCountArg ? &sizeValue : nullptr)) continue; - if (!getBufAndOffset(ptr2, buf2, &offset2, *mSettings)) + if (!getBufAndOffset(ptr2, buf2, &offset2, mSettings)) continue; if (offset1 < offset2 && offset1 + sizeValue <= offset2) @@ -4727,7 +4887,7 @@ void CheckOther::checkOverlappingWrite() constexpr bool macro = true; constexpr bool pure = true; constexpr bool follow = true; - if (!isSameExpression(macro, buf1, buf2, *mSettings, pure, follow, &errorPath)) + if (!isSameExpression(macro, buf1, buf2, mSettings, pure, follow, &errorPath)) continue; overlappingWriteFunction(tok, tok->str()); } @@ -4735,19 +4895,19 @@ void CheckOther::checkOverlappingWrite() } } -void CheckOther::overlappingWriteUnion(const Token *tok) +void CheckOtherImpl::overlappingWriteUnion(const Token *tok) { reportError(tok, Severity::error, "overlappingWriteUnion", "Overlapping read/write of union is undefined behavior"); } -void CheckOther::overlappingWriteFunction(const Token *tok, const std::string& funcname) +void CheckOtherImpl::overlappingWriteFunction(const Token *tok, const std::string& funcname) { reportError(tok, Severity::error, "overlappingWriteFunction", "Overlapping read/write in " + funcname + "() is undefined behavior"); } -void CheckOther::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) +void CheckOther::runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) { - CheckOther checkOther(&tokenizer, &tokenizer.getSettings(), errorLogger); + CheckOtherImpl checkOther(&tokenizer, tokenizer.getSettings(), errorLogger); // Checks checkOther.warningOldStylePointerCast(); @@ -4758,6 +4918,7 @@ void CheckOther::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) checkOther.checkCharVariable(); checkOther.redundantBitwiseOperationInSwitchError(); checkOther.checkSuspiciousCaseInSwitch(); + checkOther.checkUnreachableSwitchCase(); checkOther.checkDuplicateBranch(); checkOther.checkDuplicateExpression(); checkOther.checkRedundantAssignment(); @@ -4797,16 +4958,16 @@ void CheckOther::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) checkOther.checkUnionZeroInit(); } -void CheckOther::getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const +void CheckOther::getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const { - CheckOther c(nullptr, settings, errorLogger); + CheckOtherImpl c(nullptr, settings, errorLogger); // error c.zerodivError(nullptr, nullptr); c.misusedScopeObjectError(nullptr, "varname"); c.invalidPointerCastError(nullptr, "float *", "double *", false, false); - c.negativeBitwiseShiftError(nullptr, 1); - c.negativeBitwiseShiftError(nullptr, 2); + c.negativeBitwiseShiftError(nullptr, true); + c.negativeBitwiseShiftError(nullptr, false); c.raceAfterInterlockedDecrementError(nullptr); c.invalidFreeError(nullptr, "malloc", false); c.overlappingWriteUnion(nullptr); @@ -4845,6 +5006,7 @@ void CheckOther::getErrorMessages(ErrorLogger *errorLogger, const Settings *sett c.duplicateExpressionTernaryError(nullptr, ErrorPath{}); c.duplicateBreakError(nullptr, false); c.unreachableCodeError(nullptr, nullptr, false); + c.unreachableSwitchCaseError(nullptr, "case", "0"); c.unsignedLessThanZeroError(nullptr, nullptr, "varname"); c.unsignedPositiveError(nullptr, nullptr, "varname"); c.pointerLessThanZeroError(nullptr, nullptr); @@ -4863,13 +5025,14 @@ void CheckOther::getErrorMessages(ErrorLogger *errorLogger, const Settings *sett c.accessMovedError(nullptr, "v", nullptr, false); c.funcArgNamesDifferent("function", 1, nullptr, nullptr); c.redundantBitwiseOperationInSwitchError(nullptr, "varname"); - c.shadowError(nullptr, nullptr, "variable"); - c.shadowError(nullptr, nullptr, "function"); - c.shadowError(nullptr, nullptr, "argument"); + c.shadowError(nullptr, "local variable", nullptr, "variable"); + c.shadowError(nullptr, "local variable", nullptr, "argument"); + c.shadowError(nullptr, "local variable", nullptr, "function"); + c.shadowError(nullptr, "local variable", nullptr, "member"); c.knownArgumentError(nullptr, nullptr, nullptr, "x", false); c.knownPointerToBoolError(nullptr, nullptr); - c.comparePointersError(nullptr, nullptr, nullptr); - // TODO: subtractPointers + c.comparePointersError(nullptr, nullptr, nullptr, false); + c.comparePointersError(nullptr, nullptr, nullptr, true); c.redundantAssignmentError(nullptr, nullptr, "var", false); c.redundantInitializationError(nullptr, nullptr, "var", false); c.redundantContinueError(nullptr); diff --git a/lib/checkother.h b/lib/checkother.h index 28ee25cfa76..61a865e9ab6 100644 --- a/lib/checkother.h +++ b/lib/checkother.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,6 +23,7 @@ //--------------------------------------------------------------------------- #include "check.h" +#include "checkimpl.h" #include "config.h" #include "errortypes.h" @@ -49,28 +50,87 @@ struct UnionMember; /** @brief Various small checks */ class CPPCHECKLIB CheckOther : public Check { - friend class TestCharVar; - friend class TestIncompleteStatement; - friend class TestOther; - public: /** @brief This constructor is used when registering the CheckClass */ - CheckOther() : Check(myName()) {} + CheckOther() : Check("Other") {} - /** Is expression a comparison that checks if a nonzero (unsigned/pointer) expression is less than zero? */ - static bool comparisonNonZeroExpressionLessThanZero(const Token *tok, const ValueFlow::Value *&zeroValue, const Token *&nonZeroExpr, bool suppress = false); +private: + /** @brief Run checks against the normal token list */ + void runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) override; - /** Is expression a comparison that checks if a nonzero (unsigned/pointer) expression is positive? */ - static bool testIfNonZeroExpressionIsPositive(const Token *tok, const ValueFlow::Value *&zeroValue, const Token *&nonZeroExpr); + void getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const override; -private: + std::string classInfo() const override { + return "Other checks\n" + + // error + "- division with zero\n" + "- scoped object destroyed immediately after construction\n" + "- assignment in an assert statement\n" + "- free() or delete of an invalid memory location\n" + "- bitwise operation with negative right operand\n" + "- cast the return values of getc(),fgetc() and getchar() to character and compare it to EOF\n" + "- race condition with non-interlocked access after InterlockedDecrement() call\n" + "- expression 'x = x++;' depends on order of evaluation of side effects\n" + "- overlapping write of union\n" + + // warning + "- either division by zero or useless condition\n" + "- access of moved or forwarded variable.\n" + + // performance + "- redundant data copying for const variable\n" + "- subsequent assignment or copying to a variable or buffer\n" + "- passing parameter by value\n" + + // portability + "- Passing NULL pointer to function with variable number of arguments leads to UB.\n" + + // style + "- C-style pointer cast in C++ code\n" + "- casting between incompatible pointer types\n" + "- [Incomplete statement](IncompleteStatement)\n" + "- [check how signed char variables are used](CharVar)\n" + "- variable scope can be limited\n" + "- unusual pointer arithmetic. For example: \"abc\" + 'd'\n" + "- redundant assignment, increment, or bitwise operation in a switch statement\n" + "- redundant strcpy in a switch statement\n" + "- Suspicious case labels in switch()\n" + "- assignment of a variable to itself\n" + "- Comparison of values leading always to true or false\n" + "- Clarify calculation with parentheses\n" + "- suspicious comparison of '\\0' with a char\\* variable\n" + "- duplicate break statement\n" + "- unreachable code\n" + "- testing if unsigned variable is negative/positive\n" + "- Suspicious use of ; at the end of 'if/for/while' statement.\n" + "- Array filled incompletely using memset/memcpy/memmove.\n" + "- NaN (not a number) value used in arithmetic expression.\n" + "- comma in return statement (the comma can easily be misread as a semicolon).\n" + "- prefer erfc, expm1 or log1p to avoid loss of precision.\n" + "- identical code in both branches of if/else or ternary operator.\n" + "- redundant pointer operation on pointer like &\\*some_ptr.\n" + "- find unused 'goto' labels.\n" + "- function declaration and definition argument names different.\n" + "- function declaration and definition argument order different.\n" + "- shadow variable.\n" + "- variable can be declared const.\n" + "- calculating modulo of one.\n" + "- known function argument, suspicious calculation.\n"; + } +}; + +class CPPCHECKLIB CheckOtherImpl : public CheckImpl { +public: /** @brief This constructor is used when running checks. */ - CheckOther(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) - : Check(myName(), tokenizer, settings, errorLogger) {} + CheckOtherImpl(const Tokenizer *tokenizer, const Settings &settings, ErrorLogger &errorLogger) + : CheckImpl(tokenizer, settings, errorLogger) {} + /** Is expression a comparison that checks if a nonzero (unsigned/pointer) expression is less than zero? */ + static bool comparisonNonZeroExpressionLessThanZero(const Token *tok, const ValueFlow::Value *&zeroValue, const Token *&nonZeroExpr, bool suppress = false); - /** @brief Run checks against the normal token list */ - void runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) override; + /** Is expression a comparison that checks if a nonzero (unsigned/pointer) expression is positive? */ + static bool testIfNonZeroExpressionIsPositive(const Token *tok, const ValueFlow::Value *&zeroValue, const Token *&nonZeroExpr); /** @brief Clarify calculation for ".. a * b ? .." */ void clarifyCalculation(); @@ -126,6 +186,9 @@ class CPPCHECKLIB CheckOther : public Check { /** @brief %Check for code like 'case A||B:'*/ void checkSuspiciousCaseInSwitch(); + /** @brief %Check for case labels that cannot be selected */ + void checkUnreachableSwitchCase(); + /** @brief %Check for objects that are destroyed immediately */ void checkMisusedScopedObject(); @@ -215,7 +278,7 @@ class CPPCHECKLIB CheckOther : public Check { void suspiciousFloatingPointCastError(const Token *tok); void invalidPointerCastError(const Token* tok, const std::string& from, const std::string& to, bool inconclusive, bool toIsInt); void passedByValueError(const Variable* var, bool inconclusive, bool isRangeBasedFor = false); - void constVariableError(const Variable *var, const Function *function); + void constVariableError(const Variable *var, const Function *function, bool foundAllBaseClasses = true); void constStatementError(const Token *tok, const std::string &type, bool inconclusive); void signedCharArrayIndexError(const Token *tok); void unknownSignCharArrayIndexError(const Token *tok); @@ -230,6 +293,7 @@ class CPPCHECKLIB CheckOther : public Check { void redundantCopyError(const Token *tok1, const Token* tok2, const std::string& var); void redundantBitwiseOperationInSwitchError(const Token *tok, const std::string &varname); void suspiciousCaseInSwitchError(const Token* tok, const std::string& operatorString); + void unreachableSwitchCaseError(const Token* tok, const std::string& caseExpression, const std::string& switchValue); void selfAssignmentError(const Token *tok, const std::string &varname); void misusedScopeObjectError(const Token *tok, const std::string &varname, bool isAssignment = false); void duplicateBranchError(const Token *tok1, const Token *tok2, ErrorPath errors); @@ -246,7 +310,7 @@ class CPPCHECKLIB CheckOther : public Check { void unsignedPositiveError(const Token *tok, const ValueFlow::Value *v, const std::string &varname); void pointerPositiveError(const Token *tok, const ValueFlow::Value *v); void suspiciousSemicolonError(const Token *tok); - void negativeBitwiseShiftError(const Token *tok, int op); + void negativeBitwiseShiftError(const Token *tok, bool isLHS, const ValueFlow::Value *v = nullptr); void redundantCopyError(const Token *tok, const std::string &varname); void incompleteArrayFillError(const Token* tok, const std::string& buffer, const std::string& function, bool boolean); void varFuncNullUBError(const Token *tok); @@ -256,83 +320,15 @@ class CPPCHECKLIB CheckOther : public Check { void unusedLabelError(const Token* tok, bool inSwitch, bool hasIfdef); void unknownEvaluationOrder(const Token* tok, bool isUnspecifiedBehavior = false); void accessMovedError(const Token *tok, const std::string &varname, const ValueFlow::Value *value, bool inconclusive); - void funcArgNamesDifferent(const std::string & functionName, nonneg int index, const Token* declaration, const Token* definition); + void funcArgNamesDifferent(const std::string & functionName, size_t index, const Token* declaration, const Token* definition); void funcArgOrderDifferent(const std::string & functionName, const Token * declaration, const Token * definition, const std::vector & declarations, const std::vector & definitions); - void shadowError(const Token *var, const Token *shadowed, const std::string& type); + void shadowError(const Token *shadows, const std::string &shadowsType, const Token *shadowed, const std::string &shadowedType); void knownArgumentError(const Token *tok, const Token *ftok, const ValueFlow::Value *value, const std::string &varexpr, bool isVariableExpressionHidden); void knownPointerToBoolError(const Token* tok, const ValueFlow::Value* value); - void comparePointersError(const Token *tok, const ValueFlow::Value *v1, const ValueFlow::Value *v2); + void comparePointersError(const Token *tok, const ValueFlow::Value *v1, const ValueFlow::Value *v2, bool subtract); void checkModuloOfOneError(const Token *tok); void unionZeroInitError(const Token *tok, const UnionMember& largestMember); - void getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const override; - - static std::string myName() { - return "Other"; - } - - std::string classInfo() const override { - return "Other checks\n" - - // error - "- division with zero\n" - "- scoped object destroyed immediately after construction\n" - "- assignment in an assert statement\n" - "- free() or delete of an invalid memory location\n" - "- bitwise operation with negative right operand\n" - "- cast the return values of getc(),fgetc() and getchar() to character and compare it to EOF\n" - "- race condition with non-interlocked access after InterlockedDecrement() call\n" - "- expression 'x = x++;' depends on order of evaluation of side effects\n" - "- overlapping write of union\n" - - // warning - "- either division by zero or useless condition\n" - "- access of moved or forwarded variable.\n" - "- potentially dangerous C style type cast of pointer/reference to object.\n" - - // performance - "- redundant data copying for const variable\n" - "- subsequent assignment or copying to a variable or buffer\n" - "- passing parameter by value\n" - - // portability - "- Passing NULL pointer to function with variable number of arguments leads to UB.\n" - "- Casting non-zero integer literal in decimal or octal format to pointer.\n" - "- Incorrect zero initialization of unions can lead to access of uninitialized memory.\n" - - // style - "- C-style pointer cast in C++ code\n" - "- casting between incompatible pointer types\n" - "- [Incomplete statement](IncompleteStatement)\n" - "- [check how signed char variables are used](CharVar)\n" - "- variable scope can be limited\n" - "- unusual pointer arithmetic. For example: \"abc\" + 'd'\n" - "- redundant assignment, increment, or bitwise operation in a switch statement\n" - "- redundant strcpy in a switch statement\n" - "- Suspicious case labels in switch()\n" - "- assignment of a variable to itself\n" - "- Comparison of values leading always to true or false\n" - "- Clarify calculation with parentheses\n" - "- suspicious comparison of '\\0' with a char\\* variable\n" - "- duplicate break statement\n" - "- unreachable code\n" - "- testing if unsigned variable is negative/positive\n" - "- Suspicious use of ; at the end of 'if/for/while' statement.\n" - "- Array filled incompletely using memset/memcpy/memmove.\n" - "- NaN (not a number) value used in arithmetic expression.\n" - "- comma in return statement (the comma can easily be misread as a semicolon).\n" - "- prefer erfc, expm1 or log1p to avoid loss of precision.\n" - "- identical code in both branches of if/else or ternary operator.\n" - "- redundant pointer operation on pointer like &\\*some_ptr.\n" - "- find unused 'goto' labels.\n" - "- function declaration and definition argument names different.\n" - "- function declaration and definition argument order different.\n" - "- shadow variable.\n" - "- variable can be declared const.\n" - "- calculating modulo of one.\n" - "- known function argument, suspicious calculation.\n"; - } - bool diag(const Token* tok) { return !mRedundantAssignmentDiag.emplace(tok).second; } diff --git a/lib/checkpostfixoperator.cpp b/lib/checkpostfixoperator.cpp index c6c9ccba77b..f15fa293c49 100644 --- a/lib/checkpostfixoperator.cpp +++ b/lib/checkpostfixoperator.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -34,19 +34,12 @@ //--------------------------------------------------------------------------- -// Register this check class (by creating a static instance of it) -namespace { - CheckPostfixOperator instance; -} - - // CWE ids used static const CWE CWE398(398U); // Indicator of Poor Code Quality - -void CheckPostfixOperator::postfixOperator() +void CheckPostfixOperatorImpl::postfixOperator() { - if (!mSettings->severity.isEnabled(Severity::performance)) + if (!mSettings.severity.isEnabled(Severity::performance)) return; logChecker("CheckPostfixOperator::postfixOperator"); // performance @@ -90,7 +83,7 @@ void CheckPostfixOperator::postfixOperator() //--------------------------------------------------------------------------- -void CheckPostfixOperator::postfixOperatorError(const Token *tok) +void CheckPostfixOperatorImpl::postfixOperatorError(const Token *tok) { reportError(tok, Severity::performance, "postfixOperator", "Prefer prefix ++/-- operators for non-primitive types.\n" @@ -101,17 +94,17 @@ void CheckPostfixOperator::postfixOperatorError(const Token *tok) "adds a little extra code.", CWE398, Certainty::normal); } -void CheckPostfixOperator::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) +void CheckPostfixOperator::runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) { if (tokenizer.isC()) return; - CheckPostfixOperator checkPostfixOperator(&tokenizer, &tokenizer.getSettings(), errorLogger); + CheckPostfixOperatorImpl checkPostfixOperator(&tokenizer, tokenizer.getSettings(), errorLogger); checkPostfixOperator.postfixOperator(); } -void CheckPostfixOperator::getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const +void CheckPostfixOperator::getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const { - CheckPostfixOperator c(nullptr, settings, errorLogger); + CheckPostfixOperatorImpl c(nullptr, settings, errorLogger); c.postfixOperatorError(nullptr); } diff --git a/lib/checkpostfixoperator.h b/lib/checkpostfixoperator.h index c9f58a7e143..5898cefcc3c 100644 --- a/lib/checkpostfixoperator.h +++ b/lib/checkpostfixoperator.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,6 +23,7 @@ //--------------------------------------------------------------------------- #include "check.h" +#include "checkimpl.h" #include "config.h" #include @@ -40,34 +41,30 @@ class Tokenizer; */ class CPPCHECKLIB CheckPostfixOperator : public Check { - friend class TestPostfixOperator; - public: /** This constructor is used when registering the CheckPostfixOperator */ - CheckPostfixOperator() : Check(myName()) {} + CheckPostfixOperator() : Check("Using postfix operators") {} private: - /** This constructor is used when running checks. */ - CheckPostfixOperator(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) - : Check(myName(), tokenizer, settings, errorLogger) {} + void runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) override; + + void getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const override; + std::string classInfo() const override { + return "Warn if using postfix operators ++ or -- rather than prefix operator\n"; + } +}; - void runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) override; +class CPPCHECKLIB CheckPostfixOperatorImpl : public CheckImpl { +public: + /** This constructor is used when running checks. */ + CheckPostfixOperatorImpl(const Tokenizer *tokenizer, const Settings &settings, ErrorLogger &errorLogger) + : CheckImpl(tokenizer, settings, errorLogger) {} /** Check postfix operators */ void postfixOperator(); /** Report Error */ void postfixOperatorError(const Token *tok); - - void getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const override; - - static std::string myName() { - return "Using postfix operators"; - } - - std::string classInfo() const override { - return "Warn if using postfix operators ++ or -- rather than prefix operator\n"; - } }; /// @} //--------------------------------------------------------------------------- diff --git a/lib/checks.cpp b/lib/checks.cpp new file mode 100644 index 00000000000..ef4b9fef3da --- /dev/null +++ b/lib/checks.cpp @@ -0,0 +1,125 @@ +/* + * Cppcheck - A tool for static C/C++ code analysis + * Copyright (C) 2007-2026 Cppcheck team. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "checks.h" + +#include "check64bit.h" +#include "checkassert.h" +#include "checkautovariables.h" +#include "checkbool.h" +#include "checkbufferoverrun.h" +#include "checkclass.h" +#include "checkcondition.h" +#include "checkexceptionsafety.h" +#include "checkfunctions.h" +#include "checkinternal.h" +#include "checkio.h" +#include "checkleakautovar.h" +#include "checkmemoryleak.h" +#include "checknullpointer.h" +#include "checkother.h" +#include "checkpostfixoperator.h" +#include "checksizeof.h" +#include "checkstl.h" +#include "checkstring.h" +#include "checktype.h" +#include "checkuninitvar.h" +#include "checkunusedvar.h" +#include "checkvaarg.h" + +#include + +class CheckInstancesImpl +{ +private: +/* *INDENT-OFF* */ +#define UPI(c) std::unique_ptr m##c{new c} +/* *INDENT-ON* */ + UPI(Check64BitPortability); + UPI(CheckAssert); + UPI(CheckAutoVariables); + UPI(CheckBool); + UPI(CheckBufferOverrun); + UPI(CheckClass); + UPI(CheckCondition); + UPI(CheckExceptionSafety); + UPI(CheckFunctions); +#ifdef CHECK_INTERNAL + UPI(CheckInternal); +#endif + UPI(CheckIO); + UPI(CheckLeakAutoVar); + UPI(CheckMemoryLeakInFunction); + UPI(CheckMemoryLeakInClass); + UPI(CheckMemoryLeakStructMember); + UPI(CheckMemoryLeakNoVar); + UPI(CheckNullPointer); + UPI(CheckOther); + UPI(CheckPostfixOperator); + UPI(CheckSizeof); + UPI(CheckStl); + UPI(CheckString); + UPI(CheckType); + UPI(CheckUninitVar); + UPI(CheckUnusedVar); + UPI(CheckVaarg); +#undef UPI + +public: + const std::list& get() const + { + static std::list s_checks{ + mCheck64BitPortability.get(), + mCheckAssert.get(), + mCheckAutoVariables.get(), + mCheckBool.get(), + mCheckBufferOverrun.get(), + mCheckClass.get(), + mCheckCondition.get(), + mCheckExceptionSafety.get(), + mCheckFunctions.get(), + #ifdef CHECK_INTERNAL + mCheckInternal.get(), + #endif + mCheckIO.get(), + mCheckLeakAutoVar.get(), + mCheckMemoryLeakInFunction.get(), + mCheckMemoryLeakInClass.get(), + mCheckMemoryLeakStructMember.get(), + mCheckMemoryLeakNoVar.get(), + mCheckNullPointer.get(), + mCheckOther.get(), + mCheckPostfixOperator.get(), + mCheckSizeof.get(), + mCheckStl.get(), + mCheckString.get(), + mCheckType.get(), + mCheckUninitVar.get(), + mCheckUnusedVar.get(), + mCheckVaarg.get() + }; + + return s_checks; + } +}; + +const std::list& CheckInstances::get() +{ + static const CheckInstancesImpl s_impl; + return s_impl.get(); +} diff --git a/lib/checks.h b/lib/checks.h new file mode 100644 index 00000000000..9c053b81405 --- /dev/null +++ b/lib/checks.h @@ -0,0 +1,34 @@ +/* -*- C++ -*- + * Cppcheck - A tool for static C/C++ code analysis + * Copyright (C) 2007-2026 Cppcheck team. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef checksH +#define checksH + +#include "config.h" + +#include + +class Check; + +namespace CheckInstances +{ + /** List of registered check classes. This is used by Cppcheck to run checks and generate documentation */ + CPPCHECKLIB const std::list& get(); +} + +#endif // checksH diff --git a/lib/checksizeof.cpp b/lib/checksizeof.cpp index f7c0d44d399..2d9012078a6 100644 --- a/lib/checksizeof.cpp +++ b/lib/checksizeof.cpp @@ -34,19 +34,14 @@ //--------------------------------------------------------------------------- -// Register this check class (by creating a static instance of it) -namespace { - CheckSizeof instance; -} - // CWE IDs used: static const CWE CWE467(467U); // Use of sizeof() on a Pointer Type static const CWE CWE682(682U); // Incorrect Calculation //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- -void CheckSizeof::checkSizeofForNumericParameter() +void CheckSizeofImpl::checkSizeofForNumericParameter() { - if (!mSettings->severity.isEnabled(Severity::warning)) + if (!mSettings.severity.isEnabled(Severity::warning)) return; logChecker("CheckSizeof::checkSizeofForNumericParameter"); // warning @@ -62,7 +57,7 @@ void CheckSizeof::checkSizeofForNumericParameter() } } -void CheckSizeof::sizeofForNumericParameterError(const Token *tok) +void CheckSizeofImpl::sizeofForNumericParameterError(const Token *tok) { reportError(tok, Severity::warning, "sizeofwithnumericparameter", "Suspicious usage of 'sizeof' with a numeric constant as parameter.\n" @@ -74,9 +69,9 @@ void CheckSizeof::sizeofForNumericParameterError(const Token *tok) //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- -void CheckSizeof::checkSizeofForArrayParameter() +void CheckSizeofImpl::checkSizeofForArrayParameter() { - if (!mSettings->severity.isEnabled(Severity::warning)) + if (!mSettings.severity.isEnabled(Severity::warning)) return; logChecker("CheckSizeof::checkSizeofForArrayParameter"); // warning @@ -92,14 +87,14 @@ void CheckSizeof::checkSizeofForArrayParameter() } const Variable *var = varTok->variable(); - if (var && var->isArray() && var->isArgument() && !var->isReference()) + if (var && var->isArray() && var->isArgument() && !var->isReference() && !(var->isStlType() && var->getTypeName() == "std::array")) sizeofForArrayParameterError(tok); } } } } -void CheckSizeof::sizeofForArrayParameterError(const Token *tok) +void CheckSizeofImpl::sizeofForArrayParameterError(const Token *tok) { reportError(tok, Severity::warning, "sizeofwithsilentarraypointer", "Using 'sizeof' on array given as function argument " @@ -115,9 +110,9 @@ void CheckSizeof::sizeofForArrayParameterError(const Token *tok) ); } -void CheckSizeof::checkSizeofForPointerSize() +void CheckSizeofImpl::checkSizeofForPointerSize() { - if (!mSettings->severity.isEnabled(Severity::warning)) + if (!mSettings.severity.isEnabled(Severity::warning)) return; logChecker("CheckSizeof::checkSizeofForPointerSize"); // warning @@ -134,7 +129,7 @@ void CheckSizeof::checkSizeofForPointerSize() // Once leaving those tests, it is mandatory to have: // - variable matching the used pointer // - tokVar pointing on the argument where sizeof may be used - if (Token::Match(tok->tokAt(2), "%name% (") && mSettings->library.getAllocFuncInfo(tok->tokAt(2))) { + if (Token::Match(tok->tokAt(2), "%name% (") && mSettings.library.getAllocFuncInfo(tok->tokAt(2))) { if (Token::Match(tok, "%var% =")) variable = tok; else if (tok->strAt(1) == ")" && Token::Match(tok->linkAt(1)->tokAt(-2), "%var% =")) @@ -238,7 +233,8 @@ void CheckSizeof::checkSizeofForPointerSize() // Now check for the sizeof usage: Does the level of pointer indirection match? const Token * const tokLink = tokSize->linkAt(1); if (tokLink && tokLink->strAt(-1) == "*") { - if (variable && variable->valueType() && variable->valueType()->pointer == 1 && variable->valueType()->type != ValueType::VOID) + if (variable && variable->valueType() && variable->valueType()->pointer == 1 && variable->valueType()->type != ValueType::VOID && + variable->valueType()->isTypeEqual(tokSize->next()->astOperand2()->valueType())) sizeofForPointerError(variable, variable->str()); else if (variable2 && variable2->valueType() && variable2->valueType()->pointer == 1 && variable2->valueType()->type != ValueType::VOID) sizeofForPointerError(variable2, variable2->str()); @@ -267,7 +263,7 @@ void CheckSizeof::checkSizeofForPointerSize() } } -void CheckSizeof::sizeofForPointerError(const Token *tok, const std::string &varname) +void CheckSizeofImpl::sizeofForPointerError(const Token *tok, const std::string &varname) { reportError(tok, Severity::warning, "pointerSize", "Size of pointer '" + varname + "' used instead of size of its data.\n" @@ -276,7 +272,7 @@ void CheckSizeof::sizeofForPointerError(const Token *tok, const std::string &var "write 'sizeof(*" + varname + ")'.", CWE467, Certainty::normal); } -void CheckSizeof::divideBySizeofError(const Token *tok, const std::string &memfunc) +void CheckSizeofImpl::divideBySizeofError(const Token *tok, const std::string &memfunc) { reportError(tok, Severity::warning, "sizeofDivisionMemfunc", "Division by result of sizeof(). " + memfunc + "() expects a size in bytes, did you intend to multiply instead?", CWE682, Certainty::normal); @@ -284,9 +280,9 @@ void CheckSizeof::divideBySizeofError(const Token *tok, const std::string &memfu //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- -void CheckSizeof::sizeofsizeof() +void CheckSizeofImpl::sizeofsizeof() { - if (!mSettings->severity.isEnabled(Severity::warning)) + if (!mSettings.severity.isEnabled(Severity::warning)) return; logChecker("CheckSizeof::sizeofsizeof"); // warning @@ -299,7 +295,7 @@ void CheckSizeof::sizeofsizeof() } } -void CheckSizeof::sizeofsizeofError(const Token *tok) +void CheckSizeofImpl::sizeofsizeofError(const Token *tok) { reportError(tok, Severity::warning, "sizeofsizeof", "Calling 'sizeof' on 'sizeof'.\n" @@ -310,14 +306,14 @@ void CheckSizeof::sizeofsizeofError(const Token *tok) //----------------------------------------------------------------------------- -void CheckSizeof::sizeofCalculation() +void CheckSizeofImpl::sizeofCalculation() { - if (!mSettings->severity.isEnabled(Severity::warning)) + if (!mSettings.severity.isEnabled(Severity::warning)) return; logChecker("CheckSizeof::sizeofCalculation"); // warning - const bool printInconclusive = mSettings->certainty.isEnabled(Certainty::inconclusive); + const bool printInconclusive = mSettings.certainty.isEnabled(Certainty::inconclusive); for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) { if (!Token::simpleMatch(tok, "sizeof (")) @@ -348,7 +344,7 @@ void CheckSizeof::sizeofCalculation() } } -void CheckSizeof::sizeofCalculationError(const Token *tok, bool inconclusive) +void CheckSizeofImpl::sizeofCalculationError(const Token *tok, bool inconclusive) { reportError(tok, Severity::warning, "sizeofCalculation", "Found calculation inside sizeof().", CWE682, inconclusive ? Certainty::inconclusive : Certainty::normal); @@ -356,9 +352,9 @@ void CheckSizeof::sizeofCalculationError(const Token *tok, bool inconclusive) //----------------------------------------------------------------------------- -void CheckSizeof::sizeofFunction() +void CheckSizeofImpl::sizeofFunction() { - if (!mSettings->severity.isEnabled(Severity::warning) && !mSettings->isPremiumEnabled("sizeofFunctionCall")) + if (!mSettings.severity.isEnabled(Severity::warning) && !mSettings.isPremiumEnabled("sizeofFunctionCall")) return; logChecker("CheckSizeof::sizeofFunction"); // warning @@ -390,7 +386,7 @@ void CheckSizeof::sizeofFunction() } } -void CheckSizeof::sizeofFunctionError(const Token *tok) +void CheckSizeofImpl::sizeofFunctionError(const Token *tok) { reportError(tok, Severity::warning, "sizeofFunctionCall", "Found function call inside sizeof().", CWE682, Certainty::normal); @@ -399,9 +395,9 @@ void CheckSizeof::sizeofFunctionError(const Token *tok) //----------------------------------------------------------------------------- // Check for code like sizeof()*sizeof() or sizeof(ptr)/value //----------------------------------------------------------------------------- -void CheckSizeof::suspiciousSizeofCalculation() +void CheckSizeofImpl::suspiciousSizeofCalculation() { - if (!mSettings->severity.isEnabled(Severity::warning) || !mSettings->certainty.isEnabled(Certainty::inconclusive)) + if (!mSettings.severity.isEnabled(Severity::warning) || !mSettings.certainty.isEnabled(Certainty::inconclusive)) return; logChecker("CheckSizeof::suspiciousSizeofCalculation"); // warning,inconclusive @@ -429,13 +425,13 @@ void CheckSizeof::suspiciousSizeofCalculation() } } -void CheckSizeof::multiplySizeofError(const Token *tok) +void CheckSizeofImpl::multiplySizeofError(const Token *tok) { reportError(tok, Severity::warning, "multiplySizeof", "Multiplying sizeof() with sizeof() indicates a logic error.", CWE682, Certainty::inconclusive); } -void CheckSizeof::divideSizeofError(const Token *tok) +void CheckSizeofImpl::divideSizeofError(const Token *tok) { reportError(tok, Severity::warning, "divideSizeof", "Division of result of sizeof() on pointer type.\n" @@ -443,9 +439,9 @@ void CheckSizeof::divideSizeofError(const Token *tok) "not the size of the memory area it points to.", CWE682, Certainty::inconclusive); } -void CheckSizeof::sizeofVoid() +void CheckSizeofImpl::sizeofVoid() { - if (!mSettings->severity.isEnabled(Severity::portability)) + if (!mSettings.severity.isEnabled(Severity::portability)) return; logChecker("CheckSizeof::sizeofVoid"); // portability @@ -481,30 +477,30 @@ void CheckSizeof::sizeofVoid() } } -void CheckSizeof::sizeofVoidError(const Token *tok) +void CheckSizeofImpl::sizeofVoidError(const Token *tok) { const std::string message = "Behaviour of 'sizeof(void)' is not covered by the ISO C standard."; const std::string verbose = message + " A value for 'sizeof(void)' is defined only as part of a GNU C extension, which defines 'sizeof(void)' to be 1."; reportError(tok, Severity::portability, "sizeofVoid", message + "\n" + verbose, CWE682, Certainty::normal); } -void CheckSizeof::sizeofDereferencedVoidPointerError(const Token *tok, const std::string &varname) +void CheckSizeofImpl::sizeofDereferencedVoidPointerError(const Token *tok, const std::string &varname) { const std::string message = "'*" + varname + "' is of type 'void', the behaviour of 'sizeof(void)' is not covered by the ISO C standard."; const std::string verbose = message + " A value for 'sizeof(void)' is defined only as part of a GNU C extension, which defines 'sizeof(void)' to be 1."; reportError(tok, Severity::portability, "sizeofDereferencedVoidPointer", message + "\n" + verbose, CWE682, Certainty::normal); } -void CheckSizeof::arithOperationsOnVoidPointerError(const Token* tok, const std::string &varname, const std::string &vartype) +void CheckSizeofImpl::arithOperationsOnVoidPointerError(const Token* tok, const std::string &varname, const std::string &vartype) { const std::string message = "'$symbol' is of type '" + vartype + "'. When using void pointers in calculations, the behaviour is undefined."; const std::string verbose = message + " Arithmetic operations on 'void *' is a GNU C extension, which defines the 'sizeof(void)' to be 1."; reportError(tok, Severity::portability, "arithOperationsOnVoidPointer", "$symbol:" + varname + '\n' + message + '\n' + verbose, CWE467, Certainty::normal); } -void CheckSizeof::runChecks(const Tokenizer& tokenizer, ErrorLogger* errorLogger) +void CheckSizeof::runChecks(const Tokenizer& tokenizer, ErrorLogger& errorLogger) { - CheckSizeof checkSizeof(&tokenizer, &tokenizer.getSettings(), errorLogger); + CheckSizeofImpl checkSizeof(&tokenizer, tokenizer.getSettings(), errorLogger); // Checks checkSizeof.sizeofsizeof(); @@ -517,9 +513,9 @@ void CheckSizeof::runChecks(const Tokenizer& tokenizer, ErrorLogger* errorLogger checkSizeof.sizeofVoid(); } -void CheckSizeof::getErrorMessages(ErrorLogger* errorLogger, const Settings* settings) const +void CheckSizeof::getErrorMessages(ErrorLogger& errorLogger, const Settings& settings) const { - CheckSizeof c(nullptr, settings, errorLogger); + CheckSizeofImpl c(nullptr, settings, errorLogger); c.sizeofForArrayParameterError(nullptr); c.sizeofForPointerError(nullptr, "varname"); c.divideBySizeofError(nullptr, "memset"); diff --git a/lib/checksizeof.h b/lib/checksizeof.h index 4c10ca88dac..7540fe6785d 100644 --- a/lib/checksizeof.h +++ b/lib/checksizeof.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,6 +23,7 @@ //--------------------------------------------------------------------------- #include "check.h" +#include "checkimpl.h" #include "config.h" #include @@ -41,15 +42,32 @@ class Tokenizer; class CPPCHECKLIB CheckSizeof : public Check { public: /** @brief This constructor is used when registering the CheckClass */ - CheckSizeof() : Check(myName()) {} + CheckSizeof() : Check("Sizeof") {} private: - /** @brief This constructor is used when running checks. */ - CheckSizeof(const Tokenizer* tokenizer, const Settings* settings, ErrorLogger* errorLogger) - : Check(myName(), tokenizer, settings, errorLogger) {} - /** @brief Run checks against the normal token list */ - void runChecks(const Tokenizer& tokenizer, ErrorLogger* errorLogger) override; + void runChecks(const Tokenizer& tokenizer, ErrorLogger& errorLogger) override; + + void getErrorMessages(ErrorLogger& errorLogger, const Settings& settings) const override; + + std::string classInfo() const override { + return "sizeof() usage checks\n" + "- sizeof for array given as function argument\n" + "- sizeof for numeric given as function argument\n" + "- using sizeof(pointer) instead of the size of pointed data\n" + "- look for 'sizeof sizeof ..'\n" + "- look for calculations inside sizeof()\n" + "- look for function calls inside sizeof()\n" + "- look for suspicious calculations with sizeof()\n" + "- using 'sizeof(void)' which is undefined\n"; + } +}; + +class CPPCHECKLIB CheckSizeofImpl : public CheckImpl { +public: + /** @brief This constructor is used when running checks. */ + CheckSizeofImpl(const Tokenizer* tokenizer, const Settings& settings, ErrorLogger& errorLogger) + : CheckImpl(tokenizer, settings, errorLogger) {} /** @brief %Check for 'sizeof sizeof ..' */ void sizeofsizeof(); @@ -88,24 +106,6 @@ class CPPCHECKLIB CheckSizeof : public Check { void sizeofVoidError(const Token *tok); void sizeofDereferencedVoidPointerError(const Token *tok, const std::string &varname); void arithOperationsOnVoidPointerError(const Token* tok, const std::string &varname, const std::string &vartype); - - void getErrorMessages(ErrorLogger* errorLogger, const Settings* settings) const override; - - static std::string myName() { - return "Sizeof"; - } - - std::string classInfo() const override { - return "sizeof() usage checks\n" - "- sizeof for array given as function argument\n" - "- sizeof for numeric given as function argument\n" - "- using sizeof(pointer) instead of the size of pointed data\n" - "- look for 'sizeof sizeof ..'\n" - "- look for calculations inside sizeof()\n" - "- look for function calls inside sizeof()\n" - "- look for suspicious calculations with sizeof()\n" - "- using 'sizeof(void)' which is undefined\n"; - } }; /// @} //--------------------------------------------------------------------------- diff --git a/lib/checkstl.cpp b/lib/checkstl.cpp index 65bf3792615..0b3bd405c3d 100644 --- a/lib/checkstl.cpp +++ b/lib/checkstl.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,10 +19,12 @@ #include "checkstl.h" #include "astutils.h" +#include "calculate.h" #include "errortypes.h" #include "library.h" #include "mathlib.h" #include "pathanalysis.h" +#include "refthunk.h" #include "settings.h" #include "standards.h" #include "symboldatabase.h" @@ -35,6 +37,7 @@ #include "checknullpointer.h" #include +#include #include #include #include @@ -46,11 +49,6 @@ #include #include -// Register this check class (by creating a static instance of it) -namespace { - CheckStl instance; -} - // CWE IDs used: static const CWE CWE398(398U); // Indicator of Poor Code Quality static const CWE CWE597(597U); // Use of Wrong Operator in String Comparison @@ -132,7 +130,17 @@ static const Token* getContainerFromSize(const Library::Container* container, co return nullptr; } -void CheckStl::outOfBounds() +// A value that out of bounds analysis can use: not impossible, and inconclusive only when enabled +static bool isUsableValue(const ValueFlow::Value& value, const Settings& settings) +{ + if (value.isImpossible()) + return false; + if (value.isInconclusive() && !settings.certainty.isEnabled(Certainty::inconclusive)) + return false; + return true; +} + +void CheckStlImpl::outOfBounds() { logChecker("CheckStl::outOfBounds"); @@ -153,11 +161,9 @@ void CheckStl::outOfBounds() for (const ValueFlow::Value &value : tok->values()) { if (!value.isContainerSizeValue()) continue; - if (value.isImpossible()) + if (!isUsableValue(value, mSettings)) continue; - if (value.isInconclusive() && !mSettings->certainty.isEnabled(Certainty::inconclusive)) - continue; - if (!value.errorSeverity() && !mSettings->severity.isEnabled(Severity::warning)) + if (!value.errorSeverity() && !mSettings.severity.isEnabled(Severity::warning)) continue; if (value.intvalue == 0 && (indexTok || (containerYieldsElement(container, parent) && !containerAppendsElement(container, parent)) || @@ -170,7 +176,7 @@ void CheckStl::outOfBounds() } if (indexTok) { std::vector indexValues = - ValueFlow::isOutOfBounds(value, indexTok, mSettings->severity.isEnabled(Severity::warning)); + ValueFlow::isOutOfBounds(value, indexTok, mSettings.severity.isEnabled(Severity::warning)); if (!indexValues.empty()) { outOfBoundsError( accessTok, tok->expressionString(), &value, indexTok->expressionString(), &indexValues.front()); @@ -180,7 +186,7 @@ void CheckStl::outOfBounds() } if (indexTok && !indexTok->hasKnownIntValue()) { const ValueFlow::Value* value = - ValueFlow::findValue(indexTok->values(), *mSettings, [&](const ValueFlow::Value& v) { + ValueFlow::findValue(indexTok->values(), mSettings, [&](const ValueFlow::Value& v) { if (!v.isSymbolicValue()) return false; if (v.isImpossible()) @@ -220,7 +226,7 @@ static std::string indexValueString(const ValueFlow::Value& indexValue, const st return indexString; } -void CheckStl::outOfBoundsError(const Token *tok, const std::string &containerName, const ValueFlow::Value *containerSize, const std::string &index, const ValueFlow::Value *indexValue) +void CheckStlImpl::outOfBoundsError(const Token *tok, const std::string &containerName, const ValueFlow::Value *containerSize, const std::string &index, const ValueFlow::Value *indexValue) { // Do not warn if both the container size and index value are possible if (containerSize && indexValue && containerSize->isPossible() && indexValue->isPossible()) @@ -255,42 +261,34 @@ void CheckStl::outOfBoundsError(const Token *tok, const std::string &containerNa return; } - ErrorPath errorPath; - if (!indexValue) - errorPath = getErrorPath(tok, containerSize, "Access out of bounds"); - else { - ErrorPath errorPath1 = getErrorPath(tok, containerSize, "Access out of bounds"); - ErrorPath errorPath2 = getErrorPath(tok, indexValue, "Access out of bounds"); - if (errorPath1.size() <= 1) - errorPath = std::move(errorPath2); - else if (errorPath2.size() <= 1) - errorPath = std::move(errorPath1); - else { - errorPath = std::move(errorPath1); - errorPath.splice(errorPath.end(), errorPath2); - } + ErrorPath errorPath = getErrorPath(tok, containerSize, "Access out of bounds"); + if (indexValue) { + ErrorPath errorPathIdx = getErrorPath(tok, indexValue, "Access out of bounds"); + if (errorPathIdx.size() >= errorPath.size()) + errorPath = std::move(errorPathIdx); } reportError(std::move(errorPath), - (containerSize && !containerSize->errorSeverity()) || (indexValue && !indexValue->errorSeverity()) ? Severity::warning : Severity::error, + (containerSize && (!containerSize->errorSeverity() || containerSize->conditional)) || + (indexValue && (!indexValue->errorSeverity() || indexValue->conditional)) ? Severity::warning : Severity::error, "containerOutOfBounds", "$symbol:" + containerName +"\n" + errmsg, CWE398, (containerSize && containerSize->isInconclusive()) || (indexValue && indexValue->isInconclusive()) ? Certainty::inconclusive : Certainty::normal); } -bool CheckStl::isContainerSize(const Token *containerToken, const Token *expr) const +bool CheckStlImpl::isContainerSize(const Token *containerToken, const Token *expr) const { if (!Token::simpleMatch(expr, "( )")) return false; if (!Token::Match(expr->astOperand1(), ". %name% (")) return false; - if (!isSameExpression(false, containerToken, expr->astOperand1()->astOperand1(), *mSettings, false, false)) + if (!isSameExpression(false, containerToken, expr->astOperand1()->astOperand1(), mSettings, false, false)) return false; return containerToken->valueType()->container->getYield(expr->strAt(-1)) == Library::Container::Yield::SIZE; } -bool CheckStl::isContainerSizeGE(const Token * containerToken, const Token *expr) const +bool CheckStlImpl::isContainerSizeGE(const Token * containerToken, const Token *expr) const { if (!expr) return false; @@ -314,12 +312,12 @@ bool CheckStl::isContainerSizeGE(const Token * containerToken, const Token *expr op = expr->astOperand1(); else return false; - return op && op->getValueGE(0, *mSettings); + return op && op->getValueGE(0, mSettings); } return false; } -void CheckStl::outOfBoundsIndexExpression() +void CheckStlImpl::outOfBoundsIndexExpression() { logChecker("CheckStl::outOfBoundsIndexExpression"); for (const Scope *function : mTokenizer->getSymbolDatabase()->functionScopes) { @@ -339,7 +337,7 @@ void CheckStl::outOfBoundsIndexExpression() } } -void CheckStl::outOfBoundsIndexExpressionError(const Token *tok, const Token *index) +void CheckStlImpl::outOfBoundsIndexExpressionError(const Token *tok, const Token *index) { const std::string varname = tok ? tok->str() : std::string("var"); const std::string i = index ? index->expressionString() : (varname + ".size()"); @@ -357,12 +355,12 @@ void CheckStl::outOfBoundsIndexExpressionError(const Token *tok, const Token *in // Error message for bad iterator usage.. -void CheckStl::invalidIteratorError(const Token *tok, const std::string &iteratorName) +void CheckStlImpl::invalidIteratorError(const Token *tok, const std::string &iteratorName) { reportError(tok, Severity::error, "invalidIterator1", "$symbol:"+iteratorName+"\nInvalid iterator: $symbol", CWE664, Certainty::normal); } -void CheckStl::iteratorsError(const Token* tok, const std::string& containerName1, const std::string& containerName2) +void CheckStlImpl::iteratorsError(const Token* tok, const std::string& containerName1, const std::string& containerName2) { reportError(tok, Severity::error, "iterators1", "$symbol:" + containerName1 + "\n" @@ -370,7 +368,7 @@ void CheckStl::iteratorsError(const Token* tok, const std::string& containerName "Same iterator is used with different containers '" + containerName1 + "' and '" + containerName2 + "'.", CWE664, Certainty::normal); } -void CheckStl::iteratorsError(const Token* tok, const Token* containerTok, const std::string& containerName) +void CheckStlImpl::iteratorsError(const Token* tok, const Token* containerTok, const std::string& containerName) { std::list callstack = { tok, containerTok }; reportError(callstack, @@ -384,7 +382,7 @@ void CheckStl::iteratorsError(const Token* tok, const Token* containerTok, const } // Error message used when dereferencing an iterator that has been erased.. -void CheckStl::dereferenceErasedError(const Token *erased, const Token* deref, const std::string &itername, bool inconclusive) +void CheckStlImpl::dereferenceErasedError(const Token *erased, const Token* deref, const std::string &itername, bool inconclusive) { if (erased) { std::list callstack = { deref, erased }; @@ -454,7 +452,7 @@ static bool isVector(const Token* tok) return Token::simpleMatch(decltok, "std :: vector"); } -void CheckStl::iterators() +void CheckStlImpl::iterators() { logChecker("CheckStl::iterators"); @@ -475,7 +473,7 @@ void CheckStl::iterators() bool inconclusiveType=false; if (!isIterator(var, inconclusiveType)) continue; - if (inconclusiveType && !mSettings->certainty.isEnabled(Certainty::inconclusive)) + if (inconclusiveType && !mSettings.certainty.isEnabled(Certainty::inconclusive)) continue; const int iteratorId = var->declarationId(); @@ -554,7 +552,7 @@ void CheckStl::iterators() } // Not different containers if a reference is used.. - if (containerToken && containerToken->variable() && containerToken->variable()->isReference()) { + if (containerToken->variable() && containerToken->variable()->isReference()) { const Token *nameToken = containerToken->variable()->nameToken(); if (Token::Match(nameToken, "%name% =")) { const Token *name1 = nameToken->tokAt(2); @@ -639,33 +637,37 @@ void CheckStl::iterators() } } -void CheckStl::mismatchingContainerIteratorError(const Token* containerTok, const Token* iterTok, const Token* containerTok2) +void CheckStlImpl::mismatchingContainerIteratorError(const Token* containerTok, const Token* iterTok, const Token* containerTok2) { const std::string container(containerTok ? containerTok->expressionString() : std::string("v1")); + const std::string containerTemp(isTemporary(containerTok, &mSettings.library) ? " temporary " : " "); const std::string container2(containerTok2 ? containerTok2->expressionString() : std::string("v2")); + const std::string containerTemp2(isTemporary(containerTok2, &mSettings.library) ? " temporary " : " "); const std::string iter(iterTok ? iterTok->expressionString() : std::string("it")); reportError(containerTok, Severity::error, "mismatchingContainerIterator", - "Iterator '" + iter + "' referring to container '" + container2 + "' is used with container '" + container + "'.", + "Iterator '" + iter + "' referring to" + containerTemp2 + "container '" + container2 + "' is used with" + containerTemp + "container '" + container + "'.", CWE664, Certainty::normal); } // Error message for bad iterator usage.. -void CheckStl::mismatchingContainersError(const Token* tok1, const Token* tok2) +void CheckStlImpl::mismatchingContainersError(const Token* tok1, const Token* tok2, ValueFlow::Value::LifetimeKind ltk) { const std::string expr1(tok1 ? tok1->expressionString() : std::string("v1")); const std::string expr2(tok2 ? tok2->expressionString() : std::string("v2")); + const std::string dataStr = (ltk == ValueFlow::Value::LifetimeKind::Iterator) ? "Iterators" : (ltk == ValueFlow::Value::LifetimeKind::Address ? "Addresses" : "Pointers"); + const std::string objectStr = (ltk == ValueFlow::Value::LifetimeKind::Iterator || ltk == ValueFlow::Value::LifetimeKind::Object) ? "containers '" : "objects '"; reportError(tok1, Severity::error, "mismatchingContainers", - "Iterators of different containers '" + expr1 + "' and '" + expr2 + "' are used together.", + dataStr + " of different " + objectStr + expr1 + "' and '" + expr2 + "' are used together.", CWE664, Certainty::normal); } -void CheckStl::mismatchingContainerExpressionError(const Token *tok1, const Token *tok2) +void CheckStlImpl::mismatchingContainerExpressionError(const Token *tok1, const Token *tok2) { const std::string expr1(tok1 ? tok1->expressionString() : std::string("v1")); const std::string expr2(tok2 ? tok2->expressionString() : std::string("v2")); @@ -674,7 +676,7 @@ void CheckStl::mismatchingContainerExpressionError(const Token *tok1, const Toke expr1 + "' and '" + expr2 + "' are used together.", CWE664, Certainty::normal); } -void CheckStl::sameIteratorExpressionError(const Token *tok) +void CheckStlImpl::sameIteratorExpressionError(const Token *tok) { reportError(tok, Severity::style, "sameIteratorExpression", "Same iterators expression are used for algorithm.", CWE664, Certainty::normal); } @@ -759,7 +761,17 @@ static ValueFlow::Value getLifetimeIteratorValue(const Token* tok, MathLib::bigi return ValueFlow::Value{}; } -bool CheckStl::checkIteratorPair(const Token* tok1, const Token* tok2) +// Whether a container size value found on an iterator token belongs to the range of the given +// iterator value. Both values record the container they belong to when it is known. +static bool sizeValueAppliesToIterator(const ValueFlow::Value& sizeValue, const ValueFlow::Value& iterValue) +{ + if (!sizeValue.container || !iterValue.container) + return true; // the container of the size or of the iterator is not known + return iterValue.container == sizeValue.container || + (iterValue.container->exprId() != 0 && iterValue.container->exprId() == sizeValue.container->exprId()); +} + +bool CheckStlImpl::checkIteratorPair(const Token* tok1, const Token* tok2) { if (!tok1) return false; @@ -777,12 +789,12 @@ bool CheckStl::checkIteratorPair(const Token* tok1, const Token* tok2) (!astIsContainer(val1.tokvalue) || !astIsContainer(val2.tokvalue))) return false; } - if (isSameIteratorContainerExpression(val1.tokvalue, val2.tokvalue, *mSettings, val1.lifetimeKind)) + if (isSameIteratorContainerExpression(val1.tokvalue, val2.tokvalue, mSettings, val1.lifetimeKind)) return false; if (val1.tokvalue->expressionString() == val2.tokvalue->expressionString()) iteratorsError(tok1, val1.tokvalue, val1.tokvalue->expressionString()); else - mismatchingContainersError(val1.tokvalue, val2.tokvalue); + mismatchingContainersError(val1.tokvalue, val2.tokvalue, val1.lifetimeKind); return true; } @@ -797,7 +809,7 @@ bool CheckStl::checkIteratorPair(const Token* tok1, const Token* tok2) const Token* iter2 = getIteratorExpression(tok2); if (!iter2) return false; - if (!isSameIteratorContainerExpression(iter1, iter2, *mSettings)) { + if (!isSameIteratorContainerExpression(iter1, iter2, mSettings)) { mismatchingContainerExpressionError(iter1, iter2); return true; } @@ -811,7 +823,7 @@ namespace { }; } -void CheckStl::mismatchingContainers() +void CheckStlImpl::mismatchingContainers() { logChecker("CheckStl::misMatchingContainers"); @@ -833,8 +845,8 @@ void CheckStl::mismatchingContainers() // Group args together by container std::map> containers; - for (int argnr = 1; argnr <= args.size(); ++argnr) { - const Library::ArgumentChecks::IteratorInfo *i = mSettings->library.getArgIteratorInfo(ftok, argnr); + for (size_t argnr = 1; argnr <= args.size(); ++argnr) { + const Library::ArgumentChecks::IteratorInfo *i = mSettings.library.getArgIteratorInfo(ftok, argnr); if (!i) continue; const Token * const argTok = args[argnr - 1]; @@ -851,7 +863,7 @@ void CheckStl::mismatchingContainers() if (iter1.tok == iter2.tok) continue; if (iter1.info->first && iter2.info->last && - isSameExpression(false, iter1.tok, iter2.tok, *mSettings, false, false)) + isSameExpression(false, iter1.tok, iter2.tok, mSettings, false, false)) sameIteratorExpressionError(iter1.tok); if (checkIteratorPair(iter1.tok, iter2.tok)) return; @@ -871,7 +883,7 @@ void CheckStl::mismatchingContainers() } } -void CheckStl::mismatchingContainerIterator() +void CheckStlImpl::mismatchingContainerIterator() { logChecker("CheckStl::misMatchingContainerIterator"); @@ -889,7 +901,7 @@ void CheckStl::mismatchingContainerIterator() const std::vector args = getArguments(ftok); const Library::Container * c = tok->valueType()->container; - const Library::Container::Action action = c->getAction(tok->strAt(2)); + const Library::Container::Action action = c->getAction(ftok->str()); const Token* iterTok = nullptr; if (action == Library::Container::Action::INSERT && args.size() == 2) { // Skip if iterator pair @@ -915,7 +927,7 @@ void CheckStl::mismatchingContainerIterator() continue; if (iterTok->str() == "*" && iterTok->astOperand1()->valueType() && iterTok->astOperand1()->valueType()->type == ValueType::ITERATOR) continue; - if (isSameIteratorContainerExpression(tok, val.tokvalue, *mSettings)) + if (isSameIteratorContainerExpression(tok, val.tokvalue, mSettings)) continue; mismatchingContainerIteratorError(tok, iterTok, val.tokvalue); } @@ -1123,7 +1135,7 @@ static const Token* endOfExpression(const Token* tok) return endToken; } -void CheckStl::invalidContainer() +void CheckStlImpl::invalidContainer() { logChecker("CheckStl::invalidContainer"); const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); @@ -1148,7 +1160,7 @@ void CheckStl::invalidContainer() const Scope* s = tok2->scope(); if (!s) continue; - if (isReturnScope(s->bodyEnd, mSettings->library)) + if (isReturnScope(s->bodyEnd, mSettings.library)) continue; invalidContainerLoopError(r.ftok, tok, r.errorPath); bail = true; @@ -1193,14 +1205,13 @@ void CheckStl::invalidContainer() if (info.tok->variable()->isReference() && !isVariableDecl(info.tok) && reaches(info.tok->variable()->nameToken(), tok, nullptr)) { - if ((assignExpr && Token::Match(assignExpr->astOperand1(), "& %varid%", info.tok->varId())) || // TODO: fix AST - Token::Match(assignExpr, "& %varid% {|(", info.tok->varId())) { + if ((assignExpr && Token::Match(assignExpr->astOperand1()->previous(), "& %varid%", info.tok->varId()))) { return false; } ErrorPath ep; bool addressOf = false; - const Variable* var = ValueFlow::getLifetimeVariable(info.tok, ep, *mSettings, &addressOf); + const Variable* var = ValueFlow::getLifetimeVariable(info.tok, ep, mSettings, &addressOf); // Check the reference is created before the change if (var && var->declarationId() == r.tok->varId() && !addressOf) { // An argument always reaches @@ -1227,7 +1238,7 @@ void CheckStl::invalidContainer() errorPath.insert(errorPath.end(), info.errorPath.cbegin(), info.errorPath.cend()); errorPath.insert(errorPath.end(), r.errorPath.cbegin(), r.errorPath.cend()); if (v) { - invalidContainerError(info.tok, r.tok, v, std::move(errorPath)); + invalidContainerError(info.tok, v, std::move(errorPath)); } else { invalidContainerReferenceError(info.tok, r.tok, std::move(errorPath)); } @@ -1237,7 +1248,7 @@ void CheckStl::invalidContainer() } } -void CheckStl::invalidContainerLoopError(const Token* tok, const Token* loopTok, ErrorPath errorPath) +void CheckStlImpl::invalidContainerLoopError(const Token* tok, const Token* loopTok, ErrorPath errorPath) { const std::string method = tok ? tok->str() : "erase"; errorPath.emplace_back(loopTok, "Iterating container here."); @@ -1252,7 +1263,7 @@ void CheckStl::invalidContainerLoopError(const Token* tok, const Token* loopTok, reportError(std::move(errorPath), Severity::error, "invalidContainerLoop", msg, CWE664, Certainty::normal); } -void CheckStl::invalidContainerError(const Token *tok, const Token * /*contTok*/, const ValueFlow::Value *val, ErrorPath errorPath) +void CheckStlImpl::invalidContainerError(const Token *tok, const ValueFlow::Value *val, ErrorPath errorPath) { const bool inconclusive = val ? val->isInconclusive() : false; if (val) @@ -1262,7 +1273,7 @@ void CheckStl::invalidContainerError(const Token *tok, const Token * /*contTok*/ reportError(std::move(errorPath), Severity::error, "invalidContainer", msg + " that may be invalid.", CWE664, inconclusive ? Certainty::inconclusive : Certainty::normal); } -void CheckStl::invalidContainerReferenceError(const Token* tok, const Token* contTok, ErrorPath errorPath) +void CheckStlImpl::invalidContainerReferenceError(const Token* tok, const Token* contTok, ErrorPath errorPath) { std::string name = contTok ? contTok->expressionString() : "x"; std::string msg = "Reference to " + name; @@ -1270,7 +1281,7 @@ void CheckStl::invalidContainerReferenceError(const Token* tok, const Token* con reportError(std::move(errorPath), Severity::error, "invalidContainerReference", msg + " that may be invalid.", CWE664, Certainty::normal); } -void CheckStl::stlOutOfBounds() +void CheckStlImpl::stlOutOfBounds() { logChecker("CheckStl::stlOutOfBounds"); @@ -1356,7 +1367,7 @@ void CheckStl::stlOutOfBounds() } } -void CheckStl::stlOutOfBoundsError(const Token *tok, const std::string &num, const std::string &var, bool at) +void CheckStlImpl::stlOutOfBoundsError(const Token *tok, const std::string &num, const std::string &var, bool at) { if (at) reportError(tok, Severity::error, "stlOutOfBounds", "$symbol:" + var + "\nWhen " + num + "==$symbol.size(), $symbol.at(" + num + ") is out of bounds.", CWE788, Certainty::normal); @@ -1364,7 +1375,7 @@ void CheckStl::stlOutOfBoundsError(const Token *tok, const std::string &num, con reportError(tok, Severity::error, "stlOutOfBounds", "$symbol:" + var + "\nWhen " + num + "==$symbol.size(), $symbol[" + num + "] is out of bounds.", CWE788, Certainty::normal); } -void CheckStl::negativeIndex() +void CheckStlImpl::negativeIndex() { logChecker("CheckStl::negativeIndex"); @@ -1377,10 +1388,10 @@ void CheckStl::negativeIndex() const Variable * const var = tok->variable(); if (!var || tok == var->nameToken()) continue; - const Library::Container * const container = mSettings->library.detectContainer(var->typeStartToken()); + const Library::Container * const container = mSettings.library.detectContainer(var->typeStartToken()); if (!container || !container->arrayLike_indexOp) continue; - const ValueFlow::Value *index = tok->next()->astOperand2()->getValueLE(-1, *mSettings); + const ValueFlow::Value *index = tok->next()->astOperand2()->getValueLE(-1, mSettings); if (!index) continue; negativeIndexError(tok, *index); @@ -1388,7 +1399,7 @@ void CheckStl::negativeIndex() } } -void CheckStl::negativeIndexError(const Token *tok, const ValueFlow::Value &index) +void CheckStlImpl::negativeIndexError(const Token *tok, const ValueFlow::Value &index) { ErrorPath errorPath = getErrorPath(tok, &index, "Negative array index"); std::ostringstream errmsg; @@ -1402,7 +1413,7 @@ void CheckStl::negativeIndexError(const Token *tok, const ValueFlow::Value &inde reportError(std::move(errorPath), severity, "negativeContainerIndex", errmsg.str(), CWE786, certainty); } -void CheckStl::erase() +void CheckStlImpl::erase() { logChecker("CheckStl::erase"); @@ -1423,7 +1434,7 @@ void CheckStl::erase() } } -void CheckStl::eraseCheckLoopVar(const Scope &scope, const Variable *var) +void CheckStlImpl::eraseCheckLoopVar(const Scope &scope, const Variable *var) { bool inconclusiveType=false; if (!isIterator(var, inconclusiveType)) @@ -1439,7 +1450,7 @@ void CheckStl::eraseCheckLoopVar(const Scope &scope, const Variable *var) if (Token::Match(tok->astParent(), "=|return")) continue; // Iterator is invalid.. - int indentlevel = 0U; + int indentlevel = 0; const Token *tok2 = tok->link(); for (; tok2 != scope.bodyEnd; tok2 = tok2->next()) { if (tok2->str() == "{") { @@ -1447,7 +1458,7 @@ void CheckStl::eraseCheckLoopVar(const Scope &scope, const Variable *var) continue; } if (tok2->str() == "}") { - if (indentlevel > 0U) + if (indentlevel > 0) --indentlevel; else if (Token::simpleMatch(tok2, "} else {")) tok2 = tok2->linkAt(2); @@ -1467,7 +1478,7 @@ void CheckStl::eraseCheckLoopVar(const Scope &scope, const Variable *var) } } -void CheckStl::stlBoundaries() +void CheckStlImpl::stlBoundaries() { logChecker("CheckStl::stlBoundaries"); @@ -1476,7 +1487,7 @@ void CheckStl::stlBoundaries() if (!var || !var->scope() || !var->scope()->isExecutable()) continue; - const Library::Container* container = mSettings->library.detectIterator(var->typeStartToken()); + const Library::Container* container = mSettings.library.detectIterator(var->typeStartToken()); if (!container || container->opLessAllowed) continue; @@ -1492,7 +1503,7 @@ void CheckStl::stlBoundaries() } // Error message for bad boundary usage.. -void CheckStl::stlBoundariesError(const Token *tok) +void CheckStlImpl::stlBoundariesError(const Token *tok) { reportError(tok, Severity::error, "stlBoundaries", "Dangerous comparison using operator< on iterator.\n" @@ -1521,10 +1532,10 @@ static bool if_findCompare(const Token * const tokBack, bool stdStringLike) return false; } -void CheckStl::if_find() +void CheckStlImpl::if_find() { - const bool printWarning = mSettings->severity.isEnabled(Severity::warning); - const bool printPerformance = mSettings->severity.isEnabled(Severity::performance); + const bool printWarning = mSettings.severity.isEnabled(Severity::warning); + const bool printPerformance = mSettings.severity.isEnabled(Severity::performance); if (!printWarning && !printPerformance) return; @@ -1548,7 +1559,7 @@ void CheckStl::if_find() tok = tok->linkAt(1); else if (tok->variable() && Token::Match(tok, "%var% . %name% (")) { - container = mSettings->library.detectContainer(tok->variable()->typeStartToken()); + container = mSettings.library.detectContainer(tok->variable()->typeStartToken()); funcTok = tok->tokAt(2); } @@ -1562,16 +1573,16 @@ void CheckStl::if_find() funcTok = tok2->astParent()->next(); if (tok->variable()->isArrayOrPointer()) - container = mSettings->library.detectContainer(tok->variable()->typeStartToken()); + container = mSettings.library.detectContainer(tok->variable()->typeStartToken()); else { // Container of container - find the inner container - container = mSettings->library.detectContainer(tok->variable()->typeStartToken()); // outer container + container = mSettings.library.detectContainer(tok->variable()->typeStartToken()); // outer container tok2 = Token::findsimplematch(tok->variable()->typeStartToken(), "<", tok->variable()->typeEndToken()); if (container && container->type_templateArgNo >= 0 && tok2) { tok2 = tok2->next(); for (int j = 0; j < container->type_templateArgNo; j++) tok2 = tok2->nextTemplateArgument(); - container = mSettings->library.detectContainer(tok2); // inner container + container = mSettings.library.detectContainer(tok2); // inner container } else container = nullptr; } @@ -1598,9 +1609,9 @@ void CheckStl::if_find() } -void CheckStl::if_findError(const Token *tok, bool str) +void CheckStlImpl::if_findError(const Token *tok, bool str) { - if (str && mSettings->standards.cpp >= Standards::CPP20) + if (str && mSettings.standards.cpp >= Standards::CPP20) reportError(tok, Severity::performance, "stlIfStrFind", "Inefficient usage of string::find() in condition; string::starts_with() could be faster.\n" "Either inefficient or wrong usage of string::find(). string::starts_with() will be faster if " @@ -1689,9 +1700,9 @@ static const Token *findInsertValue(const Token *tok, const Token *containerTok, return nullptr; } -void CheckStl::checkFindInsert() +void CheckStlImpl::checkFindInsert() { - if (!mSettings->severity.isEnabled(Severity::performance)) + if (!mSettings.severity.isEnabled(Severity::performance)) return; logChecker("CheckStl::checkFindInsert"); // performance @@ -1712,48 +1723,91 @@ void CheckStl::checkFindInsert() if (!containerTok) continue; // In < C++17 we only warn for small simple types - if (mSettings->standards.cpp < Standards::CPP17 && !(keyTok && keyTok->valueType() && (keyTok->valueType()->isIntegral() || keyTok->valueType()->pointer > 0))) + if (mSettings.standards.cpp < Standards::CPP17 && !(keyTok && keyTok->valueType() && (keyTok->valueType()->isIntegral() || keyTok->valueType()->pointer > 0))) continue; const Token *thenTok = tok->linkAt(1)->next(); - const Token *valueTok = findInsertValue(thenTok, containerTok, keyTok, *mSettings); + const Token *valueTok = findInsertValue(thenTok, containerTok, keyTok, mSettings); if (!valueTok) continue; if (Token::simpleMatch(thenTok->link(), "} else {")) { const Token *valueTok2 = - findInsertValue(thenTok->link()->tokAt(2), containerTok, keyTok, *mSettings); + findInsertValue(thenTok->link()->tokAt(2), containerTok, keyTok, mSettings); if (!valueTok2) continue; - if (isSameExpression(true, valueTok, valueTok2, *mSettings, true, true)) { - checkFindInsertError(valueTok); + if (isSameExpression(true, valueTok, valueTok2, mSettings, true, true)) { + // Both branches set the same value; the replacement depends on both branches, so no suggestion + checkFindInsertError(valueTok, nullptr); } } else { - checkFindInsertError(valueTok); + checkFindInsertError(valueTok, condTok); } } } } -void CheckStl::checkFindInsertError(const Token *tok) -{ - std::string replaceExpr; - if (tok && Token::simpleMatch(tok->astParent(), "=") && tok == tok->astParent()->astOperand2() && Token::simpleMatch(tok->astParent()->astOperand1(), "[")) { - if (mSettings->standards.cpp < Standards::CPP11) - // We will recommend using emplace/try_emplace instead - return; - const std::string f = (mSettings->standards.cpp < Standards::CPP17) ? "emplace" : "try_emplace"; - replaceExpr = " Instead of '" + tok->astParent()->expressionString() + "' consider using '" + - tok->astParent()->astOperand1()->astOperand1()->expressionString() + - "." + f + "(" + - tok->astParent()->astOperand1()->astOperand2()->expressionString() + - ", " + - tok->expressionString() + - ");'."; +/** When is the condition of the 'if' statement checked by checkFindInsert() true? */ +enum class FindInsertCond : std::uint8_t { UNKNOWN, FOUND, NOT_FOUND }; + +static FindInsertCond getFindInsertCond(const Token* condTok) +{ + const Token* rhs = condTok->astOperand2(); + if (!rhs) + return FindInsertCond::UNKNOWN; + if (Token::simpleMatch(condTok->astOperand1()->astOperand1(), ". find (")) { + // c.find(k) == c.end() / c.find(k) != c.end() + if (!Token::simpleMatch(rhs, "(") || !Token::Match(rhs->astOperand1(), ". end|cend (")) + return FindInsertCond::UNKNOWN; + if (condTok->str() == "==") + return FindInsertCond::NOT_FOUND; + if (condTok->str() == "!=") + return FindInsertCond::FOUND; + return FindInsertCond::UNKNOWN; + } + // c.count(k) == 0, c.count(k) > 0, ...: count() yields 0 or 1 + if (!rhs->hasKnownIntValue()) + return FindInsertCond::UNKNOWN; + const MathLib::bigint n = rhs->getKnownIntValue(); + const bool found = calculate(condTok->str(), 1, n) != 0; + const bool notFound = calculate(condTok->str(), 0, n) != 0; + if (found == notFound) + return FindInsertCond::UNKNOWN; + return found ? FindInsertCond::FOUND : FindInsertCond::NOT_FOUND; +} + +void CheckStlImpl::checkFindInsertError(const Token* tok, const Token* condTok) +{ + std::string suggestion; + std::string method = "insertion"; + if (tok && condTok && Token::simpleMatch(tok->astParent(), "=") && tok == tok->astParent()->astOperand2() && + Token::simpleMatch(tok->astParent()->astOperand1(), "[")) { + const std::string containerExpr = tok->astParent()->astOperand1()->astOperand1()->expressionString(); + const std::string keyExpr = tok->astParent()->astOperand1()->astOperand2()->expressionString(); + switch (getFindInsertCond(condTok)) { + case FindInsertCond::NOT_FOUND: { + if (mSettings.standards.cpp < Standards::CPP11) + // We will recommend using emplace/try_emplace instead + return; + const std::string f = (mSettings.standards.cpp < Standards::CPP17) ? "emplace" : "try_emplace"; + suggestion = " Instead of '" + tok->astParent()->expressionString() + "' consider using '" + containerExpr + + "." + f + "(" + keyExpr + ", " + tok->expressionString() + ");'."; + break; + } + case FindInsertCond::FOUND: + // The key is present, so the element is modified rather than inserted; try_emplace() would not do that + method = "modification"; + suggestion = " Instead of '" + tok->astParent()->expressionString() + + "' consider assigning the value through the iterator returned by '" + containerExpr + + ".find(" + keyExpr + ")'."; + break; + case FindInsertCond::UNKNOWN: + break; + } } + std::string msg = "Searching before " + method + " is not necessary." + suggestion; - reportError( - tok, Severity::performance, "stlFindInsert", "Searching before insertion is not necessary." + replaceExpr, CWE398, Certainty::normal); + reportError(tok, Severity::performance, "stlFindInsert", msg, CWE398, Certainty::normal); } /** @@ -1767,12 +1821,12 @@ static bool isCpp03ContainerSizeSlow(const Token *tok) return var && var->isStlType("list"); } -void CheckStl::size() +void CheckStlImpl::size() { - if (!mSettings->severity.isEnabled(Severity::performance)) + if (!mSettings.severity.isEnabled(Severity::performance)) return; - if (mSettings->standards.cpp >= Standards::CPP11) + if (mSettings.standards.cpp >= Standards::CPP11) return; logChecker("CheckStl::size"); // performance,c++03 @@ -1816,7 +1870,7 @@ void CheckStl::size() } } -void CheckStl::sizeError(const Token *tok) +void CheckStlImpl::sizeError(const Token *tok) { const std::string varname = tok ? tok->str() : std::string("list"); reportError(tok, Severity::performance, "stlSize", @@ -1828,9 +1882,9 @@ void CheckStl::sizeError(const Token *tok) "guaranteed to take constant time.", CWE398, Certainty::normal); } -void CheckStl::redundantCondition() +void CheckStlImpl::redundantCondition() { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("redundantIfRemove")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("redundantIfRemove")) return; logChecker("CheckStl::redundantCondition"); // style @@ -1852,6 +1906,9 @@ void CheckStl::redundantCondition() const Token *var3 = var2->tokAt(7); const Token *any2 = var3->tokAt(4); + if (any2->tokAt(3) != scope.bodyEnd) + continue; + // Check if all the "%name%" fields are the same and if all the "%any%" are the same.. if (var1->str() == var2->str() && var2->str() == var3->str() && @@ -1861,7 +1918,7 @@ void CheckStl::redundantCondition() } } -void CheckStl::redundantIfRemoveError(const Token *tok) +void CheckStlImpl::redundantIfRemoveError(const Token *tok) { reportError(tok, Severity::style, "redundantIfRemove", "Redundant checking of STL container element existence before removing it.\n" @@ -1869,9 +1926,9 @@ void CheckStl::redundantIfRemoveError(const Token *tok) "It is safe to call the remove method on a non-existing element.", CWE398, Certainty::normal); } -void CheckStl::missingComparison() +void CheckStlImpl::missingComparison() { - if (!mSettings->severity.isEnabled(Severity::warning)) + if (!mSettings.severity.isEnabled(Severity::warning)) return; logChecker("CheckStl::missingComparison"); // warning @@ -1933,7 +1990,7 @@ void CheckStl::missingComparison() } } -void CheckStl::missingComparisonError(const Token *incrementToken1, const Token *incrementToken2) +void CheckStlImpl::missingComparisonError(const Token *incrementToken1, const Token *incrementToken2) { std::list callstack = { incrementToken1,incrementToken2 }; @@ -1977,9 +2034,11 @@ static bool isc_strCall(const Token* tok, const Library::Container* container) static bool isc_strConcat(const Token* tok) { - if (!tok->isBinaryOp() || !Token::simpleMatch(tok, "+")) + if (!tok->isBinaryOp() || !Token::Match(tok, "+|+=")) return false; for (const Token* op : { tok->astOperand1(), tok->astOperand2() }) { // NOLINT(readability-use-anyofallof) + if (tok->isAssignmentOp() && astIsLHS(op)) + continue; const Token* sibling = op->astSibling(); if (!sibling->valueType()) continue; @@ -2029,10 +2088,10 @@ namespace { }; } -void CheckStl::string_c_str() +void CheckStlImpl::string_c_str() { - const bool printInconclusive = mSettings->certainty.isEnabled(Certainty::inconclusive); - const bool printPerformance = mSettings->severity.isEnabled(Severity::performance); + const bool printInconclusive = mSettings.certainty.isEnabled(Certainty::inconclusive); + const bool printPerformance = mSettings.severity.isEnabled(Severity::performance); const SymbolDatabase* symbolDatabase = mTokenizer->getSymbolDatabase(); @@ -2225,25 +2284,25 @@ void CheckStl::string_c_str() } } -void CheckStl::string_c_strThrowError(const Token* tok) +void CheckStlImpl::string_c_strThrowError(const Token* tok) { reportError(tok, Severity::error, "stlcstrthrow", "Dangerous usage of c_str(). The value returned by c_str() is invalid after throwing exception.\n" "Dangerous usage of c_str(). The string is destroyed after the c_str() call so the thrown pointer is invalid."); } -void CheckStl::string_c_strError(const Token* tok) +void CheckStlImpl::string_c_strError(const Token* tok) { reportError(tok, Severity::error, "stlcstr", "Dangerous usage of c_str(). The value returned by c_str() is invalid after this call.\n" "Dangerous usage of c_str(). The c_str() return value is only valid until its string is deleted.", CWE664, Certainty::normal); } -void CheckStl::string_c_strReturn(const Token* tok) +void CheckStlImpl::string_c_strReturn(const Token* tok) { reportError(tok, Severity::performance, "stlcstrReturn", "Returning the result of c_str() in a function that returns std::string is slow and redundant.\n" "The conversion from const char* as returned by c_str() to std::string creates an unnecessary string copy. Solve that by directly returning the string.", CWE704, Certainty::normal); } -void CheckStl::string_c_strParam(const Token* tok, nonneg int number, const std::string& argtype) +void CheckStlImpl::string_c_strParam(const Token* tok, nonneg int number, const std::string& argtype) { std::ostringstream oss; oss << "Passing the result of c_str() to a function that takes " << argtype << " as argument no. " << number << " is slow and redundant.\n" @@ -2251,28 +2310,28 @@ void CheckStl::string_c_strParam(const Token* tok, nonneg int number, const std: reportError(tok, Severity::performance, "stlcstrParam", oss.str(), CWE704, Certainty::normal); } -void CheckStl::string_c_strConstructor(const Token* tok, const std::string& argtype) +void CheckStlImpl::string_c_strConstructor(const Token* tok, const std::string& argtype) { std::string msg = "Constructing a " + argtype + " from the result of c_str() is slow and redundant.\n" "Constructing a " + argtype + " from const char* requires a call to strlen(). Solve that by directly passing the string."; reportError(tok, Severity::performance, "stlcstrConstructor", msg, CWE704, Certainty::normal); } -void CheckStl::string_c_strAssignment(const Token* tok, const std::string& argtype) +void CheckStlImpl::string_c_strAssignment(const Token* tok, const std::string& argtype) { std::string msg = "Assigning the result of c_str() to a " + argtype + " is slow and redundant.\n" "Assigning a const char* to a " + argtype + " requires a call to strlen(). Solve that by directly assigning the string."; reportError(tok, Severity::performance, "stlcstrAssignment", msg, CWE704, Certainty::normal); } -void CheckStl::string_c_strConcat(const Token* tok) +void CheckStlImpl::string_c_strConcat(const Token* tok) { std::string msg = "Concatenating the result of c_str() and a std::string is slow and redundant.\n" "Concatenating a const char* with a std::string requires a call to strlen(). Solve that by directly concatenating the strings."; reportError(tok, Severity::performance, "stlcstrConcat", msg, CWE704, Certainty::normal); } -void CheckStl::string_c_strStream(const Token* tok) +void CheckStlImpl::string_c_strStream(const Token* tok) { std::string msg = "Passing the result of c_str() to a stream is slow and redundant.\n" "Passing a const char* to a stream requires a call to strlen(). Solve that by directly passing the string."; @@ -2293,10 +2352,10 @@ namespace { } -void CheckStl::uselessCalls() +void CheckStlImpl::uselessCalls() { - const bool printPerformance = mSettings->severity.isEnabled(Severity::performance); - const bool printWarning = mSettings->severity.isEnabled(Severity::warning); + const bool printPerformance = mSettings.severity.isEnabled(Severity::performance); + const bool printWarning = mSettings.severity.isEnabled(Severity::warning); if (!printPerformance && !printWarning) return; @@ -2333,7 +2392,7 @@ void CheckStl::uselessCalls() !tok->tokAt(4)->astParent() && tok->next()->variable() && tok->next()->variable()->isStlType(stl_containers_with_empty_and_clear)) uselessCallsEmptyError(tok->next()); - else if (Token::Match(tok, "[{};] std :: remove|remove_if|unique (") && tok->tokAt(5)->nextArgument()) + else if (Token::Match(tok, "[{};] std :: remove|remove_if|unique (") && tok->tokAt(5)->nextArgument() && !tok->tokAt(4)->astParent()) uselessCallsRemoveError(tok->next(), tok->strAt(3)); else if (printPerformance && tok->valueType() && tok->valueType()->type == ValueType::CONTAINER) { if (Token::Match(tok, "%var% = { %var% . begin ( ) ,") && tok->varId() == tok->tokAt(3)->varId()) @@ -2354,7 +2413,7 @@ void CheckStl::uselessCalls() } -void CheckStl::uselessCallsReturnValueError(const Token *tok, const std::string &varname, const std::string &function) +void CheckStlImpl::uselessCallsReturnValueError(const Token *tok, const std::string &varname, const std::string &function) { std::ostringstream errmsg; errmsg << "$symbol:" << varname << '\n'; @@ -2367,7 +2426,7 @@ void CheckStl::uselessCallsReturnValueError(const Token *tok, const std::string reportError(tok, Severity::warning, "uselessCallsCompare", errmsg.str(), CWE628, Certainty::normal); } -void CheckStl::uselessCallsSwapError(const Token *tok, const std::string &varname) +void CheckStlImpl::uselessCallsSwapError(const Token *tok, const std::string &varname) { reportError(tok, Severity::performance, "uselessCallsSwap", "$symbol:" + varname + "\n" @@ -2377,7 +2436,7 @@ void CheckStl::uselessCallsSwapError(const Token *tok, const std::string &varnam "code is inefficient. Is the object or the parameter wrong here?", CWE628, Certainty::normal); } -void CheckStl::uselessCallsSubstrError(const Token *tok, SubstrErrorType type) +void CheckStlImpl::uselessCallsSubstrError(const Token *tok, SubstrErrorType type) { std::string msg = "Ineffective call of function 'substr' because "; switch (type) { @@ -2397,19 +2456,19 @@ void CheckStl::uselessCallsSubstrError(const Token *tok, SubstrErrorType type) reportError(tok, Severity::performance, "uselessCallsSubstr", msg, CWE398, Certainty::normal); } -void CheckStl::uselessCallsConstructorError(const Token *tok) +void CheckStlImpl::uselessCallsConstructorError(const Token *tok) { const std::string container = tok ? tok->str() : ""; const std::string msg = "Inefficient constructor call: container '" + container + "' is assigned a partial copy of itself. Use erase() or resize() instead."; reportError(tok, Severity::performance, "uselessCallsConstructor", msg, CWE398, Certainty::normal); } -void CheckStl::uselessCallsEmptyError(const Token *tok) +void CheckStlImpl::uselessCallsEmptyError(const Token *tok) { reportError(tok, Severity::warning, "uselessCallsEmpty", "Ineffective call of function 'empty()'. Did you intend to call 'clear()' instead?", CWE398, Certainty::normal); } -void CheckStl::uselessCallsRemoveError(const Token *tok, const std::string& function) +void CheckStlImpl::uselessCallsRemoveError(const Token *tok, const std::string& function) { reportError(tok, Severity::warning, "uselessCallsRemove", "$symbol:" + function + "\n" @@ -2420,9 +2479,9 @@ void CheckStl::uselessCallsRemoveError(const Token *tok, const std::string& func // Check for iterators being dereferenced before being checked for validity. // E.g. if (*i && i != str.end()) { } -void CheckStl::checkDereferenceInvalidIterator() +void CheckStlImpl::checkDereferenceInvalidIterator() { - if (!mSettings->severity.isEnabled(Severity::warning)) + if (!mSettings.severity.isEnabled(Severity::warning)) return; logChecker("CheckStl::checkDereferenceInvalidIterator"); // warning @@ -2484,10 +2543,8 @@ void CheckStl::checkDereferenceInvalidIterator() } -void CheckStl::checkDereferenceInvalidIterator2() +void CheckStlImpl::checkDereferenceInvalidIterator2() { - const bool printInconclusive = (mSettings->certainty.isEnabled(Certainty::inconclusive)); - logChecker("CheckStl::checkDereferenceInvalidIterator2"); for (const Token *tok = mTokenizer->tokens(); tok; tok = tok->next()) { @@ -2500,20 +2557,16 @@ void CheckStl::checkDereferenceInvalidIterator2() continue; std::vector contValues; - std::copy_if(tok->values().cbegin(), tok->values().cend(), std::back_inserter(contValues), [&](const ValueFlow::Value& value) { - if (value.isImpossible()) - return false; - if (!printInconclusive && value.isInconclusive()) - return false; - return value.isContainerSizeValue(); + std::copy_if(tok->values().cbegin(), + tok->values().cend(), + std::back_inserter(contValues), + [&](const ValueFlow::Value& value) { + return isUsableValue(value, mSettings) && value.isContainerSizeValue(); }); - // Can iterator point to END or before START? for (const ValueFlow::Value& value:tok->values()) { - if (value.isImpossible()) - continue; - if (!printInconclusive && value.isInconclusive()) + if (!isUsableValue(value, mSettings)) continue; if (!value.isIteratorValue()) continue; @@ -2527,6 +2580,8 @@ void CheckStl::checkDereferenceInvalidIterator2() auto it = std::find_if(contValues.cbegin(), contValues.cend(), [&](const ValueFlow::Value& c) { if (value.path != c.path) return false; + if (!sizeValueAppliesToIterator(c, value)) + return false; if (value.isIteratorStartValue() && value.intvalue >= c.intvalue) return true; if (value.isIteratorEndValue() && -value.intvalue > c.intvalue) @@ -2556,7 +2611,7 @@ void CheckStl::checkDereferenceInvalidIterator2() emptyAdvance = tok->astParent(); } } - if (!CheckNullPointer::isPointerDeRef(tok, unknown, *mSettings) && !isInvalidIterator && !emptyAdvance) { + if (!CheckNullPointerImpl::isPointerDeRef(tok, unknown, mSettings.library) && !isInvalidIterator && !emptyAdvance) { if (!unknown) continue; inconclusive = true; @@ -2580,7 +2635,7 @@ void CheckStl::checkDereferenceInvalidIterator2() } } -void CheckStl::dereferenceInvalidIteratorError(const Token* tok, const ValueFlow::Value *value, bool inconclusive) +void CheckStlImpl::dereferenceInvalidIteratorError(const Token* tok, const ValueFlow::Value *value, bool inconclusive) { const std::string& varname = tok ? tok->expressionString() : "var"; const std::string errmsgcond("$symbol:" + varname + '\n' + ValueFlow::eitherTheConditionIsRedundant(value ? value->condition : nullptr) + " or there is possible dereference of an invalid iterator: $symbol."); @@ -2589,7 +2644,7 @@ void CheckStl::dereferenceInvalidIteratorError(const Token* tok, const ValueFlow reportError(tok, Severity::warning, "derefInvalidIteratorRedundantCheck", errmsgcond, CWE825, Certainty::normal); return; } - if (!mSettings->isEnabled(value, inconclusive)) + if (!mSettings.isEnabled(value, inconclusive)) return; ErrorPath errorPath = getErrorPath(tok, value, "Dereference of an invalid iterator"); @@ -2609,7 +2664,7 @@ void CheckStl::dereferenceInvalidIteratorError(const Token* tok, const ValueFlow } } -void CheckStl::dereferenceInvalidIteratorError(const Token* deref, const std::string &iterName) +void CheckStlImpl::dereferenceInvalidIteratorError(const Token* deref, const std::string &iterName) { reportError(deref, Severity::warning, "derefInvalidIterator", @@ -2618,7 +2673,7 @@ void CheckStl::dereferenceInvalidIteratorError(const Token* deref, const std::st "Possible dereference of an invalid iterator: $symbol. Make sure to check that the iterator is valid before dereferencing it - not after.", CWE825, Certainty::normal); } -void CheckStl::useStlAlgorithmError(const Token *tok, const std::string &algoName) +void CheckStlImpl::useStlAlgorithmError(const Token *tok, const std::string &algoName) { reportError(tok, Severity::style, "useStlAlgorithm", "Consider using " + algoName + " algorithm instead of a raw loop.", CWE398, Certainty::normal); @@ -2873,26 +2928,25 @@ static bool isTernaryAssignment(const Token* assignTok, nonneg int loopVarId, no namespace { struct LoopAnalyzer { - const Token* bodyTok = nullptr; - const Token* loopVar = nullptr; - const Settings* settings = nullptr; - std::set varsChanged; - - explicit LoopAnalyzer(const Token* tok, const Settings* psettings) - : bodyTok(tok->linkAt(1)->next()), settings(psettings) + private: + const Token* mBodyTok; + const Token* mLoopVar{}; + RefThunk mSettings; + std::set mVarsChanged; + + public: + explicit LoopAnalyzer(const Token* tok, const Settings& settings) + : mBodyTok(tok->linkAt(1)->next()), mSettings(settings) { const Token* splitTok = tok->next()->astOperand2(); if (Token::simpleMatch(splitTok, ":") && splitTok->previous()->varId() != 0) { - loopVar = splitTok->previous(); + mLoopVar = splitTok->previous(); } if (valid()) { findChangedVariables(); } } - bool isLoopVarChanged() const { - return varsChanged.count(loopVar->varId()) > 0; - } - + private: bool isModified(const Token* tok) const { if (tok->variable() && tok->variable()->isConst()) @@ -2900,11 +2954,11 @@ namespace { int n = 1 + (astIsPointer(tok) ? 1 : 0); for (int i = 0; i < n; i++) { bool inconclusive = false; - if (isVariableChangedByFunctionCall(tok, i, *settings, &inconclusive)) + if (isVariableChangedByFunctionCall(tok, i, mSettings().library, &inconclusive)) return true; if (inconclusive) return true; - if (isVariableChanged(tok, i, *settings)) + if (isVariableChanged(tok, i, mSettings)) return true; } return false; @@ -2913,7 +2967,7 @@ namespace { template void findTokens(Predicate pred, F f) const { - for (const Token* tok = bodyTok; precedes(tok, bodyTok->link()); tok = tok->next()) { + for (const Token* tok = mBodyTok; precedes(tok, mBodyTok->link()); tok = tok->next()) { if (pred(tok)) f(tok); } @@ -2922,7 +2976,7 @@ namespace { template const Token* findToken(Predicate pred) const { - for (const Token* tok = bodyTok; precedes(tok, bodyTok->link()); tok = tok->next()) { + for (const Token* tok = mBodyTok; precedes(tok, mBodyTok->link()); tok = tok->next()) { if (pred(tok)) return tok; } @@ -2937,58 +2991,56 @@ namespace { } bool valid() const { - return bodyTok && loopVar; + return mBodyTok && mLoopVar; } + public: std::string findAlgo() const { if (!valid()) return ""; - bool loopVarChanged = isLoopVarChanged(); - if (!loopVarChanged && varsChanged.empty()) { - if (hasGotoOrBreak()) - return ""; - bool alwaysTrue = true; - bool alwaysFalse = true; - auto hasReturn = [](const Token* tok) { - return Token::simpleMatch(tok, "return"); - }; - findTokens(hasReturn, [&](const Token* tok) { - const Token* returnTok = tok->astOperand1(); - if (!returnTok || !returnTok->hasKnownIntValue() || !astIsBool(returnTok)) { - alwaysTrue = false; - alwaysFalse = false; - return; - } - (returnTok->getKnownIntValue() ? alwaysTrue : alwaysFalse) &= true; - (returnTok->getKnownIntValue() ? alwaysFalse : alwaysTrue) &= false; - }); - if (alwaysTrue == alwaysFalse) - return ""; - if (alwaysTrue) - return "std::any_of"; - return "std::all_of or std::none_of"; - } - return ""; + if (!mVarsChanged.empty()) + return ""; + if (hasGotoOrBreak()) + return ""; + bool alwaysTrue = true; + bool alwaysFalse = true; + const auto hasReturn = [](const Token* tok) { + return Token::simpleMatch(tok, "return"); + }; + findTokens(hasReturn, [&](const Token* tok) { + const Token* returnTok = tok->astOperand1(); + if (!returnTok || !returnTok->hasKnownIntValue() || !astIsBool(returnTok)) { + alwaysTrue = false; + alwaysFalse = false; + return; + } + (returnTok->getKnownIntValue() ? alwaysTrue : alwaysFalse) &= true; + (returnTok->getKnownIntValue() ? alwaysFalse : alwaysTrue) &= false; + }); + if (alwaysTrue == alwaysFalse) + return ""; + if (alwaysTrue) + return "std::any_of"; + return "std::all_of or std::none_of"; } - + private: bool isLocalVar(const Variable* var) const { if (!var) return false; if (var->isPointer() || var->isReference()) return false; - if (var->declarationId() == loopVar->varId()) + if (var->declarationId() == mLoopVar->varId()) return false; const Scope* scope = var->scope(); - return scope && scope->isNestedIn(bodyTok->scope()); + return scope && scope->isNestedIn(mBodyTok->scope()); } - private: void findChangedVariables() { std::set vars; - for (const Token* tok = bodyTok; precedes(tok, bodyTok->link()); tok = tok->next()) { + for (const Token* tok = mBodyTok; precedes(tok, mBodyTok->link()); tok = tok->next()) { if (tok->varId() == 0) continue; if (vars.count(tok->varId()) > 0) @@ -2999,16 +3051,16 @@ namespace { } if (!isModified(tok)) continue; - varsChanged.insert(tok->varId()); + mVarsChanged.insert(tok->varId()); vars.insert(tok->varId()); } } }; } // namespace -void CheckStl::useStlAlgorithm() +void CheckStlImpl::useStlAlgorithm() { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("useStlAlgorithm")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("useStlAlgorithm")) return; logChecker("CheckStl::useStlAlgorithm"); // style @@ -3024,7 +3076,7 @@ void CheckStl::useStlAlgorithm() if (!Token::simpleMatch(tok, "{") || !Token::simpleMatch(tok->previous(), ")")) return false; const Token* condTok = tok->linkAt(-1)->astOperand2(); - if (isConstExpression(condTok, mSettings->library)) { + if (isConstExpression(condTok, mSettings.library)) { if (condTok->str() == "<") type = ConditionOpType::MIN; else if (condTok->str() == ">") @@ -3088,7 +3140,7 @@ void CheckStl::useStlAlgorithm() // Check for single assignment bool useLoopVarInAssign{}, hasBreak{}; - const Token *assignTok = singleAssignInScope(bodyTok, loopVar->varId(), useLoopVarInAssign, hasBreak, loopType, *mSettings); + const Token *assignTok = singleAssignInScope(bodyTok, loopVar->varId(), useLoopVarInAssign, hasBreak, loopType, mSettings); if (assignTok) { if (!checkAssignee(assignTok->astOperand1())) continue; @@ -3120,21 +3172,20 @@ void CheckStl::useStlAlgorithm() } // Check for container calls bool useLoopVarInMemCall; - const Token *memberAccessTok = singleMemberCallInScope(bodyTok, loopVar->varId(), useLoopVarInMemCall, *mSettings); + const Token *memberAccessTok = singleMemberCallInScope(bodyTok, loopVar->varId(), useLoopVarInMemCall, mSettings); if (memberAccessTok && loopType == LoopType::RANGE) { - const Token *memberCallTok = memberAccessTok->astOperand2(); const int contVarId = memberAccessTok->astOperand1()->varId(); if (contVarId == loopVar->varId()) continue; - if (memberCallTok->str() == "push_back" || - memberCallTok->str() == "push_front" || - memberCallTok->str() == "emplace_back") { + using Action = Library::Container::Action; + const auto action = astContainerAction(memberAccessTok->astOperand1(), mSettings.library); + if (contains({Action::PUSH, Action::INSERT}, action)) { std::string algo; if (useLoopVarInMemCall) algo = "std::copy"; else algo = "std::transform"; - useStlAlgorithmError(memberCallTok, algo); + useStlAlgorithmError(memberAccessTok->astOperand2(), algo); } continue; } @@ -3153,10 +3204,10 @@ void CheckStl::useStlAlgorithm() } // Check for conditionals - const Token *condBodyTok = singleConditionalInScope(bodyTok, loopVar->varId(), loopType, *mSettings); + const Token *condBodyTok = singleConditionalInScope(bodyTok, loopVar->varId(), loopType, mSettings); if (condBodyTok) { // Check for single assign - assignTok = singleAssignInScope(condBodyTok, loopVar->varId(), useLoopVarInAssign, hasBreak, loopType, *mSettings); + assignTok = singleAssignInScope(condBodyTok, loopVar->varId(), useLoopVarInAssign, hasBreak, loopType, mSettings); if (assignTok) { if (!checkAssignee(assignTok->astOperand1())) continue; @@ -3197,7 +3248,7 @@ void CheckStl::useStlAlgorithm() } // Check for container call - memberAccessTok = singleMemberCallInScope(condBodyTok, loopVar->varId(), useLoopVarInMemCall, *mSettings); + memberAccessTok = singleMemberCallInScope(condBodyTok, loopVar->varId(), useLoopVarInMemCall, mSettings); if (memberAccessTok) { const Token *memberCallTok = memberAccessTok->astOperand2(); const int contVarId = memberAccessTok->astOperand1()->varId(); @@ -3242,7 +3293,7 @@ void CheckStl::useStlAlgorithm() } } -void CheckStl::knownEmptyContainerError(const Token *tok, const std::string& algo) +void CheckStlImpl::knownEmptyContainerError(const Token *tok, const std::string& algo) { const std::string var = tok ? tok->expressionString() : std::string("var"); @@ -3273,9 +3324,9 @@ static bool isKnownEmptyContainer(const Token* tok) }); } -void CheckStl::knownEmptyContainer() +void CheckStlImpl::knownEmptyContainer() { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("knownEmptyContainer")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("knownEmptyContainer")) return; logChecker("CheckStl::knownEmptyContainer"); // style for (const Scope *function : mTokenizer->getSymbolDatabase()->functionScopes) { @@ -3300,8 +3351,8 @@ void CheckStl::knownEmptyContainer() if (args.empty()) continue; - for (int argnr = 1; argnr <= args.size(); ++argnr) { - const Library::ArgumentChecks::IteratorInfo *i = mSettings->library.getArgIteratorInfo(tok, argnr); + for (size_t argnr = 1; argnr <= args.size(); ++argnr) { + const Library::ArgumentChecks::IteratorInfo *i = mSettings.library.getArgIteratorInfo(tok, argnr); if (!i) continue; const Token * const argTok = args[argnr - 1]; @@ -3316,7 +3367,7 @@ void CheckStl::knownEmptyContainer() } } -void CheckStl::eraseIteratorOutOfBoundsError(const Token *ftok, const Token* itertok, const ValueFlow::Value* val) +void CheckStlImpl::eraseIteratorOutOfBoundsError(const Token *ftok, const Token* itertok, const ValueFlow::Value* val) { if (!ftok || !itertok || !val) { reportError(ftok, Severity::error, "eraseIteratorOutOfBounds", @@ -3337,8 +3388,8 @@ void CheckStl::eraseIteratorOutOfBoundsError(const Token *ftok, const Token* ite } const Severity severity = isConditional ? Severity::warning : Severity::error; - const std::string id = isConditional ? "eraseIteratorOutOfBoundsCond" : "eraseIteratorOutOfBounds"; - reportError(ftok, severity, + const char* id = isConditional ? "eraseIteratorOutOfBoundsCond" : "eraseIteratorOutOfBounds"; + reportError(getErrorPath(ftok, val, msg), severity, id, msg, CWE628, Certainty::normal); } @@ -3361,7 +3412,7 @@ static const ValueFlow::Value* getOOBIterValue(const Token* tok, const ValueFlow return it != tok->values().end() ? &*it : nullptr; } -void CheckStl::eraseIteratorOutOfBounds() +void CheckStlImpl::eraseIteratorOutOfBounds() { logChecker("CheckStl::eraseIteratorOutOfBounds"); for (const Scope *function : mTokenizer->getSymbolDatabase()->functionScopes) { @@ -3387,6 +3438,383 @@ void CheckStl::eraseIteratorOutOfBounds() } } +namespace { +// An iterator position described by the ValueFlow values attached to the iterator expression + struct IteratorPosition { + const ValueFlow::Value* value = nullptr; // ITERATOR_START or ITERATOR_END value + const ValueFlow::Value* sizeValue = nullptr; // container size value with the same path, if available + bool fromEnd() const { + return value->isIteratorEndValue(); + } + explicit operator bool() const { + return value != nullptr; + } + }; + +// A number of elements together with the ValueFlow values it was derived from + struct ElementCount { + MathLib::bigint count = 0; + std::vector values; + explicit operator bool() const { + return !values.empty(); + } + }; + +// The best candidate proving an out of bounds access, preferring proofs without possible values + struct BestCandidate { + ElementCount best; + bool certain = false; + void consider(const ElementCount& candidate) + { + const bool candidateCertain = + std::none_of(candidate.values.cbegin(), candidate.values.cend(), std::mem_fn(&ValueFlow::Value::isPossible)); + if (best && (certain || !candidateCertain)) + return; + best = candidate; + certain = candidateCertain; + } + }; +} // namespace + +// Get the first ValueFlow value of a token matching the predicate, preferring known values +template +static const ValueFlow::Value* selectPreferredValue(const Token* tok, const Predicate& pred) +{ + const ValueFlow::Value* result = nullptr; + for (const ValueFlow::Value& value : tok->values()) { + if (!pred(value)) + continue; + if (result && !(value.isKnown() && !result->isKnown())) + continue; + result = &value; + } + return result; +} + +// Get the iterator value of an iterator expression together with the container size value that +// ValueFlow has added to the iterator +static IteratorPosition getIteratorPosition(const Token* tok, const Settings& settings) +{ + IteratorPosition position; + if (!tok) + return position; + position.value = selectPreferredValue(tok, [&](const ValueFlow::Value& value) { + return isUsableValue(value, settings) && value.isIteratorValue(); + }); + if (!position.value) + return position; + position.sizeValue = selectPreferredValue(tok, [&](const ValueFlow::Value& value) { + return isUsableValue(value, settings) && value.isContainerSizeValue() && value.path == position.value->path && + sizeValueAppliesToIterator(value, *position.value); + }); + return position; +} + +// Compute the distance last-first between two iterators into the same container +static ElementCount getIteratorDistance(const IteratorPosition& first, const IteratorPosition& last) +{ + ElementCount distance; + if (first.value->path != last.value->path) + return distance; + // bounded values could make the distance an overestimate + if (first.value->bound != ValueFlow::Value::Bound::Point || last.value->bound != ValueFlow::Value::Bound::Point) + return distance; + if (first.fromEnd() == last.fromEnd()) { // the container size cancels out + distance.count = last.value->intvalue - first.value->intvalue; + distance.values = {first.value, last.value}; + return distance; + } + const IteratorPosition& endPosition = first.fromEnd() ? first : last; + if (!endPosition.sizeValue || endPosition.sizeValue->bound != ValueFlow::Value::Bound::Point) + return distance; + const MathLib::bigint endIndex = endPosition.sizeValue->intvalue + endPosition.value->intvalue; + distance.count = last.fromEnd() ? endIndex - first.value->intvalue : last.value->intvalue - endIndex; + distance.values = {first.value, last.value, endPosition.sizeValue}; + return distance; +} + +// Compute the number of elements available in the container behind the iterator position +static ElementCount getAvailableSpace(const IteratorPosition& position) +{ + ElementCount available; + // the position could be smaller, which would make more elements available + if (position.value->bound == ValueFlow::Value::Bound::Upper) + return available; + if (position.fromEnd()) { // the container size cancels out + available.count = -position.value->intvalue; + available.values = {position.value}; + return available; + } + // the container size could be larger, which would make more elements available + if (!position.sizeValue || position.sizeValue->bound == ValueFlow::Value::Bound::Lower) + return available; + available.count = position.sizeValue->intvalue - position.value->intvalue; + available.values = {position.value, position.sizeValue}; + return available; +} + +// Find iterator values and paired container sizes of the iterator that prove accessing +// elements to be out of bounds, preferring a proof that does not rely on possible values +static ElementCount findInsufficientSpace(const Token* tok, + MathLib::bigint accessed, + MathLib::bigint sourcePath, + const Settings& settings) +{ + BestCandidate insufficient; + if (!tok) + return insufficient.best; + const auto consider = [&](const ElementCount& candidate) { + if (!candidate) + return; + if (candidate.count < 0 || accessed <= candidate.count) + return; // the space is sufficient + insufficient.consider(candidate); + }; + for (const ValueFlow::Value& value : tok->values()) { + if (!isUsableValue(value, settings) || !value.isIteratorValue()) + continue; + if (value.path != 0 && sourcePath != 0 && value.path != sourcePath) + continue; + IteratorPosition position; + position.value = &value; + if (position.fromEnd()) { // the available space does not depend on the container size + consider(getAvailableSpace(position)); + continue; + } + for (const ValueFlow::Value& sizeValue : tok->values()) { + if (!isUsableValue(sizeValue, settings) || !sizeValue.isContainerSizeValue() || sizeValue.path != value.path) + continue; + if (!sizeValueAppliesToIterator(sizeValue, value)) + continue; + position.sizeValue = &sizeValue; + consider(getAvailableSpace(position)); + } + } + return insufficient.best; +} + +// Find iterator values and paired container sizes of the source range that prove more than +// elements to be accessed, preferring a proof that does not rely on possible values +static ElementCount findExcessiveDistance(const Token* firstTok, + const Token* lastTok, + MathLib::bigint available, + MathLib::bigint destPath, + const Settings& settings) +{ + BestCandidate excessive; + const auto consider = [&](const ElementCount& candidate) { + if (!candidate) + return; + if (candidate.count <= available) + return; // the access is within bounds + excessive.consider(candidate); + }; + for (const ValueFlow::Value& firstValue : firstTok->values()) { + if (!isUsableValue(firstValue, settings) || !firstValue.isIteratorValue()) + continue; + if (firstValue.path != 0 && destPath != 0 && firstValue.path != destPath) + continue; + for (const ValueFlow::Value& lastValue : lastTok->values()) { + if (!isUsableValue(lastValue, settings) || !lastValue.isIteratorValue()) + continue; + IteratorPosition first, last; + first.value = &firstValue; + last.value = &lastValue; + if (first.fromEnd() == last.fromEnd()) { // the distance does not depend on the container size + consider(getIteratorDistance(first, last)); + continue; + } + IteratorPosition& endPosition = first.fromEnd() ? first : last; + const Token* const endTok = first.fromEnd() ? firstTok : lastTok; + for (const ValueFlow::Value& sizeValue : endTok->values()) { + if (!isUsableValue(sizeValue, settings) || !sizeValue.isContainerSizeValue() || + sizeValue.path != endPosition.value->path) + continue; + if (!sizeValueAppliesToIterator(sizeValue, *endPosition.value)) + continue; + endPosition.sizeValue = &sizeValue; + consider(getIteratorDistance(first, last)); + } + } + } + return excessive.best; +} + +// Find count values of a count-based algorithm that prove more than elements to be accessed +static ElementCount findExcessiveCount(const Token* tok, + MathLib::bigint available, + MathLib::bigint destPath, + const Settings& settings) +{ + BestCandidate excessive; + if (!tok) + return excessive.best; + for (const ValueFlow::Value& value : tok->values()) { + if (!isUsableValue(value, settings) || !value.isIntValue()) + continue; + // a count with an upper bound could be smaller, which would make fewer elements accessed + if (value.bound == ValueFlow::Value::Bound::Upper) + continue; + if (value.path != 0 && destPath != 0 && value.path != destPath) + continue; + if (value.intvalue <= available) + continue; // the access is within bounds + ElementCount candidate; + candidate.count = value.intvalue; + candidate.values.push_back(&value); + excessive.consider(candidate); + } + return excessive.best; +} + +// Do not warn when the proof relies on possible values on both sides +static bool bothSidesPossible(const ElementCount& accessed, const ElementCount& available) +{ + const auto isPossible = std::mem_fn(&ValueFlow::Value::isPossible); + return std::any_of(accessed.values.cbegin(), accessed.values.cend(), isPossible) && + std::any_of(available.values.cbegin(), available.values.cend(), isPossible); +} + +// Get the number of accessed elements of a count-based algorithm such as std::fill_n +static const ValueFlow::Value* getCountValue(const Token* tok, const Settings& settings) +{ + if (!tok) + return nullptr; + return selectPreferredValue(tok, [&](const ValueFlow::Value& value) { + // a count with an upper bound could be smaller, which would make fewer elements accessed + return isUsableValue(value, settings) && value.isIntValue() && value.bound != ValueFlow::Value::Bound::Upper; + }); +} + +void CheckStlImpl::algorithmOutOfBounds() +{ + logChecker("CheckStl::algorithmOutOfBounds"); + for (const Scope* function : mTokenizer->getSymbolDatabase()->functionScopes) { + for (const Token* tok = function->bodyStart; tok != function->bodyEnd; tok = tok->next()) { + if (!Token::Match(tok, "std :: %name% (")) + continue; + const Token* const nameTok = tok->tokAt(2); + // algorithms accessing the range denoted by the third argument exactly last1-first1 times.. + const bool exact = Token::Match( + nameTok, + "copy|move|swap_ranges|transform|replace_copy|replace_copy_if|reverse_copy|equal|mismatch|is_permutation|partial_sum|adjacent_difference|inner_product ("); + // ..or at most last1-first1 times, depending on the values in the input range.. + const bool atMost = !exact && Token::Match(nameTok, "copy_if|remove_copy|remove_copy_if|unique_copy ("); + // ..or accessing their iterator arguments as many times as the count argument says + const bool countBased = !exact && !atMost && Token::Match(nameTok, "copy_n|fill_n|generate_n ("); + if (!exact && !atMost && !countBased) + continue; + if (atMost && !mSettings.certainty.isEnabled(Certainty::inconclusive)) + continue; + const std::vector args = getArguments(nameTok); + if (args.size() < 3) + continue; + ElementCount accessed; // source access count using the preferred values + std::vector iterArgs; + if (countBased) { + if (const ValueFlow::Value* countValue = getCountValue(args[1], mSettings)) { + accessed.count = countValue->intvalue; + accessed.values.push_back(countValue); + } + iterArgs.push_back(args[0]); + if (Token::simpleMatch(nameTok, "copy_n")) + iterArgs.push_back(args[2]); // copy_n also writes through the third argument + } else { + // two-range overloads taking a last2 iterator do not access the second range out of bounds + if (Token::Match(nameTok, "equal|mismatch|is_permutation") && args.size() >= 4 && astIsIterator(args[3])) + continue; + // both iterators must refer to the same container + const ValueFlow::Value firstLifetime = getLifetimeIteratorValue(args[0]); + const ValueFlow::Value lastLifetime = getLifetimeIteratorValue(args[1]); + if (!firstLifetime.tokvalue || !lastLifetime.tokvalue) + continue; + if (!isSameIteratorContainerExpression(firstLifetime.tokvalue, + lastLifetime.tokvalue, + mSettings, + firstLifetime.lifetimeKind)) + continue; + const IteratorPosition first = getIteratorPosition(args[0], mSettings); + const IteratorPosition last = getIteratorPosition(args[1], mSettings); + if (first && last) + accessed = getIteratorDistance(first, last); + iterArgs.push_back(args[2]); + if (Token::simpleMatch(nameTok, "transform") && args.size() == 5) + iterArgs.push_back(args[3]); // binary transform also writes through the fourth argument + } + if (accessed.count <= 0) + accessed = ElementCount(); // there is no preferred source access count + for (const Token* const iterArg : iterArgs) { + // check the preferred source access count against all destination values.. + ElementCount sourceCount = accessed; + ElementCount available; + if (sourceCount) + available = + findInsufficientSpace(iterArg, sourceCount.count, sourceCount.values.front()->path, mSettings); + if (!available || bothSidesPossible(sourceCount, available)) { + // ..or all source access counts against the preferred destination values + const IteratorPosition dest = getIteratorPosition(iterArg, mSettings); + if (!dest) + continue; + available = getAvailableSpace(dest); + if (!available || available.count < 0) + continue; + sourceCount = + countBased + ? findExcessiveCount(args[1], available.count, dest.value->path, mSettings) + : findExcessiveDistance(args[0], args[1], available.count, dest.value->path, mSettings); + if (!sourceCount || bothSidesPossible(sourceCount, available)) + continue; + } + const ValueFlow::Value* conditionValue = nullptr; + bool inconclusiveValues = false; + std::vector usedValues = sourceCount.values; + usedValues.insert(usedValues.end(), available.values.cbegin(), available.values.cend()); + for (const ValueFlow::Value* value : usedValues) { + if (!conditionValue && value->condition) + conditionValue = value; + inconclusiveValues |= value->isInconclusive(); + } + if (conditionValue && !mSettings.severity.isEnabled(Severity::warning)) + continue; + const ValueFlow::Value* pathValue = conditionValue ? conditionValue : available.values.back(); + algorithmOutOfBoundsError(iterArg, + "std::" + nameTok->str(), + sourceCount.count, + available.count, + pathValue, + atMost, + atMost || inconclusiveValues); + } + } + } +} + +void CheckStlImpl::algorithmOutOfBoundsError(const Token* tok, + const std::string& algoName, + MathLib::bigint accessed, + MathLib::bigint available, + const ValueFlow::Value* value, + bool mayAccessFewer, + bool inconclusive) +{ + const Token* const condition = value ? value->condition : nullptr; + const std::string iterExpr = tok ? tok->expressionString() : "it"; + const std::string accessedStr = MathLib::toString(accessed) + (accessed == 1 ? " element" : " elements"); + const std::string availableStr = MathLib::toString(available) + (available == 1 ? " element is" : " elements are"); + const std::string body = "algorithm '" + algoName + "' " + (mayAccessFewer ? "may access up to " : "accesses ") + + accessedStr + " through the iterator '" + iterExpr + "' but only " + availableStr + + " available."; + const std::string msg = + condition ? (ValueFlow::eitherTheConditionIsRedundant(condition) + " or the " + body) : ("The " + body); + ErrorPath errorPath = getErrorPath(tok, value, "Access out of bounds"); + reportError(std::move(errorPath), + (condition || mayAccessFewer) ? Severity::warning : Severity::error, + "algorithmOutOfBounds", + msg, + CWE788, + inconclusive ? Certainty::inconclusive : Certainty::normal); +} + static bool isMutex(const Variable* var) { const Token* tok = Token::typeDecl(var->nameToken()).first; @@ -3408,23 +3836,23 @@ static bool isLocalMutex(const Variable* var, const Scope* scope) return !var->isReference() && !var->isRValueReference() && !var->isStatic() && var->scope() == scope; } -void CheckStl::globalLockGuardError(const Token* tok) +void CheckStlImpl::globalLockGuardError(const Token* tok) { reportError(tok, Severity::warning, "globalLockGuard", "Lock guard is defined globally. Lock guards are intended to be local. A global lock guard could lead to a deadlock since it won't unlock until the end of the program.", CWE833, Certainty::normal); } -void CheckStl::localMutexError(const Token* tok) +void CheckStlImpl::localMutexError(const Token* tok) { reportError(tok, Severity::warning, "localMutex", "The lock is ineffective because the mutex is locked at the same scope as the mutex itself.", CWE667, Certainty::normal); } -void CheckStl::checkMutexes() +void CheckStlImpl::checkMutexes() { - if (!mSettings->severity.isEnabled(Severity::warning)) + if (!mSettings.severity.isEnabled(Severity::warning)) return; logChecker("CheckStl::checkMutexes"); // warning for (const Scope *function : mTokenizer->getSymbolDatabase()->functionScopes) { @@ -3459,13 +3887,13 @@ void CheckStl::checkMutexes() } } -void CheckStl::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) +void CheckStl::runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) { if (!tokenizer.isCPP()) { return; } - CheckStl checkStl(&tokenizer, &tokenizer.getSettings(), errorLogger); + CheckStlImpl checkStl(&tokenizer, tokenizer.getSettings(), errorLogger); checkStl.erase(); checkStl.if_find(); checkStl.checkFindInsert(); @@ -3486,6 +3914,7 @@ void CheckStl::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) checkStl.mismatchingContainerIterator(); checkStl.knownEmptyContainer(); checkStl.eraseIteratorOutOfBounds(); + checkStl.algorithmOutOfBounds(); checkStl.stlBoundaries(); checkStl.checkDereferenceInvalidIterator(); @@ -3496,15 +3925,15 @@ void CheckStl::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) checkStl.size(); } -void CheckStl::getErrorMessages(ErrorLogger* errorLogger, const Settings* settings) const +void CheckStl::getErrorMessages(ErrorLogger& errorLogger, const Settings& settings) const { - CheckStl c(nullptr, settings, errorLogger); + CheckStlImpl c(nullptr, settings, errorLogger); c.outOfBoundsError(nullptr, "container", nullptr, "x", nullptr); c.invalidIteratorError(nullptr, "iterator"); c.iteratorsError(nullptr, "container1", "container2"); c.iteratorsError(nullptr, nullptr, "container"); c.invalidContainerLoopError(nullptr, nullptr, ErrorPath{}); - c.invalidContainerError(nullptr, nullptr, nullptr, ErrorPath{}); + c.invalidContainerError(nullptr, nullptr, ErrorPath{}); c.invalidContainerReferenceError(nullptr, nullptr, ErrorPath{}); c.mismatchingContainerIteratorError(nullptr, nullptr, nullptr); c.mismatchingContainersError(nullptr, nullptr); @@ -3516,7 +3945,7 @@ void CheckStl::getErrorMessages(ErrorLogger* errorLogger, const Settings* settin c.stlBoundariesError(nullptr); c.if_findError(nullptr, false); c.if_findError(nullptr, true); - c.checkFindInsertError(nullptr); + c.checkFindInsertError(nullptr, nullptr); c.string_c_strError(nullptr); c.string_c_strReturn(nullptr); c.string_c_strParam(nullptr, 0); @@ -3530,13 +3959,14 @@ void CheckStl::getErrorMessages(ErrorLogger* errorLogger, const Settings* settin c.redundantIfRemoveError(nullptr); c.uselessCallsReturnValueError(nullptr, "str", "find"); c.uselessCallsSwapError(nullptr, "str"); - c.uselessCallsSubstrError(nullptr, SubstrErrorType::COPY); + c.uselessCallsSubstrError(nullptr, CheckStlImpl::SubstrErrorType::COPY); c.uselessCallsConstructorError(nullptr); c.uselessCallsEmptyError(nullptr); c.uselessCallsRemoveError(nullptr, "remove"); c.dereferenceInvalidIteratorError(nullptr, "i"); // TODO: derefInvalidIteratorRedundantCheck c.eraseIteratorOutOfBoundsError(nullptr, nullptr); + c.algorithmOutOfBoundsError(nullptr, "std::copy", 10, 6, nullptr, false, false); c.useStlAlgorithmError(nullptr, ""); c.knownEmptyContainerError(nullptr, ""); c.globalLockGuardError(nullptr); diff --git a/lib/checkstl.h b/lib/checkstl.h index 60b9d4c7000..e62b66831b9 100644 --- a/lib/checkstl.h +++ b/lib/checkstl.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,8 +23,11 @@ //--------------------------------------------------------------------------- #include "check.h" +#include "checkimpl.h" #include "config.h" #include "errortypes.h" +#include "mathlib.h" +#include "vfvalue.h" #include #include @@ -35,10 +38,6 @@ class Token; class Variable; class ErrorLogger; class Tokenizer; -namespace ValueFlow -{ - class Value; -} /// @addtogroup Checks /// @{ @@ -48,15 +47,43 @@ namespace ValueFlow class CPPCHECKLIB CheckStl : public Check { public: /** This constructor is used when registering the CheckClass */ - CheckStl() : Check(myName()) {} + CheckStl() : Check("STL usage") {} private: - /** This constructor is used when running checks. */ - CheckStl(const Tokenizer* tokenizer, const Settings* settings, ErrorLogger* errorLogger) - : Check(myName(), tokenizer, settings, errorLogger) {} - /** run checks, the token list is not simplified */ - void runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) override; + void runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) override; + + void getErrorMessages(ErrorLogger& errorLogger, const Settings& settings) const override; + + std::string classInfo() const override { + return "Check for invalid usage of STL:\n" + "- out of bounds errors\n" + "- misuse of iterators when iterating through a container\n" + "- mismatching containers in calls\n" + "- same iterators in calls\n" + "- dereferencing an erased iterator\n" + "- for vectors: using iterator/pointer after push_back has been used\n" + "- optimisation: use empty() instead of size() to guarantee fast code\n" + "- suspicious condition when using find\n" + "- unnecessary searching in associative containers\n" + "- redundant condition\n" + "- common mistakes when using string::c_str()\n" + "- useless calls of string and STL functions\n" + "- dereferencing an invalid iterator\n" + "- erasing an iterator that is out of bounds\n" + "- out of bounds access of an iterator passed to an STL algorithm\n" + "- reading from empty STL container\n" + "- iterating over an empty STL container\n" + "- consider using an STL algorithm instead of raw loop\n" + "- incorrect locking with mutex\n"; + } +}; + +class CPPCHECKLIB CheckStlImpl : public CheckImpl { +public: + /** This constructor is used when running checks. */ + CheckStlImpl(const Tokenizer* tokenizer, const Settings& settings, ErrorLogger& errorLogger) + : CheckImpl(tokenizer, settings, errorLogger) {} /** Accessing container out of bounds using ValueFlow */ void outOfBounds(); @@ -155,6 +182,12 @@ class CPPCHECKLIB CheckStl : public Check { void eraseIteratorOutOfBounds(); + /** + * Check that the iterator given to an STL algorithm is not accessed + * out of bounds: std::equal(in.begin(), in.end(), out.begin()) + */ + void algorithmOutOfBounds(); + void checkMutexes(); bool isContainerSize(const Token *containerToken, const Token *expr) const; @@ -178,16 +211,17 @@ class CPPCHECKLIB CheckStl : public Check { void iteratorsError(const Token* tok, const std::string& containerName1, const std::string& containerName2); void iteratorsError(const Token* tok, const Token* containerTok, const std::string& containerName); void mismatchingContainerIteratorError(const Token* containerTok, const Token* iterTok, const Token* containerTok2); - void mismatchingContainersError(const Token* tok1, const Token* tok2); + void mismatchingContainersError(const Token* tok1, const Token* tok2, ValueFlow::Value::LifetimeKind ltk = ValueFlow::Value::LifetimeKind::Iterator); void mismatchingContainerExpressionError(const Token *tok1, const Token *tok2); void sameIteratorExpressionError(const Token *tok); void stlBoundariesError(const Token* tok); void if_findError(const Token* tok, bool str); - void checkFindInsertError(const Token *tok); + /** @param condTok the comparison in the if condition, nullptr => no specific suggestion */ + void checkFindInsertError(const Token* tok, const Token* condTok); void sizeError(const Token* tok); void redundantIfRemoveError(const Token* tok); void invalidContainerLoopError(const Token* tok, const Token* loopTok, ErrorPath errorPath); - void invalidContainerError(const Token *tok, const Token * contTok, const ValueFlow::Value *val, ErrorPath errorPath); + void invalidContainerError(const Token *tok, const ValueFlow::Value *val, ErrorPath errorPath); void invalidContainerReferenceError(const Token* tok, const Token* contTok, ErrorPath errorPath); void uselessCallsReturnValueError(const Token* tok, const std::string& varname, const std::string& function); @@ -207,36 +241,16 @@ class CPPCHECKLIB CheckStl : public Check { void eraseIteratorOutOfBoundsError(const Token* ftok, const Token* itertok, const ValueFlow::Value* val = nullptr); + void algorithmOutOfBoundsError(const Token* tok, + const std::string& algoName, + MathLib::bigint accessed, + MathLib::bigint available, + const ValueFlow::Value* value, + bool mayAccessFewer, + bool inconclusive); + void globalLockGuardError(const Token *tok); void localMutexError(const Token *tok); - - void getErrorMessages(ErrorLogger* errorLogger, const Settings* settings) const override; - - static std::string myName() { - return "STL usage"; - } - - std::string classInfo() const override { - return "Check for invalid usage of STL:\n" - "- out of bounds errors\n" - "- misuse of iterators when iterating through a container\n" - "- mismatching containers in calls\n" - "- same iterators in calls\n" - "- dereferencing an erased iterator\n" - "- for vectors: using iterator/pointer after push_back has been used\n" - "- optimisation: use empty() instead of size() to guarantee fast code\n" - "- suspicious condition when using find\n" - "- unnecessary searching in associative containers\n" - "- redundant condition\n" - "- common mistakes when using string::c_str()\n" - "- useless calls of string and STL functions\n" - "- dereferencing an invalid iterator\n" - "- erasing an iterator that is out of bounds\n" - "- reading from empty STL container\n" - "- iterating over an empty STL container\n" - "- consider using an STL algorithm instead of raw loop\n" - "- incorrect locking with mutex\n"; - } }; /// @} //--------------------------------------------------------------------------- diff --git a/lib/checkstring.cpp b/lib/checkstring.cpp index 64b575df72d..1d391895c3e 100644 --- a/lib/checkstring.cpp +++ b/lib/checkstring.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -36,11 +36,6 @@ //--------------------------------------------------------------------------- -// Register this check class (by creating a static instance of it) -namespace { - CheckString instance; -} - // CWE ids used: static const CWE CWE570(570U); // Expression is Always False static const CWE CWE571(571U); // Expression is Always True @@ -52,7 +47,7 @@ static const CWE CWE758(758U); // Reliance on Undefined, Unspecified, or Imple //--------------------------------------------------------------------------- // Writing string literal is UB //--------------------------------------------------------------------------- -void CheckString::stringLiteralWrite() +void CheckStringImpl::stringLiteralWrite() { logChecker("CheckString::stringLiteralWrite"); const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); @@ -60,7 +55,7 @@ void CheckString::stringLiteralWrite() for (const Token* tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) { if (!tok->variable() || !tok->variable()->isPointer()) continue; - const Token *str = tok->getValueTokenMinStrSize(*mSettings); + const Token *str = tok->getValueTokenMinStrSize(mSettings); if (!str) continue; if (Token::Match(tok, "%var% [") && Token::simpleMatch(tok->linkAt(1), "] =")) @@ -71,7 +66,7 @@ void CheckString::stringLiteralWrite() } } -void CheckString::stringLiteralWriteError(const Token *tok, const Token *strValue) +void CheckStringImpl::stringLiteralWriteError(const Token *tok, const Token *strValue) { std::list callstack{ tok }; if (strValue) @@ -94,9 +89,9 @@ void CheckString::stringLiteralWriteError(const Token *tok, const Token *strValu // Check for string comparison involving two static strings. // if(strcmp("00FF00","00FF00")==0) // <- statement is always true //--------------------------------------------------------------------------- -void CheckString::checkAlwaysTrueOrFalseStringCompare() +void CheckStringImpl::checkAlwaysTrueOrFalseStringCompare() { - if (!mSettings->severity.isEnabled(Severity::warning)) + if (!mSettings.severity.isEnabled(Severity::warning)) return; logChecker("CheckString::checkAlwaysTrueOrFalseStringCompare"); // warning @@ -138,7 +133,7 @@ void CheckString::checkAlwaysTrueOrFalseStringCompare() } } -void CheckString::alwaysTrueFalseStringCompareError(const Token *tok, const std::string& str1, const std::string& str2) +void CheckStringImpl::alwaysTrueFalseStringCompareError(const Token *tok, const std::string& str1, const std::string& str2) { constexpr std::size_t stringLen = 10; const std::string string1 = (str1.size() < stringLen) ? str1 : (str1.substr(0, stringLen-2) + ".."); @@ -150,7 +145,7 @@ void CheckString::alwaysTrueFalseStringCompareError(const Token *tok, const std: "Therefore the comparison is unnecessary and looks suspicious.", (str1==str2)?CWE571:CWE570, Certainty::normal); } -void CheckString::alwaysTrueStringVariableCompareError(const Token *tok, const std::string& str1, const std::string& str2) +void CheckStringImpl::alwaysTrueStringVariableCompareError(const Token *tok, const std::string& str1, const std::string& str2) { reportError(tok, Severity::warning, "stringCompare", "Comparison of identical string variables.\n" @@ -163,9 +158,9 @@ void CheckString::alwaysTrueStringVariableCompareError(const Token *tok, const s // Detect "str == '\0'" where "*str == '\0'" is correct. // Comparing char* with each other instead of using strcmp() //----------------------------------------------------------------------------- -void CheckString::checkSuspiciousStringCompare() +void CheckStringImpl::checkSuspiciousStringCompare() { - if (!mSettings->severity.isEnabled(Severity::warning)) + if (!mSettings.severity.isEnabled(Severity::warning)) return; logChecker("CheckString::checkSuspiciousStringCompare"); // warning @@ -202,14 +197,14 @@ void CheckString::checkSuspiciousStringCompare() } } -void CheckString::suspiciousStringCompareError(const Token* tok, const std::string& var, bool isLong) +void CheckStringImpl::suspiciousStringCompareError(const Token* tok, const std::string& var, bool isLong) { const std::string cmpFunc = isLong ? "wcscmp" : "strcmp"; reportError(tok, Severity::warning, "literalWithCharPtrCompare", "$symbol:" + var + "\nString literal compared with variable '$symbol'. Did you intend to use " + cmpFunc + "() instead?", CWE595, Certainty::normal); } -void CheckString::suspiciousStringCompareError_char(const Token* tok, const std::string& var) +void CheckStringImpl::suspiciousStringCompareError_char(const Token* tok, const std::string& var) { reportError(tok, Severity::warning, "charLiteralWithCharPtrCompare", "$symbol:" + var + "\nChar literal compared with pointer '$symbol'. Did you intend to dereference it?", CWE595, Certainty::normal); @@ -225,7 +220,7 @@ static bool isChar(const Variable* var) return (var && !var->isPointer() && !var->isArray() && (var->typeStartToken()->str() == "char" || var->typeStartToken()->str() == "wchar_t")); } -void CheckString::strPlusChar() +void CheckStringImpl::strPlusChar() { logChecker("CheckString::strPlusChar"); const SymbolDatabase* symbolDatabase = mTokenizer->getSymbolDatabase(); @@ -241,7 +236,7 @@ void CheckString::strPlusChar() } } -void CheckString::strPlusCharError(const Token *tok) +void CheckStringImpl::strPlusCharError(const Token *tok) { std::string charType = "char"; if (tok && tok->astOperand2() && tok->astOperand2()->variable()) @@ -277,9 +272,9 @@ static bool isMacroUsage(const Token* tok) // Implicit casts of string literals to bool // Comparing string literal with strlen() with wrong length //--------------------------------------------------------------------------- -void CheckString::checkIncorrectStringCompare() +void CheckStringImpl::checkIncorrectStringCompare() { - if (!mSettings->severity.isEnabled(Severity::warning)) + if (!mSettings.severity.isEnabled(Severity::warning)) return; logChecker("CheckString::checkIncorrectStringCompare"); // warning @@ -319,19 +314,19 @@ void CheckString::checkIncorrectStringCompare() } } else if (Token::Match(tok, "%str%|%char%") && !Token::Match(tok->next(), "%name%") && - isUsedAsBool(tok, *mSettings) && + isUsedAsBool(tok, mSettings) && !isMacroUsage(tok)) incorrectStringBooleanError(tok, tok->str()); } } } -void CheckString::incorrectStringCompareError(const Token *tok, const std::string& func, const std::string &string) +void CheckStringImpl::incorrectStringCompareError(const Token *tok, const std::string& func, const std::string &string) { reportError(tok, Severity::warning, "incorrectStringCompare", "$symbol:" + func + "\nString literal " + string + " doesn't match length argument for $symbol().", CWE570, Certainty::normal); } -void CheckString::incorrectStringBooleanError(const Token *tok, const std::string& string) +void CheckStringImpl::incorrectStringBooleanError(const Token *tok, const std::string& string) { const bool charLiteral = isCharLiteral(string); const std::string literalType = charLiteral ? "char" : "string"; @@ -346,9 +341,9 @@ void CheckString::incorrectStringBooleanError(const Token *tok, const std::strin // always true: strcmp(str,"a")==0 || strcmp(str,"b") // TODO: Library configuration for string comparison functions //--------------------------------------------------------------------------- -void CheckString::overlappingStrcmp() +void CheckStringImpl::overlappingStrcmp() { - if (!mSettings->severity.isEnabled(Severity::warning)) + if (!mSettings.severity.isEnabled(Severity::warning)) return; logChecker("CheckString::overlappingStrcmp"); // warning @@ -400,7 +395,7 @@ void CheckString::overlappingStrcmp() if (args1[1]->isLiteral() && args2[1]->isLiteral() && args1[1]->str() != args2[1]->str() && - isSameExpression(true, args1[0], args2[0], *mSettings, true, false)) + isSameExpression(true, args1[0], args2[0], mSettings, true, false)) overlappingStrcmpError(eq0, ne0); } } @@ -408,7 +403,7 @@ void CheckString::overlappingStrcmp() } } -void CheckString::overlappingStrcmpError(const Token *eq0, const Token *ne0) +void CheckStringImpl::overlappingStrcmpError(const Token *eq0, const Token *ne0) { std::string eq0Expr(eq0 ? eq0->expressionString() : std::string("strcmp(x,\"abc\")")); if (eq0 && eq0->astParent()->str() == "!") @@ -425,7 +420,7 @@ void CheckString::overlappingStrcmpError(const Token *eq0, const Token *ne0) // Overlapping source and destination passed to sprintf(). // TODO: Library configuration for overlapping arguments //--------------------------------------------------------------------------- -void CheckString::sprintfOverlappingData() +void CheckStringImpl::sprintfOverlappingData() { logChecker("CheckString::sprintfOverlappingData"); @@ -451,7 +446,7 @@ void CheckString::sprintfOverlappingData() const bool same = isSameExpression(false, dest, arg, - *mSettings, + mSettings, true, false); if (same) { @@ -462,7 +457,7 @@ void CheckString::sprintfOverlappingData() } } -void CheckString::sprintfOverlappingDataError(const Token *funcTok, const Token *tok, const std::string &varname) +void CheckStringImpl::sprintfOverlappingDataError(const Token *funcTok, const Token *tok, const std::string &varname) { const std::string func = funcTok ? funcTok->str() : "s[n]printf"; @@ -476,9 +471,9 @@ void CheckString::sprintfOverlappingDataError(const Token *funcTok, const Token "to sprintf() or snprintf(), the results are undefined.\"", CWE628, Certainty::normal); } -void CheckString::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) +void CheckString::runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) { - CheckString checkString(&tokenizer, &tokenizer.getSettings(), errorLogger); + CheckStringImpl checkString(&tokenizer, tokenizer.getSettings(), errorLogger); // Checks checkString.strPlusChar(); @@ -490,9 +485,9 @@ void CheckString::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger checkString.checkAlwaysTrueOrFalseStringCompare(); } -void CheckString::getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const +void CheckString::getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const { - CheckString c(nullptr, settings, errorLogger); + CheckStringImpl c(nullptr, settings, errorLogger); c.stringLiteralWriteError(nullptr, nullptr); c.sprintfOverlappingDataError(nullptr, nullptr, "varname"); c.strPlusCharError(nullptr); diff --git a/lib/checkstring.h b/lib/checkstring.h index 76002f8ed3d..1614e4dc7d3 100644 --- a/lib/checkstring.h +++ b/lib/checkstring.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,6 +23,7 @@ //--------------------------------------------------------------------------- #include "check.h" +#include "checkimpl.h" #include "config.h" #include @@ -41,15 +42,31 @@ class Tokenizer; class CPPCHECKLIB CheckString : public Check { public: /** @brief This constructor is used when registering the CheckClass */ - CheckString() : Check(myName()) {} + CheckString() : Check("String") {} private: - /** @brief This constructor is used when running checks. */ - CheckString(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) - : Check(myName(), tokenizer, settings, errorLogger) {} - /** @brief Run checks against the normal token list */ - void runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) override; + void runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) override; + + void getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const override; + + std::string classInfo() const override { + return "Detect misusage of C-style strings:\n" + "- overlapping buffers passed to sprintf as source and destination\n" + "- incorrect length arguments for 'substr' and 'strncmp'\n" + "- suspicious condition (runtime comparison of string literals)\n" + "- suspicious condition (string/char literals as boolean)\n" + "- suspicious comparison of a string literal with a char\\* variable\n" + "- suspicious comparison of '\\0' with a char\\* variable\n" + "- overlapping strcmp() expression\n"; + } +}; + +class CPPCHECKLIB CheckStringImpl : public CheckImpl { +public: + /** @brief This constructor is used when running checks. */ + CheckStringImpl(const Tokenizer *tokenizer, const Settings &settings, ErrorLogger &errorLogger) + : CheckImpl(tokenizer, settings, errorLogger) {} /** @brief undefined behaviour, writing string literal */ void stringLiteralWrite(); @@ -82,23 +99,6 @@ class CPPCHECKLIB CheckString : public Check { void suspiciousStringCompareError(const Token* tok, const std::string& var, bool isLong); void suspiciousStringCompareError_char(const Token* tok, const std::string& var); void overlappingStrcmpError(const Token* eq0, const Token *ne0); - - void getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const override; - - static std::string myName() { - return "String"; - } - - std::string classInfo() const override { - return "Detect misusage of C-style strings:\n" - "- overlapping buffers passed to sprintf as source and destination\n" - "- incorrect length arguments for 'substr' and 'strncmp'\n" - "- suspicious condition (runtime comparison of string literals)\n" - "- suspicious condition (string/char literals as boolean)\n" - "- suspicious comparison of a string literal with a char\\* variable\n" - "- suspicious comparison of '\\0' with a char\\* variable\n" - "- overlapping strcmp() expression\n"; - } }; /// @} //--------------------------------------------------------------------------- diff --git a/lib/checktype.cpp b/lib/checktype.cpp index 3f42bf075ef..447a8924360 100644 --- a/lib/checktype.cpp +++ b/lib/checktype.cpp @@ -44,11 +44,6 @@ //--------------------------------------------------------------------------- -// Register this check class (by creating a static instance of it) -namespace { - CheckType instance; -} - //--------------------------------------------------------------------------- // Checking for shift by too many bits //--------------------------------------------------------------------------- @@ -61,10 +56,10 @@ static const CWE CWE758(758U); // Reliance on Undefined, Unspecified, or Imple static const CWE CWE190(190U); // Integer Overflow or Wraparound -void CheckType::checkTooBigBitwiseShift() +void CheckTypeImpl::checkTooBigBitwiseShift() { // unknown sizeof(int) => can't run this checker - if (mSettings->platform.type == Platform::Type::Unspecified) + if (mSettings.platform.type == Platform::Type::Unspecified) return; logChecker("CheckType::checkTooBigBitwiseShift"); // platform @@ -95,27 +90,27 @@ void CheckType::checkTooBigBitwiseShift() (lhstype->type == ValueType::Type::WCHAR_T) || (lhstype->type == ValueType::Type::BOOL) || (lhstype->type == ValueType::Type::INT)) - lhsbits = mSettings->platform.int_bit; + lhsbits = mSettings.platform.int_bit; else if (lhstype->type == ValueType::Type::LONG) - lhsbits = mSettings->platform.long_bit; + lhsbits = mSettings.platform.long_bit; else if (lhstype->type == ValueType::Type::LONGLONG) - lhsbits = mSettings->platform.long_long_bit; + lhsbits = mSettings.platform.long_long_bit; else continue; // Get biggest rhs value. preferably a value which doesn't have 'condition'. - const ValueFlow::Value * value = tok->astOperand2()->getValueGE(lhsbits, *mSettings); - if (value && mSettings->isEnabled(value, false)) + const ValueFlow::Value * value = tok->astOperand2()->getValueGE(lhsbits, mSettings); + if (value && mSettings.isEnabled(value, false)) tooBigBitwiseShiftError(tok, lhsbits, *value); else if (lhstype->sign == ValueType::Sign::SIGNED) { - value = tok->astOperand2()->getValueGE(lhsbits-1, *mSettings); - if (value && mSettings->isEnabled(value, false)) + value = tok->astOperand2()->getValueGE(lhsbits-1, mSettings); + if (value && mSettings.isEnabled(value, false)) tooBigSignedBitwiseShiftError(tok, lhsbits, *value); } } } -void CheckType::tooBigBitwiseShiftError(const Token *tok, int lhsbits, const ValueFlow::Value &rhsbits) +void CheckTypeImpl::tooBigBitwiseShiftError(const Token *tok, int lhsbits, const ValueFlow::Value &rhsbits) { constexpr char id[] = "shiftTooManyBits"; @@ -134,11 +129,11 @@ void CheckType::tooBigBitwiseShiftError(const Token *tok, int lhsbits, const Val reportError(std::move(errorPath), rhsbits.errorSeverity() ? Severity::error : Severity::warning, id, errmsg.str(), CWE758, rhsbits.isInconclusive() ? Certainty::inconclusive : Certainty::normal); } -void CheckType::tooBigSignedBitwiseShiftError(const Token *tok, int lhsbits, const ValueFlow::Value &rhsbits) +void CheckTypeImpl::tooBigSignedBitwiseShiftError(const Token *tok, int lhsbits, const ValueFlow::Value &rhsbits) { constexpr char id[] = "shiftTooManyBitsSigned"; - const bool cpp14 = ((tok && tok->isCpp()) || (mTokenizer && mTokenizer->isCPP())) && (mSettings->standards.cpp >= Standards::CPP14); + const bool cpp14 = ((tok && tok->isCpp()) || (mTokenizer && mTokenizer->isCPP())) && (mSettings.standards.cpp >= Standards::CPP14); std::string behaviour = "undefined"; if (cpp14) @@ -153,7 +148,7 @@ void CheckType::tooBigSignedBitwiseShiftError(const Token *tok, int lhsbits, con if (cpp14) severity = Severity::portability; - if ((severity == Severity::portability) && !mSettings->severity.isEnabled(Severity::portability)) + if ((severity == Severity::portability) && !mSettings.severity.isEnabled(Severity::portability)) return; ErrorPath errorPath = getErrorPath(tok, &rhsbits, "Shift"); @@ -169,10 +164,10 @@ void CheckType::tooBigSignedBitwiseShiftError(const Token *tok, int lhsbits, con // Checking for integer overflow //--------------------------------------------------------------------------- -void CheckType::checkIntegerOverflow() +void CheckTypeImpl::checkIntegerOverflow() { // unknown sizeof(int) => can't run this checker - if (mSettings->platform.type == Platform::Type::Unspecified || mSettings->platform.int_bit >= MathLib::bigint_bits) + if (mSettings.platform.type == Platform::Type::Unspecified || mSettings.platform.int_bit >= MathLib::bigint_bits) return; logChecker("CheckType::checkIntegerOverflow"); // platform @@ -188,11 +183,11 @@ void CheckType::checkIntegerOverflow() unsigned int bits; if (vt->type == ValueType::Type::INT) - bits = mSettings->platform.int_bit; + bits = mSettings.platform.int_bit; else if (vt->type == ValueType::Type::LONG) - bits = mSettings->platform.long_bit; + bits = mSettings.platform.long_bit; else if (vt->type == ValueType::Type::LONGLONG) - bits = mSettings->platform.long_long_bit; + bits = mSettings.platform.long_long_bit; else continue; @@ -204,12 +199,12 @@ void CheckType::checkIntegerOverflow() // is there a overflow result value bool isOverflow = true; - const ValueFlow::Value *value = tok->getValueGE(maxvalue + 1, *mSettings); + const ValueFlow::Value *value = tok->getValueGE(maxvalue + 1, mSettings); if (!value) { - value = tok->getValueLE(-maxvalue - 2, *mSettings); + value = tok->getValueLE(-maxvalue - 2, mSettings); isOverflow = false; } - if (!value || !mSettings->isEnabled(value,false)) + if (!value || !mSettings.isEnabled(value,false)) continue; // For left shift, it's common practice to shift into the sign bit @@ -220,7 +215,16 @@ void CheckType::checkIntegerOverflow() } } -void CheckType::integerOverflowError(const Token *tok, const ValueFlow::Value &value, bool isOverflow) +static std::string getMessageId(const ValueFlow::Value &value, const char id[]) +{ + if (value.condition != nullptr) + return id + std::string("Cond"); + if (value.safe) + return std::string("safe") + static_cast(std::toupper(id[0])) + (id + 1); + return id; +} + +void CheckTypeImpl::integerOverflowError(const Token *tok, const ValueFlow::Value &value, bool isOverflow) { const std::string expr(tok ? tok->expressionString() : ""); const std::string type = isOverflow ? "overflow" : "underflow"; @@ -247,9 +251,9 @@ void CheckType::integerOverflowError(const Token *tok, const ValueFlow::Value &v // Checking for sign conversion when operand can be negative //--------------------------------------------------------------------------- -void CheckType::checkSignConversion() +void CheckTypeImpl::checkSignConversion() { - if (!mSettings->severity.isEnabled(Severity::warning)) + if (!mSettings.severity.isEnabled(Severity::warning)) return; logChecker("CheckType::checkSignConversion"); // warning @@ -268,7 +272,7 @@ void CheckType::checkSignConversion() if (!tok1) continue; const ValueFlow::Value* negativeValue = - ValueFlow::findValue(tok1->values(), *mSettings, [&](const ValueFlow::Value& v) { + ValueFlow::findValue(tok1->values(), mSettings, [&](const ValueFlow::Value& v) { return !v.isImpossible() && v.isIntValue() && (v.intvalue <= -1 || v.wideintvalue <= -1); }); if (!negativeValue) @@ -279,7 +283,7 @@ void CheckType::checkSignConversion() } } -void CheckType::signConversionError(const Token *tok, const ValueFlow::Value *negativeValue, const bool constvalue) +void CheckTypeImpl::signConversionError(const Token *tok, const ValueFlow::Value *negativeValue, const bool constvalue) { const std::string expr(tok ? tok->expressionString() : "var"); @@ -297,7 +301,7 @@ void CheckType::signConversionError(const Token *tok, const ValueFlow::Value *ne ErrorPath errorPath = getErrorPath(tok,negativeValue,"Negative value is converted to an unsigned value"); reportError(std::move(errorPath), Severity::warning, - Check::getMessageId(*negativeValue, "signConversion").c_str(), + getMessageId(*negativeValue, "signConversion").c_str(), msg.str(), CWE195, negativeValue->isInconclusive() ? Certainty::inconclusive : Certainty::normal); @@ -332,9 +336,9 @@ static bool checkTypeCombination(ValueType src, ValueType tgt, const Settings& s }); } -void CheckType::checkLongCast() +void CheckTypeImpl::checkLongCast() { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("truncLongCastAssignment")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("truncLongCastAssignment")) return; logChecker("CheckType::checkLongCast"); // style @@ -345,7 +349,7 @@ void CheckType::checkLongCast() continue; if (const ValueFlow::Value* v = tok->astOperand2()->getKnownValue(ValueFlow::Value::ValueType::INT)) { - if (mSettings->platform.isIntValue(v->intvalue)) + if (mSettings.platform.isIntValue(v->intvalue)) continue; } @@ -354,7 +358,7 @@ void CheckType::checkLongCast() if (!lhstype || !rhstype) continue; - if (!checkTypeCombination(*rhstype, *lhstype, *mSettings)) + if (!checkTypeCombination(*rhstype, *lhstype, mSettings)) continue; // assign int result to long/longlong const nonpointer? @@ -381,12 +385,12 @@ void CheckType::checkLongCast() if (tok->str() == "return") { if (Token::Match(tok->astOperand1(), "<<|*")) { const ValueType *type = tok->astOperand1()->valueType(); - if (type && checkTypeCombination(*type, *retVt, *mSettings) && + if (type && checkTypeCombination(*type, *retVt, mSettings) && type->pointer == 0U && type->originalTypeName.empty()) { if (!tok->astOperand1()->hasKnownIntValue()) { ret = tok; - } else if (!mSettings->platform.isIntValue(tok->astOperand1()->getKnownIntValue())) + } else if (!mSettings.platform.isIntValue(tok->astOperand1()->getKnownIntValue())) ret = tok; } } @@ -410,7 +414,7 @@ static void makeBaseTypeString(std::string& typeStr) typeStr.erase(typeStr.begin(), typeStr.begin() + pos + 6 + 1); } -void CheckType::longCastAssignError(const Token *tok, const ValueType* src, const ValueType* tgt) +void CheckTypeImpl::longCastAssignError(const Token *tok, const ValueType* src, const ValueType* tgt) { std::string srcStr = src ? src->str() : "int"; makeBaseTypeString(srcStr); @@ -423,7 +427,7 @@ void CheckType::longCastAssignError(const Token *tok, const ValueType* src, cons srcStr + " result is assigned to " + tgtStr + " variable. If the variable is " + tgtStr + " to avoid loss of information, then there is loss of information. To avoid loss of information you must cast a calculation operand to " + tgtStr + ", for example 'l = a * b;' => 'l = (" + tgtStr + ")a * b;'.", CWE197, Certainty::normal); } -void CheckType::longCastReturnError(const Token *tok, const ValueType* src, const ValueType* tgt) +void CheckTypeImpl::longCastReturnError(const Token *tok, const ValueType* src, const ValueType* tgt) { std::string srcStr = src ? src->str() : "int"; makeBaseTypeString(srcStr); @@ -440,7 +444,7 @@ void CheckType::longCastReturnError(const Token *tok, const ValueType* src, cons // Checking for float to integer overflow //--------------------------------------------------------------------------- -void CheckType::checkFloatToIntegerOverflow() +void CheckTypeImpl::checkFloatToIntegerOverflow() { logChecker("CheckType::checkFloatToIntegerOverflow"); @@ -472,7 +476,7 @@ void CheckType::checkFloatToIntegerOverflow() while (scope && scope->type != ScopeType::eLambda && scope->type != ScopeType::eFunction) scope = scope->nestedIn; if (scope && scope->type == ScopeType::eFunction && scope->function && scope->function->retDef) { - const ValueType &valueType = ValueType::parseDecl(scope->function->retDef, *mSettings); + const ValueType &valueType = ValueType::parseDecl(scope->function->retDef, mSettings); vtfloat = tok->astOperand1()->valueType(); checkFloatToIntegerOverflow(tok, &valueType, vtfloat, tok->astOperand1()->values()); } @@ -480,7 +484,7 @@ void CheckType::checkFloatToIntegerOverflow() } } -void CheckType::checkFloatToIntegerOverflow(const Token *tok, const ValueType *vtint, const ValueType *vtfloat, const std::list &floatValues) +void CheckTypeImpl::checkFloatToIntegerOverflow(const Token *tok, const ValueType *vtint, const ValueType *vtfloat, const std::list &floatValues) { // Conversion of float to integer? if (!vtint || !vtint->isIntegral()) @@ -491,24 +495,24 @@ void CheckType::checkFloatToIntegerOverflow(const Token *tok, const ValueType *v for (const ValueFlow::Value &f : floatValues) { if (f.valueType != ValueFlow::Value::ValueType::FLOAT) continue; - if (!mSettings->isEnabled(&f, false)) + if (!mSettings.isEnabled(&f, false)) continue; - if (f.floatValue >= std::exp2(mSettings->platform.long_long_bit)) + if (f.floatValue >= std::exp2(mSettings.platform.long_long_bit)) floatToIntegerOverflowError(tok, f); - else if ((-f.floatValue) > std::exp2(mSettings->platform.long_long_bit - 1)) + else if ((-f.floatValue) > std::exp2(mSettings.platform.long_long_bit - 1)) floatToIntegerOverflowError(tok, f); - else if (mSettings->platform.type != Platform::Type::Unspecified) { + else if (mSettings.platform.type != Platform::Type::Unspecified) { int bits = 0; if (vtint->type == ValueType::Type::CHAR) - bits = mSettings->platform.char_bit; + bits = mSettings.platform.char_bit; else if (vtint->type == ValueType::Type::SHORT) - bits = mSettings->platform.short_bit; + bits = mSettings.platform.short_bit; else if (vtint->type == ValueType::Type::INT) - bits = mSettings->platform.int_bit; + bits = mSettings.platform.int_bit; else if (vtint->type == ValueType::Type::LONG) - bits = mSettings->platform.long_bit; + bits = mSettings.platform.long_bit; else if (vtint->type == ValueType::Type::LONGLONG) - bits = mSettings->platform.long_long_bit; + bits = mSettings.platform.long_long_bit; else continue; if (bits < MathLib::bigint_bits && f.floatValue >= (1ULL << bits)) @@ -517,7 +521,7 @@ void CheckType::checkFloatToIntegerOverflow(const Token *tok, const ValueType *v } } -void CheckType::floatToIntegerOverflowError(const Token *tok, const ValueFlow::Value &value) +void CheckTypeImpl::floatToIntegerOverflowError(const Token *tok, const ValueFlow::Value &value) { std::ostringstream errmsg; errmsg << "Undefined behaviour: float (" << value.floatValue << ") to integer conversion overflow."; @@ -527,10 +531,10 @@ void CheckType::floatToIntegerOverflowError(const Token *tok, const ValueFlow::V errmsg.str(), CWE190, value.isInconclusive() ? Certainty::inconclusive : Certainty::normal); } -void CheckType::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) +void CheckType::runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) { // These are not "simplified" because casts can't be ignored - CheckType checkType(&tokenizer, &tokenizer.getSettings(), errorLogger); + CheckTypeImpl checkType(&tokenizer, tokenizer.getSettings(), errorLogger); checkType.checkTooBigBitwiseShift(); checkType.checkIntegerOverflow(); checkType.checkSignConversion(); @@ -538,9 +542,9 @@ void CheckType::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) checkType.checkFloatToIntegerOverflow(); } -void CheckType::getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const +void CheckType::getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const { - CheckType c(nullptr, settings, errorLogger); + CheckTypeImpl c(nullptr, settings, errorLogger); c.tooBigBitwiseShiftError(nullptr, 32, ValueFlow::Value(64)); c.tooBigSignedBitwiseShiftError(nullptr, 31, ValueFlow::Value(31)); c.integerOverflowError(nullptr, ValueFlow::Value(1LL<<32)); diff --git a/lib/checktype.h b/lib/checktype.h index d90e11f6fd1..83eedda5583 100644 --- a/lib/checktype.h +++ b/lib/checktype.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,6 +23,7 @@ //--------------------------------------------------------------------------- #include "check.h" +#include "checkimpl.h" #include "config.h" #include @@ -47,15 +48,30 @@ namespace ValueFlow class CPPCHECKLIB CheckType : public Check { public: /** @brief This constructor is used when registering the CheckClass */ - CheckType() : Check(myName()) {} + CheckType() : Check("Type") {} private: - /** @brief This constructor is used when running checks. */ - CheckType(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) - : Check(myName(), tokenizer, settings, errorLogger) {} - /** @brief Run checks against the normal token list */ - void runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) override; + void runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) override; + + void getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const override; + + std::string classInfo() const override { + return "Type checks\n" + "- bitwise shift by too many bits (only enabled when --platform is used)\n" + "- signed integer overflow (only enabled when --platform is used)\n" + "- dangerous sign conversion, when signed value can be negative\n" + "- possible loss of information when assigning int result to long variable\n" + "- possible loss of information when returning int result as long return value\n" + "- float conversion overflow\n"; + } +}; + +class CPPCHECKLIB CheckTypeImpl : public CheckImpl { +public: + /** @brief This constructor is used when running checks. */ + CheckTypeImpl(const Tokenizer *tokenizer, const Settings &settings, ErrorLogger &errorLogger) + : CheckImpl(tokenizer, settings, errorLogger) {} /** @brief %Check for bitwise shift with too big right operand */ void checkTooBigBitwiseShift(); @@ -81,22 +97,6 @@ class CPPCHECKLIB CheckType : public Check { void longCastAssignError(const Token *tok, const ValueType* src = nullptr, const ValueType* tgt = nullptr); void longCastReturnError(const Token *tok, const ValueType* src = nullptr, const ValueType* tgt = nullptr); void floatToIntegerOverflowError(const Token *tok, const ValueFlow::Value &value); - - void getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const override; - - static std::string myName() { - return "Type"; - } - - std::string classInfo() const override { - return "Type checks\n" - "- bitwise shift by too many bits (only enabled when --platform is used)\n" - "- signed integer overflow (only enabled when --platform is used)\n" - "- dangerous sign conversion, when signed value can be negative\n" - "- possible loss of information when assigning int result to long variable\n" - "- possible loss of information when returning int result as long return value\n" - "- float conversion overflow\n"; - } }; /// @} //--------------------------------------------------------------------------- diff --git a/lib/checkuninitvar.cpp b/lib/checkuninitvar.cpp index ecaed652c5e..37d7a1ec0cd 100644 --- a/lib/checkuninitvar.cpp +++ b/lib/checkuninitvar.cpp @@ -21,6 +21,7 @@ #include "checkuninitvar.h" #include "astutils.h" +#include "checkimpl.h" #include "ctu.h" #include "errorlogger.h" #include "library.h" @@ -50,11 +51,6 @@ // CWE ids used: static const CWE CWE_USE_OF_UNINITIALIZED_VARIABLE(457U); -// Register this check class (by creating a static instance of it) -namespace { - CheckUninitVar instance; -} - //--------------------------------------------------------------------------- // get ast parent, skip possible address-of and casts @@ -102,7 +98,7 @@ static std::map getVariableValues(const Token* tok) { return ret; } -bool CheckUninitVar::diag(const Token* tok) +bool CheckUninitVarImpl::diag(const Token* tok) { if (!tok) return true; @@ -111,7 +107,7 @@ bool CheckUninitVar::diag(const Token* tok) return !mUninitDiags.insert(tok).second; } -void CheckUninitVar::check() +void CheckUninitVarImpl::check() { logChecker("CheckUninitVar::check"); @@ -131,7 +127,7 @@ void CheckUninitVar::check() } } -void CheckUninitVar::checkScope(const Scope* scope, const std::set &arrayTypeDefs) +void CheckUninitVarImpl::checkScope(const Scope* scope, const std::set &arrayTypeDefs) { for (const Variable &var : scope->varlist) { if ((mTokenizer->isCPP() && var.type() && !var.isPointer() && var.type()->needInitialization != Type::NeedInitialization::True) || @@ -216,10 +212,10 @@ void CheckUninitVar::checkScope(const Scope* scope, const std::set for (const Token *tok = scope->bodyStart; tok != scope->bodyEnd; tok = tok->next()) { if (!Token::Match(tok, "[;{}] %varid% =", arg.declarationId())) continue; - const Token *allocFuncCallToken = findAllocFuncCallToken(tok->tokAt(2)->astOperand2(), mSettings->library); + const Token *allocFuncCallToken = findAllocFuncCallToken(tok->tokAt(2)->astOperand2(), mSettings.library); if (!allocFuncCallToken) continue; - const Library::AllocFunc *allocFunc = mSettings->library.getAllocFuncInfo(allocFuncCallToken); + const Library::AllocFunc *allocFunc = mSettings.library.getAllocFuncInfo(allocFuncCallToken); if (!allocFunc || allocFunc->initData) continue; @@ -236,7 +232,7 @@ void CheckUninitVar::checkScope(const Scope* scope, const std::set } } -void CheckUninitVar::checkStruct(const Token *tok, const Variable &structvar) +void CheckUninitVarImpl::checkStruct(const Token *tok, const Variable &structvar) { const Token *typeToken = structvar.typeStartToken(); while (Token::Match(typeToken, "%name% ::")) @@ -383,7 +379,7 @@ static bool isVariableUsed(const Token *tok, const Variable& var) return isVariableUsed(tok->astOperand1(),var) || isVariableUsed(tok->astOperand2(),var); if (tok->varId() != var.declarationId()) return false; - if (!var.isArray()) + if (!var.isArray() && !var.isPointer()) return true; const Token *parent = tok->astParent(); @@ -397,10 +393,10 @@ static bool isVariableUsed(const Token *tok, const Variable& var) return !parent2 || parent2->isConstOp() || (parent2->str() == "=" && parent2->astOperand2() == parent); } -bool CheckUninitVar::checkScopeForVariable(const Token *tok, const Variable& var, bool * const possibleInit, bool * const noreturn, Alloc* const alloc, const std::string &membervar, std::map& variableValue) +bool CheckUninitVarImpl::checkScopeForVariable(const Token *tok, const Variable& var, bool * const possibleInit, bool * const noreturn, Alloc* const alloc, const std::string &membervar, std::map& variableValue) { const bool suppressErrors(possibleInit && *possibleInit); // Assume that this is a variable declaration, rather than a fundef - const bool printDebug = mSettings->debugwarnings; + const bool printDebug = mSettings.debugwarnings; if (possibleInit) *possibleInit = false; @@ -767,7 +763,7 @@ bool CheckUninitVar::checkScopeForVariable(const Token *tok, const Variable& var while (rhs && rhs->isCast()) rhs = rhs->astOperand2() ? rhs->astOperand2() : rhs->astOperand1(); if (rhs && Token::Match(rhs->previous(), "%name% (")) { - const Library::AllocFunc *allocFunc = mSettings->library.getAllocFuncInfo(rhs->astOperand1()); + const Library::AllocFunc *allocFunc = mSettings.library.getAllocFuncInfo(rhs->astOperand1()); if (allocFunc && !allocFunc->initData) { *alloc = NO_CTOR_CALL; continue; @@ -784,12 +780,16 @@ bool CheckUninitVar::checkScopeForVariable(const Token *tok, const Variable& var // standard or enum type: check if new initializes the allocated memory if (var.typeStartToken()->isStandardType() || var.typeStartToken()->isEnumType()) { // scalar new with initialization - if (Token::Match(tok->next(), "= new %type% (")) - return true; - // array new - if (Token::Match(tok->next(), "= new %type% [") && Token::simpleMatch(tok->linkAt(4), "] (")) - return true; + if (Token::Match(tok->next(), "= new ::|%type%")) { + const Token* initTok = tok->tokAt(4); + while (Token::Match(initTok, "::|%type%")) + initTok = initTok->next(); + if (Token::simpleMatch(initTok, "[")) + initTok = initTok->link()->next(); + if (Token::Match(initTok, "[({]")) + return true; + } } continue; @@ -834,7 +834,7 @@ bool CheckUninitVar::checkScopeForVariable(const Token *tok, const Variable& var } } } - if (Token::simpleMatch(parent->astParent(), "=") && astIsLHS(parent)) { + if (Token::Match(parent->astParent(), "%assign%") && astIsLHS(parent)) { const Token *eq = parent->astParent(); if (const Token *errorToken = checkExpr(eq->astOperand2(), var, *alloc, number_of_if==0)) { if (!suppressErrors) @@ -853,7 +853,7 @@ bool CheckUninitVar::checkScopeForVariable(const Token *tok, const Variable& var return false; } -const Token* CheckUninitVar::checkExpr(const Token* tok, const Variable& var, const Alloc alloc, bool known, bool* bailout) const +const Token* CheckUninitVarImpl::checkExpr(const Token* tok, const Variable& var, const Alloc alloc, bool known, bool* bailout) const { if (!tok) return nullptr; @@ -882,7 +882,7 @@ const Token* CheckUninitVar::checkExpr(const Token* tok, const Variable& var, co return nullptr; } -bool CheckUninitVar::checkIfForWhileHead(const Token *startparentheses, const Variable& var, bool suppressErrors, bool isuninit, Alloc alloc, const std::string &membervar) +bool CheckUninitVarImpl::checkIfForWhileHead(const Token *startparentheses, const Variable& var, bool suppressErrors, bool isuninit, Alloc alloc, const std::string &membervar) { const Token * const endpar = startparentheses->link(); if (Token::Match(startparentheses, "( ! %name% %oror%") && startparentheses->tokAt(2)->getValue(0)) @@ -919,7 +919,7 @@ bool CheckUninitVar::checkIfForWhileHead(const Token *startparentheses, const Va } /** recursively check loop, return error token */ -const Token* CheckUninitVar::checkLoopBodyRecursive(const Token *start, const Variable& var, const Alloc alloc, const std::string &membervar, bool &bailout, bool &alwaysReturns) const +const Token* CheckUninitVarImpl::checkLoopBodyRecursive(const Token *start, const Variable& var, const Alloc alloc, const std::string &membervar, bool &bailout, bool &alwaysReturns) const { assert(start->str() == "{"); @@ -1087,7 +1087,7 @@ const Token* CheckUninitVar::checkLoopBodyRecursive(const Token *start, const Va return errorToken; } -bool CheckUninitVar::checkLoopBody(const Token *tok, const Variable& var, const Alloc alloc, const std::string &membervar, const bool suppressErrors) +bool CheckUninitVarImpl::checkLoopBody(const Token *tok, const Variable& var, const Alloc alloc, const std::string &membervar, const bool suppressErrors) { bool bailout = false; bool alwaysReturns = false; @@ -1104,7 +1104,7 @@ bool CheckUninitVar::checkLoopBody(const Token *tok, const Variable& var, const return bailout || alwaysReturns; } -void CheckUninitVar::checkRhs(const Token *tok, const Variable &var, Alloc alloc, nonneg int number_of_if, const std::string &membervar) +void CheckUninitVarImpl::checkRhs(const Token *tok, const Variable &var, Alloc alloc, nonneg int number_of_if, const std::string &membervar) { bool rhs = false; int indent = 0; @@ -1151,7 +1151,7 @@ static bool astIsRhs(const Token *tok) return tok && tok->astParent() && tok == tok->astParent()->astOperand2(); } -const Token* CheckUninitVar::isVariableUsage(const Token *vartok, const Library& library, bool pointer, Alloc alloc, int indirect) +const Token* CheckUninitVarImpl::isVariableUsage(const Token *vartok, const Library& library, bool pointer, Alloc alloc, int indirect) { const bool cpp = vartok->isCpp(); const Token *valueExpr = vartok; // non-dereferenced , no address of value as variable @@ -1258,8 +1258,6 @@ const Token* CheckUninitVar::isVariableUsage(const Token *vartok, const Library& const Token *parent = valueExpr->astParent(); while (Token::simpleMatch(parent, ",")) parent = parent->astParent(); - if (Token::simpleMatch(parent, "{")) - return valueExpr; const int use = isFunctionParUsage(valueExpr, library, pointer, alloc, indirect); return (use>0) ? valueExpr : nullptr; } @@ -1297,6 +1295,19 @@ const Token* CheckUninitVar::isVariableUsage(const Token *vartok, const Library& } if (alloc != NO_ALLOC && astIsRhs(valueExpr)) return nullptr; + } else if (tok->astParent() && (tok->astParent()->isAssignmentOp() || tok->astParent()->isIncDecOp())) { + // NO_ALLOC -> no matter what we read the uninitialized memory. + // pointer/array -> safe, as long as we don't dereference + bool isPtr = pointer; + bool isArr = alloc == ARRAY; + // "pointer" and "alloc" get set for non-ptr non-array var + if (vartok && vartok->variable()) { + isPtr = vartok->variable()->isPointer(); + isArr = vartok->variable()->isArray(); + } + if ((alloc != NO_ALLOC) && ((isPtr || isArr) && !derefValue)) { + return nullptr; + } } } @@ -1355,9 +1366,9 @@ const Token* CheckUninitVar::isVariableUsage(const Token *vartok, const Library& return derefValue ? derefValue : valueExpr; } -const Token* CheckUninitVar::isVariableUsage(const Token *vartok, bool pointer, Alloc alloc, int indirect) const +const Token* CheckUninitVarImpl::isVariableUsage(const Token *vartok, bool pointer, Alloc alloc, int indirect) const { - return isVariableUsage(vartok, mSettings->library, pointer, alloc, indirect); + return isVariableUsage(vartok, mSettings.library, pointer, alloc, indirect); } /*** @@ -1366,7 +1377,7 @@ const Token* CheckUninitVar::isVariableUsage(const Token *vartok, bool pointer, * is passed "by reference" then it is not necessarily "used". * @return -1 => unknown 0 => not used 1 => used */ -int CheckUninitVar::isFunctionParUsage(const Token *vartok, const Library& library, bool pointer, Alloc alloc, int indirect) +int CheckUninitVarImpl::isFunctionParUsage(const Token *vartok, const Library& library, bool pointer, Alloc alloc, int indirect) { bool unknown = false; const Token *parent = getAstParentSkipPossibleCastAndAddressOf(vartok, &unknown); @@ -1422,6 +1433,8 @@ int CheckUninitVar::isFunctionParUsage(const Token *vartok, const Library& libra const bool isnullbad = library.isnullargbad(start->previous(), argumentNumber + 1); if (indirect == 0 && pointer && !address && isnullbad && alloc == NO_ALLOC) return 1; + if (vartok->varId() == 0 && vartok->valueType()) + indirect = vartok->valueType()->pointer; bool hasIndirect = false; const bool isuninitbad = library.isuninitargbad(start->previous(), argumentNumber + 1, indirect, &hasIndirect); if (alloc != NO_ALLOC) @@ -1434,12 +1447,12 @@ int CheckUninitVar::isFunctionParUsage(const Token *vartok, const Library& libra return -1; } -int CheckUninitVar::isFunctionParUsage(const Token *vartok, bool pointer, Alloc alloc, int indirect) const +int CheckUninitVarImpl::isFunctionParUsage(const Token *vartok, bool pointer, Alloc alloc, int indirect) const { - return CheckUninitVar::isFunctionParUsage(vartok, mSettings->library, pointer, alloc, indirect); + return isFunctionParUsage(vartok, mSettings.library, pointer, alloc, indirect); } -bool CheckUninitVar::isMemberVariableAssignment(const Token *tok, const std::string &membervar) const +bool CheckUninitVarImpl::isMemberVariableAssignment(const Token *tok, const std::string &membervar) const { if (Token::Match(tok, "%name% . %name%") && tok->strAt(2) == membervar) { if (Token::Match(tok->tokAt(3), "[=.[]")) @@ -1483,9 +1496,9 @@ bool CheckUninitVar::isMemberVariableAssignment(const Token *tok, const std::str // check how function handle uninitialized data arguments.. const Function *function = ftok->function(); - if (!function && mSettings) { + if (!function) { // Function definition not seen, check if direction is specified in the library configuration - const Library::ArgumentChecks::Direction argDirection = mSettings->library.getArgDirection(ftok, 1 + argumentNumber); + const Library::ArgumentChecks::Direction argDirection = mSettings.library.getArgDirection(ftok, 1 + argumentNumber); if (argDirection == Library::ArgumentChecks::Direction::DIR_IN) return false; if (argDirection == Library::ArgumentChecks::Direction::DIR_OUT) @@ -1508,7 +1521,7 @@ bool CheckUninitVar::isMemberVariableAssignment(const Token *tok, const std::str return false; } -bool CheckUninitVar::isMemberVariableUsage(const Token *tok, bool isPointer, Alloc alloc, const std::string &membervar) const +bool CheckUninitVarImpl::isMemberVariableUsage(const Token *tok, bool isPointer, Alloc alloc, const std::string &membervar) const { if (Token::Match(tok->previous(), "[(,] %name% . %name% [,)]") && tok->strAt(2) == membervar) { @@ -1550,14 +1563,14 @@ bool CheckUninitVar::isMemberVariableUsage(const Token *tok, bool isPointer, All return false; } -void CheckUninitVar::uninitdataError(const Token *tok, const std::string &varname) +void CheckUninitVarImpl::uninitdataError(const Token *tok, const std::string &varname) { reportError(tok, Severity::error, "uninitdata", "$symbol:" + varname + "\nMemory is allocated but not initialized: $symbol", CWE_USE_OF_UNINITIALIZED_VARIABLE, Certainty::normal); } -void CheckUninitVar::uninitvarError(const Token *tok, const std::string &varname, ErrorPath errorPath) +void CheckUninitVarImpl::uninitvarError(const Token *tok, const std::string &varname, ErrorPath errorPath) { - if (diag(tok)) + if (tok && diag(tok)) return; errorPath.emplace_back(tok, ""); reportError(std::move(errorPath), @@ -1568,11 +1581,11 @@ void CheckUninitVar::uninitvarError(const Token *tok, const std::string &varname Certainty::normal); } -void CheckUninitVar::uninitvarError(const Token* tok, const ValueFlow::Value& v) +void CheckUninitVarImpl::uninitvarError(const Token* tok, const ValueFlow::Value& v) { - if (!mSettings->isEnabled(&v)) + if (!mSettings.isEnabled(&v)) return; - if (diag(tok)) + if (tok && diag(tok)) return; const Token* ltok = tok; if (tok && Token::simpleMatch(tok->astParent(), ".") && astIsRHS(tok)) @@ -1605,7 +1618,7 @@ void CheckUninitVar::uninitvarError(const Token* tok, const ValueFlow::Value& v) certainty); } -void CheckUninitVar::uninitStructMemberError(const Token *tok, const std::string &membername) +void CheckUninitVarImpl::uninitStructMemberError(const Token *tok, const std::string &membername) { reportError(tok, Severity::error, @@ -1613,7 +1626,7 @@ void CheckUninitVar::uninitStructMemberError(const Token *tok, const std::string "$symbol:" + membername + "\nUninitialized struct member: $symbol", CWE_USE_OF_UNINITIALIZED_VARIABLE, Certainty::normal); } -void CheckUninitVar::valueFlowUninit() +void CheckUninitVarImpl::valueFlowUninit() { logChecker("CheckUninitVar::valueFlowUninit"); @@ -1660,28 +1673,28 @@ void CheckUninitVar::valueFlowUninit() if (isarray && tok->variable()->isMember()) continue; // Todo: this is a bailout if (isarray && tok->variable()->isStlType() && Token::simpleMatch(tok->astParent(), ".")) { - const auto yield = astContainerYield(tok, mSettings->library); + const auto yield = astContainerYield(tok, mSettings.library); if (yield != Library::Container::Yield::AT_INDEX && yield != Library::Container::Yield::ITEM) continue; } - const bool deref = CheckNullPointer::isPointerDeRef(tok, unknown, *mSettings); + const bool deref = CheckNullPointerImpl::isPointerDeRef(tok, unknown, mSettings.library); uninitderef = deref && v->indirect == 0; const bool isleaf = isLeafDot(tok) || uninitderef; if (!isleaf && Token::Match(tok->astParent(), ". %name%") && (tok->astParent()->next()->variable() || tok->astParent()->next()->isEnumerator())) continue; } - const ExprUsage usage = getExprUsage(tok, v->indirect, *mSettings); + const ExprUsage usage = getExprUsage(tok, v->indirect, mSettings); if (usage == ExprUsage::NotUsed || usage == ExprUsage::Inconclusive) continue; if (!v->subexpressions.empty() && usage == ExprUsage::PassedByReference) continue; if (usage != ExprUsage::Used) { if (!(Token::Match(tok->astParent(), ". %name% (|[") && uninitderef) && - isVariableChanged(tok, v->indirect, *mSettings)) + isVariableChanged(tok, v->indirect, mSettings)) continue; bool inconclusive = false; - if (isVariableChangedByFunctionCall(tok, v->indirect, *mSettings, &inconclusive) || inconclusive) + if (isVariableChangedByFunctionCall(tok, v->indirect, mSettings.library, &inconclusive) || inconclusive) continue; } uninitvarError(tok, *v); @@ -1697,7 +1710,7 @@ void CheckUninitVar::valueFlowUninit() static bool isVariableUsage(const Settings &settings, const Token *vartok, MathLib::bigint *value) { (void)value; - return CheckUninitVar::isVariableUsage(vartok, settings.library, true, CheckUninitVar::Alloc::ARRAY); + return !!CheckUninitVarImpl::isVariableUsage(vartok, settings.library, true, CheckUninitVarImpl::Alloc::ARRAY); } // a Clang-built executable will crash when using the anonymous MyFileInfo later on - so put it in a unique namespace for now @@ -1727,7 +1740,7 @@ static bool isVariableUsage(const Settings &settings, const Token *argtok, CTU:: return isVariableUsage(settings, argtok, &value->value); } -Check::FileInfo *CheckUninitVar::getFileInfo(const Tokenizer &tokenizer, const Settings &settings, const std::string& /*currentConfig*/) const +const Check::FileInfo *CheckUninitVar::getFileInfo(const Tokenizer &tokenizer, const Settings &settings, const std::string& /*currentConfig*/) const { const std::list &unsafeUsage = CTU::getUnsafeUsage(tokenizer, settings, ::isVariableUsage); if (unsafeUsage.empty()) @@ -1738,22 +1751,22 @@ Check::FileInfo *CheckUninitVar::getFileInfo(const Tokenizer &tokenizer, const S return fileInfo; } -Check::FileInfo * CheckUninitVar::loadFileInfoFromXml(const tinyxml2::XMLElement *xmlElement) const +const Check::FileInfo * CheckUninitVar::loadFileInfoFromXml(const tinyxml2::XMLElement *xmlElement, const std::string& file0) const { const std::list &unsafeUsage = CTU::loadUnsafeUsageListFromXml(xmlElement); if (unsafeUsage.empty()) return nullptr; - auto *fileInfo = new MyFileInfo; + auto *fileInfo = new MyFileInfo(file0); fileInfo->unsafeUsage = unsafeUsage; return fileInfo; } -bool CheckUninitVar::analyseWholeProgram(const CTU::FileInfo &ctu, const std::list &fileInfo, const Settings& settings, ErrorLogger &errorLogger) +bool CheckUninitVar::analyseWholeProgram(const CTU::FileInfo &ctu, const std::list &fileInfo, const Settings& settings, ErrorLogger &errorLogger) { (void)settings; - CheckUninitVar dummy(nullptr, &settings, &errorLogger); + CheckUninitVarImpl dummy(nullptr, settings, errorLogger); dummy. logChecker("CheckUninitVar::analyseWholeProgram"); @@ -1797,20 +1810,21 @@ bool CheckUninitVar::analyseWholeProgram(const CTU::FileInfo &ctu, const std::li return foundErrors; } -void CheckUninitVar::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) +void CheckUninitVar::runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) { - CheckUninitVar checkUninitVar(&tokenizer, &tokenizer.getSettings(), errorLogger); + CheckUninitVarImpl checkUninitVar(&tokenizer, tokenizer.getSettings(), errorLogger); checkUninitVar.valueFlowUninit(); checkUninitVar.check(); } -void CheckUninitVar::getErrorMessages(ErrorLogger* errorLogger, const Settings* settings) const +void CheckUninitVar::getErrorMessages(ErrorLogger& errorLogger, const Settings& settings) const { - CheckUninitVar c(nullptr, settings, errorLogger); + CheckUninitVarImpl c(nullptr, settings, errorLogger); ValueFlow::Value v{}; - c.uninitvarError(nullptr, v); // TODO: does not produce any output + c.uninitvarError(nullptr, v); + c.uninitvarError(nullptr, "varname", ErrorPath{}); c.uninitdataError(nullptr, "varname"); c.uninitStructMemberError(nullptr, "a.b"); } diff --git a/lib/checkuninitvar.h b/lib/checkuninitvar.h index b79e9c22be0..a9156adfe36 100644 --- a/lib/checkuninitvar.h +++ b/lib/checkuninitvar.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,6 +23,7 @@ //--------------------------------------------------------------------------- #include "check.h" +#include "checkimpl.h" #include "config.h" #include "mathlib.h" #include "errortypes.h" @@ -57,24 +58,38 @@ struct VariableValue { /** @brief Checking for uninitialized variables */ class CPPCHECKLIB CheckUninitVar : public Check { - friend class TestUninitVar; - public: /** @brief This constructor is used when registering the CheckUninitVar */ - CheckUninitVar() : Check(myName()) {} + CheckUninitVar() : Check("Uninitialized variables") {} - enum Alloc : std::uint8_t { NO_ALLOC, NO_CTOR_CALL, CTOR_CALL, ARRAY }; +private: + /** @brief Run checks against the normal token list */ + void runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) override; - static const Token *isVariableUsage(const Token *vartok, const Library &library, bool pointer, Alloc alloc, int indirect = 0); - const Token *isVariableUsage(const Token *vartok, bool pointer, Alloc alloc, int indirect = 0) const; + /** @brief Parse current TU and extract file info */ + const Check::FileInfo *getFileInfo(const Tokenizer &tokenizer, const Settings &settings, const std::string& /*currentConfig*/) const override; -private: + const Check::FileInfo * loadFileInfoFromXml(const tinyxml2::XMLElement *xmlElement, const std::string& file0) const override; + + /** @brief Analyse all file infos for all TU */ + bool analyseWholeProgram(const CTU::FileInfo &ctu, const std::list &fileInfo, const Settings& settings, ErrorLogger &errorLogger) override; + + void getErrorMessages(ErrorLogger& errorLogger, const Settings& settings) const override; + + std::string classInfo() const override { + return "Uninitialized variables\n" + "- using uninitialized local variables\n" + "- using allocated data before it has been initialized\n"; + } +}; + +class CPPCHECKLIB CheckUninitVarImpl : public CheckImpl { +public: /** @brief This constructor is used when running checks. */ - CheckUninitVar(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) - : Check(myName(), tokenizer, settings, errorLogger) {} + CheckUninitVarImpl(const Tokenizer *tokenizer, const Settings &settings, ErrorLogger &errorLogger) + : CheckImpl(tokenizer, settings, errorLogger) {} - /** @brief Run checks against the normal token list */ - void runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) override; + enum Alloc : std::uint8_t { NO_ALLOC, NO_CTOR_CALL, CTOR_CALL, ARRAY }; bool diag(const Token* tok); /** Check for uninitialized variables */ @@ -92,17 +107,12 @@ class CPPCHECKLIB CheckUninitVar : public Check { bool isMemberVariableAssignment(const Token *tok, const std::string &membervar) const; bool isMemberVariableUsage(const Token *tok, bool isPointer, Alloc alloc, const std::string &membervar) const; + static const Token *isVariableUsage(const Token *vartok, const Library &library, bool pointer, Alloc alloc, int indirect = 0); + const Token *isVariableUsage(const Token *vartok, bool pointer, Alloc alloc, int indirect = 0) const; + /** ValueFlow-based checking for uninitialized variables */ void valueFlowUninit(); - /** @brief Parse current TU and extract file info */ - Check::FileInfo *getFileInfo(const Tokenizer &tokenizer, const Settings &settings, const std::string& /*currentConfig*/) const override; - - Check::FileInfo * loadFileInfoFromXml(const tinyxml2::XMLElement *xmlElement) const override; - - /** @brief Analyse all file infos for all TU */ - bool analyseWholeProgram(const CTU::FileInfo &ctu, const std::list &fileInfo, const Settings& settings, ErrorLogger &errorLogger) override; - void uninitvarError(const Token* tok, const ValueFlow::Value& v); void uninitdataError(const Token *tok, const std::string &varname); void uninitvarError(const Token *tok, const std::string &varname, ErrorPath errorPath); @@ -118,18 +128,6 @@ class CPPCHECKLIB CheckUninitVar : public Check { void uninitStructMemberError(const Token *tok, const std::string &membername); std::set mUninitDiags; - - void getErrorMessages(ErrorLogger* errorLogger, const Settings* settings) const override; - - static std::string myName() { - return "Uninitialized variables"; - } - - std::string classInfo() const override { - return "Uninitialized variables\n" - "- using uninitialized local variables\n" - "- using allocated data before it has been initialized\n"; - } }; /// @} //--------------------------------------------------------------------------- diff --git a/lib/checkunusedfunctions.cpp b/lib/checkunusedfunctions.cpp index fa086cca1f1..4177a13a013 100644 --- a/lib/checkunusedfunctions.cpp +++ b/lib/checkunusedfunctions.cpp @@ -65,11 +65,11 @@ static bool isRecursiveCall(const Token* ftok) return ftok->function() && ftok->function() == Scope::nestedInFunction(ftok->scope()); } -void CheckUnusedFunctions::parseTokens(const Tokenizer &tokenizer, const Settings &settings) +void CheckUnusedFunctions::parseTokens(const Tokenizer &tokenizer, const Library &library) { const char * const FileName = tokenizer.list.getFiles().front().c_str(); - const bool doMarkup = settings.library.markupFile(FileName); + const bool doMarkup = library.markupFile(FileName); // Function declarations.. if (!doMarkup) { @@ -139,8 +139,8 @@ void CheckUnusedFunctions::parseTokens(const Tokenizer &tokenizer, const Setting lambdaEndToken = findLambdaEndToken(tok); // parsing of library code to find called functions - if (settings.library.isexecutableblock(FileName, tok->str())) { - const Token * markupVarToken = tok->tokAt(settings.library.blockstartoffset(FileName)); + if (library.isexecutableblock(FileName, tok->str())) { + const Token * markupVarToken = tok->tokAt(library.blockstartoffset(FileName)); // not found if (!markupVarToken) continue; @@ -148,12 +148,12 @@ void CheckUnusedFunctions::parseTokens(const Tokenizer &tokenizer, const Setting bool start = true; // find all function calls in library code (starts with '(', not if or while etc) while ((scope || start) && markupVarToken) { - if (markupVarToken->str() == settings.library.blockstart(FileName)) { + if (markupVarToken->str() == library.blockstart(FileName)) { scope++; start = false; - } else if (markupVarToken->str() == settings.library.blockend(FileName)) + } else if (markupVarToken->str() == library.blockend(FileName)) scope--; - else if (!settings.library.iskeyword(FileName, markupVarToken->str())) { + else if (!library.iskeyword(FileName, markupVarToken->str())) { mFunctionCalls.insert(markupVarToken->str()); if (mFunctions.find(markupVarToken->str()) != mFunctions.end()) mFunctions[markupVarToken->str()].usedOtherFile = true; @@ -171,10 +171,10 @@ void CheckUnusedFunctions::parseTokens(const Tokenizer &tokenizer, const Setting } if (!doMarkup // only check source files - && settings.library.isexporter(tok->str()) && tok->next() != nullptr) { + && library.isexporter(tok->str()) && tok->next() != nullptr) { const Token * propToken = tok->next(); while (propToken && propToken->str() != ")") { - if (settings.library.isexportedprefix(tok->str(), propToken->str())) { + if (library.isexportedprefix(tok->str(), propToken->str())) { const Token* nextPropToken = propToken->next(); const std::string& value = nextPropToken->str(); if (mFunctions.find(value) != mFunctions.end()) { @@ -182,7 +182,7 @@ void CheckUnusedFunctions::parseTokens(const Tokenizer &tokenizer, const Setting } mFunctionCalls.insert(value); } - if (settings.library.isexportedsuffix(tok->str(), propToken->str())) { + if (library.isexportedsuffix(tok->str(), propToken->str())) { const Token* prevPropToken = propToken->previous(); const std::string& value = prevPropToken->str(); if (value != ")" && mFunctions.find(value) != mFunctions.end()) { @@ -194,7 +194,7 @@ void CheckUnusedFunctions::parseTokens(const Tokenizer &tokenizer, const Setting } } - if (doMarkup && settings.library.isimporter(FileName, tok->str()) && tok->next()) { + if (doMarkup && library.isimporter(FileName, tok->str()) && tok->next()) { const Token * propToken = tok->next(); if (propToken->next()) { propToken = propToken->next(); @@ -210,8 +210,8 @@ void CheckUnusedFunctions::parseTokens(const Tokenizer &tokenizer, const Setting } } - if (settings.library.isreflection(tok->str())) { - const int argIndex = settings.library.reflectionArgument(tok->str()); + if (library.isreflection(tok->str())) { + const int argIndex = library.reflectionArgument(tok->str()); if (argIndex >= 0) { const Token * funcToken = tok->next(); int index = 0; @@ -366,7 +366,7 @@ void CheckUnusedFunctions::staticFunctionError(ErrorLogger& errorLogger, errorLogger.reportErr(errmsg); \ } while (false) -bool CheckUnusedFunctions::check(const Settings& settings, ErrorLogger& errorLogger) const +bool CheckUnusedFunctions::check(const Library& library, ErrorLogger& errorLogger) const { logChecker("CheckUnusedFunctions::check"); // unusedFunction @@ -379,7 +379,7 @@ bool CheckUnusedFunctions::check(const Settings& settings, ErrorLogger& errorLog const FunctionUsage &func = it->second; if (func.usedOtherFile || func.filename.empty()) continue; - if (settings.library.isentrypoint(it->first)) + if (library.isentrypoint(it->first)) continue; if (!func.usedSameFile) { if (isOperatorFunction(it->first)) @@ -505,9 +505,9 @@ void CheckUnusedFunctions::analyseWholeProgram(const Settings &settings, ErrorLo } } -void CheckUnusedFunctions::updateFunctionData(const CheckUnusedFunctions& check) +void CheckUnusedFunctions::updateFunctionData(const CheckUnusedFunctions& checkUnusedFunctions) { - for (const auto& entry : check.mFunctions) + for (const auto& entry : checkUnusedFunctions.mFunctions) { FunctionUsage &usage = mFunctions[entry.first]; if (!usage.lineNumber) { @@ -521,6 +521,6 @@ void CheckUnusedFunctions::updateFunctionData(const CheckUnusedFunctions& check) usage.usedOtherFile |= entry.second.usedOtherFile; usage.usedSameFile |= entry.second.usedSameFile; } - mFunctionDecl.insert(mFunctionDecl.cend(), check.mFunctionDecl.cbegin(), check.mFunctionDecl.cend()); - mFunctionCalls.insert(check.mFunctionCalls.cbegin(), check.mFunctionCalls.cend()); + mFunctionDecl.insert(mFunctionDecl.cend(), checkUnusedFunctions.mFunctionDecl.cbegin(), checkUnusedFunctions.mFunctionDecl.cend()); + mFunctionCalls.insert(checkUnusedFunctions.mFunctionCalls.cbegin(), checkUnusedFunctions.mFunctionCalls.cend()); } diff --git a/lib/checkunusedfunctions.h b/lib/checkunusedfunctions.h index 6f10e3446eb..1b560cfb7cb 100644 --- a/lib/checkunusedfunctions.h +++ b/lib/checkunusedfunctions.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -33,6 +33,7 @@ class ErrorLogger; class Function; class Settings; class Tokenizer; +class Library; /** @brief Check for functions never called */ /// @{ @@ -44,7 +45,7 @@ class CPPCHECKLIB CheckUnusedFunctions { // Parse current tokens and determine.. // * Check what functions are used // * What functions are declared - void parseTokens(const Tokenizer &tokenizer, const Settings &settings); + void parseTokens(const Tokenizer &tokenizer, const Library &library); std::string analyzerInfo(const Tokenizer &tokenizer) const; @@ -56,9 +57,9 @@ class CPPCHECKLIB CheckUnusedFunctions { } // Return true if an error is reported. - bool check(const Settings& settings, ErrorLogger& errorLogger) const; + bool check(const Library& library, ErrorLogger& errorLogger) const; - void updateFunctionData(const CheckUnusedFunctions& check); + void updateFunctionData(const CheckUnusedFunctions& checkUnusedFunctions); private: static void unusedFunctionError(ErrorLogger& errorLogger, diff --git a/lib/checkunusedvar.cpp b/lib/checkunusedvar.cpp index e4ea2625624..c5e914ecdec 100644 --- a/lib/checkunusedvar.cpp +++ b/lib/checkunusedvar.cpp @@ -40,11 +40,6 @@ #include //--------------------------------------------------------------------------- -// Register this check class (by creating a static instance of it) -namespace { - CheckUnusedVar instance; -} - static const CWE CWE563(563U); // Assignment to Variable without Use ('Unused Variable') static const CWE CWE665(665U); // Improper Initialization @@ -254,7 +249,7 @@ void Variables::clearAliases(nonneg int varid) void Variables::eraseAliases(nonneg int varid) { - VariableUsage *usage = find(varid); + const VariableUsage *usage = find(varid); if (usage) { for (auto aliases = usage->_aliases.cbegin(); aliases != usage->_aliases.cend(); ++aliases) @@ -334,7 +329,7 @@ void Variables::write(nonneg int varid, const Token* tok) void Variables::writeAliases(nonneg int varid, const Token* tok) { - VariableUsage *usage = find(varid); + const VariableUsage *usage = find(varid); if (usage) { for (auto aliases = usage->_aliases.cbegin(); aliases != usage->_aliases.cend(); ++aliases) { @@ -565,7 +560,7 @@ static const Token* doAssignment(Variables &variables, const Token *tok, bool de variables.readAll(varid2, tok); } } - } else if (var1->mType == Variables::reference) { + } else if (var1->mType == Variables::reference || var1->mType == Variables::referenceArray) { variables.alias(varid1, varid2, true); } else if (var1->mType == Variables::standard && addressOf) { variables.alias(varid1, varid2, true); @@ -704,7 +699,7 @@ static void useFunctionArgs(const Token *tok, Variables& variables) //--------------------------------------------------------------------------- // Usage of function variables //--------------------------------------------------------------------------- -void CheckUnusedVar::checkFunctionVariableUsage_iterateScopes(const Scope* const scope, Variables& variables) const +void CheckUnusedVarImpl::checkFunctionVariableUsage_iterateScopes(const Scope* const scope, Variables& variables) const { // Find declarations if the scope is executable.. if (scope->isExecutable()) { @@ -731,13 +726,15 @@ void CheckUnusedVar::checkFunctionVariableUsage_iterateScopes(const Scope* const i->typeEndToken()->isStandardType() || i->isStlType() || mTokenizer->getSymbolDatabase()->isRecordTypeWithoutSideEffects(i->type()) || - mSettings->library.detectContainer(i->typeStartToken()) || - mSettings->library.getTypeCheck("unusedvar", i->typeStartToken()->str()) == Library::TypeCheck::check) + mSettings.library.detectContainer(i->typeStartToken()) || + mSettings.library.getTypeCheck("unusedvar", i->typeStartToken()->str()) == Library::TypeCheck::check) type = Variables::standard; if (type == Variables::none || isPartOfClassStructUnion(i->typeStartToken())) continue; const Token* defValTok = i->nameToken()->next(); + if (Token::Match(i->nameToken()->previous(), "& %var% )")) + defValTok = defValTok->next(); while (defValTok && defValTok->str() == "[") defValTok = defValTok->link()->next(); if (Token::simpleMatch(defValTok, ") (")) @@ -855,7 +852,7 @@ void CheckUnusedVar::checkFunctionVariableUsage_iterateScopes(const Scope* const } } // Freeing memory (not considered "using" the pointer if it was also allocated in this function) - if ((Token::Match(tok, "%name% ( %var% )") && mSettings->library.getDeallocFuncInfo(tok)) || + if ((Token::Match(tok, "%name% ( %var% )") && mSettings.library.getDeallocFuncInfo(tok)) || (tok->isCpp() && (Token::Match(tok, "delete %var% ;") || Token::Match(tok, "delete [ ] %var% ;")))) { nonneg int varid = 0; if (tok->str() != "delete") { @@ -961,10 +958,10 @@ void CheckUnusedVar::checkFunctionVariableUsage_iterateScopes(const Scope* const // Consider allocating memory separately because allocating/freeing alone does not constitute using the variable else if (var && var->mType == Variables::pointer && Token::Match(start, "%name% =") && - findAllocFuncCallToken(start->next()->astOperand2(), mSettings->library)) { + findAllocFuncCallToken(start->next()->astOperand2(), mSettings.library)) { - const Token *allocFuncCallToken = findAllocFuncCallToken(start->next()->astOperand2(), mSettings->library); - const Library::AllocFunc *allocFunc = mSettings->library.getAllocFuncInfo(allocFuncCallToken); + const Token *allocFuncCallToken = findAllocFuncCallToken(start->next()->astOperand2(), mSettings.library); + const Library::AllocFunc *allocFunc = mSettings.library.getAllocFuncInfo(allocFuncCallToken); bool allocateMemory = !allocFunc || Library::ismemory(allocFunc->groupId); @@ -1012,7 +1009,7 @@ void CheckUnusedVar::checkFunctionVariableUsage_iterateScopes(const Scope* const const Token * const equal = skipBracketsAndMembers(tok->next()); // checked for chained assignments - if (tok != start && equal && equal->str() == "=") { + if (tok != start && (Token::simpleMatch(equal, "=") || (start->astParent() && Token::simpleMatch(start->astParent()->astParent(), "=")))) { const nonneg int varId = tok->varId(); const Variables::VariableUsage * const var = variables.find(varId); @@ -1046,7 +1043,7 @@ void CheckUnusedVar::checkFunctionVariableUsage_iterateScopes(const Scope* const // Consider allocating memory separately because allocating/freeing alone does not constitute using the variable if (var->mType == Variables::pointer && ((tok->isCpp() && Token::simpleMatch(skipBrackets(tok->next()), "= new")) || - (Token::Match(skipBrackets(tok->next()), "= %name% (") && mSettings->library.getAllocFuncInfo(tok->tokAt(2))))) { + (Token::Match(skipBrackets(tok->next()), "= %name% (") && mSettings.library.getAllocFuncInfo(tok->tokAt(2))))) { variables.allocateMemory(varid, tok); } else if (var->mType == Variables::pointer || var->mType == Variables::reference) { variables.read(varid, tok); @@ -1158,10 +1155,6 @@ void CheckUnusedVar::checkFunctionVariableUsage_iterateScopes(const Scope* const variables.read(tok2->varId(), tok); } } - } else if (tok->variable() && tok->variable()->isClass() && tok->variable()->type() && - (tok->variable()->type()->needInitialization == Type::NeedInitialization::False) && - tok->strAt(1) == ";") { - variables.write(tok->varId(), tok); } } } @@ -1176,9 +1169,9 @@ static bool isReturnedByRef(const Variable* var, const Function* func) }); } -void CheckUnusedVar::checkFunctionVariableUsage() +void CheckUnusedVarImpl::checkFunctionVariableUsage() { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->checkLibrary && !mSettings->isPremiumEnabled("unusedVariable")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.checkLibrary && !mSettings.isPremiumEnabled("unusedVariable") && !mSettings.isPremiumEnabled("unreadVariable")) return; logChecker("CheckUnusedVar::checkFunctionVariableUsage"); // style @@ -1187,7 +1180,7 @@ void CheckUnusedVar::checkFunctionVariableUsage() const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); auto reportLibraryCfgError = [this](const Token* tok, const std::string& typeName) { - if (mSettings->checkLibrary) { + if (mSettings.checkLibrary) { reportError(tok, Severity::information, "checkLibraryCheckType", @@ -1295,6 +1288,11 @@ void CheckUnusedVar::checkFunctionVariableUsage() while (Token::Match(op1tok, ".|[|*")) op1tok = op1tok->astOperand1(); + // Bail out for overloaded indexing + if (op1tok && op1tok->valueType() && op1tok->valueType()->pointer == 0 && + op1tok->valueType()->type == ValueType::Type::RECORD && Token::simpleMatch(op1tok->astParent(), "[")) + continue; + // Assignment in macro => do not warn if (isAssignment && tok->isExpandedMacro() && op1tok && op1tok->isExpandedMacro()) continue; @@ -1334,7 +1332,7 @@ void CheckUnusedVar::checkFunctionVariableUsage() std::string typeName = op1Var->getTypeName(); if (startsWith(typeName, "::")) typeName.erase(typeName.begin(), typeName.begin() + 2); - switch (mSettings->library.getTypeCheck("unusedvar", typeName)) { + switch (mSettings.library.getTypeCheck("unusedvar", typeName)) { case Library::TypeCheck::def: bailoutTypeName = std::move(typeName); break; @@ -1359,7 +1357,21 @@ void CheckUnusedVar::checkFunctionVariableUsage() if (tok->previous() && tok->previous()->variable() && tok->previous()->variable()->nameToken()->scope()->type == ScopeType::eUnion) continue; - FwdAnalysis fwdAnalysis(*mSettings); + if (const ValueType *vt = expr->valueType()) { + if (vt->type == ValueType::RECORD && + !vt->pointer && + vt->typeScope && + vt->typeScope->definedType && + !symbolDatabase->isRecordTypeWithoutSideEffects(vt->typeScope->definedType)) + continue; + + if (vt->type == ValueType::SMART_POINTER && + vt->smartPointerType && + !symbolDatabase->isRecordTypeWithoutSideEffects(vt->smartPointerType)) + continue; + } + + FwdAnalysis fwdAnalysis(mSettings); const Token* scopeEnd = ValueFlow::getEndOfExprScope(expr, scope, /*smallest*/ false); if (fwdAnalysis.unusedValue(expr, start, scopeEnd)) { if (!bailoutTypeName.empty()) { @@ -1367,7 +1379,7 @@ void CheckUnusedVar::checkFunctionVariableUsage() reportLibraryCfgError(tok, bailoutTypeName); continue; } - if (isTrivialInit && findExpressionChanged(expr, start, scopeEnd, *mSettings)) + if (isTrivialInit && findExpressionChanged(expr, start, scopeEnd, mSettings)) continue; // warn @@ -1449,7 +1461,7 @@ void CheckUnusedVar::checkFunctionVariableUsage() if (mTokenizer->isCPP() && var->isClass() && (!var->valueType() || var->valueType()->type == ValueType::Type::UNKNOWN_TYPE)) { const std::string typeName = var->getTypeName(); - switch (mSettings->library.getTypeCheck("unusedvar", typeName)) { + switch (mSettings.library.getTypeCheck("unusedvar", typeName)) { case Library::TypeCheck::def: reportLibraryCfgError(vnt, typeName); break; @@ -1468,25 +1480,25 @@ void CheckUnusedVar::checkFunctionVariableUsage() } } -void CheckUnusedVar::unusedVariableError(const Token *tok, const std::string &varname) +void CheckUnusedVarImpl::unusedVariableError(const Token *tok, const std::string &varname) { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("unusedVariable")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("unusedVariable")) return; reportError(tok, Severity::style, "unusedVariable", "$symbol:" + varname + "\nUnused variable: $symbol", CWE563, Certainty::normal); } -void CheckUnusedVar::allocatedButUnusedVariableError(const Token *tok, const std::string &varname) +void CheckUnusedVarImpl::allocatedButUnusedVariableError(const Token *tok, const std::string &varname) { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("unusedVariable")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("unusedVariable")) return; reportError(tok, Severity::style, "unusedAllocatedMemory", "$symbol:" + varname + "\nVariable '$symbol' is allocated memory that is never used.", CWE563, Certainty::normal); } -void CheckUnusedVar::unreadVariableError(const Token *tok, const std::string &varname, bool modified) +void CheckUnusedVarImpl::unreadVariableError(const Token *tok, const std::string &varname, bool modified) { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("unusedVariable")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("unusedVariable") && !mSettings.isPremiumEnabled("unreadVariable")) return; if (modified) @@ -1495,9 +1507,9 @@ void CheckUnusedVar::unreadVariableError(const Token *tok, const std::string &va reportError(tok, Severity::style, "unreadVariable", "$symbol:" + varname + "\nVariable '$symbol' is assigned a value that is never used.", CWE563, Certainty::normal); } -void CheckUnusedVar::unassignedVariableError(const Token *tok, const std::string &varname) +void CheckUnusedVarImpl::unassignedVariableError(const Token *tok, const std::string &varname) { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("unusedVariable")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("unusedVariable")) return; reportError(tok, Severity::style, "unassignedVariable", "$symbol:" + varname + "\nVariable '$symbol' is not assigned a value.", CWE665, Certainty::normal); @@ -1506,9 +1518,9 @@ void CheckUnusedVar::unassignedVariableError(const Token *tok, const std::string //--------------------------------------------------------------------------- // Check that all struct members are used //--------------------------------------------------------------------------- -void CheckUnusedVar::checkStructMemberUsage() +void CheckUnusedVarImpl::checkStructMemberUsage() { - if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("unusedStructMember") && !mSettings->isPremiumEnabled("unusedVariable")) + if (!mSettings.severity.isEnabled(Severity::style) && !mSettings.isPremiumEnabled("unusedStructMember") && !mSettings.isPremiumEnabled("unusedVariable")) return; logChecker("CheckUnusedVar::checkStructMemberUsage"); // style @@ -1630,7 +1642,8 @@ void CheckUnusedVar::checkStructMemberUsage() for (const Variable &var : scope.varlist) { // only warn for variables without side effects - if (!var.typeStartToken()->isStandardType() && !var.isPointer() && !astIsContainer(var.nameToken()) && !mTokenizer->getSymbolDatabase()->isRecordTypeWithoutSideEffects(var.type())) + if (!(var.valueType() && var.valueType()->type >= ValueType::VOID) && !var.isPointer() && !astIsContainer(var.nameToken()) && + !astIsSmartPointer(var.nameToken()) && !mTokenizer->getSymbolDatabase()->isRecordTypeWithoutSideEffects(var.type())) continue; if (isInherited && !var.isPrivate()) continue; @@ -1668,6 +1681,27 @@ void CheckUnusedVar::checkStructMemberUsage() if (use) break; } + // Class used in template with unknown definition + if (Token::Match(tok, "%name% <") && tok->linkAt(1)) { + if (tok->function()) + continue; + if (tok->type() && tok->type()->classScope) + continue; + const Token *start = tok; + while (Token::Match(start->tokAt(-2), "%name% ::")) + start = start->tokAt(-2); + if (mSettings.library.detectContainer(start)) + continue; + const Token *end = tok->linkAt(1); + for (; tok != end; tok = tok->next()) { + if (tok->type() && tok->type()->classScope == &scope) { + use = true; + break; + } + } + if (use) + break; + } if (tok->variable() != &var) continue; if (tok != var.nameToken()) { @@ -1687,12 +1721,12 @@ void CheckUnusedVar::checkStructMemberUsage() } } -void CheckUnusedVar::unusedStructMemberError(const Token* tok, const std::string& structname, const std::string& varname, const std::string& prefix) +void CheckUnusedVarImpl::unusedStructMemberError(const Token* tok, const std::string& structname, const std::string& varname, const std::string& prefix) { reportError(tok, Severity::style, "unusedStructMember", "$symbol:" + structname + "::" + varname + '\n' + prefix + " member '$symbol' is never used.", CWE563, Certainty::normal); } -bool CheckUnusedVar::isEmptyType(const Type* type) +bool CheckUnusedVarImpl::isEmptyType(const Type* type) { // a type that has no variables and no constructor @@ -1713,21 +1747,22 @@ bool CheckUnusedVar::isEmptyType(const Type* type) return (emptyType = false); } -void CheckUnusedVar::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) +void CheckUnusedVar::runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) { - CheckUnusedVar checkUnusedVar(&tokenizer, &tokenizer.getSettings(), errorLogger); + CheckUnusedVarImpl checkUnusedVar(&tokenizer, tokenizer.getSettings(), errorLogger); // Coding style checks checkUnusedVar.checkStructMemberUsage(); checkUnusedVar.checkFunctionVariableUsage(); } -void CheckUnusedVar::getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const +void CheckUnusedVar::getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const { - CheckUnusedVar c(nullptr, settings, errorLogger); + CheckUnusedVarImpl c(nullptr, settings, errorLogger); c.unusedVariableError(nullptr, "varname"); c.allocatedButUnusedVariableError(nullptr, "varname"); c.unreadVariableError(nullptr, "varname", false); c.unassignedVariableError(nullptr, "varname"); c.unusedStructMemberError(nullptr, "structname", "variable"); } + diff --git a/lib/checkunusedvar.h b/lib/checkunusedvar.h index 5ab79514cf9..2239b4a60d8 100644 --- a/lib/checkunusedvar.h +++ b/lib/checkunusedvar.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,6 +22,7 @@ //--------------------------------------------------------------------------- #include "check.h" +#include "checkimpl.h" #include "config.h" #include @@ -42,19 +43,33 @@ class Tokenizer; /** @brief Various small checks */ class CPPCHECKLIB CheckUnusedVar : public Check { - friend class TestUnusedVar; - public: /** @brief This constructor is used when registering the CheckClass */ - CheckUnusedVar() : Check(myName()) {} + CheckUnusedVar() : Check("UnusedVar") {} private: - /** @brief This constructor is used when running checks. */ - CheckUnusedVar(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) - : Check(myName(), tokenizer, settings, errorLogger) {} - /** @brief Run checks against the normal token list */ - void runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) override; + void runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) override; + + void getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const override; + + std::string classInfo() const override { + return "UnusedVar checks\n" + + // style + "- unused variable\n" + "- allocated but unused variable\n" + "- unread variable\n" + "- unassigned variable\n" + "- unused struct member\n"; + } +}; + +class CPPCHECKLIB CheckUnusedVarImpl : public CheckImpl { +public: + /** @brief This constructor is used when running checks. */ + CheckUnusedVarImpl(const Tokenizer *tokenizer, const Settings &settings, ErrorLogger &errorLogger) + : CheckImpl(tokenizer, settings, errorLogger) {} /** @brief %Check for unused function variables */ void checkFunctionVariableUsage_iterateScopes(const Scope* scope, Variables& variables) const; @@ -72,25 +87,7 @@ class CPPCHECKLIB CheckUnusedVar : public Check { void unreadVariableError(const Token *tok, const std::string &varname, bool modified); void unassignedVariableError(const Token *tok, const std::string &varname); - void getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const override; - - static std::string myName() { - return "UnusedVar"; - } - - std::string classInfo() const override { - return "UnusedVar checks\n" - - // style - "- unused variable\n" - "- allocated but unused variable\n" - "- unread variable\n" - "- unassigned variable\n" - "- unused struct member\n"; - } - std::map mIsEmptyTypeMap; - }; /// @} //--------------------------------------------------------------------------- diff --git a/lib/checkvaarg.cpp b/lib/checkvaarg.cpp index 8151957a7d7..ae8b1626ba6 100644 --- a/lib/checkvaarg.cpp +++ b/lib/checkvaarg.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,14 +30,6 @@ #include #include -//--------------------------------------------------------------------------- - -// Register this check class (by creating a static instance of it) -namespace { - CheckVaarg instance; -} - - //--------------------------------------------------------------------------- // Ensure that correct parameter is passed to va_start() //--------------------------------------------------------------------------- @@ -47,11 +39,11 @@ static const CWE CWE664(664U); // Improper Control of a Resource Through its L static const CWE CWE688(688U); // Function Call With Incorrect Variable or Reference as Argument static const CWE CWE758(758U); // Reliance on Undefined, Unspecified, or Implementation-Defined Behavior -void CheckVaarg::va_start_argument() +void CheckVaargImpl::va_start_argument() { const SymbolDatabase* const symbolDatabase = mTokenizer->getSymbolDatabase(); const std::size_t functions = symbolDatabase->functionScopes.size(); - const bool printWarnings = mSettings->severity.isEnabled(Severity::warning); + const bool printWarnings = mSettings.severity.isEnabled(Severity::warning); logChecker("CheckVaarg::va_start_argument"); @@ -81,13 +73,13 @@ void CheckVaarg::va_start_argument() } } -void CheckVaarg::wrongParameterTo_va_start_error(const Token *tok, const std::string& paramIsName, const std::string& paramShouldName) +void CheckVaargImpl::wrongParameterTo_va_start_error(const Token *tok, const std::string& paramIsName, const std::string& paramShouldName) { reportError(tok, Severity::warning, "va_start_wrongParameter", "'" + paramIsName + "' given to va_start() is not last named argument of the function. Did you intend to pass '" + paramShouldName + "'?", CWE688, Certainty::normal); } -void CheckVaarg::referenceAs_va_start_error(const Token *tok, const std::string& paramName) +void CheckVaargImpl::referenceAs_va_start_error(const Token *tok, const std::string& paramName) { reportError(tok, Severity::error, "va_start_referencePassed", "Using reference '" + paramName + "' as parameter for va_start() results in undefined behaviour.", CWE758, Certainty::normal); @@ -98,9 +90,9 @@ void CheckVaarg::referenceAs_va_start_error(const Token *tok, const std::string& // Detect va_list usage after va_end() //--------------------------------------------------------------------------- -void CheckVaarg::va_list_usage() +void CheckVaargImpl::va_list_usage() { - if (mSettings->clang) + if (mSettings.clang) return; logChecker("CheckVaarg::va_list_usage"); // notclang @@ -163,34 +155,34 @@ void CheckVaarg::va_list_usage() } } -void CheckVaarg::va_end_missingError(const Token *tok, const std::string& varname) +void CheckVaargImpl::va_end_missingError(const Token *tok, const std::string& varname) { reportError(tok, Severity::error, "va_end_missing", "va_list '" + varname + "' was opened but not closed by va_end().", CWE664, Certainty::normal); } -void CheckVaarg::va_list_usedBeforeStartedError(const Token *tok, const std::string& varname) +void CheckVaargImpl::va_list_usedBeforeStartedError(const Token *tok, const std::string& varname) { reportError(tok, Severity::error, "va_list_usedBeforeStarted", "va_list '" + varname + "' used before va_start() was called.", CWE664, Certainty::normal); } -void CheckVaarg::va_start_subsequentCallsError(const Token *tok, const std::string& varname) +void CheckVaargImpl::va_start_subsequentCallsError(const Token *tok, const std::string& varname) { reportError(tok, Severity::error, "va_start_subsequentCalls", "va_start() or va_copy() called subsequently on '" + varname + "' without va_end() in between.", CWE664, Certainty::normal); } -void CheckVaarg::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) +void CheckVaarg::runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) { - CheckVaarg check(&tokenizer, &tokenizer.getSettings(), errorLogger); + CheckVaargImpl check(&tokenizer, tokenizer.getSettings(), errorLogger); check.va_start_argument(); check.va_list_usage(); } -void CheckVaarg::getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const +void CheckVaarg::getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const { - CheckVaarg c(nullptr, settings, errorLogger); + CheckVaargImpl c(nullptr, settings, errorLogger); c.wrongParameterTo_va_start_error(nullptr, "arg1", "arg2"); c.referenceAs_va_start_error(nullptr, "arg1"); c.va_end_missingError(nullptr, "vl"); diff --git a/lib/checkvaarg.h b/lib/checkvaarg.h index 7af5d98d66f..eea3f345bcc 100644 --- a/lib/checkvaarg.h +++ b/lib/checkvaarg.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,6 +23,7 @@ //--------------------------------------------------------------------------- #include "check.h" +#include "checkimpl.h" #include "config.h" #include @@ -41,13 +42,28 @@ class Tokenizer; class CPPCHECKLIB CheckVaarg : public Check { public: - CheckVaarg() : Check(myName()) {} + CheckVaarg() : Check("Vaarg") {} + + void runChecks(const Tokenizer &tokenizer, ErrorLogger& errorLogger) override; private: - CheckVaarg(const Tokenizer *tokenizer, const Settings *settings, ErrorLogger *errorLogger) - : Check(myName(), tokenizer, settings, errorLogger) {} + void getErrorMessages(ErrorLogger& errorLogger, const Settings &settings) const override; + + std::string classInfo() const override { + return "Check for misusage of variable argument lists:\n" + "- Wrong parameter passed to va_start()\n" + "- Reference passed to va_start()\n" + "- Missing va_end()\n" + "- Using va_list before it is opened\n" + "- Subsequent calls to va_start/va_copy()\n"; + } +}; - void runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) override; +class CPPCHECKLIB CheckVaargImpl : public CheckImpl +{ +public: + CheckVaargImpl(const Tokenizer *tokenizer, const Settings &settings, ErrorLogger &errorLogger) + : CheckImpl(tokenizer, settings, errorLogger) {} void va_start_argument(); void va_list_usage(); @@ -57,21 +73,6 @@ class CPPCHECKLIB CheckVaarg : public Check { void va_end_missingError(const Token *tok, const std::string& varname); void va_list_usedBeforeStartedError(const Token *tok, const std::string& varname); void va_start_subsequentCallsError(const Token *tok, const std::string& varname); - - void getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const override; - - static std::string myName() { - return "Vaarg"; - } - - std::string classInfo() const override { - return "Check for misusage of variable argument lists:\n" - "- Wrong parameter passed to va_start()\n" - "- Reference passed to va_start()\n" - "- Missing va_end()\n" - "- Using va_list before it is opened\n" - "- Subsequent calls to va_start/va_copy()\n"; - } }; /// @} diff --git a/lib/clangimport.cpp b/lib/clangimport.cpp index c96a678347a..0043f5a3cde 100644 --- a/lib/clangimport.cpp +++ b/lib/clangimport.cpp @@ -20,6 +20,7 @@ #include "errortypes.h" #include "mathlib.h" +#include "refthunk.h" #include "standards.h" #include "symboldatabase.h" #include "token.h" @@ -123,6 +124,12 @@ static std::string unquote(const std::string &s) } +// location in a buffer that is generated by clang: "<:115:23, file.h:18:19>" +static bool isGeneratedBufferLocation(const std::string &s, std::string::size_type pos = 0) +{ + return s.compare(pos, 12, "<:") == 0 || s.compare(pos, 17, "<:") == 0; +} + static std::vector splitString(const std::string &line) { std::vector ret; @@ -134,13 +141,15 @@ static std::vector splitString(const std::string &line) pos1 = line.find_first_not_of(' ', pos1 + 1); continue; } - if (line[pos1] == '<') + if (line[pos1] == '<') { pos2 = line.find('>', pos1); - else if (line[pos1] == '\"') + if (pos2 != std::string::npos && isGeneratedBufferLocation(line, pos1)) + pos2 = line.find('>', pos2 + 1); + } else if (line[pos1] == '\"') pos2 = line.find('\"', pos1+1); else if (line[pos1] == '\'') { pos2 = line.find('\'', pos1+1); - if (pos2 < static_cast(line.size()) - 3 && line.compare(pos2, 3, "\':\'", 0, 3) == 0) + if (pos2 < line.size() - 3 && line.compare(pos2, 3, "\':\'", 0, 3) == 0) pos2 = line.find('\'', pos2 + 3); } else { pos2 = pos1; @@ -233,8 +242,8 @@ namespace clangimport { , mSymbolDatabase(symbolDatabase) {} - const Settings &mSettings; - SymbolDatabase &mSymbolDatabase; + RefThunk mSettings; + RefThunk mSymbolDatabase; int enumValue = 0; @@ -303,7 +312,7 @@ namespace clangimport { } // "}" tokens that are not end-of-scope - std::set mNotScope; + std::set mNotScope; std::map scopeAccessControl; private: @@ -328,7 +337,10 @@ namespace clangimport { public: AstNode(std::string nodeType, const std::string &ext, Data *data) : nodeType(std::move(nodeType)), mExtTokens(splitString(ext)), mData(data) - {} + { + if (mExtTokens.empty()) + throw InternalError(nullptr, "invalid AST node: " + this->nodeType, InternalError::AST); + } std::string nodeType; std::vector children; @@ -357,7 +369,7 @@ namespace clangimport { /** * @throws InternalError thrown if index is out of bounds */ - AstNodePtr getChild(int c) { + AstNodePtr getChild(size_t c) { if (c >= children.size()) { std::ostringstream err; err << "ClangImport: AstNodePtr::getChild(" << c << ") out of bounds. children.size=" << children.size() << " " << nodeType; @@ -422,15 +434,13 @@ std::string clangimport::AstNode::getSpelling() const if (nodeType == FunctionDecl || nodeType == CXXConstructorDecl || nodeType == CXXMethodDecl) { while (typeIndex >= 0 && mExtTokens[typeIndex][0] != '\'') typeIndex--; - if (typeIndex <= 0) - return ""; } if (nodeType == DeclRefExpr) { while (typeIndex > 0 && std::isalpha(mExtTokens[typeIndex][0])) typeIndex--; - if (typeIndex <= 0) - return ""; } + if (typeIndex <= 0) + return ""; const std::string &str = mExtTokens[typeIndex - 1]; if (startsWith(str,"col:")) return ""; @@ -509,7 +519,7 @@ void clangimport::AstNode::dumpAst(int num, int indent) const for (const auto& tok: mExtTokens) std::cout << " " << tok; std::cout << std::endl; - for (int c = 0; c < children.size(); ++c) { + for (size_t c = 0; c < children.size(); ++c) { if (children[c]) children[c]->dumpAst(c, indent + 2); else @@ -562,19 +572,32 @@ void clangimport::AstNode::setLocations(TokenList &tokenList, int file, int line if (pos != std::string::npos) col = strToInt(ext.substr(pos+6, ext.find_first_of(":,>", pos+6) - (pos+6))); } else if (ext[0] == '<') { - const std::string::size_type colon = ext.find(':'); - if (colon != std::string::npos) { - const bool windowsPath = colon == 2 && ext.size() > 3 && ext[2] == ':'; - const std::string::size_type sep1 = windowsPath ? ext.find(':', 4) : colon; - const std::string::size_type sep2 = ext.find(':', sep1 + 1); - file = tokenList.appendFileIfNew(ext.substr(1, sep1 - 1)); - line = strToInt(ext.substr(sep1 + 1, sep2 - sep1 - 1)); + // start of the file location + std::string::size_type pos = 1; + if (isGeneratedBufferLocation(ext)) { + // Use the end of the range if that is a file location, otherwise the location is unchanged + pos = ext.find(", "); + if (pos != std::string::npos) { + pos += 2; + if (ext[pos] == '<' || ext.compare(pos, 4, "col:") == 0 || ext.compare(pos, 5, "line:") == 0) + pos = std::string::npos; + } } - else { - // "" are encountered in every AST dump by some built-in TypedefDecl - // an completely empty location block was encountered with a CompoundStmt - if (ext != "<" && ext != "<>") - throw InternalError(nullptr, "invalid AST location: " + ext, InternalError::AST); + if (pos != std::string::npos) { + const std::string::size_type colon = ext.find(':', pos); + if (colon != std::string::npos) { + const bool windowsPath = colon == pos + 1 && ext.size() > pos + 2; + const std::string::size_type sep1 = windowsPath ? ext.find(':', pos + 3) : colon; + const std::string::size_type sep2 = ext.find(':', sep1 + 1); + file = tokenList.appendFileIfNew(ext.substr(pos, sep1 - pos)); + line = strToInt(ext.substr(sep1 + 1, sep2 - sep1 - 1)); + } + else { + // "" are encountered in every AST dump by some built-in TypedefDecl + // an completely empty location block was encountered with a CompoundStmt + if (ext != "<" && ext != "<>") + throw InternalError(nullptr, "invalid AST location: " + ext, InternalError::AST); + } } } } @@ -643,7 +666,7 @@ const ::Type * clangimport::AstNode::addTypeTokens(TokenList &tokenList, const s for (const Token *typeToken = tokenList.back(); Token::Match(typeToken, "&|*|%name%"); typeToken = typeToken->previous()) { if (!typeToken->isName()) continue; - const ::Type *recordType = scope->symdb.findVariableType(scope, typeToken); + const ::Type *recordType = scope->symdb().findVariableType(scope, typeToken); if (recordType) { const_cast(typeToken)->type(recordType); return recordType; @@ -672,7 +695,7 @@ void clangimport::AstNode::addFullScopeNameTokens(TokenList &tokenList, const Sc const Scope *clangimport::AstNode::getNestedInScope(TokenList &tokenList) { if (!tokenList.back()) - return &mData->mSymbolDatabase.scopeList.front(); + return &mData->mSymbolDatabase().scopeList.front(); if (tokenList.back()->str() == "}" && mData->mNotScope.find(tokenList.back()) == mData->mNotScope.end()) return tokenList.back()->scope()->nestedIn; return tokenList.back()->scope(); @@ -1033,7 +1056,10 @@ Token *clangimport::AstNode::createTokens(TokenList &tokenList) return nullptr; } if (nodeType == EnumConstantDecl) { - Token *nameToken = addtoken(tokenList, getSpelling()); + const std::string name = getSpelling(); + if (name.empty()) + throw InternalError(nullptr, "invalid AST node: " + nodeType, InternalError::AST); + Token *nameToken = addtoken(tokenList, name); auto *scope = const_cast(nameToken->scope()); scope->enumeratorList.emplace_back(nameToken->scope()); Enumerator *e = &scope->enumeratorList.back(); @@ -1071,8 +1097,8 @@ Token *clangimport::AstNode::createTokens(TokenList &tokenList) const_cast(enumscope->bodyEnd)->deletePrevious(); // Create enum type - mData->mSymbolDatabase.typeList.emplace_back(enumtok, enumscope, enumtok->scope()); - enumscope->definedType = &mData->mSymbolDatabase.typeList.back(); + mData->mSymbolDatabase().typeList.emplace_back(enumtok, enumscope, enumtok->scope()); + enumscope->definedType = &mData->mSymbolDatabase().typeList.back(); if (nametok) const_cast(enumtok->scope())->definedTypesMap[nametok->str()] = enumscope->definedType; @@ -1120,6 +1146,8 @@ Token *clangimport::AstNode::createTokens(TokenList &tokenList) return nullptr; } if (nodeType == GotoStmt) { + if (mExtTokens.size() < 2) + return nullptr; addtoken(tokenList, "goto"); addtoken(tokenList, unquote(mExtTokens[mExtTokens.size() - 2])); addtoken(tokenList, ";"); @@ -1209,9 +1237,12 @@ Token *clangimport::AstNode::createTokens(TokenList &tokenList) if (children.empty()) return nullptr; const Token *defToken = addtoken(tokenList, "namespace"); - const std::string &s = mExtTokens[mExtTokens.size() - 2]; - const Token* nameToken = (startsWith(s, "col:") || startsWith(s, "line:")) ? - addtoken(tokenList, mExtTokens.back()) : nullptr; + const Token* nameToken = nullptr; + if (mExtTokens.size() >= 2) { + const std::string &s = mExtTokens[mExtTokens.size() - 2]; + if (startsWith(s, "col:") || startsWith(s, "line:")) + nameToken = addtoken(tokenList, mExtTokens.back()); + } Scope *scope = createScope(tokenList, ScopeType::eNamespace, children, defToken); if (nameToken) scope->className = nameToken->str(); @@ -1238,8 +1269,8 @@ Token *clangimport::AstNode::createTokens(TokenList &tokenList) } Scope *recordScope = createScope(tokenList, ScopeType::eStruct, children, classDef); - mData->mSymbolDatabase.typeList.emplace_back(classDef, recordScope, classDef->scope()); - recordScope->definedType = &mData->mSymbolDatabase.typeList.back(); + mData->mSymbolDatabase().typeList.emplace_back(classDef, recordScope, classDef->scope()); + recordScope->definedType = &mData->mSymbolDatabase().typeList.back(); if (!recordName.empty()) { recordScope->className = recordName; const_cast(classDef->scope())->definedTypesMap[recordName] = recordScope->definedType; @@ -1364,6 +1395,10 @@ Token * clangimport::AstNode::createTokensCall(TokenList &tokenList) void clangimport::AstNode::createTokensFunctionDecl(TokenList &tokenList) { + // an address-only node has no name: addtoken("") adds nothing and would return a null back() + const std::string name = getSpelling(); + if (name.empty()) + throw InternalError(nullptr, "invalid AST node: " + nodeType, InternalError::AST); const bool prev = contains(mExtTokens, "prev"); const bool hasBody = !children.empty() && children.back()->nodeType == CompoundStmt; const bool isStatic = contains(mExtTokens, "static"); @@ -1385,11 +1420,14 @@ void clangimport::AstNode::createTokensFunctionDecl(TokenList &tokenList) if (mExtTokens.size() > 4 && mExtTokens[1] == "parent") addFullScopeNameTokens(tokenList, mData->getScope(mExtTokens[2])); - Token *nameToken = addtoken(tokenList, getSpelling() + getTemplateParameters()); + Token *nameToken = addtoken(tokenList, name + getTemplateParameters()); auto *nestedIn = const_cast(nameToken->scope()); if (prev) { - const std::string addr = *(std::find(mExtTokens.cbegin(), mExtTokens.cend(), "prev") + 1); + const auto it = std::find(mExtTokens.cbegin(), mExtTokens.cend(), "prev"); + if (it + 1 == mExtTokens.cend()) + throw InternalError(nullptr, "invalid AST node: " + nodeType, InternalError::AST); + const std::string addr = *(it + 1); mData->ref(addr, nameToken); } if (!nameToken->function()) { @@ -1432,10 +1470,13 @@ void clangimport::AstNode::createTokensFunctionDecl(TokenList &tokenList) function->nestedIn = nestedIn; function->argDef = par1; // Function arguments - for (int i = 0; i < children.size(); ++i) { + for (size_t i = 0; i < children.size(); ++i) { AstNodePtr child = children[i]; if (child->nodeType != ParmVarDecl) continue; + // there must be a token between the address and the type + if (child->mExtTokens.size() < 3) + throw InternalError(nullptr, "invalid AST node: " + child->nodeType, InternalError::AST); if (tokenList.back() != par1) addtoken(tokenList, ","); const Type *recordType = addTypeTokens(tokenList, child->mExtTokens.back(), nestedIn); @@ -1488,10 +1529,12 @@ void clangimport::AstNode::createTokensForCXXRecord(TokenList &tokenList) const bool isStruct = contains(mExtTokens, "struct"); Token * const classToken = addtoken(tokenList, isStruct ? "struct" : "class"); std::string className; - if (mExtTokens[mExtTokens.size() - 2] == (isStruct?"struct":"class")) - className = mExtTokens.back(); - else - className = mExtTokens[mExtTokens.size() - 2]; + if (mExtTokens.size() >= 2) { + if (mExtTokens[mExtTokens.size() - 2] == (isStruct?"struct":"class")) + className = mExtTokens.back(); + else + className = mExtTokens[mExtTokens.size() - 2]; + } className += getTemplateParameters(); /*Token *nameToken =*/ addtoken(tokenList, className); // base classes @@ -1520,8 +1563,8 @@ void clangimport::AstNode::createTokensForCXXRecord(TokenList &tokenList) const std::string addr = mExtTokens[0]; mData->scopeDecl(addr, scope); scope->className = className; - mData->mSymbolDatabase.typeList.emplace_back(classToken, scope, classToken->scope()); - scope->definedType = &mData->mSymbolDatabase.typeList.back(); + mData->mSymbolDatabase().typeList.emplace_back(classToken, scope, classToken->scope()); + scope->definedType = &mData->mSymbolDatabase().typeList.back(); const_cast(classToken->scope())->definedTypesMap[className] = scope->definedType; } addtoken(tokenList, ";"); @@ -1536,6 +1579,9 @@ Token * clangimport::AstNode::createTokensVarDecl(TokenList &tokenList) int typeIndex = mExtTokens.size() - 1; while (typeIndex > 1 && std::isalpha(mExtTokens[typeIndex][0])) typeIndex--; + // there must be a name token between the address and the type + if (typeIndex < 2) + throw InternalError(nullptr, "invalid AST node: " + nodeType, InternalError::AST); const std::string type = mExtTokens[typeIndex]; const std::string name = mExtTokens[typeIndex - 1]; const Token *startToken = tokenList.back(); @@ -1639,7 +1685,9 @@ void clangimport::parseClangAstDump(Tokenizer &tokenizer, std::istream &f) if (pos1 == std::string::npos) continue; if (!tree.empty() && line.substr(pos1) == "-<<>>") { - const int level = (pos1 - 1) / 2; + const size_t level = (pos1 - 1) / 2; + if (level == 0 || level > tree.size()) + continue; tree[level - 1]->children.push_back(nullptr); continue; } @@ -1657,7 +1705,7 @@ void clangimport::parseClangAstDump(Tokenizer &tokenizer, std::istream &f) continue; } - const int level = (pos1 - 1) / 2; + const size_t level = (pos1 - 1) / 2; if (level == 0 || level > tree.size()) continue; diff --git a/lib/config.h b/lib/config.h index 960c2c91aa4..c88c431288c 100644 --- a/lib/config.h +++ b/lib/config.h @@ -203,7 +203,7 @@ #if HAVE_EXECINFO_H #define USE_UNIX_BACKTRACE_SUPPORT #endif -#elif defined(__GNUC__) && !defined(__APPLE__) && !defined(__CYGWIN__) && !defined(__MINGW32__) && !defined(__NetBSD__) && !defined(__OpenBSD__) && !defined(__SVR4) && !defined(__QNX__) && !defined(_AIX) +#elif __has_include() #define USE_UNIX_BACKTRACE_SUPPORT #endif #endif diff --git a/lib/cppcheck.cpp b/lib/cppcheck.cpp index 0a8e721936b..8e32e3e3152 100644 --- a/lib/cppcheck.cpp +++ b/lib/cppcheck.cpp @@ -21,6 +21,7 @@ #include "addoninfo.h" #include "analyzerinfo.h" #include "check.h" +#include "checks.h" #include "checkunusedfunctions.h" #include "clangimport.h" #include "color.h" @@ -46,6 +47,7 @@ #ifdef HAVE_RULES #include "regex.h" +#include "rule.h" #endif #include @@ -59,6 +61,7 @@ #include // IWYU pragma: keep #include #include +#include #include #include #include @@ -98,9 +101,9 @@ class CppCheck::CppCheckLogger : public ErrorLogger closePlist(); } - void setRemarkComments(std::vector remarkComments) + void addRemarkComments(const std::vector &remarkComments) { - mRemarkComments = std::move(remarkComments); + mRemarkComments.insert(mRemarkComments.end(), remarkComments.begin(), remarkComments.end()); } void setLocationMacros(const Token* startTok, const std::vector& files) @@ -122,17 +125,25 @@ class CppCheck::CppCheckLogger : public ErrorLogger mErrorList.clear(); } - void openPlist(const std::string& filename, const std::vector& files) + void openPlist(const std::string& filename) { mPlistFile.open(filename); - mPlistFile << ErrorLogger::plistHeader(version(), files); + mPlistFile << ErrorLogger::plistHeader(version()); + } + + void setPlistFilenames(std::vector files) + { + if (mPlistFile.is_open()) { + mPlistFilenames = std::move(files); + } } void closePlist() { if (mPlistFile.is_open()) { - mPlistFile << ErrorLogger::plistFooter(); + mPlistFile << ErrorLogger::plistFooter(mPlistFilenames); mPlistFile.close(); + mPlistFilenames.clear(); } } @@ -199,7 +210,9 @@ class CppCheck::CppCheckLogger : public ErrorLogger } // TODO: there should be no need for the verbose and default messages here - std::string errmsg = msg.toString(mSettings.verbose, mSettings.templateFormat, mSettings.templateLocation); + // Code is not needed for deduplication + const bool noCode = true; + std::string errmsg = msg.toString(mSettings.verbose, mSettings.templateFormat, mSettings.templateLocation, noCode); if (errmsg.empty()) return; @@ -280,6 +293,7 @@ class CppCheck::CppCheckLogger : public ErrorLogger std::map> mLocationMacros; // What macros are used on a location? std::ofstream mPlistFile; + std::vector mPlistFilenames; unsigned int mExitCode{}; @@ -823,6 +837,7 @@ unsigned int CppCheck::check(const FileSettings &fs) else tempSettings.userDefines += fs.cppcheckDefines(); tempSettings.includePaths = fs.includePaths; + tempSettings.userIncludes.insert(tempSettings.userIncludes.end(), fs.forcedIncludes.cbegin(), fs.forcedIncludes.cend()); tempSettings.userUndefs.insert(fs.undefs.cbegin(), fs.undefs.cend()); if (fs.standard.find("++") != std::string::npos) tempSettings.standards.setCPP(fs.standard); @@ -881,21 +896,25 @@ std::size_t CppCheck::calculateHash(const Preprocessor& preprocessor, const std: unsigned int CppCheck::checkBuffer(const FileWithDetails &file, const std::string &cfgname, const char* data, std::size_t size) { - const auto f = [&file, data, size](std::vector& files, simplecpp::OutputList* outputList) { - return simplecpp::TokenList{{data, size}, files, file.spath(), outputList}; + const auto f = [&file, data, size, this](std::vector& files, simplecpp::OutputList* outputList) { + simplecpp::DUI dui; + dui.std = mSettings.standards.getStdForLanguage(file.lang()); + return simplecpp::TokenList{{data, size}, files, file.spath(), dui, outputList}; }; return checkInternal(file, cfgname, f); } unsigned int CppCheck::checkFile(const FileWithDetails& file, const std::string &cfgname) { - const auto f = [&file](std::vector& files, simplecpp::OutputList* outputList) { - return simplecpp::TokenList{file.spath(), files, outputList}; + const auto f = [&file, this](std::vector& files, simplecpp::OutputList* outputList) { + simplecpp::DUI dui; + dui.std = mSettings.standards.getStdForLanguage(file.lang()); + return simplecpp::TokenList{file.spath(), files, dui, outputList}; }; return checkInternal(file, cfgname, f); } -void CppCheck::checkPlistOutput(const FileWithDetails& file, const std::vector& files) +void CppCheck::checkPlistOutput(const FileWithDetails& file) { if (!mSettings.plistOutput.empty()) { const bool slashFound = file.spath().find('/') != std::string::npos; @@ -906,7 +925,7 @@ void CppCheck::checkPlistOutput(const FileWithDetails& file, const std::vector {}(file.spath()); filename = mSettings.plistOutput + noSuffixFilename + "_" + std::to_string(fileNameHash) + ".plist"; - mLogger->openPlist(filename, files); + mLogger->openPlist(filename); } } @@ -923,8 +942,9 @@ unsigned int CppCheck::checkInternal(const FileWithDetails& file, const std::str if (Settings::terminated()) return mLogger->exitcode(); - TimerResults checkTimeResults; - Timer fileTotalTimer{"Check time: " + file.spath(), mSettings.showtime, &checkTimeResults, Timer::Type::FILE}; + std::unique_ptr checkTimeTimer; + if (mSettings.showtime == Settings::ShowTime::FILE || mSettings.showtime == Settings::ShowTime::FILE_TOTAL || mSettings.showtime == Settings::ShowTime::TOP5_FILE) + checkTimeTimer.reset(new OneShotTimer("Check time: " + file.spath())); if (!mSettings.quiet) { std::string fixedpath = Path::toNativeSeparators(file.spath()); @@ -973,7 +993,7 @@ unsigned int CppCheck::checkInternal(const FileWithDetails& file, const std::str tokenlist.createTokens(std::move(tokens)); // this is not a real source file - we just want to tokenize it. treat it as C anyways as the language needs to be determined. Tokenizer tokenizer(std::move(tokenlist), mErrorLogger); - mUnusedFunctionsCheck->parseTokens(tokenizer, mSettings); + mUnusedFunctionsCheck->parseTokens(tokenizer, mSettings.library); if (analyzerInformation) { mLogger->setAnalyzerInfo(nullptr); @@ -996,25 +1016,58 @@ unsigned int CppCheck::checkInternal(const FileWithDetails& file, const std::str if (preprocessor.reportOutput(outputList, true)) return mLogger->exitcode(); - if (!preprocessor.loadFiles(files)) - return mLogger->exitcode(); - - checkPlistOutput(file, files); + checkPlistOutput(file); - std::string dumpProlog; + std::string dumpFooter; if (mSettings.dump || !mSettings.addons.empty()) { - dumpProlog += getDumpFileContentsRawTokens(files, tokens1); + dumpFooter += getDumpFileContentsRawTokensFooter(tokens1); } // Parse comments and then remove them - mLogger->setRemarkComments(preprocessor.getRemarkComments()); + mLogger->addRemarkComments(preprocessor.getRemarkComments()); preprocessor.inlineSuppressions(mSuppressions.nomsg); - if (mSettings.dump || !mSettings.addons.empty()) { - std::ostringstream oss; - mSuppressions.nomsg.dump(oss); - dumpProlog += oss.str(); - } - preprocessor.removeComments(); + if (!mSettings.keepComments) + preprocessor.removeComments(); + + // Get directives + std::list directives; + preprocessor.createDirectives(directives); + preprocessor.simplifyPragmaAsm(); + + std::set configurations; + std::set configDefines = { "__cplusplus" }; + + // Insert library defines + const auto getDefineName = [](const std::string &defineString) { + return defineString.substr(0, defineString.find_first_of("( ")); + }; + std::transform(mSettings.library.defines().begin(), + mSettings.library.defines().end(), + std::inserter(configDefines, configDefines.end()), + getDefineName); + + // Keep track of all included files when using build dir + std::set includedFiles; + + preprocessor.setLoadCallback([&](simplecpp::FileData &data, bool loaded) { + if (analyzerInformation) { + includedFiles.insert(data.filename); + } + if (loaded) { + // Do preprocessing on included file + mLogger->addRemarkComments(preprocessor.getRemarkComments(data.tokens)); + preprocessor.inlineSuppressions(data.tokens, mSuppressions.nomsg); + if (!mSettings.keepComments) + Preprocessor::removeComments(data.tokens); + Preprocessor::createDirectives(data.tokens, directives); + Preprocessor::simplifyPragmaAsm(data.tokens); + // Discover new configurations from included file + if (configurations.size() < maxConfigs) + preprocessor.getConfigs(data.filename, data.tokens, configDefines, configurations); + } + }); + + preprocessor.setPlatformInfo(); if (!mSettings.buildDir.empty()) { analyzerInformation.reset(new AnalyzerInformation); @@ -1022,6 +1075,9 @@ unsigned int CppCheck::checkInternal(const FileWithDetails& file, const std::str } if (analyzerInformation) { + // Load all included files to get correct hashes and suppressions + for (const std::string &filename : analyzerInformation->getIncludes(mSettings.buildDir, file.spath(), cfgname, file.fsFileId())) + preprocessor.loadFile(files, filename); // Calculate hash so it can be compared with old hash / future hashes const std::size_t hash = calculateHash(preprocessor, file.spath()); std::list errors; @@ -1033,22 +1089,20 @@ unsigned int CppCheck::checkInternal(const FileWithDetails& file, const std::str mLogger->setAnalyzerInfo(nullptr); return mLogger->exitcode(); // known results => no need to reanalyze file } + // Clear included file list; we don't want to keep includes that have been removed from the source + // Any includes that are still present will be readded + includedFiles.clear(); } - // Get directives - std::list directives = preprocessor.createDirectives(); - preprocessor.simplifyPragmaAsm(); - - preprocessor.setPlatformInfo(); - // Get configurations.. - std::set configurations; if (maxConfigs > 1) { - Timer::run("Preprocessor::getConfigs", mSettings.showtime, mTimerResults, [&]() { - configurations = preprocessor.getConfigs(); + Timer::run("Preprocessor::getConfigs", mTimerResults, [&]() { + configurations = { "" }; + preprocessor.getConfigs(configDefines, configurations); + preprocessor.loadAllIncludes(files); }); } else { - configurations.insert(mSettings.userDefines); + configurations = { mSettings.userDefines }; } if (mSettings.checkConfiguration) { @@ -1085,7 +1139,6 @@ unsigned int CppCheck::checkInternal(const FileWithDetails& file, const std::str createDumpFile(mSettings, file, fdump, dumpFile); if (fdump.is_open()) { fdump << getLibraryDumpData(); - fdump << dumpProlog; if (!mSettings.dump) filesDeleter.addFile(dumpFile); } @@ -1129,19 +1182,11 @@ unsigned int CppCheck::checkInternal(const FileWithDetails& file, const std::str if (mSettings.preprocessOnly) { std::string codeWithoutCfg; - Timer::run("Preprocessor::getcode", mSettings.showtime, mTimerResults, [&]() { + Timer::run("Preprocessor::getcode", mTimerResults, [&]() { codeWithoutCfg = preprocessor.getcode(currentConfig, files, true); }); - if (startsWith(codeWithoutCfg,"#file")) - codeWithoutCfg.insert(0U, "//"); std::string::size_type pos = 0; - while ((pos = codeWithoutCfg.find("\n#file",pos)) != std::string::npos) - codeWithoutCfg.insert(pos+1U, "//"); - pos = 0; - while ((pos = codeWithoutCfg.find("\n#endfile",pos)) != std::string::npos) - codeWithoutCfg.insert(pos+1U, "//"); - pos = 0; while ((pos = codeWithoutCfg.find(Preprocessor::macroChar,pos)) != std::string::npos) codeWithoutCfg[pos] = ' '; mErrorLogger.reportOut(codeWithoutCfg, Color::Reset); @@ -1153,7 +1198,7 @@ unsigned int CppCheck::checkInternal(const FileWithDetails& file, const std::str { bool skipCfg = false; // Create tokens, skip rest of iteration if failed - Timer::run("Tokenizer::createTokens", mSettings.showtime, mTimerResults, [&]() { + Timer::run("Tokenizer::createTokens", mTimerResults, [&]() { simplecpp::OutputList outputList_cfg; simplecpp::TokenList tokensP = preprocessor.preprocess(currentConfig, files, outputList_cfg); const simplecpp::Output* o = preprocessor.handleErrors(outputList_cfg); @@ -1179,8 +1224,7 @@ unsigned int CppCheck::checkInternal(const FileWithDetails& file, const std::str Tokenizer tokenizer(std::move(tokenlist), mErrorLogger); try { - if (mSettings.showtime != ShowTime::NONE) - tokenizer.setTimerResults(mTimerResults); + tokenizer.setTimerResults(mTimerResults); tokenizer.setDirectives(directives); // TODO: how to avoid repeated copies? // locations macros @@ -1264,12 +1308,25 @@ unsigned int CppCheck::checkInternal(const FileWithDetails& file, const std::str } // TODO: will not be closed if we encountered an exception - // dumped all configs, close root element now if (fdump.is_open()) { + // dump all filenames, raw tokens, suppressions + std::string dumpHeader = getDumpFileContentsRawTokensHeader(files); + fdump << getDumpFileContentsRawTokens(dumpHeader, dumpFooter); + mSuppressions.nomsg.dump(fdump); + // dumped all configs, close root element now fdump << "" << std::endl; fdump.close(); } + if (!mSettings.plistOutput.empty()) { + mLogger->setPlistFilenames(std::move(files)); + } + + if (analyzerInformation) { + analyzerInformation->writeIncludes(includedFiles); + analyzerInformation->writeHash(calculateHash(preprocessor, file.spath())); + } + executeAddons(dumpFile, file); } catch (const TerminateException &) { // Analysis is terminated @@ -1293,11 +1350,12 @@ unsigned int CppCheck::checkInternal(const FileWithDetails& file, const std::str // TODO: clear earlier? mLogger->clear(); - if (mTimerResults && (mSettings.showtime == ShowTime::FILE || mSettings.showtime == ShowTime::TOP5_FILE)) - mTimerResults->showResults(mSettings.showtime); - - fileTotalTimer.stop(); - checkTimeResults.showResults(mSettings.showtime, false, true); + if (mTimerResults) { + if (mSettings.showtime == Settings::ShowTime::FILE) + mTimerResults->showResults(); + else if (mSettings.showtime == Settings::ShowTime::TOP5_FILE) + mTimerResults->showResults(5); + } return mLogger->exitcode(); } @@ -1337,8 +1395,7 @@ void CppCheck::checkNormalTokens(const Tokenizer &tokenizer, AnalyzerInformation const std::time_t maxTime = mSettings.checksMaxTime > 0 ? std::time(nullptr) + mSettings.checksMaxTime : 0; // call all "runChecks" in all registered Check classes - // cppcheck-suppress shadowFunction - TODO: fix this - for (Check *check : Check::instances()) { + for (Check * const c : CheckInstances::get()) { if (Settings::terminated()) return; @@ -1356,17 +1413,17 @@ void CppCheck::checkNormalTokens(const Tokenizer &tokenizer, AnalyzerInformation return; } - Timer::run(check->name() + "::runChecks", mSettings.showtime, mTimerResults, [&]() { - check->runChecks(tokenizer, &mErrorLogger); + Timer::run(c->name() + "::runChecks", mTimerResults, [&]() { + c->runChecks(tokenizer, mErrorLogger); }); } } if (mSettings.checks.isEnabled(Checks::unusedFunction) && !mSettings.buildDir.empty()) { - unusedFunctionsChecker.parseTokens(tokenizer, mSettings); + unusedFunctionsChecker.parseTokens(tokenizer, mSettings.library); } if (mUnusedFunctionsCheck && mSettings.useSingleJob() && mSettings.buildDir.empty()) { - mUnusedFunctionsCheck->parseTokens(tokenizer, mSettings); + mUnusedFunctionsCheck->parseTokens(tokenizer, mSettings.library); } if (mSettings.clang) { @@ -1377,7 +1434,7 @@ void CppCheck::checkNormalTokens(const Tokenizer &tokenizer, AnalyzerInformation if (mSettings.useSingleJob() || analyzerInformation) { // Analyse the tokens.. { - CTU::FileInfo * const fi1 = CTU::getFileInfo(tokenizer); + const CTU::FileInfo * const fi1 = CTU::getFileInfo(tokenizer); if (analyzerInformation) analyzerInformation->setFileInfo("ctu", fi1->toString()); if (mSettings.useSingleJob()) @@ -1387,11 +1444,10 @@ void CppCheck::checkNormalTokens(const Tokenizer &tokenizer, AnalyzerInformation } if (!doUnusedFunctionOnly) { - // cppcheck-suppress shadowFunction - TODO: fix this - for (const Check *check : Check::instances()) { - if (Check::FileInfo * const fi = check->getFileInfo(tokenizer, mSettings, currentConfig)) { + for (const Check * const c : CheckInstances::get()) { + if (const Check::FileInfo * const fi = c->getFileInfo(tokenizer, mSettings, currentConfig)) { if (analyzerInformation) - analyzerInformation->setFileInfo(check->name(), fi->toString()); + analyzerInformation->setFileInfo(c->name(), fi->toString()); if (mSettings.useSingleJob()) mFileInfo.push_back(fi); else @@ -1415,7 +1471,7 @@ void CppCheck::checkNormalTokens(const Tokenizer &tokenizer, AnalyzerInformation #ifdef HAVE_RULES bool CppCheck::hasRule(const std::string &tokenlist) const { - return std::any_of(mSettings.rules.cbegin(), mSettings.rules.cend(), [&](const Settings::Rule& rule) { + return std::any_of(mSettings.rules.cbegin(), mSettings.rules.cend(), [&](const Rule& rule) { return rule.tokenlist == tokenlist; }); } @@ -1433,7 +1489,7 @@ void CppCheck::executeRules(const std::string &tokenlist, const TokenList &list) str += tok->str(); } - for (const Settings::Rule &rule : mSettings.rules) { + for (const Rule &rule : mSettings.rules) { if (rule.tokenlist != tokenlist) continue; @@ -1491,7 +1547,7 @@ void CppCheck::executeAddons(const std::string& dumpFile, const FileWithDetails& { if (!dumpFile.empty()) { std::vector f{dumpFile}; - Timer::run("CppCheck::executeAddons", mSettings.showtime, mTimerResults, [&]() { + Timer::run("CppCheck::executeAddons", mTimerResults, [&]() { executeAddons(f, file.spath()); }); } @@ -1713,8 +1769,8 @@ void CppCheck::getErrorMessages(ErrorLogger &errorlogger) s.addEnabled("all"); // call all "getErrorMessages" in all registered Check classes - for (auto it = Check::instances().cbegin(); it != Check::instances().cend(); ++it) - (*it)->getErrorMessages(&errorlogger, &s); + for (const Check * const c : CheckInstances::get()) + c->getErrorMessages(errorlogger, s); CheckUnusedFunctions::getErrorMessages(errorlogger); Preprocessor::getErrorMessages(errorlogger, s); @@ -1831,13 +1887,12 @@ bool CppCheck::analyseWholeProgram() } } - // cppcheck-suppress shadowFunction - TODO: fix this - for (Check *check : Check::instances()) - errors |= check->analyseWholeProgram(ctu, mFileInfo, mSettings, mErrorLogger); // TODO: ctu + for (Check * const c : CheckInstances::get()) + errors |= c->analyseWholeProgram(ctu, mFileInfo, mSettings, mErrorLogger); // TODO: ctu } if (mUnusedFunctionsCheck) - errors |= mUnusedFunctionsCheck->check(mSettings, mErrorLogger); + errors |= mUnusedFunctionsCheck->check(mSettings.library, mErrorLogger); return errors && (mLogger->exitcode() > 0); } @@ -1848,14 +1903,14 @@ unsigned int CppCheck::analyseWholeProgram(const std::string &buildDir, const st CheckUnusedFunctions::analyseWholeProgram(mSettings, mErrorLogger, buildDir); if (mUnusedFunctionsCheck) - mUnusedFunctionsCheck->check(mSettings, mErrorLogger); + mUnusedFunctionsCheck->check(mSettings.library, mErrorLogger); if (Settings::unusedFunctionOnly()) return mLogger->exitcode(); executeAddonsWholeProgram(files, fileSettings, ctuInfo); - std::list fileInfoList; + std::list fileInfoList; CTU::FileInfo ctuFileInfo; const auto handler = [&fileInfoList, &ctuFileInfo](const char* checkattr, const tinyxml2::XMLElement* e, const AnalyzerInformation::Info& filesTxtInfo) { @@ -1863,10 +1918,9 @@ unsigned int CppCheck::analyseWholeProgram(const std::string &buildDir, const st ctuFileInfo.loadFromXml(e); return; } - for (const Check *check : Check::instances()) { + for (const Check *check : CheckInstances::get()) { if (checkattr == check->name()) { - if (Check::FileInfo* fi = check->loadFileInfoFromXml(e)) { - fi->file0 = filesTxtInfo.sourceFile; + if (const Check::FileInfo* fi = check->loadFileInfoFromXml(e, filesTxtInfo.sourceFile)) { fileInfoList.push_back(fi); } } @@ -1880,12 +1934,11 @@ unsigned int CppCheck::analyseWholeProgram(const std::string &buildDir, const st } else { // Analyse the tokens - // cppcheck-suppress shadowFunction - TODO: fix this - for (Check *check : Check::instances()) - check->analyseWholeProgram(ctuFileInfo, fileInfoList, mSettings, mErrorLogger); + for (Check * const c : CheckInstances::get()) + c->analyseWholeProgram(ctuFileInfo, fileInfoList, mSettings, mErrorLogger); } - for (Check::FileInfo *fi : fileInfoList) + for (const Check::FileInfo *fi : fileInfoList) delete fi; return mLogger->exitcode(); @@ -1901,9 +1954,26 @@ bool CppCheck::isPremiumCodingStandardId(const std::string& id) const { return false; } -std::string CppCheck::getDumpFileContentsRawTokens(const std::vector& files, const simplecpp::TokenList& tokens1) const { +std::string CppCheck::getDumpFileContentsRawTokens(const std::vector& files, const simplecpp::TokenList& tokens1) const +{ + std::string header = getDumpFileContentsRawTokensHeader(files); + std::string footer = getDumpFileContentsRawTokensFooter(tokens1); + return getDumpFileContentsRawTokens(header, footer); +} + +std::string CppCheck::getDumpFileContentsRawTokens(const std::string& header, const std::string& footer) +{ std::string dumpProlog; dumpProlog += " \n"; + dumpProlog += header; + dumpProlog += footer; + dumpProlog += " \n"; + return dumpProlog; +} + +std::string CppCheck::getDumpFileContentsRawTokensHeader(const std::vector& files) const +{ + std::string dumpProlog; for (unsigned int i = 0; i < files.size(); ++i) { dumpProlog += " \n"; } + return dumpProlog; +} + +std::string CppCheck::getDumpFileContentsRawTokensFooter(const simplecpp::TokenList& tokens1) +{ + std::string dumpProlog; for (const simplecpp::Token *tok = tokens1.cfront(); tok; tok = tok->next) { dumpProlog += " location.line); dumpProlog += "\" "; - dumpProlog +="column=\""; + dumpProlog += "column=\""; dumpProlog += std::to_string(tok->location.col); dumpProlog += "\" "; @@ -1932,6 +2008,5 @@ std::string CppCheck::getDumpFileContentsRawTokens(const std::vector #include +#ifdef HAVE_RULES class TokenList; +#endif struct FileSettings; class CheckUnusedFunctions; class Tokenizer; @@ -151,6 +153,9 @@ class CPPCHECKLIB CppCheck { * @brief Get dumpfile contents, this is only public for testing purposes */ std::string getDumpFileContentsRawTokens(const std::vector& files, const simplecpp::TokenList& tokens1) const; + static std::string getDumpFileContentsRawTokens(const std::string& header, const std::string& footer); + std::string getDumpFileContentsRawTokensHeader(const std::vector& files) const; + static std::string getDumpFileContentsRawTokensFooter(const simplecpp::TokenList& tokens1); std::string getLibraryDumpData() const; @@ -181,7 +186,7 @@ class CPPCHECKLIB CppCheck { */ unsigned int checkFile(const FileWithDetails& file, const std::string &cfgname); - void checkPlistOutput(const FileWithDetails& file, const std::vector& files); + void checkPlistOutput(const FileWithDetails& file); /** * @brief Check a file using buffer @@ -248,7 +253,7 @@ class CPPCHECKLIB CppCheck { bool mUseGlobalSuppressions; /** File info used for whole program analysis */ - std::list mFileInfo; + std::list mFileInfo; /** Callback for executing a shell command (exe, args, output) */ ExecuteCmdFn mExecuteCommand; diff --git a/lib/cppcheck.vcxproj b/lib/cppcheck.vcxproj index b0e52a814fe..6957309e40f 100644 --- a/lib/cppcheck.vcxproj +++ b/lib/cppcheck.vcxproj @@ -32,12 +32,6 @@ - - Create - Create - Create - Create - @@ -50,6 +44,12 @@ + + Create + Create + Create + Create + @@ -57,6 +57,7 @@ + @@ -123,6 +124,7 @@ + @@ -130,6 +132,7 @@ + @@ -163,8 +166,10 @@ + + diff --git a/lib/ctu.cpp b/lib/ctu.cpp index 938bb2a4dcc..feef065d3ac 100644 --- a/lib/ctu.cpp +++ b/lib/ctu.cpp @@ -280,11 +280,11 @@ std::list CTU::loadUnsafeUsageListFromXml(const tiny return ret; } -static int isCallFunction(const Scope *scope, int argnr, const Token *&tok) +static size_t isCallFunction(const Scope *scope, size_t argnr, const Token *&tok) { const Variable * const argvar = scope->function->getArgumentVar(argnr); if (!argvar->isPointer()) - return -1; + return 0; for (const Token *tok2 = scope->bodyStart; tok2 != scope->bodyEnd; tok2 = tok2->next()) { if (tok2->variable() != argvar) continue; @@ -306,11 +306,11 @@ static int isCallFunction(const Scope *scope, int argnr, const Token *&tok) tok = prev->previous(); return argnr2; } - return -1; + return 0; } -CTU::FileInfo *CTU::getFileInfo(const Tokenizer &tokenizer) +const CTU::FileInfo *CTU::getFileInfo(const Tokenizer &tokenizer) { const SymbolDatabase * const symbolDatabase = tokenizer.getSymbolDatabase(); @@ -330,7 +330,7 @@ CTU::FileInfo *CTU::getFileInfo(const Tokenizer &tokenizer) if (!tokFunction) continue; const std::vector args(getArguments(tok->previous())); - for (int argnr = 0; argnr < args.size(); ++argnr) { + for (size_t argnr = 0; argnr < args.size(); ++argnr) { const Token *argtok = args[argnr]; if (!argtok) continue; @@ -427,9 +427,9 @@ CTU::FileInfo *CTU::getFileInfo(const Tokenizer &tokenizer) } // Nested function calls - for (int argnr = 0; argnr < scopeFunction->argCount(); ++argnr) { + for (size_t argnr = 0; argnr < scopeFunction->argCount(); ++argnr) { const Token *tok; - const int argnr2 = isCallFunction(&scope, argnr, tok); + const size_t argnr2 = isCallFunction(&scope, argnr, tok); if (argnr2 > 0) { FileInfo::NestedCall nestedCall(tokenizer, scopeFunction, tok); nestedCall.myArgNr = argnr + 1; @@ -442,7 +442,7 @@ CTU::FileInfo *CTU::getFileInfo(const Tokenizer &tokenizer) return fileInfo; } -static std::vector> getUnsafeFunction(const Settings &settings, const Scope *scope, int argnr, bool (*isUnsafeUsage)(const Settings &settings, const Token *argtok, CTU::FileInfo::Value *value)) +static std::vector> getUnsafeFunction(const Settings &settings, const Scope *scope, size_t argnr, bool (*isUnsafeUsage)(const Settings &settings, const Token *argtok, CTU::FileInfo::Value *value)) { std::vector> ret; const Variable * const argvar = scope->function->getArgumentVar(argnr); @@ -487,7 +487,7 @@ std::list CTU::getUnsafeUsage(const Tokenizer &token const Function *const function = scope.function; // "Unsafe" functions unconditionally reads data before it is written.. - for (int argnr = 0; argnr < function->argCount(); ++argnr) { + for (size_t argnr = 0; argnr < function->argCount(); ++argnr) { for (const std::pair &v : getUnsafeFunction(settings, &scope, argnr, isUnsafeUsage)) { const Token *tok = v.first; const MathLib::bigint val = v.second.value; @@ -500,7 +500,7 @@ std::list CTU::getUnsafeUsage(const Tokenizer &token } static bool findPath(const std::string &callId, - nonneg int callArgNr, + size_t callArgNr, MathLib::bigint unsafeValue, CTU::FileInfo::InvalidValueType invalidValue, const std::map> &callsMap, diff --git a/lib/ctu.h b/lib/ctu.h index 4f5e0b0294c..7eea7e76cc1 100644 --- a/lib/ctu.h +++ b/lib/ctu.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -52,6 +52,8 @@ namespace tinyxml2 { namespace CTU { class CPPCHECKLIB FileInfo : public Check::FileInfo { public: + FileInfo() : Check::FileInfo("") {} + enum class InvalidValueType : std::uint8_t { null, uninit, bufferOverflow }; std::string toString() const override; @@ -77,14 +79,14 @@ namespace CTU { struct UnsafeUsage { UnsafeUsage() = default; - UnsafeUsage(std::string myId, nonneg int myArgNr, std::string myArgumentName, Location location, MathLib::bigint value) + UnsafeUsage(std::string myId, size_t myArgNr, std::string myArgumentName, Location location, MathLib::bigint value) : myId(std::move(myId)) , myArgNr(myArgNr) , myArgumentName(std::move(myArgumentName)) , location(std::move(location)) , value(value) {} std::string myId; - nonneg int myArgNr{}; + size_t myArgNr{}; std::string myArgumentName; Location location; MathLib::bigint value{}; @@ -94,14 +96,14 @@ namespace CTU { class CallBase { public: CallBase() = default; - CallBase(std::string callId, int callArgNr, std::string callFunctionName, Location loc) + CallBase(std::string callId, size_t callArgNr, std::string callFunctionName, Location loc) : callId(std::move(callId)), callArgNr(callArgNr), callFunctionName(std::move(callFunctionName)), location(std::move(loc)) {} CallBase(const Tokenizer &tokenizer, const Token *callToken); virtual ~CallBase() = default; CallBase(const CallBase&) = default; std::string callId; - int callArgNr{}; + size_t callArgNr{}; std::string callFunctionName; Location location; protected: @@ -136,7 +138,7 @@ namespace CTU { bool loadFromXml(const tinyxml2::XMLElement *xmlElement); std::string myId; - nonneg int myArgNr{}; + size_t myArgNr{}; }; std::list functionCalls; @@ -160,7 +162,7 @@ namespace CTU { CPPCHECKLIB std::string getFunctionId(const Tokenizer &tokenizer, const Function *function); /** @brief Parse current TU and extract file info */ - CPPCHECKLIB RET_NONNULL FileInfo *getFileInfo(const Tokenizer &tokenizer); + CPPCHECKLIB RET_NONNULL const FileInfo *getFileInfo(const Tokenizer &tokenizer); CPPCHECKLIB std::list getUnsafeUsage(const Tokenizer &tokenizer, const Settings &settings, bool (*isUnsafeUsage)(const Settings &settings, const Token *argtok, FileInfo::Value *value)); diff --git a/lib/errorlogger.cpp b/lib/errorlogger.cpp index 3b05a7d5a8f..30097a4f21c 100644 --- a/lib/errorlogger.cpp +++ b/lib/errorlogger.cpp @@ -23,6 +23,7 @@ #include "path.h" #include "settings.h" #include "suppressions.h" +#include "symboldatabase.h" #include "token.h" #include "tokenlist.h" #include "utils.h" @@ -35,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -45,6 +47,7 @@ const std::set ErrorLogger::mCriticalErrorIds{ "cppcheckError", "cppcheckLimit", + "directiveAsMacroParameter", "includeNestedTooDeeply", "internalAstError", "instantiationError", @@ -59,10 +62,21 @@ const std::set ErrorLogger::mCriticalErrorIds{ "unknownMacro" }; +static std::size_t sdbm(const std::string& hashString) { + return std::accumulate(hashString.cbegin(), hashString.cend(), std::size_t{0}, [](std::size_t h, unsigned char c) { + return static_cast(c) + (h << 6) + (h << 16) - h; + }); +} + ErrorMessage::ErrorMessage() : severity(Severity::none), cwe(0U), certainty(Certainty::normal) {} +static bool needsFallbackHash(const std::string &id) +{ + return startsWith(id, "ctu") || id == "unusedFunction" || id == "staticFunction"; +} + // TODO: id and msg are swapped compared to other calls ErrorMessage::ErrorMessage(std::list callStack, std::string file1, Severity severity, const std::string &msg, std::string id, Certainty certainty) : callStack(std::move(callStack)), // locations for this error message @@ -74,6 +88,9 @@ ErrorMessage::ErrorMessage(std::list callStack, std::string file1, { // set the summary and verbose messages setmsg(msg); + + if (hash == 0 && needsFallbackHash(this->id)) + calculateWarningHashFromLocations(); } @@ -88,6 +105,9 @@ ErrorMessage::ErrorMessage(std::list callStack, std::string file1, { // set the summary and verbose messages setmsg(msg); + + if (hash == 0 && needsFallbackHash(this->id)) + calculateWarningHashFromLocations(); } ErrorMessage::ErrorMessage(const std::list& callstack, const TokenList* list, Severity severity, std::string id, const std::string& msg, Certainty certainty) @@ -106,6 +126,8 @@ ErrorMessage::ErrorMessage(const std::list& callstack, const Token file0 = list->getFiles()[0]; setmsg(msg); + + calculateWarningHash(callstack); } @@ -126,7 +148,7 @@ ErrorMessage::ErrorMessage(const std::list& callstack, const Token setmsg(msg); - // hash = calculateWarningHash(list, hashWarning.str()); + calculateWarningHash(callstack); } ErrorMessage::ErrorMessage(ErrorPath errorPath, const TokenList *tokenList, Severity severity, const char id[], const std::string &msg, const CWE &cwe, Certainty certainty) @@ -159,7 +181,12 @@ ErrorMessage::ErrorMessage(ErrorPath errorPath, const TokenList *tokenList, Seve setmsg(msg); - // hash = calculateWarningHash(tokenList, hashWarning.str()); + std::list tokens; + std::transform(errorPath.cbegin(), errorPath.cend(), std::back_inserter(tokens), + [](const ErrorPathItem& e) { + return e.first; + }); + calculateWarningHash(tokens); } // TODO: improve errorhandling? @@ -244,6 +271,74 @@ void ErrorMessage::setmsg(const std::string &msg) } } +void ErrorMessage::calculateWarningHash(const std::list& callstack) +{ + if (callstack.empty()) + return; + // Calculate a hash for this warning message + std::string hashString; + for (const Token* tok: callstack) { + if (!tok) + continue; + if (!tok->scope()) + return; // might be a syntax error before scope info has been set + if (tok->scope()->isExecutable()) { + // Executable scope => include all tokens in the function => if the + // function is changed the hash is changed + for (const Token* t = tok; t; t = t->previous()) { + if (!t->scope()->isExecutable()) + break; + hashString += " " + t->str(); + } + for (const Token* t = tok->next(); t; t = t->next()) { + if (!t->scope()->isExecutable()) + break; + hashString += " " + t->str(); + } + } else { + // Non executable scope => include tokens in current statement => if the current statement is changed the hash is changed + for (const Token* t = tok; t; t = t->previous()) { + if (t->str() == ";") + break; + if (t->scope() != tok->scope()) // stop on {} unless its an initializer + break; + hashString += " " + t->str(); + } + for (const Token* t = tok->next(); t; t = t->next()) { + hashString += " " + t->str(); + if (t->str() == ";") + break; + if (t->scope() != tok->scope()) // stop on {} unless its an initializer + break; + } + } + } + + hashString = id + '\n' + mShortMessage + '\n' + hashString; + + // hash algorithm: sdbm + // any hash algorithm can be used but it has to be the same hash on different platforms and compilers + hash = sdbm(hashString); +} + +void ErrorMessage::calculateWarningHashFromLocations() +{ + // No token information is available for this warning (e.g. whole-program/CTU + // checks, unusedFunction, staticFunction) so calculateWarningHash() can't be + // used. Instead hash the id, message and all filenames/notes in the callstack. + std::string hashString = id + '\n' + mShortMessage; + for (const FileLocation &loc : callStack) { + std::string fileName = loc.getfile(false); + if (Path::isAbsolute(fileName)) + fileName = fileName.substr(fileName.rfind('/') + 1); + hashString += '\n' + fileName + '\n' + loc.getinfo(); + } + + // hash algorithm: sdbm + // any hash algorithm can be used but it has to be the same hash on different platforms and compilers + hash = sdbm(hashString); +} + static void serializeString(std::string &oss, const std::string & str) { oss += std::to_string(str.length()); @@ -635,7 +730,7 @@ static void replaceColors(std::string& source, bool erase) { replace(source, substitutionMapErase); } -std::string ErrorMessage::toString(bool verbose, const std::string &templateFormat, const std::string &templateLocation) const +std::string ErrorMessage::toString(bool verbose, const std::string &templateFormat, const std::string &templateLocation, bool noCode) const { assert(!templateFormat.empty()); @@ -676,7 +771,8 @@ std::string ErrorMessage::toString(bool verbose, const std::string &templateForm endl = "\r\n"; else endl = "\r"; - findAndReplace(result, "{code}", readCode(callStack.back().getOrigFile(), callStack.back().line, callStack.back().column, endl)); + const std::string code = noCode ? "" : readCode(callStack.back().getOrigFile(), callStack.back().line, callStack.back().column, endl); + findAndReplace(result, "{code}", code); } } else { static const std::unordered_map callStackSubstitutionMap = @@ -707,7 +803,8 @@ std::string ErrorMessage::toString(bool verbose, const std::string &templateForm endl = "\r\n"; else endl = "\r"; - findAndReplace(text, "{code}", readCode(fileLocation.getOrigFile(), fileLocation.line, fileLocation.column, endl)); + const std::string code = noCode ? "" : readCode(fileLocation.getOrigFile(), fileLocation.line, fileLocation.column, endl); + findAndReplace(text, "{code}", code); } result += '\n' + text; } @@ -821,7 +918,7 @@ std::string ErrorLogger::toxml(const std::string &str) return xml; } -std::string ErrorLogger::plistHeader(const std::string &version, const std::vector &files) +std::string ErrorLogger::plistHeader(const std::string &version) { std::ostringstream ostr; ostr << "\r\n" @@ -829,12 +926,7 @@ std::string ErrorLogger::plistHeader(const std::string &version, const std::vect << "\r\n" << "\r\n" << " clang_version\r\n" - << "cppcheck version " << version << "\r\n" - << " files\r\n" - << " \r\n"; - for (const std::string & file : files) - ostr << " " << ErrorLogger::toxml(file) << "\r\n"; - ostr << " \r\n" + << " cppcheck version " << version << "\r\n" << " diagnostics\r\n" << " \r\n"; return ostr.str(); diff --git a/lib/errorlogger.h b/lib/errorlogger.h index d739cb3fbf9..fa5ad67c7c1 100644 --- a/lib/errorlogger.h +++ b/lib/errorlogger.h @@ -23,11 +23,13 @@ #include "config.h" #include "errortypes.h" +#include "refthunk.h" #include #include #include #include +#include #include #include #include @@ -88,7 +90,7 @@ class CPPCHECKLIB ErrorMessage { std::string stringify(bool addcolumn = false) const; unsigned int fileIndex; - int line; // negative value means "no line" + int line; // negative value means "no line" - TODO: actually 0 means no line - lines from simplecpp are unsigned unsigned int column; const std::string& getinfo() const { @@ -151,11 +153,13 @@ class CPPCHECKLIB ErrorMessage { * or template to be used. E.g. "{file}:{line},{severity},{id},{message}" * @param templateLocation Format Empty string to use default output format * or template to be used. E.g. "{file}:{line},{info}" + * @param noCode Always replace {code} with an empty string * @return formatted string */ std::string toString(bool verbose, const std::string &templateFormat, - const std::string &templateLocation) const; + const std::string &templateLocation, + bool noCode = false) const; std::string serialize() const; /** @@ -208,6 +212,15 @@ class CPPCHECKLIB ErrorMessage { private: static std::string fixInvalidChars(const std::string& raw); + void calculateWarningHash(const std::list& callstack); + + /** + * Fallback hash calculation for warnings that have no token information + * (e.g. whole-program/CTU checks, unusedFunction, staticFunction). Hashes + * the id, message and all filenames/notes in the callstack instead. + */ + void calculateWarningHashFromLocations(); + /** Short message */ std::string mShortMessage; @@ -271,12 +284,19 @@ class CPPCHECKLIB ErrorLogger { */ static std::string toxml(const std::string &str); - static std::string plistHeader(const std::string &version, const std::vector &files); + static std::string plistHeader(const std::string &version); static std::string plistData(const ErrorMessage &msg); - static const char *plistFooter() { - return " \r\n" - "\r\n" - ""; + static std::string plistFooter(const std::vector& files) { + std::ostringstream ostr; + ostr << " \r\n" + << " files\r\n" + << " \r\n"; + for (const std::string& file : files) + ostr << " " << ErrorLogger::toxml(file) << "\r\n"; + ostr << " \r\n" + << "\r\n" + << ""; + return ostr.str(); } static bool isCriticalErrorId(const std::string& id) { @@ -301,7 +321,7 @@ class CPPCHECKLIB ProgressReporter { ~ProgressReporter() { if (mReportProgressInterval < 0) return; - mErrorLogger.reportProgress(mFilename, mStage.c_str(), 100); + mErrorLogger().reportProgress(mFilename, mStage.c_str(), 100); } void report(int value) { @@ -309,17 +329,17 @@ class CPPCHECKLIB ProgressReporter { return; const std::time_t t = std::time(nullptr); if (t >= mLastTime + mReportProgressInterval) { - mErrorLogger.reportProgress(mFilename, mStage.c_str(), value); + mErrorLogger().reportProgress(mFilename, mStage.c_str(), value); mLastTime = t; mLastValue = value; } } private: - ErrorLogger& mErrorLogger; - const int mReportProgressInterval; - const std::string mFilename; - const std::string mStage; + RefThunk mErrorLogger; + int mReportProgressInterval; + std::string mFilename; + std::string mStage; std::time_t mLastTime{0}; int mLastValue{-1}; }; diff --git a/lib/filesettings.h b/lib/filesettings.h index e9f883d5a87..0ef53db85c5 100644 --- a/lib/filesettings.h +++ b/lib/filesettings.h @@ -46,9 +46,9 @@ class FileWithDetails throw std::runtime_error("empty path specified"); } - void setPath(std::string path) + void setPath(std::string p) { - mPath = std::move(path); + mPath = std::move(p); mPathSimplified = Path::simplifyPath(mPath); mPathAbsolute.clear(); } @@ -76,9 +76,9 @@ class FileWithDetails return mSize; } - void setLang(Standards::Language lang) + void setLang(Standards::Language language) { - mLang = lang; + mLang = language; } Standards::Language lang() const @@ -91,9 +91,9 @@ class FileWithDetails return mFsFileId; } - void setFsFileId(std::size_t fsFileId) + void setFsFileId(std::size_t id) { - mFsFileId = fsFileId; + mFsFileId = id; } private: std::string mPath; @@ -148,6 +148,7 @@ struct CPPCHECKLIB FileSettings { } std::set undefs; std::list includePaths; + std::list forcedIncludes; // only used by clang mode std::list systemIncludePaths; std::string standard; diff --git a/lib/forwardanalyzer.cpp b/lib/forwardanalyzer.cpp index 5c97528372b..88cee242c8c 100644 --- a/lib/forwardanalyzer.cpp +++ b/lib/forwardanalyzer.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,6 +24,7 @@ #include "errorlogger.h" #include "errortypes.h" #include "mathlib.h" +#include "refthunk.h" #include "settings.h" #include "symboldatabase.h" #include "token.h" @@ -50,15 +51,19 @@ namespace { : analyzer(analyzer), tokenList(tokenList), errorLogger(errorLogger), settings(settings) {} ValuePtr analyzer; - const TokenList& tokenList; - ErrorLogger& errorLogger; - const Settings& settings; + RefThunk tokenList; + RefThunk errorLogger; + RefThunk settings; Analyzer::Action actions; bool analyzeOnly{}; bool analyzeTerminate{}; Analyzer::Terminate terminate = Analyzer::Terminate::None; std::vector loopEnds; int branchCount = 0; + // Nested condition-fork depth on this lineage (copied by fork()); bounds the fan-out. + int forkDepth = 0; + // Total forks of the traversal (shared via the fork() copy); backstop past the depth bound. + std::shared_ptr forkBudget = std::make_shared(0); Progress Break(Analyzer::Terminate t = Analyzer::Terminate::None) { if ((!analyzeOnly || analyzeTerminate) && t != Analyzer::Terminate::None) @@ -73,7 +78,6 @@ namespace { bool check = false; bool escape = false; bool escapeUnknown = false; - bool active = false; bool isEscape() const { return escape || escapeUnknown; } @@ -89,6 +93,9 @@ namespace { bool isDead() const { return action.isModified() || action.isInconclusive() || isEscape(); } + bool hasGoto() const { + return endBlock ? ForwardTraversal::hasGoto(endBlock) : false; + } }; bool stopUpdates() { @@ -138,11 +145,11 @@ namespace { // If we are in a loop then jump to the end if (out) *out = loopEnds.back(); - } else if (Token::Match(tok, "return|throw")) { + } else if (isEscapeKeyword(tok, settings)) { traverseRecursive(tok->astOperand2(), f, traverseUnknown); traverseRecursive(tok->astOperand1(), f, traverseUnknown); return Break(Analyzer::Terminate::Escape); - } else if (Token::Match(tok, "%name% (") && isEscapeFunction(tok, settings.library)) { + } else if (Token::Match(tok, "%name% (") && isEscapeFunction(tok, settings().library)) { // Traverse the parameters of the function before escaping traverseRecursive(tok->next()->astOperand2(), f, traverseUnknown); return Break(Analyzer::Terminate::Escape); @@ -348,21 +355,9 @@ namespace { return Token::findmatch(endBlock->link(), "goto|break", endBlock); } - bool hasInnerReturnScope(const Token* start, const Token* end) const { - for (const Token* tok=start; tok != end; tok = tok->previous()) { - if (Token::simpleMatch(tok, "}")) { - const Token* ftok = nullptr; - const bool r = isReturnScope(tok, settings.library, &ftok); - if (r) - return true; - } - } - return false; - } - bool isEscapeScope(const Token* endBlock, bool& unknown) const { const Token* ftok = nullptr; - const bool r = isReturnScope(endBlock, settings.library, &ftok); + const bool r = isReturnScope(endBlock, settings().library, &ftok); if (!r && ftok) unknown = true; return r; @@ -383,30 +378,60 @@ namespace { return a; } - bool checkBranch(Branch& branch) const { - Analyzer::Action a = analyzeScope(branch.endBlock); - branch.action = a; - std::vector ft1 = tryForkUpdateScope(branch.endBlock, a.isModified()); - const bool bail = hasGoto(branch.endBlock); - if (!a.isModified() && !bail) { - if (ft1.empty()) { - // Traverse into the branch to see if there is a conditional escape - if (!branch.escape && hasInnerReturnScope(branch.endBlock->previous(), branch.endBlock->link())) { - ForwardTraversal ft2 = fork(true); - ft2.updateScope(branch.endBlock); - if (ft2.terminate == Analyzer::Terminate::Escape) { - branch.escape = true; - branch.escapeUnknown = false; - } - } - } else { - if (ft1.front().terminate == Analyzer::Terminate::Escape) { - branch.escape = true; - branch.escapeUnknown = false; - } + Progress updateBranch(Branch& branch, int depth) + { + // Save and reset actions + Analyzer::Action prevActions = actions; + actions = Analyzer::Action::None; + Progress p = updateRange(branch.endBlock->link(), branch.endBlock, depth); + branch.action |= actions; + // Restore actions + actions |= prevActions; + + if (terminate == Analyzer::Terminate::Escape) { + branch.escape = true; + // The traversal followed an escaping path, but if the scope does not structurally + // always escape then another path (e.g. a modified fork) falls through, so the escape + // is only conditional - keep isModified() meaningful by not treating it as conclusive. + bool structuralUnknown = false; + const bool structuralEscape = isEscapeScope(branch.endBlock, structuralUnknown); + branch.escapeUnknown = !structuralEscape || structuralUnknown; + // The traversal stopped at the escape, so the rest of the scope was not walked; a + // fall-through path could still modify the value there - include the whole scope's + // actions so isModified() sees it. + if (branch.escapeUnknown) + branch.action |= analyzeScope(branch.endBlock); + } else { + // Detect an escape the traversal did not flag (e.g. an unknown noreturn call); + // escapeUnknown reports a possible (unknown) escape. + branch.escape = isEscapeScope(branch.endBlock, branch.escapeUnknown); + if (terminate != Analyzer::Terminate::None && terminate != Analyzer::Terminate::Modified) { + branch.action |= analyzeScope(branch.endBlock); } } - return bail; + + return p; + } + + // Update the branch that the evaluated condition takes + Progress updateTakenBranch(Branch& branch, const Token* skippedBlock, const Token* condTok, int depth) + { + // The condition is only "known" because of an earlier assumption, so the + // skipped block could still modify the value -> lower to possible + if (!condTok->hasKnownIntValue() && skippedBlock && analyzeScope(skippedBlock).isModified() && + !analyzer->lowerToPossible()) + return Break(Analyzer::Terminate::Bail); + if (!branch.endBlock) + return Progress::Continue; + updateScopeState(branch.endBlock); + if (updateBranch(branch, depth - 1) == Progress::Break) + return Progress::Break; + // The branch was entered because of the tracked value; if it might not + // return (it ends in a call to an unknown, possibly noreturn function) + // then the value might not flow past the branch. + if (!condTok->hasKnownIntValue() && !branch.escape && branch.escapeUnknown && !analyzer->lowerToInconclusive()) + return Break(Analyzer::Terminate::Bail); + return Progress::Continue; } bool reentersLoop(Token* endBlock, const Token* condTok, const Token* stepTok) const { @@ -529,14 +554,12 @@ namespace { forkContinue = false; } - if (allAnalysis.isModified() || !forkContinue) { - // TODO: Don't bail on missing condition - if (!condTok) - return Break(Analyzer::Terminate::Bail); - if (analyzer->isConditional() && stopUpdates()) - return Break(Analyzer::Terminate::Conditional); - analyzer->assume(condTok, false); - } + // TODO: Don't bail on missing condition + if (!condTok) + return Break(Analyzer::Terminate::Bail); + if (analyzer->isConditional() && stopUpdates()) + return Break(Analyzer::Terminate::Conditional); + analyzer->assume(condTok, false); if (forkContinue) { for (ForwardTraversal& ft : ftv) { if (!ft.actions.isIncremental()) @@ -562,16 +585,21 @@ namespace { return updateLoop(endToken, endBlock, condTok, initTok, stepTok, true); } - Progress updateScope(Token* endBlock, int depth = 20) + void updateScopeState(const Token* endBlock) { - if (!endBlock) - return Break(); assert(endBlock->link()); - Token* ctx = endBlock->link()->previous(); + const Token* ctx = endBlock->link()->previous(); if (Token::simpleMatch(ctx, ")")) ctx = ctx->link()->previous(); if (ctx) analyzer->updateState(ctx); + } + + Progress updateScope(Token* endBlock, int depth = 20) + { + if (!endBlock) + return Break(); + updateScopeState(endBlock); return updateRange(endBlock->link(), endBlock, depth); } @@ -646,11 +674,20 @@ namespace { const bool inElse = scope->type == ScopeType::eElse; const bool inDoWhile = scope->type == ScopeType::eDo; const bool inLoop = contains({ScopeType::eDo, ScopeType::eFor, ScopeType::eWhile}, scope->type); + const bool hasElse = Token::simpleMatch(tok, "} else {"); Token* condTok = getCondTokFromEnd(tok); if (!condTok) return Break(); + // When the 'else' branch escapes (e.g. returns), control can only continue + // here via the 'then' branch, so the value established there is still + // definite - keep it known instead of lowering to possible. + bool elseEscape = false; + if (!inLoop && !inElse && hasElse) { + bool unknownEscape = false; + elseEscape = isEscapeScope(tok->linkAt(2), unknownEscape); + } if (!condTok->hasKnownIntValue() || inLoop) { - if (!analyzer->lowerToPossible()) + if (!elseEscape && !analyzer->lowerToPossible()) return Break(Analyzer::Terminate::Bail); } else if (condTok->getKnownIntValue() == inElse) { return Break(); @@ -673,9 +710,9 @@ namespace { return Break(); } } - analyzer->assume(condTok, !inElse, Analyzer::Assume::Quiet); + analyzer->assume(condTok, !inElse); assert(!inDoWhile || Token::simpleMatch(tok, "} while (")); - if (Token::simpleMatch(tok, "} else {") || inDoWhile) + if (hasElse || inDoWhile) tok = tok->linkAt(2); } else if (contains({ScopeType::eTry, ScopeType::eCatch}, scope->type)) { if (!analyzer->lowerToPossible()) @@ -685,9 +722,9 @@ namespace { } } else if (tok->isControlFlowKeyword() && Token::Match(tok, "if|while|for (") && Token::simpleMatch(tok->linkAt(1), ") {")) { - if ((settings.vfOptions.maxForwardBranches > 0) && (++branchCount > settings.vfOptions.maxForwardBranches)) { + if ((settings().vfOptions.maxForwardBranches > 0) && (++branchCount > settings().vfOptions.maxForwardBranches)) { // TODO: should be logged on function-level instead of file-level - if (settings.severity.isEnabled(Severity::information)) { + if (settings().severity.isEnabled(Severity::information)) { reportError(Severity::information, "normalCheckLevelMaxBranches", "Limiting analysis of branches. Use --check-level=exhaustive to analyze all branches."); } return Break(Analyzer::Terminate::Bail); @@ -731,71 +768,80 @@ namespace { if (!thenBranch.check && !elseBranch.check && stopOnCondition(condTok) && stopUpdates()) return Break(Analyzer::Terminate::Conditional); const bool hasElse = Token::simpleMatch(endBlock, "} else {"); - bool bail = false; - - // Traverse then block - thenBranch.escape = isEscapeScope(endBlock, thenBranch.escapeUnknown); + tok = hasElse ? endBlock->linkAt(2) : endBlock; if (thenBranch.check) { - thenBranch.active = true; - if (updateScope(endBlock, depth - 1) == Progress::Break) + if (updateTakenBranch(thenBranch, hasElse ? elseBranch.endBlock : nullptr, condTok, depth) == + Progress::Break) + return Break(); + } else if (elseBranch.check) { + if (updateTakenBranch(elseBranch, thenBranch.endBlock, condTok, depth) == Progress::Break) return Break(); - } else if (!elseBranch.check) { - thenBranch.active = true; - if (checkBranch(thenBranch)) - bail = true; - } - // Traverse else block - if (hasElse) { - elseBranch.escape = isEscapeScope(endBlock->linkAt(2), elseBranch.escapeUnknown); - if (elseBranch.check) { - elseBranch.active = true; - const Progress result = updateScope(endBlock->linkAt(2), depth - 1); - if (result == Progress::Break) - return Break(); - } else if (!thenBranch.check) { - elseBranch.active = true; - if (checkBranch(elseBranch)) - bail = true; - } - tok = endBlock->linkAt(2); } else { - tok = endBlock; - } - if (thenBranch.active) + const bool conditional = stopOnCondition(condTok); + // The value only flows into the then-branch when the condition can split + // it; for an opaque or correlated condition (e.g. 'if (f(x))') it does + // not, so fork in analyze-only mode: the branch's effect is still tracked + // but nothing is reported in it. + ForwardTraversal ft = fork(!analyzer->updateScope(thenBranch.endBlock, false)); + // The branch is traversed below, so don't record its boundary state here. + ft.analyzer->assume(condTok, true, Analyzer::Assume::Pending); + Progress pThen = ft.updateBranch(thenBranch, depth - 1); + // Merge the fork's actions so a modification in the then-branch bubbles up + // to the enclosing branch's isModified(). actions |= thenBranch.action; - if (elseBranch.active) - actions |= elseBranch.action; - if (bail) - return Break(Analyzer::Terminate::Bail); - if (thenBranch.isDead() && elseBranch.isDead()) { - if (thenBranch.isModified() && elseBranch.isModified()) - return Break(Analyzer::Terminate::Modified); - if (thenBranch.isConclusiveEscape() && elseBranch.isConclusiveEscape()) - return Break(Analyzer::Terminate::Escape); - return Break(Analyzer::Terminate::Bail); - } - // Conditional return - if (thenBranch.active && thenBranch.isEscape() && !hasElse) { - if (!thenBranch.isConclusiveEscape()) { - if (!analyzer->lowerToInconclusive()) - return Break(Analyzer::Terminate::Bail); - } else if (thenBranch.check) { - return Break(); - } else { - if (analyzer->isConditional() && stopUpdates()) - return Break(Analyzer::Terminate::Conditional); - analyzer->assume(condTok, false); + + // Commit the condition as false on the main path only when the then-branch + // is dead. The else block, if any, is traversed separately (Pending); with + // no else the false path continues past the closing brace, so record the + // assumed state there (None). + if (thenBranch.isDead()) + analyzer->assume(condTok, + false, + hasElse ? Analyzer::Assume::Pending : Analyzer::Assume::None); + // The else block is traversed on the main path. If it kills the value + // (modified) the main path stops, but the then-fork may still carry the + // value forward, so defer the break until after the fork continues. + Progress pElse = Progress::Continue; + if (hasElse) + pElse = updateBranch(elseBranch, depth - 1); + if (thenBranch.isDead() || elseBranch.isDead()) { + if (conditional && stopUpdates()) + pElse = Break(Analyzer::Terminate::Conditional); } - } - if (thenBranch.isInconclusive() || elseBranch.isInconclusive()) { - if (!analyzer->lowerToInconclusive()) - return Break(Analyzer::Terminate::Bail); - } else if (thenBranch.isModified() || elseBranch.isModified()) { - if (!hasElse && analyzer->isConditional() && stopUpdates()) - return Break(Analyzer::Terminate::Conditional); - if (!analyzer->lowerToPossible()) + if (thenBranch.isModified() || elseBranch.isModified()) { + if (!ft.analyzer->lowerToPossible()) + pThen = Progress::Break; + if (pElse != Progress::Break && !analyzer->lowerToPossible()) + pElse = Break(Analyzer::Terminate::Bail); + } + if (thenBranch.isInconclusive() || elseBranch.isInconclusive()) { + if (!ft.analyzer->lowerToInconclusive()) + pThen = Progress::Break; + if (pElse != Progress::Break && !analyzer->lowerToInconclusive()) + pElse = Break(Analyzer::Terminate::Bail); + } + if (thenBranch.hasGoto() || elseBranch.hasGoto()) { return Break(Analyzer::Terminate::Bail); - analyzer->assume(condTok, elseBranch.isModified()); + } + // Carry the then-fork forward, unless a limit is hit - then only the linear main + // path continues (no bail). forkDepth bounds nesting, forkBudget total. <0 = off. + assert(forkBudget != nullptr); + const int forkDepthLimit = settings().vfOptions.maxForwardConditionForkDepth; + const int forkBudgetLimit = settings().vfOptions.maxForwardConditionForks; + const bool depthOk = forkDepthLimit < 0 || forkDepth < forkDepthLimit; + const bool budgetOk = forkBudgetLimit < 0 || *forkBudget < forkBudgetLimit; + if (pThen != Progress::Break && !thenBranch.isEscape() && depthOk && budgetOk) { + ++(*forkBudget); + ++ft.forkDepth; + ft.updateRange(thenBranch.endBlock, end, depth - 1); + } + if (pElse == Progress::Break) { + // Only the else branch escaped; the then branch falls through, so + // the scope as a whole does not always escape. + if (terminate == Analyzer::Terminate::Escape && !thenBranch.isEscape()) + terminate = Analyzer::Terminate::None; + return Break(); + } } } } else if (Token::simpleMatch(tok, "try {")) { @@ -845,7 +891,7 @@ namespace { return Break(); } else if (Token* callTok = callExpr(tok)) { // TODO: Dont traverse tokens a second time - if (start != callTok && tok != callTok && updateRecursive(callTok->astOperand1()) == Progress::Break) + if (start != callTok && tok != callTok && (tok->str() != "." || tok != callTok->astOperand1()) && updateRecursive(callTok->astOperand1()) == Progress::Break) return Break(); // Since the call could be an unknown macro, traverse the tokens as a range instead of recursively if (!Token::simpleMatch(callTok, "( )") && @@ -873,10 +919,10 @@ namespace { return Progress::Continue; } - void reportError(Severity severity, const std::string& id, const std::string& msg) { - ErrorMessage::FileLocation loc(tokenList.getSourceFilePath(), 0, 0); - const ErrorMessage errmsg({std::move(loc)}, tokenList.getSourceFilePath(), severity, msg, id, Certainty::normal); - errorLogger.reportErr(errmsg); + void reportError(Severity severity, const std::string& id, const std::string& msg) const { + ErrorMessage::FileLocation loc(tokenList().getSourceFilePath(), 0, 0); + const ErrorMessage errmsg({std::move(loc)}, tokenList().getSourceFilePath(), severity, msg, id, Certainty::normal); + errorLogger().reportErr(errmsg); } static bool isFunctionCall(const Token* tok) diff --git a/lib/fwdanalysis.cpp b/lib/fwdanalysis.cpp index c45213a4be9..7b7987e2e91 100644 --- a/lib/fwdanalysis.cpp +++ b/lib/fwdanalysis.cpp @@ -30,6 +30,7 @@ #include #include #include +#include static bool isUnchanged(const Token *startToken, const Token *endToken, const std::set &exprVarIds, bool local) { @@ -94,7 +95,7 @@ static bool hasVolatileCastOrVar(const Token *expr) return ret; } -FwdAnalysis::Result FwdAnalysis::checkRecursive(const Token *expr, const Token *startToken, const Token *endToken, const std::set &exprVarIds, bool local, bool inInnerClass, int depth) +FwdAnalysis::Result FwdAnalysis::checkRecursive(const Token *expr, const Token *startToken, const Token *endToken, const std::set &exprVarIds, bool local, bool inInnerClass, int depth) const { // Parse the given tokens if (++depth > 1000) @@ -155,10 +156,6 @@ FwdAnalysis::Result FwdAnalysis::checkRecursive(const Token *expr, const Token * } if (tok->str() == "}") { - // Known value => possible value - if (tok->scope() == expr->scope()) - mValueFlowKnown = false; - if (tok->scope()->isLoopScope()) { // check condition const Token *conditionStart = nullptr; @@ -195,65 +192,6 @@ FwdAnalysis::Result FwdAnalysis::checkRecursive(const Token *expr, const Token * if (Token::simpleMatch(tok, "asm (")) return Result(Result::Type::BAILOUT); - if (mWhat == What::ValueFlow && (Token::Match(tok, "while|for (") || Token::simpleMatch(tok, "do {"))) { - const Token *bodyStart = nullptr; - const Token *conditionStart = nullptr; - if (Token::simpleMatch(tok, "do {")) { - bodyStart = tok->next(); - if (Token::simpleMatch(bodyStart->link(), "} while (")) - conditionStart = bodyStart->link()->tokAt(2); - } else { - conditionStart = tok->next(); - if (Token::simpleMatch(conditionStart->link(), ") {")) - bodyStart = conditionStart->link()->next(); - } - - if (!bodyStart || !conditionStart) - return Result(Result::Type::BAILOUT); - - // Is expr changed in condition? - if (!isUnchanged(conditionStart, conditionStart->link(), exprVarIds, local)) - return Result(Result::Type::BAILOUT); - - // Is expr changed in loop body? - if (!isUnchanged(bodyStart, bodyStart->link(), exprVarIds, local)) - return Result(Result::Type::BAILOUT); - } - - if (mWhat == What::ValueFlow && Token::simpleMatch(tok, "if (") && Token::simpleMatch(tok->linkAt(1), ") {")) { - const Token *bodyStart = tok->linkAt(1)->next(); - const Token *conditionStart = tok->next(); - const Token *condTok = conditionStart->astOperand2(); - if (const ValueFlow::Value* v = condTok->getKnownValue(ValueFlow::Value::ValueType::INT)) { - const bool cond = !!v->intvalue; - if (cond) { - FwdAnalysis::Result result = checkRecursive(expr, bodyStart, bodyStart->link(), exprVarIds, local, true, depth); - if (result.type != Result::Type::NONE) - return result; - } else if (Token::simpleMatch(bodyStart->link(), "} else {")) { - bodyStart = bodyStart->link()->tokAt(2); - FwdAnalysis::Result result = checkRecursive(expr, bodyStart, bodyStart->link(), exprVarIds, local, true, depth); - if (result.type != Result::Type::NONE) - return result; - } - } - tok = bodyStart->link(); - if (isReturnScope(tok, mSettings.library)) - return Result(Result::Type::BAILOUT); - if (Token::simpleMatch(tok, "} else {")) - tok = tok->linkAt(2); - if (!tok) - return Result(Result::Type::BAILOUT); - - // Is expr changed in condition? - if (!isUnchanged(conditionStart, conditionStart->link(), exprVarIds, local)) - return Result(Result::Type::BAILOUT); - - // Is expr changed in condition body? - if (!isUnchanged(bodyStart, bodyStart->link(), exprVarIds, local)) - return Result(Result::Type::BAILOUT); - } - if (!local && Token::Match(tok, "%name% (") && !Token::simpleMatch(tok->linkAt(1), ") {")) { // TODO: this is a quick bailout return Result(Result::Type::BAILOUT); @@ -279,22 +217,15 @@ FwdAnalysis::Result FwdAnalysis::checkRecursive(const Token *expr, const Token * parent = parent->astParent(); if (parent->str() == "(" && !parent->isCast()) break; - if (isSameExpression(false, expr, parent, mSettings, true, false, nullptr)) { + if (isSameExpression(false, expr, parent, mSettings, true, false, nullptr)) same = true; - if (mWhat == What::ValueFlow) { - KnownAndToken v; - v.known = mValueFlowKnown; - v.token = parent; - mValueFlow.push_back(v); - } - } if (Token::Match(parent, ". %var%") && parent->next()->varId() && exprVarIds.find(parent->next()->varId()) == exprVarIds.end() && isSameExpression(false, expr->astOperand1(), parent->astOperand1(), mSettings, true, false, nullptr)) { other = true; break; } } - if (mWhat != What::ValueFlow && same && Token::simpleMatch(parent->astParent(), "[") && parent == parent->astParent()->astOperand2()) { + if (same && Token::simpleMatch(parent->astParent(), "[") && parent == parent->astParent()->astOperand2()) { return Result(Result::Type::READ); } if (other) @@ -358,11 +289,11 @@ FwdAnalysis::Result FwdAnalysis::checkRecursive(const Token *expr, const Token * ftok = ftok->astParent(); if (ftok && Token::Match(ftok->previous(), "%name% (")) { const std::vector args = getArguments(ftok); - int argnr = 0; + size_t argnr = 0; while (argnr < args.size() && args[argnr] != parent) argnr++; if (argnr < args.size()) { - if (mSettings.library.getArgDirection(ftok->astOperand1(), argnr + 1, /*indirect*/ 1) == Library::ArgumentChecks::Direction::DIR_OUT) + if (mSettings().library.getArgDirection(ftok->astOperand1(), argnr + 1, /*indirect*/ 1) == Library::ArgumentChecks::Direction::DIR_OUT) continue; } } @@ -381,8 +312,6 @@ FwdAnalysis::Result FwdAnalysis::checkRecursive(const Token *expr, const Token * return result1; if (mWhat == What::UnusedValue && result1.type == Result::Type::WRITE && expr->variable() && expr->variable()->isReference()) return result1; - if (mWhat == What::ValueFlow && result1.type == Result::Type::WRITE) - mValueFlowKnown = false; if (mWhat == What::Reassign && result1.type == Result::Type::BREAK) { const Token *scopeEndToken = findNextTokenFromBreak(result1.token); if (scopeEndToken) { @@ -394,8 +323,6 @@ FwdAnalysis::Result FwdAnalysis::checkRecursive(const Token *expr, const Token * if (Token::simpleMatch(tok->linkAt(1), "} else {")) { const Token *elseStart = tok->linkAt(1)->tokAt(2); const Result &result2 = checkRecursive(expr, elseStart, elseStart->link(), exprVarIds, local, inInnerClass, depth); - if (mWhat == What::ValueFlow && result2.type == Result::Type::WRITE) - mValueFlowKnown = false; if (result2.type == Result::Type::READ || result2.type == Result::Type::BAILOUT) return result2; if (result1.type == Result::Type::WRITE && result2.type == Result::Type::WRITE) @@ -454,7 +381,7 @@ std::set FwdAnalysis::getExprVarIds(const Token* expr, bool* localOu return exprVarIds; } -FwdAnalysis::Result FwdAnalysis::check(const Token* expr, const Token* startToken, const Token* endToken) +FwdAnalysis::Result FwdAnalysis::check(const Token* expr, const Token* startToken, const Token* endToken) const { // all variable ids in expr. bool local = true; @@ -475,7 +402,7 @@ FwdAnalysis::Result FwdAnalysis::check(const Token* expr, const Token* startToke Result result = checkRecursive(expr, startToken, endToken, exprVarIds, local, false); // Break => continue checking in outer scope - while (mWhat!=What::ValueFlow && result.type == FwdAnalysis::Result::Type::BREAK) { + while (result.type == FwdAnalysis::Result::Type::BREAK) { const Token *scopeEndToken = findNextTokenFromBreak(result.token); if (!scopeEndToken) break; @@ -542,7 +469,7 @@ bool FwdAnalysis::possiblyAliased(const Token *expr, const Token *startToken) co if (Token::Match(tok, "%name% (") && !Token::Match(tok, "if|while|for")) { // Is argument passed by reference? const std::vector args = getArguments(tok); - for (int argnr = 0; argnr < args.size(); ++argnr) { + for (size_t argnr = 0; argnr < args.size(); ++argnr) { if (!Token::Match(args[argnr], "%name%|.|::")) continue; if (tok->function() && tok->function()->getArgumentVar(argnr) && !tok->function()->getArgumentVar(argnr)->isReference() && !tok->function()->isConst()) diff --git a/lib/fwdanalysis.h b/lib/fwdanalysis.h index 389ea195da2..b57e36ba34b 100644 --- a/lib/fwdanalysis.h +++ b/lib/fwdanalysis.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2024 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,10 +22,10 @@ //--------------------------------------------------------------------------- #include "config.h" +#include "refthunk.h" #include #include -#include class Token; class Settings; @@ -60,11 +60,6 @@ class FwdAnalysis { */ bool unusedValue(const Token *expr, const Token *startToken, const Token *endToken); - struct KnownAndToken { - bool known{}; - const Token* token{}; - }; - /** Is there some possible alias for given expression */ bool possiblyAliased(const Token *expr, const Token *startToken) const; @@ -80,13 +75,11 @@ class FwdAnalysis { const Token* token{}; }; - Result check(const Token *expr, const Token *startToken, const Token *endToken); - Result checkRecursive(const Token *expr, const Token *startToken, const Token *endToken, const std::set &exprVarIds, bool local, bool inInnerClass, int depth=0); + Result check(const Token *expr, const Token *startToken, const Token *endToken) const; + Result checkRecursive(const Token *expr, const Token *startToken, const Token *endToken, const std::set &exprVarIds, bool local, bool inInnerClass, int depth=0) const; - const Settings &mSettings; - enum class What : std::uint8_t { Reassign, UnusedValue, ValueFlow } mWhat = What::Reassign; - std::vector mValueFlow; - bool mValueFlowKnown = true; + RefThunk mSettings; + enum class What : std::uint8_t { Reassign, UnusedValue } mWhat = What::Reassign; }; #endif // fwdanalysisH diff --git a/lib/importproject.cpp b/lib/importproject.cpp index 401f1334b81..a53760c1a32 100644 --- a/lib/importproject.cpp +++ b/lib/importproject.cpp @@ -23,26 +23,46 @@ #include "settings.h" #include "standards.h" #include "suppressions.h" -#include "token.h" -#include "tokenlist.h" #include "utils.h" #include +#include +#include +#include #include #include #include #include #include #include -#include +#include +#include +#include +#include +#include #include #include #include +// Directory listing for wildcard support -- see listDirectoryFiles() +// just above processImport(). +#ifndef _WIN32 +#include +#else +#ifndef NOMINMAX +#define NOMINMAX +#endif +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#endif + #include "xml.h" #include "json.h" + std::string ImportProject::collectArgs(const std::string &cmd, std::vector &args) { args.clear(); @@ -111,73 +131,104 @@ std::string ImportProject::collectArgs(const std::string &cmd, std::vector &args) { + // Accept index by value, return a completion state pair to prevent loop corruption const auto getOptArg = [&args](std::initializer_list optNames, - std::size_t &i) { + std::size_t i) -> std::pair { const auto &arg = args[i]; const auto *const it = std::find_if(optNames.begin(), optNames.end(), - [&arg] (const std::string &optName) { + [&arg](const std::string &optName) { return startsWith(arg, optName); }); if (it == optNames.end()) - return std::string(); + return std::make_pair(std::string(), false); const std::size_t optLen = it->size(); - if (arg.size() == optLen) - return ++i >= args.size() ? std::string() : args[i]; + if (arg.size() == optLen) { + // Space-separated argument format (e.g., /I path) + if (i + 1 >= args.size()) + return std::make_pair(std::string(), false); + return std::make_pair(args[i + 1], true); // true flags lookahead consumption + } - return arg.substr(optLen); + // Glued argument format (e.g., /Ipath) + return std::make_pair(arg.substr(optLen), false); }; std::string defs; for (std::size_t i = 0; i < args.size(); i++) { - std::string optArg; + if (args[i].empty()) + continue; - if (!(optArg = getOptArg({ "-I", "/I" }, i)).empty()) { + std::pair optResult; + + if (!(optResult = getOptArg({ "-I", "/I" }, i)).first.empty()) { if (std::none_of(fs.includePaths.cbegin(), fs.includePaths.cend(), [&](const std::string &path) { - return path == optArg; - })) - fs.includePaths.push_back(std::move(optArg)); + return path == optResult.first; + })) { + fs.includePaths.push_back(std::move(optResult.first)); + } + if (optResult.second) + i++; // Safely advance only if the separate lookahead was consumed + continue; + } + + if (!(optResult = getOptArg({ "-isystem" }, i)).first.empty()) { + fs.systemIncludePaths.push_back(std::move(optResult.first)); + if (optResult.second) + i++; continue; } - if (!(optArg = getOptArg({ "-isystem" }, i)).empty()) { - fs.systemIncludePaths.push_back(std::move(optArg)); + if (!(optResult = getOptArg({ "-include", "/FI", "-FI" }, i)).first.empty()) { + fs.forcedIncludes.push_back(std::move(optResult.first)); + if (optResult.second) + i++; continue; } - if (!(optArg = getOptArg({ "-D", "/D" }, i)).empty()) { - defs += optArg + ";"; + if (!(optResult = getOptArg({ "-D", "/D" }, i)).first.empty()) { + defs += optResult.first + ";"; + if (optResult.second) + i++; continue; } - if (!(optArg = getOptArg({ "-U", "/U" }, i)).empty()) { - fs.undefs.insert(std::move(optArg)); + if (!(optResult = getOptArg({ "-U", "/U" }, i)).first.empty()) { + fs.undefs.insert(std::move(optResult.first)); + if (optResult.second) + i++; continue; } - if (!(optArg = getOptArg({ "-std=", "/std:" }, i)).empty()) { - fs.standard = std::move(optArg); + if (!(optResult = getOptArg({ "-std=", "/std:" }, i)).first.empty()) { + fs.standard = std::move(optResult.first); + if (optResult.second) + i++; continue; } - if (!(optArg = getOptArg({ "-f" }, i)).empty()) { - if (optArg == "pic") + if (!(optResult = getOptArg({ "-f" }, i)).first.empty()) { + if (optResult.first == "pic") defs += "__pic__;"; - else if (optArg == "PIC") + else if (optResult.first == "PIC") defs += "__PIC__;"; - else if (optArg == "pie") + else if (optResult.first == "pie") defs += "__pie__;"; - else if (optArg == "PIE") + else if (optResult.first == "PIE") defs += "__PIE__;"; + if (optResult.second) + i++; continue; } - if (!(optArg = getOptArg({ "-m" }, i)).empty()) { - if (optArg == "unicode") + if (!(optResult = getOptArg({ "-m" }, i)).first.empty()) { + if (optResult.first == "unicode") defs += "UNICODE;"; + if (optResult.second) + i++; continue; } } @@ -209,8 +260,31 @@ void ImportProject::ignoreOtherConfigs(const std::string &cfg) } } +// Decode %XX sequences back to their original characters. +static std::string msbuildUnescape(const std::string &s) +{ + std::string result; + result.reserve(s.size()); + for (std::size_t i = 0; i < s.size(); ++i) { + if (s[i] == '%' && i + 2 < s.size() && + std::isxdigit(static_cast(s[i + 1])) && + std::isxdigit(static_cast(s[i + 2]))) { + result += static_cast(std::stoi(s.substr(i + 1, 2), nullptr, 16)); + i += 2; + } else { + result += s[i]; + } + } + return result; +} + void ImportProject::fsSetDefines(FileSettings& fs, std::string defs) { + // Strip %(metadata) tokens at the start of the string (no preceding semicolon). + while (startsWith(defs, "%(")) { + const std::string::size_type pos2 = defs.find(';'); + defs.erase(0, pos2 == std::string::npos ? std::string::npos : pos2 + 1); + } while (defs.find(";%(") != std::string::npos) { const std::string::size_type pos1 = defs.find(";%("); const std::string::size_type pos2 = defs.find(';', pos1+1); @@ -237,647 +311,4318 @@ void ImportProject::fsSetDefines(FileSettings& fs, std::string defs) } if (!eq && !defs.empty()) defs += "=1"; + defs = msbuildUnescape(defs); fs.defines.swap(defs); } -static bool simplifyPathWithVariables(std::string &s, std::map &variables) -{ - std::set expanded; - std::string::size_type start = 0; - while ((start = s.find("$(")) != std::string::npos) { - const std::string::size_type end = s.find(')',start); - if (end == std::string::npos) - break; - const std::string var = s.substr(start+2,end-start-2); - if (expanded.find(var) != expanded.end()) - break; - expanded.insert(var); - auto it1 = utils::as_const(variables).find(var); - // variable was not found within defined variables - if (it1 == variables.end()) { - const char *envValue = std::getenv(var.c_str()); - if (!envValue) { - //! \todo generate a debug/info message about undefined variable - break; - } - variables[var] = std::string(envValue); - it1 = variables.find(var); - } - s.replace(start, end - start + 1, it1->second); +void ImportProject::addDebug(const std::string &msg) { + for (const auto &debug : debugs) { + // cppcheck-suppress useStlAlgorithm + if (debug == msg) + return; } - if (s.find("$(") != std::string::npos) - return false; - s = Path::simplifyPath(std::move(s)); - return true; + debugs.emplace_back(msg); } -void ImportProject::fsSetIncludePaths(FileSettings& fs, const std::string &basepath, const std::list &in, std::map &variables) -{ - std::set found; - // NOLINTNEXTLINE(performance-unnecessary-copy-initialization) - const std::list copyIn(in); - fs.includePaths.clear(); - for (const std::string &ipath : copyIn) { - if (ipath.empty()) - continue; - if (startsWith(ipath,"%(")) - continue; - std::string s(Path::fromNativeSeparators(ipath)); - if (!found.insert(s).second) - continue; - if (s[0] == '/' || (s.size() > 1U && s.compare(1,2,":/") == 0)) { - if (!endsWith(s,'/')) - s += '/'; - fs.includePaths.push_back(std::move(s)); +// Find the ')' that matches the '(' at position parenPos. +// Tracks depth for every '(' and ')', not just '$(' pairs: bare parentheses +// inside static-function argument lists (e.g. Pow(2,3), Format(...)) must +// also open and close a nesting level, otherwise the inner ')' would be +// mistaken for the match of the outer one. +static std::string::size_type findMatchingParen(const std::string &s, std::string::size_type parenPos) { + if (parenPos >= s.size() || s[parenPos] != '(') + return std::string::npos; + + int depth = 0; + char quote = '\0'; + + for (std::string::size_type i = parenPos; i < s.size(); ++i) { + const char c = s[i]; + + if (quote != '\0') { + if (c == quote) { + // A doubled quote represents a literal quote. + if (i + 1 < s.size() && s[i + 1] == quote) { + ++i; + continue; + } + quote = '\0'; + } continue; } - if (endsWith(s,'/')) // this is a temporary hack, simplifyPath can crash if path ends with '/' - s.pop_back(); - - if (s.find("$(") == std::string::npos) { - s = Path::simplifyPath(basepath + s); - } else { - if (!simplifyPathWithVariables(s, variables)) - continue; + if (c == '\'' || c == '"') { + quote = c; + } else if (c == '(') { + ++depth; + } else if (c == ')') { + --depth; + if (depth == 0) + return i; } - if (s.empty()) - continue; - fs.includePaths.push_back(s.back() == '/' ? s : (s + '/')); } + + return std::string::npos; } -ImportProject::Type ImportProject::import(const std::string &filename, Settings *settings, Suppressions *supprs) -{ - std::ifstream fin(filename); - if (!fin.is_open()) - return ImportProject::Type::MISSING; +// Apply an MSBuild property string method (ToLower, Replace, etc.). +// Used by both the condition evaluator and the property value expander. +static std::string applyPropertyMethod(std::string value, + const std::string &method, + const std::vector &args) { + if (caseInsensitiveStringCompare(method, "ToUpper") == 0) { + if (!args.empty()) + throw std::runtime_error("ToUpper takes no arguments"); + std::transform(value.cbegin(), value.cend(), value.begin(), static_cast(std::toupper)); + return value; + } - mPath = Path::getPathFromFilename(Path::fromNativeSeparators(filename)); - if (!mPath.empty() && !endsWith(mPath,'/')) - mPath += '/'; + if (caseInsensitiveStringCompare(method, "ToLower") == 0) { + if (!args.empty()) + throw std::runtime_error("ToLower takes no arguments"); + std::transform(value.cbegin(), value.cend(), value.begin(), static_cast(std::tolower)); + return value; + } - const std::vector fileFilters = - settings ? settings->fileFilters : std::vector(); + if (caseInsensitiveStringCompare(method, "Contains") == 0) { + if (args.size() != 1) + throw std::runtime_error("Contains requires one argument"); + // .NET String.Contains is case-sensitive by default + return value.find(args[0]) != std::string::npos ? "True" : "False"; + } - if (endsWith(filename, ".json")) { - if (importCompileCommands(fin)) { - setRelativePaths(filename); - return ImportProject::Type::COMPILE_DB; + if (caseInsensitiveStringCompare(method, "StartsWith") == 0) { + if (args.size() != 1) + throw std::runtime_error("StartsWith requires one argument"); + // .NET String.StartsWith is case-sensitive by default + if (args[0].size() > value.size()) + return "False"; + return value.compare(0, args[0].size(), args[0]) == 0 ? "True" : "False"; + } + + if (caseInsensitiveStringCompare(method, "EndsWith") == 0) { + if (args.size() != 1) + throw std::runtime_error("EndsWith requires one argument"); + // .NET String.EndsWith is case-sensitive by default + if (args[0].size() > value.size()) + return "False"; + return value.compare(value.size() - args[0].size(), args[0].size(), args[0]) == 0 ? "True" : "False"; + } + + if (caseInsensitiveStringCompare(method, "Trim") == 0) { + if (args.empty()) { + const std::size_t first = value.find_first_not_of(" \t\r\n"); + if (first == std::string::npos) + return ""; + const std::size_t last = value.find_last_not_of(" \t\r\n"); + return value.substr(first, last - first + 1); } - } else if (endsWith(filename, ".sln")) { - if (importSln(fin, mPath, fileFilters)) { - setRelativePaths(filename); - return ImportProject::Type::VS_SLN; + std::string chars; + for (const std::string &arg : args) + chars += arg; + const std::size_t first = value.find_first_not_of(chars); + if (first == std::string::npos) + return ""; + const std::size_t last = value.find_last_not_of(chars); + return value.substr(first, last - first + 1); + } + + if (caseInsensitiveStringCompare(method, "TrimStart") == 0) { + if (args.empty()) { + const std::size_t first = value.find_first_not_of(" \t\r\n"); + return first == std::string::npos ? "" : value.substr(first); } - } else if (endsWith(filename, ".slnx")) { - if (importSlnx(filename, fileFilters)) { - setRelativePaths(filename); - return ImportProject::Type::VS_SLNX; + std::string chars; + for (const std::string &arg : args) + chars += arg; + const std::size_t first = value.find_first_not_of(chars); + return first == std::string::npos ? "" : value.substr(first); + } + + if (caseInsensitiveStringCompare(method, "TrimEnd") == 0) { + if (args.empty()) { + const std::size_t last = value.find_last_not_of(" \t\r\n"); + return last == std::string::npos ? "" : value.substr(0, last + 1); } - } else if (endsWith(filename, ".vcxproj")) { - std::map variables; - std::vector sharedItemsProjects; - if (importVcxproj(filename, variables, "", fileFilters, sharedItemsProjects)) { - setRelativePaths(filename); - return ImportProject::Type::VS_VCXPROJ; + std::string chars; + for (const std::string &arg : args) + chars += arg; + const std::size_t last = value.find_last_not_of(chars); + return last == std::string::npos ? "" : value.substr(0, last + 1); + } + + if (caseInsensitiveStringCompare(method, "Substring") == 0) { + if (args.size() != 1 && args.size() != 2) + throw std::runtime_error("Substring requires one or two arguments"); + char *end = nullptr; + const long start = std::strtol(args[0].c_str(), &end, 10); + if (end == args[0].c_str() || *end != '\0') + throw std::runtime_error("Invalid Substring start index"); + if (start < 0 || static_cast(start) > value.size()) + throw std::runtime_error("Substring start index out of range"); + const auto index = static_cast(start); + if (args.size() == 1) + return value.substr(index); + end = nullptr; + const long length = std::strtol(args[1].c_str(), &end, 10); + if (end == args[1].c_str() || *end != '\0') + throw std::runtime_error("Invalid Substring length"); + if (length < 0 || static_cast(length) > value.size() - index) + throw std::runtime_error("Substring length out of range"); + return value.substr(index, static_cast(length)); + } + + if (caseInsensitiveStringCompare(method, "Replace") == 0) { + if (args.size() != 2) + throw std::runtime_error("Replace requires two arguments"); + if (args[0].empty()) + throw std::runtime_error("Replace search string cannot be empty"); + std::size_t pos = 0; + while ((pos = value.find(args[0], pos)) != std::string::npos) { + value.replace(pos, args[0].size(), args[1]); + pos += args[1].size(); } - } else if (endsWith(filename, ".bpr")) { - if (importBcb6Prj(filename)) { - setRelativePaths(filename); - return ImportProject::Type::BORLAND; + return value; + } + + if (caseInsensitiveStringCompare(method, "IndexOf") == 0) { + if (args.empty() || args.size() > 2) + throw std::runtime_error("IndexOf requires one or two arguments"); + std::size_t from = 0; + if (args.size() == 2) { + char *end = nullptr; + const long idx = std::strtol(args[1].c_str(), &end, 10); + if (end == args[1].c_str() || *end != '\0') + throw std::runtime_error("Invalid IndexOf start index"); + if (idx < 0 || static_cast(idx) > value.size()) + throw std::runtime_error("IndexOf start index out of range"); + from = static_cast(idx); } - } else if (settings && supprs && endsWith(filename, ".cppcheck")) { - if (importCppcheckGuiProject(fin, *settings, *supprs)) { - setRelativePaths(filename); - return ImportProject::Type::CPPCHECK_GUI; + const std::size_t found = value.find(args[0], from); + return std::to_string(found == std::string::npos ? -1L : static_cast(found)); + } + + if (caseInsensitiveStringCompare(method, "LastIndexOf") == 0) { + if (args.empty() || args.size() > 2) + throw std::runtime_error("LastIndexOf requires one or two arguments"); + std::size_t from = std::string::npos; + if (args.size() == 2) { + char *end = nullptr; + const long idx = std::strtol(args[1].c_str(), &end, 10); + if (end == args[1].c_str() || *end != '\0') + throw std::runtime_error("Invalid LastIndexOf start index"); + if (idx < 0 || static_cast(idx) > value.size()) + throw std::runtime_error("LastIndexOf start index out of range"); + from = static_cast(idx); } - } else { - return ImportProject::Type::UNKNOWN; + const std::size_t found = value.rfind(args[0], from); + return std::to_string(found == std::string::npos ? -1L : static_cast(found)); } - return ImportProject::Type::FAILURE; + + const bool isPadLeft = caseInsensitiveStringCompare(method, "PadLeft") == 0; + if (isPadLeft || caseInsensitiveStringCompare(method, "PadRight") == 0) { + if (args.empty() || args.size() > 2) + throw std::runtime_error(method + " requires one or two arguments"); + char *end = nullptr; + const long totalWidth = std::strtol(args[0].c_str(), &end, 10); + if (end == args[0].c_str() || *end != '\0' || totalWidth < 0) + throw std::runtime_error(method + " totalWidth must be a non-negative integer"); + const char padChar = (args.size() == 2 && !args[1].empty()) ? args[1][0] : ' '; + const auto width = static_cast(totalWidth); + if (value.size() >= width) + return value; + const std::string padding(width - value.size(), padChar); + return isPadLeft ? padding + value : value + padding; + } + + throw std::runtime_error("Unhandled method '" + method + "'"); } -bool ImportProject::importCompileCommands(std::istream &istr) +static std::string findFileAbove(const std::string &startDirectory, const std::string &file) { - picojson::value compileCommands; - istr >> compileCommands; - if (!compileCommands.is()) { - errors.emplace_back("compilation database is not a JSON array"); - return false; + // startDirectory comes from MSBuildThisFileDirectory which is already + // normalized to '/' separators by Path::simplifyPath. + std::string currentDir = startDirectory; + if (currentDir.size() > 1 && currentDir.back() == '/' && currentDir[currentDir.size() - 2] != ':') + currentDir.pop_back(); + + while (!currentDir.empty()) { + std::string targetFile = Path::join(currentDir, file); + if (Path::isFile(targetFile)) + return targetFile; + if (currentDir.back() == '/' || (currentDir.back() == ':' && currentDir.size() == 2)) + break; + const std::size_t lastSlash = currentDir.rfind('/'); + if (lastSlash == std::string::npos) + break; + currentDir.resize(lastSlash); } - std::map fsFileIds; + return ""; +} - for (const picojson::value &fileInfo : compileCommands.get()) { - picojson::object obj = fileInfo.get(); +/// Five-way Windows/Unix path classification used by pathCombineAppend and isPathRooted. +enum class PathKind : std::uint8_t { + Empty, ///< "" + UNC, ///< \\server\share or //server/share + DriveAbsolute, ///< C:\foo or C:/foo + RootRelative, ///< \foo or /foo (no drive letter) + DriveRelative, ///< C:foo (drive letter, no separator) + Relative, ///< foo (everything else) +}; + +static PathKind classifyPath(const std::string &path) +{ + if (path.empty()) + return PathKind::Empty; + // UNC: two leading separators. + if ((path[0] == '/' || path[0] == '\\') && + path.size() >= 2 && (path[1] == '/' || path[1] == '\\')) + return PathKind::UNC; + // Windows drive letter. + if (path.size() >= 2 && + std::isalpha(static_cast(path[0])) && + path[1] == ':') { + if (path.size() >= 3 && (path[2] == '/' || path[2] == '\\')) + return PathKind::DriveAbsolute; + return PathKind::DriveRelative; + } + // Single leading separator. + if (path[0] == '/' || path[0] == '\\') + return PathKind::RootRelative; + return PathKind::Relative; +} - if (obj.count("directory") == 0) { - errors.emplace_back("'directory' field in compilation database entry missing"); - return false; +static bool isRelative(const std::string &path) { + return classifyPath(path) >= PathKind::DriveRelative; +} + +// Append one path segment to `result` using Windows path-combination semantics. +// classifyPath() is the sole authority for path kind; Path::isAbsolute() is +// intentionally NOT used here because it is host-dependent and would misclassify +// root-relative Windows paths (\foo -> /foo after fromNativeSeparators) as +// fully-absolute on Linux. +// +// Returns true if the segment was fully resolved, false if it could not be +// resolved correctly and `result` was left unchanged. Callers must emit a +// diagnostic on false; they must NOT silently continue with a wrong path. +// +// Two modes, selected by `checkIsAbsolute`: +// +// false -- MSBuild property-evaluation combine (e.g. $(A)\$(B)): +// UNC / DriveAbsolute -> full reset, returns true +// RootRelative \foo -> reset path, inherit drive: "C:" + "\foo" = "C:/foo", returns true +// DriveRelative C:foo -> UNSUPPORTED: resolving "C:foo" requires the per-drive CWD +// for drive C:, which is a Windows kernel concept unavailable +// here. Leaves result unchanged and returns false. +// Relative / Empty -> plain join, returns true +// +// true -- System.IO.Path.Combine semantics: +// UNC / DriveAbsolute -> full reset, returns true +// RootRelative \foo -> full reset (Path.IsPathRooted = true; drive NOT inherited, +// matching .NET Path.Combine behaviour), returns true +// DriveRelative C:foo -> full reset (Path.IsPathRooted = true for "C:foo"), returns true +// Relative / Empty -> plain join, returns true +static bool pathCombineAppend(std::string &result, const std::string &seg, bool checkIsAbsolute = false) { + if (seg.empty()) + return true; + switch (classifyPath(seg)) { + case PathKind::UNC: + case PathKind::DriveAbsolute: + // Full reset in both modes. + result = seg; + return true; + case PathKind::RootRelative: + if (checkIsAbsolute) { + // System.IO.Path.Combine: root-relative is rooted; discard accumulated + // base (including any drive letter) and return the segment as-is. + result = seg; + } else { + // MSBuild property eval: reset the path component but preserve the + // accumulated drive letter so "\foo" on "C:/project/" -> "C:/foo". + if (result.size() >= 2 && std::isalpha(static_cast(result[0])) && result[1] == ':') + result.replace(0, 2, seg); + else + result = seg; + } + return true; + case PathKind::DriveRelative: + if (checkIsAbsolute) { + // System.IO.Path.Combine: drive-relative is rooted (Path.IsPathRooted + // returns true for "C:foo"); discard the accumulated base. + result = seg; + return true; } + // MSBuild property eval: "C:foo" means foo relative to the current directory + // of drive C:, a per-drive CWD that is a Windows kernel concept. We have no + // way to resolve this correctly in a cross-platform context. Leave result + // unchanged and signal the caller to emit a diagnostic. + return false; + case PathKind::Relative: + case PathKind::Empty: + if (!result.empty() && result.back() != '/' && result.back() != '\\') + result += '/'; + result += seg; + return true; + } + return true; // unreachable; silences -Wreturn-type +} - if (!obj["directory"].is()) { - errors.emplace_back("'directory' field in compilation database entry is not a string"); - return false; +// MSBuild special characters that must be percent-encoded in property values. +static const char MSBUILD_SPECIAL_CHARS[] = "%$@';?*!"; + +// Encode every MSBuild special character in `s` as %XX. +static std::string msbuildEscape(const std::string &s) +{ + static const char hex[] = "0123456789ABCDEF"; + std::string result; + result.reserve(s.size()); + for (const unsigned char c : s) { + if (std::strchr(MSBUILD_SPECIAL_CHARS, static_cast(c))) { + result += '%'; + result += hex[c >> 4]; + result += hex[c & 0xF]; + } else { + result += static_cast(c); } + } + return result; +} - std::string dirpath = Path::fromNativeSeparators(obj["directory"].get()); +static std::string stripDirectoryPart(const std::string &filename) { + const auto slash = filename.rfind('/'); + return (slash != std::string::npos) ? filename.substr(slash + 1) : filename; +} - /* CMAKE produces the directory without trailing / so add it if not - * there - it is needed by setIncludePaths() */ - if (!endsWith(dirpath, '/')) - dirpath += '/'; +/// Return the stem of \p filename (basename with its final extension removed). +/// Strips only the tail extension so "foo.targets.props" -> "foo.targets", not "foo". +static std::string fileStem(const std::string &filename) { + const std::string ext = Path::getFilenameExtension(filename); + if (ext.empty() || filename.size() <= ext.size()) + return filename; + return filename.substr(0, filename.size() - ext.size()); +} - const std::string directory = std::move(dirpath); - std::vector arguments; - if (obj.count("arguments")) { - if (obj["arguments"].is()) { - for (const picojson::value& arg : obj["arguments"].get()) { - if (arg.is()) - arguments.push_back(arg.get()); - } - } else { - errors.emplace_back("'arguments' field in compilation database entry is not a JSON array"); - return false; - } - } else if (obj.count("command")) { - std::string command; - if (obj["command"].is()) { - command = obj["command"].get(); - } else { - errors.emplace_back("'command' field in compilation database entry is not a string"); - return false; - } +static bool isPathRooted(const std::string &filename) { + switch (classifyPath(filename)) { + case PathKind::UNC: + case PathKind::DriveAbsolute: + case PathKind::RootRelative: + case PathKind::DriveRelative: // "C:foo" -- Path.IsPathRooted returns true on Windows + return true; + default: + return false; + } +} - std::string error = collectArgs(command, arguments); - if (!error.empty()) { - errors.emplace_back(error); - return false; +static std::string getRelativePath(const std::string &absolutePath, const std::vector &basePaths) { + const std::string normAbs = Path::fromNativeSeparators(absolutePath); + + // Split a forward-slash path into components where the first element is + // the root token -- keeping roots distinct prevents the common-prefix + // algorithm from emitting relative paths that cross root boundaries. + // + // UNC absolute //server/share/a -> ["//server/share", "a"] + // Drive absolute C:/foo/a -> ["C:", "foo", "a"] + // Root relative /foo/a -> ["/", "foo", "a"] + // Relative foo/a -> ["foo", "a"] (no root token) + // + // Cross-root pairs (different drive letters, or UNC paths whose server OR + // share differs) will get common == 0 and be rejected before any ".." are + // emitted, which is correct: no well-formed relative path can cross roots. + const auto split = [](const std::string &s) { + std::vector parts; + std::size_t pos = 0; + if (s.size() >= 2 && s[0] == '/' && s[1] == '/') { + // UNC path: root is the "//server/share" unit. + const std::size_t serverEnd = s.find('/', 2); + if (serverEnd == std::string::npos) { + // Degenerate "//server" with no share -- treat whole string as root. + parts.push_back(s); + return parts; } - } else { - errors.emplace_back("no 'arguments' or 'command' field found in compilation database entry"); - return false; + const std::size_t shareEnd = s.find('/', serverEnd + 1); + if (shareEnd == std::string::npos) { + // "//server/share" with no trailing path. + parts.push_back(s); + return parts; + } + parts.push_back(s.substr(0, shareEnd)); // "//server/share" + pos = shareEnd + 1; + } else if (s.size() >= 2 && std::isalpha(static_cast(s[0])) && s[1] == ':') { + // Drive-letter path: root is the two-character drive token "C:". + parts.push_back(s.substr(0, 2)); + pos = 2; + if (pos < s.size() && s[pos] == '/') + ++pos; + } else if (!s.empty() && s[0] == '/') { + // Root-relative path: root is "/". + parts.emplace_back("/"); + pos = 1; + } + // else: relative path -- no root token is prepended. + + while (pos < s.size()) { + const std::size_t slash = s.find('/', pos); + std::string seg = s.substr(pos, slash == std::string::npos ? std::string::npos : slash - pos); + if (!seg.empty()) + parts.push_back(std::move(seg)); + if (slash == std::string::npos) + break; + pos = slash + 1; } + return parts; + }; - if (!obj.count("file") || !obj["file"].is()) { - errors.emplace_back("skip compilation database entry because it does not have a proper 'file' field"); + for (const std::string &bp : basePaths) { + if (absolutePath == bp || bp.empty()) // Seems to be a file, or path is empty continue; + + const std::string normBase = Path::fromNativeSeparators(bp); + + // Fast path: absolutePath is directly under basePath -- strip the prefix. + if (normAbs.compare(0, normBase.length(), normBase) == 0) { + if (endsWith(normBase, '/')) + return normAbs.substr(normBase.length()); + if (normAbs.size() > normBase.size() && normAbs[normBase.size()] == '/') + return normAbs.substr(normBase.size() + 1); } - std::string file = Path::fromNativeSeparators(obj["file"].get()); + // Slow path: build a relative path using ".." when absolutePath is above + // or beside basePath but shares a common ancestor. + std::string base = normBase; + if (!base.empty() && base.back() != '/') + base += '/'; + + const std::vector absParts = split(normAbs); + const std::vector baseParts_ = split(base); + + // Find the length of the common component prefix. + std::size_t common = 0; + while (common < absParts.size() && common < baseParts_.size() && + Path::sameFileName(absParts[common], baseParts_[common])) + ++common; + + if (common == 0) + continue; // truly different roots -- skip this basePath + + // One ".." for every extra component in base beyond the common prefix, + // then the remaining components of absolutePath. + std::string rel; + for (std::size_t i = common; i < baseParts_.size(); ++i) { + if (!rel.empty()) + rel += '/'; + rel += ".."; + } + for (std::size_t i = common; i < absParts.size(); ++i) { + if (!rel.empty()) + rel += '/'; + rel += absParts[i]; + } + if (!rel.empty()) + return rel; + } + // No base path shares a root with absolutePath. Return the normalized form + // so the caller always gets forward slashes, consistent with every other + // path in the property map. + return normAbs; +} - // Accept file? - if (!Path::acceptFile(file)) - continue; +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmissing-format-attribute" +#pragma clang diagnostic ignored "-Wformat-nonliteral" +#endif - std::string path; - if (Path::isAbsolute(file)) - path = Path::simplifyPath(std::move(file)); -#ifdef _WIN32 - else if (file[0] == '/' && directory.size() > 2 && std::isalpha(directory[0]) && directory[1] == ':') - // directory: C:\foo\bar - // file: /xy/z.c - // => c:/xy/z.c - path = Path::simplifyPath(directory.substr(0,2) + file); +template +static std::string safeFormat(const char *fmt, Args... args) { + const int needed = std::snprintf(nullptr, 0, fmt, args ...); + if (needed < 0) + return std::string(); + std::vector buf(static_cast(needed) + 1); + std::snprintf(buf.data(), buf.size(), fmt, args ...); + return std::string(buf.data()); +} + +#if defined(__clang__) +#pragma clang diagnostic pop #endif - else - path = Path::simplifyPath(directory + file); - FileSettings fs{path, Standards::Language::None, 0}; // file will be identified later on - parseArgs(fs, arguments); - std::map variables; - fsSetIncludePaths(fs, directory, fs.includePaths, variables); - // Assign a unique index to each file path. If the file path already exists in the map, - // increment the index to handle duplicate file entries. - fs.file.setFsFileId(fsFileIds[path]++); - fileSettings.push_back(std::move(fs)); - } - return true; +// MSBuild evaluates the [MSBuild]::Add/Subtract/Multiply intrinsics in .NET's +// default unchecked context, so on overflow the result wraps (two's complement). +// Do the arithmetic on unsigned operands to get the same result without +// signed-overflow UB. +static long long wrapAdd(long long a, long long b) { + return static_cast(static_cast(a) + static_cast(b)); } -bool ImportProject::importSln(std::istream &istr, const std::string &path, const std::vector &fileFilters) -{ - std::string line; +static long long wrapSub(long long a, long long b) { + return static_cast(static_cast(a) - static_cast(b)); +} - if (!std::getline(istr,line)) { - errors.emplace_back("Visual Studio solution file is empty"); - return false; - } +static long long wrapMul(long long a, long long b) { + return static_cast(static_cast(a) * static_cast(b)); +} - if (!startsWith(line, "Microsoft Visual Studio Solution File")) { - // Skip BOM - if (!std::getline(istr, line) || !startsWith(line, "Microsoft Visual Studio Solution File")) { - errors.emplace_back("Visual Studio solution file header not found"); +// Evaluate a $([ClassName]::Method(args)) static property function. +// Returns an empty string for unknown or unimplementable functions rather +// than throwing, so import can continue gracefully. +std::string ImportProject::applyMSBuildStaticFunction(const std::string &className, + const std::string &member, + const std::vector &args, + const PropertiesMap *properties) { + const auto toInt = [](const std::string &s, long long &out) -> bool { + if (s.empty()) return false; + try { + size_t idx = 0; + out = std::stoll(s, &idx); + return idx == s.length(); } - } - - std::map variables; - variables["SolutionDir"] = path; - - bool found = false; - std::vector sharedItemsProjects; - while (std::getline(istr,line)) { - if (!startsWith(line,"Project(")) - continue; - const std::string::size_type pos = line.find(".vcxproj"); - if (pos == std::string::npos) - continue; - const std::string::size_type pos1 = line.rfind('\"',pos); - if (pos1 == std::string::npos) - continue; - std::string vcxproj(line.substr(pos1+1, pos-pos1+7)); - vcxproj = Path::toNativeSeparators(std::move(vcxproj)); - if (!Path::isAbsolute(vcxproj)) - vcxproj = path + vcxproj; - vcxproj = Path::fromNativeSeparators(std::move(vcxproj)); - if (!importVcxproj(vcxproj, variables, "", fileFilters, sharedItemsProjects)) { - errors.emplace_back("failed to load '" + vcxproj + "' from Visual Studio solution"); + catch (...) { return false; } - found = true; - } + }; - if (!found) { - errors.emplace_back("no projects found in Visual Studio solution file"); - return false; - } + if (caseInsensitiveStringCompare(className, "MSBuild") == 0) { - return true; -} + // $([MSBuild]::IsOSPlatform('Windows'|'Linux'|'OSX')) + if (caseInsensitiveStringCompare(member, "IsOSPlatform") == 0 && args.size() == 1) { +#if defined(_WIN32) + const bool onWindows = true, onLinux = false, onOSX = false; +#elif defined(__APPLE__) + const bool onWindows = false, onLinux = false, onOSX = true; +#else + const bool onWindows = false, onLinux = true, onOSX = false; +#endif + if (caseInsensitiveStringCompare(args[0], "Windows") == 0) + return onWindows ? "True" : "False"; + if (caseInsensitiveStringCompare(args[0], "Linux") == 0) + return onLinux ? "True" : "False"; + if (caseInsensitiveStringCompare(args[0], "OSX") == 0 || + caseInsensitiveStringCompare(args[0], "MacOS") == 0) + return onOSX ? "True" : "False"; + return "False"; + } -bool ImportProject::importSlnx(const std::string& filename, const std::vector& fileFilters) -{ - tinyxml2::XMLDocument doc; + // Arithmetic: Add, Subtract, Multiply, Divide, Modulo + if (args.size() == 2) { + long long a = 0, b = 0; + if (toInt(args[0], a) && toInt(args[1], b)) { + if (caseInsensitiveStringCompare(member, "Add") == 0) + return std::to_string(wrapAdd(a, b)); + if (caseInsensitiveStringCompare(member, "Subtract") == 0) + return std::to_string(wrapSub(a, b)); + if (caseInsensitiveStringCompare(member, "Multiply") == 0) + return std::to_string(wrapMul(a, b)); + if (caseInsensitiveStringCompare(member, "Divide") == 0) { + if (b == 0) { + addDebug("MSBuild::Divide: division by zero"); + return std::string(); + } + if (a == LLONG_MIN && b == -1) { + addDebug("MSBuild::Divide: integer overflow"); + return std::string(); + } + return std::to_string(a / b); + } + if (caseInsensitiveStringCompare(member, "Modulo") == 0) { + if (b == 0) { + addDebug("MSBuild::Modulo: division by zero"); + return std::string(); + } + if (a == LLONG_MIN && b == -1) { + addDebug("MSBuild::Modulo: integer overflow"); + return std::string(); + } + return std::to_string(a % b); + } + } + // $([MSBuild]::ValueOrDefault(value, default)) + if (caseInsensitiveStringCompare(member, "ValueOrDefault") == 0) + return args[0].empty() ? args[1] : args[0]; + // $([MSBuild]::MakeRelative(basePath, path)) + // Returns path expressed relative to basePath. If the two paths + // share no common root (e.g. different drive letters) path is + // returned unchanged. + if (caseInsensitiveStringCompare(member, "MakeRelative") == 0) + return getRelativePath(args[1], {args[0]}); + // $([MSBuild]::GetDirectoryNameOfFileAbove(startingDirectory, fileName)) + // Walks up from startingDirectory looking for fileName; returns the + // containing directory (no trailing separator) or "" if not found. + if (caseInsensitiveStringCompare(member, "GetDirectoryNameOfFileAbove") == 0) { + const std::string found = findFileAbove(args[0], args[1]); + if (found.empty()) + return ""; + std::string dir = Path::getPathFromFilename(found); + if (!dir.empty() && (dir.back() == '/' || dir.back() == '\\')) + dir.pop_back(); + return dir; + } + // $([MSBuild]::GetPathOfFileAbove(file, startingDirectory)) + // Walks up from startingDirectory looking for file; returns the full + // path of the file or "" if not found. + if (caseInsensitiveStringCompare(member, "GetPathOfFileAbove") == 0) + return findFileAbove(args[1], args[0]); + } + + // $([MSBuild]::GetPathOfFileAbove(file)) -- 1-arg form. MSBuild uses the + // directory of the file being evaluated (MSBuildThisFileDirectory) as the + // start directory. We read it from the supplied properties map when available; + // if absent (properties is null or the key is missing) findFile receives an + // empty start directory and returns "" without crashing. + if (caseInsensitiveStringCompare(member, "GetPathOfFileAbove") == 0 && args.size() == 1) { + std::string startDir; + if (properties) { + const auto it = properties->find("MSBuildThisFileDirectory"); + if (it != properties->end()) + startDir = it->second; + } + return findFileAbove(startDir, args[0]); + } + + // $([MSBuild]::NormalizePath(seg1[, seg2, ...])) -- join segments, normalize + // \ to /, and resolve . and .. components. Internally MSBuild calls + // Path.GetFullPath(Path.Combine(paths)), so multi-segment joining follows + // System.IO.Path.Combine semantics: a rooted segment (UNC, DriveAbsolute, + // RootRelative, or DriveRelative) resets the accumulated path. + if (caseInsensitiveStringCompare(member, "NormalizePath") == 0) { + if (args.empty()) { + addDebug("NormalizePath: called with no arguments"); + return ""; + } + // If any arg still contains an unexpanded $(...) reference, the property was + // not defined at evaluation time. Log it and bail out rather than treating + // the raw reference text as a literal path component. + for (const std::string &a : args) { + // cppcheck-suppress useStlAlgorithm + if (a.find("$(") != std::string::npos) { + addDebug("NormalizePath: arg contains unexpanded property reference: " + a); + return ""; + } + } + std::string result = args[0]; + for (std::size_t i = 1; i < args.size(); ++i) { + // cppcheck-suppress useStlAlgorithm + if (!pathCombineAppend(result, args[i], true)) { + addDebug("NormalizePath: could not combine path segments"); + return ""; + } + } + const PathKind resultKind = classifyPath(result); + if (resultKind == PathKind::DriveRelative) { + addDebug("NormalizePath: drive-relative path cannot be resolved: " + result); + return ""; + } + // Delegate separator normalization and . / .. resolution to the + // central Path utilities so all path handling stays consistent. + return Path::simplifyPath(Path::fromNativeSeparators(result)); + } + + // $([MSBuild]::NormalizeDirectory(seg1[, seg2, ...])) -- same as NormalizePath + // but always returns a path with a trailing slash. + if (caseInsensitiveStringCompare(member, "NormalizeDirectory") == 0) { + if (args.empty()) { + addDebug("NormalizeDirectory: called with no arguments"); + return ""; + } + // Reuse NormalizePath logic via recursive call with renamed member. + const std::string normalized = applyMSBuildStaticFunction(className, "NormalizePath", args); + if (!normalized.empty() && normalized.back() != '/') + return normalized + '/'; + return normalized; + } + + if (args.size() == 1) { + // $([MSBuild]::EnsureTrailingSlash(path)) + if (caseInsensitiveStringCompare(member, "EnsureTrailingSlash") == 0) { + std::string s = args[0]; + if (!s.empty() && s.back() != '/' && s.back() != '\\') + s += '/'; + return s; + } + // $([MSBuild]::GetTargetPlatformVersion(version)) -- pass through + if (caseInsensitiveStringCompare(member, "GetTargetPlatformVersion") == 0) + return args[0]; + } + + // $([MSBuild]::VersionGreaterThan / VersionGreaterThanOrEquals / + // VersionLessThan / VersionLessThanOrEquals / + // VersionEquals / VersionNotEquals(v1, v2)) + // Component-by-component numeric comparison; missing trailing components + // are treated as 0 (so '1.0' == '1.0.0'), which differs from the + // condition-expression < / > operators that use -1 for missing parts. + if (args.size() == 2 && + (caseInsensitiveStringCompare(member, "VersionGreaterThan") == 0 || + caseInsensitiveStringCompare(member, "VersionGreaterThanOrEquals") == 0 || + caseInsensitiveStringCompare(member, "VersionLessThan") == 0 || + caseInsensitiveStringCompare(member, "VersionLessThanOrEquals") == 0 || + caseInsensitiveStringCompare(member, "VersionEquals") == 0 || + caseInsensitiveStringCompare(member, "VersionNotEquals") == 0)) { + const auto parseVer = [](const std::string &s) -> std::vector { + std::vector parts; + std::size_t pos = 0; + while (pos <= s.size()) { + char *end = nullptr; + const long v = std::strtol(s.c_str() + pos, &end, 10); + if (end == s.c_str() + pos) + break; + parts.push_back(static_cast(v)); + pos = static_cast(end - s.c_str()); + if (pos < s.size() && s[pos] == '.') + ++pos; + else + break; + } + return parts; + }; + const std::vector lv = parseVer(args[0]); + const std::vector rv = parseVer(args[1]); + const std::size_t count = std::max(lv.size(), rv.size()); + int cmp = 0; + for (std::size_t i = 0; i < count && cmp == 0; ++i) { + const int l = (i < lv.size()) ? lv[i] : 0; + const int r = (i < rv.size()) ? rv[i] : 0; + if (l < r) + cmp = -1; + else if (l > r) + cmp = 1; + } + bool result = false; + if (caseInsensitiveStringCompare(member, "VersionGreaterThan") == 0) + result = cmp > 0; + else if (caseInsensitiveStringCompare(member, "VersionGreaterThanOrEquals") == 0) + result = cmp >= 0; + else if (caseInsensitiveStringCompare(member, "VersionLessThan") == 0) + result = cmp < 0; + else if (caseInsensitiveStringCompare(member, "VersionLessThanOrEquals") == 0) + result = cmp <= 0; + else if (caseInsensitiveStringCompare(member, "VersionEquals") == 0) + result = cmp == 0; + else // VersionNotEquals + result = cmp != 0; + return result ? "True" : "False"; + } + + if (args.empty()) { + if (caseInsensitiveStringCompare(member, "GetCurrentToolsVersion") == 0) + return "Current"; + } + } + + if (caseInsensitiveStringCompare(className, "System.Environment") == 0) { + // $([System.Environment]::GetEnvironmentVariable('NAME')) + if (caseInsensitiveStringCompare(member, "GetEnvironmentVariable") == 0 && args.size() == 1) { + const char *env = std::getenv(args[0].c_str()); + return env ? env : ""; + } + // $([System.Environment]::GetFolderPath(SpecialFolder.X)) + if (caseInsensitiveStringCompare(member, "GetFolderPath") == 0 && args.size() == 1) { + std::string folderName = args[0]; + static constexpr char specialFolderPrefix[] = "SpecialFolder."; + if (folderName.size() > sizeof(specialFolderPrefix) - 1 && + caseInsensitiveStringCompare(folderName.substr(0, sizeof(specialFolderPrefix) - 1), + specialFolderPrefix) == 0) { + folderName.erase(0, sizeof(specialFolderPrefix) - 1); + } + + if (caseInsensitiveStringCompare(folderName, "ProgramFiles") == 0) { + const char *pf = std::getenv("ProgramFiles"); + return pf ? pf : ""; + } + + if (caseInsensitiveStringCompare(folderName, "ProgramFilesX86") == 0) { + const char *pf86 = std::getenv("ProgramFiles(x86)"); + if (pf86) + return pf86; + + // On a 32-bit Windows environment there may be no separate + // ProgramFiles(x86); ProgramFiles is the x86 Program Files directory. + const char *pf = std::getenv("ProgramFiles"); + return pf ? pf : ""; + } + + return ""; + } + } + + if (caseInsensitiveStringCompare(className, "System.IO.Path") == 0) { + if (args.size() == 1) { + const std::string filename = Path::simplifyPath(Path::fromNativeSeparators(args[0])); // FIXME can this be relative (toAbsolute) + if (caseInsensitiveStringCompare(member, "GetFileName") == 0) + return stripDirectoryPart(filename); + if (caseInsensitiveStringCompare(member, "GetFileNameWithoutExtension") == 0) + return fileStem(stripDirectoryPart(filename)); + if (caseInsensitiveStringCompare(member, "GetDirectoryName") == 0) { + std::string path = Path::getPathFromFilename(filename); + if (!path.empty() && path.back() == '/') + path.pop_back(); + return path; + } + if (caseInsensitiveStringCompare(member, "GetExtension") == 0) + return Path::getFilenameExtension(filename); + if (caseInsensitiveStringCompare(member, "IsPathRooted") == 0) + return isPathRooted(filename) ? "True" : "False"; + if (caseInsensitiveStringCompare(member, "GetFullPath") == 0) { + if (classifyPath(filename) == PathKind::DriveRelative) { + addDebug("GetFullPath: drive-relative path cannot be resolved: " + filename); + return ""; + } + return toAbsolute(filename); + } + } + if (!args.empty() && caseInsensitiveStringCompare(member, "Combine") == 0) { + std::string result = args[0]; + for (std::size_t i = 1; i < args.size(); ++i) { + // cppcheck-suppress useStlAlgorithm + if (!pathCombineAppend(result, args[i], true)) { + addDebug("Path.Combine: could not combine path segments"); + return ""; + } + } + return result; + } + if (args.size() == 2) { + if (caseInsensitiveStringCompare(member, "GetFullPath") == 0) { + const std::string path = Path::fromNativeSeparators(args[0]); + const std::string basePath = Path::fromNativeSeparators(args[1]); + // Use classifyPath so that root-relative paths (\foo) are resolved + // against the drive of basePath rather than being misidentified as + // fully-absolute on Linux via Path::isAbsolute. + const PathKind pathKind = classifyPath(path); + const PathKind baseKind = classifyPath(basePath); + // basePath must be a fully-qualified absolute path; root-relative or + // relative bases cannot be used for GetFullPath resolution. + if (baseKind != PathKind::UNC && baseKind != PathKind::DriveAbsolute) + return ""; + switch (pathKind) { + case PathKind::UNC: + case PathKind::DriveAbsolute: + return Path::simplifyPath(path); + case PathKind::RootRelative: { + // "\foo" is rooted but not fully qualified. Resolve it using the + // volume/root represented by basePath. + if (baseKind == PathKind::DriveAbsolute) { + // "\foo" with "C:/base/" -> "C:/foo" + return Path::simplifyPath(basePath.substr(0, 2) + path); + } + + // A UNC base has a UNC root of "//server/share". + if (baseKind == PathKind::UNC) { + const std::size_t firstSlash = basePath.find('/', 2); + if (firstSlash == std::string::npos) + return ""; + + const std::size_t secondSlash = basePath.find('/', firstSlash + 1); + if (secondSlash == std::string::npos) + return Path::simplifyPath(basePath + path); + + return Path::simplifyPath(basePath.substr(0, secondSlash) + path); + } + + return ""; + } + case PathKind::DriveRelative: + // "C:foo" requires the per-drive current directory for drive C:, + // a Windows kernel concept unavailable in a cross-platform context. + addDebug("GetFullPath: drive-relative path cannot be resolved: " + path); + return ""; + default: { + std::string combined = basePath; + if (!combined.empty() && combined.back() != '/') + combined += '/'; + combined += path; + return Path::simplifyPath(combined); + } + } + } + } + } + + if (caseInsensitiveStringCompare(className, "System.String") == 0) { + if (caseInsensitiveStringCompare(member, "IsNullOrEmpty") == 0 && args.size() == 1) + return args[0].empty() ? "True" : "False"; + if (caseInsensitiveStringCompare(member, "IsNullOrWhiteSpace") == 0 && args.size() == 1) { + for (const char c : args[0]) { + // cppcheck-suppress useStlAlgorithm + if (!std::isspace(static_cast(c))) + return "False"; + } + return "True"; + } + if (caseInsensitiveStringCompare(member, "Concat") == 0) { + std::string result; + for (const std::string &a : args) + result += a; + return result; + } + if (caseInsensitiveStringCompare(member, "Join") == 0 && args.size() >= 2) { + std::string result; + for (std::size_t i = 1; i < args.size(); ++i) { + if (i > 1) + result += args[0]; + result += args[i]; + } + return result; + } + // Format: replace {n} and {n:spec} placeholders; {{ and }} are literal braces. + if (caseInsensitiveStringCompare(member, "Format") == 0 && !args.empty()) { + // Apply a .NET composite-format specifier to a string value. + const auto applySpec = [this](const std::string &arg, const std::string &spec) -> std::string { + if (spec.empty()) + return arg; + const char specChar = spec[0]; + + int width = -1; + if (spec.size() > 1) { + char *wend = nullptr; + const long w = std::strtol(spec.c_str() + 1, &wend, 10); + if (wend != spec.c_str() + 1 && *wend == '\0' && w >= 0) + width = static_cast(w); + } + + char *iend = nullptr; + const long long lval = std::strtoll(arg.c_str(), &iend, 10); + const bool isInt = !arg.empty() && iend != arg.c_str() && *iend == '\0'; + + char *dend = nullptr; + const double dval = std::strtod(arg.c_str(), &dend); + const bool isDouble = !arg.empty() && dend != arg.c_str() && *dend == '\0'; + + if (specChar == 'D' || specChar == 'd') { + if (!isInt) + return arg; + if (width > 0) { + // Handle negative numbers manually to keep '-' outside the zero padding + if (lval == LLONG_MIN) { + // Prevents -lval overflow: LLONG_MIN magnitude is 19 digits. + const std::string magnitude = "9223372036854775808"; + const int padding = width - static_cast(magnitude.size()); + + if (padding > 0) + return safeFormat("-%s%s", std::string(padding, '0').c_str(), magnitude.c_str()); + + return safeFormat("-%s", magnitude.c_str()); + } + if (lval < 0) + return safeFormat("-%0*lld", width, -lval); + return safeFormat("%0*lld", width, lval); + } + return safeFormat("%lld", lval); + } + + if (specChar == 'X' || specChar == 'x') { + if (!isInt) + return arg; + const auto uval = static_cast(lval); + if (width > 0) { + return specChar == 'X' + ? safeFormat("%0*llX", width, uval) + : safeFormat("%0*llx", width, uval); + } + return specChar == 'X' + ? safeFormat("%llX", uval) + : safeFormat("%llx", uval); + } + + if (specChar == 'F' || specChar == 'f') { + if (!isDouble) + return arg; + const int p = width >= 0 ? width : 2; + return safeFormat("%.*f", p, dval); + } + + if (specChar == 'E' || specChar == 'e') { + if (!isDouble) + return arg; + const int p = width >= 0 ? width : 6; + return specChar == 'E' + ? safeFormat("%.*E", p, dval) + : safeFormat("%.*e", p, dval); + } + + if (specChar == 'G' || specChar == 'g') { + if (!isDouble) + return arg; + // .NET G/g without explicit precision uses 15 significant digits + // for double; C's %g default of 6 is not the same thing. + const int p = width > 0 ? width : 15; + return specChar == 'G' + ? safeFormat("%.*G", p, dval) + : safeFormat("%.*g", p, dval); + } + // Unrecognised specifier -- return the raw argument unchanged and log + // so that MSBuild incompatibilities are visible rather than silent. + this->addDebug("String.Format: unsupported format specifier '" + spec + "'"); + return arg; + }; + + const std::string &fmt = args[0]; + std::string result; + result.reserve(fmt.size()); + for (std::size_t i = 0; i < fmt.size(); ++i) { + if (fmt[i] == '{') { + if (i + 1 < fmt.size() && fmt[i + 1] == '{') { + result += '{'; ++i; + continue; + } + const std::size_t close = fmt.find('}', i + 1); + if (close == std::string::npos) { + result += '{'; + continue; + } + const std::string inner = fmt.substr(i + 1, close - i - 1); + const std::size_t colon = inner.find(':'); + const std::string indexStr = inner.substr(0, colon == std::string::npos ? inner.size() : colon); + const std::string spec = colon != std::string::npos ? inner.substr(colon + 1) : ""; + char *end = nullptr; + const long idx = std::strtol(indexStr.c_str(), &end, 10); + if (end != indexStr.c_str() && *end == '\0' && idx >= 0) { + const std::size_t argIdx = static_cast(idx) + 1; + result += applySpec(argIdx < args.size() ? args[argIdx] : "", spec); + i = close; + } else { + result += '{'; + } + } else if (fmt[i] == '}' && i + 1 < fmt.size() && fmt[i + 1] == '}') { + result += '}'; ++i; + } else { + result += fmt[i]; + } + } + return result; + } + } + + if (caseInsensitiveStringCompare(className, "System.Math") == 0) { + const auto toDouble = [](const std::string &s, double &out) -> bool { + if (s.empty()) + return false; + char *end = nullptr; + out = std::strtod(s.c_str(), &end); + return end != s.c_str() && *end == '\0'; + }; + // Format a double as an integer string when the value is whole, + // otherwise use std::to_string (which gives 6 decimal places). + const auto fmtDouble = [](double d) -> std::string { + // Converting a double outside the long long range is undefined behaviour + if (d >= static_cast(LLONG_MIN) && d < -static_cast(LLONG_MIN)) { + const auto i = static_cast(d); + if (!(static_cast(i) < d) && !(static_cast(i) > d)) + return std::to_string(i); + } + return std::to_string(d); + }; + if (args.size() == 1) { + double x = 0; + if (toDouble(args[0], x)) { + if (caseInsensitiveStringCompare(member, "Abs") == 0) + return fmtDouble(x < 0 ? -x : x); + if (caseInsensitiveStringCompare(member, "Floor") == 0) + return fmtDouble(std::floor(x)); + if (caseInsensitiveStringCompare(member, "Ceiling") == 0) + return fmtDouble(std::ceil(x)); + if (caseInsensitiveStringCompare(member, "Round") == 0) { + // .NET Math.Round defaults to banker's rounding (round-half-to-even), + // not round-half-away-from-zero. + const double fl = std::floor(x); + const double frac = x - fl; + double rounded; + if (frac < 0.5) + rounded = fl; + else if (frac > 0.5) + rounded = fl + 1; + else // exactly 0.5 -- round to nearest even integer + rounded = (std::fabs(std::fmod(fl, 2.0)) < 1) ? fl : fl + 1; + return fmtDouble(rounded); + } + if (caseInsensitiveStringCompare(member, "Sqrt") == 0 && x >= 0) + return fmtDouble(std::sqrt(x)); + if (caseInsensitiveStringCompare(member, "Log") == 0 && x > 0) + return fmtDouble(std::log(x)); + if (caseInsensitiveStringCompare(member, "Log10") == 0 && x > 0) + return fmtDouble(std::log10(x)); + } + } + if (args.size() == 2) { + double a = 0, b = 0; + if (toDouble(args[0], a) && toDouble(args[1], b)) { + if (caseInsensitiveStringCompare(member, "Max") == 0) + return fmtDouble(a > b ? a : b); + if (caseInsensitiveStringCompare(member, "Min") == 0) + return fmtDouble(a < b ? a : b); + if (caseInsensitiveStringCompare(member, "Pow") == 0) + return fmtDouble(std::pow(a, b)); + } + } + } + + // $([MSBuild]::Escape / Unescape) -- encode/decode MSBuild special chars as %XX + if (caseInsensitiveStringCompare(className, "MSBuild") == 0 && args.size() == 1) { + if (caseInsensitiveStringCompare(member, "Escape") == 0) + return msbuildEscape(args[0]); + if (caseInsensitiveStringCompare(member, "Unescape") == 0) + return msbuildUnescape(args[0]); + // Bitwise operations + { + long long a = 0; + if (toInt(args[0], a)) { + if (caseInsensitiveStringCompare(member, "BitwiseNot") == 0) + return std::to_string(~a); + } + } + } + + if (caseInsensitiveStringCompare(className, "MSBuild") == 0 && args.size() == 2) { + long long a = 0, b = 0; + if (toInt(args[0], a) && toInt(args[1], b)) { + if (caseInsensitiveStringCompare(member, "BitwiseAnd") == 0) + return std::to_string(a & b); + if (caseInsensitiveStringCompare(member, "BitwiseOr") == 0) + return std::to_string(a | b); + if (caseInsensitiveStringCompare(member, "BitwiseXor") == 0) + return std::to_string(a ^ b); + } + } + + // $([MSBuild]::GetRegistryValue / GetRegistryValueFromView) + // Returns empty on non-Windows; on Windows would need registry access. + if (caseInsensitiveStringCompare(className, "MSBuild") == 0 && + (caseInsensitiveStringCompare(member, "GetRegistryValue") == 0 || + caseInsensitiveStringCompare(member, "GetRegistryValueFromView") == 0)) + return ""; + + // $([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform(...)) + // Arg is itself a static property like $([...OSPlatform]::Windows) which + // expands to the platform name string via the same mechanism. + if (caseInsensitiveStringCompare(className, "System.Runtime.InteropServices.RuntimeInformation") == 0 && + caseInsensitiveStringCompare(member, "IsOSPlatform") == 0 && args.size() == 1) + return applyMSBuildStaticFunction("MSBuild", "IsOSPlatform", args); + + // $([System.Runtime.InteropServices.OSPlatform]::Windows|Linux|OSX) -- static property + if (caseInsensitiveStringCompare(className, "System.Runtime.InteropServices.OSPlatform") == 0) + return member; // return the platform name ("Windows", "Linux", "OSX") as a string + + // $([System.IO.FileInfo]::new('path')) / $([System.IO.DirectoryInfo]::new('path')) + // Model as a normalized path string so .FullName / .DirectoryName / .Name chains work. + if ((caseInsensitiveStringCompare(className, "System.IO.FileInfo") == 0 || + caseInsensitiveStringCompare(className, "System.IO.DirectoryInfo") == 0) && + caseInsensitiveStringCompare(member, "new") == 0 && !args.empty()) { + std::string path = Path::fromNativeSeparators(args[0]); + + if (classifyPath(path) != PathKind::UNC && classifyPath(path) != PathKind::DriveAbsolute) + path = Path::fromNativeSeparators(Path::getCurrentPath()) + '/' + path; + + return Path::simplifyPath(std::move(path)); + } + // Unknown class or method -- return empty so import continues + addDebug("unknown class " + className + " or member " + member); + return ""; +} + +// Parse the "[ClassName]::Member" portion of a $([ClassName]::Member(...)) static +// function reference. On entry pos must point at '['; on return pos is advanced +// past the member name. className and member are appended (not assigned) so the +// caller can initialise them to "" before the call. +static void parseMSBuildStaticRef(const std::string &s, std::size_t &pos, + std::string &className, std::string &member) +{ + ++pos; // skip '[' + while (pos < s.size() && s[pos] != ']') + className += s[pos++]; + if (pos < s.size()) // skip ']' + ++pos; + if (pos + 1 < s.size() && s[pos] == ':' && s[pos + 1] == ':') + pos += 2; // skip '::' + while (pos < s.size()) { + const auto c = static_cast(s[pos]); + if (!std::isalnum(c) && c != '_') + break; + member += s[pos++]; + } +} + +// Expands $(Name) and $(Name.Method(args)) references in property value strings. +// Unknown variables are left unexpanded. Use expandPropertyValue() to invoke. +struct ImportProject::PropertyValueExpander { + ImportProject &mProject; + const PropertiesMap &mVars; + std::string mStr; + std::size_t mPos{0}; + bool mUnknownAsEmpty{ false }; + + PropertyValueExpander(ImportProject &project, const PropertiesMap &vars, std::string str, bool unknownAsEmpty = false) + : mProject(project), mVars(vars), mStr(std::move(str)), mUnknownAsEmpty(unknownAsEmpty) {} + PropertyValueExpander(const PropertyValueExpander &) = delete; + PropertyValueExpander &operator=(const PropertyValueExpander &) = delete; + + bool hasValue(const std::string &name) const { + if (mVars.count(name)) + return true; + return std::getenv(name.c_str()) != nullptr; + } + + std::string lookup(const std::string &name) const { + const auto it = mVars.find(name); + if (it != mVars.end()) + return it->second; + const char *env = std::getenv(name.c_str()); + return env ? env : std::string(); + } + + // Parses a PROPERTY name, handling nested $(...) within the name. + // MSBuild property names are [A-Za-z_][A-Za-z0-9_-]* -- unlike method + // names (see e.g. the inline scans in tryParseExpr() for '.Method(...)' + // chains, and parseMSBuildStaticRef()'s "[ClassName]::Member" scan, + // neither of which this function is used for), a property name MAY + // contain '-' after the first character, e.g. foo + // referenced as $(My-Property). This is the sole call site that reads + // the identifier directly after '$(' (see tryParseExpr()), so it's safe + // for this one to accept '-' without affecting method-name parsing. + std::string parseIdentifier() { + std::string result; + while (mPos < mStr.size()) { + if (mStr.compare(mPos, 2, "$(") == 0) { + result += tryParseExpr(); + continue; + } + const auto c = static_cast(mStr[mPos]); + if (!std::isalnum(c) && c != '_' && c != '-') + break; + result += mStr[mPos++]; + } + return result; + } + + // Parses one method argument: a quoted string literal (with inner $(...) + // expansion) or an unquoted $(...) reference or bare word. + std::string parseArg() { + while (mPos < mStr.size() && std::isspace(static_cast(mStr[mPos]))) + ++mPos; + if (mPos < mStr.size() && mStr[mPos] == '\'') { + ++mPos; + std::string s; + // Expand $(...) references embedded inside the quoted string so that + // e.g. '$(MSBuildThisFileDirectory)' is resolved before being passed + // to NormalizePath / NormalizeDirectory / Path.Combine, etc. + while (mPos < mStr.size() && mStr[mPos] != '\'') { + if (mStr.compare(mPos, 2, "$(") == 0) + s += tryParseExpr(); + else + s += mStr[mPos++]; + } + if (mPos < mStr.size()) // consume closing '\'' + ++mPos; + return s; + } + if (mStr.compare(mPos, 2, "$(") == 0) { + std::string s = tryParseExpr(); + // Append any trailing bare-word text so that unquoted args like + // $(ReactNativeDir)..\..\node_modules become one concatenated arg + // instead of two separate args. + while (mPos < mStr.size() && mStr[mPos] != ',' && mStr[mPos] != ')') + s += mStr[mPos++]; + return s; + } + // Bare word -- consume until ',' or ')'. + std::string s; + while (mPos < mStr.size() && mStr[mPos] != ',' && mStr[mPos] != ')') + s += mStr[mPos++]; + return s; + } + + // Apply a no-parenthesis property access (.Length, .FullName, .DirectoryName, .Name) + // to `value`. `context` is appended to the "unhandled" debug message. + void applyNoParenProperty(std::string &value, const std::string &method, const std::string &context) { + if (caseInsensitiveStringCompare(method, "Length") == 0) + value = std::to_string(value.size()); + else if (caseInsensitiveStringCompare(method, "FullName") == 0) + value = Path::simplifyPath(value); + else if (caseInsensitiveStringCompare(method, "DirectoryName") == 0) + value = Path::getPathFromFilename(value); + else if (caseInsensitiveStringCompare(method, "Name") == 0) + value = stripDirectoryPart(Path::fromNativeSeparators(value)); + else + mProject.addDebug("unhandled property access '." + method + "'" + context); + } + + // Parse a ')'-terminated comma-separated arg list starting at mPos (which + // must already point past the opening '('). Advances mPos past the closing ')'. + std::vector parseArgList() { + std::vector args; + while (mPos < mStr.size() && mStr[mPos] != ')') { + args.push_back(parseArg()); + while (mPos < mStr.size() && std::isspace(static_cast(mStr[mPos]))) + ++mPos; + if (mPos < mStr.size() && mStr[mPos] == ',') + ++mPos; + } + if (mPos < mStr.size()) // skip ')' + ++mPos; + return args; + } + + // Parses and evaluates $(Name[.Method(args)...]) starting at mPos. + // Also handles $([ClassName]::Method(args)) static property functions. + // Unknown properties expand to the empty string, except for explicitly + // recognized Visual Studio/MSBuild infrastructure properties which remain + // symbolic for importer/emulation detection. + std::string tryParseExpr() { + const std::size_t start = mPos; + mPos += 2; // skip "$(" + + // $([ClassName]::Method(args)) -- static property function + if (mPos < mStr.size() && mStr[mPos] == '[') { + std::string className, member; + parseMSBuildStaticRef(mStr, mPos, className, member); + std::vector args; + // Skip optional whitespace between method name and '(' -- legal in MSBuild. + while (mPos < mStr.size() && std::isspace(static_cast(mStr[mPos]))) + ++mPos; + if (mPos < mStr.size() && mStr[mPos] == '(') { + ++mPos; // skip '(' + args = parseArgList(); + } + std::string value = mProject.applyMSBuildStaticFunction(className, member, args, &mVars); + // Handle optional .Property or .Method(args) chain on the result, + // e.g. $([System.IO.FileInfo]::new('path').DirectoryName). + while (mPos < mStr.size() && mStr[mPos] == '.') { + ++mPos; + std::string chainMethod; + while (mPos < mStr.size()) { + const auto c = static_cast(mStr[mPos]); + if (!std::isalnum(c) && c != '_') + break; + chainMethod += mStr[mPos++]; + } + if (mPos >= mStr.size() || mStr[mPos] != '(') { + applyNoParenProperty(value, chainMethod, " after static function"); + continue; + } + ++mPos; // skip '(' + std::vector chainArgs = parseArgList(); + try { + value = applyPropertyMethod(value, chainMethod, chainArgs); + } catch (const std::exception &e) { + mProject.addDebug(std::string("applyPropertyMethod (chained): ") + e.what()); + } catch (...) { + mProject.addDebug("applyPropertyMethod (chained): unknown error for method '" + chainMethod + "'"); + } + } + if (mPos < mStr.size() && mStr[mPos] == ')') // skip outer ')' + ++mPos; + return value; + } + + const std::string name = parseIdentifier(); + if (name.empty() || !hasValue(name)) { + // mStr[start] is '$' and mStr[start + 1] is the opening '('. + const std::size_t end = findMatchingParen(mStr, start + 1); + mPos = (end != std::string::npos) ? end + 1 : mStr.size(); + return mUnknownAsEmpty ? std::string() : mStr.substr(start, mPos - start); + } + + std::string value = lookup(name); + // Parse optional .Method(args) chain. + while (mPos < mStr.size() && mStr[mPos] == '.') { + ++mPos; + std::string method; + while (mPos < mStr.size()) { + const auto c = static_cast(mStr[mPos]); + if (!std::isalnum(c) && c != '_') + break; + method += mStr[mPos++]; + } + if (mPos >= mStr.size() || mStr[mPos] != '(') { + // Property access without parentheses (e.g. $(Foo.Length)). + applyNoParenProperty(value, method, " on '" + name + "'"); + continue; + } + ++mPos; // skip '(' + std::vector args = parseArgList(); + try { + value = applyPropertyMethod(value, method, args); + } catch (const std::exception &e) { + mProject.addDebug(std::string("applyPropertyMethod: ") + e.what()); + } catch (...) { + mProject.addDebug("applyPropertyMethod: unknown error for method '" + method + "'"); + } + } + if (mPos < mStr.size() && mStr[mPos] == ')') // skip closing ')' + ++mPos; + return value; + } + + // Expand all property expressions in mStr in a single left-to-right pass. + // Property values are already evaluated when assigned; do not re-evaluate + // newly produced $(...) references in later passes. + std::string expand() { + mPos = 0; + std::string result; + result.reserve(mStr.size()); + while (mPos < mStr.size()) { + if (mStr.compare(mPos, 2, "$(") == 0) + result += tryParseExpr(); + else + result += mStr[mPos++]; + } + return result; + } +}; + +void ImportProject::expandMSBuildVariables(std::string &s, const PropertiesMap &properties) +{ + PropertyValueExpander expander{*this, properties, s}; + s = expander.expand(); +} + +ImportProject::Type ImportProject::import(const std::string &filename, Settings *settings, Suppressions *supprs) +{ + std::ifstream fin(filename); + if (!fin.is_open()) + return ImportProject::Type::MISSING; + + mPath = Path::getPathFromFilename(Path::fromNativeSeparators(filename)); + if (!mPath.empty() && !endsWith(mPath,'/')) + mPath += '/'; + + const std::vector fileFilters = + settings ? settings->fileFilters : std::vector(); + + if (endsWith(filename, ".json")) { + if (processCompileCommands(fin)) { + setRelativePaths(filename); + return ImportProject::Type::COMPILE_DB; + } + } else if (endsWith(filename, ".sln")) { + if (importSln(fin, filename, fileFilters)) { + setRelativePaths(filename); + return ImportProject::Type::VS_SLN; + } + } else if (endsWith(filename, ".slnx")) { + if (importSlnx(filename, fileFilters)) { + setRelativePaths(filename); + return ImportProject::Type::VS_SLNX; + } + } else if (endsWith(filename, ".vcxproj")) { + PropertiesMap mVariables; + if (importVcxproj(toAbsolute(filename), mVariables, fileFilters)) { + setRelativePaths(filename); + return ImportProject::Type::VS_VCXPROJ; + } + } else if (endsWith(filename, ".bpr")) { + if (importBcb6Prj(filename)) { + setRelativePaths(filename); + return ImportProject::Type::BORLAND; + } + } else if (settings && supprs && endsWith(filename, ".cppcheck")) { + if (importCppcheckGuiProject(fin, *settings, *supprs)) { + setRelativePaths(filename); + return ImportProject::Type::CPPCHECK_GUI; + } + } else { + return ImportProject::Type::UNKNOWN; + } + return ImportProject::Type::FAILURE; +} + +bool ImportProject::processCompileCommands(std::istream &istr) +{ + picojson::value compileCommands; + istr >> compileCommands; + if (!compileCommands.is()) { + errors.emplace_back("compilation database is not a JSON array"); + return false; + } + + std::map fsFileIds; + + for (const picojson::value &fileInfo : compileCommands.get()) { + picojson::object obj = fileInfo.get(); + + if (obj.count("directory") == 0) { + errors.emplace_back("'directory' field in compilation database entry missing"); + return false; + } + + if (!obj["directory"].is()) { + errors.emplace_back("'directory' field in compilation database entry is not a string"); + return false; + } + + std::string dirpath = Path::fromNativeSeparators(obj["directory"].get()); + + /* CMAKE produces the directory without trailing / so add it if not + * there - it is needed by setIncludePaths() */ + if (!endsWith(dirpath, '/')) + dirpath += '/'; + + const std::string directory = std::move(dirpath); + + std::vector arguments; + if (obj.count("arguments")) { + if (obj["arguments"].is()) { + for (const picojson::value& arg : obj["arguments"].get()) { + if (arg.is()) + arguments.push_back(arg.get()); + } + } else { + errors.emplace_back("'arguments' field in compilation database entry is not a JSON array"); + return false; + } + } else if (obj.count("command")) { + std::string command; + if (obj["command"].is()) { + command = obj["command"].get(); + } else { + errors.emplace_back("'command' field in compilation database entry is not a string"); + return false; + } + + std::string error = collectArgs(command, arguments); + if (!error.empty()) { + errors.emplace_back(error); + return false; + } + } else { + errors.emplace_back("no 'arguments' or 'command' field found in compilation database entry"); + return false; + } + + if (!obj.count("file") || !obj["file"].is()) { + errors.emplace_back("skip compilation database entry because it does not have a proper 'file' field"); + continue; + } + + std::string file = Path::fromNativeSeparators(obj["file"].get()); + + // Accept file? + if (!Path::acceptFile(file)) + continue; + + std::string path; + if (Path::isAbsolute(file)) + path = Path::simplifyPath(std::move(file)); +#ifdef _WIN32 + else if (file[0] == '/' && directory.size() > 2 && std::isalpha(directory[0]) && directory[1] == ':') + // directory: C:\foo\bar + // file: /xy/z.c + // => c:/xy/z.c + path = Path::simplifyPath(directory.substr(0,2) + file); +#endif + else + path = Path::simplifyPath(directory + file); + FileSettings fs{path, Standards::Language::None, 0}; // file will be identified later on + parseArgs(fs, arguments); + PropertiesMap properties; + fsSetIncludePaths(fs, directory, fs.includePaths, properties); + // Assign a unique index to each file path. If the file path already exists in the map, + // increment the index to handle duplicate file entries. + fs.file.setFsFileId(fsFileIds[path]++); + fileSettings.push_back(std::move(fs)); + } + + return true; +} + +void ImportProject::setSolution(const std::string &filename, PropertiesMap &properties) { + const std::string absolutePath = toAbsolute(filename); + properties["SolutionDir"] = Path::getPathFromFilename(absolutePath); + properties["SolutionExt"] = Path::getFilenameExtensionInLowerCase(absolutePath); + properties["SolutionPath"] = absolutePath; + properties["SolutionFileName"] = stripDirectoryPart(absolutePath); + properties["SolutionName"] = fileStem(properties["SolutionFileName"]); +} + +bool ImportProject::importSln(std::istream &istr, const std::string &filename, const std::vector &fileFilters) +{ + std::string line; + + debugs.clear(); + + // Strip trailing \r so that CRLF .sln files opened in text mode on Linux/macOS + // do not leave a trailing \r on every extracted value. + const auto stripCR = [](std::string &s) { + if (!s.empty() && s.back() == '\r') + s.pop_back(); + }; + + if (!std::getline(istr,line)) { + errors.emplace_back("Visual Studio solution file is empty"); + return false; + } + stripCR(line); + + // Strip UTF-8 BOM (\xEF\xBB\xBF) when present. + if (line.size() >= 3 && + static_cast(line[0]) == 0xEF && + static_cast(line[1]) == 0xBB && + static_cast(line[2]) == 0xBF) + line.erase(0, 3); + + // Visual Studio writes a blank line before the header (also handles a BOM-only line). + if (line.empty()) { + if (!std::getline(istr, line)) { + errors.emplace_back("Visual Studio solution file header not found"); + return false; + } + stripCR(line); + } + + if (!startsWith(line, "Microsoft Visual Studio Solution File")) { + errors.emplace_back("Visual Studio solution file header not found"); + return false; + } + + PropertiesMap solutionVariables; + setSolution(filename, solutionVariables); + + solutionVariables["VisualStudioVersion"] = "17.0"; + + const std::string solutionDir = solutionVariables["SolutionDir"]; + + // First pass: parse the solution file to collect the header properties and the list of vcxproj paths. + std::vector vcxprojs; + while (std::getline(istr,line)) { + stripCR(line); + if (startsWith(line, "VisualStudioVersion = ")) { + solutionVariables["VisualStudioVersion"] = line.substr(std::strlen("VisualStudioVersion = ")); + continue; + } + if (startsWith(line, "MinimumVisualStudioVersion = ")) { + solutionVariables["MinimumVisualStudioVersion"] = line.substr(std::strlen("MinimumVisualStudioVersion = ")); + continue; + } + if (!startsWith(line,"Project(")) + continue; + // Search for .vcxproj only in the path field (third quoted token), not in + // the project display name which precedes it. SLN line format is: + // Project("{TypeGUID}") = "DisplayName", "RelativePath", "{ProjGUID}" + // The separator between display name and path is the first ", " after ") = ". + const std::string::size_type eqMark = line.find(") = \""); + const std::string::size_type innerFind = (eqMark != std::string::npos) + ? line.find("\", \"", eqMark) + : std::string::npos; + const std::string::size_type searchStart = (innerFind != std::string::npos) ? innerFind + 3 : 0; + // searchStart points to the opening quote of the path field; extract it + // and check the extension properly rather than searching for a substring. + if (searchStart >= line.size() || line[searchStart] != '\"') + continue; + const std::string::size_type pathEnd = line.find('\"', searchStart + 1); + if (pathEnd == std::string::npos) + continue; + std::string vcxproj = line.substr(searchStart + 1, pathEnd - searchStart - 1); + if (Path::getFilenameExtensionInLowerCase(vcxproj) != ".vcxproj") + continue; + vcxproj = Path::toNativeSeparators(std::move(vcxproj)); + vcxproj = toAbsolute(vcxproj, solutionDir, solutionVariables); + vcxproj = Path::fromNativeSeparators(std::move(vcxproj)); + vcxprojs.push_back(std::move(vcxproj)); + } + + if (vcxprojs.empty()) { + errors.emplace_back("no projects found in Visual Studio solution file"); + return false; + } + + for (const std::string &vcxproj : vcxprojs) { + PropertiesMap mVariables = solutionVariables; + if (!importVcxproj(vcxproj, mVariables, fileFilters)) { + errors.emplace_back("failed to load '" + vcxproj + "' from Visual Studio solution"); + return false; + } + } + + return true; +} + +bool ImportProject::importSlnx(const std::string& filename, const std::vector& fileFilters) +{ + debugs.clear(); + + tinyxml2::XMLDocument doc; const tinyxml2::XMLError error = doc.LoadFile(filename.c_str()); if (error != tinyxml2::XML_SUCCESS) { errors.emplace_back(std::string("Visual Studio solution file is not a valid XML - ") + tinyxml2::XMLDocument::ErrorIDToName(error)); return false; } - const tinyxml2::XMLElement* const rootnode = doc.FirstChildElement(); - if (rootnode == nullptr) { - errors.emplace_back("Visual Studio solution file has no XML root node"); + const tinyxml2::XMLElement* const rootnode = doc.FirstChildElement(); + if (rootnode == nullptr) { + errors.emplace_back("Visual Studio solution file has no XML root node"); + return false; + } + + if (std::strcmp(rootnode->Name(), "Solution") != 0) { + errors.emplace_back("Invalid Visual Studio solution file format"); + return false; + } + + PropertiesMap solutionVariables; + setSolution(filename, solutionVariables); + + solutionVariables["VisualStudioVersion"] = "18.0"; + + bool found = false; + + auto processProject = [&](const tinyxml2::XMLElement* projectNode) -> bool { + const char* pathAttribute = projectNode->Attribute("Path"); + if (pathAttribute == nullptr) + return true; + + std::string vcxproj(pathAttribute); + vcxproj = Path::toNativeSeparators(std::move(vcxproj)); + + if (Path::getFilenameExtensionInLowerCase(vcxproj) != ".vcxproj") + return true; // skip other project types + + vcxproj = toAbsolute(vcxproj, solutionVariables["SolutionDir"], solutionVariables); + + vcxproj = Path::fromNativeSeparators(std::move(vcxproj)); + + PropertiesMap mVariables = solutionVariables; + if (!importVcxproj(vcxproj, mVariables, fileFilters)) { + errors.emplace_back("failed to load '" + vcxproj + "' from Visual Studio solution"); + return false; + } + found = true; + return true; + }; + + for (const tinyxml2::XMLElement* node = rootnode->FirstChildElement(); node; node = node->NextSiblingElement()) { + const char* name = node->Name(); + if (std::strcmp(name, "Project") == 0) { + if (!processProject(node)) + return false; + } else if (std::strcmp(name, "Folder") == 0) { + // Walk nested Folder/Project nodes recursively + std::function processFolder; + processFolder = [&](const tinyxml2::XMLElement *folder) -> bool { + for (const tinyxml2::XMLElement *child = folder->FirstChildElement(); child; child = child->NextSiblingElement()) { + const char *childName = child->Name(); + if (std::strcmp(childName, "Project") == 0) { + if (!processProject(child)) + return false; + } else if (std::strcmp(childName, "Folder") == 0) { + if (!processFolder(child)) + return false; + } + } + return true; + }; + if (!processFolder(node)) + return false; + } + } + + if (!found) { + errors.emplace_back("no projects found in Visual Studio solution file"); + return false; + } + + return true; +} + +ImportProject::ProjectConfiguration::ProjectConfiguration(const tinyxml2::XMLElement *cfg) { + const char *a = cfg->Attribute("Include"); + if (a) + name = a; + for (const tinyxml2::XMLElement *e = cfg->FirstChildElement(); e; e = e->NextSiblingElement()) { + const char * const text = e->GetText(); + if (!text) + continue; + const char * ename = e->Name(); + if (std::strcmp(ename,"Configuration")==0) + configuration = text; + else if (std::strcmp(ename,"Platform")==0) { + platformStr = text; + if (platformStr == "Win32") + platform = Win32; + else if (platformStr == "x64") + platform = x64; + else if (platformStr == "ARM64") + platform = ARM64; + else if (platformStr == "ARM64EC") + platform = ARM64EC; + else if (platformStr == "ARM") + platform = ARM; + else + platform = Unknown; + } + } +} + +void ImportProject::checkUnexpandedExpressions(const std::string &text, const char *context) +{ + // these are emulated so ignore them + if (text == "$(VCTargetsPath)/Microsoft.Cpp.targets" || + text == "$(VCTargetsPath)/Microsoft.Cpp.props" || + text == "$(VCTargetsPath)/Microsoft.Cpp.Default.props") + return; + + std::string::size_type pos = 0; + while ((pos = text.find("$(", pos)) != std::string::npos) { + const std::string::size_type end = findMatchingParen(text, pos + 1); + if (end == std::string::npos) + break; + const std::string propName = text.substr(pos + 2, end - pos - 2); + std::stringstream message; + message << "unexpanded property $(" + << propName + << ")" + << (context ? " in " : "") + << (context ? context : "") + << ": " << text; + addDebug(message.str()); + pos = end + 1; + } + pos = 0; + while ((pos = text.find("%(", pos)) != std::string::npos) { + const std::string::size_type end = findMatchingParen(text, pos + 1); + if (end == std::string::npos) + break; + std::stringstream message; + message << "unexpanded metadata %(" + << text.substr(pos + 2, end - pos - 2) + << ")" + << (context ? " in " : "") + << (context ? context : "") + << ": " << text; + addDebug(message.str()); + pos = end + 1; + } +} + +/** MSBuild version number: one or more dot-separated non-negative integers. + * + * Comparison follows .NET System.Version semantics: missing trailing + * components are treated as -1 rather than 0, so "17" < "17.0" (because + * new Version("17").Minor == -1 < 0 == new Version("17.0").Minor). + * + * Use MSBuildVersion::parse() to construct from a string. An empty + * (default-constructed or failed-parse) instance compares as invalid; + * callers should check empty() before comparing. + */ +class MSBuildVersion { +public: + MSBuildVersion() = default; + + /** Parse a version string ("17.0", "16.11.2", "v14.0", "17.0.0-preview.1"). + * Rules applied in order: + * 1. Strip a single leading 'v' or 'V'. + * 2. Truncate at the first '-' or '+' (semver pre-release / build-metadata). + * 3. Split the remainder on '.' and parse each segment as a non-negative integer. + * 4. Reject any component that is empty, contains whitespace, or is not a + * valid decimal integer (strtol would silently accept leading whitespace + * so we check explicitly). + * Returns an empty (invalid) instance on any parse failure. */ + static MSBuildVersion parse(const std::string &s) { + if (s.empty()) + return MSBuildVersion(); + + // Rule 1: optional leading 'v'/'V'. + std::size_t pos = (s[0] == 'v' || s[0] == 'V') ? 1 : 0; + if (pos == s.size()) + return MSBuildVersion(); + + // Rule 2: stop at the first pre-release or build-metadata separator. + const std::size_t sep = s.find_first_of("-+", pos); + const std::size_t limit = (sep == std::string::npos) ? s.size() : sep; + + MSBuildVersion ver; + while (pos < limit) { + const std::size_t dot = s.find('.', pos); + const std::size_t end = (dot == std::string::npos || dot > limit) ? limit : dot; + + // Rule 4a: reject empty components (e.g. "17..0" or trailing dot). + if (end == pos) + return MSBuildVersion(); + + // Rule 4b: reject leading whitespace -- strtol silently skips it. + if (std::isspace(static_cast(s[pos]))) + return MSBuildVersion(); + + // Rule 3: parse the numeric component. + const std::string part = s.substr(pos, end - pos); + char *endPtr = nullptr; + const long value = std::strtol(part.c_str(), &endPtr, 10); + + // Rule 4c: all characters must have been consumed and the value non-negative. + if (endPtr == part.c_str() || *endPtr != '\0' || value < 0) + return MSBuildVersion(); + + ver.mComponents.push_back(static_cast(value)); + + if (dot == std::string::npos || dot >= limit) + break; + pos = dot + 1; + } + + if (ver.mComponents.empty()) + return MSBuildVersion(); + + return ver; + } + + /** True when this instance could not be parsed or was default-constructed. */ + bool empty() const { + return mComponents.empty(); + } + + /** Return component \p i, or -1 if the version has fewer than i+1 components + * (.NET semantics: Major/Minor/Build/Revision default to -1). */ + int component(std::size_t i) const { + return (i < mComponents.size()) ? mComponents[i] : -1; + } + + bool operator==(const MSBuildVersion &rhs) const { + return cmp(rhs) == 0; + } + bool operator!=(const MSBuildVersion &rhs) const { + return cmp(rhs) != 0; + } + bool operator< (const MSBuildVersion &rhs) const { + return cmp(rhs) < 0; + } + bool operator> (const MSBuildVersion &rhs) const { + return cmp(rhs) > 0; + } + bool operator<=(const MSBuildVersion &rhs) const { + return cmp(rhs) <= 0; + } + bool operator>=(const MSBuildVersion &rhs) const { + return cmp(rhs) >= 0; + } + + // Apply an MSBuild comparison operator string. + bool compareOp(const std::string &op, const MSBuildVersion &rhs) const { + if (op == "==" || op == "!=") { + const std::size_t count = std::max(mComponents.size(), rhs.mComponents.size()); + + // For equality, MSBuild treats omitted trailing version components + // as zero: 1 == 1.0 == 1.0.0. + bool equal = true; + for (std::size_t i = 0; i < count; ++i) { + const int lhsComponent = i < mComponents.size() ? mComponents[i] : 0; + const int rhsComponent = i < rhs.mComponents.size() ? rhs.mComponents[i] : 0; + if (lhsComponent != rhsComponent) { + equal = false; + break; + } + } + + return op == "==" ? equal : !equal; + } + + if (op == "<") + return *this < rhs; + if (op == ">") + return *this > rhs; + if (op == "<=") + return *this <= rhs; + if (op == ">=") + return *this >= rhs; + + return false; + } + + std::string toString() const { + std::string s; + for (std::size_t i = 0; i < mComponents.size(); ++i) { + if (i > 0) + s += '.'; + s += std::to_string(mComponents[i]); + } + return s; + } + +private: + std::vector mComponents; + + int cmp(const MSBuildVersion &rhs) const { + const std::size_t count = std::max(mComponents.size(), rhs.mComponents.size()); + for (std::size_t i = 0; i < count; ++i) { + const int l = component(i); + const int r = rhs.component(i); + if (l < r) + return -1; + if (l > r) + return 1; + } + return 0; + } +}; + +// see https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-conditions +class ImportProject::ConditionParser { +public: + ConditionParser(ImportProject &project, const std::string &condition, const PropertiesMap &properties) + : mProject(project), mCondition(condition), mVariables(properties) {} + ConditionParser(const ConditionParser &) = delete; + ConditionParser &operator=(const ConditionParser &) = delete; + + bool parse() { + const std::string value = parseOr(); + + skipWhitespace(); + + if (mPos != mCondition.size()) { + if (mCondition[mPos] == ')') + throw std::runtime_error("unmatched ')' in condition " + mCondition); + + throw std::runtime_error("Invalid condition: '" + mCondition + "'"); + } + + if (value != "True" && value != "False") + throw std::runtime_error("Invalid condition: '" + mCondition + "'"); + + return value == "True"; + } + +private: + ImportProject &mProject; + const std::string &mCondition; + const PropertiesMap &mVariables; + std::size_t mPos = 0; + bool mEvaluate = true; // false while parsing a short-circuited operand + + void skipWhitespace() { + while (mPos < mCondition.size() && std::isspace(static_cast(mCondition[mPos]))) + ++mPos; + } + + bool match(const std::string &text) { + skipWhitespace(); + if (mCondition.compare(mPos, text.size(), text) != 0) + return false; + mPos += text.size(); + return true; + } + + bool matchWord(const std::string &word) { + skipWhitespace(); + if (mCondition.size() - mPos < word.size()) + return false; + for (std::size_t i = 0; i < word.size(); ++i) { + if (std::tolower(static_cast(mCondition[mPos + i])) != + std::tolower(static_cast(word[i]))) + return false; + } + + const std::size_t end = mPos + word.size(); + if (end < mCondition.size() && + (std::isalnum(static_cast(mCondition[end])) || mCondition[end] == '_')) + return false; + + mPos = end; + return true; + } + + void expect(const std::string &text) { + if (match(text)) + return; + + if (text == ")") + throw std::runtime_error("'(' without closing ')'!"); + + throw std::runtime_error("Expected '" + text + "' in condition '" + mCondition + "'"); + } + + std::string parseOr() { + std::string lhs = parseAnd(); + while (matchWord("or")) { + const bool savedEvaluate = mEvaluate; + if (lhs == "True") + mEvaluate = false; + const std::string rhs = parseAnd(); + mEvaluate = savedEvaluate; + if (lhs != "True") + lhs = (rhs == "True") ? "True" : "False"; + } + return lhs; + } + + std::string parseAnd() { + std::string lhs = parseUnary(); + while (matchWord("and")) { + const bool savedEvaluate = mEvaluate; + if (lhs == "False") + mEvaluate = false; + const std::string rhs = parseUnary(); + mEvaluate = savedEvaluate; + if (lhs != "False") + lhs = (rhs == "True") ? "True" : "False"; + } + return lhs; + } + + std::string parseUnary() { + if (match("!") || matchWord("not")) { + if (mPos == mCondition.size()) + throw std::runtime_error("Invalid condition: '" + mCondition + "'"); + return parseUnary() == "False" ? "True" : "False"; + } + + return parsePrimary(); + } + + std::string parsePrimary() { + skipWhitespace(); + + if (match("(")) { + std::string value = parseOr(); + expect(")"); + return value; + } + + if (matchWord("Exists")) + return parseExists(); + + if (matchWord("And") || matchWord("Or")) + throw std::runtime_error("Invalid condition: '" + mCondition + "'"); + + if (matchWord("HasTrailingSlash")) + return parseHasTrailingSlash(); + + return parseComparison(); + } + + std::string parseComparison() { + const std::string lhs = parseValue(); + skipWhitespace(); + + static constexpr const char *ops[] = { "==", "!=", "<=", ">=", "<", ">" }; + for (const char *op : ops) { + if (match(op)) { + const std::string rhs = parseValue(); + if (!mEvaluate) + return "False"; + return compare(lhs, op, rhs) ? "True" : "False"; + } + } + + // No operator -- normalize bare boolean-like values (e.g. from property + // expansion: $(EnableUnityBuild) == "true") so the rest of the parser, + // which uses exact "True"/"False" comparisons, sees a canonical form. + if (caseInsensitiveStringCompare(lhs, "true") == 0) + return "True"; + if (caseInsensitiveStringCompare(lhs, "false") == 0) + return "False"; + return lhs; + } + + std::string parseValue() { + skipWhitespace(); + + if (mPos >= mCondition.size()) + throw std::runtime_error("Missing operator"); + + if (matchWord("true")) + return "True"; + + if (matchWord("false")) + return "False"; + + if (mCondition[mPos] == '\'') + return parseString(); + + if (mCondition.compare(mPos, 2, "$(") == 0) + return parsePropertyExpression(); + + if (std::isdigit(static_cast(mCondition[mPos])) || + (mCondition[mPos] == '-' && + mPos + 1 < mCondition.size() && std::isdigit(static_cast(mCondition[mPos + 1])))) { + const std::size_t begin = mPos++; + + // Skip leading '-' when checking for hex prefix. + const std::size_t digitStart = mPos; + // Hex literal: ([-]?)0x or ([-]?)0X + if (mCondition[digitStart] == '0' && + digitStart + 1 < mCondition.size() && + (mCondition[digitStart + 1] == 'x' || mCondition[digitStart + 1] == 'X') && + digitStart + 2 < mCondition.size() && + std::isxdigit(static_cast(mCondition[digitStart + 2]))) { + mPos = digitStart + 2; // skip '0x'/'0X' + while (mPos < mCondition.size() && std::isxdigit(static_cast(mCondition[mPos]))) + ++mPos; + } else { + while (mPos < mCondition.size() && std::isdigit(static_cast(mCondition[mPos]))) + ++mPos; + } + + return mCondition.substr(begin, mPos - begin); + } + + const std::size_t begin = mPos; + while (mPos < mCondition.size()) { + const auto c = static_cast(mCondition[mPos]); + if (!std::isalnum(c) && c != '_' && c != '-' && c != '.') + break; + ++mPos; + } + + if (mPos != begin) + return mCondition.substr(begin, mPos - begin); + if (!mEvaluate) + return std::string(); + throw std::runtime_error("Unknown/unhandled operator/operand '" + mCondition.substr(mPos) + "'"); + } + + std::string parseString() { + ++mPos; + std::string value; + + while (mPos < mCondition.size()) { + const char c = mCondition[mPos++]; + + if (c == '\'') + return expandProperties(value); + + value += c; + } + + if (!mEvaluate) + return std::string(); + throw std::runtime_error("Can not tokenize condition"); + } + + static bool parseInteger(const std::string &s, long &value) + { + if (s.empty()) + return false; + + const char *begin = s.c_str(); + char *end = nullptr; + int base = 10; + + if (s.size() > 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { + begin += 2; + if (*begin == '\0') + return false; + base = 16; + } + + value = std::strtol(begin, &end, base); + return end != begin && *end == '\0'; + } + + // Parses a METHOD or chained-member name (e.g. the "Method" in + // $(Foo.Method(...)) or the "Member" in $([Class]::Member.Method(...))). + // MSBuild method/member names are [A-Za-z_][A-Za-z0-9_]* -- '-' is not + // valid here. See parsePropertyName() below for the different rule that + // applies to a PROPERTY name instead; do not use this function for one. + std::string parseMethodName() { + skipWhitespace(); + std::string result; + while (mPos < mCondition.size()) { + if (mCondition.compare(mPos, 2, "$(") == 0) { + result += parsePropertyExpression(); + continue; + } + const auto c = static_cast(mCondition[mPos]); + if (!std::isalnum(c) && c != '_') + break; + result += mCondition[mPos++]; + } + if (result.empty()) + throw std::runtime_error("Expected identifier in condition '" + mCondition + "'"); + return result; + } + + // Parses a PROPERTY name -- the "Name" in $(Name) or $(Name.Method(...)). + // MSBuild property names are [A-Za-z_][A-Za-z0-9_-]*: unlike a method or + // member name (parseMethodName() above), '-' IS valid here after the + // first character, e.g. foo referenced as + // $(My-Property). Use this only for the property-name position, never + // for a method/member name -- see parseMethodName()'s own doc comment. + std::string parsePropertyName() { + skipWhitespace(); + std::string result; + while (mPos < mCondition.size()) { + if (mCondition.compare(mPos, 2, "$(") == 0) { + result += parsePropertyExpression(); + continue; + } + const auto c = static_cast(mCondition[mPos]); + if (!std::isalnum(c) && c != '_' && c != '-') + break; + result += mCondition[mPos++]; + } + if (result.empty()) + throw std::runtime_error("Expected identifier in condition '" + mCondition + "'"); + return result; + } + + std::string parsePropertyExpression() { + expect("$("); + + // $([ClassName]::Method(args)) -- static property function + if (mPos < mCondition.size() && mCondition[mPos] == '[') { + std::string className, member; + parseMSBuildStaticRef(mCondition, mPos, className, member); + std::vector args; + skipWhitespace(); // allow space between method name and '(' + if (mPos < mCondition.size() && mCondition[mPos] == '(') { + ++mPos; // skip '(' + skipWhitespace(); + if (!match(")")) { + do { + args.push_back(parseValue()); + skipWhitespace(); + } while (match(",")); + expect(")"); + } + } + std::string value = mEvaluate ? mProject.applyMSBuildStaticFunction(className, member, args, &mVariables) : std::string(); + // Optional .Property / .Method(args) chain on the static-function result. + while (true) { + skipWhitespace(); + if (!match(".")) + break; + const std::string chainMethod = parseMethodName(); + if (!match("(")) { + if (mEvaluate) { + if (caseInsensitiveStringCompare(chainMethod, "Length") == 0) + value = std::to_string(value.size()); + else if (caseInsensitiveStringCompare(chainMethod, "FullName") == 0) + value = Path::simplifyPath(value); + else if (caseInsensitiveStringCompare(chainMethod, "DirectoryName") == 0) + value = Path::getPathFromFilename(value); + else if (caseInsensitiveStringCompare(chainMethod, "Name") == 0) + value = stripDirectoryPart(Path::fromNativeSeparators(value)); + else + mProject.addDebug("unhandled property access '." + chainMethod + "' after static function in condition"); + } + continue; + } + std::vector chainArgs; + skipWhitespace(); + if (!match(")")) { + do { + chainArgs.push_back(parseValue()); + } while (match(",")); + expect(")"); + } + value = mEvaluate ? applyPropertyMethod(std::move(value), chainMethod, chainArgs) : std::string(); + } + expect(")"); // outer closing paren of $(...) + return value; + } + + std::string value = getPropertyValue(parsePropertyName()); + + while (true) { + skipWhitespace(); + if (!match(".")) + break; + + const std::string method = parseMethodName(); + if (!match("(")) { + // Property access without parentheses (e.g. $(Foo.Length), $(Foo.Name)). + if (mEvaluate) { + if (caseInsensitiveStringCompare(method, "Length") == 0) + value = std::to_string(value.size()); + else if (caseInsensitiveStringCompare(method, "FullName") == 0) + value = Path::simplifyPath(value); + else if (caseInsensitiveStringCompare(method, "DirectoryName") == 0) + value = Path::getPathFromFilename(value); + else if (caseInsensitiveStringCompare(method, "Name") == 0) + value = stripDirectoryPart(Path::fromNativeSeparators(value)); + else + mProject.addDebug("unhandled property access '." + method + "' in condition"); + } + continue; + } + std::vector args; + skipWhitespace(); + if (!match(")")) { + do { + args.push_back(parseValue()); + } while (match(",")); + expect(")"); + } + value = mEvaluate ? applyPropertyMethod(std::move(value), method, args) : std::string(); + } + + expect(")"); + return value; + } + + std::string parseExists() { + expect("("); + std::string path = parseValue(); + expect(")"); + + // Apply the same normalization used by toAbsolute() / processImport(): + // 1. Normalize native separators so classifyPath() and rfind('/') work. + path = Path::fromNativeSeparators(std::move(path)); + // 2. If any $(Property) survived expansion (unknown property), we cannot + // resolve the path -- return "False" rather than querying the filesystem + // with a literal "$(...)" in the name. + if (path.find("$(") != std::string::npos) + return "False"; + // 3. Resolve non-absolute paths against the file containing this condition. + // Use classifyPath so that root-relative paths (\foo -> /foo after separator + // normalization) are not misidentified as absolute on Linux. + { + const PathKind pk = classifyPath(path); + if (pk != PathKind::UNC && pk != PathKind::DriveAbsolute) { + auto it = mVariables.find("MSBuildThisFileDirectory"); + if (it == mVariables.end()) + it = mVariables.find("ProjectDir"); + if (it != mVariables.end()) + path = it->second + path; + } + } + // 4. Canonicalize: collapse . / .. and remove double slashes. + path = Path::simplifyPath(std::move(path)); + + return (Path::isFile(path) || Path::isDirectory(path)) ? "True" : "False"; + } + + std::string parseHasTrailingSlash() { + expect("("); + const std::string value = parseValue(); + expect(")"); + + return (!value.empty() && (value.back() == '/' || value.back() == '\\')) + ? "True" + : "False"; + } + + std::string getPropertyValue(const std::string &name) const { + const auto it = mVariables.find(name); + if (it != mVariables.end()) + return it->second; + + const char *envValue = std::getenv(name.c_str()); + return envValue ? envValue : ""; + } + + std::string expandProperties(const std::string &input) const { + // Conditions use the same single-pass property expansion semantics. + PropertyValueExpander expander{mProject, mVariables, input, true}; + return expander.expand(); + } + + template + static bool applyComparison(const T &lhsValue, const T &rhsValue, const std::string &op) { + if (op == "==") + return lhsValue == rhsValue; + if (op == "!=") + return lhsValue != rhsValue; + if (op == "<") + return lhsValue < rhsValue; + if (op == ">") + return lhsValue > rhsValue; + if (op == "<=") + return lhsValue <= rhsValue; + if (op == ">=") + return lhsValue >= rhsValue; + throw std::runtime_error("Unsupported operation: " + op); + } + + bool compare(const std::string &lhs, const std::string &op, const std::string &rhs) const + { + // Real MSBuild documents <, >, <=, >= as usable only with numeric + // values -- a dotted, multi-component string like '17.9.34607.119' + // isn't a valid number, which is precisely why the dedicated + // $([MSBuild]::VersionGreaterThan(...)) family of functions exists. + // This emulation additionally accepts such version strings directly + // with these four RELATIONAL operators (matching how real vcxproj + // imports compare things like $(VCToolsVersion)), via + // MSBuildVersion::compareOp() below and the "Current" keyword + // handling right after this comment. + // + // == and != are different: MSBuild documents them as usable "with + // strings, or with numeric values" -- ordinary equality/inequality, + // numeric if both sides parse as plain numbers (see the + // parseInteger() case below -- unlike <,>,<=,>=, this uniform + // "numeric-if-possible" rule for ==/!= IS real, documented MSBuild + // behavior, e.g. '$(X)' == '01' matching '$(X)' == '1'), otherwise + // case-insensitive string comparison. That is NOT the same thing as + // the zero-padded, component-wise equality + // $([MSBuild]::VersionEquals(...)) implements (and that + // MSBuildVersion::compareOp()'s "==" branch below deliberately + // reproduces, for that function's use): applying THAT to a plain == + // would make e.g. '1.1' == '1.1.0' true, which is wrong -- that + // zero-padding is VersionEquals()'s own documented behavior, not + // =='s. So the version-comparison paths below (both the "Current" + // keyword and the general dotted-string one) are gated to the four + // relational operators only; ==/!= always fall through to the + // ordinary numeric-or-string handling at the bottom of this + // function. + const bool relational = (op == "<" || op == ">" || op == "<=" || op == ">="); + + if (relational) { + // MSBuild keyword "Current" represents the installed toolset version. + MSBuildVersion currentVersion; + const auto it = mVariables.find("VisualStudioVersion"); + if (it != mVariables.end()) + currentVersion = MSBuildVersion::parse(it->second); + + if (currentVersion.empty()) + currentVersion = MSBuildVersion::parse("18.0"); // VS 2026 fallback + + if (caseInsensitiveStringCompare(lhs, "Current") == 0) { + const MSBuildVersion rhsVersion = MSBuildVersion::parse(rhs); + if (!rhsVersion.empty()) + return currentVersion.compareOp(op, rhsVersion); + } + + if (caseInsensitiveStringCompare(rhs, "Current") == 0) { + const MSBuildVersion lhsVersion = MSBuildVersion::parse(lhs); + if (!lhsVersion.empty()) + return lhsVersion.compareOp(op, currentVersion); + } + } + + // MSBuild tries numeric comparison before other comparison types -- + // for every operator, == and != included (see this function's own + // comment above). + long lhsInt = 0; + long rhsInt = 0; + if (parseInteger(lhs, lhsInt) && parseInteger(rhs, rhsInt)) + return applyComparison(lhsInt, rhsInt, op); + + // Then it tries boolean comparison -- also for every operator. + const auto parseBoolean = [](const std::string &value, bool &result) { + if (caseInsensitiveStringCompare(value, "true") == 0) { + result = true; + return true; + } + if (caseInsensitiveStringCompare(value, "false") == 0) { + result = false; + return true; + } + return false; + }; + + bool lhsBool = false; + bool rhsBool = false; + if (parseBoolean(lhs, lhsBool) && parseBoolean(rhs, rhsBool)) + return applyComparison(lhsBool, rhsBool, op); + + if (relational) { + // Then it tries Version comparison -- relational operators only + // (see this function's own comment above). + const MSBuildVersion lhsVersion = MSBuildVersion::parse(lhs); + const MSBuildVersion rhsVersion = MSBuildVersion::parse(rhs); + if (!lhsVersion.empty() && !rhsVersion.empty()) + return lhsVersion.compareOp(op, rhsVersion); + } + + // Finally, == and != fall back to case-insensitive string comparison. + if (op == "==" || op == "!=") { + const bool equal = caseInsensitiveStringCompare(lhs, rhs) == 0; + return op == "==" ? equal : !equal; + } + + throw std::runtime_error("Cannot compare '" + lhs + "' and '" + rhs + "'"); + } +}; + +bool ImportProject::evalCondition(const std::string &condition, const PropertiesMap &properties) { + try { + return ConditionParser(*this, condition, properties).parse(); + } catch (const std::exception &e) { + // Malformed or unsupported condition syntax. Log so callers can + // distinguish "evaluated false" from "could not be evaluated" -- the + // build behavior (treat as false and continue) is unchanged. + addDebug(std::string("condition unsupported: '") + condition + "': " + e.what()); + return false; + } +} + +bool ImportProject::conditionIsTrue(const tinyxml2::XMLElement *node, const PropertiesMap &properties) { + // See mDiscovering's doc comment: while the discovery bootstrap scan is + // running, every Condition is treated as satisfied rather than evaluated + // against guessed property values. + if (mDiscovering) + return true; + const char *condAttr = node->Attribute("Condition"); + if (!condAttr) + return true; + return evalCondition(condAttr, properties); +} + +bool ImportProject::hasName(const tinyxml2::XMLElement *node, const char *nodeName, const PropertiesMap &properties) { + const char *name = node->Name(); + if (!name || std::strcmp(nodeName, name) != 0) + return false; + return conditionIsTrue(node, properties); +} + +bool ImportProject::hasNameAndLabel(const tinyxml2::XMLElement *node, const char *nodeName, const char *nodeAttr, const PropertiesMap &properties) { + const char *name = node->Name(); + const char *label = node->Attribute("Label"); + if (!name || !label || std::strcmp(nodeName, name) != 0 || std::strcmp(label, nodeAttr) != 0) + return false; + return conditionIsTrue(node, properties); +} + +bool ImportProject::hasNameAndNotLabel(const tinyxml2::XMLElement *node, const char *nodeName, const char *nodeAttr, const PropertiesMap &properties) { + const char *name = node->Name(); + if (!name || std::strcmp(nodeName, name) != 0) + return false; + const char *label = node->Attribute("Label"); + if (label && std::strcmp(label, nodeAttr) == 0) + return false; + return conditionIsTrue(node, properties); +} + +// Structural name check only -- unlike hasName(), does NOT evaluate `node`'s own +// Condition attribute. Microsoft documents that the Visual Studio C++ project +// system does not support Condition on project items themselves (item types +// governed by a rule definition, e.g. ClCompile/ClInclude/etc. inside an +// or ): "Conditions aren't supported for +// Project items (that is, item types that are treated as project items by +// rules definitions)." -- only Condition on the item's *metadata* children is +// honored there (see applyClCompileChild(), which still calls conditionIsTrue() +// for exactly that reason). Use this instead of hasName() when checking a +// project item element's name for that reason. +static bool hasElementName(const tinyxml2::XMLElement *node, const char *nodeName) { + const char *name = node->Name(); + return name && std::strcmp(nodeName, name) == 0; +} + +// Canonical key identifying one project/props/targets file within an evaluation: +// forward slashes, '.'/'..' collapsed, lower-cased (NTFS is case-insensitive, and +// MSBuild's own import bookkeeping is case-insensitive too). +static std::string importFileKey(const std::string &filename) +{ + std::string key = Path::simplifyPath(Path::fromNativeSeparators(filename)); + std::transform(key.begin(), key.end(), key.begin(), [](unsigned char c) { + return std::tolower(c); + }); + return key; +} + +bool ImportProject::importGraphDecision(bool conditionHolds, std::string &file) +{ + if (!mImportGraph.active) + return conditionHolds; + + auto &decisions = mImportGraph.decisions[mImportGraph.currentFile]; + + if (mImportGraph.replay) { + auto &pos = mImportGraph.cursor[mImportGraph.currentFile]; + if (pos >= decisions.size()) { + // Structural mismatch between the Properties pass and this replay pass -- + // should not happen (both walk the same, unchanged document), but fail + // closed (treat as not taken) rather than read out of bounds or guess. + addDebug("import graph replay desync in '" + mImportGraph.currentFile + "'"); + return false; + } + const ImportGraph::Decision &decision = decisions[pos++]; + file = decision.file; + return decision.taken; + } + + ImportGraph::Decision decision; + decision.taken = conditionHolds; + decision.file = conditionHolds ? file : std::string(); + decisions.push_back(std::move(decision)); + return conditionHolds; +} + +bool ImportProject::importGraphChooseDecision(bool matched, std::size_t &branch) +{ + if (!mImportGraph.active) + return matched; + + auto &decisions = mImportGraph.decisions[mImportGraph.currentFile]; + + if (mImportGraph.replay) { + auto &pos = mImportGraph.cursor[mImportGraph.currentFile]; + if (pos >= decisions.size()) { + addDebug("import graph replay desync in '" + mImportGraph.currentFile + "'"); + return false; + } + const ImportGraph::Decision &decision = decisions[pos++]; + branch = decision.branch; + return decision.taken; + } + + ImportGraph::Decision decision; + decision.taken = matched; + decision.branch = matched ? branch : 0; + decisions.push_back(std::move(decision)); + return matched; +} + +ImportProject::ImportResult ImportProject::attemptSyntheticImport(std::string file, + PropertiesMap &properties, + MetadataMap &metadata, + std::list &compileList, + std::list &projectConfigurationList, + std::unordered_set &importStack, + EvalPhase phase) +{ + if (!importGraphDecision(!file.empty(), file)) + return ImportResult::Ok; + + const ImportResult result = processImport(file, properties, metadata, compileList, projectConfigurationList, importStack, phase); + if (result > ImportResult::NotResolvable) + addDebug("Could not fully import \"" + file + "\" - " + importResultStr(result) + " (continuing)"); + return result; +} + +namespace { + // Trim leading and trailing ASCII whitespace in-place. + void trimWhitespace(std::string &s) { + const auto first = s.find_first_not_of(" \t\r\n"); + if (first == std::string::npos) { + s.clear(); + return; + } + s.erase(0, first); + s.erase(s.find_last_not_of(" \t\r\n") + 1); + } + + std::list toStringList(const std::string &s) { + std::list ret; + std::string::size_type pos1 = 0; + std::string::size_type pos2; + + while ((pos2 = s.find(';', pos1)) != std::string::npos) { + if (pos2 > pos1) { + std::string piece = s.substr(pos1, pos2 - pos1); + trimWhitespace(piece); + if (!piece.empty()) + ret.push_back(msbuildUnescape(piece)); + } + pos1 = pos2 + 1; + } + + if (pos1 < s.size()) { + std::string piece = s.substr(pos1); + trimWhitespace(piece); + if (!piece.empty()) + ret.push_back(msbuildUnescape(piece)); + } + + return ret; + } + + /// Return \p path with its root component stripped. + /// The path must already be normalised to forward slashes. + /// UNC paths "//server/share/foo/" -> "foo/" + /// Local paths "C:/foo/" -> "foo/" + /// The trailing separator (if any) is preserved unchanged. + std::string stripPathRoot(const std::string &path) { + if (path.size() >= 2 && path[0] == '/' && path[1] == '/') { + // UNC: //server/share/... -- skip server then share component. + const auto serverEnd = path.find('/', 2); + if (serverEnd == std::string::npos) + return {}; + const auto shareEnd = path.find('/', serverEnd + 1); + if (shareEnd == std::string::npos) + return {}; + return path.substr(shareEnd + 1); + } + // Local absolute (C:/...) or relative: strip up to and including the first '/'. + const auto pos = path.find('/'); + return (pos != std::string::npos) ? path.substr(pos + 1) : path; + } + + struct MSBuildThis { + PropertiesMap &propertiesMap; + std::string thisFile; + std::string thisFileName; + std::string thisFileExtension; + std::string thisFileDirectory; + std::string thisFileDirectoryNoRoot; + std::string thisFileFullPath; + + MSBuildThis(const std::string &filename, PropertiesMap &properties) + : propertiesMap(properties) + , thisFile(properties["MSBuildThisFile"]) + , thisFileName(properties["MSBuildThisFileName"]) + , thisFileExtension(properties["MSBuildThisFileExtension"]) + , thisFileDirectory(properties["MSBuildThisFileDirectory"]) + , thisFileDirectoryNoRoot(properties["MSBuildThisFileDirectoryNoRoot"]) + , thisFileFullPath(properties["MSBuildThisFileFullPath"]) { + setMSBuildThis(filename, properties); + } + MSBuildThis(const MSBuildThis &) = delete; + MSBuildThis &operator=(const MSBuildThis &) = delete; + + static void setMSBuildThis(const std::string &filename, PropertiesMap &properties) { + // Normalize once so all subsequent path ops can assume '/' separators. + const std::string nfilename = Path::simplifyPath(Path::fromNativeSeparators(filename)); + properties["MSBuildThisFileFullPath"] = nfilename; + const std::string thisFile = stripDirectoryPart(nfilename); + properties["MSBuildThisFile"] = thisFile; + properties["MSBuildThisFileName"] = fileStem(thisFile); + properties["MSBuildThisFileDirectory"] = Path::getPathFromFilename(nfilename); + properties["MSBuildThisFileDirectoryNoRoot"] = stripPathRoot(Path::getPathFromFilename(nfilename)); + properties["MSBuildThisFileExtension"] = Path::getFilenameExtensionInLowerCase(nfilename); + } + + ~MSBuildThis() { + propertiesMap["MSBuildThisFile"] = thisFile; + propertiesMap["MSBuildThisFileName"] = thisFileName; + propertiesMap["MSBuildThisFileExtension"] = thisFileExtension; + propertiesMap["MSBuildThisFileDirectory"] = thisFileDirectory; + propertiesMap["MSBuildThisFileDirectoryNoRoot"] = thisFileDirectoryNoRoot; + propertiesMap["MSBuildThisFileFullPath"] = thisFileFullPath; + } + }; + + // Sets ImportProject::mDiscovering for the lifetime of the discovery bootstrap + // scan in importVcxproj(), and guarantees it is cleared again even if that scan + // exits early -- a stuck mDiscovering would silently make every subsequent + // Condition in the whole import (not just this project) evaluate as true. + struct DiscoveringGuard { + bool &mFlag; + + explicit DiscoveringGuard(bool &flag) : mFlag(flag) { + mFlag = true; + } + DiscoveringGuard(const DiscoveringGuard &) = delete; + DiscoveringGuard &operator=(const DiscoveringGuard &) = delete; + + ~DiscoveringGuard() { + mFlag = false; + } + }; + + struct ImportStackGuard { + std::unordered_set &mStack; + std::string mKey; + + ImportStackGuard(std::unordered_set &stack, std::string key) + : mStack(stack), mKey(std::move(key)) {} + ImportStackGuard(const ImportStackGuard &) = delete; + ImportStackGuard &operator=(const ImportStackGuard &) = delete; + + ~ImportStackGuard() { + mStack.erase(mKey); + } + }; + + // Tracks, for the duration of one processImport() call, which container file's + // children are being walked -- so importGraphDecision() keys the decision it + // records/replays by the right file. Restores the caller's file on return so + // recursion unwinds back to the correct context. + struct CurrentFileGuard { + std::string &mCurrent; + std::string mPrev; + + CurrentFileGuard(std::string ¤t, std::string next) + : mCurrent(current), mPrev(current) { + mCurrent = std::move(next); + } + CurrentFileGuard(const CurrentFileGuard &) = delete; + CurrentFileGuard &operator=(const CurrentFileGuard &) = delete; + + ~CurrentFileGuard() { + mCurrent = std::move(mPrev); + } + }; +} + +std::string ImportProject::toAbsoluteExpanded(const std::string &filename, const std::string &baseDir) +{ + if (filename.empty()) + return filename; + + const std::string normalized = Path::simplifyPath(filename); + + // classifyPath(), not the host-dependent Path::isAbsolute(), decides + // whether `normalized` is already fully rooted -- see the doc comment + // above pathCombineAppend() for why Path::isAbsolute() is avoided + // throughout this file: on a non-Windows host it only recognizes a + // leading '/', so a Windows drive-absolute path straight out of a + // .vcxproj, e.g. "C:/src/foo.cpp", would be misclassified as relative + // and joined onto baseDir -- "/C:/src/foo.cpp" -- which is not + // what Visual Studio does, and exactly the kind of host-dependent + // divergence this emulation exists to avoid. UNC and DriveAbsolute are + // the only PathKinds Path::isAbsolute() itself ever recognized as + // absolute, even natively on Windows (a bare "\foo" or "C:foo" both + // return false from it there too), so restricting to those two here + // keeps this in exact agreement with the previous, Windows-native + // behaviour -- just no longer host-dependent. + switch (classifyPath(normalized)) { + case PathKind::UNC: + case PathKind::DriveAbsolute: + return normalized; + default: + break; + } + + return Path::simplifyPath(Path::join(baseDir, normalized)); +} + +std::string ImportProject::toAbsolute(const std::string &path) +{ + std::string internal(Path::fromNativeSeparators(path)); + switch (classifyPath(internal)) { + case PathKind::UNC: + case PathKind::DriveAbsolute: + return Path::simplifyPath(internal); + case PathKind::RootRelative: { + // Inherit drive letter from CWD: "\foo" on drive C: -> "C:/foo" + const std::string cwd = Path::fromNativeSeparators(Path::getCurrentPath()); + const std::string drive = (cwd.size() >= 2 && + std::isalpha(static_cast(cwd[0])) && + cwd[1] == ':') ? cwd.substr(0, 2) : std::string(); + return Path::simplifyPath(drive + internal); + } + default: + return Path::simplifyPath(Path::fromNativeSeparators(Path::getCurrentPath()) + "/" + internal); + } +} + +std::string ImportProject::toAbsolute(const std::string &filename, const std::string &baseDir, const PropertiesMap &properties) +{ + std::string resolved(Path::fromNativeSeparators(filename)); + if (!simplifyPathWithVariables(resolved, properties)) + return resolved; + + switch (classifyPath(resolved)) { + case PathKind::UNC: + case PathKind::DriveAbsolute: + return Path::simplifyPath(resolved); + case PathKind::RootRelative: { + // Inherit drive letter from baseDir: "\foo" with base "C:/project/" -> "C:/foo" + const std::string drive = (baseDir.size() >= 2 && + std::isalpha(static_cast(baseDir[0])) && + baseDir[1] == ':') ? baseDir.substr(0, 2) : std::string(); + return Path::simplifyPath(drive + resolved); + } + case PathKind::DriveRelative: + // "C:foo" is relative to the current directory of drive C:, a per-drive + // CWD that is a Windows kernel concept unavailable in a cross-platform + // context. Return the path unmodified rather than inventing a wrong base. + addDebug("toAbsolute: drive-relative path cannot be resolved: " + resolved); + return resolved; + default: + return Path::simplifyPath(baseDir + resolved); + } +} + +bool ImportProject::simplifyPathWithVariables(std::string &s, const PropertiesMap &properties) +{ + // Normalize native separators before expansion so the expander sees clean + // paths and debug messages report '/' not '\\'. + s = Path::fromNativeSeparators(std::move(s)); + expandMSBuildVariables(s, properties); + checkUnexpandedExpressions(s, "path"); + if (s.find("$(") != std::string::npos) return false; + // Property values substituted above may also carry native separators; normalize again. + s = Path::fromNativeSeparators(std::move(s)); + s = Path::simplifyPath(std::move(s)); + return true; +} + +void ImportProject::fsSetIncludePaths(FileSettings &fs, const std::string &basepath, const std::list &in, const PropertiesMap &properties) +{ + std::set found; + // NOLINTNEXTLINE(performance-unnecessary-copy-initialization) + const std::list copyIn(in); + fs.includePaths.clear(); + for (const std::string &ipath : copyIn) { + if (ipath.empty()) + continue; + if (startsWith(ipath, "%(")) + continue; + std::string s(Path::fromNativeSeparators(ipath)); + + if (s.find("$(") != std::string::npos) { + // MSBuild expands an undefined property to the empty string, so + // Visual Studio sees "$(Undefined)include" as "include" (relative to + // the project directory) and "$(Undefined)\include" as "\include" + // (root of the project directory's drive). Do the same here instead of keeping + // the literal "$(...)" text, which can never name a real directory. + // + // It's not possible to determine whether an unknown property is + // intentional or a deficiency in the importer, so expand once with + // unknowns preserved purely to report them as debug messages. + // Debug messages are not currently communicated to the GUI or CLI + // from importers. + std::string withUnknowns = s; + expandMSBuildVariables(withUnknowns, properties); + checkUnexpandedExpressions(withUnknowns, "include path"); + + PropertyValueExpander expander{*this, properties, s, /*unknownAsEmpty*/ true}; + s = Path::fromNativeSeparators(expander.expand()); + trimWhitespace(s); + // An entry consisting only of unknown macros is dropped by Visual Studio. + if (s.empty()) + continue; + } + + // simplifyPath can crash if path ends with '/'. Keep the separator of a + // bare root ("/", "C:/") so its PathKind doesn't change. + while (s.size() > 1U && endsWith(s, '/') && + !(s.size() == 3U && s[1] == ':')) + s.pop_back(); + + // Resolve the entry the same way as every other path in a project file: + // absolute and UNC paths are kept, root-relative paths ("\include") + // take the drive letter of the project directory, and relative paths + // are relative to the project directory. + s = toAbsolute(s, basepath, properties); + if (s.empty()) + continue; + if (!endsWith(s, '/')) + s += '/'; + if (found.insert(s).second) + fs.includePaths.push_back(std::move(s)); + } +} + +static void findAndReplaceCaseInsensitive(std::string &s, + const std::string &search, + const std::string &replacement) +{ + if (search.empty()) + return; + + std::size_t pos = 0; + while ((pos = s.find(search[0], pos)) != std::string::npos) { + if (pos + search.size() <= s.size() && + caseInsensitiveStringCompare(s.substr(pos, search.size()), search) == 0) { + s.replace(pos, search.size(), replacement); + pos += replacement.size(); + } else { + ++pos; + } } +} - std::map variables; - variables["SolutionDir"] = Path::simplifyPath(Path::getPathFromFilename(filename)); +void ImportProject::addProperty(const tinyxml2::XMLElement *node, PropertiesMap &properties) { + const char *eName = node->Name(); + if (!eName || !conditionIsTrue(node, properties)) + return; + const char *eText = node->GetText(); + std::string text(eText ? eText : ""); + // Trim indentation/newlines from pretty-printed XML text content. + trimWhitespace(text); + // Normalize native path separators before expansion so property values are + // stored with '/' and debug messages show normalized paths. + text = Path::fromNativeSeparators(std::move(text)); + const std::string selfRef = "$(" + std::string(eName) + ")"; + // Properties are evaluated at assignment time. Only the explicit self-reference + // used for MSBuild-style accumulation needs to be substituted from the old value. + const auto it = properties.find(eName); + const std::string original = (it != properties.end()) ? it->second : std::string(); + findAndReplaceCaseInsensitive(text, selfRef, original); + expandMSBuildVariables(text, properties); + properties[eName] = text; + if (caseInsensitiveStringCompare(eName, "ConfigurationType") == 0) { + std::string targetExtension = ".exe"; // Windows standard floor default + bool extDetermined = false; + + if (caseInsensitiveStringCompare(text, "StaticLibrary") == 0) { + targetExtension = ".lib"; + extDetermined = true; + } else if (caseInsensitiveStringCompare(text, "DynamicLibrary") == 0) { + targetExtension = ".dll"; + extDetermined = true; + } else if (caseInsensitiveStringCompare(text, "Application") == 0) { + targetExtension = ".exe"; + extDetermined = true; + } else if (caseInsensitiveStringCompare(text, "Makefile") == 0 || + caseInsensitiveStringCompare(text, "Utility") == 0) { + targetExtension = ""; // Explicitly clear target extension for meta/script nodes + extDetermined = true; + } - bool found = false; - std::vector sharedItemsProjects; + // Only overwrite TargetExt if the project file hasn't already provided + // an explicit, dedicated user extension override. + if (properties.find("TargetExt") == properties.end() || extDetermined) { + properties["TargetExt"] = targetExtension; + } - for (const tinyxml2::XMLElement* node = rootnode->FirstChildElement(); node; node = node->NextSiblingElement()) { - const char* name = node->Name(); - if (std::strcmp(name, "Project") == 0) { - const char* labelAttribute = node->Attribute("Path"); - if (labelAttribute) { - std::string vcxproj(labelAttribute); - vcxproj = Path::toNativeSeparators(std::move(vcxproj)); - if (!Path::isAbsolute(vcxproj)) - vcxproj = variables["SolutionDir"] + vcxproj; - vcxproj = Path::fromNativeSeparators(std::move(vcxproj)); - if (!importVcxproj(vcxproj, variables, "", fileFilters, sharedItemsProjects)) { - errors.emplace_back("failed to load '" + vcxproj + "' from Visual Studio solution"); - return false; + // TargetName defaults to ProjectName when ConfigurationType is established + if (properties.find("TargetName") == properties.end() && properties.count("ProjectName") > 0) { + properties["TargetName"] = properties["ProjectName"]; + } + } + + checkUnexpandedExpressions(text, eName); +} + +void ImportProject::addMetadata(const tinyxml2::XMLElement *node, const PropertiesMap &properties, MetadataMap &metadata) { + const char *eName = node->Name(); + if (!eName || !conditionIsTrue(node, properties)) + return; + const char *eText = node->GetText(); + std::string text(eText ? eText : ""); + trimWhitespace(text); + text = Path::fromNativeSeparators(std::move(text)); + const std::string metaSelfRef = "%(" + std::string(eName) + ")"; + const std::string propSelfRef = "$(" + std::string(eName) + ")"; + + // Pre-expand the accumulated metadata value before embedding it: resolve %(other) + // metadata refs and $(prop) refs inside `original` first, then break any tainted + // self-references, so they don't propagate into the new value and risk step-3 erasure. + std::string original = metadata[eName]; + findAndReplaceCaseInsensitive(original, metaSelfRef, ""); + std::string::size_type p = 0; + + // Add a substitution tracking depth limit to explicitly catch and break + // circular/infinite macro evaluation loops from malformed recursive property sheets. + std::size_t substitutionDepth = 0; + while ((p = original.find("%(", p)) != std::string::npos) { + if (++substitutionDepth > 100) { + addDebug("Circular metadata inheritance chain broken in addMetadata for: " + std::string(eName)); + break; + } + + const std::string::size_type e = findMatchingParen(original, p + 1); + if (e == std::string::npos) + break; + const std::string key = original.substr(p + 2, e - p - 2); + const auto it = metadata.find(key); + const std::string repl = (it != metadata.end()) ? it->second : std::string(); + original.replace(p, e - p + 1, repl); + p += repl.size(); + } + + expandMSBuildVariables(original, properties); + findAndReplaceCaseInsensitive(original, propSelfRef, ""); + findAndReplaceCaseInsensitive(text, metaSelfRef, original); + + std::string::size_type pos = 0; + std::size_t textSubstitutionDepth = 0; + while ((pos = text.find("%(", pos)) != std::string::npos) { + if (++textSubstitutionDepth > 100) { + addDebug("Circular text metadata reference chain broken in addMetadata for: " + std::string(eName)); + break; + } + + const std::string::size_type end = findMatchingParen(text, pos + 1); + if (end == std::string::npos) + break; + const std::string key = text.substr(pos + 2, end - pos - 2); + const auto it = metadata.find(key); + const std::string replacement = (it != metadata.end()) ? it->second : std::string(); + text.replace(pos, end - pos + 1, replacement); + pos += replacement.size(); + } + + expandMSBuildVariables(text, properties); + + // Handle $(eName) self-references: some props files use property-style + // accumulation (e.g. $(DisableSpecificWarnings);4100 + // ) in ItemDefinitionGroup blocks. Property (and + // metadata) names are case-insensitive in MSBuild, matching the + // case-insensitive findAndReplaceCaseInsensitive() used for propSelfRef and + // metaSelfRef everywhere else in this function and in getMetadata() below -- + // the plain, case-sensitive findAndReplace() (lib/utils.h, used elsewhere for + // unrelated literal placeholder substitution) would miss a self-reference + // spelled with different casing than eName. + findAndReplaceCaseInsensitive(text, propSelfRef, original); + findAndReplaceCaseInsensitive(text, propSelfRef, ""); + metadata[eName] = text; + checkUnexpandedExpressions(text, eName); +} + +std::string ImportProject::getMetadata(const tinyxml2::XMLElement *node, const PropertiesMap &properties, const MetadataMap &metadata, const std::string &original) { + const char *eName = node->Name(); + if (!eName || !conditionIsTrue(node, properties)) + return original; + // An explicitly empty element () is a meaningful assignment + // to ""; do NOT treat GetText()==nullptr as "no change". + const char *eText = node->GetText(); + std::string text(eText ? eText : ""); + trimWhitespace(text); + text = Path::fromNativeSeparators(std::move(text)); + const std::string metaSelfRef = "%(" + std::string(eName) + ")"; + const std::string propSelfRef = "$(" + std::string(eName) + ")"; + + // Pre-expand `original` (the prior per-item value) before embedding it, + // matching the same strategy used in addMetadata and addProperty. + std::string expandedOriginal = original; + findAndReplaceCaseInsensitive(expandedOriginal, metaSelfRef, ""); + std::string::size_type p = 0; + + // Add a substitution tracking depth limit to explicitly catch and break + // circular/infinite macro evaluation loops from malformed recursive property sheets. + std::size_t substitutionDepth = 0; + while ((p = expandedOriginal.find("%(", p)) != std::string::npos) { + if (++substitutionDepth > 100) { + addDebug("Circular metadata inheritance chain broken in getMetadata for: " + std::string(eName)); + break; + } + + const std::string::size_type e = findMatchingParen(expandedOriginal, p + 1); + if (e == std::string::npos) + break; + const std::string key = expandedOriginal.substr(p + 2, e - p - 2); + const auto it = metadata.find(key); + const std::string repl = (it != metadata.end()) ? it->second : std::string(); + expandedOriginal.replace(p, e - p + 1, repl); + p += repl.size(); + } + + expandMSBuildVariables(expandedOriginal, properties); + findAndReplaceCaseInsensitive(expandedOriginal, propSelfRef, ""); + findAndReplaceCaseInsensitive(text, metaSelfRef, expandedOriginal); + + std::string::size_type pos = 0; + std::size_t textSubstitutionDepth = 0; + while ((pos = text.find("%(", pos)) != std::string::npos) { + if (++textSubstitutionDepth > 100) { + addDebug("Circular text metadata reference chain broken in getMetadata for: " + std::string(eName)); + break; + } + + const std::string::size_type end = findMatchingParen(text, pos + 1); + if (end == std::string::npos) + break; + const std::string key = text.substr(pos + 2, end - pos - 2); + const auto it = metadata.find(key); + const std::string replacement = (it != metadata.end()) ? it->second : std::string(); + text.replace(pos, end - pos + 1, replacement); + pos += replacement.size(); + } + + expandMSBuildVariables(text, properties); + + // Handle $(eName) self-references: same accumulation pattern as addMetadata. + findAndReplaceCaseInsensitive(text, propSelfRef, expandedOriginal); + findAndReplaceCaseInsensitive(text, propSelfRef, ""); + checkUnexpandedExpressions(text, eName); + return text; +} + +const std::string &ImportProject::importResultStr(ImportProject::ImportResult result) { + static const std::string ok("ok"); + static const std::string notResolvable("Not Resolvable"); + static const std::string notFound("Not Found"); + static const std::string notValid("Not Valid"); + static const std::string cycle("Cycle"); + static const std::string unknown("Unknown"); + + switch (result) { + case ImportProject::ImportResult::Ok: + return ok; + case ImportProject::ImportResult::NotResolvable: + return notResolvable; + case ImportProject::ImportResult::NotFound: + return notFound; + case ImportProject::ImportResult::NotValid: + return notValid; + case ImportProject::ImportResult::Cycle: + return cycle; + } + return unknown; +} + +void ImportProject::applyClCompileChild(const tinyxml2::XMLElement *e1, + const PropertiesMap &properties, + MetadataMap &metadata) +{ + const char *eName = e1->Name(); + if (!eName || !conditionIsTrue(e1, properties)) + return; + + // Visual Studio permits any item metadata to be overridden at item level. + // Keep the same metadata expansion and inheritance behavior used by + // ItemDefinitionGroup processing. + auto &value = metadata[eName]; + value = getMetadata(e1, properties, metadata, value); +} + +static void applyAdditionalOptions(MetadataMap &metadata) +{ + const std::string &additionalOptions = metadata["AdditionalOptions"]; + if (additionalOptions.empty()) + return; + + std::vector args; + std::string arg; + bool quoted = false; + + // Tokenize options by space boundaries, honoring double-quote scopes + for (std::size_t i = 0; i < additionalOptions.size(); ++i) { + const char c = additionalOptions[i]; + if (c == '"') { + quoted = !quoted; + } else if (std::isspace(static_cast(c)) && !quoted) { + if (!arg.empty()) { + args.emplace_back(std::move(arg)); + arg.clear(); + } + } else { + arg += c; + } + } + if (!arg.empty()) + args.emplace_back(std::move(arg)); + + for (std::size_t i = 0; i < args.size(); ++i) { + const std::string &origOption = args[i]; + if (origOption.empty()) + continue; + + std::string option = origOption; + std::transform(option.begin(), option.end(), option.begin(), [](unsigned char c) { + return std::tolower(c); + }); + + // Ensure token starts with valid MSVC/GCC switch symbols + if (option[0] != '/' && option[0] != '-') + continue; + + // 1. Precise Match for Preprocessor Definitions (/D or -D) + if (option.size() >= 2 && option[1] == 'd') { + // Verify this isn't a long system flag prefix like /debug or /diagnostics + if (option.size() == 2 || (option.compare(0, 12, "/diagnostics") != 0 && option.compare(0, 12, "-diagnostics") != 0 && + option.compare(0, 6, "/debug") != 0 && option.compare(0, 6, "-debug") != 0 && + option.compare(0, 12, "/dynamicbase") != 0 && option.compare(0, 12, "-dynamicbase") != 0 && + option.compare(0, 6, "/delay") != 0 && option.compare(0, 6, "-delay") != 0 && + option.compare(0, 4, "/doc") != 0 && option.compare(0, 4, "-doc") != 0)) { + + std::string define; + if (option.size() == 2) { + // Form: /D MacroName (Space-separated) + if (i + 1 < args.size()) { + define = args[i + 1]; + args[i + 1] = ""; // Neutralize lookahead token to skip it cleanly next round + } + } else { + // Form: /DMacroName (Glued payload) + define = origOption.substr(2); + } + + if (!define.empty()) { + if (!metadata["PreprocessorDefinitions"].empty()) + metadata["PreprocessorDefinitions"] += ';'; + metadata["PreprocessorDefinitions"] += define; + } + continue; + } + } + + // 2. Precise Match for Include Directories (/I or -I) + if (option.size() >= 2 && option[1] == 'i') { + // Explicitly shield long option variants like GCC's -isystem + if (option.compare(0, 8, "-isystem") != 0 && option.compare(0, 8, "/isystem") != 0) { + + std::string path; + if (option.size() == 2) { + // Form: /I PathName (Space-separated) + if (i + 1 < args.size()) { + path = args[i + 1]; + args[i + 1] = ""; // Neutralize lookahead token to skip it cleanly next round + } + } else { + // Form: /IPathName (Glued payload) + path = origOption.substr(2); + } + + if (!path.empty()) { + if (!metadata["AdditionalIncludeDirectories"].empty()) + metadata["AdditionalIncludeDirectories"] += ';'; + metadata["AdditionalIncludeDirectories"] += path; } - found = true; + continue; + } + } + + // 3. Strict String Matching for Language Standards + if (option == "/std:c++11" || option == "-std=c++11") { + metadata["LanguageStandard"] = "stdcpp11"; + } else if (option == "/std:c++14" || option == "-std=c++14") { + metadata["LanguageStandard"] = "stdcpp14"; + } else if (option == "/std:c++17" || option == "-std=c++17") { + metadata["LanguageStandard"] = "stdcpp17"; + } else if (option == "/std:c++20" || option == "-std=c++20") { + metadata["LanguageStandard"] = "stdcpp20"; + } else if (option == "/std:c++23" || option == "-std=c++23") { + metadata["LanguageStandard"] = "stdcpp23"; + } else if (option == "/std:c++latest" || option == "-std=c++latest") { + metadata["LanguageStandard"] = "stdcpplatest"; + } else if (option == "/std:c11" || option == "-std=c11") { + metadata["LanguageStandard_C"] = "stdc11"; + } else if (option == "/std:c17" || option == "-std=c17") { + metadata["LanguageStandard_C"] = "stdc17"; + } else if (option == "/std:clatest" || option == "-std=clatest") { + metadata["LanguageStandard_C"] = "stdclatest"; + } + } +} + +// Visual Studio's C++ project system does not reliably resolve a macro in a +// project item path if that macro's value COULD differ by configuration: +// "Macros that change their value for different configurations will cause +// problems... The IDE doesn't expect project item paths to be different for +// different project configurations." That is a statement about macros whose +// value varies by configuration, not about user-defined macros in general -- +// a macro a property sheet or PropertyGroup sets to a fixed location (e.g. +// $(BoostRoot) or $(wxMathPlot) pointing at a third-party library, unconditional +// on Configuration/Platform) resolves the same way regardless of which +// configuration the IDE happens to evaluate, so real Visual Studio expands it +// in an Include/Update/Remove path exactly as it would anywhere else. +// +// A small, fixed set of MSBuild "this file"/"this project" location +// properties are ALWAYS config-invariant by construction, no matter what a +// given project does with them -- these are exactly what Visual Studio's own +// Shared Items projects (.vcxitems) use to write portable Include paths, e.g. +// (see +// test/cli/shared-items-project). A property this project's own PropertyGroups +// never touch at all but that resolves to a real OS environment variable is +// config-invariant too -- it has exactly one value for the whole cppcheck +// invocation, and MSBuild property evaluation itself falls back to the +// environment for anything no PropertyGroup sets (PropertyValueExpander::lookup() +// already does this; the check here just has to agree, or expandMSBuildVariables() +// below would never even be called to do it). Everything else is only as +// safe as this particular project makes it: mConfigInvariantProperties (see +// its doc comment in importproject.h) is computed per project file by +// priming every configuration's Properties pass before the real +// per-configuration passes run, and holds every property name that came out +// with the same value in all of them. +// MSBuild property names are case-insensitive -- $(msbuildprojectdirectory) +// and $(MSBuildProjectDirectory) name the same built-in property to real +// Visual Studio -- so this uses the same cppcheck::stricmp comparator +// PropertiesMap itself does, not a plain (case-sensitive) std::set. +static const std::set &invariantItemPathProperties() { + static const std::set props = { + "MSBuildThisFileDirectory", "MSBuildThisFileFullPath", "MSBuildThisFile", + "MSBuildThisFileName", "MSBuildThisFileExtension", "MSBuildThisFileDirectoryNoRoot", + "MSBuildProjectDirectory", "MSBuildProjectFullPath", "MSBuildProjectFile", + "MSBuildProjectName", "MSBuildProjectExtension", "MSBuildProjectDirectoryNoRoot", + "SolutionDir", "ProjectDir", + }; + return props; +} + +// Returns whether every $(...) reference in `spec` is a bare reference (no +// .Method(...) chain, no $([Class]::...) static function) to a property that +// is always config-invariant (invariantItemPathProperties() above), that +// this project's configInvariant set says is config-invariant here (every +// configuration of THIS project resolved it to the same value -- see +// mConfigInvariantProperties's doc comment), or that resolves via a real OS +// environment variable of the same name (see this function's own doc +// comment above). A false result means `spec` depends on something Visual +// Studio's C++ project system does not reliably resolve in a project item +// path -- expandItemSpec() must not expand it, though (see its own comment) +// that does not mean discarding the item. +static bool hasOnlyInvariantItemPathVariables(const std::string &spec, const std::set &configInvariant) { + std::size_t pos = 0; + while ((pos = spec.find("$(", pos)) != std::string::npos) { + std::size_t i = pos + 2; + std::string name; + // MSBuild property names are [A-Za-z_][A-Za-z0-9_-]* -- '-' is valid + // after the first character (see PropertyValueExpander::parseIdentifier()'s + // doc comment) -- and this scan is only ever used for a bare $(Name) + // property reference (the caller requires the very next character to + // be ')', never '.' or '('), so there's no method name to worry about + // conflating this with here. + while (i < spec.size() && (std::isalnum(static_cast(spec[i])) || spec[i] == '_' || spec[i] == '-')) + name += spec[i++]; + if (name.empty() || i >= spec.size() || spec[i] != ')' || + (!invariantItemPathProperties().count(name) && !configInvariant.count(name) && + std::getenv(name.c_str()) == nullptr)) + return false; + pos = i + 1; + } + return true; +} + +std::pair ImportProject::expandItemSpec(const std::string &spec, + const std::string &projectDir, + const PropertiesMap &properties) +{ + if (spec.empty()) + return std::make_pair(std::string(), std::string()); + + std::string expandedSpec = spec; + if (hasOnlyInvariantItemPathVariables(spec, mConfigInvariantProperties)) { + // Every $(...) reference here is either one of Visual Studio's own + // "this file"/"this project" properties, a property this project + // resolves to the same value in every configuration, or a real + // environment variable -- all config-invariant, so expanding it here + // matches what the IDE itself does (see invariantItemPathProperties() + // and mConfigInvariantProperties). + expandMSBuildVariables(expandedSpec, properties); + } else if (spec.find("$(") != std::string::npos) { + // Some other macro reference, one whose value could differ by + // configuration in this project and that no environment variable + // resolves either -- Visual Studio's C++ project system does not + // support that in a project item path (see + // invariantItemPathProperties()'s doc comment), so it is not + // expanded here. + // + // That does NOT mean discarding the item, unlike the wildcard/glob + // and semicolon-list cases below: a silently vanished ClCompile item + // leaves the user with no idea their file went unchecked, short of + // rerunning with --debug to see this addDebug() line. Instead, + // `expandedSpec` is left as-is (the literal, unexpanded "$(...)" + // text stays in it) and falls through the rest of this function to + // become the item's resolved path below. That literal path can never + // exist on disk, so when cppcheck later tries to actually open it, + // simplecpp's normal FileStream failure path reports it exactly like + // any other missing source file -- a visible "File is missing: ..." + // error citing this literal, still-macro'd path -- which also tells + // the user precisely which macro it was that didn't resolve. + addDebug("Macro in item path not supported by Visual Studio IDE layout, left unexpanded: '" + spec + "'"); + } + + if (expandedSpec.find(';') != std::string::npos) { + addDebug("Multiple files or semicolon-delimited list detected in path attribute, skipped: '" + spec + "'"); + return std::make_pair(std::string(), std::string()); + } + + // Treat the ENTIRE expanded string as a single literal path. + // Visual Studio IDE does NOT split 'Include', 'Update', or 'Remove' attributes by semicolons. + std::size_t lo = 0, hi = expandedSpec.size(); + while (lo < hi && std::isspace(static_cast(expandedSpec[lo]))) ++lo; + while (hi > lo && std::isspace(static_cast(expandedSpec[hi - 1]))) --hi; + + if (lo < hi) { + std::string trimmed = expandedSpec.substr(lo, hi - lo); + + // Strip outer encapsulation quotes if present across the entire path string + if (trimmed.size() >= 2 && trimmed.front() == '"' && trimmed.back() == '"') { + trimmed = trimmed.substr(1, trimmed.size() - 2); + } + + const std::string decoded = msbuildUnescape(trimmed); + + // Visual Studio IDE rejects item wildcards/globs entirely during project loading + if (decoded.find('*') != std::string::npos || decoded.find('?') != std::string::npos) { + addDebug("ClCompile item glob not supported by Visual Studio IDE layout, skipped: '" + decoded + "'"); + return std::make_pair(std::string(), std::string()); + } + + // Return a single token pair mapping directly to one literal path on disk + return std::make_pair(decoded, toAbsoluteExpanded(decoded, projectDir)); + } + + return std::make_pair(std::string(), std::string()); +} + +void ImportProject::applyClCompileUpdate(const tinyxml2::XMLElement *node, + const std::string &baseDir, + const PropertiesMap &properties, + std::list &compileList) +{ + const char *update = node->Attribute("Update"); + if (!update) + return; + + const std::pair updateItem = expandItemSpec(update, baseDir, properties); + + if (updateItem.first.empty()) + return; + + for (ItemGroupClCompile &compile : compileList) { + if (Path::sameFileName(updateItem.second, compile.filename)) { + for (const tinyxml2::XMLElement *child = node->FirstChildElement(); + child; + child = child->NextSiblingElement()) { + applyClCompileChild(child, properties, compile.metadata); } + applyAdditionalOptions(compile.metadata); } } +} + +void ImportProject::applyClCompileRemove(const tinyxml2::XMLElement *node, + const std::string &baseDir, + const PropertiesMap &properties, + std::list &compileList) +{ + const char *remove = node->Attribute("Remove"); + if (!remove) + return; + + const std::pair removeItem = expandItemSpec(remove, baseDir, properties); + + if (removeItem.first.empty()) + return; + + for (auto it = compileList.begin(); it != compileList.end();) { + if (Path::sameFileName(removeItem.second, it->filename)) + it = compileList.erase(it); + else + ++it; + } +} - if (!found) { - errors.emplace_back("no projects found in Visual Studio solution file"); - return false; +ImportProject::ImportResult ImportProject::processCompile(const tinyxml2::XMLElement *node, + const std::string &projectDir, + const PropertiesMap &properties, + const MetadataMap &metadata, + std::list &compileList) +{ + const char *include = node->Attribute("Include"); + if (!include) + return ImportResult::NotFound; + + const std::pair spec = expandItemSpec(include, projectDir, properties); + if (spec.first.empty()) + return ImportResult::NotFound; + + const std::string &toInclude = spec.second; + if (!Path::acceptFile(toInclude)) + return ImportResult::Ok; + + ItemGroupClCompile compile(toInclude); + compile.metadata = metadata; + + const std::string base = stripDirectoryPart(toInclude); + const std::string::size_type dot = base.rfind('.'); + const std::string ext = dot != std::string::npos ? base.substr(dot) : std::string(); + const std::string stem = fileStem(base); + + std::string rootDir; + std::size_t afterRoot = 0; + if (toInclude.size() >= 2 && toInclude[0] == '/' && toInclude[1] == '/') { + const std::size_t srv = toInclude.find('/', 2); + const std::size_t shr = (srv != std::string::npos) ? toInclude.find('/', srv + 1) : std::string::npos; + if (shr != std::string::npos) { + rootDir = toInclude.substr(0, shr + 1); + afterRoot = shr + 1; + } else { + rootDir = toInclude; + } + } else if (toInclude.size() >= 2 && + std::isalpha(static_cast(toInclude[0])) && + toInclude[1] == ':') { + if (toInclude.size() > 2 && toInclude[2] == '/') { + rootDir = toInclude.substr(0, 2) + "/"; + afterRoot = 3; + } else { + afterRoot = 2; + } + } else if (!toInclude.empty() && toInclude[0] == '/') { + rootDir = "/"; + afterRoot = 1; } + const std::size_t lastSlash = toInclude.rfind('/'); + const std::string directory = (lastSlash != std::string::npos && lastSlash >= afterRoot) + ? toInclude.substr(afterRoot, lastSlash - afterRoot + 1) + : std::string(); + + compile.metadata["Identity"] = Path::fromNativeSeparators(spec.first); + compile.metadata["FullPath"] = toInclude; + compile.metadata["RootDir"] = rootDir; + compile.metadata["Filename"] = stem; + compile.metadata["Extension"] = ext; + compile.metadata["Directory"] = directory; + compile.metadata["RecursiveDir"] = std::string(); + + const std::string origNorm = Path::fromNativeSeparators(spec.first); + const std::size_t relSlash = origNorm.rfind('/'); + compile.metadata["RelativeDir"] = (relSlash != std::string::npos) + ? origNorm.substr(0, relSlash + 1) + : std::string(); + + for (const tinyxml2::XMLElement *e1 = node->FirstChildElement(); e1; e1 = e1->NextSiblingElement()) + applyClCompileChild(e1, properties, compile.metadata); + + applyAdditionalOptions(compile.metadata); + + compileList.emplace_back(std::move(compile)); + return ImportResult::Ok; +} - return true; +// Looks up `name` in `properties`, returning its value or an empty string if absent +static std::string propertyOrEmpty(const PropertiesMap &properties, const std::string &name) +{ + const auto it = properties.find(name); + return it != properties.end() ? it->second : std::string(); } -namespace { - struct ProjectConfiguration { - ProjectConfiguration() = default; - explicit ProjectConfiguration(const tinyxml2::XMLElement *cfg) { - const char *a = cfg->Attribute("Include"); - if (a) - name = a; - for (const tinyxml2::XMLElement *e = cfg->FirstChildElement(); e; e = e->NextSiblingElement()) { - const char * const text = e->GetText(); - if (!text) - continue; - const char * ename = e->Name(); - if (std::strcmp(ename,"Configuration")==0) - configuration = text; - else if (std::strcmp(ename,"Platform")==0) { - platformStr = text; - if (platformStr == "Win32") - platform = Win32; - else if (platformStr == "x64") - platform = x64; - else - platform = Unknown; - } - } +ImportProject::ImportResult ImportProject::processImportProject(const tinyxml2::XMLElement *node, + const std::string &projectDir, + PropertiesMap &properties, + MetadataMap &metadata, + std::list &compileList, + std::list &projectConfigurationList, + std::unordered_set &importStack, + EvalPhase phase) { + // Structural check: is `node` an element at all? Callers + // no longer pre-filter this (see the call sites) -- whether it is actually taken + // is decided/replayed below via importGraphDecision(), which must run for every + // structurally-matching element, including ones whose Condition is false, so the + // decision sequence recorded per file stays positionally aligned with what + // ItemDefs/Items replay. + const char *elementName = node->Name(); + if (!elementName || std::strcmp(elementName, "Import") != 0) + return ImportResult::Ok; + const char *projectAttribute = node->Attribute("Project"); + if (!projectAttribute) + return ImportResult::Ok; + // During the discovery pass, re-run as a Properties-phase import but silently + // discard any errors/debugs it generates -- approximate properties cause many + // spurious failures that would confuse the user. + if (phase == EvalPhase::Discover) { + const auto errSize = errors.size(); + const auto dbgSize = debugs.size(); + const ImportResult r = processImportProject(node, projectDir, properties, metadata, compileList, + projectConfigurationList, importStack, EvalPhase::Properties); + errors.resize(errSize); + debugs.resize(dbgSize); + return r; + } + // Resolve this 's target and decide (Properties pass) or replay + // (ItemDefs/Items pass) whether it is taken. See importGraphDecision() for why + // ItemDefs/Items must reuse the file Properties actually resolved rather than + // recomputing toAbsolute() against properties that may have changed since. + std::string file = toAbsolute(projectAttribute, projectDir, properties); + if (!importGraphDecision(conditionIsTrue(node, properties), file)) + return ImportResult::Ok; + + const std::string extension = Path::getFilenameExtensionInLowerCase(file); + if (extension == ".props" || extension == ".targets" || extension == ".vcxitems") { + const char *sdk = node->Attribute("Sdk"); + if (sdk) { + addDebug("Could not import \"" + file + "\" - " + " (Sdk not supported)"); + return ImportResult::NotResolvable; } - std::string name; - std::string configuration; - enum : std::uint8_t { Win32, x64, Unknown } platform = Unknown; - std::string platformStr; - }; - struct ConditionalGroup { - explicit ConditionalGroup(const tinyxml2::XMLElement *idg){ - const char *condAttr = idg->Attribute("Condition"); - if (condAttr) - mCondition = condAttr; - } + // Identify well-known system files by their logical filename rather than a + // substring search on the full path. This avoids false positives when a + // user file's path happens to contain "Microsoft.Cpp.props" etc. + const auto filenameIs = [&file](const char *name) { + const std::string::size_type slash = file.rfind('/'); + const std::string part = (slash != std::string::npos) ? file.substr(slash + 1) : file; + const std::string::size_type paren = part.rfind(')'); + const std::string namePart = (paren != std::string::npos) ? part.substr(paren + 1) : part; + return caseInsensitiveStringCompare(namePart, name) == 0; + }; - static void replaceAll(std::string &c, const std::string &from, const std::string &to) { - std::string::size_type pos; - while ((pos = c.find(from)) != std::string::npos) { - c.erase(pos,from.size()); - c.insert(pos,to); + if (filenameIs("Microsoft.Cpp.targets")) { + attemptSyntheticImport(propertyOrEmpty(properties, "ForceImportBeforeCppTargets"), + properties, metadata, compileList, projectConfigurationList, importStack, phase); + + // Microsoft.Common.targets (imported by Microsoft.Cpp.targets) sets + // OutputPath = $(OutDir) when OutputPath is not already defined. + // Emulate this before importing Directory.Build.targets so that files + // in that chain (e.g. bundle output paths) can expand $(OutputPath). + // Property assignment only happens during the Properties pass; ItemDefs/Items + // reuse the properties Properties already finished computing. + if (phase == EvalPhase::Properties && properties.find("OutputPath") == properties.end()) { + const auto outDirIt = properties.find("OutDir"); + if (outDirIt != properties.end()) + properties["OutputPath"] = outDirIt->second; } - } - // see https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-conditions - // properties are .NET String objects and you can call any of its members on them - bool conditionIsTrue(const ProjectConfiguration &p, const std::string &filename, std::vector &errors) const { - if (mCondition.empty()) - return true; - try { - return evalCondition(mCondition, p); - } - catch (const std::runtime_error& r) - { - errors.emplace_back(filename + ": Can not evaluate condition '" + mCondition + "': " + r.what()); - return false; + // Emulate key side-effect: Microsoft.Cpp.targets -> Microsoft.Common.targets -> Directory.Build.targets. + // $(ImportDirectoryBuildTargets) defaults to true; an explicit "false" + // suppresses the import entirely. $(DirectoryBuildTargetsPath), when set, + // overrides the upward directory search with an explicit file. See + // Microsoft's "Customize your build by folder or solution" docs. + const auto importTargetsIt = properties.find("ImportDirectoryBuildTargets"); + if (importTargetsIt == properties.end() || caseInsensitiveStringCompare(importTargetsIt->second, "false") != 0) { + const auto targetsPathIt = properties.find("DirectoryBuildTargetsPath"); + const std::string directoryBuildTargets = (targetsPathIt != properties.end() && !targetsPathIt->second.empty()) + ? targetsPathIt->second + : findFileAbove(projectDir, "Directory.Build.targets"); + attemptSyntheticImport(directoryBuildTargets, + properties, metadata, compileList, projectConfigurationList, importStack, phase); } - } - static bool evalCondition(const std::string& condition, const ProjectConfiguration &p) { - std::string c = '(' + condition + ")"; - replaceAll(c, "$(Configuration)", p.configuration); - replaceAll(c, "$(Platform)", p.platformStr); + attemptSyntheticImport(propertyOrEmpty(properties, "ForceImportAfterCppTargets"), + properties, metadata, compileList, projectConfigurationList, importStack, phase); - const Settings s; - TokenList tokenlist(s, Standards::Language::C); - if (!tokenlist.createTokensFromBuffer(c.data(), c.size())) { - throw std::runtime_error("Can not tokenize condition"); + return ImportResult::Ok; + } + + if (filenameIs("Microsoft.Cpp.Default.props")) { + attemptSyntheticImport(propertyOrEmpty(properties, "ForceImportBeforeCppDefaultProps"), + properties, metadata, compileList, projectConfigurationList, importStack, phase); + + // Emulate key side-effects here, including the Directory.Build.props import that + // Microsoft.Common.props would normally perform. + // Microsoft.Cpp.Default.props -> Microsoft.Common.props -> Directory.Build.props. + // Directory.Build.props is an ordinary import: it is walked in every phase so + // that its ItemDefinitionGroups and ItemGroups are collected as well, exactly + // like Directory.Build.targets in the Microsoft.Cpp.targets emulation above. + // $(ImportDirectoryBuildProps) defaults to true; an explicit "false" suppresses + // the import entirely. $(DirectoryBuildPropsPath), when set, overrides the + // upward directory search with an explicit file. See Microsoft's "Customize + // your build by folder or solution" docs. + const auto importPropsIt = properties.find("ImportDirectoryBuildProps"); + if (importPropsIt == properties.end() || caseInsensitiveStringCompare(importPropsIt->second, "false") != 0) { + const auto propsPathIt = properties.find("DirectoryBuildPropsPath"); + const std::string directoryBuildProps = (propsPathIt != properties.end() && !propsPathIt->second.empty()) + ? propsPathIt->second + : findFileAbove(projectDir, "Directory.Build.props"); + attemptSyntheticImport(directoryBuildProps, + properties, metadata, compileList, projectConfigurationList, importStack, phase); } - // generate links - { - std::stack lpar; - for (Token* tok2 = tokenlist.front(); tok2; tok2 = tok2->next()) { - if (tok2->str() == "(") - lpar.push(tok2); - else if (tok2->str() == ")") { - if (lpar.empty()) - throw std::runtime_error("unmatched ')' in condition " + condition); - Token::createMutualLinks(lpar.top(), tok2); - lpar.pop(); - } + // Emulate key defaults set by Microsoft.Cpp.Default.props (properties only). + // Derive DefaultPlatformToolset from VisualStudioVersion (already in properties from + // the .sln header or the importVcxproj default). The mapping is: + // VS 10 -> v100, VS 11 -> v110, VS 12 -> v120, + // VS 14 -> v140, VS 15 -> v141, VS 16 -> v142, VS 17 -> v143, VS 18 -> v145 + if (phase == EvalPhase::Properties && properties.find("DefaultPlatformToolset") == properties.end()) { + auto vsIt = properties.find("VisualStudioVersion"); + if (vsIt != properties.end()) { + int major = 0; + try { major = std::stoi(vsIt->second); } catch (...) {} + std::string toolset; + if (major == 18) + toolset = "v145"; + else if (major == 17) + toolset = "v143"; + else if (major == 16) + toolset = "v142"; + else if (major == 15) + toolset = "v141"; + else if (major == 14) + toolset = "v140"; + else if (major == 12) + toolset = "v120"; + else if (major == 11) + toolset = "v110"; + else if (major == 10) + toolset = "v100"; + if (!toolset.empty()) + properties["DefaultPlatformToolset"] = toolset; } - if (!lpar.empty()) - throw std::runtime_error("'(' without closing ')'!"); } - // Replace "And" and "Or" with "&&" and "||" - for (Token *tok = tokenlist.front(); tok; tok = tok->next()) { - if (tok->str() == "And") - tok->str("&&"); - else if (tok->str() == "Or") - tok->str("||"); - } + attemptSyntheticImport(propertyOrEmpty(properties, "ForceImportAfterCppDefaultProps"), + properties, metadata, compileList, projectConfigurationList, importStack, phase); - tokenlist.createAst(); + return ImportResult::Ok; + } - // Locate ast top and execute the condition - for (const Token *tok = tokenlist.front(); tok; tok = tok->next()) { - if (tok->astParent()) { - return execute(tok->astTop(), p) == "True"; - } + if (filenameIs("Microsoft.Cpp.props")) { + // ForceImportBeforeCppProps: honour any value set before Microsoft.Cpp.props + // is processed (e.g. by the vcxproj itself or by Directory.Build.props, which + // was already imported via the Microsoft.Cpp.Default.props handler above). + attemptSyntheticImport(propertyOrEmpty(properties, "ForceImportBeforeCppProps"), + properties, metadata, compileList, projectConfigurationList, importStack, phase); + + if (phase == EvalPhase::Properties) { + // Provide the output-directory defaults normally supplied by Cpp.props. + // Use emplace so values already assigned earlier in the project are preserved. + const auto platformIt = properties.find("Platform"); + const bool isWin32 = platformIt != properties.end() && + caseInsensitiveStringCompare(platformIt->second, "Win32") == 0; + std::string intDir = isWin32 ? "$(Configuration)/" : "$(Platform)/$(Configuration)/"; + std::string outDir = isWin32 ? "$(SolutionDir)$(Configuration)/" : "$(SolutionDir)$(Platform)/$(Configuration)/"; + expandMSBuildVariables(intDir, properties); + expandMSBuildVariables(outDir, properties); + properties.emplace("IntDir", intDir); + properties.emplace("OutDir", outDir); + // GeneratedFilesDir defaults to "Generated Files\" (relative to the project + // directory) -- that is what Microsoft.Cpp.Default.props provides, and it is + // the path CppWinRT projects use by convention (e.g. module.g.cpp). + // Do NOT prefix with IntDir here: IntDir is an absolute intermediate path + // specific to the build configuration, whereas GeneratedFilesDir is a + // project-relative folder that is often committed to source control. + properties.emplace("GeneratedFilesDir", "Generated Files/"); } - throw std::runtime_error("Invalid condition: '" + condition + "'"); - } + attemptSyntheticImport(propertyOrEmpty(properties, "ForceImportAfterCppProps"), + properties, metadata, compileList, projectConfigurationList, importStack, phase); - private: + return ImportResult::Ok; + } - static std::string executeOp1(const Token* tok, const ProjectConfiguration &p) { - return execute(tok->astOperand1(), p); + ImportResult result = processImport(file, properties, metadata, compileList, projectConfigurationList, importStack, phase); + if (result > ImportResult::NotResolvable) + addDebug("Could not fully import \"" + file + "\" - " + importResultStr(result) + " (continuing)"); + if (result == ImportResult::NotResolvable) { + addDebug("Could not import \"" + file + "\" - " + importResultStr(result)); } + } else { + addDebug("Could not import \"" + file + "\" unsupported extension " + extension); + } + return ImportResult::Ok; +} - static std::string executeOp2(const Token* tok, const ProjectConfiguration &p) { - return execute(tok->astOperand2(), p); +ImportProject::ImportResult ImportProject::processImportGroup(const tinyxml2::XMLElement *node, + const std::string &baseDir, + PropertiesMap &properties, + MetadataMap &metadata, + std::list &compileList, + std::list &projectConfigurationList, + std::unordered_set &importStack, + EvalPhase phase) { + ImportResult ret = ImportResult::Ok; + for (const tinyxml2::XMLElement *e = node->FirstChildElement(); e; e = e->NextSiblingElement()) { + // processImportProject() itself checks whether `e` is structurally an + // element and safely no-ops (returns Ok) otherwise, so + // no pre-filter is needed here. + const ImportResult result = processImportProject(e, baseDir, properties, metadata, compileList, projectConfigurationList, importStack, phase); + if (result > ImportResult::NotResolvable) { + if (phase != EvalPhase::Discover) { + const char *proj = e->Attribute("Project"); + addDebug("Could not fully import \"" + std::string(proj ? proj : "") + "\" - " + importResultStr(result) + " (continuing)"); + } + ret = std::max(result, ret); } + } + return ret; +} - static std::string execute(const Token* tok, const ProjectConfiguration &p) { - if (!tok) - throw std::runtime_error("Missing operator"); - auto boolResult = [](bool b) -> std::string { - return b ? "True" : "False"; - }; - if (tok->isUnaryOp("!")) - return boolResult(executeOp1(tok, p) == "False"); - if (tok->str() == "==") - return boolResult(executeOp1(tok, p) == executeOp2(tok, p)); - if (tok->str() == "!=") - return boolResult(executeOp1(tok, p) != executeOp2(tok, p)); - if (tok->str() == "&&") - return boolResult(executeOp1(tok, p) == "True" && executeOp2(tok, p) == "True"); - if (tok->str() == "||") - return boolResult(executeOp1(tok, p) == "True" || executeOp2(tok, p) == "True"); - if (tok->str() == "(" && Token::Match(tok->previous(), "$ ( %name% . %name% (")) { - const std::string& propertyName = tok->strAt(1); - std::string propertyValue; - if (propertyName == "Configuration") - propertyValue = p.configuration; - else if (propertyName == "Platform") - propertyValue = p.platformStr; - else - throw std::runtime_error("Unhandled property '" + propertyName + "'"); - const std::string& method = tok->strAt(3); - std::string arg = executeOp2(tok->tokAt(4), p); - if (arg.size() >= 2 && arg[0] == '\'') - arg = arg.substr(1, arg.size() - 2); - if (method == "Contains") - return boolResult(propertyValue.find(arg) != std::string::npos); - if (method == "EndsWith") - return boolResult(endsWith(propertyValue,arg.c_str(),arg.size())); - if (method == "StartsWith") - return boolResult(startsWith(propertyValue,arg)); - throw std::runtime_error("Unhandled method '" + method + "'"); - } - if (tok->str().size() >= 2 && tok->str()[0] == '\'') // String Literal - return tok->str(); - - throw std::runtime_error("Unknown/unhandled operator/operand '" + tok->str() + "'"); - } - - std::string mCondition; - }; +ImportProject::ImportResult ImportProject::processChoose(const tinyxml2::XMLElement *node, + const std::string &baseDir, + PropertiesMap &properties, + MetadataMap &metadata, + std::list &compileList, + std::list &projectConfigurationList, + std::unordered_set &importStack, + EvalPhase phase) { + if (mDiscovering) { + // Discovery cannot know which single a real build would select -- + // that depends on properties (Configuration/Platform above all) that are + // themselves what discovery is trying to find -- so explore every + // and any instead of picking one. mImportGraph is never active + // while mDiscovering is set (discovery runs before the per-configuration + // loop activates it), so there is no decision to record/replay here either. + ImportResult result = ImportResult::Ok; + for (const tinyxml2::XMLElement *child = node->FirstChildElement(); child; child = child->NextSiblingElement()) { + const char *childName = child->Name(); + if (!childName) + continue; + if (std::strcmp(childName, "When") == 0 || std::strcmp(childName, "Otherwise") == 0) { + const ImportResult r = processElementChildren(child, baseDir, properties, metadata, compileList, projectConfigurationList, importStack, phase); + result = std::max(result, r); + } + } + return result; + } - struct ItemDefinitionGroup : ConditionalGroup { - explicit ItemDefinitionGroup(const tinyxml2::XMLElement *idg, std::string includePaths) : ConditionalGroup(idg), additionalIncludePaths(std::move(includePaths)) { - for (const tinyxml2::XMLElement *e1 = idg->FirstChildElement(); e1; e1 = e1->NextSiblingElement()) { - const char* name = e1->Name(); - if (std::strcmp(name, "ClCompile") == 0) { - enhancedInstructionSet = "StreamingSIMDExtensions2"; - for (const tinyxml2::XMLElement *e = e1->FirstChildElement(); e; e = e->NextSiblingElement()) { - const char * const text = e->GetText(); - if (!text) - continue; - const char * const ename = e->Name(); - if (std::strcmp(ename, "PreprocessorDefinitions") == 0) - preprocessorDefinitions = text; - else if (std::strcmp(ename, "AdditionalIncludeDirectories") == 0) { - if (!additionalIncludePaths.empty()) - additionalIncludePaths += ';'; - additionalIncludePaths += text; - } else if (std::strcmp(ename, "LanguageStandard") == 0) { - if (std::strcmp(text, "stdcpp14") == 0) - cppstd = Standards::CPP14; - else if (std::strcmp(text, "stdcpp17") == 0) - cppstd = Standards::CPP17; - else if (std::strcmp(text, "stdcpp20") == 0) - cppstd = Standards::CPP20; - else if (std::strcmp(text, "stdcpplatest") == 0) - cppstd = Standards::CPPLatest; - } else if (std::strcmp(ename, "EnableEnhancedInstructionSet") == 0) { - enhancedInstructionSet = text; - } - } - } - else if (std::strcmp(name, "Link") == 0) { - for (const tinyxml2::XMLElement *e = e1->FirstChildElement(); e; e = e->NextSiblingElement()) { - const char * const text = e->GetText(); - if (!text) - continue; - if (std::strcmp(e->Name(), "EntryPointSymbol") == 0) { - entryPointSymbol = text; - } - } + bool matched = false; + std::size_t branch = 0; + + if (!mImportGraph.replay) { + // Decide (or, outside the three-pass evaluation, simply compute -- + // mDiscovering is handled separately above and never reaches here) + // which branch is taken: the first (document order) whose + // Condition evaluates true, or the if none did. Unlike + // PropertyGroup/ItemGroup/etc., itself carries no Condition -- + // only its children do -- so this does not go through hasName(). + std::size_t index = 0; + std::size_t otherwiseIndex = 0; + bool haveOtherwise = false; + for (const tinyxml2::XMLElement *child = node->FirstChildElement(); child; child = child->NextSiblingElement(), ++index) { + const char *childName = child->Name(); + if (!childName) + continue; + if (std::strcmp(childName, "When") == 0) { + const char *cond = child->Attribute("Condition"); + if (cond && evalCondition(cond, properties)) { + matched = true; + branch = index; + break; } + } else if (std::strcmp(childName, "Otherwise") == 0 && !haveOtherwise) { + haveOtherwise = true; + otherwiseIndex = index; } } + if (!matched && haveOtherwise) { + matched = true; + branch = otherwiseIndex; + } + } - std::string enhancedInstructionSet; - std::string preprocessorDefinitions; - std::string additionalIncludePaths; - std::string entryPointSymbol; // TODO: use this - Standards::cppstd_t cppstd = Standards::CPPLatest; - }; + if (!importGraphChooseDecision(matched, branch)) + return ImportResult::Ok; + + // Re-locate the taken child by index rather than reusing a pointer from the + // scan above: on replay this file's XML may have been reloaded into a fresh + // tinyxml2::XMLDocument since the Properties pass ran (processImport() opens + // a new document per phase for every imported file), so any XMLElement* + // captured earlier would not be valid here. + std::size_t index = 0; + for (const tinyxml2::XMLElement *child = node->FirstChildElement(); child; child = child->NextSiblingElement(), ++index) { + if (index == branch) + return processElementChildren(child, baseDir, properties, metadata, compileList, projectConfigurationList, importStack, phase); + } + return ImportResult::Ok; +} + +ImportProject::ImportResult ImportProject::processElementChildren(const tinyxml2::XMLElement *parent, + const std::string &baseDir, + PropertiesMap &properties, + MetadataMap &metadata, + std::list &compileList, + std::list &projectConfigurationList, + std::unordered_set &importStack, + EvalPhase phase) { + ImportResult result = ImportResult::Ok; + + for (const tinyxml2::XMLElement *node = parent->FirstChildElement(); node; node = node->NextSiblingElement()) { + if (hasName(node, "PropertyGroup", properties)) { + // Properties pass only: by the time ItemDefs/Items run, every property in + // the whole resolved graph is already final in `properties` -- re-running + // PropertyGroup assignment then would be redundant, and evaluating it + // against final rather than as-accumulated properties could even produce a + // different value than what Properties actually computed. + // Also applied directly under EvalPhase::Discover: the discovery bootstrap + // scan in importVcxproj() walks its document through this same function + // (so // are handled identically to the real + // passes), and unlike an imported file reached via processImportProject() + // -- which converts Discover to Properties one level down before recursing + // -- this is the top-level entry point, so Discover itself must unlock + // PropertyGroup here or its properties would never be seeded at all. + if (phase == EvalPhase::Properties || phase == EvalPhase::Discover) { + for (const tinyxml2::XMLElement *child = node->FirstChildElement(); child; child = child->NextSiblingElement()) + addProperty(child, properties); + } + } else if (hasName(node, "ItemDefinitionGroup", properties)) { + // ItemDefs pass only, using the final properties Properties has already + // computed -- matching MSBuild's own item-definition evaluation, which runs + // as a separate pass over the whole graph after property evaluation. + if (phase == EvalPhase::ItemDefs) { + // Evaluate metadata defaults sequentially to capture preceding overrides. + // Structural name check only (hasElementName(), not hasName()): Visual + // Studio does not support Condition on the ClCompile item-definition + // element itself, only on its metadata children -- see hasElementName(). + for (const tinyxml2::XMLElement *item = node->FirstChildElement(); item; item = item->NextSiblingElement()) { + if (!hasElementName(item, "ClCompile")) + continue; + + for (const tinyxml2::XMLElement *child = item->FirstChildElement(); child; child = child->NextSiblingElement()) + addMetadata(child, properties, metadata); + } + } + } else if (hasNameAndLabel(node, "ItemGroup", "ProjectConfigurations", properties)) { + // Idempotent (guarded by alreadyPresent below) and cheap, so it is left + // unconditional rather than restricted to one phase. + for (const tinyxml2::XMLElement *configuration = node->FirstChildElement("ProjectConfiguration"); configuration; configuration = configuration->NextSiblingElement("ProjectConfiguration")) { + const ProjectConfiguration pc(configuration); + if (pc.configuration.empty()) + continue; + + const bool alreadyPresent = std::any_of(projectConfigurationList.cbegin(), + projectConfigurationList.cend(), + [&pc](const ProjectConfiguration &existing) { + return existing.name == pc.name; + }); + + if (!alreadyPresent) { + projectConfigurationList.emplace_back(pc); + mAllVSConfigs.insert(pc.configuration); + } + } + } else if (hasNameAndNotLabel(node, "ItemGroup", "ProjectConfigurations", properties)) { + // Items pass only, using the final properties and item definitions Properties + // and ItemDefs have already computed -- matching MSBuild's own item + // evaluation, the last of its three passes over the whole graph. + if (phase == EvalPhase::Items) { + // Structural name check only (hasElementName(), not hasName()): Visual + // Studio's C++ project system does not support Condition on a ClCompile + // project item itself -- only on its metadata children (see + // hasElementName()) -- so a Condition here must not hide the item. + for (const tinyxml2::XMLElement *item = node->FirstChildElement(); item; item = item->NextSiblingElement()) { + if (!hasElementName(item, "ClCompile")) + continue; - struct ConfigurationPropertyGroup : ConditionalGroup { - explicit ConfigurationPropertyGroup(const tinyxml2::XMLElement *idg) : ConditionalGroup(idg) { - for (const tinyxml2::XMLElement *e = idg->FirstChildElement(); e; e = e->NextSiblingElement()) { - if (std::strcmp(e->Name(), "UseOfMfc") == 0) { - useOfMfc = true; - } else if (std::strcmp(e->Name(), "CharacterSet") == 0) { - useUnicode = std::strcmp(e->GetText(), "Unicode") == 0; + if (item->Attribute("Include")) + processCompile(item, baseDir, properties, metadata, compileList); + else if (item->Attribute("Update")) + applyClCompileUpdate(item, baseDir, properties, compileList); + else if (item->Attribute("Remove")) + applyClCompileRemove(item, baseDir, properties, compileList); } } + } else if (hasElementName(node, "ImportGroup")) { + // An 's own Condition gates every import inside it, so it is an + // import-graph branch point exactly like an individual and must be + // decided/replayed the same way (see importGraphDecision()) -- ImportGroup + // itself never resolves to a file, so the frozen-file half is unused. + std::string unusedFile; + if (importGraphDecision(conditionIsTrue(node, properties), unusedFile)) { + const ImportResult importResult = processImportGroup(node, baseDir, properties, metadata, compileList, projectConfigurationList, importStack, phase); + result = std::max(result, importResult); + } + } else if (hasElementName(node, "Choose")) { + // 's branch selection is an import-graph branch point exactly + // like / -- see processChoose() and + // importGraphChooseDecision() for why it must be decided once + // (Properties) and replayed (ItemDefs/Items) rather than + // re-evaluated every phase. + const ImportResult chooseResult = processChoose(node, baseDir, properties, metadata, compileList, projectConfigurationList, importStack, phase); + result = std::max(result, chooseResult); + } else { + // processImportProject() itself checks whether `node` is structurally an + // element (safely no-opping otherwise) and, when it + // is, decides/replays whether it is taken (it needs to freeze the resolved + // file, not just the true/false). + const ImportResult importResult = processImportProject(node, baseDir, properties, metadata, compileList, projectConfigurationList, importStack, phase); + result = std::max(result, importResult); } + } - bool useOfMfc = false; - bool useUnicode = false; - }; + return result; } -static std::list toStringList(const std::string &s) +// Returns whether `name` matches the '*'/'?' wildcard `pattern` (case-insensitive, +// matching NTFS semantics -- Visual Studio's C++ project system is a Windows-only +// concept). '*' matches any run of characters (including none); '?' matches +// exactly one character. Neither argument is expected to contain a path +// separator -- this matches one filename against one pattern within a single +// directory, not a path. Standard greedy iterative glob match. +static bool matchesWildcardName(const std::string &name, const std::string &pattern) { - std::list ret; - std::string::size_type pos1 = 0; - std::string::size_type pos2; - while ((pos2 = s.find(';',pos1)) != std::string::npos) { - ret.push_back(s.substr(pos1, pos2-pos1)); - pos1 = pos2 + 1; - if (pos1 >= s.size()) - break; + std::size_t n = 0, p = 0; + std::size_t star = std::string::npos, matchPos = 0; + while (n < name.size()) { + if (p < pattern.size() && + (pattern[p] == '?' || + std::tolower(static_cast(pattern[p])) == std::tolower(static_cast(name[n])))) { + ++n; + ++p; + } else if (p < pattern.size() && pattern[p] == '*') { + star = p++; + matchPos = n; + } else if (star != std::string::npos) { + p = star + 1; + n = ++matchPos; + } else { + return false; + } } - if (pos1 < s.size()) - ret.push_back(s.substr(pos1)); - return ret; + while (p < pattern.size() && pattern[p] == '*') + ++p; + return p == pattern.size(); } -static void importPropertyGroup(const tinyxml2::XMLElement *node, std::map &variables, std::string &includePath) +// Lists the regular files (not subdirectories) directly inside `dir` (no +// trailing separator required, '/' separators only). Returns an empty list if +// `dir` does not exist or cannot be opened -- a wildcard matching +// nothing is a silent no-op, exactly like MSBuild's own ImportBefore/ImportAfter +// extensibility folders when nothing has been dropped into them. Not recursive: +// MSBuild's own wildcard Import examples (e.g. "ImportBefore\*") only ever +// reach into one directory, matching the non-recursive '*' used elsewhere in +// this file (see expandItemSpec()'s rejection of '*'/'?' in project items). +#ifdef _WIN32 +static std::vector listDirectoryFiles(const std::string &dir) { - const char* labelAttribute = node->Attribute("Label"); - if (labelAttribute && std::strcmp(labelAttribute, "UserMacros") == 0) { - for (const tinyxml2::XMLElement *propertyGroup = node->FirstChildElement(); propertyGroup; propertyGroup = propertyGroup->NextSiblingElement()) { - const char* name = propertyGroup->Name(); - const char *text = empty_if_null(propertyGroup->GetText()); - variables[name] = text; - } - - } else if (!labelAttribute) { - for (const tinyxml2::XMLElement *propertyGroup = node->FirstChildElement(); propertyGroup; propertyGroup = propertyGroup->NextSiblingElement()) { - if (std::strcmp(propertyGroup->Name(), "IncludePath") != 0) - continue; - const char *text = propertyGroup->GetText(); - if (!text) - continue; - std::string path(text); - const std::string::size_type pos = path.find("$(IncludePath)"); - if (pos != std::string::npos) - path.replace(pos, 14U, includePath); - includePath = std::move(path); - } + std::vector files; + const std::string searchPath = (dir.empty() ? std::string(".") : dir) + "/*"; + WIN32_FIND_DATAA findData; + const HANDLE hFind = FindFirstFileA(searchPath.c_str(), &findData); + if (hFind == INVALID_HANDLE_VALUE) + return files; + do { + if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + files.emplace_back(findData.cFileName); + } while (FindNextFileA(hFind, &findData)); + FindClose(hFind); + return files; +} +#else +static std::vector listDirectoryFiles(const std::string &dir) +{ + std::vector files; + const std::string path = dir.empty() ? std::string(".") : dir; + DIR * const dp = opendir(path.c_str()); + if (!dp) + return files; + while (const struct dirent * const entry = readdir(dp)) { + const std::string name = entry->d_name; + if (name == "." || name == "..") + continue; + if (Path::isDirectory(path + "/" + name)) + continue; + files.push_back(name); } + closedir(dp); + return files; } +#endif -static void loadVisualStudioProperties(const std::string &props, std::map &variables, std::string &includePath, const std::string &additionalIncludeDirectories, std::list &itemDefinitionGroupList) -{ - std::string filename(props); - // variables can't be resolved - if (!simplifyPathWithVariables(filename, variables)) - return; +ImportProject::ImportResult ImportProject::processImport(const std::string &file, + PropertiesMap &properties, + MetadataMap &metadata, + std::list &compileList, + std::list &projectConfigurationList, + std::unordered_set &importStack, + EvalPhase phase) { + std::string filename(file); + // file can't be resolved + if (!simplifyPathWithVariables(filename, properties)) + return ImportResult::NotResolvable; // prepend project dir (if it exists) to transform relative paths into absolute ones - if (!Path::isAbsolute(filename) && variables.count("ProjectDir") > 0) - filename = Path::getAbsoluteFilePath(variables.at("ProjectDir") + filename); + // Use classifyPath so that root-relative paths (\foo -> C:\foo) are resolved + // against the base drive, not treated as absolute on Linux. + const PathKind _fkind = classifyPath(Path::fromNativeSeparators(filename)); + if (_fkind != PathKind::UNC && _fkind != PathKind::DriveAbsolute && properties.count("ProjectDir") > 0) + filename = toAbsolute(filename, properties.at("ProjectDir"), properties); + + // MSBuild allows wildcards in an 's Project attribute -- unlike + // project items (see expandItemSpec()'s rejection of '*'/'?' there, a + // *different*, narrower Visual Studio C++ project-system restriction that + // does not apply to imports): "When there are wildcards, all matches found + // are sorted (for reproducibility), and then they are imported in that + // order as if the order had been explicitly set." This is the mechanism + // behind the ImportBefore/ImportAfter extensibility folders Visual + // Studio's own C++ toolchain files use (e.g. + // $(VCTargetsPath)\ImportBefore\Default\*.props), but it applies equally + // to any ordinary user-authored wildcard . + const std::string::size_type lastSlash = filename.find_last_of('/'); + const std::string patternPart = (lastSlash != std::string::npos) ? filename.substr(lastSlash + 1) : filename; + if (patternPart.find('*') != std::string::npos || patternPart.find('?') != std::string::npos) { + const std::string dirPart = (lastSlash != std::string::npos) ? filename.substr(0, lastSlash) : std::string(); + std::vector matches; + for (const std::string &candidate : listDirectoryFiles(dirPart)) { + if (matchesWildcardName(candidate, patternPart)) + // cppcheck-suppress useStlAlgorithm + matches.push_back(candidate); + } + std::sort(matches.begin(), matches.end()); + ImportResult result = ImportResult::Ok; + for (const std::string &match : matches) { + const ImportResult r = processImport(dirPart + "/" + match, properties, metadata, compileList, + projectConfigurationList, importStack, phase); + result = std::max(result, r); + } + return result; + } + + const std::string key = importFileKey(filename); + // Detect circular imports before duplicate-import suppression. + if (!importStack.insert(key).second) + return ImportResult::Cycle; + ImportStackGuard guard(importStack, key); + + // A previously completed import is ignored. + if (mImportGraph.active && !mImportGraph.imported.insert(key).second) + return ImportResult::Ok; tinyxml2::XMLDocument doc; - if (doc.LoadFile(filename.c_str()) != tinyxml2::XML_SUCCESS) - return; + + const tinyxml2::XMLError xmlErr = doc.LoadFile(filename.c_str()); + if (xmlErr != tinyxml2::XML_SUCCESS) { + if (xmlErr == tinyxml2::XML_ERROR_FILE_NOT_FOUND || + xmlErr == tinyxml2::XML_ERROR_FILE_COULD_NOT_BE_OPENED || + xmlErr == tinyxml2::XML_ERROR_FILE_READ_ERROR) + return ImportResult::NotFound; + return ImportResult::NotValid; // file exists but is malformed XML + } + const tinyxml2::XMLElement * const rootnode = doc.FirstChildElement(); if (rootnode == nullptr) - return; - for (const tinyxml2::XMLElement *node = rootnode->FirstChildElement(); node; node = node->NextSiblingElement()) { - const char* name = node->Name(); - if (std::strcmp(name, "ImportGroup") == 0) { - const char *labelAttribute = node->Attribute("Label"); - if (labelAttribute == nullptr || std::strcmp(labelAttribute, "PropertySheets") != 0) - continue; - for (const tinyxml2::XMLElement *importGroup = node->FirstChildElement(); importGroup; importGroup = importGroup->NextSiblingElement()) { - if (std::strcmp(importGroup->Name(), "Import") == 0) { - const char *projectAttribute = importGroup->Attribute("Project"); - if (projectAttribute == nullptr) - continue; - std::string loadprj(projectAttribute); - if (loadprj.find('$') == std::string::npos) { - loadprj = Path::getPathFromFilename(filename) + loadprj; - } - loadVisualStudioProperties(loadprj, variables, includePath, additionalIncludeDirectories, itemDefinitionGroupList); - } - } - } else if (std::strcmp(name,"PropertyGroup")==0) { - importPropertyGroup(node, variables, includePath); - } else if (std::strcmp(name,"ItemDefinitionGroup")==0) { - itemDefinitionGroupList.emplace_back(node, additionalIncludeDirectories); - } - } + return ImportResult::NotValid; + + // Every import-graph branch point encountered while walking this file's children + // (import decisions or replays) is keyed by this file, so ItemDefs/Items replay + // the exact sequence Properties recorded for it. + CurrentFileGuard fileGuard(mImportGraph.currentFile, key); + + MSBuildThis msBuildThis(filename, properties); + std::string propsDir = Path::getPathFromFilename(filename); + + return processElementChildren(rootnode, propsDir, properties, metadata, compileList, + projectConfigurationList, importStack, phase); } bool ImportProject::importVcxproj(const std::string &filename, - std::map &variables, - const std::string &additionalIncludeDirectories, - const std::vector &fileFilters, - std::vector &cache) + PropertiesMap &properties, + const std::vector &fileFilters) { tinyxml2::XMLDocument doc; const tinyxml2::XMLError error = doc.LoadFile(filename.c_str()); @@ -885,245 +4630,656 @@ bool ImportProject::importVcxproj(const std::string &filename, errors.emplace_back(std::string("Visual Studio project file is not a valid XML - ") + tinyxml2::XMLDocument::ErrorIDToName(error)); return false; } - return importVcxproj(filename, doc, variables, additionalIncludeDirectories, fileFilters, cache); -} -bool ImportProject::importVcxproj(const std::string &filename, const tinyxml2::XMLDocument &doc, std::map &variables, const std::string &additionalIncludeDirectories, const std::vector &fileFilters, std::vector &cache) -{ - variables["ProjectDir"] = Path::simplifyPath(Path::getPathFromFilename(filename)); + // Normalize separators once; callers typically pass toAbsolute() results + // but normalize here as a safety net so all subsequent rfind('/') are correct. + const std::string nfilename = Path::simplifyPath(Path::fromNativeSeparators(filename)); + + // A solution import may already provide VisualStudioVersion. + // Direct .vcxproj imports have no solution header, so use the current + // supported Visual Studio version as the fallback. + properties.emplace("VisualStudioVersion", "18.0"); + + // User-extensible MSVC properties that legitimately default to "" when not + // set by the project. In real MSBuild every undefined property expands to + // the empty string; seed them here so that props files which append to them + // (e.g. ...;$(DisableSpecificWarnings)) + // produce a clean resolved value instead of leaving $(X) unexpanded. + properties.emplace("DisableSpecificWarnings", ""); + + properties["ProjectPath"] = nfilename; + const std::string projFileName = stripDirectoryPart(nfilename); + properties["ProjectFileName"] = projFileName; + const std::string projName = fileStem(projFileName); + properties["ProjectName"] = projName; + properties["ShortProjectName"] = projName.substr(0, std::min(projName.size(), std::size_t(16))); + properties["ProjectExt"] = Path::getFilenameExtensionInLowerCase(nfilename); + properties["ProjectDir"] = Path::getPathFromFilename(nfilename); + + // importVcxproj called directly + if (properties.find("SolutionDir") == properties.end()) { + debugs.clear(); + properties["SolutionDir"] = properties["ProjectDir"]; + } + + properties["MSBuildProjectName"] = properties["ProjectName"]; + properties["MSBuildProjectExtension"] = properties["ProjectExt"]; + properties["MSBuildProjectDirectory"] = properties["ProjectDir"]; + // remove file seperator on end of path + if (!properties["MSBuildProjectDirectory"].empty() && + (properties["MSBuildProjectDirectory"].back() == '/' || + properties["MSBuildProjectDirectory"].back() == '\\')) { + properties["MSBuildProjectDirectory"].pop_back(); + } + properties["MSBuildProjectFile"] = properties["ProjectFileName"]; + properties["MSBuildProjectFullPath"] = properties["ProjectPath"]; + + // TargetName defaults to ProjectName; can be overridden by the XML + properties.emplace("TargetName", properties["ProjectName"]); + // MSBuildProjectDirectoryNoRoot: like MSBuildThisFileDirectoryNoRoot but for the + // project itself. ProjectDir has a trailing '/' which we strip to match the + // MSBuildProjectDirectory (no trailing separator) convention. + std::string noRoot = stripPathRoot(properties["ProjectDir"]); + if (!noRoot.empty() && noRoot.back() == '/') + noRoot.pop_back(); + properties["MSBuildProjectDirectoryNoRoot"] = noRoot; + + properties["BuildingInsideVisualStudio"] = "true"; + properties["DesignTimeBuild"] = "true"; + + MSBuildThis::setMSBuildThis(nfilename, properties); + + std::string projectDir = properties["ProjectDir"]; std::list projectConfigurationList; - std::list compileList; - std::list itemDefinitionGroupList; - std::vector configurationPropertyGroups; - std::string includePath; - std::vector sharedItemsProjects; + std::list compileList; + std::unordered_set importStack; + MetadataMap metadata; const tinyxml2::XMLElement * const rootnode = doc.FirstChildElement(); if (rootnode == nullptr) { errors.emplace_back("Visual Studio project file has no XML root node"); return false; } - for (const tinyxml2::XMLElement *node = rootnode->FirstChildElement(); node; node = node->NextSiblingElement()) { - const char* name = node->Name(); - if (std::strcmp(name, "ItemGroup") == 0) { - const char *labelAttribute = node->Attribute("Label"); - if (labelAttribute && std::strcmp(labelAttribute, "ProjectConfigurations") == 0) { - for (const tinyxml2::XMLElement *cfg = node->FirstChildElement(); cfg; cfg = cfg->NextSiblingElement()) { - if (std::strcmp(cfg->Name(), "ProjectConfiguration") == 0) { - const ProjectConfiguration p(cfg); - if (p.platform != ProjectConfiguration::Unknown) { - projectConfigurationList.emplace_back(cfg); - mAllVSConfigs.insert(p.configuration); - } - } - } - } else { - for (const tinyxml2::XMLElement *e = node->FirstChildElement(); e; e = e->NextSiblingElement()) { - if (std::strcmp(e->Name(), "ClCompile") == 0) { - const char *include = e->Attribute("Include"); - if (include && Path::acceptFile(include)) { - std::string toInclude = Path::simplifyPath(Path::isAbsolute(include) ? include : Path::getPathFromFilename(filename) + include); - compileList.emplace_back(toInclude); - } - } - } - } - } else if (std::strcmp(name, "ItemDefinitionGroup") == 0) { - itemDefinitionGroupList.emplace_back(node, additionalIncludeDirectories); - } else if (std::strcmp(name, "PropertyGroup") == 0) { - const char* labelAttribute = node->Attribute("Label"); - if (labelAttribute && std::strcmp(labelAttribute, "Configuration") == 0) { - configurationPropertyGroups.emplace_back(node); - } else { - importPropertyGroup(node, variables, includePath); - } - } else if (std::strcmp(name, "ImportGroup") == 0) { - const char *labelAttribute = node->Attribute("Label"); - if (labelAttribute && std::strcmp(labelAttribute, "PropertySheets") == 0) { - for (const tinyxml2::XMLElement *e = node->FirstChildElement(); e; e = e->NextSiblingElement()) { - if (std::strcmp(e->Name(), "Import") == 0) { - const char *projectAttribute = e->Attribute("Project"); - if (projectAttribute) - loadVisualStudioProperties(projectAttribute, variables, includePath, additionalIncludeDirectories, itemDefinitionGroupList); - } - } - } else if (labelAttribute && std::strcmp(labelAttribute, "Shared") == 0) { - for (const tinyxml2::XMLElement *e = node->FirstChildElement(); e; e = e->NextSiblingElement()) { - if (std::strcmp(e->Name(), "Import") == 0) { - const char *projectAttribute = e->Attribute("Project"); - if (projectAttribute) { - // Path to shared items project is relative to current project directory, - // unless the string starts with $(SolutionDir) - std::string pathToSharedItemsFile; - if (std::string(projectAttribute).rfind("$(SolutionDir)", 0) == 0) { - pathToSharedItemsFile = projectAttribute; - } else { - pathToSharedItemsFile = variables["ProjectDir"] + projectAttribute; - } - if (!simplifyPathWithVariables(pathToSharedItemsFile, variables)) { - errors.emplace_back("Could not simplify path to referenced shared items project"); - return false; - } - SharedItemsProject toAdd = importVcxitems(pathToSharedItemsFile, fileFilters, cache); - if (!toAdd.successful) { - errors.emplace_back("Could not load shared items project \"" + pathToSharedItemsFile + "\" from original path \"" + std::string(projectAttribute) + "\"."); - return false; - } - sharedItemsProjects.emplace_back(toAdd); - } - } + // Read MSBuildToolsVersion directly from . + // "Current" is the standard value for VS2019+ and is the correct fallback. + const char *toolsVersion = rootnode->Attribute("ToolsVersion"); + properties["MSBuildToolsVersion"] = toolsVersion ? toolsVersion : "Current"; + + // find all Visual Studio project configurations + for (const tinyxml2::XMLElement *node = rootnode->FirstChildElement(); node; node = node->NextSiblingElement()) { + if (hasNameAndLabel(node, "ItemGroup", "ProjectConfigurations", properties)) { + for (const tinyxml2::XMLElement *pcNode = node->FirstChildElement("ProjectConfiguration"); pcNode; pcNode = pcNode->NextSiblingElement("ProjectConfiguration")) { + const ProjectConfiguration pc(pcNode); + if (!pc.configuration.empty()) { // only require a configuration name + projectConfigurationList.emplace_back(pc); + mAllVSConfigs.insert(pc.configuration); } } } } - // # TODO: support signedness of char via /J (and potential XML option for it)? - // we can only set it globally but in this context it needs to be treated per file - // Include shared items project files - std::vector sharedItemsIncludePaths; - for (const auto& sharedProject : sharedItemsProjects) { - for (const auto &file : sharedProject.sourceFiles) { - std::string pathToFile = Path::simplifyPath(Path::getPathFromFilename(sharedProject.pathToProjectFile) + file); - compileList.emplace_back(std::move(pathToFile)); + // Discovery pass: if no ProjectConfigurations were found inline in the vcxproj, walk + // the whole document through processElementChildren -- the same dispatch the real + // per-configuration passes use -- so every construct that can contain or gate a + // configuration-defining import (PropertyGroup, ImportGroup, a bare Import, and + // Choose/When/Otherwise, at any depth reached through them) is honoured generically, + // no special-casing of individual property names or node kinds required. + // + // While mDiscovering is set (see its doc comment and DiscoveringGuard), + // conditionIsTrue() treats every Condition as satisfied and processChoose() + // explores every branch instead of selecting one: real MSBuild/Visual Studio must + // know the complete configuration set before evaluation with a specific + // Configuration/Platform can even begin, so this scan does not try to predict + // which Condition would hold for values it is itself trying to discover -- it + // simply assumes every path might contribute a configuration and walks all of + // them. Configurations can legitimately be split across multiple imports (e.g. + // one property sheet per configuration, each behind its own Condition, or behind + // a Choose), and both stopping early and guessing a single property value would + // silently lose configurations reachable only a different way. + // + // Use isolated copies of properties, metadata and importStack so that side-effects + // of the discovery imports (extra properties, pre-populated import stack, etc.) do + // not bleed into the real per-configuration import pass that follows -- only the + // configurations themselves (accumulated into projectConfigurationList/mAllVSConfigs) + // persist beyond this block. An extra configuration this over-inclusive scan finds + // either genuinely exists or simply picks up no items during the real pass (which + // always evaluates every Condition properly, against that configuration's real + // properties) and costs nothing. + if (projectConfigurationList.empty()) { + PropertiesMap discoverProps = properties; + // Seed properties that would otherwise be unknown at discovery time. This is + // more than cosmetic debug-noise suppression: simplifyPathWithVariables() + // treats any path with a surviving unexpanded $(...) as unresolvable, so an + // import whose PATH ITSELF embeds one of these -- e.g. + // , with no Condition or + // Choose gating it at all -- would otherwise fail to resolve and its + // configuration would be missed entirely, not just logged. Conditions + // themselves no longer need a guessed value to select a branch (mDiscovering + // ignores them, see above), but property-VALUE expansion is unaffected by + // that and still depends on one. These only affect the isolated discovery + // copy -- the real per-config pass uses the unmodified properties map. + discoverProps.emplace("Platform", "x64"); + discoverProps.emplace("Configuration", "Debug"); + // Name-mismatched env vars (same-name ones are auto-resolved by isKnown). + auto discoverSeedEnv = [&](const char *prop, const char *envVar) { + const char *val = std::getenv(envVar); + discoverProps.emplace(prop, val ? val : ""); + }; + discoverSeedEnv("VsInstallRoot", "VSINSTALLDIR"); + discoverSeedEnv("VsInstallDir", "VSINSTALLDIR"); + discoverSeedEnv("VCInstallDir", "VCINSTALLDIR"); + discoverSeedEnv("MSBuildProgramFiles32", "ProgramFiles(x86)"); + discoverSeedEnv("WindowsSdkDir_10", "WindowsSdkDir"); + // MSBuild-internal properties with no env var equivalent. + discoverProps.emplace("VC_LibraryPath_x64", ""); + discoverProps.emplace("WindowsSDK_WindowsMetadata", ""); + discoverProps.emplace("WindowsSDK_LibraryPath_x64", ""); + MetadataMap discoverMeta; + std::list discoverCompile; + std::unordered_set discoverStack; + + DiscoveringGuard discoveringGuard(mDiscovering); + processElementChildren(rootnode, projectDir, discoverProps, discoverMeta, discoverCompile, + projectConfigurationList, discoverStack, EvalPhase::Discover); + } + + PropertiesMap originalVariables = properties; + + // Visual Studio (like MSBuild) evaluates a project in three passes over the + // whole resolved import graph: every PropertyGroup/Import first, so every + // property -- wherever in the graph it is set -- is final before anything + // else runs, then every ItemDefinitionGroup, then every ItemGroup. See the + // EvalPhase doc comment and importGraphDecision() for the full rationale. + const std::string rootKey = importFileKey(nfilename); + + // Prime mConfigInvariantProperties: an isolated Properties-only walk per + // configuration (same idea as the discovery bootstrap above -- scratch + // copies of properties/metadata/import state so nothing here bleeds into + // the real per-configuration passes below), just to learn which property + // names resolve to the same value in every configuration this project + // has. expandItemSpec() (via hasOnlyInvariantItemPathVariables()) treats + // those, in addition to the fixed MSBuild "this file"/"this project" set, + // as safe to expand in a project item path -- see mConfigInvariantProperties's + // doc comment in importproject.h for why that's the right line to draw. + // With zero or one configuration every property trivially qualifies (there + // is nothing for it to vary across), which is correct: a project that + // only ever builds one way can't run into the per-configuration mismatch + // the underlying Visual Studio restriction is about. + mConfigInvariantProperties.clear(); + { + // Run the priming Properties pass once per configuration, keeping + // each configuration's full resulting property map -- needed below + // to tell "this property was never set by any PropertyGroup this + // project has" (fine: hasOnlyInvariantItemPathVariables() falls + // back to checking the OS environment for those, independently of + // mConfigInvariantProperties) apart from "this property is set in + // SOME of this project's configurations but not others" (not fine: + // that is itself a value that varies by configuration -- see below). + std::vector perConfigProperties; + perConfigProperties.reserve(projectConfigurationList.size()); + for (const ProjectConfiguration &primePc : projectConfigurationList) { + PropertiesMap primeProperties = originalVariables; + primeProperties["Configuration"] = primePc.configuration; + primeProperties["Platform"] = primePc.platformStr; + + MetadataMap primeMetadata; + std::list primeCompileList; + std::unordered_set primeImportStack; + + mImportGraph = ImportGraph(); + mImportGraph.active = true; + mImportGraph.currentFile = rootKey; + mImportGraph.imported.insert(rootKey); + + processElementChildren(rootnode, projectDir, primeProperties, primeMetadata, primeCompileList, + projectConfigurationList, primeImportStack, EvalPhase::Properties); + + perConfigProperties.push_back(std::move(primeProperties)); + } + + // Case-insensitive for the same reason mConfigInvariantProperties + // itself is (see its doc comment in importproject.h): each + // perConfigProperties entry above is itself a PropertiesMap, so + // within one configuration "MyRoot" and "myroot" already collapse + // to a single entry, but without this comparator here two DIFFERENT + // configurations spelling the same property differently -- one + // PropertyGroup writing "MyRoot", another "myroot" -- would be + // tracked as two unrelated single-value names instead of one + // property with (potentially) two different values, and each could + // wrongly look config-invariant on its own. + // + // The union of property names across every configuration -- not + // just each configuration's own names -- matters just as much: a + // property some, but not all, configurations set (e.g. a PropertyGroup + // conditioned on Configuration=='Debug' with no Release counterpart) + // is genuinely config-varying, exactly like real MSBuild treats it, + // since $(MyRoot) resolves to that PropertyGroup's value under Debug + // and to "" (PropertyValueExpander::lookup()'s own fallback for a + // name no PropertyGroup and no OS environment variable defines -- + // see hasOnlyInvariantItemPathVariables()'s separate getenv() check + // for the case where an environment variable of the same name DOES + // make it invariant regardless) under every configuration that never + // sets it. Naively collecting only the values each configuration's + // own map happens to contain would miss this entirely: a property + // set in exactly one configuration would have exactly one recorded + // value there -- itself -- and look spuriously invariant. + std::set allPropertyNames; + for (const auto &configProperties : perConfigProperties) { + for (const auto &prop : configProperties) + // cppcheck-suppress useStlAlgorithm + allPropertyNames.insert(prop.first); } - for (const auto &p : sharedProject.includePaths) { - std::string path = Path::simplifyPath(Path::getPathFromFilename(sharedProject.pathToProjectFile) + p); - sharedItemsIncludePaths.emplace_back(std::move(path)); + std::map, cppcheck::stricmp> valuesByProperty; + for (const auto &configProperties : perConfigProperties) { + for (const auto &name : allPropertyNames) { + const auto it = configProperties.find(name); + valuesByProperty[name].insert(it != configProperties.end() ? it->second : std::string()); + } + } + for (const auto &prop : valuesByProperty) { + if (prop.second.size() == 1) + mConfigInvariantProperties.insert(prop.first); } } - // Project files - PathMatch filtermatcher(fileFilters, Path::getCurrentPath()); - for (const std::string &cfilename : compileList) { - if (!fileFilters.empty() && !filtermatcher.match(cfilename)) - continue; + bool first = true; + + for (const ProjectConfiguration &pc : projectConfigurationList) { + if (!first) { + compileList.clear(); + properties = originalVariables; + metadata.clear(); + importStack.clear(); + } else + first = false; + + properties["Configuration"] = pc.configuration; + properties["Platform"] = pc.platformStr; + + // Pass 1 (Properties): decide, for every import-graph branch point, whether it + // is taken -- using the properties known at that exact point in the walk -- + // and record the decision sequence per file. + mImportGraph = ImportGraph(); + mImportGraph.active = true; + mImportGraph.currentFile = rootKey; + mImportGraph.imported.insert(rootKey); + + const ImportResult propertiesResult = processElementChildren(rootnode, projectDir, properties, metadata, + compileList, projectConfigurationList, importStack, + EvalPhase::Properties); + if (propertiesResult > ImportResult::NotResolvable) + addDebug("Could not fully evaluate \"" + nfilename + "\" - " + importResultStr(propertiesResult)); + + // Pass 2 (ItemDefs) and pass 3 (Items) walk the identical document structure + // again but *replay* the decisions pass 1 recorded instead of re-evaluating + // Condition -- re-checking against the now-final properties could produce a + // different graph than pass 1 built, which would desync the properties pass 1 + // computed from the item definitions/items passes 2/3 evaluate. Which files + // get (re-)visited is an automatic, consistent consequence of replaying the + // same decisions in the same order, so only 'imported' and 'cursor' -- not + // 'decisions' itself -- reset between passes. + mImportGraph.replay = true; + + mImportGraph.imported.clear(); + mImportGraph.imported.insert(rootKey); + mImportGraph.cursor.clear(); + importStack.clear(); + + processElementChildren(rootnode, projectDir, properties, metadata, + compileList, projectConfigurationList, importStack, + EvalPhase::ItemDefs); + + mImportGraph.imported.clear(); + mImportGraph.imported.insert(rootKey); + mImportGraph.cursor.clear(); + importStack.clear(); + + const ImportResult itemsResult = processElementChildren(rootnode, projectDir, properties, metadata, + compileList, projectConfigurationList, importStack, + EvalPhase::Items); + if (itemsResult > ImportResult::NotResolvable) + addDebug("Could not fully evaluate \"" + nfilename + "\" - " + importResultStr(itemsResult)); + + // # TODO: support signedness of char via /J (and potential XML option for it)? + // we can only set it globally but in this context it needs to be treated per file + + // Project files + PathMatch filtermatcher(fileFilters, Path::getCurrentPath()); + for (const ItemGroupClCompile &compile : compileList) { + if (!fileFilters.empty() && !filtermatcher.match(compile.filename)) + continue; - for (const ProjectConfiguration &p : projectConfigurationList) { + const std::string &excl = compile.get("ExcludedFromBuild"); + if (!excl.empty() && caseInsensitiveStringCompare(excl, "true") == 0) + continue; if (!guiProject.checkVsConfigs.empty()) { - const bool doChecking = std::any_of(guiProject.checkVsConfigs.cbegin(), guiProject.checkVsConfigs.cend(), [&](const std::string& c) { - return c == p.configuration; + const bool doChecking = std::any_of(guiProject.checkVsConfigs.cbegin(), guiProject.checkVsConfigs.cend(), [&](const std::string &c) { + return c == pc.configuration; }); if (!doChecking) continue; } - FileSettings fs{cfilename, Standards::Language::None, 0}; // file will be identified later on - fs.cfg = p.name; - // TODO: detect actual MSC version + FileSettings fs{ compile.filename, Standards::Language::None, 0 }; // file will be identified later on + fs.cfg = pc.name; fs.msc = true; fs.defines = "_WIN32=1"; - if (p.platform == ProjectConfiguration::Win32) + if (pc.platform == ProjectConfiguration::Win32) { fs.platformType = Platform::Type::Win32W; - else if (p.platform == ProjectConfiguration::x64) { + // MSVC always defines _M_IX86 for x86 targets; 600 = Pentium Pro / modern default. + fs.defines += ";_M_IX86=600"; + } else if (pc.platform == ProjectConfiguration::x64) { fs.platformType = Platform::Type::Win64; fs.defines += ";_WIN64=1"; + // MSVC defines both _M_X64 and _M_AMD64 (both == 100) for x64 targets. + fs.defines += ";_M_X64=100;_M_AMD64=100"; + } else if (pc.platform == ProjectConfiguration::ARM64) { + fs.platformType = Platform::Type::WinARM64; + fs.defines += ";_M_ARM64=1"; + } else if (pc.platform == ProjectConfiguration::ARM64EC) { + // ARM64EC is an x64-ABI on ARM64 hardware (VS 2022+). + // MSVC defines _M_ARM64EC and the two x64 macros for this target. + fs.platformType = Platform::Type::WinARM64EC; + fs.defines += ";_WIN64=1"; + fs.defines += ";_M_ARM64EC=1;_M_X64=100;_M_AMD64=100"; + } else if (pc.platform == ProjectConfiguration::ARM) { + fs.platformType = Platform::Type::WinARM; + // MSVC defines _M_ARM=7 (Thumb-2 instruction set) for ARM targets. + fs.defines += ";_M_ARM=7"; } - std::string additionalIncludePaths; - for (const ItemDefinitionGroup &i : itemDefinitionGroupList) { - if (!i.conditionIsTrue(p, cfilename, errors)) - continue; - fs.standard = Standards::getCPP(i.cppstd); - fs.defines += ';' + i.preprocessorDefinitions; - if (i.enhancedInstructionSet == "StreamingSIMDExtensions") - fs.defines += ";__SSE__"; - else if (i.enhancedInstructionSet == "StreamingSIMDExtensions2") - fs.defines += ";__SSE2__"; - else if (i.enhancedInstructionSet == "AdvancedVectorExtensions") - fs.defines += ";__AVX__"; - else if (i.enhancedInstructionSet == "AdvancedVectorExtensions2") - fs.defines += ";__AVX2__"; - else if (i.enhancedInstructionSet == "AdvancedVectorExtensions512") - fs.defines += ";__AVX512__"; - additionalIncludePaths += ';' + i.additionalIncludePaths; - } - bool useUnicode = false; - for (const ConfigurationPropertyGroup &c : configurationPropertyGroups) { - if (!c.conditionIsTrue(p, cfilename, errors)) - continue; - // in msbuild the last definition wins - useUnicode = c.useUnicode; - fs.useMfc = c.useOfMfc; + + // Currently selects C or C++ from the file extension. + bool isCFile = Path::getFilenameExtensionInLowerCase(compile.filename) == ".c"; + const std::string &compileAs = compile.get("CompileAs"); + if (caseInsensitiveStringCompare(compileAs, "CompileAsC") == 0) + isCFile = true; + else if (caseInsensitiveStringCompare(compileAs, "CompileAsCpp") == 0) + isCFile = false; + if (isCFile) { + Standards::cstd_t cstd = Standards::C17; + const std::string &languageStandardC = compile.get("LanguageStandard_C"); + if (caseInsensitiveStringCompare(languageStandardC, "stdc11") == 0) + cstd = Standards::C11; + else if (caseInsensitiveStringCompare(languageStandardC, "stdc17") == 0) + cstd = Standards::C17; + else if (caseInsensitiveStringCompare(languageStandardC, "stdclatest") == 0) + cstd = Standards::CLatest; + fs.standard = Standards::getC(cstd); + } else { + Standards::cppstd_t cppstd = Standards::CPP14; + const std::string &languageStandard = compile.get("LanguageStandard"); + if (caseInsensitiveStringCompare(languageStandard, "stdcpp11") == 0) + cppstd = Standards::CPP11; + else if (caseInsensitiveStringCompare(languageStandard, "stdcpp14") == 0) + cppstd = Standards::CPP14; + else if (caseInsensitiveStringCompare(languageStandard, "stdcpp17") == 0) + cppstd = Standards::CPP17; + else if (caseInsensitiveStringCompare(languageStandard, "stdcpp20") == 0) + cppstd = Standards::CPP20; + else if (caseInsensitiveStringCompare(languageStandard, "stdcpp23") == 0) + cppstd = Standards::CPP23; + else if (caseInsensitiveStringCompare(languageStandard, "stdcpplatest") == 0) + cppstd = Standards::CPPLatest; + fs.standard = Standards::getCPP(cppstd); } - if (useUnicode) { - fs.defines += ";UNICODE=1;_UNICODE=1"; + + // Inject _MSC_VER and _MSC_FULL_VER derived from PlatformToolset. + // Without these, standard-library and Windows SDK headers (, + // ) use generic fallbacks, fail to compile, or misidentify + // the supported language standard. + std::string mscVer = "1950"; // VS 2026 fallback + std::string mscFullVer = "195000000"; + + // Prefer item-level override, then project property, then DefaultPlatformToolset. + std::string toolset = compile.get("PlatformToolset"); + if (toolset.empty()) { + const auto tsIt = properties.find("PlatformToolset"); + if (tsIt != properties.end()) + toolset = tsIt->second; + else { + const auto defIt = properties.find("DefaultPlatformToolset"); + if (defIt != properties.end()) + toolset = defIt->second; + } } - fsSetDefines(fs, fs.defines); - fsSetIncludePaths(fs, Path::getPathFromFilename(filename), toStringList(includePath + ';' + additionalIncludePaths), variables); - for (const auto &path : sharedItemsIncludePaths) { - fs.includePaths.emplace_back(path); + + if (caseInsensitiveStringCompare(toolset, "v145") == 0) { // VS 2026 + mscVer = "1950"; + mscFullVer = "195000000"; + } else if (caseInsensitiveStringCompare(toolset, "v144") == 0) { // VS 2025 + mscVer = "1940"; + mscFullVer = "194000000"; + } else if (caseInsensitiveStringCompare(toolset, "v143") == 0) { // VS 2022 + mscVer = "1930"; + mscFullVer = "193000000"; + } else if (caseInsensitiveStringCompare(toolset, "v142") == 0) { // VS 2019 + mscVer = "1920"; + mscFullVer = "192000000"; + } else if (caseInsensitiveStringCompare(toolset, "v141") == 0) { // VS 2017 + mscVer = "1910"; + mscFullVer = "191000000"; + } else if (caseInsensitiveStringCompare(toolset, "v140") == 0) { // VS 2015 + mscVer = "1900"; + mscFullVer = "190000000"; + } else if (caseInsensitiveStringCompare(toolset, "v14") == 0) { + try { + const int sub = std::stoi(toolset.substr(3)); + mscVer = std::to_string(1900 + (sub * 10)); + mscFullVer = mscVer + "00000"; + } catch (...) {} + } else { + // Unknown or absent toolset: derive from VisualStudioVersion. + const auto vsIt = properties.find("VisualStudioVersion"); + if (vsIt != properties.end()) { + try { + const double vsVer = std::stod(vsIt->second); + if (vsVer >= 19.0) { + // future VS: keep the default (1940) as a safe floor + } else if (vsVer >= 18.0) { // VS 2026 + mscVer = "1950"; mscFullVer = "195000000"; + } else if (vsVer >= 17.0) { // VS 2025 + mscVer = "1940"; mscFullVer = "194000000"; + } else if (vsVer >= 16.0) { // VS 2022 + mscVer = "1930"; mscFullVer = "193000000"; + } else if (vsVer >= 15.0) { // VS 2019 + mscVer = "1920"; mscFullVer = "192000000"; + } else if (vsVer >= 14.0) { // VS 2017 + mscVer = "1910"; mscFullVer = "191000000"; + } + } catch (...) {} + } } - fileSettings.push_back(std::move(fs)); - } - } - return true; -} + fs.defines += ";_MSC_VER=" + mscVer + ";_MSC_FULL_VER=" + mscFullVer; + + // _MSVC_LANG mirrors the C++ standard flag. MSVC only defines this for + // C++ translation units; C files do not get it (even with /TC). + // Note: MSVC does NOT set __cplusplus to the standard value unless + // /Zc:__cplusplus is passed; code that needs the standard should test + // _MSVC_LANG, which is always set correctly. + if (!isCFile) { + std::string msvcLang = "201402L"; // MSVC default when no /std: flag is set + const std::string &languageStandard = compile.get("LanguageStandard"); + if (languageStandard == "stdcpp11") + msvcLang = "201103L"; + else if (languageStandard == "stdcpp14") + msvcLang = "201402L"; + else if (languageStandard == "stdcpp17") + msvcLang = "201703L"; + else if (languageStandard == "stdcpp20") + msvcLang = "202002L"; + else if (languageStandard == "stdcpp23") + msvcLang = "202302L"; + else if (languageStandard == "stdcpplatest") + msvcLang = "202604L"; // current C++26 draft baseline + fs.defines += ";_MSVC_LANG=" + msvcLang; + } -ImportProject::SharedItemsProject ImportProject::importVcxitems(const std::string& filename, const std::vector& fileFilters, std::vector &cache) -{ - auto isInCacheCheck = [filename](const ImportProject::SharedItemsProject& e) -> bool { - return filename == e.pathToProjectFile; - }; - const auto iterator = std::find_if(cache.begin(), cache.end(), isInCacheCheck); - if (iterator != std::end(cache)) { - return *iterator; - } + const std::string enableEnhancedInstructionSet = compile.get("EnableEnhancedInstructionSet"); + if (caseInsensitiveStringCompare(enableEnhancedInstructionSet, "StreamingSIMDExtensions") == 0) { + if (pc.platform == ProjectConfiguration::Win32) + fs.defines += ";_M_IX86_FP=1"; + } else if (caseInsensitiveStringCompare(enableEnhancedInstructionSet, "StreamingSIMDExtensions2") == 0) { + if (pc.platform == ProjectConfiguration::Win32) + fs.defines += ";_M_IX86_FP=2"; + } else if (caseInsensitiveStringCompare(enableEnhancedInstructionSet, "AdvancedVectorExtensions") == 0) + fs.defines += ";__AVX__"; + else if (caseInsensitiveStringCompare(enableEnhancedInstructionSet, "AdvancedVectorExtensions2") == 0) + fs.defines += ";__AVX2__"; + else if (caseInsensitiveStringCompare(enableEnhancedInstructionSet, "AdvancedVectorExtensions512") == 0) + fs.defines += ";__AVX512F__"; + + const auto charSetIt = properties.find("CharacterSet"); + const std::string charSet = (charSetIt != properties.end()) ? charSetIt->second : std::string(); + + const auto useOfMfcIt = properties.find("UseOfMfc"); + fs.useMfc = useOfMfcIt != properties.end() && !useOfMfcIt->second.empty() && + caseInsensitiveStringCompare(useOfMfcIt->second, "false") != 0; + + if (caseInsensitiveStringCompare(charSet, "Unicode") == 0) + fs.defines += ";UNICODE=1;_UNICODE=1"; + else if (caseInsensitiveStringCompare(charSet, "MultiByte") == 0) + fs.defines += ";_MBCS=1"; - SharedItemsProject result; - result.pathToProjectFile = filename; + const auto configurationTypeIt = properties.find("ConfigurationType"); + if (configurationTypeIt != properties.end() && + caseInsensitiveStringCompare(configurationTypeIt->second, "DynamicLibrary") == 0) { + fs.defines += ";_WINDLL"; + } - PathMatch filtermatcher(fileFilters, Path::getCurrentPath()); + if (useOfMfcIt != properties.end() && + caseInsensitiveStringCompare(useOfMfcIt->second, "Dynamic") == 0) { + fs.defines += ";_AFXDLL"; + } - tinyxml2::XMLDocument doc; - const tinyxml2::XMLError error = doc.LoadFile(filename.c_str()); - if (error != tinyxml2::XML_SUCCESS) { - errors.emplace_back(std::string("Visual Studio project file is not a valid XML - ") + tinyxml2::XMLDocument::ErrorIDToName(error)); - return result; - } - const tinyxml2::XMLElement * const rootnode = doc.FirstChildElement(); - if (rootnode == nullptr) { - errors.emplace_back("Visual Studio project file has no XML root node"); - return result; - } - for (const tinyxml2::XMLElement *node = rootnode->FirstChildElement(); node; node = node->NextSiblingElement()) { - if (std::strcmp(node->Name(), "ItemGroup") == 0) { - for (const tinyxml2::XMLElement *e = node->FirstChildElement(); e; e = e->NextSiblingElement()) { - if (std::strcmp(e->Name(), "ClCompile") == 0) { - const char* include = e->Attribute("Include"); - if (include && Path::acceptFile(include)) { - std::string file(include); - findAndReplace(file, "$(MSBuildThisFileDirectory)", "./"); - - // Skip file if it doesn't match the filter - if (!fileFilters.empty() && !filtermatcher.match(file)) - continue; - - result.sourceFiles.emplace_back(file); + const auto useOfAtlIt = properties.find("UseOfAtl"); + if (useOfAtlIt != properties.end() && + caseInsensitiveStringCompare(useOfAtlIt->second, "Dynamic") == 0) { + fs.defines += ";_ATL_DLL"; + } + + std::string runtimeLibrary; + const auto runtimeLibraryIt = properties.find("RuntimeLibrary"); + if (runtimeLibraryIt != properties.end()) + runtimeLibrary = runtimeLibraryIt->second; + + if (runtimeLibrary.empty()) { + const auto useDebugLibrariesIt = properties.find("UseDebugLibraries"); + const bool debug = useDebugLibrariesIt != properties.end() && + caseInsensitiveStringCompare(useDebugLibrariesIt->second, "true") == 0; + runtimeLibrary = debug ? "MultiThreadedDebugDLL" : "MultiThreadedDLL"; + } + + if (caseInsensitiveStringCompare(runtimeLibrary, "MultiThreadedDebugDLL") == 0) + fs.defines += ";_MT;_DLL;_DEBUG"; + else if (caseInsensitiveStringCompare(runtimeLibrary, "MultiThreadedDLL") == 0) + fs.defines += ";_MT;_DLL"; + else if (caseInsensitiveStringCompare(runtimeLibrary, "MultiThreadedDebug") == 0) + fs.defines += ";_MT;_DEBUG"; + else if (caseInsensitiveStringCompare(runtimeLibrary, "MultiThreaded") == 0) + fs.defines += ";_MT"; + + std::string defines = fs.defines; + if (!compile.get("PreprocessorDefinitions").empty()) + defines += (";" + compile.get("PreprocessorDefinitions")); + const std::string &undefStr = compile.get("UndefinePreprocessorDefinitions"); + if (!undefStr.empty()) { + // Build a set of macro names to suppress (skip %(InheritedValues) tokens). + std::set undefs; + std::string seg; + for (std::size_t i = 0; i <= undefStr.size(); ++i) { + const char c = (i < undefStr.size()) ? undefStr[i] : ';'; + if (c == ';') { + if (!seg.empty() && !startsWith(seg, "%(")) + undefs.insert(seg); + seg.clear(); + } else { + seg += c; + } + } + // Remove matching entries from the accumulated defines string. + std::string filtered; + seg.clear(); + for (std::size_t i = 0; i <= defines.size(); ++i) { + const char c = (i < defines.size()) ? defines[i] : ';'; + if (c == ';') { + if (!seg.empty()) { + const std::string name = seg.substr(0, seg.find('=')); + if (undefs.find(name) == undefs.end()) { + if (!filtered.empty()) + filtered += ';'; + filtered += seg; + } + seg.clear(); + } } else { - errors.emplace_back("Could not find shared items source file"); - return result; + seg += c; } } + defines = std::move(filtered); } - } else if (std::strcmp(node->Name(), "ItemDefinitionGroup") == 0) { - ItemDefinitionGroup temp(node, ""); - for (const auto& includePath : toStringList(temp.additionalIncludePaths)) { - if (includePath == "%(AdditionalIncludeDirectories)") + fsSetDefines(fs, defines); + fsSetIncludePaths(fs, projectDir, toStringList(propertyOrEmpty(properties, "IncludePath")), properties); + + std::string rawAdditionalIncludes = compile.get("AdditionalIncludeDirectories"); + expandMSBuildVariables(rawAdditionalIncludes, properties); // Pre-expand so toStringList splits them cleanly! + + fs.systemIncludePaths = std::move(fs.includePaths); + fsSetIncludePaths(fs, projectDir, toStringList(rawAdditionalIncludes), properties); + + std::string rawForcedIncludes = compile.get("ForcedIncludeFiles"); + expandMSBuildVariables(rawForcedIncludes, properties); + + fs.forcedIncludes.clear(); + for (const std::string &forcedInclude : toStringList(rawForcedIncludes)) { + if (forcedInclude.empty()) continue; - std::string toAdd(includePath); - findAndReplace(toAdd, "$(MSBuildThisFileDirectory)", "./"); - result.includePaths.emplace_back(toAdd); + // Standardize native backslashes to uniform forward slashes + const std::string normalized = Path::fromNativeSeparators(forcedInclude); + const bool relative = isRelative(normalized); + std::string resolved; + + if (!relative) { + // Step 2a: For absolute paths, clean up dot notation and simplify structures + resolved = normalized; + simplifyPathWithVariables(resolved, properties); + } else { + // Relative paths - Search prioritized include paths FIRST (Visual Studio Priority) + bool matchFound = false; + bool isDir = false; + + for (const std::string &includePath : fs.includePaths) { + std::string candidate = toAbsolute(normalized, includePath, properties); + + // Pass the address of isDir to explicitly verify the path is a FILE, never a directory + if (Path::exists(candidate, &isDir) && !isDir) { + resolved = std::move(candidate); + matchFound = true; + break; + } + } + + // Step 3: Fall back to checking the Project Root Folder only if no include paths matched + if (!matchFound) { + resolved = toAbsolute(normalized, projectDir, properties); + } + } + + // Append the safely validated and prioritized file path + fs.forcedIncludes.push_back(std::move(resolved)); } + + fileSettings.push_back(std::move(fs)); } } - result.successful = true; - cache.emplace_back(result); - return result; + mImportGraph = ImportGraph(); // deactivate: later imports evaluate conditions live + + return true; } bool ImportProject::importBcb6Prj(const std::string &projectFilename) @@ -1360,17 +5516,17 @@ bool ImportProject::importBcb6Prj(const std::string &projectFilename) predefines += ";__WIN32__=1"; } - // Include paths may contain variables like "$(BCB)\include" or "$(BCB)\include\vcl". + // Include paths may contain properties like "$(BCB)\include" or "$(BCB)\include\vcl". // Those get resolved by ImportProject::FileSettings::setIncludePaths by - // 1. checking the provided variables map ("BCB" => "C:\\Program Files (x86)\\Borland\\CBuilder6") - // 2. checking env variables as a fallback - // Setting env is always possible. Configuring the variables via cli might be an addition. + // 1. checking the provided properties map ("BCB" => "C:\\Program Files (x86)\\Borland\\CBuilder6") + // 2. checking env properties as a fallback + // Setting env is always possible. Configuring the properties via cli might be an addition. // Reading the BCB6 install location from registry in windows environments would also be possible, // but I didn't see any such functionality around the source. Not in favor of adding it only // for the BCB6 project loading. - std::map variables; + PropertiesMap properties; const std::string defines = predefines + ";" + sysdefines + ";" + userdefines; - const std::string cppDefines = cppPredefines + ";" + defines; + const std::string cppDefines = cppPredefines + ";" + defines; const bool forceCppMode = (cflags.find("-P") != cflags.end()); for (const std::string &c : compileList) { @@ -1385,8 +5541,11 @@ bool ImportProject::importBcb6Prj(const std::string &projectFilename) // We can also force C++ compilation for all files using the -P command line switch. const bool cppMode = forceCppMode || Path::getFilenameExtensionInLowerCase(c) == ".cpp"; // TODO: needs to set language and ignore later identification and language enforcement - FileSettings fs{Path::simplifyPath(Path::isAbsolute(c) ? c : projectDir + c), Standards::Language::None, 0}; // file will be identified later on - fsSetIncludePaths(fs, projectDir, toStringList(includePath), variables); + // Use classifyPath so that root-relative source paths (\src\foo.cpp) are + // resolved against projectDir's drive rather than passed through as-is. + const PathKind _ck = classifyPath(Path::fromNativeSeparators(c)); + FileSettings fs{Path::simplifyPath((_ck == PathKind::UNC || _ck == PathKind::DriveAbsolute) ? c : projectDir + c), Standards::Language::None, 0}; // file will be identified later on + fsSetIncludePaths(fs, projectDir, toStringList(includePath), properties); fsSetDefines(fs, cppMode ? cppDefines : defines); fileSettings.push_back(std::move(fs)); } @@ -1396,8 +5555,20 @@ bool ImportProject::importBcb6Prj(const std::string &projectFilename) static std::string joinRelativePath(const std::string &path1, const std::string &path2) { - if (!path1.empty() && !Path::isAbsolute(path2)) - return path1 + path2; + if (!path1.empty()) { + // Classify path2 to decide whether to prepend path1. + // Use the original string (before fromNativeSeparators) so we can tell a + // Windows root-relative path "\foo" (starts with backslash) from a + // genuine Unix absolute path "/foo" (starts with forward slash): + // - UNC (\\server or //server) and DriveAbsolute (C:\...) are always absolute. + // - A native forward-slash start means Unix-absolute ("/foo") or UNC ("//"). + // - A native backslash start means Windows root-relative ("\foo"), which on + // Linux after fromNativeSeparators would look like "/foo" but is NOT absolute. + const bool nativelyAbsolute = !path2.empty() && path2[0] == '/'; + const PathKind pk = classifyPath(Path::fromNativeSeparators(path2)); + if (!nativelyAbsolute && pk != PathKind::UNC && pk != PathKind::DriveAbsolute) + return path1 + path2; + } return path2; } @@ -1484,6 +5655,10 @@ bool ImportProject::importCppcheckGuiProject(std::istream &istr, Settings &setti else if (strcmp(name, CppcheckXml::UndefinesElementName) == 0) { for (const std::string &u : readXmlStringList(node, "", CppcheckXml::UndefineName, nullptr)) temp.userUndefs.insert(u); + } else if (strcmp(name, CppcheckXml::UserIncludeElementName) == 0) { + const char* i = node->GetText(); + if (i) + temp.userIncludes.emplace_back(i); } else if (strcmp(name, CppcheckXml::ImportProjectElementName) == 0) { const std::string t_str = empty_if_null(node->GetText()); if (!t_str.empty()) @@ -1605,6 +5780,7 @@ bool ImportProject::importCppcheckGuiProject(std::istream &istr, Settings &setti settings.includePaths = temp.includePaths; // TODO: append instead of overwrite settings.userDefines = temp.userDefines; // TODO: append instead of overwrite settings.userUndefs = temp.userUndefs; // TODO: append instead of overwrite + settings.userIncludes = temp.userIncludes; // TODO: append instead of overwrite for (const std::string &addon : temp.addons) settings.addons.emplace(addon); settings.clang = temp.clang; @@ -1618,7 +5794,18 @@ bool ImportProject::importCppcheckGuiProject(std::istream &istr, Settings &setti for (const std::string &p : paths) guiProject.pathNames.push_back(Path::fromNativeSeparators(p)); - supprs.nomsg.addSuppressions(std::move(suppressions)); // TODO: check result + + bool ok = true; + for (const auto &suppression : suppressions) { + const std::string addError = supprs.nomsg.addSuppression(suppression); + if (!addError.empty()) { + errors.emplace_back(addError); + ok = false; + } + } + if (!ok) + return false; + settings.checkHeaders = temp.checkHeaders; settings.checkUnusedTemplates = temp.checkUnusedTemplates; settings.maxCtuDepth = temp.maxCtuDepth; @@ -1640,11 +5827,23 @@ void ImportProject::selectOneVsConfig(Platform::Type platform) } const FileSettings &fs = *it; bool remove = false; - if (!startsWith(fs.cfg,"Debug")) + const std::string cfgName = fs.cfg.substr(0, fs.cfg.find('|')); + if (cfgName.size() < 5 || caseInsensitiveStringCompare(cfgName.substr(0, 5), "Debug") != 0) + remove = true; + + if (platform == Platform::Type::Win64 && fs.platformType != Platform::Type::Win64) + remove = true; + else if (platform == Platform::Type::WinARM64 && fs.platformType != Platform::Type::WinARM64) remove = true; - if (platform == Platform::Type::Win64 && fs.platformType != platform) + else if (platform == Platform::Type::WinARM64EC && fs.platformType != Platform::Type::WinARM64EC) remove = true; - else if ((platform == Platform::Type::Win32A || platform == Platform::Type::Win32W) && fs.platformType == Platform::Type::Win64) + else if (platform == Platform::Type::WinARM && fs.platformType != Platform::Type::WinARM) + remove = true; + else if ((platform == Platform::Type::Win32A || platform == Platform::Type::Win32W) && + (fs.platformType == Platform::Type::Win64 || + fs.platformType == Platform::Type::WinARM64 || + fs.platformType == Platform::Type::WinARM64EC || + fs.platformType == Platform::Type::WinARM)) remove = true; else if (filenames.find(fs.filename()) != filenames.end()) remove = true; @@ -1669,9 +5868,19 @@ void ImportProject::selectVsConfigurations(Platform::Type platform, const std::v bool remove = false; if (std::find(configurations.begin(), configurations.end(), config) == configurations.end()) remove = true; - if (platform == Platform::Type::Win64 && fs.platformType != platform) + if (platform == Platform::Type::Win64 && fs.platformType != Platform::Type::Win64) + remove = true; + else if (platform == Platform::Type::WinARM64 && fs.platformType != Platform::Type::WinARM64) + remove = true; + else if (platform == Platform::Type::WinARM64EC && fs.platformType != Platform::Type::WinARM64EC) + remove = true; + else if (platform == Platform::Type::WinARM && fs.platformType != Platform::Type::WinARM) remove = true; - else if ((platform == Platform::Type::Win32A || platform == Platform::Type::Win32W) && fs.platformType == Platform::Type::Win64) + else if ((platform == Platform::Type::Win32A || platform == Platform::Type::Win32W) && + (fs.platformType == Platform::Type::Win64 || + fs.platformType == Platform::Type::WinARM64 || + fs.platformType == Platform::Type::WinARM64EC || + fs.platformType == Platform::Type::WinARM)) remove = true; if (remove) { it = fileSettings.erase(it); @@ -1693,18 +5902,76 @@ void ImportProject::setRelativePaths(const std::string &filename) const std::vector basePaths{Path::fromNativeSeparators(Path::getCurrentPath())}; for (auto &fs: fileSettings) { fs.file.setPath(Path::getRelativePath(fs.filename(), basePaths)); - for (auto &includePath: fs.includePaths) - includePath = Path::getRelativePath(includePath, basePaths); + for (auto &includePath: fs.includePaths) { + const std::string rel = Path::getRelativePath(includePath, basePaths); + includePath = rel.empty() ? "." : rel; + } + for (auto &includePath: fs.systemIncludePaths) { + const std::string rel = Path::getRelativePath(includePath, basePaths); + includePath = rel.empty() ? "." : rel; + } + for (auto &forcedInclude: fs.forcedIncludes) + forcedInclude = Path::getRelativePath(forcedInclude, basePaths); } } // only used by tests (testimportproject.cpp::testVcxprojConditions): // cppcheck-suppress unusedFunction -bool cppcheck::testing::evaluateVcxprojCondition(const std::string& condition, const std::string& configuration, +bool cppcheck::testing::evaluateVcxprojCondition(const std::string& condition, + const std::string& configuration, const std::string& platform) { - ProjectConfiguration p; - p.configuration = configuration; - p.platformStr = platform; - return ConditionalGroup::evalCondition(condition, p); + ImportProject project; + PropertiesMap properties; + properties["Platform"] = platform; + properties["Configuration"] = configuration; + // Use ConditionParser directly so exceptions propagate to the caller; + // evalCondition swallows them (by design for production use). + ImportProject::ConditionParser parser(project, condition, properties); + return parser.parse(); +} + +// cppcheck-suppress unusedFunction +std::string cppcheck::testing::expandMSBuildExpression(const std::string& expr) +{ + ImportProject project; + PropertiesMap properties; + std::string s = expr; + project.expandMSBuildVariables(s, properties); + return s; +} + +// cppcheck-suppress unusedFunction +std::string cppcheck::testing::expandMSBuildProperties(const std::string& expr, + const std::string& configuration, + const std::string& platform) +{ + ImportProject project; + PropertiesMap properties; + properties["Configuration"] = configuration; + properties["Platform"] = platform; + std::string s = expr; + project.expandMSBuildVariables(s, properties); + return s; +} + +// only used by tests (testimportproject.cpp::testVcxitemsPathResolution): +// cppcheck-suppress unusedFunction +std::string cppcheck::testing::resolveVcxitemsFilename(const std::string& items, const std::string& projectDir) +{ + ImportProject project; + PropertiesMap properties; + if (!projectDir.empty()) + properties["ProjectDir"] = projectDir; + std::string filename(items); + if (!project.simplifyPathWithVariables(filename, properties)) + return {}; + // Use classifyPath so that root-relative paths (\foo -> C:\foo) are resolved + // against the base drive, not treated as absolute on Linux. + { + const PathKind _fkind = classifyPath(Path::fromNativeSeparators(filename)); + if (_fkind != PathKind::UNC && _fkind != PathKind::DriveAbsolute && properties.count("ProjectDir") > 0) + filename = project.toAbsolute(filename, properties.at("ProjectDir"), properties); + } + return filename; } diff --git a/lib/importproject.h b/lib/importproject.h index 4a6e318c02d..a258336a551 100644 --- a/lib/importproject.h +++ b/lib/importproject.h @@ -26,18 +26,22 @@ #include "platform.h" #include "utils.h" +#include #include #include #include #include #include #include +#include +#include #include class Settings; struct Suppressions; + namespace tinyxml2 { - class XMLDocument; + class XMLElement; } /// @addtogroup Core @@ -53,14 +57,25 @@ namespace cppcheck { namespace testing { CPPCHECKLIB bool evaluateVcxprojCondition(const std::string& condition, const std::string& configuration, const std::string& platform); + CPPCHECKLIB std::string expandMSBuildExpression(const std::string& expr); + CPPCHECKLIB std::string expandMSBuildProperties(const std::string& expr, const std::string& configuration, const std::string& platform); + CPPCHECKLIB std::string resolveVcxitemsFilename(const std::string& items, const std::string& projectDir); } } +using PropertiesMap = std::map; +using MetadataMap = std::map; + /** * @brief Importing project settings. */ class CPPCHECKLIB WARN_UNUSED ImportProject { public: + friend CPPCHECKLIB bool cppcheck::testing::evaluateVcxprojCondition(const std::string &condition, const std::string &configuration, const std::string &platform); + friend CPPCHECKLIB std::string cppcheck::testing::expandMSBuildExpression(const std::string &expr); + friend CPPCHECKLIB std::string cppcheck::testing::expandMSBuildProperties(const std::string &expr, const std::string &configuration, const std::string &platform); + friend CPPCHECKLIB std::string cppcheck::testing::resolveVcxitemsFilename(const std::string &items, const std::string &projectDir); + enum class Type : std::uint8_t { NONE, UNKNOWN, @@ -73,14 +88,46 @@ class CPPCHECKLIB WARN_UNUSED ImportProject { BORLAND, CPPCHECK_GUI }; + enum class ImportResult : std::uint8_t { + Ok, + Cycle, // Visual Studio/MSBuild reports a circular import; continue safely + NotResolvable, + NotFound, + NotValid, + }; + + /// Visual Studio (like MSBuild) evaluates a project in separate passes over the + /// whole resolved import graph: every PropertyGroup/Import first (so every + /// property, wherever in the graph it is set, is final before anything else + /// runs), then every ItemDefinitionGroup, then every ItemGroup. A PropertyGroup + /// positioned after an ItemDefinitionGroup -- or reached through a file imported + /// later in the document -- is still visible to that earlier ItemDefinitionGroup. + enum class EvalPhase : std::uint8_t { + Properties, ///< Pass 1: PropertyGroup + Import/ImportGroup only; decides and records the import graph + ItemDefs, ///< Pass 2: ItemDefinitionGroup only; replays the recorded import graph + Items, ///< Pass 3: ItemGroup only; replays the recorded import graph + Discover, ///< Structural discovery bootstrap scan: PropertyGroup/ImportGroup/ + ///< Import/Choose are walked like Properties, but every Condition is + ///< treated as satisfied and Choose explores every branch instead of + ///< selecting one (see mDiscovering), and any errors/debugs generated + ///< are discarded rather than surfaced. + }; protected: static void fsSetDefines(FileSettings& fs, std::string defs); - static void fsSetIncludePaths(FileSettings& fs, const std::string &basepath, const std::list &in, std::map &variables); + void fsSetIncludePaths(FileSettings& fs, const std::string &basepath, const std::list &in, const PropertiesMap &properties); + /** Set the project path prefix used to resolve relative paths in the project file. + * Normally set automatically by import(); exposed here so unit tests can exercise + * path-joining without needing a real file on disk. */ + // cppcheck-suppress unusedFunction + void setProjectPath(const std::string& p) { + mPath = p; + } public: std::list fileSettings; std::vector errors; + std::vector debugs; ImportProject() = default; virtual ~ImportProject() = default; @@ -106,32 +153,257 @@ class CPPCHECKLIB WARN_UNUSED ImportProject { void ignoreOtherConfigs(const std::string &cfg); Type import(const std::string &filename, Settings *settings=nullptr, Suppressions *supprs=nullptr); + + static const std::string &importResultStr(ImportResult result); + protected: - bool importCompileCommands(std::istream &istr); + bool processCompileCommands(std::istream &istr); bool importCppcheckGuiProject(std::istream &istr, Settings &settings, Suppressions &supprs); static std::string collectArgs(const std::string &cmd, std::vector &args); - - struct SharedItemsProject { - bool successful = false; - std::string pathToProjectFile; - std::vector includePaths; - std::vector sourceFiles; - }; - - bool importVcxproj(const std::string &filename, std::map &variables, const std::string &additionalIncludeDirectories, const std::vector &fileFilters, std::vector &cache); - bool importVcxproj(const std::string &filename, const tinyxml2::XMLDocument &doc, std::map &variables, const std::string &additionalIncludeDirectories, const std::vector &fileFilters, std::vector &cache); + void setRelativePaths(const std::string &filename); private: + struct PropertyValueExpander; + class ConditionParser; + static void parseArgs(FileSettings &fs, const std::vector &args); - void setRelativePaths(const std::string &filename); - bool importSln(std::istream &istr, const std::string &path, const std::vector &fileFilters); - bool importSlnx(const std::string& filename, const std::vector& fileFilters); - SharedItemsProject importVcxitems(const std::string &filename, const std::vector &fileFilters, std::vector &cache); bool importBcb6Prj(const std::string &projectFilename); + struct ProjectConfiguration { + explicit ProjectConfiguration(const tinyxml2::XMLElement *cfg); + + std::string name; + std::string configuration; + enum : std::uint8_t { Win32, x64, ARM64, ARM64EC, ARM, Unknown } platform = Unknown; + std::string platformStr; + }; + + struct ItemGroupClCompile { + explicit ItemGroupClCompile(std::string filename) : filename(std::move(filename)) {} + std::string filename; + MetadataMap metadata; + const std::string &get(const std::string &key) const { + static const std::string empty; + const auto it = metadata.find(key); + return (it != metadata.end()) ? it->second : empty; + } + }; + + bool importSln(std::istream &istr, const std::string &filename, const std::vector &fileFilters); + bool importSlnx(const std::string& filename, const std::vector& fileFilters); + bool importVcxproj(const std::string &filename, PropertiesMap &properties, const std::vector &fileFilters); + + ImportResult processImport(const std::string &file, + PropertiesMap &properties, + MetadataMap &metadata, + std::list &compileList, + std::list &projectConfigurationList, + std::unordered_set &importStack, + EvalPhase phase); + ImportResult processImportProject(const tinyxml2::XMLElement *node, + const std::string &projectDir, + PropertiesMap &properties, + MetadataMap &metadata, + std::list &compileList, + std::list &projectConfigurationList, + std::unordered_set &importStack, + EvalPhase phase); + ImportResult processImportGroup(const tinyxml2::XMLElement *node, + const std::string &baseDir, + PropertiesMap &properties, + MetadataMap &metadata, + std::list &compileList, + std::list &projectConfigurationList, + std::unordered_set &importStack, + EvalPhase phase); + // Decide (Properties pass) or replay (ItemDefs/Items pass) which single + // / child of a is taken, then process that child's + // own children (PropertyGroup/ItemDefinitionGroup/ItemGroup/ImportGroup/nested + // Choose) exactly as processElementChildren() would process them inline. + // During the discovery bootstrap scan (mDiscovering set) this selection is + // skipped entirely: every and any is processed instead of + // choosing one -- see the mDiscovering branch at the top of the definition. + ImportResult processChoose(const tinyxml2::XMLElement *node, + const std::string &baseDir, + PropertiesMap &properties, + MetadataMap &metadata, + std::list &compileList, + std::list &projectConfigurationList, + std::unordered_set &importStack, + EvalPhase phase); + ImportResult processCompile(const tinyxml2::XMLElement *node, + const std::string &projectDir, + const PropertiesMap &properties, + const MetadataMap &metadata, + std::list &compileList); + ImportResult processElementChildren(const tinyxml2::XMLElement *parent, + const std::string &baseDir, + PropertiesMap &properties, + MetadataMap &metadata, + std::list &compileList, + std::list &projectConfigurationList, + std::unordered_set &importStack, + EvalPhase phase); + void applyClCompileUpdate(const tinyxml2::XMLElement *node, + const std::string &baseDir, + const PropertiesMap &properties, + std::list &compileList); + void applyClCompileRemove(const tinyxml2::XMLElement *node, + const std::string &baseDir, + const PropertiesMap &properties, + std::list &compileList); + // Returns (original-segment, absolute-path) pairs. The original segment is + // the spec after property expansion but before toAbsolute(), preserving the + // relative form needed to compute %(RelativeDir) in processCompile(). + std::pair expandItemSpec(const std::string &spec, + const std::string &projectDir, + const PropertiesMap &properties); + std::string applyMSBuildStaticFunction(const std::string &className, + const std::string &member, + const std::vector &args, + const PropertiesMap *properties = nullptr); + void applyClCompileChild(const tinyxml2::XMLElement *e1, + const PropertiesMap &properties, + MetadataMap &metadata); + void expandMSBuildVariables(std::string &s, const PropertiesMap &properties); + bool evalCondition(const std::string &condition, const PropertiesMap &properties); + bool conditionIsTrue(const tinyxml2::XMLElement *node, const PropertiesMap &properties); + bool hasName(const tinyxml2::XMLElement *node, const char *nodeName, const PropertiesMap &properties); + bool hasNameAndLabel(const tinyxml2::XMLElement *node, const char *nodeName, const char *nodeAttr, const PropertiesMap &properties); + bool hasNameAndNotLabel(const tinyxml2::XMLElement * node, const char *nodeName, const char *nodeAttr, const PropertiesMap & properties); + // Decide (Properties pass) or replay (ItemDefs/Items pass) whether one import-graph + // branch point -- an , an , or a synthetic ForceImportXxx / + // Directory.Build.* import attempt performed by attemptSyntheticImport() -- is + // taken. During the Properties pass `conditionHolds` (already evaluated by the + // caller) is recorded and returned as-is, and `file` (the target path the caller + // has already resolved, if any) is recorded alongside it. During ItemDefs/Items, + // both the condition and the file the caller just (re)computed are ignored, and + // the recorded pair from the Properties pass is replayed instead: the resolved + // file's own $(...) references may include a property that was still unset (or + // held a different value) at the point Properties visited this branch, so + // recomputing it against the now-final properties could resolve to a different + // file than Properties actually walked, which would desync the properties + // Properties built from the items/definitions ItemDefs/Items evaluate. Outside + // the three-pass evaluation (mImportGraph inactive) this simply returns + // `conditionHolds` unchanged. + bool importGraphDecision(bool conditionHolds, std::string &file); + // Same decide/replay discipline as importGraphDecision(), for a 's + // branch selection instead of an 's target file. During the + // Properties pass `matched` (already computed by the caller: did any + // match, or is there an to fall back to) and `branch` + // (the 0-based index, among the Choose's / children in + // document order, of the one that matched) are recorded and returned/left + // as-is. During ItemDefs/Items, both are ignored and the recorded pair is + // replayed instead: a 's Condition may reference a property that was + // still unset (or held a different value) when the Properties pass reached + // this Choose, so recomputing it against the now-final properties could + // select a different branch than Properties actually walked -- desyncing + // the properties Properties built from the items/definitions ItemDefs/Items + // evaluate, exactly like an Import resolving to a different file would. + // Outside the three-pass evaluation (mImportGraph inactive) this simply + // returns `matched` unchanged. + bool importGraphChooseDecision(bool matched, std::size_t &branch); + // Attempt one of the synthetic (non-XML) imports the Microsoft.Cpp.Default.props / + // .props / .targets emulation performs implicitly: the ForceImportBeforeXxx / + // ForceImportAfterXxx hook properties, and the Directory.Build.props/.targets + // auto-import. `file` is the already-resolved target path, or empty if there is + // nothing to import. Participates in the same decide/replay discipline as a + // regular element via importGraphDecision(). + ImportResult attemptSyntheticImport(std::string file, + PropertiesMap &properties, + MetadataMap &metadata, + std::list &compileList, + std::list &projectConfigurationList, + std::unordered_set &importStack, + EvalPhase phase); + void checkUnexpandedExpressions(const std::string &text, const char *context); + bool simplifyPathWithVariables(std::string &s, const PropertiesMap &properties); + void addProperty(const tinyxml2::XMLElement *node, PropertiesMap &properties); + void addMetadata(const tinyxml2::XMLElement *node, const PropertiesMap &properties, MetadataMap &metadata); + std::string getMetadata(const tinyxml2::XMLElement *node, const PropertiesMap &properties, const MetadataMap &metadata, const std::string &original); + std::string toAbsolute(const std::string &filename, const std::string &baseDir, const PropertiesMap &properties); + static std::string toAbsoluteExpanded(const std::string &filename, const std::string &baseDir); + static std::string toAbsolute(const std::string &path); + static void setSolution(const std::string &filename, PropertiesMap &properties); + void addDebug(const std::string &msg); + + /// Tracks the state of the current three-pass (Properties/ItemDefs/Items) + /// evaluation for one project configuration. During the Properties pass every + /// import-graph branch point is decided and its outcome recorded; during the + /// ItemDefs and Items passes those decisions are replayed in the same order + /// instead of being re-evaluated, so all three passes walk the identical + /// resolved graph -- exactly what real MSBuild/Visual Studio evaluation does by + /// resolving imports once, during property evaluation, and reusing that graph + /// for item definitions and items. + struct ImportGraph { + /// One import-graph branch point's outcome, as decided during the + /// Properties pass: whether it was taken and, if so, either the target + /// file it resolved to at that time (Import/ImportGroup) or the index of + /// the / child it selected (Choose) -- whichever applies + /// to this branch point's kind -- frozen so later passes reuse it + /// verbatim instead of recomputing it against possibly-different state. + struct Decision { + bool taken = false; + std::string file; ///< Import/ImportGroup only; unused for Choose. + std::size_t branch = 0; ///< Choose only; unused for Import/ImportGroup. + }; + /// Files already imported during the current pass. An imported file is + /// processed at most once per pass; a repeated of it is ignored + /// (MSB4011). Reset at the start of each pass; which files end up in it is + /// an automatic consequence of replaying the same decisions in the same + /// order, so it does not itself need to be recorded/replayed. + std::unordered_set imported; + /// Per container file (by import-file-key): the decision for each + /// import-graph branch point encountered while walking that file's + /// children, in traversal order. Populated during the Properties pass; + /// read-only afterwards. + std::map> decisions; + /// Per container file: how far ItemDefs/Items replay has consumed that + /// file's decisions vector. Reset at the start of each replay pass. + std::map cursor; + /// Import-file-key of the file whose children are currently being walked; + /// keys 'decisions'/'cursor'. Maintained by CurrentFileGuard in processImport(). + std::string currentFile; + bool active = false; ///< true only inside importVcxproj's per-configuration loop + bool replay = false; ///< true during ItemDefs/Items: replay recorded decisions, don't decide + }; + std::string mPath; std::set mAllVSConfigs; + /// Names of properties that resolve to the SAME value in every one of this + /// project's configurations, computed once per project file by a priming + /// walk over projectConfigurationList (see importVcxproj()) before the + /// real per-configuration passes run. A macro is unsafe in a project item + /// path only if its value could differ by configuration -- see + /// expandItemSpec()'s use of this alongside invariantItemPathProperties() + /// -- so this set, not just that fixed name list, is what a macro's name + /// is checked against. Cleared and repopulated at the top of each + /// importVcxproj() call; empty (and therefore inert) before the first one. + /// MSBuild property names are case-insensitive (PropertyGroup and + /// PropertiesMap itself, above, agree on this), so this uses the same + /// cppcheck::stricmp comparator PropertiesMap does -- otherwise "MyRoot" + /// set in one configuration's PropertyGroup and "myroot" set in another's + /// would be tracked as two unrelated names instead of one property, and + /// each could wrongly look config-invariant on its own even when the + /// property's real value differs by configuration. The priming walk + /// (see its comment in importVcxproj()) also tracks a property that only + /// SOME configurations set at all -- a value in one configuration and no + /// PropertyGroup for it whatsoever in another is itself a difference, + /// exactly like real MSBuild resolving it to "" wherever nothing sets + /// it -- rather than considering the property only where it happens to + /// be present. + std::set mConfigInvariantProperties; + ImportGraph mImportGraph; + /// True only while importVcxproj()'s discovery bootstrap scan (used when a + /// project has no inline ProjectConfigurations) is walking the document. While + /// set, conditionIsTrue() treats every Condition as satisfied and processChoose() + /// explores every / instead of selecting one: discovery's only + /// goal is to enumerate every ProjectConfiguration reachable through ANY + /// combination of property values, and it cannot correctly predict which single + /// branch a real build would take for values -- Configuration/Platform above + /// all -- that are themselves what is being discovered. See DiscoveringGuard. + bool mDiscovering = false; }; @@ -151,6 +423,7 @@ namespace CppcheckXml { static constexpr char DefineNameAttrib[] = "name"; static constexpr char UndefinesElementName[] = "undefines"; static constexpr char UndefineName[] = "undefine"; + static constexpr char UserIncludeElementName[] = "user-include"; static constexpr char PathsElementName[] = "paths"; static constexpr char PathName[] = "dir"; static constexpr char PathNameAttrib[] = "name"; @@ -197,14 +470,9 @@ namespace CppcheckXml { static constexpr char CodingStandardsElementName[] = "coding-standards"; static constexpr char CodingStandardElementName[] = "coding-standard"; static constexpr char CertIntPrecisionElementName[] = "cert-c-int-precision"; - static constexpr char LicenseFileElementName[] = "license-file"; static constexpr char ProjectNameElementName[] = "project-name"; } -namespace testing -{ - CPPCHECKLIB bool evaluateVcxprojCondition(const std::string& condition, const std::string& configuration, const std::string& platform); -} /// @} //--------------------------------------------------------------------------- #endif // importprojectH diff --git a/lib/library.cpp b/lib/library.cpp index 7520944d8ad..6c80e88e5cc 100644 --- a/lib/library.cpp +++ b/lib/library.cpp @@ -177,7 +177,7 @@ static std::vector getnames(const char *names) static void gettokenlistfromvalid(const std::string& valid, TokenList& tokenList) { - const std::string str(valid + ','); + const std::string str(valid + ",\n"); tokenList.createTokensFromBuffer(str.data(), str.size()); // TODO: check result? for (Token *tok = tokenList.front(); tok; tok = tok->next()) { if (Token::Match(tok,"- %num%")) { @@ -932,6 +932,8 @@ Library::Error Library::loadFunction(const tinyxml2::XMLElement * const node, co } if (const char* const argIndirect = functionnode->Attribute("indirect")) { const int indirect = strToInt(argIndirect); + if (indirect < 0 || indirect >= static_cast(ac.direction.size())) + return Error(ErrorCode::BAD_ATTRIBUTE_VALUE, argIndirect); ac.direction[indirect] = dir; // TODO: handle multiple directions/indirect levels } else @@ -1781,6 +1783,8 @@ bool Library::isFunctionConst(const Token *ftok) const const Yield yield = astContainerYield(ftok->astParent()->astOperand1(), *this); if (yield == Yield::EMPTY || yield == Yield::SIZE || yield == Yield::BUFFER_NT) return true; + if ((yield == Yield::START_ITERATOR || yield == Yield::END_ITERATOR) && ftok->str()[0] == 'c') + return true; } return false; } diff --git a/lib/mathlib.cpp b/lib/mathlib.cpp index 403662f4df2..9aba7118858 100644 --- a/lib/mathlib.cpp +++ b/lib/mathlib.cpp @@ -266,7 +266,7 @@ MathLib::value MathLib::value::shiftLeft(const MathLib::value &v) const if (!isInt() || !v.isInt()) throw InternalError(nullptr, "Shift operand is not integer"); MathLib::value ret(*this); - if (v.mIntValue >= MathLib::bigint_bits) { + if (v.mIntValue < 0 || v.mIntValue >= MathLib::bigint_bits || ret.mIntValue < 0) { return ret; } ret.mIntValue <<= v.mIntValue; @@ -278,7 +278,7 @@ MathLib::value MathLib::value::shiftRight(const MathLib::value &v) const if (!isInt() || !v.isInt()) throw InternalError(nullptr, "Shift operand is not integer"); MathLib::value ret(*this); - if (v.mIntValue >= MathLib::bigint_bits) { + if (v.mIntValue < 0 || v.mIntValue >= MathLib::bigint_bits) { return ret; } ret.mIntValue >>= v.mIntValue; @@ -532,8 +532,15 @@ double MathLib::toDoubleNumber(const std::string &str, const Token * const tok) std::istringstream istr(str); istr.imbue(std::locale::classic()); double ret; - if (!(istr >> ret)) - throw InternalError(tok, "Internal Error. MathLib::toDoubleNumber: conversion failed: " + str); + if (!(istr >> ret)) { + // Out of the range of double, such as 1e10000 (glibc's HUGE_VAL for + // compilers other than GCC): the stream stores the largest finite + // value and fails, a compiler makes it infinity. + if (!isFloat(str) || std::fabs(ret) < std::numeric_limits::max()) + throw InternalError(tok, "Internal Error. MathLib::toDoubleNumber: conversion failed: " + str); + ret = std::copysign(std::numeric_limits::infinity(), ret); + istr.clear(); + } std::string s; if (istr >> s) { if (isDecimalFloat(str)) @@ -584,6 +591,13 @@ bool MathLib::isFloat(const std::string &str) return isDecimalFloat(str) || isFloatHex(str); } +bool MathLib::isLongDoubleOutOfDoubleRange(const std::string &str) +{ + if (str.empty() || (str.back() != 'l' && str.back() != 'L') || !isFloat(str)) + return false; + return std::isinf(toDoubleNumber(str)); +} + bool MathLib::isDecimalFloat(const std::string &str) { if (str.empty()) diff --git a/lib/mathlib.h b/lib/mathlib.h index 9b6b304501c..bfa1a5b877b 100644 --- a/lib/mathlib.h +++ b/lib/mathlib.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -115,6 +115,8 @@ class CPPCHECKLIB MathLib { static bool isInt(const std::string & str); static bool isFloat(const std::string &str); + /** @brief a long double literal out of the range of double, such as 1e400L, which toDoubleNumber() makes infinity */ + static bool isLongDoubleOutOfDoubleRange(const std::string &str); static bool isDecimalFloat(const std::string &str); static bool isNegative(const std::string &str); static bool isPositive(const std::string &str); diff --git a/lib/path.cpp b/lib/path.cpp index caa45898f99..fc059cdea4e 100644 --- a/lib/path.cpp +++ b/lib/path.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -51,6 +51,8 @@ #endif #if defined(__APPLE__) #include +#elif defined(__HAIKU__) +#include #endif @@ -158,6 +160,17 @@ std::string Path::getCurrentExecutablePath(const char* fallback) #elif defined(__APPLE__) uint32_t size = sizeof(buf); success = (_NSGetExecutablePath(buf, &size) == 0); +#elif defined(__HAIKU__) + int32 cookie = 0; + image_info info; + while (get_next_image_info(B_CURRENT_TEAM, &cookie, &info) == B_OK) + { + if (info.type == B_APP_IMAGE) + { + break; + } + } + return std::string(info.name); #else const char* procPath = #ifdef __SVR4 // Solaris diff --git a/lib/path.h b/lib/path.h index 22bfcf667f3..863e74580f8 100644 --- a/lib/path.h +++ b/lib/path.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/lib/pathmatch.h b/lib/pathmatch.h index 33d9a64ec90..72fc89b7faa 100644 --- a/lib/pathmatch.h +++ b/lib/pathmatch.h @@ -274,9 +274,9 @@ class PathMatch::PathIterator { /* Position struct */ struct Pos { /* String pointer */ - const char *p; + const char *p{}; /* Raw characters left */ - std::size_t l; + std::size_t l{}; /* Buffered character */ int c {EOF}; }; diff --git a/lib/platform.cpp b/lib/platform.cpp index 0e202b9f49a..b956b687419 100644 --- a/lib/platform.cpp +++ b/lib/platform.cpp @@ -97,6 +97,62 @@ bool Platform::set(Type t) char_bit = 8; calculateBitMembers(); return true; + case Type::WinARM64: + type = t; + windows = true; + sizeof_bool = 1; + sizeof_short = 2; + sizeof_int = 4; + sizeof_long = 4; + sizeof_long_long = 8; + sizeof_float = 4; + sizeof_double = 8; + sizeof_long_double = 8; + sizeof_wchar_t = 2; + sizeof_size_t = 8; + sizeof_pointer = 8; + defaultSign = 's'; + char_bit = 8; + calculateBitMembers(); + return true; + case Type::WinARM64EC: + // ARM64EC is an x64-ABI on ARM64 hardware (VS 2022+). + // Pointer width and size_t are 64-bit; all other layout matches WinARM64. + type = t; + windows = true; + sizeof_bool = 1; + sizeof_short = 2; + sizeof_int = 4; + sizeof_long = 4; + sizeof_long_long = 8; + sizeof_float = 4; + sizeof_double = 8; + sizeof_long_double = 8; + sizeof_wchar_t = 2; + sizeof_size_t = 8; + sizeof_pointer = 8; + defaultSign = 's'; + char_bit = 8; + calculateBitMembers(); + return true; + case Type::WinARM: + type = t; + windows = true; + sizeof_bool = 1; + sizeof_short = 2; + sizeof_int = 4; + sizeof_long = 4; + sizeof_long_long = 8; + sizeof_float = 4; + sizeof_double = 8; + sizeof_long_double = 8; + sizeof_wchar_t = 2; + sizeof_size_t = 4; + sizeof_pointer = 4; + defaultSign = 's'; + char_bit = 8; + calculateBitMembers(); + return true; case Type::Unix32: type = t; windows = false; @@ -150,6 +206,12 @@ bool Platform::set(const std::string& platformstr, std::string& errstr, const st set(Type::Win32W); else if (platformstr == "win64") set(Type::Win64); + else if (platformstr == "winARM64") + set(Type::WinARM64); + else if (platformstr == "winARM64EC") + set(Type::WinARM64EC); + else if (platformstr == "winARM") + set(Type::WinARM); else if (platformstr == "unix32") set(Type::Unix32); else if (platformstr == "unix64") diff --git a/lib/platform.h b/lib/platform.h index 97e7d296fba..f630df9cbd7 100644 --- a/lib/platform.h +++ b/lib/platform.h @@ -140,6 +140,9 @@ class CPPCHECKLIB Platform { Win32A, Win32W, Win64, + WinARM64, + WinARM64EC, + WinARM, Unix32, Unix64, File @@ -188,6 +191,12 @@ class CPPCHECKLIB Platform { return "win32W"; case Type::Win64: return "win64"; + case Type::WinARM64: + return "winARM64"; + case Type::WinARM64EC: + return "winARM64EC"; + case Type::WinARM: + return "winARM"; case Type::Unix32: return "unix32"; case Type::Unix64: diff --git a/lib/preprocessor.cpp b/lib/preprocessor.cpp index 9528360906e..c14eaf813ad 100644 --- a/lib/preprocessor.cpp +++ b/lib/preprocessor.cpp @@ -336,67 +336,49 @@ static void addInlineSuppressions(const simplecpp::TokenList &tokens, const Sett } } -void Preprocessor::inlineSuppressions(SuppressionList &suppressions) +void Preprocessor::inlineSuppressions(SuppressionList &suppressions) const +{ + inlineSuppressions(mTokens, suppressions); +} + +void Preprocessor::inlineSuppressions(const simplecpp::TokenList &tokens, SuppressionList &suppressions) const { if (!mSettings.inlineSuppressions) return; std::list err; - ::addInlineSuppressions(mTokens, mSettings, suppressions, err); - for (const auto &filedata : mFileCache) { - ::addInlineSuppressions(filedata->tokens, mSettings, suppressions, err); - } + ::addInlineSuppressions(tokens, mSettings, suppressions, err); for (const BadInlineSuppression &bad : err) { invalidSuppression(bad.location, bad.errmsg); } } -std::vector Preprocessor::getRemarkComments() const +void Preprocessor::createDirectives(std::list &directives) const { - std::vector ret; - addRemarkComments(mTokens, ret); - for (const auto &filedata : mFileCache) { - addRemarkComments(filedata->tokens, ret); - } - return ret; + createDirectives(mTokens, directives); } -std::list Preprocessor::createDirectives() const +void Preprocessor::createDirectives(const simplecpp::TokenList &tokens, std::list &directives) { - // directive list.. - std::list directives; - - std::vector list; - list.reserve(1U + mFileCache.size()); - list.push_back(&mTokens); - std::transform(mFileCache.cbegin(), mFileCache.cend(), std::back_inserter(list), - [](const std::unique_ptr &filedata) { - return &filedata->tokens; - }); - - for (const simplecpp::TokenList *tokenList : list) { - for (const simplecpp::Token *tok = tokenList->cfront(); tok; tok = tok->next) { - if ((tok->op != '#') || (tok->previous && tok->previous->location.line == tok->location.line)) - continue; - if (tok->next && tok->next->str() == "endfile") + for (const simplecpp::Token *tok = tokens.cfront(); tok; tok = tok->next) { + if ((tok->op != '#') || (tok->previous && tok->previous->location.line == tok->location.line)) + continue; + if (tok->next && tok->next->str() == "endfile") + continue; + Directive directive(tokens, tok->location, ""); + for (const simplecpp::Token *tok2 = tok; tok2 && tok2->location.line == directive.linenr; tok2 = tok2->next) { + if (tok2->comment) continue; - Directive directive(mTokens, tok->location, ""); - for (const simplecpp::Token *tok2 = tok; tok2 && tok2->location.line == directive.linenr; tok2 = tok2->next) { - if (tok2->comment) - continue; - if (!directive.str.empty() && (tok2->location.col > tok2->previous->location.col + tok2->previous->str().size())) - directive.str += ' '; - if (directive.str == "#" && tok2->str() == "file") - directive.str += "include"; - else - directive.str += tok2->str(); - - directive.strTokens.emplace_back(*tok2); - } - directives.push_back(std::move(directive)); + if (!directive.str.empty() && (tok2->location.col > tok2->previous->location.col + tok2->previous->str().size())) + directive.str += ' '; + if (directive.str == "#" && tok2->str() == "file") + directive.str += "include"; + else + directive.str += tok2->str(); + + directive.strTokens.emplace_back(*tok2); } + directives.push_back(std::move(directive)); } - - return directives; } static std::string readcondition(const simplecpp::Token *iftok, const std::set &defined, const std::set &undefined) @@ -469,6 +451,8 @@ static std::string readcondition(const simplecpp::Token *iftok, const std::setstr() == "==" || cond->str() == "<=" || cond->str() == ">=") { + if (!cond->next) + break; if (cond->next->number) { const simplecpp::Token *dtok = cond->previous; if (sameline(iftok,dtok) && dtok->name && defined.find(dtok->str()) == defined.end() && undefined.find(dtok->str()) == undefined.end()) @@ -477,6 +461,8 @@ static std::string readcondition(const simplecpp::Token *iftok, const std::setop == '<' || cond->op == '>') { + if (!cond->next) + break; if (cond->next->number) { const simplecpp::Token *dtok = cond->previous; if (sameline(iftok,dtok) && dtok->name && defined.find(dtok->str()) == defined.end() && undefined.find(dtok->str()) == undefined.end()) { @@ -512,14 +498,14 @@ static std::string readcondition(const simplecpp::Token *iftok, const std::set &configs, const std::strin return ret; } -static bool isUndefined(const std::string &cfg, const std::set &undefined) +static bool isUndefined(const std::string &cfgStr, const std::set &undefined) { - for (std::string::size_type pos1 = 0U; pos1 < cfg.size();) { - const std::string::size_type pos2 = cfg.find(';',pos1); - const std::string def = (pos2 == std::string::npos) ? cfg.substr(pos1) : cfg.substr(pos1, pos2 - pos1); + for (std::string::size_type pos1 = 0U; pos1 < cfgStr.size();) { + const std::string::size_type pos2 = cfgStr.find(';',pos1); + const std::string def = (pos2 == std::string::npos) ? cfgStr.substr(pos1) : cfgStr.substr(pos1, pos2 - pos1); const std::string::size_type eq = def.find('='); if (eq == std::string::npos && undefined.find(def) != undefined.end()) @@ -765,43 +751,28 @@ static void getConfigs(const simplecpp::TokenList &tokens, std::set ret.insert(std::move(elseError)); } - -std::set Preprocessor::getConfigs() const +void Preprocessor::getConfigs(std::set &defined, std::set &configs) const { - std::set ret = { "" }; if (!mTokens.cfront()) - return ret; - - std::set defined = { "__cplusplus" }; - - // Insert library defines - for (const auto &define : mSettings.library.defines()) { - - const std::string::size_type paren = define.find("("); - const std::string::size_type space = define.find(" "); - std::string::size_type end = space; - - if (paren != std::string::npos && paren < space) - end = paren; - - defined.insert(define.substr(0, end)); - } + return; - ::getConfigs(mTokens, defined, mSettings.userDefines, mSettings.userUndefs, ret); + ::getConfigs(mTokens, defined, mSettings.userDefines, mSettings.userUndefs, configs); +} - for (const auto &filedata : mFileCache) { - if (!mSettings.configurationExcluded(filedata->filename)) - ::getConfigs(filedata->tokens, defined, mSettings.userDefines, mSettings.userUndefs, ret); - } +void Preprocessor::getConfigs(const std::string &filename, const simplecpp::TokenList &tokens, std::set &defined, std::set &configs) const +{ + if (!tokens.cfront()) + return; - return ret; + if (!mSettings.configurationExcluded(filename)) + ::getConfigs(tokens, defined, mSettings.userDefines, mSettings.userUndefs, configs); } -static void splitcfg(const std::string &cfg, std::list &defines, const std::string &defaultValue) +static void splitcfg(const std::string &cfgStr, std::list &defines, const std::string &defaultValue) { - for (std::string::size_type defineStartPos = 0U; defineStartPos < cfg.size();) { - const std::string::size_type defineEndPos = cfg.find(';', defineStartPos); - std::string def = (defineEndPos == std::string::npos) ? cfg.substr(defineStartPos) : cfg.substr(defineStartPos, defineEndPos - defineStartPos); + for (std::string::size_type defineStartPos = 0U; defineStartPos < cfgStr.size();) { + const std::string::size_type defineEndPos = cfgStr.find(';', defineStartPos); + std::string def = (defineEndPos == std::string::npos) ? cfgStr.substr(defineStartPos) : cfgStr.substr(defineStartPos, defineEndPos - defineStartPos); if (!defaultValue.empty() && def.find('=') == std::string::npos) def += '=' + defaultValue; defines.push_back(std::move(def)); @@ -811,14 +782,14 @@ static void splitcfg(const std::string &cfg, std::list &defines, co } } -static simplecpp::DUI createDUI(const Settings &mSettings, const std::string &cfg, Standards::Language lang) +static simplecpp::DUI createDUI(const Settings &mSettings, const std::string &cfgStr, Standards::Language lang) { // TODO: make it possible to specify platform-dependent sizes simplecpp::DUI dui; splitcfg(mSettings.userDefines, dui.defines, "1"); - if (!cfg.empty()) - splitcfg(cfg, dui.defines, ""); + if (!cfgStr.empty()) + splitcfg(cfgStr, dui.defines, ""); for (const std::string &def : mSettings.library.defines()) { const std::string::size_type pos = def.find_first_of(" ("); @@ -862,21 +833,30 @@ const simplecpp::Output* Preprocessor::handleErrors(const simplecpp::OutputList& return reportOutput(outputList, showerror); } -bool Preprocessor::loadFiles(std::vector &files) +bool Preprocessor::loadAllIncludes(std::vector &files) { const simplecpp::DUI dui = createDUI(mSettings, "", mLang); simplecpp::OutputList outputList; - mFileCache = simplecpp::load(mTokens, files, dui, &outputList); + mFileCache = simplecpp::load(mTokens, files, dui, &outputList, std::move(mFileCache)); return !handleErrors(outputList); } +simplecpp::FileData *Preprocessor::loadFile(std::vector &files, const std::string &file) +{ + const simplecpp::DUI dui = createDUI(mSettings, "", mLang); + + return mFileCache.get("", file, dui, false, files, nullptr).first; +} + void Preprocessor::removeComments() { - mTokens.removeComments(); - for (const auto &filedata : mFileCache) { - filedata->tokens.removeComments(); - } + removeComments(mTokens); +} + +void Preprocessor::removeComments(simplecpp::TokenList &tokens) +{ + tokens.removeComments(); } void Preprocessor::setPlatformInfo() @@ -899,9 +879,9 @@ void Preprocessor::setPlatformInfo() mTokens.sizeOfType["long double *"] = mSettings.platform.sizeof_pointer; } -simplecpp::TokenList Preprocessor::preprocess(const std::string &cfg, std::vector &files, simplecpp::OutputList& outputList) +simplecpp::TokenList Preprocessor::preprocess(const std::string &cfgStr, std::vector &files, simplecpp::OutputList& outputList) { - const simplecpp::DUI dui = createDUI(mSettings, cfg, mLang); + const simplecpp::DUI dui = createDUI(mSettings, cfgStr, mLang); std::list macroUsage; std::list ifCond; @@ -910,15 +890,16 @@ simplecpp::TokenList Preprocessor::preprocess(const std::string &cfg, std::vecto mMacroUsage = std::move(macroUsage); mIfCond = std::move(ifCond); - tokens2.removeComments(); + if (!mSettings.keepComments) + tokens2.removeComments(); return tokens2; } -std::string Preprocessor::getcode(const std::string &cfg, std::vector &files, const bool writeLocations) +std::string Preprocessor::getcode(const std::string &cfgStr, std::vector &files, const bool writeLocations) { simplecpp::OutputList outputList; - simplecpp::TokenList tokens2 = preprocess(cfg, files, outputList); + simplecpp::TokenList tokens2 = preprocess(cfgStr, files, outputList); handleErrors(outputList); unsigned int prevfile = 0; unsigned int line = 1; @@ -939,6 +920,13 @@ std::string Preprocessor::getcode(const std::string &cfg, std::vectormacro.empty()) ret << Preprocessor::macroChar; ret << tok->str(); + + if (tok->comment) + line += std::count_if(tok->str().cbegin(), + tok->str().cend(), + [](char c) { + return c == '\n'; + }); } return ret.str(); @@ -957,6 +945,8 @@ const simplecpp::Output* Preprocessor::reportOutput(const simplecpp::OutputList break; case simplecpp::Output::WARNING: case simplecpp::Output::PORTABILITY_BACKSLASH: + case simplecpp::Output::PORTABILITY_LINE_DIRECTIVE: + case simplecpp::Output::PORTABILITY_NO_EOF_NEWLINE: break; case simplecpp::Output::MISSING_HEADER: { // not considered an "error" @@ -966,6 +956,7 @@ const simplecpp::Output* Preprocessor::reportOutput(const simplecpp::OutputList missingInclude(out.location, out.msg.substr(pos1+1, pos2-pos1-1), out.msg[pos1] == '\"' ? UserHeader : SystemHeader); } break; + case simplecpp::Output::DIRECTIVE_AS_MACRO_PARAMETER: case simplecpp::Output::INCLUDE_NESTED_TOO_DEEPLY: case simplecpp::Output::SYNTAX_ERROR: case simplecpp::Output::UNHANDLED_CHAR_ERROR: @@ -991,32 +982,37 @@ static std::string simplecppErrToId(simplecpp::Output::Type type) return "preprocessorErrorDirective"; case simplecpp::Output::SYNTAX_ERROR: return "syntaxError"; + case simplecpp::Output::DIRECTIVE_AS_MACRO_PARAMETER: + return "directiveAsMacroParameter"; case simplecpp::Output::UNHANDLED_CHAR_ERROR: return "unhandledChar"; case simplecpp::Output::INCLUDE_NESTED_TOO_DEEPLY: return "includeNestedTooDeeply"; case simplecpp::Output::FILE_NOT_FOUND: return "missingFile"; - // should never occur case simplecpp::Output::EXPLICIT_INCLUDE_NOT_FOUND: + return "missingIncludeExplicit"; + // should never occur case simplecpp::Output::DUI_ERROR: // handled separately case simplecpp::Output::MISSING_HEADER: // no handled at all (warnings) case simplecpp::Output::WARNING: case simplecpp::Output::PORTABILITY_BACKSLASH: + case simplecpp::Output::PORTABILITY_LINE_DIRECTIVE: + case simplecpp::Output::PORTABILITY_NO_EOF_NEWLINE: throw std::runtime_error("unexpected simplecpp::Output type " + std::to_string(type)); } cppcheck::unreachable(); } -void Preprocessor::error(const simplecpp::Location& loc, const std::string &msg, simplecpp::Output::Type type) +void Preprocessor::error(const simplecpp::Location& loc, const std::string &msg, simplecpp::Output::Type type) const { error(loc, msg, simplecppErrToId(type)); } -void Preprocessor::error(const simplecpp::Location& loc, const std::string &msg, const std::string& id) +void Preprocessor::error(const simplecpp::Location& loc, const std::string &msg, const std::string& id) const { std::list locationList; if (!mTokens.file(loc).empty()) { @@ -1054,7 +1050,7 @@ void Preprocessor::missingInclude(const simplecpp::Location& loc, const std::str mErrorLogger.reportErr(errmsg); } -void Preprocessor::invalidSuppression(const simplecpp::Location& loc, const std::string &msg) +void Preprocessor::invalidSuppression(const simplecpp::Location& loc, const std::string &msg) const { error(loc, msg, "invalidSuppression"); } @@ -1071,9 +1067,11 @@ void Preprocessor::getErrorMessages(ErrorLogger &errorLogger, const Settings &se preprocessor.missingInclude(loc, "", SystemHeader); preprocessor.error(loc, "message", simplecpp::Output::ERROR); preprocessor.error(loc, "message", simplecpp::Output::SYNTAX_ERROR); + preprocessor.error(loc, "message", simplecpp::Output::DIRECTIVE_AS_MACRO_PARAMETER); preprocessor.error(loc, "message", simplecpp::Output::UNHANDLED_CHAR_ERROR); preprocessor.error(loc, "message", simplecpp::Output::INCLUDE_NESTED_TOO_DEEPLY); preprocessor.error(loc, "message", simplecpp::Output::FILE_NOT_FOUND); + preprocessor.error(loc, "message", simplecpp::Output::EXPLICIT_INCLUDE_NOT_FOUND); preprocessor.invalidSuppression(loc, "message"); } @@ -1086,10 +1084,10 @@ void Preprocessor::dump(std::ostream &out) const for (const simplecpp::MacroUsage ¯oUsage: mMacroUsage) { out << " " << std::endl; for (const simplecpp::IfCond &ifCond: mIfCond) { out << " next) { - if (!tok->comment) { - hashData += tok->str(); - hashData += static_cast(tok->location.line); - hashData += static_cast(tok->location.col); - } - } - for (const auto &filedata : mFileCache) { - for (const simplecpp::Token *tok = filedata->tokens.cfront(); tok; tok = tok->next) { + const auto addTokens = [&hashData](const simplecpp::TokenList &tokens) { + for (const simplecpp::Token *tok = tokens.cfront(); tok; tok = tok->next) { if (!tok->comment) { hashData += tok->str(); hashData += static_cast(tok->location.line); hashData += static_cast(tok->location.col); } } - } + }; + addTokens(mTokens); + + // Hash the included files in a fixed order, the order they are loaded in differs between runs + std::vector fileData; + fileData.reserve(mFileCache.size()); + std::transform(mFileCache.cbegin(), mFileCache.cend(), std::back_inserter(fileData), [](const std::unique_ptr &data) { + return data.get(); + }); + std::sort(fileData.begin(), fileData.end(), [](const simplecpp::FileData *a, const simplecpp::FileData *b) { + return a->filename < b->filename; + }); + for (const simplecpp::FileData *data : fileData) + addTokens(data->tokens); + return (std::hash{})(hashData); } void Preprocessor::simplifyPragmaAsm() { - Preprocessor::simplifyPragmaAsmPrivate(mTokens); - for (const auto &filedata : mFileCache) { - Preprocessor::simplifyPragmaAsmPrivate(filedata->tokens); - } + simplifyPragmaAsm(mTokens); } -void Preprocessor::simplifyPragmaAsmPrivate(simplecpp::TokenList &tokenList) +void Preprocessor::simplifyPragmaAsm(simplecpp::TokenList &tokenList) { // assembler code.. for (simplecpp::Token *tok = tokenList.front(); tok; tok = tok->next) { @@ -1190,9 +1192,15 @@ void Preprocessor::simplifyPragmaAsmPrivate(simplecpp::TokenList &tokenList) } } +std::vector Preprocessor::getRemarkComments() const +{ + return getRemarkComments(mTokens); +} -void Preprocessor::addRemarkComments(const simplecpp::TokenList &tokens, std::vector &remarkComments) const +std::vector Preprocessor::getRemarkComments(const simplecpp::TokenList &tokens) const { + std::vector remarkComments; + for (const simplecpp::Token *tok = tokens.cfront(); tok; tok = tok->next) { if (!tok->comment) continue; @@ -1239,4 +1247,6 @@ void Preprocessor::addRemarkComments(const simplecpp::TokenList &tokens, std::ve // Add the suppressions. remarkComments.emplace_back(relativeFilename, remarkedToken->location.line, remarkText); } + + return remarkComments; } diff --git a/lib/preprocessor.h b/lib/preprocessor.h index a211341691f..5df52934dfc 100644 --- a/lib/preprocessor.h +++ b/lib/preprocessor.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -98,29 +98,45 @@ class CPPCHECKLIB RemarkComment { * configurations that exist in a source file. */ class CPPCHECKLIB WARN_UNUSED Preprocessor { + friend class TestPreprocessor; + public: /** character that is inserted in expanded macros */ static char macroChar; Preprocessor(simplecpp::TokenList& tokens, const Settings& settings, ErrorLogger &errorLogger, Standards::Language lang); + Preprocessor(const Preprocessor &) = delete; + Preprocessor &operator=(const Preprocessor &) = delete; + + void inlineSuppressions(SuppressionList &suppressions) const; + + void inlineSuppressions(const simplecpp::TokenList &tokens, SuppressionList &suppressions) const; - void inlineSuppressions(SuppressionList &suppressions); + void createDirectives(std::list &directives) const; - std::list createDirectives() const; + static void createDirectives(const simplecpp::TokenList &tokens, std::list &directives); - std::set getConfigs() const; + void getConfigs(std::set &defined, std::set &configs) const; + + void getConfigs(const std::string &filename, const simplecpp::TokenList &tokens, std::set &defined, std::set &configs) const; std::vector getRemarkComments() const; - bool loadFiles(std::vector &files); + std::vector getRemarkComments(const simplecpp::TokenList &tokens) const; + + bool loadAllIncludes(std::vector &files); + + simplecpp::FileData *loadFile(std::vector &files, const std::string &file); void removeComments(); + static void removeComments(simplecpp::TokenList &tokens); + void setPlatformInfo(); - simplecpp::TokenList preprocess(const std::string &cfg, std::vector &files, simplecpp::OutputList& outputList); + simplecpp::TokenList preprocess(const std::string &cfgStr, std::vector &files, simplecpp::OutputList& outputList); - std::string getcode(const std::string &cfg, std::vector &files, bool writeLocations); + std::string getcode(const std::string &cfgStr, std::vector &files, bool writeLocations); /** * Calculate HASH. Using toolinfo, tokens1, filedata. @@ -132,6 +148,8 @@ class CPPCHECKLIB WARN_UNUSED Preprocessor { void simplifyPragmaAsm(); + static void simplifyPragmaAsm(simplecpp::TokenList &tokenList); + static void getErrorMessages(ErrorLogger &errorLogger, const Settings &settings); /** @@ -141,12 +159,15 @@ class CPPCHECKLIB WARN_UNUSED Preprocessor { const simplecpp::Output* reportOutput(const simplecpp::OutputList &outputList, bool showerror); - void error(const simplecpp::Location& loc, const std::string &msg, simplecpp::Output::Type type); + void error(const simplecpp::Location& loc, const std::string &msg, simplecpp::Output::Type type) const; const simplecpp::Output* handleErrors(const simplecpp::OutputList &outputList); + void setLoadCallback(simplecpp::FileDataCache::load_callback_type cb) { + mFileCache.set_load_callback(std::move(cb)); + } + private: - static void simplifyPragmaAsmPrivate(simplecpp::TokenList &tokenList); /** * Include file types. @@ -157,17 +178,13 @@ class CPPCHECKLIB WARN_UNUSED Preprocessor { }; void missingInclude(const simplecpp::Location& loc, const std::string &header, HeaderTypes headerType); - void invalidSuppression(const simplecpp::Location& loc, const std::string &msg); - void error(const simplecpp::Location& loc, const std::string &msg, const std::string& id); - - void addRemarkComments(const simplecpp::TokenList &tokens, std::vector &remarkComments) const; + void invalidSuppression(const simplecpp::Location& loc, const std::string &msg) const; + void error(const simplecpp::Location& loc, const std::string &msg, const std::string& id) const; simplecpp::TokenList& mTokens; const Settings& mSettings; - ErrorLogger &mErrorLogger; - - /** list of all directives met while preprocessing file */ + ErrorLogger& mErrorLogger; simplecpp::FileDataCache mFileCache; diff --git a/lib/programmemory.cpp b/lib/programmemory.cpp index eb0b290ac6c..0d84dc053a8 100644 --- a/lib/programmemory.cpp +++ b/lib/programmemory.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,6 +23,7 @@ #include "infer.h" #include "library.h" #include "mathlib.h" +#include "refthunk.h" #include "settings.h" #include "standards.h" #include "symboldatabase.h" @@ -150,11 +151,11 @@ bool ProgramMemory::getContainerEmptyValue(nonneg int exprid, MathLib::bigint& r return false; } -void ProgramMemory::setContainerSizeValue(const Token* expr, MathLib::bigint value, bool isEqual) +void ProgramMemory::setContainerSizeValue(const Token* expr, MathLib::bigint value, bool equal) { ValueFlow::Value v(value); v.valueType = ValueFlow::Value::ValueType::CONTAINER_SIZE; - if (!isEqual) + if (!equal) v.valueKind = ValueFlow::Value::ValueKind::Impossible; setValue(expr, v); } @@ -262,26 +263,33 @@ ProgramMemory::Map::iterator ProgramMemory::find(nonneg int exprid) return mValues->find(ExprIdToken::create(exprid)); } -static ValueFlow::Value execute(const Token* expr, ProgramMemory& pm, const Settings& settings); +static ValueFlow::Value execute(const Token* expr, + ProgramMemory& pm, + const Settings& settings, + const ProgramMemory::Map& vars = {}); -static bool evaluateCondition(MathLib::bigint r, const Token* condition, ProgramMemory& pm, const Settings& settings) +static bool evaluateCondition(MathLib::bigint r, + const Token* condition, + ProgramMemory& pm, + const Settings& settings, + const ProgramMemory::Map& vars = {}) { if (!condition) return false; MathLib::bigint result = 0; bool error = false; - execute(condition, pm, &result, &error, settings); + execute(condition, pm, &result, &error, settings, vars); return !error && result == r; } -bool conditionIsFalse(const Token* condition, ProgramMemory pm, const Settings& settings) +bool conditionIsFalse(const Token* condition, ProgramMemory pm, const Settings& settings, const ProgramMemory::Map& vars) { - return evaluateCondition(0, condition, pm, settings); + return evaluateCondition(0, condition, pm, settings, vars); } -bool conditionIsTrue(const Token* condition, ProgramMemory pm, const Settings& settings) +bool conditionIsTrue(const Token* condition, ProgramMemory pm, const Settings& settings, const ProgramMemory::Map& vars) { - return evaluateCondition(1, condition, pm, settings); + return evaluateCondition(1, condition, pm, settings, vars); } static bool frontIs(const std::vector& v, bool i) @@ -337,7 +345,13 @@ static bool isBasicForLoop(const Token* tok) return true; } -static void programMemoryParseCondition(ProgramMemory& pm, const Token* tok, const Token* endTok, const Settings& settings, bool then) +// findChanged: optional cached findExpressionChanged (see ProgramMemoryState::FindChangedFn). +static void programMemoryParseCondition(ProgramMemory& pm, + const Token* tok, + const Token* endTok, + const Settings& settings, + bool then, + const ProgramMemoryState::FindChangedFn& findChanged = {}) { auto eval = [&](const Token* t) -> std::vector { if (!t) @@ -351,6 +365,10 @@ static void programMemoryParseCondition(ProgramMemory& pm, const Token* tok, con return {result}; return std::vector{}; }; + // Use the cached closure if given, else compute directly. + auto changed = [&](const Token* e, const Token* s, const Token* en) -> const Token* { + return findChanged ? findChanged(e, s, en) : findExpressionChanged(e, s, en, settings); + }; if (Token::Match(tok, "==|>=|<=|<|>|!=")) { ValueFlow::Value truevalue; ValueFlow::Value falsevalue; @@ -361,7 +379,7 @@ static void programMemoryParseCondition(ProgramMemory& pm, const Token* tok, con return; if (!truevalue.isIntValue()) return; - if (endTok && findExpressionChanged(vartok, tok->next(), endTok, settings)) + if (endTok && changed(vartok, tok->next(), endTok)) return; const bool impossible = (tok->str() == "==" && !then) || (tok->str() == "!=" && then); const ValueFlow::Value& v = then ? truevalue : falsevalue; @@ -370,26 +388,26 @@ static void programMemoryParseCondition(ProgramMemory& pm, const Token* tok, con if (containerTok) pm.setContainerSizeValue(containerTok, v.intvalue, !impossible); } else if (Token::simpleMatch(tok, "!")) { - programMemoryParseCondition(pm, tok->astOperand1(), endTok, settings, !then); + programMemoryParseCondition(pm, tok->astOperand1(), endTok, settings, !then, findChanged); } else if (then && Token::simpleMatch(tok, "&&")) { - programMemoryParseCondition(pm, tok->astOperand1(), endTok, settings, then); - programMemoryParseCondition(pm, tok->astOperand2(), endTok, settings, then); + programMemoryParseCondition(pm, tok->astOperand1(), endTok, settings, then, findChanged); + programMemoryParseCondition(pm, tok->astOperand2(), endTok, settings, then, findChanged); } else if (!then && Token::simpleMatch(tok, "||")) { - programMemoryParseCondition(pm, tok->astOperand1(), endTok, settings, then); - programMemoryParseCondition(pm, tok->astOperand2(), endTok, settings, then); + programMemoryParseCondition(pm, tok->astOperand1(), endTok, settings, then, findChanged); + programMemoryParseCondition(pm, tok->astOperand2(), endTok, settings, then, findChanged); } else if (Token::Match(tok, "&&|%oror%")) { std::vector lhs = eval(tok->astOperand1()); std::vector rhs = eval(tok->astOperand2()); if (lhs.empty() || rhs.empty()) { if (frontIs(lhs, !then)) - programMemoryParseCondition(pm, tok->astOperand2(), endTok, settings, then); + programMemoryParseCondition(pm, tok->astOperand2(), endTok, settings, then, findChanged); else if (frontIs(rhs, !then)) - programMemoryParseCondition(pm, tok->astOperand1(), endTok, settings, then); + programMemoryParseCondition(pm, tok->astOperand1(), endTok, settings, then, findChanged); else pm.setIntValue(tok, 0, then); } } else if (tok && tok->exprId() > 0) { - if (endTok && findExpressionChanged(tok, tok->next(), endTok, settings)) + if (endTok && changed(tok, tok->next(), endTok)) return; pm.setIntValue(tok, 0, then); const Token* containerTok = settings.library.getContainerFromYield(tok, Library::Container::Yield::EMPTY); @@ -398,14 +416,18 @@ static void programMemoryParseCondition(ProgramMemory& pm, const Token* tok, con } } -static void fillProgramMemoryFromConditions(ProgramMemory& pm, const Scope* scope, const Token* endTok, const Settings& settings) +static void fillProgramMemoryFromConditions(ProgramMemory& pm, + const Scope* scope, + const Token* endTok, + const Settings& settings, + const ProgramMemoryState::FindChangedFn& findChanged) { if (!scope) return; if (!scope->isLocal()) return; assert(scope != scope->nestedIn); - fillProgramMemoryFromConditions(pm, scope->nestedIn, endTok, settings); + fillProgramMemoryFromConditions(pm, scope->nestedIn, endTok, settings, findChanged); if (scope->type == ScopeType::eIf || scope->type == ScopeType::eWhile || scope->type == ScopeType::eElse || scope->type == ScopeType::eFor) { const Token* condTok = getCondTokFromEnd(scope->bodyEnd); if (!condTok) @@ -414,13 +436,16 @@ static void fillProgramMemoryFromConditions(ProgramMemory& pm, const Scope* scop bool error = false; execute(condTok, pm, &result, &error, settings); if (error) - programMemoryParseCondition(pm, condTok, endTok, settings, scope->type != ScopeType::eElse); + programMemoryParseCondition(pm, condTok, endTok, settings, scope->type != ScopeType::eElse, findChanged); } } -static void fillProgramMemoryFromConditions(ProgramMemory& pm, const Token* tok, const Settings& settings) +static void fillProgramMemoryFromConditions(ProgramMemory& pm, + const Token* tok, + const Settings& settings, + const ProgramMemoryState::FindChangedFn& findChanged = {}) { - fillProgramMemoryFromConditions(pm, tok->scope(), tok, settings); + fillProgramMemoryFromConditions(pm, tok->scope(), tok, settings, findChanged); } static void fillProgramMemoryFromAssignments(ProgramMemory& pm, const Token* tok, const Settings& settings, const ProgramMemory& state, const ProgramMemory::Map& vars) @@ -429,22 +454,12 @@ static void fillProgramMemoryFromAssignments(ProgramMemory& pm, const Token* tok for (const Token *tok2 = tok; tok2; tok2 = tok2->previous()) { if ((Token::simpleMatch(tok2, "=") || Token::Match(tok2->previous(), "%var% (|{")) && tok2->astOperand1() && tok2->astOperand2()) { - bool setvar = false; const Token* vartok = tok2->astOperand1(); - for (const auto& p:vars) { - if (p.first.getExpressionId() != vartok->exprId()) - continue; - if (vartok == tok) - continue; - pm.setValue(vartok, p.second); - setvar = true; - } - if (!setvar) { - if (!pm.hasValue(vartok->exprId())) { - const Token* valuetok = tok2->astOperand2(); - ProgramMemory local = state; - pm.setValue(vartok, execute(valuetok, local, settings)); - } + if (!pm.hasValue(vartok->exprId())) { + const Token* valuetok = tok2->astOperand2(); + ProgramMemory local = state; + // Tracked values are substituted by execute() when the expression is evaluated. + pm.setValue(vartok, execute(valuetok, local, settings, vars)); } } else if (Token::simpleMatch(tok2, ")") && tok2->link() && Token::Match(tok2->link()->previous(), "assert|ASSERT ( !!)")) { @@ -516,7 +531,7 @@ static ProgramMemory getInitialProgramState(const Token* tok, return pm; } -ProgramMemoryState::ProgramMemoryState(const Settings& s) : settings(s) +ProgramMemoryState::ProgramMemoryState(const Settings& s) : settings(s), changedCache(std::make_shared()) {} void ProgramMemoryState::replace(ProgramMemory pm, const Token* origin) @@ -539,7 +554,7 @@ void ProgramMemoryState::addState(const Token* tok, const ProgramMemory::Map& va { ProgramMemory local = state; addVars(local, vars); - fillProgramMemoryFromConditions(local, tok, settings); + fillProgramMemoryFromConditions(local, tok, settings, getCachedFindExpressionChanged(/*skipDeadCode*/ false)); ProgramMemory pm; fillProgramMemoryFromAssignments(pm, tok, settings, local, vars); local.replace(std::move(pm)); @@ -547,44 +562,73 @@ void ProgramMemoryState::addState(const Token* tok, const ProgramMemory::Map& va replace(std::move(local), tok); } -void ProgramMemoryState::assume(const Token* tok, bool b, bool isEmpty) +void ProgramMemoryState::assume(const Token* tok, bool b, bool isEmpty, const Token* origin) { ProgramMemory pm = state; if (isEmpty) pm.setContainerSizeValue(tok, 0, b); else programMemoryParseCondition(pm, tok, nullptr, settings, b); - const Token* origin = tok; - const Token* top = tok->astTop(); - if (Token::Match(top->previous(), "for|while|if (") && !Token::simpleMatch(tok->astParent(), "?")) { - origin = top->link()->next(); - if (!b && origin->link()) { - origin = origin->link(); + if (!origin) { + origin = tok; + const Token* top = tok->astTop(); + if (Token::Match(top->previous(), "for|while|if (") && !Token::simpleMatch(tok->astParent(), "?")) { + origin = top->link()->next(); + if (!b && origin->link()) { + origin = origin->link(); + } } } replace(std::move(pm), origin); } -void ProgramMemoryState::removeModifiedVars(const Token* tok) +ProgramMemoryState::FindChangedFn ProgramMemoryState::getCachedFindExpressionChanged(bool skipDeadCode) const { - const ProgramMemory& pm = state; - auto eval = [&](const Token* cond) -> std::vector { - ProgramMemory pm2 = pm; - auto result = execute(cond, pm2, settings); - if (isTrue(result)) - return {1}; - if (isFalse(result)) - return {0}; - return {}; + // Structural findExpressionChanged() is pure, so memoize it in changedCache (never invalidated). + // skipDeadCode adds the dead-code walk; it evaluates guards against a fixed state snapshot (so every + // variable follows the same path) and memoizes those evals in evalCache for the closure's lifetime. + using EvalCache = std::map>; + const std::shared_ptr cache = changedCache; + const Settings* const sp = &settings(); + ProgramMemory snapshot = state; + const std::shared_ptr evalCache = skipDeadCode ? std::make_shared() : nullptr; + return [cache, sp, snapshot, skipDeadCode, evalCache](const Token* expr, + const Token* start, + const Token* end) -> const Token* { + const auto key = std::make_tuple(expr, start, end); + const auto it = cache->find(key); + const Token* modified = (it != cache->end()) + ? it->second + : cache->emplace(key, findExpressionChanged(expr, start, end, *sp)).first->second; + if (!skipDeadCode || !modified) + return modified; + auto eval = [&](const Token* cond) -> std::vector { + const auto cit = evalCache->find(cond); + if (cit != evalCache->end()) + return cit->second; + ProgramMemory pm2 = snapshot; + const auto result = execute(cond, pm2, *sp); + std::vector r; + if (isTrue(result)) + r = {1}; + else if (isFalse(result)) + r = {0}; + return evalCache->emplace(cond, std::move(r)).first->second; + }; + return findExpressionChangedSkipDeadCode(expr, start, end, *sp, eval); }; +} + +void ProgramMemoryState::removeModifiedVars(const Token* tok) +{ + const auto findChanged = getCachedFindExpressionChanged(/*skipDeadCode*/ true); state.erase_if([&](const ExprIdToken& e) { const Token* start = origins[e.getExpressionId()]; const Token* expr = e.tok; - if (!expr || findExpressionChangedSkipDeadCode(expr, start, tok, settings, eval)) { + const bool changed = !expr || findChanged(expr, start, tok); + if (changed) origins.erase(e.getExpressionId()); - return true; - } - return false; + return changed; }); } @@ -632,10 +676,6 @@ static MathLib::bigint asInt(const ValueFlow::Value& value) return value.isFloatValue() ? static_cast(value.floatValue) : value.intvalue; } -static std::string removeAssign(const std::string& assign) { - return std::string{assign.cbegin(), assign.cend() - 1}; -} - namespace { struct assign { template @@ -651,22 +691,23 @@ static bool isIntegralValue(const ValueFlow::Value& value) return value.isIntValue() || value.isIteratorValue() || value.isSymbolicValue(); } -static ValueFlow::Value evaluate(const std::string& op, const ValueFlow::Value& lhs, const ValueFlow::Value& rhs) +static ValueFlow::Value evaluate(const Token* op, const ValueFlow::Value& lhs, const ValueFlow::Value& rhs, bool removeAssign = false) { + const std::string opStr = removeAssign ? op->str().substr(0, op->str().size() - 1) : op->str(); ValueFlow::Value result; if (lhs.isImpossible() && rhs.isImpossible()) return ValueFlow::Value::unknown(); if (lhs.isImpossible() || rhs.isImpossible()) { // noninvertible - if (contains({"%", "/", "&", "|"}, op)) + if (contains({"%", "/", "&", "|"}, opStr)) return ValueFlow::Value::unknown(); result.setImpossible(); } if (isNumericValue(lhs) && isNumericValue(rhs)) { if (lhs.isFloatValue() || rhs.isFloatValue()) { - result.valueType = ValueFlow::Value::ValueType::FLOAT; + result.valueType = op->isArithmeticalOp() ? ValueFlow::Value::ValueType::FLOAT : ValueFlow::Value::ValueType::INT; bool error = false; - result.floatValue = calculate(op, asFloat(lhs), asFloat(rhs), &error); + result.floatValue = calculate(opStr, asFloat(lhs), asFloat(rhs), &error); if (error) return ValueFlow::Value::unknown(); return result; @@ -678,12 +719,12 @@ static ValueFlow::Value evaluate(const std::string& op, const ValueFlow::Value& // If not the same type then one must be int if (lhs.valueType != rhs.valueType && !lhs.isIntValue() && !rhs.isIntValue()) return ValueFlow::Value::unknown(); - const bool compareOp = contains({"==", "!=", "<", ">", ">=", "<="}, op); + const bool compareOp = op->isComparisonOp(); // Comparison must be the same type if (compareOp && lhs.valueType != rhs.valueType) return ValueFlow::Value::unknown(); // Only add, subtract, and compare for non-integers - if (!compareOp && !contains({"+", "-"}, op) && !lhs.isIntValue() && !rhs.isIntValue()) + if (!compareOp && !contains({"+", "-"}, opStr) && !lhs.isIntValue() && !rhs.isIntValue()) return ValueFlow::Value::unknown(); // Both can't be iterators for non-compare if (!compareOp && lhs.isIteratorValue() && rhs.isIteratorValue()) @@ -701,10 +742,10 @@ static ValueFlow::Value evaluate(const std::string& op, const ValueFlow::Value& result.valueType = ValueFlow::Value::ValueType::INT; } bool error = false; - result.intvalue = calculate(op, lhs.intvalue, rhs.intvalue, &error); + result.intvalue = calculate(opStr, lhs.intvalue, rhs.intvalue, &error); if (error) return ValueFlow::Value::unknown(); - if (result.isImpossible() && op == "!=") { + if (result.isImpossible() && opStr == "!=") { if (isTrue(result)) { result.intvalue = 1; } else if (isFalse(result)) { @@ -1318,7 +1359,10 @@ static void pruneConditions(std::vector& conds, namespace { struct Executor { ProgramMemory* pm; - const Settings& settings; + RefThunk settings; + // Values tracked by the forward/reverse analysis. A tracked value is the authoritative + // current value of its expression and takes precedence over the program memory. + const ProgramMemory::Map* vars = nullptr; int fdepth = 4; int depth = 10; @@ -1327,6 +1371,26 @@ namespace { assert(pm != nullptr); } + // Is the tracked value for this expression available? + const ValueFlow::Value* getTrackedValue(const Token* expr) const + { + if (!vars || expr->exprId() == 0) + return nullptr; + const auto it = vars->find(ExprIdToken::create(expr->exprId())); + return it == vars->end() ? nullptr : &it->second; + } + + // Does the expression read a tracked value? If so, any value cached for it may be stale + // (the tracked value may have changed since), so it must be re-evaluated, not served cached. + bool dependsOnTrackedValue(const Token* expr) const + { + if (!vars || vars->empty()) + return false; + return findAstNode(expr, [&](const Token* tok) { + return getTrackedValue(tok) != nullptr; + }) != nullptr; + } + static ValueFlow::Value unknown() { return ValueFlow::Value::unknown(); } @@ -1449,6 +1513,31 @@ namespace { return unknown(); } + // Get the size of the container. If the container itself is not tracked in the program + // memory then check if it is symbolically equal to a container whose size is tracked. + ValueFlow::Value executeContainerSize(const Token* containerTok) + { + ValueFlow::Value v = execute(containerTok); + if (v.isContainerSizeValue()) + return v; + for (const ValueFlow::Value& value : containerTok->values()) { + if (!value.isSymbolicValue()) + continue; + if (value.isImpossible()) + continue; + if (value.intvalue != 0) + continue; + if (!value.tokvalue) + continue; + if (value.tokvalue->exprId() == 0) + continue; + const ValueFlow::Value* sizeValue = pm->getValue(value.tokvalue->exprId()); + if (sizeValue && sizeValue->isContainerSizeValue()) + return *sizeValue; + } + return unknown(); + } + ValueFlow::Value executeImpl(const Token* expr) { const ValueFlow::Value* value = nullptr; @@ -1477,14 +1566,14 @@ namespace { const Token* containerTok = expr->tokAt(-2)->astOperand1(); const Library::Container::Yield yield = containerTok->valueType()->container->getYield(expr->strAt(-1)); if (yield == Library::Container::Yield::SIZE) { - ValueFlow::Value v = execute(containerTok); + ValueFlow::Value v = executeContainerSize(containerTok); if (!v.isContainerSizeValue()) return unknown(); v.valueType = ValueFlow::Value::ValueType::INT; return v; } if (yield == Library::Container::Yield::EMPTY) { - ValueFlow::Value v = execute(containerTok); + ValueFlow::Value v = executeContainerSize(containerTok); if (!v.isContainerSizeValue()) return unknown(); if (v.isImpossible() && v.intvalue == 0) @@ -1501,7 +1590,7 @@ namespace { if (!pm->hasValue(expr->astOperand1()->exprId())) return unknown(); ValueFlow::Value& lhs = pm->at(expr->astOperand1()->exprId()); - rhs = evaluate(removeAssign(expr->str()), lhs, rhs); + rhs = evaluate(expr, lhs, rhs, /*removeAssign*/ true); if (lhs.isIntValue()) ValueFlow::Value::visitValue(rhs, std::bind(assign{}, std::ref(lhs.intvalue), std::placeholders::_1)); else if (lhs.isFloatValue()) @@ -1555,7 +1644,7 @@ namespace { return unknown(); const MathLib::bigint index = rhs.intvalue; if (index >= 0 && index < strValue.size()) - return ValueFlow::Value{strValue[static_cast(index)]}; + return ValueFlow::Value{strValue[index]}; if (index == strValue.size()) return ValueFlow::Value{}; } else if (Token::Match(expr, "%cop%") && expr->astOperand1() && expr->astOperand2()) { @@ -1565,7 +1654,7 @@ namespace { ValueFlow::Value rhs = execute(expr->astOperand2()); if (rhs.isUninitValue()) return unknown(); - ValueFlow::Value r = evaluate(expr->str(), lhs, rhs); + ValueFlow::Value r = evaluate(expr, lhs, rhs); if (expr->isComparisonOp() && (r.isUninitValue() || r.isImpossible())) { if (rhs.isIntValue() && !expr->astOperand1()->values().empty()) { std::vector result = infer(makeIntegralInferModel(), @@ -1625,7 +1714,15 @@ namespace { } return execute(expr->astOperand1()); } - if (expr->exprId() > 0 && pm->hasValue(expr->exprId())) { + // Return the tracked value and write it back when it differs, so later reads see the + // same value (as fillProgramMemoryFromAssignments used to do). + if (const ValueFlow::Value* tracked = getTrackedValue(expr)) { + const ValueFlow::Value* stored = pm->getValue(expr->exprId(), /*impossible*/ true); + if (!stored || *stored != *tracked) + pm->setValue(expr, *tracked); + return *tracked; + } + if (expr->exprId() > 0 && pm->hasValue(expr->exprId()) && !dependsOnTrackedValue(expr)) { ValueFlow::Value result = utils::as_const(*pm).at(expr->exprId()); if (result.isImpossible() && result.isIntValue() && result.intvalue == 0 && isUsedAsBool(expr, settings)) { result.intvalue = !result.intvalue; @@ -1666,7 +1763,7 @@ namespace { BuiltinLibraryFunction lf = getBuiltinLibraryFunction(ftok->str()); if (lf) return lf(args); - const std::string& returnValue = settings.library.returnValue(ftok); + const std::string& returnValue = settings().library.returnValue(ftok); if (!returnValue.empty()) { std::unordered_map arg_map; int argn = 0; @@ -1818,9 +1915,13 @@ namespace { }; } // namespace -static ValueFlow::Value execute(const Token* expr, ProgramMemory& pm, const Settings& settings) +static ValueFlow::Value execute(const Token* expr, + ProgramMemory& pm, + const Settings& settings, + const ProgramMemory::Map& vars) { Executor ex{&pm, settings}; + ex.vars = &vars; return ex.execute(expr); } @@ -1837,7 +1938,7 @@ static std::shared_ptr createTokenFromExpression(const std::string& retur { std::shared_ptr tokenList = std::make_shared(settings, cpp ? Standards::Language::CPP : Standards::Language::C); { - const std::string code = "return " + returnValue + ";"; + const std::string code = "return " + returnValue + ";\n"; if (!tokenList->createTokensFromBuffer(code.data(), code.size())) return nullptr; } @@ -1910,9 +2011,10 @@ void execute(const Token* expr, ProgramMemory& programMemory, MathLib::bigint* result, bool* error, - const Settings& settings) + const Settings& settings, + const ProgramMemory::Map& vars) { - ValueFlow::Value v = execute(expr, programMemory, settings); + ValueFlow::Value v = execute(expr, programMemory, settings, vars); if (!v.isIntValue() || v.isImpossible()) { if (error) *error = true; diff --git a/lib/programmemory.h b/lib/programmemory.h index bb68a9404d4..f3133a53c31 100644 --- a/lib/programmemory.h +++ b/lib/programmemory.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,6 +21,7 @@ #include "config.h" #include "mathlib.h" +#include "refthunk.h" #include "vfvalue.h" // needed for alias #include @@ -28,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -102,7 +104,7 @@ struct ExprIdToken { }; struct CPPCHECKLIB ProgramMemory { - using Map = std::unordered_map; + using Map = std::map; ProgramMemory() : mValues(new Map()) {} @@ -116,7 +118,7 @@ struct CPPCHECKLIB ProgramMemory { bool getContainerSizeValue(nonneg int exprid, MathLib::bigint& result) const; bool getContainerEmptyValue(nonneg int exprid, MathLib::bigint& result) const; - void setContainerSizeValue(const Token* expr, MathLib::bigint value, bool isEqual = true); + void setContainerSizeValue(const Token* expr, MathLib::bigint value, bool equal = true); void setUnknown(const Token* expr); @@ -161,9 +163,26 @@ struct CPPCHECKLIB ProgramMemory { }; struct ProgramMemoryState { + struct ChangedKeyHash { + std::size_t operator()(const std::tuple& t) const + { + const std::hash h; + std::size_t seed = h(std::get<0>(t)); + seed ^= h(std::get<1>(t)) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + seed ^= h(std::get<2>(t)) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + return seed; + } + }; + using ChangedCache = + std::unordered_map, const Token*, ChangedKeyHash>; + // The token modifying expr between start and end, or nullptr. + using FindChangedFn = std::function; + ProgramMemory state; std::map origins; - const Settings& settings; + RefThunk settings; + // Memoized findExpressionChanged() pre-filter; structural, so never invalidated. + std::shared_ptr changedCache; explicit ProgramMemoryState(const Settings& s); @@ -171,10 +190,13 @@ struct ProgramMemoryState { void addState(const Token* tok, const ProgramMemory::Map& vars); - void assume(const Token* tok, bool b, bool isEmpty = false); + void assume(const Token* tok, bool b, bool isEmpty = false, const Token* origin = nullptr); void removeModifiedVars(const Token* tok); + // A findExpressionChanged() closure memoized in changedCache + FindChangedFn getCachedFindExpressionChanged(bool skipDeadCode) const; + ProgramMemory get(const Token* tok, const Token* ctx, const ProgramMemory::Map& vars) const; }; @@ -184,21 +206,30 @@ void execute(const Token* expr, ProgramMemory& programMemory, MathLib::bigint* result, bool* error, - const Settings& settings); + const Settings& settings, + const ProgramMemory::Map& vars = {}); /** * Is condition always false when variable has given value? * \param condition top ast token in condition * \param pm program memory + * \param vars optional tracked values that take precedence over the program memory */ -bool conditionIsFalse(const Token* condition, ProgramMemory pm, const Settings& settings); +bool conditionIsFalse(const Token* condition, + ProgramMemory pm, + const Settings& settings, + const ProgramMemory::Map& vars = {}); /** * Is condition always true when variable has given value? * \param condition top ast token in condition * \param pm program memory + * \param vars optional tracked values that take precedence over the program memory */ -bool conditionIsTrue(const Token* condition, ProgramMemory pm, const Settings& settings); +bool conditionIsTrue(const Token* condition, + ProgramMemory pm, + const Settings& settings, + const ProgramMemory::Map& vars = {}); /** * Get program memory by looking backwards from given token. diff --git a/lib/refthunk.h b/lib/refthunk.h new file mode 100644 index 00000000000..5b2e4d0d5c0 --- /dev/null +++ b/lib/refthunk.h @@ -0,0 +1,61 @@ +/* -*- C++ -*- + * Cppcheck - A tool for static C/C++ code analysis + * Copyright (C) 2007-2026 Cppcheck team. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +//--------------------------------------------------------------------------- +#ifndef refthunkH +#define refthunkH +//--------------------------------------------------------------------------- + +#include + +/** + * Reference wrapper whose operator() returns the referenced object. + * + * It is constructed from a reference and converts implicitly back to one + * (like std::reference_wrapper), so it can replace a reference data member + * without changing the constructor or call sites that pass the member to + * functions taking T&. Unlike a reference member it is copyable and + * assignable; assignment rebinds it. + */ +template +struct RefThunk { + RefThunk() = delete; + + // cppcheck-suppress noExplicitConstructor + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr RefThunk(T& t) noexcept : mPtr(std::addressof(t)) {} + RefThunk(T&& t) = delete; + + constexpr T& operator()() const noexcept { + return *mPtr; + } + + constexpr T& get() const noexcept { + return *mPtr; + } + + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr operator T&() const noexcept { + return *mPtr; + } + +private: + T* mPtr; +}; + +#endif diff --git a/lib/regex.cpp b/lib/regex.cpp index bee8acb2a2a..3a94b293ed5 100644 --- a/lib/regex.cpp +++ b/lib/regex.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -177,7 +177,11 @@ namespace { } std::string compile(); - std::string match(const std::string& str, const MatchFn& match) const override; + std::string match(const std::string& str, const MatchFn& matchFn) const override; + + Engine engine() const override { + return Engine::Pcre; + } private: std::string mPattern; @@ -219,7 +223,7 @@ namespace { return ""; } - std::string PcreRegex::match(const std::string& str, const MatchFn& match) const + std::string PcreRegex::match(const std::string& str, const MatchFn& matchFn) const { if (!mRe) return "regular expression has not been compiled yet"; @@ -236,7 +240,7 @@ namespace { const auto pos1 = static_cast(ovector[0]); const auto pos2 = static_cast(ovector[1]); - match(pos1, pos2); + matchFn(pos1, pos2); // jump to the end of the match for the next pcre_exec pos = static_cast(pos2); diff --git a/lib/regex.h b/lib/regex.h index afe92d92d40..c17fba41d4e 100644 --- a/lib/regex.h +++ b/lib/regex.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -44,6 +44,8 @@ class CPPCHECKLIB Regex Pcre = 1 }; + virtual Engine engine() const = 0; + static std::shared_ptr create(std::string pattern, Engine engine, std::string& err); }; diff --git a/lib/reverseanalyzer.cpp b/lib/reverseanalyzer.cpp index 50aad01fdd9..d6e9480adb7 100644 --- a/lib/reverseanalyzer.cpp +++ b/lib/reverseanalyzer.cpp @@ -23,6 +23,7 @@ #include "errortypes.h" #include "forwardanalyzer.h" #include "mathlib.h" +#include "refthunk.h" #include "settings.h" #include "symboldatabase.h" #include "token.h" @@ -41,9 +42,9 @@ namespace { : analyzer(analyzer), tokenlist(tokenlist), errorLogger(errorLogger), settings(settings) {} ValuePtr analyzer; - const TokenList& tokenlist; - ErrorLogger& errorLogger; - const Settings& settings; + RefThunk tokenlist; + RefThunk errorLogger; + RefThunk settings; std::pair evalCond(const Token* tok) const { std::vector result = analyzer->evaluate(tok); @@ -110,8 +111,11 @@ namespace { { if (Token::simpleMatch(tok->tokAt(-2), "} else {")) tok = tok->linkAt(-2); - if (Token::simpleMatch(tok->previous(), ") {")) + if (Token::simpleMatch(tok->previous(), ") {")) { + if (Token::simpleMatch(tok->linkAt(-1)->astOperand2(), ";")) + return tok->linkAt(-1)->astOperand2(); return tok->linkAt(-1); + } if (Token::simpleMatch(tok->previous(), "do {")) return tok->previous(); return tok; @@ -201,7 +205,7 @@ namespace { void traverse(Token* start, const Token* end = nullptr) { if (start == end) return; - std::size_t i = start->index(); + nonneg int i = start->index(); for (Token* tok = start->previous(); succeeds(tok, end); tok = tok->previous()) { if (tok->index() >= i) throw InternalError(tok, "Cyclic reverse analysis."); @@ -232,7 +236,7 @@ namespace { if (!Token::Match(assignTop->astOperand1(), "%assign%")) { continueB &= updateRecursive(assignTop->astOperand1()); } - if (!assignTop->astParent()) + if (!assignTop->astParent() || Token::simpleMatch(assignTop->astParent(), ";")) break; assignTop = assignTop->astParent(); } @@ -262,7 +266,7 @@ namespace { // Assignment to } else if (lhsAction.matches() && !assignTok->astOperand2()->hasKnownIntValue() && assignTok->astOperand2()->exprId() > 0 && - isConstExpression(assignTok->astOperand2(), settings.library)) { + isConstExpression(assignTok->astOperand2(), settings().library)) { const std::string info = "Assignment to '" + assignTok->expressionString() + "'"; ValuePtr a = analyzer->reanalyze(assignTok->astOperand2(), info); if (a) { @@ -337,9 +341,6 @@ namespace { valueFlowGenericForward(condTok, analyzer, tokenlist, errorLogger, settings); else if (condAction.isRead()) break; - // If the condition modifies the variable then bail - if (condAction.isModified()) - break; tok = jumpToStart(tok->link()); continue; } diff --git a/lib/rule.h b/lib/rule.h new file mode 100644 index 00000000000..dd5cf92effd --- /dev/null +++ b/lib/rule.h @@ -0,0 +1,43 @@ +/* -*- C++ -*- + * Cppcheck - A tool for static C/C++ code analysis + * Copyright (C) 2007-2026 Cppcheck team. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ruleH +#define ruleH + +#ifdef HAVE_RULES + +#include "errortypes.h" + +#include +#include + +class Regex; + +/** Rule */ +struct Rule +{ + std::string tokenlist = "normal"; // use normal tokenlist + std::string pattern; + std::string id = "rule"; // default id + std::string summary; + Severity severity = Severity::style; // default severity + std::shared_ptr regex; +}; +#endif // HAVE_RULES + +#endif // ruleH diff --git a/lib/settings.cpp b/lib/settings.cpp index 0708a37516b..da576f7a5c8 100644 --- a/lib/settings.cpp +++ b/lib/settings.cpp @@ -16,13 +16,19 @@ * along with this program. If not, see . */ +#include "addoninfo.h" #include "config.h" +#include "errortypes.h" #include "settings.h" #include "path.h" #include "summaries.h" #include "suppressions.h" #include "vfvalue.h" +#ifdef HAVE_RULES +#include "rule.h" +#endif + #include #include #include @@ -68,6 +74,14 @@ Settings::Settings() pid = getPid(); } +Settings::~Settings() = default; + +Settings::Settings(const Settings&) = default; +Settings & Settings::operator=(const Settings &) = default; + +Settings::Settings(Settings&&) CPPCHECK_NOEXCEPT = default; +Settings & Settings::operator=(Settings &&) CPPCHECK_NOEXCEPT = default; + std::string Settings::loadCppcheckCfg(Settings& settings, Suppressions& suppressions, bool debug) { static const std::string cfgFilename = "cppcheck.cfg"; @@ -321,6 +335,7 @@ void Settings::setCheckLevel(CheckLevel level) vfOptions.maxIfCount = 100; vfOptions.doConditionExpressionAnalysis = false; vfOptions.maxForwardBranches = 4; + vfOptions.maxForwardConditionForkDepth = 0; vfOptions.maxIterations = 1; } else if (level == CheckLevel::normal) { @@ -330,6 +345,7 @@ void Settings::setCheckLevel(CheckLevel level) vfOptions.maxIfCount = 100; vfOptions.doConditionExpressionAnalysis = false; vfOptions.maxForwardBranches = 4; + vfOptions.maxForwardConditionForkDepth = 1; } else if (level == CheckLevel::exhaustive) { // Checking can take a little while. ~ 10 times slower than normal analysis is OK. @@ -338,6 +354,8 @@ void Settings::setCheckLevel(CheckLevel level) vfOptions.maxSubFunctionArgs = 256; vfOptions.doConditionExpressionAnalysis = true; vfOptions.maxForwardBranches = -1; + vfOptions.maxForwardConditionForkDepth = 4; + vfOptions.maxForwardConditionForks = 256; } } @@ -351,6 +369,7 @@ static const std::set autosarCheckers{ "arrayIndexThenCheck", "bufferAccessOutOfBounds", "comparePointers", + "compareValueOutOfTypeRangeError", "constParameter", "ctuOneDefinitionRuleViolation", "doubleFree", @@ -510,6 +529,7 @@ static const std::set certCppCheckers{ "uninitdata", "uninitvar", "useClosedFile", + "uselessCallsRemove", "virtualCallInConstructor", "virtualDestructor", "wrongPrintfScanfArgNum", @@ -518,34 +538,45 @@ static const std::set certCppCheckers{ static const std::set misrac2012Checkers{ "argumentSize", + "arrayIndexOutOfBounds", + "arrayIndexOutOfBoundsCond", + "autoVariables", "autovarInvalidDeallocation", "bufferAccessOutOfBounds", "comparePointers", "compareValueOutOfTypeRangeError", + "constParameter", + "constParameterCallback", "constParameterPointer", "constStatement", + "constVariablePointer", "danglingLifetime", "danglingTemporaryLifetime", + "directiveAsMacroParameter", "duplicateBreak", "funcArgNamesDifferent", "incompatibleFileOpen", + "incorrectLogicOperator", "invalidFunctionArg", + "invalidFunctionArgStr", "knownConditionTrueFalse", "leakNoVarFunctionCall", "leakReturnValNotUsed", + "legacyUninitvar", "memleak", "memleakOnRealloc", "missingReturn", + "negativeIndex", "overlappingWriteFunction", "overlappingWriteUnion", "pointerOutOfBounds", - "preprocessorErrorDirective", "redundantAssignInSwitch", "redundantAssignment", "redundantCondition", "resourceLeak", "returnDanglingLifetime", "shadowVariable", + "shiftNegative", "sizeofCalculation", "sizeofwithsilentarraypointer", "syntaxError", @@ -562,34 +593,45 @@ static const std::set misrac2012Checkers{ static const std::set misrac2023Checkers{ "argumentSize", + "arrayIndexOutOfBounds", + "arrayIndexOutOfBoundsCond", + "autoVariables", "autovarInvalidDeallocation", "bufferAccessOutOfBounds", "comparePointers", "compareValueOutOfTypeRangeError", + "constParameter", + "constParameterCallback", "constParameterPointer", "constStatement", + "constVariablePointer", "danglingLifetime", "danglingTemporaryLifetime", + "directiveAsMacroParameter", "duplicateBreak", "funcArgNamesDifferent", "incompatibleFileOpen", + "incorrectLogicOperator", "invalidFunctionArg", + "invalidFunctionArgStr", "knownConditionTrueFalse", "leakNoVarFunctionCall", "leakReturnValNotUsed", + "legacyUninitvar", "memleak", "memleakOnRealloc", "missingReturn", + "negativeIndex", "overlappingWriteFunction", "overlappingWriteUnion", "pointerOutOfBounds", - "preprocessorErrorDirective", "redundantAssignInSwitch", "redundantAssignment", "redundantCondition", "resourceLeak", "returnDanglingLifetime", "shadowVariable", + "shiftNegative", "sizeofCalculation", "sizeofwithsilentarraypointer", "syntaxError", @@ -606,34 +648,45 @@ static const std::set misrac2023Checkers{ static const std::set misrac2025Checkers{ "argumentSize", + "arrayIndexOutOfBounds", + "arrayIndexOutOfBoundsCond", + "autoVariables", "autovarInvalidDeallocation", "bufferAccessOutOfBounds", "comparePointers", "compareValueOutOfTypeRangeError", + "constParameter", + "constParameterCallback", "constParameterPointer", "constStatement", + "constVariablePointer", "danglingLifetime", "danglingTemporaryLifetime", + "directiveAsMacroParameter", "duplicateBreak", "funcArgNamesDifferent", "incompatibleFileOpen", + "incorrectLogicOperator", "invalidFunctionArg", + "invalidFunctionArgStr", "knownConditionTrueFalse", "leakNoVarFunctionCall", "leakReturnValNotUsed", + "legacyUninitvar", "memleak", "memleakOnRealloc", "missingReturn", + "negativeIndex", "overlappingWriteFunction", "overlappingWriteUnion", "pointerOutOfBounds", - "preprocessorErrorDirective", "redundantAssignInSwitch", "redundantAssignment", "redundantCondition", "resourceLeak", "returnDanglingLifetime", "shadowVariable", + "shiftNegative", "sizeofCalculation", "sizeofwithsilentarraypointer", "syntaxError", @@ -651,6 +704,7 @@ static const std::set misrac2025Checkers{ static const std::set misracpp2008Checkers{ "autoVariables", "comparePointers", + "compareValueOutOfTypeRangeError", "constParameter", "constVariable", "cstyleCast", @@ -737,18 +791,31 @@ static const std::set misracpp2023Checkers{ bool Settings::isPremiumEnabled(const char id[]) const { - if (premiumArgs.find("autosar") != std::string::npos && autosarCheckers.count(id)) - return true; - if (premiumArgs.find("cert-c-") != std::string::npos && certCCheckers.count(id)) - return true; - if (premiumArgs.find("cert-c++") != std::string::npos && certCppCheckers.count(id)) - return true; - if (premiumArgs.find("misra-c-") != std::string::npos && (misrac2012Checkers.count(id) || misrac2023Checkers.count(id) || misrac2025Checkers.count(id))) - return true; - if (premiumArgs.find("misra-c++-2008") != std::string::npos && misracpp2008Checkers.count(id)) - return true; - if (premiumArgs.find("misra-c++-2023") != std::string::npos && misracpp2023Checkers.count(id)) - return true; + for (std::string arg: splitString(premiumArgs, ' ')) { + std::transform(arg.cbegin(), arg.cend(), arg.begin(), [](char c) { + return c=='+' ? 'p' : c; + }); + if (endsWith(arg, ":all")) + arg.erase(arg.size()-4); + if (arg == "--autosar" && autosarCheckers.count(id)) + return true; + if (arg == "--cert-c" && certCCheckers.count(id)) + return true; + if (arg == "--cert-c-2016" && certCCheckers.count(id)) + return true; + if (startsWith(arg, "--cert-cpp") && certCppCheckers.count(id)) + return true; + if (arg == "--misra-c-2012" && misrac2012Checkers.count(id)) + return true; + if (arg == "--misra-c-2023" && misrac2023Checkers.count(id)) + return true; + if (arg == "--misra-c-2025" && misrac2025Checkers.count(id)) + return true; + if (arg == "--misra-cpp-2008" && misracpp2008Checkers.count(id)) + return true; + if (arg == "--misra-cpp-2023" && misracpp2023Checkers.count(id)) + return true; + } return false; } @@ -768,3 +835,18 @@ bool Settings::unusedFunctionOnly() const char* unusedFunctionOnly = std::getenv("UNUSEDFUNCTION_ONLY"); return unusedFunctionOnly && (std::strcmp(unusedFunctionOnly, "1") == 0); } + +bool Settings::collectLogCheckers(bool* summary, bool* xmlReport, bool* textReport) const +{ + const bool s = safety || severity.isEnabled(Severity::information); + if (summary) + *summary = s; + const bool x = outputFormat == Settings::OutputFormat::xml && xml_version == 3; + if (xmlReport) + *xmlReport = x; + const bool t = !checkersReportFilename.empty(); + if (textReport) + *textReport = t; + + return s || x || t; +} diff --git a/lib/settings.h b/lib/settings.h index 85da57c4458..52f20afb7c4 100644 --- a/lib/settings.h +++ b/lib/settings.h @@ -21,9 +21,7 @@ #define settingsH //--------------------------------------------------------------------------- -#include "addoninfo.h" #include "config.h" -#include "errortypes.h" #include "library.h" #include "platform.h" #include "standards.h" @@ -40,23 +38,21 @@ #include #include -#include "regex.h" - #if defined(USE_WINDOWS_SEH) || defined(USE_UNIX_SIGNAL_HANDLING) #include #endif #ifdef HAVE_RULES -#include - -class Regex; +struct Rule; #endif - struct Suppressions; -enum class ShowTime : std::uint8_t; +struct AddonInfo; namespace ValueFlow { class Value; } +enum class Severity : std::uint8_t; +enum class Certainty : std::uint8_t; +enum class Checks : std::uint8_t; /// @addtogroup Core /// @{ @@ -98,6 +94,16 @@ class SimpleEnableGroup { }; +#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ <= 9 +// Hack to workaround GCC bug. +// Details: https://trac.cppcheck.net/ticket/14850 +// seen on g++ before 10.x +#define CPPCHECK_NOEXCEPT +#else +#define CPPCHECK_NOEXCEPT noexcept +#endif + + /** * @brief This is just a container for general settings so that we don't need * to pass individual values to functions or constructors now or in the @@ -112,6 +118,13 @@ class CPPCHECKLIB WARN_UNUSED Settings { public: Settings(); + ~Settings(); + + Settings(const Settings&); + Settings& operator=(const Settings&); + + Settings(Settings&&) CPPCHECK_NOEXCEPT; + Settings& operator=(Settings&&) CPPCHECK_NOEXCEPT; static std::string loadCppcheckCfg(Settings& settings, Suppressions& suppressions, bool debug = false); @@ -203,6 +216,9 @@ class CPPCHECKLIB WARN_UNUSED Settings { /** @brief Is --debug-ignore given? */ bool debugignore{}; + /** @brief Is --debug-ipc given? */ + bool debugipc{}; + /** @brief Internal: Is --debug-lookup or --debug-lookup=all given? */ bool debuglookup{}; @@ -356,6 +372,9 @@ class CPPCHECKLIB WARN_UNUSED Settings { /** @brief Using -E for debugging purposes */ bool preprocessOnly{}; + /** @brief Keep comments in preprocessed output */ + bool keepComments{}; + /** @brief Is --quiet given? */ bool quiet{}; @@ -366,17 +385,6 @@ class CPPCHECKLIB WARN_UNUSED Settings { int reportProgress{-1}; #ifdef HAVE_RULES - /** Rule */ - struct CPPCHECKLIB Rule { - std::string tokenlist = "normal"; // use normal tokenlist - std::string pattern; - std::string id = "rule"; // default id - std::string summary; - Severity severity = Severity::style; // default severity - Regex::Engine engine = Regex::Engine::Pcre; - std::shared_ptr regex; - }; - /** * @brief Extra rules */ @@ -442,6 +450,15 @@ class CPPCHECKLIB WARN_UNUSED Settings { SimpleEnableGroup certainty; SimpleEnableGroup checks; + enum class ShowTime : std::uint8_t { + NONE, + FILE, + FILE_TOTAL, + SUMMARY, + TOP5_SUMMARY, + TOP5_FILE + }; + /** @brief show timing information (--showtime=file|summary|top5) */ ShowTime showtime{}; @@ -498,6 +515,15 @@ class CPPCHECKLIB WARN_UNUSED Settings { /** @brief Maximum performed forward branches */ int maxForwardBranches = -1; + /** @brief Maximum depth of nested condition-fork continuations in the forward analyzer. + Bounds the exponential fan-out of carrying condition state forward at exhaustive level (where + maxForwardBranches is unlimited); past it the forward analysis continues on a single linear path + without skipping any branch. 0 disables forking (linear); a negative value means unlimited. */ + int maxForwardConditionForkDepth = 4; + + /** @brief Maximum total condition-fork continuations in one forward traversal. */ + int maxForwardConditionForks = 256; + /** @brief Maximum performed alignof recursion */ int maxAlignOfRecursion = 100; @@ -585,6 +611,8 @@ class CPPCHECKLIB WARN_UNUSED Settings { static bool unusedFunctionOnly(); + bool collectLogCheckers(bool* summary = nullptr, bool* xmlReport = nullptr, bool* textReport = nullptr) const; + private: static std::string parseEnabled(const std::string &str, std::tuple, SimpleEnableGroup> &groups); std::string applyEnabled(const std::string &str, bool enable); diff --git a/lib/standards.cpp b/lib/standards.cpp index a0ec72c1580..959f271912d 100644 --- a/lib/standards.cpp +++ b/lib/standards.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -157,3 +157,15 @@ bool Standards::setStd(const std::string& str) { return setC(str) || setCPP(str); } + +std::string Standards::getStdForLanguage(Standards::Language language) const +{ + switch (language) { + case C: + return getC(); + case CPP: + return getCPP(); + default: + return ""; + } +} diff --git a/lib/standards.h b/lib/standards.h index d2c2a2b8e01..0d10b8c6d5d 100644 --- a/lib/standards.h +++ b/lib/standards.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -58,6 +58,7 @@ struct CPPCHECKLIB Standards { static std::string getCPP(cppstd_t std); static cppstd_t getCPP(const std::string &std); bool setStd(const std::string& str); + std::string getStdForLanguage(Language language) const; }; /// @} diff --git a/lib/suppressions.cpp b/lib/suppressions.cpp index 70b689e2cdb..0089510c86f 100644 --- a/lib/suppressions.cpp +++ b/lib/suppressions.cpp @@ -31,7 +31,7 @@ #include #include // std::isdigit, std::isalnum, etc #include -#include // std::bind, std::placeholders +#include #include #include #include @@ -135,7 +135,10 @@ std::string SuppressionList::parseXmlFile(const char *filename) s.lineNumber = strToInt(text); else if (std::strcmp(name, "symbolName") == 0) s.symbolName = text; - else if (*text && std::strcmp(name, "hash") == 0) + else if (std::strcmp(name, "macroName") == 0) { + s.macroName = text; + s.type = SuppressionList::Type::macro; + } else if (*text && std::strcmp(name, "hash") == 0) s.hash = strToInt(text); else return std::string("unknown element '") + name + "' in suppressions XML '" + filename + "', expected id/fileName/lineNumber/symbolName/hash."; @@ -149,6 +152,36 @@ std::string SuppressionList::parseXmlFile(const char *filename) return ""; } +static std::string getExtraComment(const std::string &comment, std::string::size_type startPos, std::string::size_type *delimPos = nullptr) +{ + const std::string::size_type semiPos = comment.find(';', startPos); + const std::string::size_type slashPos = comment.find("//", startPos); + std::string::size_type pos; + + if (delimPos) + *delimPos = std::min(semiPos, slashPos); + + if (semiPos < slashPos) { + pos = semiPos + 1; + } else if (slashPos < semiPos) { + pos = slashPos + 2; + } else { + return ""; + } + + std::string extra = comment.substr(pos); + + if (startsWith(comment, "/*") && endsWith(comment, "*/")) + extra.erase(extra.size() - 2, 2); + + extra = trim(extra); + + for (auto it = extra.begin(); it != extra.end();) + it = (*it & 0x80) ? extra.erase(it) : it + 1; + + return extra; +} + std::vector SuppressionList::parseMultiSuppressComment(const std::string &comment, std::string *errorMessage) { std::vector suppressions; @@ -203,6 +236,14 @@ std::vector SuppressionList::parseMultiSuppressCom suppressions.push_back(std::move(s)); } + const std::string extraComment = getExtraComment(comment, end_position); + + if (extraComment.empty()) + return suppressions; + + for (auto &suppression : suppressions) + suppression.extraComment = extraComment; + return suppressions; } @@ -213,6 +254,8 @@ SuppressionList::Suppression SuppressionList::parseLine(std::string line) // Strip any end of line comments std::string::size_type endpos = std::min(line.find('#'), line.find("//")); if (endpos != std::string::npos) { + const std::string::size_type offset = line[endpos] == '#' ? 1 : 2; + suppression.extraComment = trim(line.substr(endpos + offset)); while (endpos > 0 && std::isspace(line[endpos-1])) { endpos--; } @@ -281,14 +324,27 @@ std::string SuppressionList::addSuppressionLine(const std::string &line) } } +SuppressionList::Suppression *SuppressionList::findSameParameters(const Suppression &suppression) +{ + const std::vector *candidates = &mNonHashSuppressions; + if (suppression.hash > 0) { + const auto it = mHashSuppressions.find(suppression.hash); + if (it == mHashSuppressions.end()) + return nullptr; + candidates = &it->second; + } + const auto found = std::find_if(candidates->cbegin(), candidates->cend(), [&](const Suppression *s) { + return suppression.isSameParameters(*s); + }); + return found != candidates->cend() ? *found : nullptr; +} + std::string SuppressionList::addSuppression(SuppressionList::Suppression suppression) { std::lock_guard lg(mSuppressionsSync); // Check if suppression is already in list - auto foundSuppression = std::find_if(mSuppressions.begin(), mSuppressions.end(), - std::bind(&Suppression::isSameParameters, &suppression, std::placeholders::_1)); - if (foundSuppression != mSuppressions.end()) { + if (findSameParameters(suppression)) { return "suppression '" + suppression.toString() + "' already exists"; } @@ -312,6 +368,12 @@ std::string SuppressionList::addSuppression(SuppressionList::Suppression suppres mSuppressions.push_back(std::move(suppression)); + Suppression *const added = &mSuppressions.back(); + if (added->hash > 0) + mHashSuppressions[added->hash].push_back(added); + else + mNonHashSuppressions.push_back(added); + return ""; } @@ -330,9 +392,8 @@ bool SuppressionList::updateSuppressionState(const SuppressionList::Suppression& std::lock_guard lg(mSuppressionsSync); // Check if suppression is already in list - auto foundSuppression = std::find_if(mSuppressions.begin(), mSuppressions.end(), - std::bind(&Suppression::isSameParameters, &suppression, std::placeholders::_1)); - if (foundSuppression != mSuppressions.end()) { + Suppression *const foundSuppression = findSameParameters(suppression); + if (foundSuppression) { if (suppression.checked) foundSuppression->checked = true; if (suppression.matched) @@ -356,20 +417,11 @@ bool SuppressionList::Suppression::parseComment(std::string comment, std::string if (comment.compare(comment.size() - 2, 2, "*/") == 0) comment.erase(comment.size() - 2, 2); - std::string::size_type extraPos = comment.find(';'); - std::string::size_type extraDelimiterSize = 1; - - if (extraPos == std::string::npos) { - extraPos = comment.find("//", 2); - extraDelimiterSize = 2; - } + std::string::size_type extraPos; + extraComment = getExtraComment(comment, 2, &extraPos); - if (extraPos != std::string::npos) { - extraComment = trim(comment.substr(extraPos + extraDelimiterSize)); - for (auto it = extraComment.begin(); it != extraComment.end();) - it = *it & 0x80 ? extraComment.erase(it) : it + 1; + if (!extraComment.empty()) comment.erase(extraPos); - } const std::set cppchecksuppress{ "cppcheck-suppress", @@ -419,7 +471,7 @@ SuppressionList::Suppression::Result SuppressionList::Suppression::isSuppressed( if (!thisAndNextLine || lineNumber + 1 != errmsg.lineNumber) return Result::None; } - if (!fileName.empty() && !PathMatch::match(fileName, errmsg.getFileName())) + if (!fileName.empty() && !isFileNameMatch(errmsg.getFileName())) return Result::None; if (hash > 0 && hash != errmsg.hash) return Result::Checked; @@ -452,6 +504,21 @@ SuppressionList::Suppression::Result SuppressionList::Suppression::isSuppressed( return Result::Matched; } +bool SuppressionList::Suppression::isFileNameMatch(const std::string &errorFileName) const +{ + const auto it = mFileNameMatchCache.find(errorFileName); + if (it != mFileNameMatchCache.end()) + return it->second; + + const bool result = PathMatch::match(fileName, errorFileName); + + if (mFileNameMatchCache.size() >= mFileNameMatchCacheMaxEntries) + mFileNameMatchCache.clear(); + + mFileNameMatchCache.emplace(errorFileName, result); + return result; +} + bool SuppressionList::Suppression::isMatch(const SuppressionList::ErrorMessage &errmsg) { switch (isSuppressed(errmsg)) { @@ -475,13 +542,21 @@ bool SuppressionList::isSuppressed(const SuppressionList::ErrorMessage &errmsg, // TODO: handle unmatchedPolyspaceSuppression? const bool unmatchedSuppression(errmsg.errorId == "unmatchedSuppression"); bool returnValue = false; - for (Suppression &s : mSuppressions) { - if (!global && !s.isLocal()) - continue; - if (unmatchedSuppression && s.errorId != errmsg.errorId) - continue; - if (s.isMatch(errmsg)) - returnValue = true; + const auto match = [&](const std::vector &suppressions) { + for (Suppression *s : suppressions) { + if (!global && !s->isLocal()) + continue; + if (unmatchedSuppression && s->errorId != errmsg.errorId) + continue; + if (s->isMatch(errmsg)) + returnValue = true; + } + }; + match(mNonHashSuppressions); + if (errmsg.hash != 0) { + const auto it = mHashSuppressions.find(errmsg.hash); + if (it != mHashSuppressions.end()) + match(it->second); } return returnValue; } @@ -490,13 +565,21 @@ bool SuppressionList::isSuppressedExplicitly(const SuppressionList::ErrorMessage { std::lock_guard lg(mSuppressionsSync); - for (Suppression &s : mSuppressions) { - if (!global && !s.isLocal()) - continue; - if (s.errorId != errmsg.errorId) // Error id must match exactly - continue; - if (s.isMatch(errmsg)) - return true; + const auto match = [&](const std::vector &suppressions) { + return std::any_of(suppressions.cbegin(), suppressions.cend(), [&](Suppression *s) { + if (!global && !s->isLocal()) + return false; + if (s->errorId != errmsg.errorId) // Error id must match exactly + return false; + return s->isMatch(errmsg); + }); + }; + if (match(mNonHashSuppressions)) + return true; + if (errmsg.hash != 0) { + const auto it = mHashSuppressions.find(errmsg.hash); + if (it != mHashSuppressions.end()) + return match(it->second); } return false; } @@ -637,7 +720,9 @@ void SuppressionList::markUnmatchedInlineSuppressionsAsChecked(const TokenList & if (currFileIdx != tok->fileIndex() || currLineNr != tok->linenr()) { currLineNr = tok->linenr(); currFileIdx = tok->fileIndex(); - for (auto &suppression : mSuppressions) { + // checked is not used for hash suppressions + for (Suppression *s : mNonHashSuppressions) { + Suppression &suppression = *s; if (suppression.type == SuppressionList::Type::unique) { if (!suppression.checked && (suppression.lineNumber == currLineNr) && (suppression.fileName == tokenlist.file(tok))) { suppression.checked = true; diff --git a/lib/suppressions.h b/lib/suppressions.h index 849c972d0a7..42955ab7b91 100644 --- a/lib/suppressions.h +++ b/lib/suppressions.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -51,14 +52,14 @@ class CPPCHECKLIB SuppressionList { }; struct CPPCHECKLIB ErrorMessage { - std::size_t hash; + std::size_t hash{}; std::string errorId; void setFileName(std::string s); const std::string &getFileName() const { return mFileName; } - int lineNumber; // TODO: need to be unsigned - Certainty certainty; + int lineNumber{}; // TODO: need to be unsigned + Certainty certainty{}; std::string symbolNames; std::set macroNames; @@ -168,6 +169,12 @@ class CPPCHECKLIB SuppressionList { bool isPolyspace{}; enum : std::int8_t { NO_LINE = -1 }; + + private: + bool isFileNameMatch(const std::string &errorFileName) const; + + static constexpr std::size_t mFileNameMatchCacheMaxEntries = 256; + mutable std::map mFileNameMatchCache; }; /** @@ -288,9 +295,20 @@ class CPPCHECKLIB SuppressionList { void markUnmatchedInlineSuppressionsAsChecked(const TokenList &tokenlist); private: + /** + * @brief Find a suppression with the same parameters. mSuppressionsSync must be locked. + * @param suppression the suppression to look for + * @return the found suppression, or nullptr + */ + Suppression *findSameParameters(const Suppression &suppression); + mutable std::mutex mSuppressionsSync; /** @brief List of error which the user doesn't want to see. */ std::list mSuppressions; + /** @brief Suppressions in mSuppressions with hash > 0, indexed by hash */ + std::unordered_map> mHashSuppressions; + /** @brief Suppressions in mSuppressions with hash == 0 */ + std::vector mNonHashSuppressions; }; struct Suppressions diff --git a/lib/symboldatabase.cpp b/lib/symboldatabase.cpp index 7c125ea002d..26801640e99 100644 --- a/lib/symboldatabase.cpp +++ b/lib/symboldatabase.cpp @@ -39,6 +39,7 @@ #include #include +#include #include #include #include @@ -138,8 +139,14 @@ const Token* SymbolDatabase::isEnumDefinition(const Token* tok) if (tok->str() == "{") return tok; tok = tok->next(); // skip ':' - while (Token::Match(tok, "%name%|::")) + bool hasType = false; + while (Token::Match(tok, "%name%|::")) { + if (tok->isName()) + hasType = true; tok = tok->next(); + } + if (!hasType) + throw InternalError(tok, "SymbolDatabase bailout; invalid enum", InternalError::SYNTAX); return Token::simpleMatch(tok, "{") ? tok : nullptr; } @@ -175,9 +182,9 @@ void SymbolDatabase::createSymbolDatabaseFindAllScopes() // Store current access in each scope (depends on evaluation progress) std::map access; - std::map> forwardDecls; + std::map> forwardDecls; - const std::function findForwardDeclScope = [&](const Token *tok, Scope *startScope) { + const std::function findForwardDeclScope = [&](const Token *tok, const Scope *startScope) { if (tok->str() == "::") return findForwardDeclScope(tok->next(), &scopeList.front()); @@ -187,14 +194,14 @@ void SymbolDatabase::createSymbolDatabaseFindAllScopes() }); if (it == startScope->nestedList.cend()) - return static_cast(nullptr); + return static_cast(nullptr); return findForwardDeclScope(tok->tokAt(2), *it); } auto it = forwardDecls.find(startScope); if (it == forwardDecls.cend()) - return static_cast(nullptr); + return static_cast(nullptr); return it->second.count(tok->str()) > 0 ? startScope : nullptr; }; @@ -910,7 +917,8 @@ void SymbolDatabase::createSymbolDatabaseCopyAndMoveConstructors() continue; const Variable* firstArg = func->getArgumentVar(0); - if (firstArg->type() == scope.definedType) { + // skip references to functions: Foo(Foo& (&f)()) + if (firstArg->type() == scope.definedType && !Token::Match(firstArg->typeEndToken(), "&|&& %name%| ) (")) { if (firstArg->isRValueReference()) func->type = FunctionType::eMoveConstructor; else if (firstArg->isReference() && !firstArg->isPointer()) @@ -1043,10 +1051,8 @@ void SymbolDatabase::createSymbolDatabaseNeedInitialization() scope.definedType->needInitialization = Type::NeedInitialization::True; else if (!unknown) scope.definedType->needInitialization = Type::NeedInitialization::False; - else { - if (scope.definedType->needInitialization == Type::NeedInitialization::Unknown) - unknowns++; - } + else + unknowns++; } } else if (scope.type == ScopeType::eUnion && scope.definedType->needInitialization == Type::NeedInitialization::Unknown) scope.definedType->needInitialization = Type::NeedInitialization::True; @@ -1203,7 +1209,7 @@ void SymbolDatabase::createSymbolDatabaseSetFunctionPointers(bool firstPass) tok->function(function); - if (tok->strAt(1) != "(") + if (!function->isConstructor() && tok->strAt(1) != "(") const_cast(function)->functionPointerUsage = tok; } } @@ -1246,6 +1252,7 @@ void SymbolDatabase::createSymbolDatabaseSetTypePointers() { std::unordered_set typenames; for (const Type &t : typeList) { + // cppcheck-suppress useStlAlgorithm - std::transform is cumbersome typenames.insert(t.name()); } @@ -1492,7 +1499,7 @@ void SymbolDatabase::createSymbolDatabaseEnums() void SymbolDatabase::createSymbolDatabaseIncompleteVars() { - for (Token* tok = mTokenizer.list.front(); tok != mTokenizer.list.back(); tok = tok->next()) { + for (Token* tok = mTokenizer.list.front(); precedes(tok, mTokenizer.list.back()); tok = tok->next()) { const Scope * scope = tok->scope(); if (!scope) continue; @@ -1611,6 +1618,26 @@ static std::string getIncompleteNameID(const Token* tok) } namespace { + int getExprIdForOperand(const Token* tok) { + if (!tok) + return 0; + + int otherExprId = 0; + + // Look through all referenced tokens. + // If two exprIds are found and one matches tok->exprId(), return the other. + // Otherwise, default to returning tok->exprId(). + for (const auto& ref: followAllReferences(tok)) { + const int refExprId = ref.token->exprId(); + if (refExprId != 0 && refExprId != tok->exprId()) { + if (otherExprId != 0 && otherExprId != refExprId) + return tok->exprId(); + otherExprId = refExprId; + } + } + return otherExprId != 0 ? otherExprId : tok->exprId(); + } + struct ExprIdKey { std::string parentOp; nonneg int operand1; @@ -1645,8 +1672,8 @@ namespace { ExprIdKey key; key.parentOp = tok->astParent()->str(); - key.operand1 = op1 ? op1->exprId() : 0; - key.operand2 = op2 ? op2->exprId() : 0; + key.operand1 = getExprIdForOperand(op1); + key.operand2 = getExprIdForOperand(op2); if (tok->astParent()->isCast() && tok->astParent()->str() == "(") { const Token* typeStartToken; @@ -1665,19 +1692,6 @@ namespace { key.parentOp += type; } - for (const auto& ref: followAllReferences(op1)) { - if (ref.token->exprId() != 0) { // cppcheck-suppress useStlAlgorithm - key.operand1 = ref.token->exprId(); - break; - } - } - for (const auto& ref: followAllReferences(op2)) { - if (ref.token->exprId() != 0) { // cppcheck-suppress useStlAlgorithm - key.operand2 = ref.token->exprId(); - break; - } - } - if (key.operand1 > key.operand2 && key.operand2 && Token::Match(tok->astParent(), "%or%|%oror%|+|*|&|&&|^|==|!=")) { // In C++ the order of operands of + might matter @@ -2166,7 +2180,7 @@ namespace { { if (const Scope* scope = var->nameToken()->scope()) { auto it = std::find_if(scope->functionList.begin(), scope->functionList.end(), [&](const Function& function) { - for (nonneg int arg = 0; arg < function.argCount(); ++arg) { + for (size_t arg = 0; arg < function.argCount(); ++arg) { if (var == function.getArgumentVar(arg)) return true; } @@ -2370,9 +2384,9 @@ void SymbolDatabase::validate() const validateVariables(); } -void SymbolDatabase::clangSetVariables(const std::vector &variableList) +void SymbolDatabase::clangSetVariables(const std::vector &vars) { - mVariableList = variableList; + mVariableList = vars; } void SymbolDatabase::debugSymbolDatabase() const @@ -2503,7 +2517,12 @@ bool Variable::isMember() const { bool Variable::isPointerArray() const { - return isArray() && nameToken() && nameToken()->previous() && (nameToken()->strAt(-1) == "*"); + if (!isArray()) + return false; + const Token* tok = nameToken() ? nameToken()->previous() : nullptr; + while (Token::Match(tok, "const|volatile")) + tok = tok->previous(); + return Token::simpleMatch(tok, "*"); } bool Variable::isUnsigned() const @@ -2541,7 +2560,7 @@ void Variable::evaluate(const Settings& settings) bool isContainer = false; if (mNameToken) { setFlag(fIsArray, arrayDimensions(settings, isContainer)); - setFlag(fIsMaybeUnused, mNameToken->isAttributeMaybeUnused()); + setFlag(fIsMaybeUnused, mNameToken->isAttributeMaybeUnused() || mNameToken->isAttributeUnused()); } @@ -2578,6 +2597,10 @@ void Variable::evaluate(const Settings& settings) } else if (tok->str() == "&&") { // Before simplification, && isn't split up setFlag(fIsRValueRef, true); setFlag(fIsReference, true); // Set also fIsReference + } else if (tok->str() == "(" && Token::simpleMatch(tok->link(), ") (")) { + // a reference before the parentheses belongs to the return type of a function pointer: int& (*f)() + setFlag(fIsRValueRef, false); + setFlag(fIsReference, false); } if (tok->str() == "<" && tok->link()) @@ -2636,16 +2659,16 @@ void Variable::evaluate(const Settings& settings) } } -void Variable::setValueType(const ValueType &valueType) +void Variable::setValueType(const ValueType &vt) { - if (valueType.type == ValueType::Type::UNKNOWN_TYPE) { + if (vt.type == ValueType::Type::UNKNOWN_TYPE) { const Token *declType = Token::findsimplematch(mTypeStartToken, "decltype (", mTypeEndToken); if (declType && !declType->next()->valueType()) return; } - const auto* vt = new ValueType(valueType); + const auto* tmp = new ValueType(vt); delete mValueType; - mValueType = vt; + mValueType = tmp; if ((mValueType->pointer > 0) && (!isArray() || Token::Match(mNameToken->previous(), "( * %name% )"))) setFlag(fIsPointer, true); setFlag(fIsConst, mValueType->constness & (1U << mValueType->pointer)); @@ -3013,10 +3036,10 @@ static bool typesMatch( const Token *&new_second) { // get first type - const Type* first_type = first_scope->symdb.findType(first_token, first_scope, /*lookOutside*/ true); + const Type* first_type = first_scope->symdb().findType(first_token, first_scope, /*lookOutside*/ true); if (first_type) { // get second type - const Type* second_type = second_scope->symdb.findType(second_token, second_scope, /*lookOutside*/ true); + const Type* second_type = second_scope->symdb().findType(second_token, second_scope, /*lookOutside*/ true); // check if types match if (first_type == second_type) { const Token* tok1 = first_token; @@ -3390,6 +3413,20 @@ bool Function::returnsVoid(const Function* function, bool unknown) }); } +bool Function::isCoroutine(const Function* function, const Standards &standards, const Tokenizer &tokens) +{ + if (!tokens.isCPP() || standards.cpp < Standards::CPP20) + return false; + if (!function->functionScope) + return false; + const Scope *scope = function->functionScope; + for (const Token *tok = scope->bodyStart; tok != scope->bodyEnd; tok = tok->next()) { + if (Token::Match(tok, "co_return|co_await|co_yield")) + return true; + } + return false; +} + std::vector Function::findReturns(const Function* f) { std::vector result; @@ -3798,7 +3835,7 @@ const Token *Type::initBaseInfo(const Token *tok, const Token *tok1) } } - const Type * baseType = classScope->symdb.findType(base.nameTok, enclosingScope); + const Type * baseType = classScope->symdb().findType(base.nameTok, enclosingScope); if (baseType && !baseType->findDependency(this)) base.type = baseType; @@ -4440,6 +4477,11 @@ void SymbolDatabase::printXml(std::ostream &out) const outs += ErrorLogger::toxml(scope->className); outs += "\""; } + if (scope->classDef) { + outs += " classDef=\""; + outs += id_string(scope->classDef); + outs += "\""; + } if (scope->bodyStart) { outs += " bodyStart=\""; outs += id_string(scope->bodyStart); @@ -4524,7 +4566,7 @@ void SymbolDatabase::printXml(std::ostream &out) const outs += "/>\n"; else { outs += ">\n"; - for (unsigned int argnr = 0; argnr < function->argCount(); ++argnr) { + for (size_t argnr = 0; argnr < function->argCount(); ++argnr) { const Variable *arg = function->getArgumentVar(argnr); outs += " symdb.findVariableType(scope, typeTok); + const Type* argType = scope->symdb().findVariableType(scope, typeTok); if (argType) return argType; @@ -4663,7 +4704,7 @@ static const Type* findVariableTypeIncludingUsedNamespaces(const Scope* scope, c while (scope) { for (const Scope::UsingInfo &ui : scope->usingList) { if (ui.scope) { - argType = scope->symdb.findVariableType(ui.scope, typeTok); + argType = scope->symdb().findVariableType(ui.scope, typeTok); if (argType) return argType; } @@ -4736,7 +4777,7 @@ void Function::addArguments(const Scope *scope) while (Token::Match(typeTok, "const|volatile|enum|struct|::")) typeTok = typeTok->next(); if (Token::Match(typeTok, ",|)")) { // #8333 - scope->symdb.mTokenizer.syntaxError(typeTok); + scope->symdb().mTokenizer.syntaxError(typeTok); } if (Token::Match(typeTok, "%type% <") && Token::Match(typeTok->linkAt(1), "> :: %type%")) typeTok = typeTok->linkAt(1)->tokAt(2); @@ -4755,7 +4796,7 @@ void Function::addArguments(const Scope *scope) endTok = nameTok->previous(); if (hasBody()) - scope->symdb.debugMessage(nameTok, "varid0", "Function::addArguments found argument \'" + nameTok->str() + "\' with varid 0."); + scope->symdb().debugMessage(nameTok, "varid0", "Function::addArguments found argument \'" + nameTok->str() + "\' with varid 0."); } else endTok = typeTok; } else @@ -4786,7 +4827,7 @@ void Function::addArguments(const Scope *scope) if (startTok == nameTok) break; - argumentList.emplace_back(nameTok, startTok, endTok, count++, AccessControl::Argument, argType, functionScope, scope->symdb.mSettings); + argumentList.emplace_back(nameTok, startTok, endTok, count++, AccessControl::Argument, argType, functionScope, scope->symdb().mSettings); if (tok->str() == ")") { // check for a variadic function or a variadic template function @@ -4946,7 +4987,7 @@ const Function * Function::getOverriddenFunctionRecursive(const ::Type* baseType return nullptr; } -const Variable* Function::getArgumentVar(nonneg int num) const +const Variable* Function::getArgumentVar(size_t num) const { if (num < argumentList.size()) { auto it = argumentList.begin(); @@ -5030,7 +5071,7 @@ void Scope::addVariable(const Token *token_, const Token *start_, const Token *e // keep possible size_t -> int truncation outside emplace_back() to have a single line // C4267 VC++ warning instead of several dozens lines const int varIndex = varlist.size(); - varlist.emplace_back(token_, start_, end_, varIndex, access_, type_, scope_, scope_->symdb.mSettings); + varlist.emplace_back(token_, start_, end_, varIndex, access_, type_, scope_, scope_->symdb().mSettings); } // Get variable list.. @@ -5043,7 +5084,7 @@ void Scope::getVariableList() // global scope else if (type == ScopeType::eGlobal) - getVariableList(symdb.mTokenizer.tokens(), nullptr); + getVariableList(symdb().mTokenizer.tokens(), nullptr); // forward declaration else @@ -5053,7 +5094,7 @@ void Scope::getVariableList() void Scope::getVariableList(const Token* start, const Token* end) { // Variable declared in condition: if (auto x = bar()) - if (Token::Match(classDef, "if|while ( %type%") && Token::simpleMatch(classDef->next()->astOperand2(), "=")) { + if (Token::Match(classDef, "if|while|switch ( %type%") && Token::simpleMatch(classDef->next()->astOperand2(), "=")) { checkVariable(classDef->tokAt(2), defaultAccess()); } @@ -5188,7 +5229,7 @@ const Token *Scope::checkVariable(const Token *tok, AccessControl varaccess) const Token *typestart = tok; // C++17 structured bindings - if (tok && tok->isCpp() && (symdb.mSettings.standards.cpp >= Standards::CPP17) && Token::Match(tok, "auto &|&&| [")) { + if (tok && tok->isCpp() && (symdb().mSettings.standards.cpp >= Standards::CPP17) && Token::Match(tok, "auto &|&&| [")) { const Token *typeend = Token::findsimplematch(typestart, "[")->previous(); for (tok = typeend->tokAt(2); Token::Match(tok, "%name%|,"); tok = tok->next()) { if (tok->varId()) @@ -5214,7 +5255,7 @@ const Token *Scope::checkVariable(const Token *tok, AccessControl varaccess) if (vartok->varId() == 0) { if (!vartok->isBoolean()) - symdb.debugMessage(vartok, "varid0", "Scope::checkVariable found variable \'" + vartok->str() + "\' with varid 0."); + symdb().debugMessage(vartok, "varid0", "Scope::checkVariable found variable \'" + vartok->str() + "\' with varid 0."); return tok; } @@ -5240,7 +5281,7 @@ const Token *Scope::checkVariable(const Token *tok, AccessControl varaccess) } if (Token::Match(tok2, ", %name%")) { if (tok2->next()->varId() == 0) { - symdb.debugMessage(tok2->next(), "varid0", "Scope::checkVariable found variable \'" + tok2->strAt(1) + "\' with varid 0."); + symdb().debugMessage(tok2->next(), "varid0", "Scope::checkVariable found variable \'" + tok2->strAt(1) + "\' with varid 0."); return tok; } addVariable(tok2->next(), typestart, vartok->previous(), varaccess, vType, this); @@ -5330,7 +5371,7 @@ bool Scope::isVariableDeclaration(const Token* const tok, const Token*& vartok, if (isCPP && Token::Match(tok, "throw|new")) return false; - const bool isCPP11 = isCPP && symdb.mSettings.standards.cpp >= Standards::CPP11; + const bool isCPP11 = isCPP && symdb().mSettings.standards.cpp >= Standards::CPP11; if (isCPP11 && tok->str() == "using") return false; @@ -5826,7 +5867,10 @@ bool Scope::hasInlineOrLambdaFunction(const Token** tokStart, bool onlyInline) c }); } -void Scope::findFunctionInBase(const Token* tok, nonneg int args, std::vector & matches) const +void Scope::findFunctionInBase(const std::string& name, + const Token* tok, + size_t args, + std::vector& matches) const { if (isClassOrStruct() && definedType && !definedType->derivedFrom.empty()) { const std::vector &derivedFrom = definedType->derivedFrom; @@ -5836,7 +5880,7 @@ void Scope::findFunctionInBase(const Token* tok, nonneg int args, std::vectorclassScope == this) // Ticket #5120, #5125: Recursive class; tok should have been found already continue; - auto range = base->classScope->functionMap.equal_range(tok->str()); + auto range = base->classScope->functionMap.equal_range(name); for (auto it = range.first; it != range.second; ++it) { const Function *func = it->second; if (func->isDestructor() && !Token::simpleMatch(tok->tokAt(-1), "~")) @@ -5847,7 +5891,7 @@ void Scope::findFunctionInBase(const Token* tok, nonneg int args, std::vectorclassScope->findFunctionInBase(tok, args, matches); + base->classScope->findFunctionInBase(name, tok, args, matches); } } } @@ -5897,6 +5941,39 @@ std::vector Scope::findAssociatedScopes() const //--------------------------------------------------------------------------- +// Might an object of the class be converted to a pointer by a conversion operator (also in an unknown base class)? +// operator C*(), operator ptr() +static bool mayConvertToPointer(const Scope* classScope) +{ + const std::vector scopes = classScope->findAssociatedScopes(); + return std::any_of(scopes.cbegin(), scopes.cend(), [](const Scope* scope) { + const bool hasConversionOperator = std::any_of(scope->functionList.cbegin(), scope->functionList.cend(), [](const Function& f) { + return f.name().size() > 8 && startsWith(f.name(), "operator") && + (std::isalpha(static_cast(f.name()[8])) || f.name()[8] == '_' || f.name()[8] == ':'); + }); + const bool hasUnknownBase = scope->definedType && std::any_of(scope->definedType->derivedFrom.cbegin(), scope->definedType->derivedFrom.cend(), [](const Type::BaseInfo& base) { + return !base.type || !base.type->classScope; + }); + return hasConversionOperator || hasUnknownBase; + }); +} + +// Returns true if the variable can't be passed for the function argument because only one of them is a pointer to +// the same class: struct C; void f(C* p); void g(C& r); C c; C* p; f(c); g(p); +static bool isPointerMismatch(const Variable* callarg, const Variable* funcarg) +{ + if (!callarg->type() || callarg->type() != funcarg->type() || callarg->isArray() || funcarg->isArray()) + return false; + // function pointer or reference: C (*f)() + if (Token::simpleMatch(callarg->nameToken()->next(), ") (") || (funcarg->nameToken() && Token::simpleMatch(funcarg->nameToken()->next(), ") ("))) + return false; + // assume that incomplete types have no conversion operator + if (funcarg->isPointer()) + return !callarg->isPointer() && (!callarg->type()->classScope || !mayConvertToPointer(callarg->type()->classScope)); + // a temporary from a converting constructor can bind to const C& and C&& + return callarg->isPointer() && funcarg->isReference() && !funcarg->isRValueReference() && !funcarg->isConst(); +} + static void checkVariableCallMatch(const Variable* callarg, const Variable* funcarg, size_t& same, size_t& fallback1, size_t& fallback2) { if (callarg) { @@ -5986,8 +6063,10 @@ static bool hasMatchingConstructor(const Scope* classScope, const ValueType* arg }); } -const Function* Scope::findFunction(const Token *tok, bool requireConst, Reference ref) const +const Function* Scope::findFunction(const Token* tok, bool requireConst, Reference ref, const std::string& funcName) const { + const std::string& name = funcName.empty() ? tok->str() : funcName; + const bool isCall = Token::Match(tok->next(), "(|{"); const std::vector arguments = getArguments(tok); @@ -5997,8 +6076,8 @@ const Function* Scope::findFunction(const Token *tok, bool requireConst, Referen // find all the possible functions that could match const std::size_t args = arguments.size(); - auto addMatchingFunctions = [&](const Scope *scope) { - auto range = scope->functionMap.equal_range(tok->str()); + auto addMatchingFunctions = [&](const Scope* scope) { + auto range = scope->functionMap.equal_range(name); for (auto it = range.first; it != range.second; ++it) { const Function *func = it->second; if (ref == Reference::LValue && func->hasRvalRefQualifier()) @@ -6030,10 +6109,10 @@ const Function* Scope::findFunction(const Token *tok, bool requireConst, Referen addMatchingFunctions(nestedScope); } - const std::size_t numberOfMatchesNonBase = matches.size(); + std::size_t numberOfMatchesNonBase = matches.size(); // check in base classes - findFunctionInBase(tok, args, matches); + findFunctionInBase(name, tok, args, matches); // Non-call => Do not match parameters if (!isCall) { @@ -6144,6 +6223,14 @@ const Function* Scope::findFunction(const Token *tok, bool requireConst, Referen // check for a match with a variable if (Token::Match(arguments[j], "%var% ,|)")) { const Variable * callarg = arguments[j]->variable(); + if (callarg && isPointerMismatch(callarg, funcarg)) { + // can't match so remove this function from possible matches + matches.erase(matches.begin() + i); + if (i < numberOfMatchesNonBase) + --numberOfMatchesNonBase; + erased = true; + break; + } checkVariableCallMatch(callarg, funcarg, same, fallback1, fallback2); } @@ -6263,8 +6350,8 @@ const Function* Scope::findFunction(const Token *tok, bool requireConst, Referen matches.erase(itPure); // Only one candidate left - if (matches.size() == 1 && std::none_of(functionList.begin(), functionList.end(), [tok](const Function& f) { - return startsWith(f.name(), tok->str() + " <"); + if (matches.size() == 1 && std::none_of(functionList.begin(), functionList.end(), [&name](const Function& f) { + return startsWith(f.name(), name + " <"); })) return matches[0]; @@ -7005,6 +7092,10 @@ void SymbolDatabase::setValueType(Token* tok, const ValueType& valuetype, const setAutoTokenProperties(autoTok); if (vt2->pointer > vt.pointer) vt.pointer++; + if (Token::simpleMatch(autoTok->next(), "&")) + vt.reference = Reference::LValue; + if (Token::simpleMatch(autoTok->next(), "&&")) + vt.reference = Reference::RValue; setValueType(var1Tok, vt); if (var1Tok != parent->previous()) setValueType(parent->previous(), vt); @@ -7037,7 +7128,7 @@ void SymbolDatabase::setValueType(Token* tok, const ValueType& valuetype, const return; } - if (parent->str() == "[" && (!parent->isCpp() || parent->astOperand1() == tok) && valuetype.pointer > 0U && !Token::Match(parent->previous(), "[{,]")) { + if (parent->str() == "[" && (!parent->isCpp() || parent->astOperand1() == tok) && valuetype.pointer > 0 && !Token::Match(parent->previous(), "[{,]")) { const Token *op1 = parent->astOperand1(); while (op1 && op1->str() == "[") op1 = op1->astOperand1(); @@ -7049,7 +7140,7 @@ void SymbolDatabase::setValueType(Token* tok, const ValueType& valuetype, const setValueType(parent, vt); return; } - if (Token::Match(parent->tokAt(-1), "%name% (") && !parent->tokAt(-1)->isKeyword() && parent->astOperand1() == tok && valuetype.pointer > 0U) { + if (Token::Match(parent->tokAt(-1), "%name% (") && !parent->tokAt(-1)->isKeyword() && parent->astOperand1() == tok && valuetype.pointer > 0) { ValueType vt(valuetype); vt.pointer -= 1U; setValueType(parent, vt); @@ -7062,7 +7153,7 @@ void SymbolDatabase::setValueType(Token* tok, const ValueType& valuetype, const setValueType(parent, vt); return; } - if (parent->str() == "*" && !parent->astOperand2() && valuetype.pointer > 0U) { + if (parent->str() == "*" && !parent->astOperand2() && valuetype.pointer > 0) { ValueType vt(valuetype); vt.pointer -= 1U; setValueType(parent, vt); @@ -7095,7 +7186,7 @@ void SymbolDatabase::setValueType(Token* tok, const ValueType& valuetype, const return; } } - if (parent->str() == "*" && Token::simpleMatch(parent->astOperand2(), "[") && valuetype.pointer > 0U) { + if (parent->str() == "*" && Token::simpleMatch(parent->astOperand2(), "[") && valuetype.pointer > 0) { const Token *op1 = parent->astOperand2()->astOperand1(); while (op1 && op1->str() == "[") op1 = op1->astOperand1(); @@ -7279,15 +7370,55 @@ void SymbolDatabase::setValueType(Token* tok, const ValueType& valuetype, const } // c++17 auto type deduction of braced init list - if (parent->isCpp() && mSettings.standards.cpp >= Standards::CPP17 && vt2 && Token::Match(parent->tokAt(-2), "auto %var% {")) { - Token *autoTok = parent->tokAt(-2); - setValueType(autoTok, *vt2); - setAutoTokenProperties(autoTok); - if (parent->previous()->variable()) - const_cast(parent->previous()->variable())->setValueType(*vt2); - else - debugMessage(parent->previous(), "debug", "Missing variable class for variable with varid"); - return; + if (parent->isCpp() && mSettings.standards.cpp >= Standards::CPP17 + && Token::Match(parent->astOperand1(), "%var% {") && vt2) { + + auto reference = Reference::None; + nonneg int pointer = 0; + nonneg int constness = 0; + nonneg int volatileness = 0; + + Token *varTok = parent->astOperand1(); + Token *typeTok = varTok->previous(); + + while (Token::Match(typeTok, "&|&&|*|const|volatile")) { + if (typeTok->str() == "&") + reference = Reference::LValue; + else if (typeTok->str() == "&&") + reference = Reference::RValue; + else if (typeTok->str() == "*") + pointer++; + else if (typeTok->str() == "const") + constness |= 1 << pointer; + else if (typeTok->str() == "volatile") + volatileness |= 1 << pointer; + typeTok = typeTok->previous(); + } + + if (typeTok->str() == "auto") { + setValueType(typeTok, *vt2); + setAutoTokenProperties(typeTok); + + auto *varVt = new ValueType(*vt2); + + varVt->reference = reference; + varVt->constness |= constness; + varVt->volatileness |= volatileness; + + if (Token::simpleMatch(typeTok->previous(), "const auto")) + varVt->constness |= 1 << pointer; + + if (Token::simpleMatch(typeTok->previous(), "volatile auto")) + varVt->volatileness |= 1 << pointer; + + varTok->setValueType(varVt); + + if (varTok->variable()) + const_cast(varTok->variable())->setValueType(*varVt); + else + debugMessage(varTok, "debug", "Missing variable class for variable with varid"); + return; + } } if (!vt1) @@ -7542,7 +7673,7 @@ static const Token* parsedecl(const Token* type, valuetype->container = container; } else { const Scope *scope = type->scope(); - valuetype->typeScope = scope->symdb.findScope(typeTokens.front(), scope); + valuetype->typeScope = scope->symdb().findScope(typeTokens.front(), scope); if (valuetype->typeScope) valuetype->type = (scope->type == ScopeType::eClass) ? ValueType::Type::RECORD : ValueType::Type::NONSTD; } @@ -7713,6 +7844,13 @@ static const Function* getFunction(const Token* tok) { lambda = lvar->nameToken()->tokAt(2)->function(); if (lambda && lambda->retDef) return lambda; + // calling an object of a class that overloads operator() + if (tok != lvar->nameToken() && !lvar->isPointer() && !lvar->isArray() && lvar->typeScope()) { + const Function* callOp = + lvar->typeScope()->findFunction(tok, lvar->isConst(), Reference::LValue, "operator()"); + if (callOp && callOp->retDef) + return callOp; + } } return nullptr; } @@ -7751,6 +7889,78 @@ static int getIntegerConstantMacroWidth(const Token* tok) { return intnum; } +void SymbolDatabase::setGenericValueType(Token *par) +{ + if (!par) + return; + + const Token *tok = par->astOperand2(); + std::vector stack; + + while (tok && tok->str() == ",") { + stack.push_back(tok); + tok = tok->astOperand1(); + } + + if (!tok) + return; + + const ValueType *controlVt = tok->valueType(); + + if (!controlVt) + return; + + const Token *selected = nullptr; + + const auto matchVt = [](const ValueType *control, const ValueType *type) { + // Strip top level qualifiers of controlling expression + const unsigned int controlMask = ~(1U << control->pointer); + return control->isTypeEqual(type) && + control->sign == type->sign && + (static_cast(control->constness) & controlMask) == static_cast(type->constness) && + (static_cast(control->volatileness) & controlMask) == static_cast(type->volatileness); + }; + + while (!stack.empty()) { + const Token *comma = stack.back(); + stack.pop_back(); + + const Token *type = comma->next(); + const Token *expr = comma->astOperand2(); + + if (type->str() == "default") { + if (!selected) + selected = expr; + } else { + ValueType typeVt; + + if (!parsedecl(type, &typeVt, mDefaultSignedness, mSettings)) + continue; + + if (matchVt(controlVt, &typeVt)) { + selected = expr; + break; + } + } + } + + if (!selected) + return; + + if (selected->valueType()) { + setValueType(par, *selected->valueType()); + } else { + const Function *f = selected->function(); + Token *parent = par->astParent(); + + if (f && f->retDef && parent && parent->str() == "(") { + ValueType returnVt; + if (parsedecl(f->retDef, &returnVt, mDefaultSignedness, mSettings)) + setValueType(parent, returnVt); + } + } +} + void SymbolDatabase::setValueTypeInTokenList(bool reportDebugWarnings, Token *tokens) { if (!tokens) @@ -7759,7 +7969,18 @@ void SymbolDatabase::setValueTypeInTokenList(bool reportDebugWarnings, Token *to for (Token *tok = tokens; tok; tok = tok->next()) tok->setValueType(nullptr); + std::vector genericClosingParens; + for (Token *tok = tokens; tok; tok = tok->next()) { + if (Token::simpleMatch(tok, "_Generic (")) { + genericClosingParens.push_back(tok->linkAt(1)); + continue; + } + if (!genericClosingParens.empty() && tok == genericClosingParens.back()) { + setGenericValueType(tok->link()); + genericClosingParens.pop_back(); + continue; + } if (tok->isNumber()) { if (MathLib::isFloat(tok->str())) { ValueType::Type type = ValueType::Type::DOUBLE; @@ -7807,7 +8028,7 @@ void SymbolDatabase::setValueTypeInTokenList(bool reportDebugWarnings, Token *to setValueType(tok, ValueType(sign, type, 0U)); } - } else if (tok->isComparisonOp() || tok->tokType() == Token::eLogicalOp) { + } else if ((tok->isComparisonOp() || tok->tokType() == Token::eLogicalOp) && tok->astOperand1()) { if (tok->isCpp() && tok->isComparisonOp() && (getClassScope(tok->astOperand1()) || getClassScope(tok->astOperand2()))) { const Function *function = getOperatorFunction(tok); if (function) { @@ -8002,7 +8223,7 @@ void SymbolDatabase::setValueTypeInTokenList(bool reportDebugWarnings, Token *to if (!typestr.empty()) { ValueType valuetype; TokenList tokenList(mSettings, tok->isCpp() ? Standards::Language::CPP : Standards::Language::C); - const std::string str(typestr+";"); + const std::string str(typestr+";\n"); tokenList.createTokensFromBuffer(str.data(), str.size()); // TODO: check result? tokenList.simplifyStdType(); if (parsedecl(tokenList.front(), &valuetype, mDefaultSignedness, mSettings)) { @@ -8035,17 +8256,20 @@ void SymbolDatabase::setValueTypeInTokenList(bool reportDebugWarnings, Token *to } } //Is iterator fetching function called? - } else if (Token::simpleMatch(tok->astOperand1(), "::") && - tok->astOperand2() && - tok->astOperand2()->isVariable()) { - const auto* const paramVariable = tok->astOperand2()->variable(); + } else if (Token::simpleMatch(tok->astOperand1(), "::") && Token::Match(tok->astOperand2(), "%var%|.|[")) { + const Token* varTok = tok->astOperand2(); + while (Token::simpleMatch(varTok, "[")) + varTok = varTok->astOperand1(); + while (Token::simpleMatch(varTok, ".")) + varTok = varTok->astOperand2(); + const Variable* const paramVariable = varTok ? varTok->variable() : nullptr; if (!paramVariable || !paramVariable->valueType() || !paramVariable->valueType()->container) { continue; } - const auto yield = astFunctionYield(tok->previous(), mSettings); + const auto yield = astFunctionYield(tok->previous(), mSettings.library); if (yield == Library::Container::Yield::START_ITERATOR || yield == Library::Container::Yield::END_ITERATOR || yield == Library::Container::Yield::ITERATOR) { @@ -8092,7 +8316,7 @@ void SymbolDatabase::setValueTypeInTokenList(bool reportDebugWarnings, Token *to continue; } TokenList tokenList(mSettings, tok->isCpp() ? Standards::Language::CPP : Standards::Language::C); - const std::string str(typestr+";"); + const std::string str(typestr+";\n"); if (tokenList.createTokensFromBuffer(str.data(), str.size())) { ValueType vt; tokenList.simplifyPlatformTypes(); @@ -8463,7 +8687,7 @@ namespace { } template -static Result accumulateStructMembers(const Scope* scope, F f, ValueType::Accuracy accuracy) +static Result accumulateStructMembers(const Scope* scope, F f, ValueType::Accuracy accuracy, const Settings& settings) { size_t total = 0; std::set anonScopes; @@ -8482,6 +8706,10 @@ static Result accumulateStructMembers(const Scope* scope, F f, ValueType::Accura if (ret.second) total = f(total, *vt, dim, bits); } + else if (vt->container && vt->container->startPattern == "std :: array <") { + const ValueType vtElement = ValueType::parseDecl(vt->containerTypeToken, settings); + total = f(total, vtElement, dim, bits); + } else total = f(total, *vt, dim, bits); } @@ -8521,12 +8749,12 @@ static size_t getAlignOf(const ValueType& vt, const Settings& settings, ValueTyp size_t a = getAlignOf(vt2, settings, accuracy, ValueType::SizeOf::Pointer, ++maxRecursion); return std::max(max, a); }; - Result result = accumulateStructMembers(vt.typeScope, accHelper, accuracy); + Result result = accumulateStructMembers(vt.typeScope, accHelper, accuracy, settings); size_t total = result.total; if (const Type* dt = vt.typeScope->definedType) { total = std::accumulate(dt->derivedFrom.begin(), dt->derivedFrom.end(), total, [&](size_t v, const Type::BaseInfo& bi) { if (bi.type && bi.type->classScope) - v += accumulateStructMembers(bi.type->classScope, accHelper, accuracy).total; + v += accumulateStructMembers(bi.type->classScope, accHelper, accuracy, settings).total; return v; }); } @@ -8614,14 +8842,14 @@ size_t ValueType::getSizeOf( const Settings& settings, Accuracy accuracy, SizeOf } return typeScope->type == ScopeType::eUnion ? std::max(total, n) : total + padding + n; }; - Result result = accumulateStructMembers(typeScope, accHelper, accuracy); + Result result = accumulateStructMembers(typeScope, accHelper, accuracy, settings); size_t total = result.total; if (currentBitCount > 0) total += currentBitfieldAlloc; if (const ::Type* dt = typeScope->definedType) { total = std::accumulate(dt->derivedFrom.begin(), dt->derivedFrom.end(), total, [&](size_t v, const ::Type::BaseInfo& bi) { if (bi.type && bi.type->classScope) - v += accumulateStructMembers(bi.type->classScope, accHelper, accuracy).total; + v += accumulateStructMembers(bi.type->classScope, accHelper, accuracy, settings).total; return v; }); } @@ -8699,7 +8927,7 @@ std::string ValueType::str() const } else if (type == ValueType::Type::SMART_POINTER && smartPointer) { ret += " smart-pointer(" + smartPointer->name + ")"; } - for (unsigned int p = 0; p < pointer; p++) { + for (nonneg int p = 0; p < pointer; p++) { ret += " *"; if (constness & (2 << p)) ret += " const"; @@ -8827,5 +9055,5 @@ void SymbolDatabase::getErrorMessages(ErrorLogger& /*errorLogger*/) { // TODO //SymbolDatabase symdb; - //symdb.returnImplicitIntError(nullptr); + //symdb().returnImplicitIntError(nullptr); } diff --git a/lib/symboldatabase.h b/lib/symboldatabase.h index a4cdfc5370a..832bcddeab3 100644 --- a/lib/symboldatabase.h +++ b/lib/symboldatabase.h @@ -25,6 +25,7 @@ #include "errortypes.h" #include "library.h" #include "mathlib.h" +#include "refthunk.h" #include "sourcelocation.h" #include "token.h" #include "utils.h" @@ -651,7 +652,7 @@ class CPPCHECKLIB Variable { return mValueType; } - void setValueType(const ValueType &valueType); + void setValueType(const ValueType &vt); AccessControl accessControl() const { return mAccess; @@ -767,14 +768,14 @@ class CPPCHECKLIB Function { std::string fullName() const; - nonneg int argCount() const { + size_t argCount() const { return argumentList.size(); } - nonneg int minArgCount() const { + size_t minArgCount() const { return argumentList.size() - initArgCount; } - const Variable* getArgumentVar(nonneg int num) const; - nonneg int initializedArgCount() const { + const Variable* getArgumentVar(size_t num) const; + size_t initializedArgCount() const { return initArgCount; } /** @@ -928,7 +929,7 @@ class CPPCHECKLIB Function { const Scope* functionScope{}; ///< scope of function body const Scope* nestedIn{}; ///< Scope the function is declared in std::list argumentList; ///< argument list, must remain list due to clangimport usage! - nonneg int initArgCount{}; ///< number of args with default values + size_t initArgCount{}; ///< number of args with default values FunctionType type = FunctionType::eFunction; ///< constructor, destructor, ... const Token* noexceptArg{}; ///< noexcept token const Token* throwArg{}; ///< throw token @@ -945,6 +946,7 @@ class CPPCHECKLIB Function { static bool returnsStandardType(const Function* function, bool unknown = false); static bool returnsVoid(const Function* function, bool unknown = false); + static bool isCoroutine(const Function* function, const Standards &standards, const Tokenizer &tokens); static std::vector findReturns(const Function* f); @@ -1044,7 +1046,7 @@ class CPPCHECKLIB Scope { Scope(const SymbolDatabase &symdb_, const Token *classDef_, const Scope *nestedIn_); Scope(const SymbolDatabase &symdb_, const Token *classDef_, const Scope *nestedIn_, ScopeType type_, const Token *start_); - const SymbolDatabase& symdb; + RefThunk symdb; std::string className; const Token* classDef{}; ///< class/struct/union/namespace token const Token* bodyStart{}; ///< '{' token @@ -1143,9 +1145,14 @@ class CPPCHECKLIB Scope { * @brief find a function * @param tok token of function call * @param requireConst if const refers to a const variable only const methods should be matched + * @param ref reference qualification of the object the function is called on + * @param funcName name to look up instead of tok->str(), e.g. "operator()" when tok is a variable that is called * @return pointer to function if found or NULL if not found */ - const Function *findFunction(const Token *tok, bool requireConst=false, Reference ref=Reference::None) const; + const Function* findFunction(const Token* tok, + bool requireConst = false, + Reference ref = Reference::None, + const std::string& funcName = "") const; const Scope *findRecordInNestedList(const std::string & name, bool isC = false) const; Scope *findRecordInNestedList(const std::string & name, bool isC = false); @@ -1209,7 +1216,10 @@ class CPPCHECKLIB Scope { */ bool isVariableDeclaration(const Token* tok, const Token*& vartok, const Token*& typetok) const; - void findFunctionInBase(const Token* tok, nonneg int args, std::vector & matches) const; + void findFunctionInBase(const std::string& name, + const Token* tok, + size_t args, + std::vector& matches) const; /** @brief initialize varlist */ void getVariableList(const Token *start, const Token *end); @@ -1340,6 +1350,8 @@ class CPPCHECKLIB SymbolDatabase { public: explicit SymbolDatabase(Tokenizer& tokenizer); ~SymbolDatabase(); + SymbolDatabase(const SymbolDatabase &) = delete; + SymbolDatabase &operator=(const SymbolDatabase &) = delete; /** @brief Information about all namespaces/classes/structures */ std::list scopeList; @@ -1411,6 +1423,12 @@ class CPPCHECKLIB SymbolDatabase { */ void validate() const; + /** + * Set value type for generic selection (_Generic). + * @param par The opening parenthesis of the _Generic expression. + */ + void setGenericValueType(Token *par); + /** Set valuetype in provided tokenlist */ void setValueTypeInTokenList(bool reportDebugWarnings, Token *tokens=nullptr); @@ -1424,13 +1442,13 @@ class CPPCHECKLIB SymbolDatabase { /** Set array dimensions when valueflow analysis is completed */ void setArrayDimensionsUsingValueFlow(); - void clangSetVariables(const std::vector &variableList); + void clangSetVariables(const std::vector &vars); void createSymbolDatabaseExprIds(); /* returns the opening { if tok points to enum */ static const Token* isEnumDefinition(const Token* tok); - static void getErrorMessages(ErrorLogger &errorLogger); + static void getErrorMessages(ErrorLogger& /*errorLogger*/); // check if type has no side effects (no constructors and no members with constructors) /** @todo false negative: check constructors for side effects */ @@ -1503,8 +1521,8 @@ class CPPCHECKLIB SymbolDatabase { std::list checkedFuncs) const; Tokenizer& mTokenizer; - const Settings &mSettings; - ErrorLogger &mErrorLogger; + const Settings& mSettings; + ErrorLogger& mErrorLogger; /** variable symbol table */ std::vector mVariableList; diff --git a/lib/templatesimplifier.cpp b/lib/templatesimplifier.cpp index 1fe1df10037..c59fdf288f7 100644 --- a/lib/templatesimplifier.cpp +++ b/lib/templatesimplifier.cpp @@ -71,7 +71,7 @@ namespace { return tokenAndName.token() == mToken; } private: - const Token * const mToken; + const Token * mToken; }; class FindName { @@ -81,7 +81,7 @@ namespace { return tokenAndName.name() == mName; } private: - const std::string mName; + std::string mName; }; class FindFullName { @@ -91,7 +91,7 @@ namespace { return tokenAndName.fullName() == mFullName; } private: - const std::string mFullName; + std::string mFullName; }; } @@ -170,8 +170,12 @@ TemplateSimplifier::TokenAndName::TokenAndName(Token *token, std::string scope, if (isFunction()) tok1 = tok1->link()->next(); while (tok1 && !Token::Match(tok1, ";|{")) { - if (tok1->str() == "<") - tok1 = tok1->findClosingBracket(); + if (tok1->str() == "<") { + if (const Token* closing = tok1->findClosingBracket()) + tok1 = closing; + else + syntaxError(tok1); + } else if (Token::Match(tok1, "(|[") && tok1->link()) tok1 = tok1->link(); if (tok1) @@ -277,7 +281,7 @@ TemplateSimplifier::TemplateSimplifier(Tokenizer &tokenizer) mErrorLogger(mTokenizer.getErrorLogger()) {} -void TemplateSimplifier::checkComplicatedSyntaxErrorsInTemplates() +void TemplateSimplifier::checkComplicatedSyntaxErrorsInTemplates() const { // check for more complicated syntax errors when using templates.. for (const Token *tok = mTokenList.front(); tok; tok = tok->next()) { @@ -390,13 +394,14 @@ unsigned int TemplateSimplifier::templateParameters(const Token *tok) return 0; if (tok->str() != "<") return 0; - if (Token::Match(tok->previous(), "%var% <")) + if (Token::Match(tok->previous(), "%var%|) <") && !(tok->linkAt(-1) && Token::simpleMatch(tok->linkAt(-1)->tokAt(-1), "operator"))) return 0; tok = tok->next(); if (!tok || tok->str() == ">") return 0; unsigned int level = 0; + unsigned int ternaryLevel = 0; while (tok) { // skip template template @@ -456,8 +461,16 @@ unsigned int TemplateSimplifier::templateParameters(const Token *tok) } // Skip '=', '?', ':' - if (Token::Match(tok, "=|?|:")) + if (Token::Match(tok, "=|?|:")) { + if (tok->str()[0] == '?') + ++ternaryLevel; + else if (tok->str()[0] == ':') { + if (ternaryLevel == 0) + return 0; + --ternaryLevel; + } tok = tok->next(); + } if (!tok) return 0; @@ -492,7 +505,7 @@ unsigned int TemplateSimplifier::templateParameters(const Token *tok) return 0; // num/type .. - if (!tok->isNumber() && tok->tokType() != Token::eChar && tok->tokType() != Token::eString && !tok->isName() && !tok->isOp()) + if (!tok->isNumber() && tok->tokType() != Token::eChar && tok->tokType() != Token::eString && !tok->isName() && !tok->isOp() && tok->str() != ".") return 0; tok = tok->next(); if (!tok) @@ -623,7 +636,7 @@ void TemplateSimplifier::deleteToken(Token *tok) tok->deleteThis(); } -static void invalidateForwardDecls(const Token* beg, const Token* end, std::map* forwardDecls) { +static void invalidateForwardDecls(const Token* beg, const Token* end, std::map* forwardDecls) { if (!forwardDecls) return; for (auto& fwd : *forwardDecls) { @@ -635,7 +648,7 @@ static void invalidateForwardDecls(const Token* beg, const Token* end, std::map< } } -bool TemplateSimplifier::removeTemplate(Token *tok, std::map* forwardDecls) +bool TemplateSimplifier::removeTemplate(Token *tok, std::map* forwardDecls) { if (!Token::simpleMatch(tok, "template <")) return false; @@ -1765,7 +1778,7 @@ void TemplateSimplifier::expandTemplate( --typeindentlevel; else if (typetok->str() == "(") ++typeindentlevel; - else if (typetok->str() == ")") + else if (typeindentlevel > 0 && typetok->str() == ")") --typeindentlevel; dst->insertTokenBefore(typetok->str(), typetok->originalName(), typetok->getMacroName()); dst->previous()->linenr(start->linenr()); @@ -2151,7 +2164,7 @@ void TemplateSimplifier::expandTemplate( ++typeindentlevel; } else if (typetok->str() == "(") ++typeindentlevel; - else if (typetok->str() == ")") + else if (typeindentlevel > 0 && typetok->str() == ")") --typeindentlevel; Token *back; if (copy) { @@ -2258,7 +2271,7 @@ void TemplateSimplifier::expandTemplate( Token::Match(tok3->next()->findClosingBracket(), ">|>>")) { const Token *closingBracket = tok3->next()->findClosingBracket(); if (Token::simpleMatch(closingBracket->next(), "&")) { - int num = 0; + size_t num = 0; const Token *par = tok3->next(); while (num < typeParametersInDeclaration.size() && par != closingBracket) { const std::string pattern("[<,] " + typeParametersInDeclaration[num]->str() + " [,>]"); @@ -2720,7 +2733,7 @@ static bool validTokenEnd(bool bounded, const Token *tok, const Token *backToken // TODO: This is not the correct class for simplifyCalculations(), so it // should be moved away. -bool TemplateSimplifier::simplifyCalculations(Token* frontToken, const Token *backToken, bool isTemplate) +bool TemplateSimplifier::simplifyCalculations(Token* frontToken, const Token *backToken, bool isTemplate) const { bool ret = false; const bool bounded = frontToken || backToken; @@ -3041,7 +3054,7 @@ bool TemplateSimplifier::matchSpecialization( declToken->isSigned() != instToken->isSigned() || declToken->isUnsigned() != instToken->isUnsigned() || declToken->isLong() != instToken->isLong()) { - int nr = 0; + size_t nr = 0; while (nr < templateParameters.size() && templateParameters[nr]->str() != declToken->str()) ++nr; @@ -3139,7 +3152,7 @@ bool TemplateSimplifier::simplifyTemplateInstantiations( // locate template usage.. std::string::size_type numberOfTemplateInstantiations = mTemplateInstantiations.size(); - unsigned int recursiveCount = 0; + int recursiveCount = 0; bool instantiated = false; @@ -3827,7 +3840,7 @@ void TemplateSimplifier::simplifyTemplates(const std::time_t maxtime) // Remove concepts/requires // TODO concepts are not removed yet for (Token *tok = mTokenList.front(); tok; tok = tok->next()) { - if (!Token::Match(tok, ")|>|>> requires %name%|(")) + if (!Token::Match(tok, ")|>|>>|const|noexcept requires %name%|(")) continue; const Token* end = skipRequires(tok->next()); if (end) @@ -3916,6 +3929,7 @@ void TemplateSimplifier::simplifyTemplates(const std::time_t maxtime) std::unordered_map nameOrdinal; int ordinal = 0; for (const auto& decl : mTemplateDeclarations) { + // cppcheck-suppress useStlAlgorithm - std::transform is cumbersome nameOrdinal.emplace(decl.fullName(), ordinal++); } diff --git a/lib/templatesimplifier.h b/lib/templatesimplifier.h index f6e507459a9..3dc8b6bf1fd 100644 --- a/lib/templatesimplifier.h +++ b/lib/templatesimplifier.h @@ -47,6 +47,8 @@ struct newInstantiation; class CPPCHECKLIB TemplateSimplifier { public: explicit TemplateSimplifier(Tokenizer &tokenizer); + TemplateSimplifier(const TemplateSimplifier &) = delete; + TemplateSimplifier &operator=(const TemplateSimplifier &) = delete; const std::string& dump() const { return mDump; @@ -58,7 +60,7 @@ class CPPCHECKLIB TemplateSimplifier { /** */ - void checkComplicatedSyntaxErrorsInTemplates(); + void checkComplicatedSyntaxErrorsInTemplates() const; /** * is the token pointing at a template parameters block @@ -333,7 +335,7 @@ class CPPCHECKLIB TemplateSimplifier { * @return true if modifications to token-list are done. * false if no modifications are done. */ - bool simplifyCalculations(Token* frontToken = nullptr, const Token *backToken = nullptr, bool isTemplate = true); + bool simplifyCalculations(Token* frontToken = nullptr, const Token *backToken = nullptr, bool isTemplate = true) const; /** Simplify template instantiation arguments. * @param start first token of arguments @@ -463,7 +465,7 @@ class CPPCHECKLIB TemplateSimplifier { /** * Remove a specific "template < ..." template class/function */ - static bool removeTemplate(Token *tok, std::map* forwardDecls = nullptr); + static bool removeTemplate(Token *tok, std::map* forwardDecls = nullptr); /** Syntax error * @throws InternalError thrown unconditionally @@ -504,17 +506,17 @@ class CPPCHECKLIB TemplateSimplifier { const std::string &indent = " ") const; void printOut(const std::string &text = "") const; - Tokenizer &mTokenizer; - TokenList &mTokenList; - const Settings &mSettings; - ErrorLogger &mErrorLogger; + Tokenizer& mTokenizer; + TokenList& mTokenList; + const Settings& mSettings; + ErrorLogger& mErrorLogger; bool mChanged{}; std::list mTemplateDeclarations; std::list mTemplateForwardDeclarations; - std::map mTemplateForwardDeclarationsMap; - std::map mTemplateSpecializationMap; - std::map mTemplatePartialSpecializationMap; + std::map mTemplateForwardDeclarationsMap; + std::map mTemplateSpecializationMap; + std::map mTemplatePartialSpecializationMap; std::list mTemplateInstantiations; std::list mInstantiatedTemplates; std::list mMemberFunctionsToDelete; diff --git a/lib/timer.cpp b/lib/timer.cpp index 5a18953767a..275acc06078 100644 --- a/lib/timer.cpp +++ b/lib/timer.cpp @@ -19,66 +19,69 @@ #include "timer.h" #include -#include -#include #include +#include #include #include namespace { - using dataElementType = std::pair; - bool more_second_sec(const dataElementType& lhs, const dataElementType& rhs) - { - return lhs.second.getSeconds() > rhs.second.getSeconds(); - } - // TODO: remove and print through (synchronized) ErrorLogger instead std::mutex stdCoutLock; } // TODO: this does not include any file context when SHOWTIME_FILE thus rendering it useless - should we include the logging with the progress logging? // that could also get rid of the broader locking -void TimerResults::showResults(ShowTime mode, bool metrics, bool format) const +void TimerResults::showResults(size_t max_results, bool metrics) const { - if (mode == ShowTime::NONE) - return; - std::vector data; + using dataElementType = std::pair>; + std::vector data; { std::lock_guard l(mResultsSync); data.reserve(mResults.size()); data.insert(data.begin(), mResults.cbegin(), mResults.cend()); } - std::sort(data.begin(), data.end(), more_second_sec); + + const auto asSeconds = [](std::chrono::milliseconds ms) -> std::chrono::duration { + return std::chrono::duration_cast>(ms); + }; + + const auto getSeconds = [&asSeconds](const std::vector& results) -> std::chrono::duration { + return std::accumulate(results.cbegin(), results.cend(), std::chrono::duration{}, [&asSeconds](std::chrono::duration secs, std::chrono::milliseconds duration) { + return secs + asSeconds(duration); + }); + }; + + std::sort(data.begin(), data.end(), [&getSeconds](const dataElementType& lhs, const dataElementType& rhs) -> bool { + return getSeconds(lhs.second) > getSeconds(rhs.second); + }); // lock the whole logging operation to avoid multiple threads printing their results at the same time std::lock_guard l(stdCoutLock); size_t ordinal = 1; // maybe it would be nice to have an ordinal in output later! for (auto iter=data.cbegin(); iter!=data.cend(); ++iter) { - const double sec = iter->second.getSeconds().count(); - const double secAverage = sec / static_cast(iter->second.mNumberOfResults); - if ((mode != ShowTime::TOP5_FILE && mode != ShowTime::TOP5_SUMMARY) || (ordinal<=5)) { - std::cout << iter->first << ": "; - if (format) - std::cout << TimerResultsData::durationToString(iter->second.mDuration); - else - std::cout << sec << "s"; - if (metrics) - std::cout << " (avg. " << secAverage << "s - " << iter->second.mNumberOfResults << " result(s))"; + if (ordinal <= max_results) { + const double sec = getSeconds(iter->second).count(); + std::cout << iter->first << ": " << sec << "s"; + if (metrics) { + const double secAverage = sec / static_cast(iter->second.size()); + const double secMin = asSeconds(*std::min_element(iter->second.cbegin(), iter->second.cend())).count(); + const double secMax = asSeconds(*std::max_element(iter->second.cbegin(), iter->second.cend())).count(); + std::cout << " (avg. " << secAverage << "s / min " << secMin << "s / max " << secMax << "s - " << iter->second.size() << " result(s))"; + } std::cout << std::endl; } ++ordinal; } } -void TimerResults::addResults(const std::string& str, std::chrono::milliseconds duration) +void TimerResults::addResults(const std::string& name, std::chrono::milliseconds duration) { std::lock_guard l(mResultsSync); - mResults[str].mDuration += duration; - mResults[str].mNumberOfResults++; + mResults[name].push_back(duration); } void TimerResults::reset() @@ -87,13 +90,14 @@ void TimerResults::reset() mResults.clear(); } -Timer::Timer(std::string str, ShowTime showtimeMode, TimerResultsIntf* timerResults, Type type) +Timer::Timer(std::string str, TimerResultsIntf* timerResults) : mName(std::move(str)) - , mMode(showtimeMode) - , mType(type) - , mStart(Clock::now()) , mResults(timerResults) -{} +{ + if (!mResults) + return; + mStart = Clock::now(); +} Timer::~Timer() { @@ -102,29 +106,16 @@ Timer::~Timer() void Timer::stop() { - if (mMode == ShowTime::NONE) - return; - if (mType == Type::OVERALL && mMode != ShowTime::TOP5_SUMMARY && mMode != ShowTime::SUMMARY) { - mMode = ShowTime::NONE; - return; - } - if (mType == Type::FILE && mMode != ShowTime::TOP5_FILE && mMode != ShowTime::FILE && mMode != ShowTime::FILE_TOTAL) { - mMode = ShowTime::NONE; + if (mStart == TimePoint{}) return; - } - if (mStart != TimePoint{}) { - if (!mResults) { - assert(false); - } - else { - auto diff = std::chrono::duration_cast(Clock::now() - mStart); - mResults->addResults(mName, diff); - } - } - mMode = ShowTime::NONE; // prevent multiple stops + + const auto diff = std::chrono::duration_cast(Clock::now() - mStart); + mResults->addResults(mName, diff); + + mStart = TimePoint{}; // prevent multiple stops } -std::string TimerResultsData::durationToString(std::chrono::milliseconds duration) +static std::string durationToString(std::chrono::milliseconds duration) { // Extract hours auto hours = std::chrono::duration_cast(duration); @@ -148,3 +139,21 @@ std::string TimerResultsData::durationToString(std::chrono::milliseconds duratio secondsStr.resize(pos + 4); // keep three decimal return (ellapsedTime + secondsStr + "s"); } + +OneShotTimer::OneShotTimer(std::string name) +{ + class MyResults : public TimerResultsIntf + { + private: + void addResults(const std::string &name, std::chrono::milliseconds duration) override + { + std::lock_guard l(stdCoutLock); + + // TODO: do not use std::cout directly + std::cout << name << ": " << durationToString(duration) << std::endl; + } + }; + + mResults.reset(new MyResults); + mTimer.reset(new Timer(std::move(name), mResults.get())); +} diff --git a/lib/timer.h b/lib/timer.h index e0c4ee2d45f..1ee7fde52f9 100644 --- a/lib/timer.h +++ b/lib/timer.h @@ -23,50 +23,38 @@ #include "config.h" #include -#include +#include +#include #include +#include #include #include #include - -enum class ShowTime : std::uint8_t { - NONE, - FILE, - FILE_TOTAL, - SUMMARY, - TOP5_SUMMARY, - TOP5_FILE -}; +#include class CPPCHECKLIB TimerResultsIntf { public: virtual ~TimerResultsIntf() = default; - virtual void addResults(const std::string& timerName, std::chrono::milliseconds duation) = 0; -}; - -struct TimerResultsData { - std::chrono::milliseconds mDuration; - long mNumberOfResults{}; - - std::chrono::duration getSeconds() const { - return std::chrono::duration_cast>(mDuration); - } - - static std::string durationToString(std::chrono::milliseconds duration); + virtual void addResults(const std::string& name, std::chrono::milliseconds duration) = 0; }; class CPPCHECKLIB WARN_UNUSED TimerResults : public TimerResultsIntf { public: TimerResults() = default; - void showResults(ShowTime mode, bool metrics = true, bool format = false) const; - void addResults(const std::string& str, std::chrono::milliseconds duration) override; + void showResults(size_t max_results = std::numeric_limits::max(), bool metrics = true) const; + void addResults(const std::string& name, std::chrono::milliseconds duration) override; void reset(); -private: - std::map mResults; + std::map> getResults() const { + std::lock_guard l(mResultsSync); + return mResults; + } + +protected: + std::map> mResults; mutable std::mutex mResultsSync; }; @@ -75,13 +63,7 @@ class CPPCHECKLIB Timer { using Clock = std::chrono::high_resolution_clock; using TimePoint = std::chrono::time_point; - enum class Type : std::uint8_t { - FILE, - OVERALL, - OTHER - }; - - Timer(std::string str, ShowTime showtimeMode, TimerResultsIntf* timerResults = nullptr, Type type = Type::OTHER); + explicit Timer(std::string str, TimerResultsIntf* timerResults = nullptr); ~Timer(); Timer(const Timer&) = delete; @@ -90,18 +72,25 @@ class CPPCHECKLIB Timer { void stop(); template - static void run(std::string str, ShowTime showtimeMode, TimerResultsIntf* timerResults, const TFunc& f) { - Timer t(std::move(str), showtimeMode, timerResults); + static void run(std::string str, TimerResultsIntf* timerResults, const TFunc& f) { + Timer t(std::move(str), timerResults); f(); } private: const std::string mName; - ShowTime mMode{}; - Type mType{}; TimePoint mStart; TimerResultsIntf* mResults{}; }; +class CPPCHECKLIB OneShotTimer +{ +public: + explicit OneShotTimer(std::string name); +private: + std::unique_ptr mResults; + std::unique_ptr mTimer; +}; + //--------------------------------------------------------------------------- #endif // timerH diff --git a/lib/token.cpp b/lib/token.cpp index b11e12bf0a7..eac69decee7 100644 --- a/lib/token.cpp +++ b/lib/token.cpp @@ -204,16 +204,15 @@ static const std::unordered_set stdTypes = { "bool" , "int" , "long" , "short" - , "size_t" , "void" , "wchar_t" , "signed" , "unsigned" }; -bool Token::isStandardType(const std::string& str) +bool Token::isStandardType(const std::string& s) { - return stdTypes.find(str) != stdTypes.end(); + return stdTypes.find(s) != stdTypes.end(); } void Token::update_property_isStandardType() @@ -614,7 +613,6 @@ bool Token::simpleMatch(const Token *tok, const char pattern[], size_t pattern_l return false; // shortcut const char *current = pattern; const char *end = pattern + pattern_len; - // cppcheck-suppress shadowFunction - TODO: fix this const char *next = static_cast(std::memchr(pattern, ' ', pattern_len)); if (!next) next = end; @@ -637,31 +635,31 @@ bool Token::simpleMatch(const Token *tok, const char pattern[], size_t pattern_l return true; } -bool Token::firstWordEquals(const char *str, const char *word) +bool Token::firstWordEquals(const char *s, const char *word) { for (;;) { - if (*str != *word) - return (*str == ' ' && *word == 0); - if (*str == 0) + if (*s != *word) + return (*s == ' ' && *word == 0); + if (*s == 0) break; - ++str; + ++s; ++word; } return true; } -const char *Token::chrInFirstWord(const char *str, char c) +const char *Token::chrInFirstWord(const char *s, char c) { for (;;) { - if (*str == ' ' || *str == 0) + if (*s == ' ' || *s == 0) return nullptr; - if (*str == c) - return str; + if (*s == c) + return s; - ++str; + ++s; } } @@ -769,7 +767,6 @@ nonneg int Token::getStrArraySize(const Token *tok) { assert(tok != nullptr); assert(tok->tokType() == eString); - // cppcheck-suppress shadowFunction - TODO: fix this const std::string str(getStringLiteral(tok->str())); int sizeofstring = 1; for (int i = 0; i < static_cast(str.size()); i++) { @@ -1014,41 +1011,41 @@ Token *Token::findsimplematch(Token * const startTok, const char pattern[], size } template )> -static T *findmatchImpl(T * const startTok, const char pattern[], const nonneg int varId) +static T *findmatchImpl(T * const startTok, const char pattern[], const nonneg int varid) { for (T* tok = startTok; tok; tok = tok->next()) { - if (Token::Match(tok, pattern, varId)) + if (Token::Match(tok, pattern, varid)) return tok; } return nullptr; } -const Token *Token::findmatch(const Token * const startTok, const char pattern[], const nonneg int varId) +const Token *Token::findmatch(const Token * const startTok, const char pattern[], const nonneg int varid) { - return findmatchImpl(startTok, pattern, varId); + return findmatchImpl(startTok, pattern, varid); } -Token *Token::findmatch(Token * const startTok, const char pattern[], const nonneg int varId) { - return findmatchImpl(startTok, pattern, varId); +Token *Token::findmatch(Token * const startTok, const char pattern[], const nonneg int varid) { + return findmatchImpl(startTok, pattern, varid); } template )> -static T *findmatchImpl(T * const startTok, const char pattern[], const Token * const end, const nonneg int varId) +static T *findmatchImpl(T * const startTok, const char pattern[], const Token * const end, const nonneg int varid) { for (T* tok = startTok; tok && tok != end; tok = tok->next()) { - if (Token::Match(tok, pattern, varId)) + if (Token::Match(tok, pattern, varid)) return tok; } return nullptr; } -const Token *Token::findmatch(const Token * const startTok, const char pattern[], const Token * const end, const nonneg int varId) +const Token *Token::findmatch(const Token * const startTok, const char pattern[], const Token * const end, const nonneg int varid) { - return findmatchImpl(startTok, pattern, end, varId); + return findmatchImpl(startTok, pattern, end, varid); } -Token *Token::findmatch(Token * const startTok, const char pattern[], const Token * const end, const nonneg int varId) { - return findmatchImpl(startTok, pattern, end, varId); +Token *Token::findmatch(Token * const startTok, const char pattern[], const Token * const end, const nonneg int varid) { + return findmatchImpl(startTok, pattern, end, varid); } void Token::function(const Function *f) @@ -1333,10 +1330,10 @@ std::string Token::stringifyList(const stringifyOptions& options, const std::vec std::string ret; - unsigned int lineNumber = mImpl->mLineNumber - (options.linenumbers ? 1U : 0U); + nonneg int lineNumber = mImpl->mLineNumber - (options.linenumbers ? 1 : 0); // cppcheck-suppress shadowFunction - TODO: fix this - unsigned int fileIndex = options.files ? ~0U : mImpl->mFileIndex; - std::map lineNumbers; + nonneg int fileIndex = options.files ? ~0U : mImpl->mFileIndex; + std::map lineNumbers; for (const Token *tok = this; tok != end; tok = tok->next()) { assert(tok && "end precedes token"); if (!tok) @@ -1816,7 +1813,7 @@ void Token::printValueFlow(const std::vector& files, bool xml, std: outs += " valueType() && tok->valueType()->sign == ValueType::UNSIGNED) { + if (tok->valueType() && tok->valueType()->sign == ValueType::UNSIGNED && value.toString() != "!<=-1") { outs += "intvalue=\""; outs += MathLib::toString(static_cast(value.intvalue)); outs += '\"'; @@ -2359,11 +2356,9 @@ const ::Type* Token::typeOf(const Token* tok, const Token** typeTok) if (tok->valueType() && tok->valueType()->typeScope && tok->valueType()->typeScope->definedType) return tok->valueType()->typeScope->definedType; if (Token::simpleMatch(tok, "return")) { - // cppcheck-suppress shadowFunction - TODO: fix this const Scope *scope = tok->scope(); if (!scope) return nullptr; - // cppcheck-suppress shadowFunction - TODO: fix this const Function *function = scope->function; if (!function) return nullptr; @@ -2438,6 +2433,7 @@ std::pair Token::typeDecl(const Token* tok, bool poi varTok = varTok->next(); while (Token::Match(varTok, "%name% ::")) varTok = varTok->tokAt(2); + assert(varTok != tok); std::pair r = typeDecl(varTok); if (r.first) return r; @@ -2473,18 +2469,15 @@ std::pair Token::typeDecl(const Token* tok, bool poi return {var->typeStartToken(), var->typeEndToken()->next()}; } if (Token::simpleMatch(tok, "return")) { - // cppcheck-suppress shadowFunction - TODO: fix this const Scope* scope = tok->scope(); if (!scope) return {}; - // cppcheck-suppress shadowFunction - TODO: fix this const Function* function = scope->function; if (!function) return {}; return { function->retDef, function->returnDefEnd() }; } if (tok->previous() && tok->previous()->function()) { - // cppcheck-suppress shadowFunction - TODO: fix this const Function *function = tok->previous()->function(); return {function->retDef, function->returnDefEnd()}; } @@ -2661,26 +2654,26 @@ Token::Impl::~Impl() } } -void Token::Impl::setCppcheckAttribute(CppcheckAttributesType type, MathLib::bigint value) +void Token::Impl::setCppcheckAttribute(CppcheckAttributesType attrType, MathLib::bigint value) { CppcheckAttributes *attr = mCppcheckAttributes; - while (attr && attr->type != type) + while (attr && attr->type != attrType) attr = attr->next; if (attr) attr->value = value; else { attr = new CppcheckAttributes; - attr->type = type; + attr->type = attrType; attr->value = value; attr->next = mCppcheckAttributes; mCppcheckAttributes = attr; } } -bool Token::Impl::getCppcheckAttribute(CppcheckAttributesType type, MathLib::bigint &value) const +bool Token::Impl::getCppcheckAttribute(CppcheckAttributesType attrType, MathLib::bigint &value) const { const CppcheckAttributes *attr = mCppcheckAttributes; - while (attr && attr->type != type) + while (attr && attr->type != attrType) attr = attr->next; if (attr) value = attr->value; diff --git a/lib/token.h b/lib/token.h index 733887207ad..8e96c405e97 100644 --- a/lib/token.h +++ b/lib/token.h @@ -60,7 +60,7 @@ struct ReferenceToken; struct ScopeInfo2 { ScopeInfo2(std::string name_, const Token *bodyEnd_, std::set usingNamespaces_ = std::set()) : name(std::move(name_)), bodyEnd(bodyEnd_), usingNamespaces(std::move(usingNamespaces_)) {} std::string name; - const Token* const bodyEnd{}; + const Token* bodyEnd{}; std::set usingNamespaces; }; @@ -174,8 +174,8 @@ class CPPCHECKLIB Token { std::int8_t mMutableExpr{-1}; - void setCppcheckAttribute(CppcheckAttributesType type, MathLib::bigint value); - bool getCppcheckAttribute(CppcheckAttributesType type, MathLib::bigint &value) const; + void setCppcheckAttribute(CppcheckAttributesType attrType, MathLib::bigint value); + bool getCppcheckAttribute(CppcheckAttributesType attrType, MathLib::bigint &value) const; Impl() = default; ~Impl(); @@ -249,35 +249,35 @@ class CPPCHECKLIB Token { * For example index 1 would return next token, and 2 * would return next from that one. */ - const Token *tokAt(int index) const + const Token *tokAt(int idx) const { - return tokAtImpl(this, index); + return tokAtImpl(this, idx); } - Token *tokAt(int index) + Token *tokAt(int idx) { - return tokAtImpl(this, index); + return tokAtImpl(this, idx); } /** * @return the link to the token in given index, related to this token. * For example index 1 would return the link to next token. */ - const Token *linkAt(int index) const + const Token *linkAt(int idx) const { - return linkAtImpl(this, index); + return linkAtImpl(this, idx); } - Token *linkAt(int index) + Token *linkAt(int idx) { - return linkAtImpl(this, index); + return linkAtImpl(this, idx); } /** * @return String of the token in given index, related to this token. * If that token does not exist, an empty string is being returned. */ - const std::string &strAt(int index) const + const std::string &strAt(int idx) const { - const Token *tok = this->tokAt(index); + const Token *tok = this->tokAt(idx); return tok ? tok->mStr : mEmptyString; } @@ -604,11 +604,11 @@ class CPPCHECKLIB Token { bool hasAttributeCleanup() const { return !mImpl->mAttributeCleanup.empty(); } - void setCppcheckAttribute(CppcheckAttributesType type, MathLib::bigint value) { - mImpl->setCppcheckAttribute(type, value); + void setCppcheckAttribute(CppcheckAttributesType attrType, MathLib::bigint value) { + mImpl->setCppcheckAttribute(attrType, value); } - bool getCppcheckAttribute(CppcheckAttributesType type, MathLib::bigint &value) const { - return mImpl->getCppcheckAttribute(type, value); + bool getCppcheckAttribute(CppcheckAttributesType attrType, MathLib::bigint &value) const { + return mImpl->getCppcheckAttribute(attrType, value); } // cppcheck-suppress unusedFunction bool hasCppcheckAttributes() const { @@ -732,11 +732,11 @@ class CPPCHECKLIB Token { setFlag(fIsTemplate, b); } - bool isSimplifiedScope() const { - return getFlag(fIsSimplifedScope); + bool isSimplifiedIfInitStmt() const { + return getFlag(fIsSimplifiedIfInitStmt); } - void isSimplifiedScope(bool b) { - setFlag(fIsSimplifedScope, b); + void isSimplifiedIfInitStmt(bool b) { + setFlag(fIsSimplifiedIfInitStmt, b); } bool isFinalType() const { @@ -767,7 +767,14 @@ class CPPCHECKLIB Token { setFlag(fIsAnonymous, b); } - // cppcheck-suppress unusedFunction + bool isInsertedBrace() const { + return getFlag(fIsInsertedBrace); + } + Token* isInsertedBrace(bool b) { + setFlag(fIsInsertedBrace, b); + return this; + } + bool isBitfield() const { return mImpl->mBits >= 0; } @@ -872,8 +879,8 @@ class CPPCHECKLIB Token { } static const Token *findsimplematch(const Token * startTok, const char pattern[], size_t pattern_len, const Token * end); - static const Token *findmatch(const Token * startTok, const char pattern[], nonneg int varId = 0); - static const Token *findmatch(const Token * startTok, const char pattern[], const Token * end, nonneg int varId = 0); + static const Token *findmatch(const Token * startTok, const char pattern[], nonneg int varid = 0); + static const Token *findmatch(const Token * startTok, const char pattern[], const Token * end, nonneg int varid = 0); template static Token *findsimplematch(Token * const startTok, const char (&pattern)[count]) { @@ -886,20 +893,20 @@ class CPPCHECKLIB Token { } static Token *findsimplematch(Token * startTok, const char pattern[], size_t pattern_len, const Token * end); - static Token *findmatch(Token * startTok, const char pattern[], nonneg int varId = 0); - static Token *findmatch(Token * startTok, const char pattern[], const Token * end, nonneg int varId = 0); + static Token *findmatch(Token * startTok, const char pattern[], nonneg int varid = 0); + static Token *findmatch(Token * startTok, const char pattern[], const Token * end, nonneg int varid = 0); private: template )> - static T *tokAtImpl(T *tok, int index) + static T *tokAtImpl(T *tok, int idx) { - while (index > 0 && tok) { + while (idx > 0 && tok) { tok = tok->next(); - --index; + --idx; } - while (index < 0 && tok) { + while (idx < 0 && tok) { tok = tok->previous(); - ++index; + ++idx; } return tok; } @@ -908,9 +915,9 @@ class CPPCHECKLIB Token { * @throws InternalError thrown if index is out of range */ template )> - static T *linkAtImpl(T *thisTok, int index) + static T *linkAtImpl(T *thisTok, int idx) { - T *tok = thisTok->tokAt(index); + T *tok = thisTok->tokAt(idx); if (!tok) { throw InternalError(thisTok, "Internal error. Token::linkAt called with index outside the tokens range."); } @@ -1267,7 +1274,7 @@ class CPPCHECKLIB Token { static std::string typeStr(const Token* tok); - static bool isStandardType(const std::string& str); + static bool isStandardType(const std::string& s); /** * @return a pointer to the Enumerator associated with this token. @@ -1439,17 +1446,17 @@ class CPPCHECKLIB Token { /** * Works almost like strcmp() except returns only true or false and - * if str has empty space ' ' character, that character is handled + * if s has empty space ' ' character, that character is handled * as if it were '\\0' */ - static bool firstWordEquals(const char *str, const char *word); + static bool firstWordEquals(const char *s, const char *word); /** * Works almost like strchr() except - * if str has empty space ' ' character, that character is handled + * if s has empty space ' ' character, that character is handled * as if it were '\\0' */ - static const char *chrInFirstWord(const char *str, char c); + static const char *chrInFirstWord(const char *s, char c); RET_NONNULL Token* insertToken(const std::string& tokenStr, bool prepend); RET_NONNULL Token* insertToken(const std::string& tokenStr, const std::string& originalNameStr, bool prepend); @@ -1498,7 +1505,7 @@ class CPPCHECKLIB Token { fIsImplicitInt = (1ULL << 33), // Is "int" token implicitly added? fIsInline = (1ULL << 34), // Is this a inline type fIsTemplate = (1ULL << 35), - fIsSimplifedScope = (1ULL << 36), // scope added when simplifying e.g. if (int i = ...; ...) + fIsSimplifiedIfInitStmt = (1ULL << 36), // simplified if/switch/while init statement e.g. if (int i = ...; ...) => { int i = ...; if (..) .. } fIsRemovedVoidParameter = (1ULL << 37), // A void function parameter has been removed fIsIncompleteConstant = (1ULL << 38), fIsRestrict = (1ULL << 39), // Is this a restrict pointer type @@ -1508,6 +1515,7 @@ class CPPCHECKLIB Token { fIsInitComma = (1ULL << 43), // Is this comma located inside some {..}. i.e: {1,2,3,4} fIsInitBracket = (1ULL << 44), // Is this bracket used as a part of variable initialization i.e: int a{5}, b(2); fIsAnonymous = (1ULL << 45), // Is this a token added for an unnamed member + fIsInsertedBrace = (1ULL << 46), // brace added when simplifying e.g. if (x) f(); => if (x) { f(); } }; enum : std::uint8_t { diff --git a/lib/tokenize.cpp b/lib/tokenize.cpp index 8e76ff6a422..bbcd1bd6b89 100644 --- a/lib/tokenize.cpp +++ b/lib/tokenize.cpp @@ -70,10 +70,10 @@ namespace { VarIdScopeInfo(bool isExecutable, bool isStructInit, bool isEnum, nonneg int startVarid) : isExecutable(isExecutable), isStructInit(isStructInit), isEnum(isEnum), startVarid(startVarid) {} - const bool isExecutable{}; - const bool isStructInit{}; - const bool isEnum{}; - const nonneg int startVarid{}; + bool isExecutable{}; + bool isStructInit{}; + bool isEnum{}; + nonneg int startVarid{}; }; } @@ -549,9 +549,9 @@ namespace { private: Token* mTypedefToken; // The "typedef" token Token* mEndToken{nullptr}; // Semicolon - std::pair mRangeType; - std::pair mRangeTypeQualifiers; - std::pair mRangeAfterVar; + std::pair mRangeType; + std::pair mRangeTypeQualifiers; + std::pair mRangeAfterVar; Token* mNameToken{nullptr}; bool mFail = false; bool mReplaceFailed = false; @@ -563,28 +563,50 @@ namespace { if (Token::simpleMatch(start, "typename")) start = start->next(); + const auto checkForRecursion = [this]() { + if (Token::Match(mTypedefToken, "typedef %name% %name% ;")) + return; + int nBraces = 0; + for (const Token *tok = mTypedefToken; tok != mEndToken; tok = tok->next()) { + if (tok->str() == "{") + ++nBraces; + else if (tok->str() == "}") + --nBraces; + if (nBraces > 0) + continue; + if (tok == mNameToken) + continue; + if (tok->str() != mNameToken->str()) + continue; + if (Token::Match(tok->previous(), "struct|class|enum|union")) + continue; + throw InternalError(tok, "recursive typedef encountered"); + } + }; + // TODO handle unnamed structs etc if (Token::Match(start, "const| enum|struct|union|class %name%| {")) { - const std::pair rangeBefore(start, Token::findsimplematch(start, "{")); + const std::pair rangeBefore(start, Token::findsimplematch(start, "{")); // find typedef name token - Token* nameToken = rangeBefore.second->link()->next(); - while (Token::Match(nameToken, "%name%|* %name%|*")) - nameToken = nameToken->next(); - const std::pair rangeQualifiers(rangeBefore.second->link()->next(), nameToken); + Token* nameTok = rangeBefore.second->link()->next(); + while (Token::Match(nameTok, "%name%|* %name%|*")) + nameTok = nameTok->next(); + const std::pair rangeQualifiers(rangeBefore.second->link()->next(), nameTok); - if (Token::Match(nameToken, "%name% ;")) { + if (Token::Match(nameTok, "%name% ;")) { if (Token::Match(rangeBefore.second->previous(), "enum|struct|union|class {")) - rangeBefore.second->previous()->insertToken(nameToken->str()); + rangeBefore.second->previous()->insertToken(nameTok->str()); mRangeType = rangeBefore; mRangeTypeQualifiers = rangeQualifiers; Token* typeName = rangeBefore.second->previous(); if (typeName->isKeyword()) { // TODO typeName->insertToken("T:" + std::to_string(num++)); - typeName->insertToken(nameToken->str()); + typeName->insertToken(nameTok->str()); } - mNameToken = nameToken; - mEndToken = nameToken->next(); + mNameToken = nameTok; + mEndToken = nameTok->next(); + checkForRecursion(); return; } } @@ -595,6 +617,7 @@ namespace { mRangeType.second = type; mNameToken = type; mEndToken = mNameToken->next(); + checkForRecursion(); return; } if (type != start && Token::Match(type, "%name% [")) { @@ -609,6 +632,7 @@ namespace { mEndToken = end->next(); mRangeAfterVar.first = mNameToken->next(); mRangeAfterVar.second = mEndToken; + checkForRecursion(); return; } if (Token::Match(type->next(), "( * const| %name% ) (") && Token::simpleMatch(type->linkAt(1)->linkAt(1), ") ;")) { @@ -618,6 +642,7 @@ namespace { mRangeType.second = mNameToken; mRangeAfterVar.first = mNameToken->next(); mRangeAfterVar.second = mEndToken; + checkForRecursion(); return; } if (type != start && Token::Match(type, "%name% ( !!(") && Token::simpleMatch(type->linkAt(1), ") ;") && !type->isStandardType()) { @@ -627,6 +652,7 @@ namespace { mRangeType.second = type; mRangeAfterVar.first = mNameToken->next(); mRangeAfterVar.second = mEndToken; + checkForRecursion(); return; } } @@ -723,7 +749,7 @@ namespace { // Special handling of function pointer cast if (isFunctionPointer && isCast(tok->previous())) { tok->insertToken("*"); - Token* const tok_1 = insertTokens(tok, std::pair(mRangeType.first, mNameToken->linkAt(1))); + Token* const tok_1 = insertTokens(tok, std::pair(mRangeType.first, mNameToken->linkAt(1))); tok_1->originalName(originalname); tok->deleteThis(); return; @@ -835,7 +861,10 @@ namespace { } if (isFunctionPointer) { - if (Token::Match(after, "( * %name% ) (")) + // Keep the argument list for a parenthesized pointer declarator: fp *(name). + if (after->previous() != tok3 && Token::Match(after->previous(), "* ( %name% ) ;|,|=")) + after = after->link()->next(); + else if (Token::Match(after, "( * %name% ) (")) after = after->link()->linkAt(1)->next(); else if (after->str() == "(") { useAfterVarRange = false; @@ -998,7 +1027,7 @@ namespace { return false; } - static Token* insertTokens(Token* to, std::pair range) { + static Token* insertTokens(Token* to, std::pair range) { for (const Token* from = range.first; from != range.second; from = from->next()) { to->insertToken(from->str()); to->next()->column(to->column()); @@ -1025,13 +1054,20 @@ namespace { } bool Tokenizer::isFunctionPointer(const Token* tok) { - return Token::Match(tok, "%name% ) ("); + if (!Token::Match(tok, "%name%")) + return false; + tok = tok->next(); + while (Token::Match(tok, "[")) + tok = tok->link()->next(); + return Token::simpleMatch(tok, ") ("); } -static bool matchCurrentType(const std::string& typeStr, const std::map& types) +static bool matchCurrentType(const Token* tok, std::map& types) { + if (tok->isC()) + return false; return std::any_of(types.begin(), types.end(), [&](const std::pair& element) { - return typeStr == element.second; + return tok->str() == element.second; }); } @@ -1086,7 +1122,7 @@ void Tokenizer::simplifyTypedef() } auto it = typedefs.find(tok->str()); - if (it != typedefs.end() && it->second.canReplace(tok) && !matchCurrentType(tok->str(), inType)) { + if (it != typedefs.end() && it->second.canReplace(tok) && !matchCurrentType(tok, inType)) { std::set r; std::string originalname; while (it != typedefs.end() && r.insert(tok->str()).second) { @@ -1113,14 +1149,18 @@ void Tokenizer::simplifyTypedef() syntaxError(t.second.getTypedefToken()); } else { const Token* const typedefToken = t.second.getTypedefToken(); + const Token* const nameToken = t.second.nameToken(); TypedefInfo typedefInfo; typedefInfo.name = t.second.name(); - typedefInfo.filename = list.file(typedefToken); - typedefInfo.lineNumber = typedefToken->linenr(); - typedefInfo.column = typedefToken->column(); - if (Token::Match(typedefToken->next(), "struct|enum|class|union %name% {") && typedefToken->strAt(2) == typedefInfo.name) { - typedefInfo.tagLine = typedefToken->tokAt(2)->linenr(); - typedefInfo.tagColumn = typedefToken->tokAt(2)->column(); + typedefInfo.filename = list.file(nameToken); + typedefInfo.lineNumber = nameToken->linenr(); + typedefInfo.column = nameToken->column(); + if (Token::Match(typedefToken->next(), "struct|enum|class|union %name% {")) { + typedefInfo.originalName = typedefToken->strAt(2); + if (typedefToken->strAt(2) == typedefInfo.name) { + typedefInfo.tagLine = typedefToken->tokAt(2)->linenr(); + typedefInfo.tagColumn = typedefToken->tokAt(2)->column(); + } } typedefInfo.used = t.second.isUsed(); typedefInfo.isFunctionPointer = isFunctionPointer(t.second.nameToken()); @@ -1150,21 +1190,23 @@ void Tokenizer::simplifyTypedef() simplifyTypedefCpp(); } -static Token* simplifyTypedefCopyTokens(Token* to, const Token* fromStart, const Token* toEnd, const Token* location) { +static Token* simplifyTypedefCopyTokens(Token* to, const Token* fromStart, const Token* toEnd, const Token* location, const std::string &originalName) { Token* ret = TokenList::copyTokens(to, fromStart, toEnd); for (Token* tok = to->next(); tok != ret->next(); tok = tok->next()) { tok->linenr(location->linenr()); tok->column(location->column()); tok->isSimplifiedTypedef(true); + tok->originalName(originalName); } return ret; } -static Token* simplifyTypedefInsertToken(Token* tok, const std::string& str, const Token* location) { +static Token* simplifyTypedefInsertToken(Token* tok, const std::string& str, const Token* location, const std::string &originalName) { tok = tok->insertToken(str); tok->linenr(location->linenr()); tok->column(location->column()); tok->isSimplifiedTypedef(true); + tok->originalName(originalName); return tok; } @@ -2000,12 +2042,13 @@ void Tokenizer::simplifyTypedefCpp() // start substituting at the typedef name by replacing it with the type const Token* location = tok2; + const std::string originalName = tok2->str(); for (Token* tok3 = typeStart; tok3 && (tok3->str() != ";"); tok3 = tok3->next()) tok3->isSimplifiedTypedef(true); if (isPointerTypeCall) { tok2->deleteThis(); - tok2 = simplifyTypedefInsertToken(tok2, "0", location); - simplifyTypedefInsertToken(tok2->next(), "0", location); + tok2 = simplifyTypedefInsertToken(tok2, "0", location, originalName); + simplifyTypedefInsertToken(tok2->next(), "0", location, originalName); } if (Token::Match(tok2->tokAt(-1), "class|struct|union") && tok2->strAt(-1) == typeStart->str()) tok2->deletePrevious(); @@ -2014,6 +2057,7 @@ void Tokenizer::simplifyTypedefCpp() tok2->previous()->str("typedef"); tok2->insertToken(tok2->str()); } + tok2->originalName(originalName); tok2->str(typeStart->str()); // restore qualification if it was removed @@ -2022,12 +2066,12 @@ void Tokenizer::simplifyTypedefCpp() tok2 = tok2->previous(); if (globalScope) { - tok2 = simplifyTypedefInsertToken(tok2, "::", location); + tok2 = simplifyTypedefInsertToken(tok2, "::", location, originalName); } for (std::size_t i = classLevel; i < spaceInfo.size(); ++i) { - tok2 = simplifyTypedefInsertToken(tok2, spaceInfo[i].className, location); - tok2 = simplifyTypedefInsertToken(tok2, "::", location); + tok2 = simplifyTypedefInsertToken(tok2, spaceInfo[i].className, location, originalName); + tok2 = simplifyTypedefInsertToken(tok2, "::", location, originalName); } } @@ -2044,11 +2088,11 @@ void Tokenizer::simplifyTypedefCpp() std::string::size_type spaceIdx = 0; std::string::size_type startIdx = 0; while ((spaceIdx = removed1.find(' ', startIdx)) != std::string::npos) { - simplifyTypedefInsertToken(tok2->previous(), removed1.substr(startIdx, spaceIdx - startIdx), location); + simplifyTypedefInsertToken(tok2->previous(), removed1.substr(startIdx, spaceIdx - startIdx), location, originalName); startIdx = spaceIdx + 1; } - simplifyTypedefInsertToken(tok2->previous(), removed1.substr(startIdx), location); - simplifyTypedefInsertToken(tok2->previous(), "::", location); + simplifyTypedefInsertToken(tok2->previous(), removed1.substr(startIdx), location, originalName); + simplifyTypedefInsertToken(tok2->previous(), "::", location, originalName); break; } idx = removed1.rfind(" ::"); @@ -2060,23 +2104,23 @@ void Tokenizer::simplifyTypedefCpp() } Token* constTok = Token::simpleMatch(tok2->previous(), "const") ? tok2->previous() : nullptr; // add remainder of type - tok2 = simplifyTypedefCopyTokens(tok2, typeStart->next(), typeEnd, location); + tok2 = simplifyTypedefCopyTokens(tok2, typeStart->next(), typeEnd, location, originalName); if (!pointers.empty()) { for (const std::string &p : pointers) // cppcheck-suppress useStlAlgorithm - tok2 = simplifyTypedefInsertToken(tok2, p, location); + tok2 = simplifyTypedefInsertToken(tok2, p, location, originalName); if (constTok && !functionPtr) { - tok2 = simplifyTypedefInsertToken(tok2, "const", location); + tok2 = simplifyTypedefInsertToken(tok2, "const", location, originalName); constTok->deleteThis(); location = constTok; } } if (funcStart && funcEnd) { - tok2 = simplifyTypedefInsertToken(tok2, "(", location); + tok2 = simplifyTypedefInsertToken(tok2, "(", location, originalName); Token *paren = tok2; - tok2 = simplifyTypedefCopyTokens(tok2, funcStart, funcEnd, location); + tok2 = simplifyTypedefCopyTokens(tok2, funcStart, funcEnd, location, originalName); if (!inCast) tok2 = processFunc(tok2, inOperator); @@ -2087,17 +2131,17 @@ void Tokenizer::simplifyTypedefCpp() while (Token::Match(tok2, "%name%|] [")) tok2 = tok2->linkAt(1); - tok2 = simplifyTypedefInsertToken(tok2, ")", location); + tok2 = simplifyTypedefInsertToken(tok2, ")", location, originalName); Token::createMutualLinks(tok2, paren); - tok2 = simplifyTypedefCopyTokens(tok2, argStart, argEnd, location); + tok2 = simplifyTypedefCopyTokens(tok2, argStart, argEnd, location, originalName); if (specStart) { Token *spec = specStart; - tok2 = simplifyTypedefInsertToken(tok2, spec->str(), location); + tok2 = simplifyTypedefInsertToken(tok2, spec->str(), location, originalName); while (spec != specEnd) { spec = spec->next(); - tok2 = simplifyTypedefInsertToken(tok2, spec->str(), location); + tok2 = simplifyTypedefInsertToken(tok2, spec->str(), location, originalName); } } } @@ -2109,20 +2153,20 @@ void Tokenizer::simplifyTypedefCpp() if (!inTemplate && function && tok2->next() && tok2->strAt(1) != "*") needParen = false; if (needParen) { - tok2 = simplifyTypedefInsertToken(tok2, "(", location); + tok2 = simplifyTypedefInsertToken(tok2, "(", location, originalName); } Token *tok3 = tok2; if (namespaceStart) { const Token *tok4 = namespaceStart; while (tok4 != namespaceEnd) { - tok2 = simplifyTypedefInsertToken(tok2, tok4->str(), location); + tok2 = simplifyTypedefInsertToken(tok2, tok4->str(), location, originalName); tok4 = tok4->next(); } - tok2 = simplifyTypedefInsertToken(tok2, namespaceEnd->str(), location); + tok2 = simplifyTypedefInsertToken(tok2, namespaceEnd->str(), location, originalName); } if (functionPtr) { - tok2 = simplifyTypedefInsertToken(tok2, "*", location); + tok2 = simplifyTypedefInsertToken(tok2, "*", location, originalName); } if (!inCast) @@ -2132,35 +2176,35 @@ void Tokenizer::simplifyTypedefCpp() if (!tok2) syntaxError(nullptr); - tok2 = simplifyTypedefInsertToken(tok2, ")", location); + tok2 = simplifyTypedefInsertToken(tok2, ")", location, originalName); Token::createMutualLinks(tok2, tok3); } if (!tok2) syntaxError(nullptr); - tok2 = simplifyTypedefCopyTokens(tok2, argStart, argEnd, location); + tok2 = simplifyTypedefCopyTokens(tok2, argStart, argEnd, location, originalName); if (inTemplate) { tok2 = tok2->next(); } if (specStart) { Token *spec = specStart; - tok2 = simplifyTypedefInsertToken(tok2, spec->str(), location); + tok2 = simplifyTypedefInsertToken(tok2, spec->str(), location, originalName); while (spec != specEnd) { spec = spec->next(); - tok2 = simplifyTypedefInsertToken(tok2, spec->str(), location); + tok2 = simplifyTypedefInsertToken(tok2, spec->str(), location, originalName); } } } else if (functionRetFuncPtr || functionPtrRetFuncPtr) { - tok2 = simplifyTypedefInsertToken(tok2, "(", location); + tok2 = simplifyTypedefInsertToken(tok2, "(", location, originalName); Token *tok3 = tok2; - tok2 = simplifyTypedefInsertToken(tok2, "*", location); + tok2 = simplifyTypedefInsertToken(tok2, "*", location, originalName); Token * tok4 = nullptr; if (functionPtrRetFuncPtr) { - tok2 = simplifyTypedefInsertToken(tok2, "(", location); + tok2 = simplifyTypedefInsertToken(tok2, "(", location, originalName); tok4 = tok2; - tok2 = simplifyTypedefInsertToken(tok2, "*", location); + tok2 = simplifyTypedefInsertToken(tok2, "*", location, originalName); } // skip over variable name if there @@ -2173,24 +2217,24 @@ void Tokenizer::simplifyTypedefCpp() } if (tok4 && functionPtrRetFuncPtr) { - tok2 = simplifyTypedefInsertToken(tok2,")", location); + tok2 = simplifyTypedefInsertToken(tok2,")", location, originalName); Token::createMutualLinks(tok2, tok4); } - tok2 = simplifyTypedefCopyTokens(tok2, argStart, argEnd, location); + tok2 = simplifyTypedefCopyTokens(tok2, argStart, argEnd, location, originalName); - tok2 = simplifyTypedefInsertToken(tok2, ")", location); + tok2 = simplifyTypedefInsertToken(tok2, ")", location, originalName); Token::createMutualLinks(tok2, tok3); - tok2 = simplifyTypedefCopyTokens(tok2, argFuncRetStart, argFuncRetEnd, location); + tok2 = simplifyTypedefCopyTokens(tok2, argFuncRetStart, argFuncRetEnd, location, originalName); } else if (ptrToArray || refToArray) { - tok2 = simplifyTypedefInsertToken(tok2, "(", location); + tok2 = simplifyTypedefInsertToken(tok2, "(", location, originalName); Token *tok3 = tok2; if (ptrToArray) - tok2 = simplifyTypedefInsertToken(tok2, "*", location); + tok2 = simplifyTypedefInsertToken(tok2, "*", location, originalName); else - tok2 = simplifyTypedefInsertToken(tok2, "&", location); + tok2 = simplifyTypedefInsertToken(tok2, "&", location, originalName); bool hasName = false; // skip over name @@ -2209,14 +2253,14 @@ void Tokenizer::simplifyTypedefCpp() tok2 = tok2->linkAt(1); } - simplifyTypedefInsertToken(tok2, ")", location); + simplifyTypedefInsertToken(tok2, ")", location, originalName); Token::createMutualLinks(tok2->next(), tok3); if (!hasName) tok2 = tok2->next(); } else if (ptrMember) { if (Token::simpleMatch(tok2, "* (")) { - tok2 = simplifyTypedefInsertToken(tok2, "*", location); + tok2 = simplifyTypedefInsertToken(tok2, "*", location, originalName); } else { // This is the case of casting operator. // Name is not available, and () should not be @@ -2225,7 +2269,7 @@ void Tokenizer::simplifyTypedefCpp() Token *openParenthesis = nullptr; if (!castOperator) { - tok2 = simplifyTypedefInsertToken(tok2, "(", location); + tok2 = simplifyTypedefInsertToken(tok2, "(", location, originalName); openParenthesis = tok2; } @@ -2233,25 +2277,25 @@ void Tokenizer::simplifyTypedefCpp() const Token *tok4 = namespaceStart; while (tok4 != namespaceEnd) { - tok2 = simplifyTypedefInsertToken(tok2, tok4->str(), location); + tok2 = simplifyTypedefInsertToken(tok2, tok4->str(), location, originalName); tok4 = tok4->next(); } - tok2 = simplifyTypedefInsertToken(tok2, namespaceEnd->str(), location); + tok2 = simplifyTypedefInsertToken(tok2, namespaceEnd->str(), location, originalName); - tok2 = simplifyTypedefInsertToken(tok2, "*", location); + tok2 = simplifyTypedefInsertToken(tok2, "*", location, originalName); if (openParenthesis) { // Skip over name, if any if (Token::Match(tok2->next(), "%name%")) tok2 = tok2->next(); - tok2 = simplifyTypedefInsertToken(tok2, ")", location); + tok2 = simplifyTypedefInsertToken(tok2, ")", location, originalName); Token::createMutualLinks(tok2, openParenthesis); } } } else if (typeOf) { - tok2 = simplifyTypedefCopyTokens(tok2, argStart, argEnd, location); + tok2 = simplifyTypedefCopyTokens(tok2, argStart, argEnd, location, originalName); } else if (Token::Match(tok2, "%name% [")) { while (Token::Match(tok2, "%name%|] [")) { tok2 = tok2->linkAt(1); @@ -2273,7 +2317,7 @@ void Tokenizer::simplifyTypedefCpp() // reference or pointer to array? if (Token::Match(tok2, "&|*|&&")) { tok2 = tok2->previous(); - Token *tok3 = simplifyTypedefInsertToken(tok2, "(", location); + Token *tok3 = simplifyTypedefInsertToken(tok2, "(", location, originalName); // handle missing variable name if (Token::Match(tok3, "( *|&|&& *|&|&& %name%")) @@ -2284,6 +2328,13 @@ void Tokenizer::simplifyTypedefCpp() tok2 = tok2->tokAt(2); else tok2 = tok2->tokAt(3); + while (tok2->link()) { + tok2 = tok2->link()->next(); + if (Token::simpleMatch(tok2, ";")) { + tok2 = tok2->previous(); + break; + } + } if (!tok2) syntaxError(nullptr); @@ -2304,7 +2355,7 @@ void Tokenizer::simplifyTypedefCpp() tok2 = tok2->tokAt(3); } - tok2 = simplifyTypedefInsertToken(tok2, ")", location); + tok2 = simplifyTypedefInsertToken(tok2, ")", location, originalName); Token::createMutualLinks(tok2, tok3); } @@ -2315,7 +2366,7 @@ void Tokenizer::simplifyTypedefCpp() while (tok2->strAt(1) == "[") tok2 = tok2->linkAt(1); - tok2 = simplifyTypedefCopyTokens(tok2, arrayStart, arrayEnd, location); + tok2 = simplifyTypedefCopyTokens(tok2, arrayStart, arrayEnd, location, originalName); if (!tok2->next()) syntaxError(tok2); @@ -2693,7 +2744,7 @@ namespace { { Token *tok1 = tok; - if (tok1 && tok1->str() != nameToken->str()) + if (!tok1 || tok1->str() != nameToken->str()) return false; // skip this using @@ -2702,6 +2753,9 @@ namespace { return false; } + if (!tok->tokAt(-1)) + return false; + // skip other using with this name if (tok1->strAt(-1) == "using") { // fixme: this is wrong @@ -2997,7 +3051,6 @@ bool Tokenizer::simplifyUsing() Token::Match(tok->linkAt(2), "] ] = ::| %name%"))))) continue; - const std::string& name = tok->strAt(1); const Token *nameToken = tok->next(); std::string scope = currentScope->fullName; Token *usingStart = tok; @@ -3013,6 +3066,9 @@ bool Tokenizer::simplifyUsing() if (!usingEnd) continue; + for (Token *typeTok = start; typeTok != usingEnd; typeTok = typeTok->next()) + typeTok->isSimplifiedTypedef(true); + // Move struct defined in using out of using. // using T = struct t { }; => struct t { }; using T = struct t; // fixme: this doesn't handle attributes @@ -3043,7 +3099,7 @@ bool Tokenizer::simplifyUsing() if (!hasName) { std::string newName; if (structEnd->strAt(2) == ";") - newName = name; + newName = nameToken->str(); else newName = "Unnamed" + std::to_string(mUnnamedCount++); TokenList::copyTokens(structEnd->next(), tok, start); @@ -3145,8 +3201,15 @@ bool Tokenizer::simplifyUsing() continue; } - // skip template definitions if (Token::Match(tok1, "template < !!>")) { + Token *paramsEnd = tok1->next()->findClosingBracket(); + bool shadowed = !paramsEnd; + for (const Token *param = tok1->next(); !shadowed && param != paramsEnd; param = param->next()) + shadowed = param->str() == nameToken->str(); + if (!shadowed) { + tok1 = paramsEnd; + continue; + } Token *declEndToken = TemplateSimplifier::findTemplateDeclarationEnd(tok1); if (declEndToken) tok1 = declEndToken; @@ -3190,7 +3253,7 @@ bool Tokenizer::simplifyUsing() if (!isTypedefInfoAdded && Token::Match(tok1, "%name% (")) { isTypedefInfoAdded = true; TypedefInfo usingInfo; - usingInfo.name = name; + usingInfo.name = nameToken->str(); usingInfo.filename = list.file(nameToken); usingInfo.lineNumber = nameToken->linenr(); usingInfo.column = nameToken->column(); @@ -3366,12 +3429,17 @@ bool Tokenizer::simplifyUsing() } else if (fpArgList && fpQual && Token::Match(tok1->next(), "%name%")) { // function pointer const bool isFuncDecl = Token::simpleMatch(tok1->tokAt(2), "("); - TokenList::copyTokens(tok1->next(), fpArgList, usingEnd->previous()); + Token *dest = tok1->next(); + while (Token::Match(dest, "%name% :: %name%")) + dest = dest->tokAt(2); + TokenList::copyTokens(dest, fpArgList, usingEnd->previous()); Token* const copyEnd = TokenList::copyTokens(tok1, start, fpQual->link()->previous()); Token* leftPar = copyEnd->previous(); while (leftPar->str() != "(") leftPar = leftPar->previous(); - Token* const insertTok = isFuncDecl ? copyEnd->linkAt(2) : copyEnd->next(); + Token *insertTok = isFuncDecl ? copyEnd->linkAt(2) : copyEnd->next(); + while (Token::Match(insertTok, "%name% :: %name%")) + insertTok = insertTok->tokAt(2); Token* const rightPar = insertTok->insertToken(")"); Token::createMutualLinks(leftPar, rightPar); tok1->deleteThis(); @@ -3447,6 +3515,8 @@ bool Tokenizer::simplifyUsing() skip = true; simplifyUsingError(usingStart, usingEnd); } + if (!after) + syntaxError(tok1); tok1 = after->previous(); } @@ -3499,7 +3569,7 @@ bool Tokenizer::simplifyTokens1(const std::string &configuration, int fileIndex) mConfiguration = configuration; if (mTimerResults) { - Timer t("Tokenizer::simplifyTokens1::simplifyTokenList1", mSettings.showtime, mTimerResults); + Timer t("Tokenizer::simplifyTokens1::simplifyTokenList1", mTimerResults); if (!simplifyTokenList1(list.getFiles().front().c_str())) return false; } else { @@ -3507,18 +3577,16 @@ bool Tokenizer::simplifyTokens1(const std::string &configuration, int fileIndex) return false; } - const ShowTime showTime = mTimerResults ? mSettings.showtime : ShowTime::NONE; - - Timer::run("Tokenizer::simplifyTokens1::createAst", showTime, mTimerResults, [&]() { + Timer::run("Tokenizer::simplifyTokens1::createAst", mTimerResults, [&]() { list.createAst(); list.validateAst(mSettings.debugnormal); }); - Timer::run("Tokenizer::simplifyTokens1::createSymbolDatabase", showTime, mTimerResults, [&]() { + Timer::run("Tokenizer::simplifyTokens1::createSymbolDatabase", mTimerResults, [&]() { createSymbolDatabase(); }); - Timer::run("Tokenizer::simplifyTokens1::setValueType", showTime, mTimerResults, [&]() { + Timer::run("Tokenizer::simplifyTokens1::setValueType", mTimerResults, [&]() { mSymbolDatabase->setValueTypeInTokenList(false); mSymbolDatabase->setValueTypeInTokenList(true); }); @@ -3533,7 +3601,7 @@ bool Tokenizer::simplifyTokens1(const std::string &configuration, int fileIndex) const bool doValueFlow = !disableValueflowEnv || (std::strcmp(disableValueflowEnv, "1") != 0); if (doValueFlow) { - Timer::run("Tokenizer::simplifyTokens1::ValueFlow", showTime, mTimerResults, [&]() { + Timer::run("Tokenizer::simplifyTokens1::ValueFlow", mTimerResults, [&]() { ValueFlow::setValues(list, *mSymbolDatabase, mErrorLogger, mSettings, mTimerResults); }); @@ -3564,7 +3632,7 @@ bool Tokenizer::simplifyTokens1(const std::string &configuration, int fileIndex) //--------------------------------------------------------------------------- -void Tokenizer::findComplicatedSyntaxErrorsInTemplates() +void Tokenizer::findComplicatedSyntaxErrorsInTemplates() const { validate(); mTemplateSimplifier->checkComplicatedSyntaxErrorsInTemplates(); @@ -3710,7 +3778,11 @@ void Tokenizer::concatenateNegativeNumberAndAnyPositive() if (tok->findOpeningBracket()) continue; - while (tok->str() != ">" && tok->next() && tok->strAt(1) == "+" && (!Token::Match(tok->tokAt(2), "%name% (|;") || Token::Match(tok, "%op%"))) + if (!tok->tokAt(2) || (tok->tokAt(2)->isOp() && !Token::Match(tok->tokAt(2), "[+-*]"))) + syntaxError(tok); + + while ((tok->isArithmeticalOp() || tok->isComparisonOp()) && tok->str() != ">" && tok->next() && tok->strAt(1) == "+" && + (!Token::Match(tok->tokAt(2), "%name% (|;") || Token::Match(tok, "%op%"))) tok->deleteNext(); if (Token::Match(tok->next(), "+|- %num%")) { @@ -3843,7 +3915,8 @@ void Tokenizer::simplifyRedundantConsecutiveBraces() for (Token *tok = list.front(); tok;) { if (Token::simpleMatch(tok, "= {")) { tok = tok->linkAt(1); - } else if (Token::simpleMatch(tok, "{ {") && Token::simpleMatch(tok->linkAt(1), "} }")) { + } else if (Token::simpleMatch(tok, "{ {") && Token::simpleMatch(tok->linkAt(1), "} }") && + !Token::Match(tok->previous(), "%name%")) { //remove internal parentheses tok->linkAt(1)->deleteThis(); tok->deleteNext(); @@ -4491,13 +4564,17 @@ static void setVarIdStructMembers(Token *&tok1, tok->varId(it->second); } } + if (Token::Match(tok, "%name% = { . %name% =|{")) { + setVarIdStructMembers(tok, structMembers, varId); + tok = tok->linkAt(2); + } tok = tok->next(); } return; } - while (Token::Match(tok->next(), ")| . %name% !!(")) { + while (Token::Match(tok->next(), ")| . %name%")) { // Don't set varid for trailing return type if (tok->strAt(1) == ")" && Token::Match(tok->linkAt(1)->tokAt(-1), "%name%|]") && !tok->linkAt(1)->tokAt(-1)->isKeyword() && TokenList::isFunctionHead(tok->linkAt(1), "{;")) { @@ -4519,6 +4596,8 @@ static void setVarIdStructMembers(Token *&tok1, std::map& members = structMembers[struct_varid]; const auto it = utils::as_const(members).find(tok->str()); if (it == members.cend()) { + if (Token::Match(tok, "%name% (")) + break; members[tok->str()] = ++varId; tok->varId(varId); } else { @@ -4684,6 +4763,26 @@ static const std::unordered_set notstart_cpp = { NOTSTART_C, "delete", "friend", "new", "throw", "using", "virtual", "explicit", "const_cast", "dynamic_cast", "reinterpret_cast", "static_cast", "template" }; +// Returns the end of the lambda that starts at tok in a constructor initializer list, or nullptr +static const Token* findInitListLambdaEnd(const Token* tok) +{ + if (!Token::simpleMatch(tok, "[") || Token::Match(tok->previous(), "%name%|)|]|>")) + return nullptr; // array subscript or array size of a new expression + // array size of a new expression with pointer or reference type: new T*[n]{...}, new (p) T*[n]{...} + for (const Token* prev = tok->previous(); Token::Match(prev, "*|&|&&|::|%name%|>|)"); prev = prev->previous()) { + if (prev->str() == "new") + return nullptr; + if (prev->str() == ")") + prev = prev->link(); + else if (prev->str() == ">") { + prev = prev->findOpeningBracket(); + if (!prev) + break; + } + } + return findLambdaEndScope(tok); +} + void Tokenizer::setVarIdPass1() { const bool cpp = isCPP(); @@ -4699,6 +4798,7 @@ void Tokenizer::setVarIdPass1() std::stack functionDeclEndStack; const Token *functionDeclEndToken = nullptr; bool initlist = false; + std::stack initlistLambdaEnds; // ends of lambdas in constructor initializer lists bool inlineFunction = false; for (Token *tok = list.front(); tok; tok = tok->next()) { if (tok->isOp()) @@ -4734,6 +4834,13 @@ void Tokenizer::setVarIdPass1() variableMap.enterScope(); } } + } else if (const Token* lambdaEnd = initlist ? findInitListLambdaEnd(tok) : nullptr) { + // lambda in initializer list: parse it like a lambda in executable code, the + // extra scope holds its parameters and is left at the end of the lambda + initlistLambdaEnds.push(lambdaEnd); + scopeStack.emplace(/*isExecutable=*/ true, /*isStructInit=*/ false, /*isEnum=*/ false, variableMap.getVarId()); + variableMap.enterScope(); + initlist = false; } else if (!initlist && tok->str()=="(") { const Token * newFunctionDeclEnd = nullptr; if (!scopeStack.top().isExecutable) @@ -4763,7 +4870,7 @@ void Tokenizer::setVarIdPass1() // parse anonymous namespaces as part of the current scope if (!Token::Match(startToken->previous(), "union|struct|enum|namespace {") && - !(initlist && Token::Match(startToken->previous(), "%name%|>|>>|(") && Token::Match(startToken->link(), "} ,|{|)|..."))) { + !(initlist && Token::Match(startToken->previous(), "%name%|>|>>|(|,|{") && Token::Match(startToken->link(), "} ,|{|)|}|..."))) { if (tok->str() == "{") { bool isExecutable; @@ -4824,6 +4931,15 @@ void Tokenizer::setVarIdPass1() } } } + + if (!initlistLambdaEnds.empty() && initlistLambdaEnds.top() == tok) { + // end of lambda in initializer list + initlistLambdaEnds.pop(); + if (scopeStack.size() > 1) + scopeStack.pop(); + variableMap.leaveScope(); + initlist = true; + } } if ((!scopeStack.top().isStructInit && @@ -4959,7 +5075,7 @@ void Tokenizer::setVarIdPass1() decl = false; } else if (cpp && Token::Match(prev2, "%type% {") && Token::simpleMatch(tok2->link(), "} ;")) { // C++11 initialization style if (tok2->link() != tok2->next() && // add value-initialized variable T x{}; - (Token::Match(prev2, "do|try|else") || Token::Match(prev2->tokAt(-2), "struct|class|:"))) + (Token::Match(prev2, "do|try|else") || Token::simpleMatch(prev2->tokAt(-2), ":"))) continue; } else decl = false; @@ -4967,7 +5083,7 @@ void Tokenizer::setVarIdPass1() if (decl) { if (isC() && Token::Match(prev2->previous(), "&|&&")) syntaxErrorC(prev2, prev2->strAt(-2) + prev2->strAt(-1) + " " + prev2->str()); - variableMap.addVariable(prev2->str(), scopeStack.size() <= 1); + variableMap.addVariable(prev2->str(), scopeStack.size() <= 1 && !functionDeclEndToken); if (Token::simpleMatch(tok->previous(), "for (") && Token::Match(prev2, "%name% [=[({,]")) { for (const Token *tok3 = prev2->next(); tok3 && tok3->str() != ";"; tok3 = tok3->next()) { @@ -5104,14 +5220,30 @@ static std::string getScopeName(const std::list &scopeInfo) return ret; } +// If tok is the name in a qualification "name ::" or "name < ... > ::", return the token after the "::" +static Token * skipQualification(Token *tok) +{ + if (!Token::Match(tok, "%name% ::|<")) + return nullptr; + if (tok->strAt(1) == "<") { + tok = tok->next()->findClosingBracket(); + if (!Token::simpleMatch(tok, "> ::")) + return nullptr; + } + return tok->tokAt(2); +} + +// indexMembersByScopeName() relies on how this matches the scope names: keep the two in sync static Token * matchMemberName(const std::list &scope, const Token *nsToken, Token *memberToken, const std::list &scopeInfo) { auto scopeIt = scopeInfo.cbegin(); // Current scope.. for (auto it = scope.cbegin(); it != scope.cend(); ++it) { - if (scopeIt == scopeInfo.cend() || scopeIt->name != *it) - return nullptr; + if (scopeIt == scopeInfo.cend() || scopeIt->name != *it) { + scopeIt = scopeInfo.cbegin(); + break; + } ++scopeIt; } @@ -5134,16 +5266,11 @@ static Token * matchMemberName(const std::list &scope, const Token // Parse member tokens.. while (scopeIt != scopeInfo.end()) { - if (!Token::Match(memberToken, "%name% ::|<")) + if (!Token::Match(memberToken, "%name%") || memberToken->str() != scopeIt->name) return nullptr; - if (memberToken->str() != scopeIt->name) + memberToken = skipQualification(memberToken); + if (!memberToken) return nullptr; - if (memberToken->strAt(1) == "<") { - memberToken = memberToken->next()->findClosingBracket(); - if (!Token::simpleMatch(memberToken, "> ::")) - return nullptr; - } - memberToken = memberToken->tokAt(2); ++scopeIt; } @@ -5170,11 +5297,52 @@ static Token * matchMemberName(const Member &member, const std::list return nullptr; } +using MembersByScopeName = std::unordered_map>; + +// matchMemberName() can only match when the last scope name is matched by a name in the +// member's scope, in one of its "using namespace" or in its qualification. Index the members +// by these names, keeping their order, so that each scope only tries its candidates. +static MembersByScopeName indexMembersByScopeName(const std::list &members) +{ + MembersByScopeName index; + for (const Member &member : members) { + // members are visited in order, so a member that is already a candidate is the last one + const auto addCandidate = [&](const std::string &name) { + std::vector &candidates = index[name]; + if (candidates.empty() || candidates.back() != &member) + candidates.push_back(&member); + }; + for (const std::string &name : member.scope) + addCandidate(name); + for (const Token *ns : member.usingnamespaces) { + for (; Token::Match(ns, "%name% ::"); ns = ns->tokAt(2)) + addCandidate(ns->str()); + if (Token::Match(ns, "%name%")) + addCandidate(ns->str()); + } + Token *tok = member.tok; + while (Token *next = skipQualification(tok)) { + addCandidate(tok->str()); + tok = next; + } + } + return index; +} + +static const std::vector &membersForScope(const MembersByScopeName &index, const std::list &scopeInfo) +{ + static const std::vector none; + if (scopeInfo.empty()) + return none; + const auto it = index.find(scopeInfo.back().name); + return it == index.end() ? none : it->second; +} + static Token * matchMemberVarName(const Member &var, const std::list &scopeInfo) { Token *tok = matchMemberName(var, scopeInfo); if (Token::Match(tok, "%name%")) { - if (!tok->next() || tok->strAt(1) != "(" || (tok->tokAt(2) && tok->tokAt(2)->isLiteral())) + if (!tok->next() || tok->strAt(1) != "(" || (tok->tokAt(2) && (tok->tokAt(2)->isLiteral() || tok->tokAt(2)->isOp()))) return tok; } return nullptr; @@ -5215,7 +5383,16 @@ void Tokenizer::setVarIdPass2() std::map endOfScope; std::list scope; std::list usingnamespaces; + const Token *enumEnd = nullptr; for (Token *tok = list.front(); tok; tok = tok->next()) { + if (isEnumStart(tok)) { + enumEnd = tok->link(); + continue; + } + if (tok == enumEnd) { + enumEnd = nullptr; + continue; + } if (!tok->previous() || Token::Match(tok->previous(), "[;{}]")) { if (Token::Match(tok, "using namespace %name% ::|;")) { Token *endtok = tok->tokAt(2); @@ -5245,7 +5422,8 @@ void Tokenizer::setVarIdPass2() tok = tok->next()->findClosingBracket()->next(); else if (usingnamespaces.empty() || tok->varId() || !tok->isName() || tok->isStandardType() || tok->tokType() == Token::eKeyword || tok->tokType() == Token::eBoolean || Token::Match(tok->previous(), ".|namespace|class|struct|&|&&|*|> %name%") || Token::Match(tok->previous(), "%type%| %name% ( %type%|)") || Token::Match(tok, "public:|private:|protected:") || - (!tok->next() && Token::Match(tok->previous(), "}|; %name%"))) + (!tok->next() && Token::Match(tok->previous(), "}|; %name%")) || + (enumEnd && Token::Match(tok->previous(), "{|, %name% =|,|}"))) continue; if (tok->strAt(-1) == "::" && tok->tokAt(-2) && tok->tokAt(-2)->isName()) @@ -5264,13 +5442,16 @@ void Tokenizer::setVarIdPass2() } if (!tok->next()) syntaxError(tok); - if (Token::Match(tok, "%name% (") && !(tok->tokAt(2) && tok->tokAt(2)->isLiteral())) + if (Token::Match(tok, "%name% (") && !(tok->tokAt(2) && (tok->tokAt(2)->isLiteral() || tok->tokAt(2)->isOp()))) allMemberFunctions.emplace_back(scope, usingnamespaces, tok1); else allMemberVars.emplace_back(scope, usingnamespaces, tok1); } } + const MembersByScopeName memberFunctionsByScopeName = indexMembersByScopeName(allMemberFunctions); + const MembersByScopeName memberVarsByScopeName = indexMembersByScopeName(allMemberVars); + std::list scopeInfo; // class members.. @@ -5387,8 +5568,8 @@ void Tokenizer::setVarIdPass2() continue; // Member variables - for (const Member &var : allMemberVars) { - Token *tok2 = matchMemberVarName(var, scopeInfo); + for (const Member *var : membersForScope(memberVarsByScopeName, scopeInfo)) { + Token *tok2 = matchMemberVarName(*var, scopeInfo); if (!tok2) continue; if (tok2->varId() == 0) @@ -5399,8 +5580,8 @@ void Tokenizer::setVarIdPass2() continue; // Set variable ids in member functions for this class.. - for (const Member &func : allMemberFunctions) { - Token *tok2 = matchMemberFunctionName(func, scopeInfo); + for (const Member *func : membersForScope(memberFunctionsByScopeName, scopeInfo)) { + Token *tok2 = matchMemberFunctionName(*func, scopeInfo); if (!tok2) continue; @@ -5514,7 +5695,7 @@ void Tokenizer::createLinks2() bool isStruct = false; std::stack type; - std::stack templateTokens; + std::stack templateTokens; for (Token *token = list.front(); token; token = token->next()) { if (Token::Match(token, "%name%|> %name% [:<]")) isStruct = true; @@ -5570,6 +5751,8 @@ void Tokenizer::createLinks2() const Token* end = type.top()->findClosingBracket(); if (Token::Match(end, "> %comp%|;|.|=|{|}|(|)|::")) break; + if (Token::Match(end, "> %name% (|::")) + break; // Variable declaration if (Token::Match(end, "> %var% ;") && (type.top()->tokAt(-2) == nullptr || Token::Match(type.top()->tokAt(-2), ";|}|{"))) break; @@ -5670,7 +5853,22 @@ bool Tokenizer::simplifyTokenList1(const char FileName[]) // if MACRO for (Token *tok = list.front(); tok; tok = tok->next()) { - if (Token::Match(tok, "if|for|while %name% (")) { + if (Token::simpleMatch(tok, "if consteval {")) { + // 'if consteval { ... }' -> 'if ( __cppcheck_consteval__ ) { ... }' + Token *parTok = tok->next(); + parTok->str("("); + Token *evalTok = parTok->insertToken("__cppcheck_consteval__"); + evalTok->originalName("consteval"); + evalTok->insertToken(")"); + } else if (Token::Match(tok, "if !|not consteval {")) { + // 'if !consteval { ... }' / 'if not consteval { ... }' -> 'if ( ! __cppcheck_consteval__ ) { ... }' + Token *notTok = tok->next(); + notTok->insertTokenBefore("("); + Token *evalTok = notTok->next(); + evalTok->str("__cppcheck_consteval__"); + evalTok->originalName("consteval"); + evalTok->insertToken(")"); + } else if (Token::Match(tok, "if|for|while %name% (")) { if (Token::simpleMatch(tok, "for each")) { // 'for each (x in y )' -> 'for (x : y)' if (Token* in = Token::findsimplematch(tok->tokAt(2), "in", tok->linkAt(2))) @@ -5740,10 +5938,8 @@ bool Tokenizer::simplifyTokenList1(const char FileName[]) validate(); - const ShowTime showTime = mTimerResults ? mSettings.showtime : ShowTime::NONE; - // Bail out if code is garbage - Timer::run("Tokenizer::simplifyTokens1::simplifyTokenList1::findGarbageCode", showTime, mTimerResults, [&]() { + Timer::run("Tokenizer::simplifyTokens1::simplifyTokenList1::findGarbageCode", mTimerResults, [&]() { findGarbageCode(); }); @@ -5880,7 +6076,7 @@ bool Tokenizer::simplifyTokenList1(const char FileName[]) simplifyTypedefLHS(); // typedef.. - Timer::run("Tokenizer::simplifyTokens1::simplifyTokenList1::simplifyTypedef", showTime, mTimerResults, [&]() { + Timer::run("Tokenizer::simplifyTokens1::simplifyTokenList1::simplifyTypedef", mTimerResults, [&]() { simplifyTypedef(); }); @@ -5974,7 +6170,7 @@ bool Tokenizer::simplifyTokenList1(const char FileName[]) simplifyTypeIntrinsics(); // Handle templates.. - Timer::run("Tokenizer::simplifyTokens1::simplifyTokenList1::simplifyTemplates", showTime, mTimerResults, [&]() { + Timer::run("Tokenizer::simplifyTokens1::simplifyTokenList1::simplifyTemplates", mTimerResults, [&]() { simplifyTemplates(); }); @@ -6001,13 +6197,16 @@ bool Tokenizer::simplifyTokenList1(const char FileName[]) validate(); // #6772 "segmentation fault (invalid code) in Tokenizer::setVarId" - Timer::run("Tokenizer::simplifyTokens1::simplifyTokenList1::setVarId", showTime, mTimerResults, [&](){ + Timer::run("Tokenizer::simplifyTokens1::simplifyTokenList1::setVarId", mTimerResults, [&](){ setVarId(); }); // Link < with > createLinks2(); + // Handle std::aligned_storage<...> + simplifyAlignedStorage(); + // Mark C++ casts markCppCasts(); @@ -6193,6 +6392,8 @@ void Tokenizer::dump(std::ostream &out) const } if (tok->isRemovedVoidParameter()) outs += " isRemovedVoidParameter=\"true\""; + if (tok->isInsertedBrace()) + outs += " isInsertedBrace=\"true\""; if (tok->isSplittedVarDeclComma()) outs += " isSplittedVarDeclComma=\"true\""; if (tok->isSplittedVarDeclEq()) @@ -6211,6 +6412,8 @@ void Tokenizer::dump(std::ostream &out) const outs += " isAttributeMaybeUnused=\"true\""; if (tok->isAttributeUnused()) outs += " isAttributeUnused=\"true\""; + if (tok->isAttributeUsed()) + outs += " isAttributeUsed=\"true\""; if (tok->isAttributeFallthrough()) outs += " isAttributeFallthrough=\"true\""; if (tok->isInitBracket()) @@ -6368,6 +6571,12 @@ std::string Tokenizer::dumpTypedefInfo() const outs += typedefInfo.name; outs += "\""; + if (!typedefInfo.originalName.empty()) { + outs += " originalName=\""; + outs += typedefInfo.originalName; + outs += "\""; + } + outs += " file=\""; outs += ErrorLogger::toxml(typedefInfo.filename); outs += "\""; @@ -6860,7 +7069,7 @@ Token *Tokenizer::simplifyAddBracesToCommand(Token *tok) // before the "while" if (tokEnd) { tokEnd=tokEnd->next(); - if (!tokEnd || tokEnd->str()!="while") // no while + if (!Token::simpleMatch(tokEnd, "while (") || !Token::simpleMatch(tokEnd->linkAt(1), ") ;")) // no while syntaxError(tok); } } @@ -6972,10 +7181,13 @@ Token *Tokenizer::simplifyAddBracesPair(Token *tok, bool commandWithCondition) tokAfterCondition->previous()->insertToken("{"); Token * tokOpenBrace=tokAfterCondition->previous(); + tokOpenBrace->column(tokAfterCondition->column()); + tokOpenBrace->isInsertedBrace(true); tokEnd->insertToken("}"); Token * tokCloseBrace=tokEnd->next(); tokCloseBrace->column(tokEnd->column()); + tokCloseBrace->isInsertedBrace(true); Token::createMutualLinks(tokOpenBrace,tokCloseBrace); tokBracesEnd=tokCloseBrace; @@ -6996,7 +7208,7 @@ void Tokenizer::simplifyFunctionParameters() // We have found old style function, now we need to change it // First step: Get list of argument names in parentheses - std::map argumentNames; + std::map argumentNames; bool bailOut = false; const Token * tokparam = nullptr; @@ -7115,7 +7327,7 @@ void Tokenizer::simplifyFunctionParameters() if (argumentNames.size() != argumentNames2.size()) { //move back 'tok1' to the last ';' tok1 = tok1->previous(); - for (const std::pair& argumentName : argumentNames) { + for (const std::pair& argumentName : argumentNames) { if (argumentNames2.find(argumentName.first) == argumentNames2.end()) { //add the missing parameter argument declaration tok1->insertToken(";"); @@ -7303,6 +7515,19 @@ void Tokenizer::simplifyVarDecl(const bool only_k_r_fpar) simplifyVarDecl(list.front(), nullptr, only_k_r_fpar); } +static Token* isTrailingReturnType(Token* tok) +{ + while (Token::Match(tok, "%name%|::|>")) { + if (Token* open = tok->findOpeningBracket()) + tok = open->tokAt(-1); + else + tok = tok->tokAt(-1); + } + if (tok && Token::simpleMatch(tok->tokAt(-1), ") .")) + return tok->tokAt(-1); + return nullptr; +} + // cppcheck-suppress functionConst - has side effects void Tokenizer::simplifyVarDecl(Token * tokBegin, const Token * const tokEnd, const bool only_k_r_fpar) { @@ -7330,14 +7555,16 @@ void Tokenizer::simplifyVarDecl(Token * tokBegin, const Token * const tokEnd, co if (!tok->linkAt(1)) syntaxError(tokBegin); // Check for lambdas before skipping - if (Token::Match(tok->tokAt(-2), ") . %name%")) { // trailing return type + if (Token* trailingStart = isTrailingReturnType(tok)) { // TODO: support lambda without parameter clause? - Token* lambdaStart = tok->linkAt(-2)->previous(); + Token* lambdaStart = trailingStart->link()->tokAt(-1); if (Token::simpleMatch(lambdaStart, "]")) lambdaStart = lambdaStart->link(); Token* lambdaEnd = findLambdaEndScope(lambdaStart); if (lambdaEnd) simplifyVarDecl(lambdaEnd->link()->next(), lambdaEnd, only_k_r_fpar); + else + simplifyVarDecl(tok->tokAt(2), tok->linkAt(1), only_k_r_fpar); } else { for (Token* tok2 = tok->next(); tok2 != tok->linkAt(1); tok2 = tok2->next()) { Token* lambdaEnd = findLambdaEndScope(tok2); @@ -7991,8 +8218,8 @@ void Tokenizer::elseif() if (Token::Match(tok2, "}|;")) { if (tok2->next() && tok2->strAt(1) != "else") { - tok->insertToken("{")->isSimplifiedScope(true); - tok2->insertToken("}")->isSimplifiedScope(true); + tok->insertToken("{")->isInsertedBrace(true); + tok2->insertToken("}")->isInsertedBrace(true); Token::createMutualLinks(tok->next(), tok2->next()); break; } @@ -8058,7 +8285,8 @@ void Tokenizer::simplifyIfSwitchForInit() tok->str("{"); endscope->insertToken("}"); Token::createMutualLinks(tok, endscope->next()); - tok->isSimplifiedScope(true); + tok->isInsertedBrace(true); + tok->isSimplifiedIfInitStmt(true); } } @@ -8731,6 +8959,9 @@ void Tokenizer::findGarbageCode() const if (!cpp || mSettings.standards.cpp < Standards::CPP20 || !Token::Match(tok->previous(), "%name% : %num% =")) syntaxError(tok, tok->strAt(1) + " " + tok->strAt(2)); } + else if (!cpp && Token::Match(tok, "++|-- ++|--")) { + syntaxError(tok, tok->str() + tok->strAt(1)); + } else if (Token::simpleMatch(tok, ") return") && !Token::Match(tok->link()->previous(), "if|while|for (")) { if (tok->link()->previous() && tok->link()->previous()->isUpperCaseName()) unknownMacroError(tok->link()->previous()); @@ -8906,6 +9137,8 @@ void Tokenizer::findGarbageCode() const const Token* const endTok = tok->linkAt(1); for (tok = tok->tokAt(2); tok != endTok; tok = tok->next()) { if (const Token* lam = findLambdaEndTokenWithoutAST(tok)) { + if (lam == endTok) + break; tok = lam; continue; } @@ -8915,7 +9148,7 @@ void Tokenizer::findGarbageCode() const if (tok->strAt(-1) == ",") syntaxError(tok); colons++; - } else if (tok->str() == "(") { // skip pairs of ( ) + } else if (tok->str() == "(" || tok->str() == "{") { // skip pairs of ( ) tok = tok->link(); } } @@ -8975,7 +9208,8 @@ void Tokenizer::findGarbageCode() const if (tok->strAt(1) == "(") syntaxError(tok); else if (!(tok->tokType() == Token::Type::eString && Token::simpleMatch(tok->tokAt(-1), "extern")) && - !(tok->tokType() == Token::Type::eBoolean && cpp && Token::simpleMatch(tok->tokAt(-1), "requires"))) + !(tok->tokType() == Token::Type::eBoolean && cpp && Token::simpleMatch(tok->tokAt(-1), "requires")) && + !Token::simpleMatch(tok->linkAt(1), "} ;")) syntaxError(tok); } if (Token::Match(tok, "( ) %num%|%bool%|%char%|%str%")) @@ -8992,7 +9226,7 @@ void Tokenizer::findGarbageCode() const syntaxError(tok); if (Token::Match(tok, "[;([{] %comp%|%oror%|%or%|%|/")) syntaxError(tok); - if (Token::Match(tok, "%cop%|= ]") && !Token::simpleMatch(tok, "*") && !(cpp && Token::Match(tok->previous(), "%type%|[|,|%num% &|=|> ]"))) + if (Token::Match(tok, "%cop%|= ]") && !Token::simpleMatch(tok, "*") && !(cpp && Token::Match(tok->previous(), "<|%type%|[|,|%num% &|=|> ]"))) syntaxError(tok); if (Token::Match(tok, "[+-] [;,)]}]") && !(cpp && Token::simpleMatch(tok->previous(), "operator"))) syntaxError(tok); @@ -9032,9 +9266,21 @@ void Tokenizer::findGarbageCode() const syntaxError(tok); if (Token::Match(tok, "==|!=|<=|>= %comp%") && tok->strAt(-1) != "operator") syntaxError(tok, tok->str() + " " + tok->strAt(1)); - if (Token::simpleMatch(tok, "::") && (!Token::Match(tok->next(), "%name%|*|~") || - (tok->next()->isKeyword() && !Token::Match(tok->next(), "new|delete|operator")))) - syntaxError(tok); + if (Token::simpleMatch(tok, "::")) { + if (!Token::Match(tok->next(), "%name%|*|~") || (tok->next()->isKeyword() && !Token::Match(tok->next(), "new|delete|operator"))) + syntaxError(tok); + if (Token::simpleMatch(tok->tokAt(-1), ")")) { + // NAME(...):: => NAME is most likely an unknown macro + // other cases are valid, e.g. (void)::f(), return (T)::x, new (p) ::T, decltype(x)::type + const Token* const prev = tok->linkAt(-1)->tokAt(-1); + if (Token::Match(prev, "%name% (") && !prev->isKeyword() && prev->str() != "decltype") { // decltype is no keyword before C++11 + if (prev->isUpperCaseName()) + unknownMacroError(prev); + else + syntaxError(tok); + } + } + } if (Token::Match(tok, "& %comp%|&&|%oror%|&|%or%") && tok->strAt(1) != ">") syntaxError(tok); if (Token::Match(tok, "%comp%|&&|%oror%|&|%or% }") && tok->str() != ">") @@ -9087,14 +9333,18 @@ void Tokenizer::findGarbageCode() const } if (!tok2->next() || tok2->isControlFlowKeyword() || Token::Match(tok2, "typedef|static|.")) syntaxError(tok); - if (Token::Match(tok2, "%name% %name%") && tok2->str() == tok2->strAt(1)) { - if (Token::simpleMatch(tok2->tokAt(2), ";")) - continue; - if (tok2->isStandardType() && tok2->str() == "long") - continue; - if (Token::Match(tok2->tokAt(-1), "enum|struct|union") || (isCPP() && Token::Match(tok2->tokAt(-1), "class|::"))) - continue; - syntaxError(tok2); + if (Token::Match(tok2, "%name% %name%")) { + if (tok2->str() == tok2->strAt(1)) { + if (Token::simpleMatch(tok2->tokAt(2), ";")) + continue; + if (tok2->isStandardType() && tok2->str() == "long") + continue; + if (Token::Match(tok2->tokAt(-1), "enum|struct|union") || (isCPP() && Token::Match(tok2->tokAt(-1), "class|::"))) + continue; + syntaxError(tok2); + } + if (Token::Match(tok2->tokAt(2), "%name%") && tok2->isNameOnly() && tok2->tokAt(1)->isNameOnly() && tok2->tokAt(2)->isNameOnly()) + syntaxError(tok2); } } } @@ -9355,6 +9605,7 @@ void Tokenizer::simplifyStructDecl() if (Token::Match(after->next(), "const|static|volatile| *|&| const| (| %type% )| ,|;|[|=|(|{")) { after->insertToken(";"); after = after->next(); + Token *declEnd = after; while (!Token::Match(start, "struct|class|union|enum")) { after->insertToken(start->str()); after = after->next(); @@ -9397,6 +9648,16 @@ void Tokenizer::simplifyStructDecl() } } } + + // pull declaration out of for loop + if (Token::simpleMatch(start->tokAt(-2), "for ( struct")) { + Token *link = start->linkAt(-1); + start->deletePrevious(2); + declEnd->insertToken("("); + declEnd->next()->link(link); + link->link(declEnd->next()); + declEnd->insertToken("for"); + } } } @@ -9457,8 +9718,8 @@ static Token* getVariableTokenAfterAttributes(Token* tok) { Token *vartok = nullptr; Token *after = getTokenAfterAttributes(tok, true); - // check if after variable name - if (Token::Match(after, "[;={]")) { + // check if after variable or parameter name + if (Token::Match(after, "[;={,)]")) { Token *prev = tok->previous(); while (Token::simpleMatch(prev, "]")) prev = prev->link()->previous(); @@ -9718,7 +9979,7 @@ void Tokenizer::simplifyCPPAttribute() if (!head) syntaxError(tok); - if (Token::simpleMatch(head, ";")) { + if (Token::Match(head, ";|,|)")) { Token *backTok = tok; while (Token::Match(backTok, "]|[|)")) { if (Token::Match(backTok, "]|)")) @@ -9875,6 +10136,15 @@ void Tokenizer::simplifyKeyword() } } + if (isC() && (tok->str() == "_Noreturn" || tok->str() == "noreturn")) { + Token *nameTok = tok; + while (Token::Match(nameTok, "%name%|*")) + nameTok = nameTok->next(); + if (nameTok && nameTok->str() == "(" && TokenList::isFunctionHead(nameTok, "{;")) { + nameTok->previous()->isAttributeNoreturn(true); + } + } + if (isC() || mSettings.standards.cpp == Standards::CPP03) { if (tok->str() == "auto") tok->deleteThis(); @@ -10256,6 +10526,7 @@ void Tokenizer::simplifyBitfields() while (Token::Match(typeTok, "%name% :: %name%")) typeTok = typeTok->tokAt(2); if (Token::Match(typeTok, "%type% %name% :") && + typeTok->str() != "enum" && !Token::Match(tok->next(), "case|public|protected|private|class|struct") && !Token::simpleMatch(tok->tokAt(2), "default :")) { Token *tok1 = typeTok->next(); @@ -10264,7 +10535,7 @@ void Tokenizer::simplifyBitfields() tooLargeError(tok1->tokAt(2)); if (tok1 && tok1->tokAt(2) && (Token::Match(tok1->tokAt(2), "%bool%|%num%") || - !Token::Match(tok1->tokAt(2), "public|protected|private| %type% ::|<|,|{|;"))) { + !Token::Match(tok1->tokAt(2), "public|protected|private| %type% ::|<|,|{"))) { while (tok1->next() && !Token::Match(tok1->next(), "[;,)]{}=]")) { if (Token::Match(tok1->next(), "[([]")) Token::eraseTokens(tok1, tok1->linkAt(1)); @@ -10310,18 +10581,25 @@ void Tokenizer::simplifyBitfields() } } -static bool isStdContainerOrIterator(const Token* tok, const Settings& settings) +static bool isStdContainerOrIterator(const Token* tok, const Library& library) { - const Library::Container* ctr = settings.library.detectContainerOrIterator(tok, nullptr, /*withoutStd*/ true); + const Library::Container* ctr = library.detectContainerOrIterator(tok, nullptr, /*withoutStd*/ true); return ctr && startsWith(ctr->startPattern, "std ::"); } -static bool isStdSmartPointer(const Token* tok, const Settings& settings) +static bool isStdSmartPointer(const Token* tok, const Library& library) { - const Library::SmartPointer* ptr = settings.library.detectSmartPointer(tok, /*withoutStd*/ true); + const Library::SmartPointer* ptr = library.detectSmartPointer(tok, /*withoutStd*/ true); return ptr && startsWith(ptr->name, "std::"); } +static bool isLibraryType(const Token* tok, const Library& library) +{ + return library.hasAnyTypeCheck("std::" + tok->str()) || + library.podtype("std::" + tok->str()) || + isStdContainerOrIterator(tok, library); +} + // Add std:: in front of std classes, when using namespace std; was given void Tokenizer::simplifyNamespaceStd() { @@ -10349,14 +10627,15 @@ void Tokenizer::simplifyNamespaceStd() if (start != tok && start->isName() && !start->isKeyword() && (!start->previous() || Token::Match(start->previous(), "[;{}]"))) userFunctions.insert(tok->str()); } - if (userFunctions.find(tok->str()) == userFunctions.end() && mSettings.library.matchArguments(tok, "std::" + tok->str())) + if ((userFunctions.find(tok->str()) == userFunctions.end() && mSettings.library.matchArguments(tok, "std::" + tok->str())) || + (tok->tokAt(-1)->isKeyword() && isLibraryType(tok, mSettings.library))) insert = true; } else if (Token::simpleMatch(tok->next(), "<") && - (isStdContainerOrIterator(tok, mSettings) || isStdSmartPointer(tok, mSettings))) + (isStdContainerOrIterator(tok, mSettings.library) || isStdSmartPointer(tok, mSettings.library))) insert = true; - else if (mSettings.library.hasAnyTypeCheck("std::" + tok->str()) || - mSettings.library.podtype("std::" + tok->str()) || - isStdContainerOrIterator(tok, mSettings)) + else if (isLibraryType(tok, mSettings.library)) + insert = true; + else if (Token::simpleMatch(tok, "aligned_storage")) insert = true; if (insert) { @@ -11173,6 +11452,57 @@ void Tokenizer::simplifyNamespaceAliases() } } +void Tokenizer::simplifyAlignedStorage() +{ + if (!isCPP()) + return; + + const Standards::cppstd_t std = mSettings.standards.cpp; + if (std < Standards::CPP11 || std >= Standards::CPP23) + return; + + for (Token *tok = list.front(); tok; tok = tok->next()) { + if (!Token::simpleMatch(tok, "std :: aligned_storage <")) + continue; + + tok = tok->tokAt(3); + const Token *end = tok->link(); + tok = tok->next(); + + if (!tok) + break; + + if (!end) + continue; + + for (; tok != end; tok = tok->next()) { + if (Token::simpleMatch(tok, ",")) { + tok = tok->next(); + break; + } + + if (Token::Match(tok, "(|<")) + tok = tok->link(); + } + + std::string str; + for (; tok != end; tok = tok->next()) { + str += " " + tok->str(); + } + + if (str.empty()) + continue; + + if (!Token::Match(tok, "> :: type %name%")) + continue; + + str = str.substr(1); + + tok = tok->tokAt(3); + tok->addAttributeAlignas(str); + } +} + void Tokenizer::setDirectives(std::list directives) { mDirectives = std::move(directives); diff --git a/lib/tokenize.h b/lib/tokenize.h index 94f08f9f68e..36f4e48c751 100644 --- a/lib/tokenize.h +++ b/lib/tokenize.h @@ -327,7 +327,7 @@ class CPPCHECKLIB Tokenizer { void checkForEnumsWithTypedef(); - void findComplicatedSyntaxErrorsInTemplates(); + void findComplicatedSyntaxErrorsInTemplates() const; protected: /** @@ -528,6 +528,11 @@ class CPPCHECKLIB Tokenizer { */ void simplifyNamespaceAliases(); + /** + * Handle std::aligned_storage<...> + */ + void simplifyAlignedStorage(); + /** * Convert C++17 style nested namespace to older style */ @@ -693,13 +698,14 @@ class CPPCHECKLIB Tokenizer { }; struct TypedefInfo { std::string name; + std::string originalName; std::string filename; - int lineNumber; - int column; + int lineNumber{}; + int column{}; int tagLine{-1}; int tagColumn{-1}; - bool used; - bool isFunctionPointer; + bool used{}; + bool isFunctionPointer{}; std::vector typedefInfoTokens; }; std::vector mTypedefInfo; diff --git a/lib/tokenlist.cpp b/lib/tokenlist.cpp index 3df17141f24..26b24d649c8 100644 --- a/lib/tokenlist.cpp +++ b/lib/tokenlist.cpp @@ -102,7 +102,7 @@ int TokenList::appendFileIfNew(std::string fileName) if (it != mFiles.cend()) return static_cast(std::distance(mFiles.cbegin(), it)); - assert(mTokensFrontBack->front == nullptr); // has no effect if tokens have already been created + assert(mSettings.clang || mTokensFrontBack->front == nullptr); // has no effect if tokens have already been created // The "mFiles" vector remembers what files have been tokenized.. mFiles.push_back(std::move(fileName)); @@ -333,10 +333,30 @@ bool TokenList::createTokensFromBuffer(const char* data, size_t size) //--------------------------------------------------------------------------- +#ifdef STORE_INPUT_DIR +#include +#include + +static void storeInput(const char* data, size_t size) +{ + static std::atomic_uint64_t num(0); + { + std::ofstream out(STORE_INPUT_DIR "/" + std::to_string(num++)); + out.write(data, size); + } +} +#endif + bool TokenList::createTokensFromBufferInternal(const char* data, size_t size, const std::string& file0) { +#ifdef STORE_INPUT_DIR + storeInput(data, size); +#endif + simplecpp::OutputList outputList; - simplecpp::TokenList tokens({data, size}, mFiles, file0, &outputList); + simplecpp::DUI dui; + dui.std = mSettings.standards.getStdForLanguage(mLang); + simplecpp::TokenList tokens({data, size}, mFiles, file0, dui, &outputList); createTokens(std::move(tokens)); @@ -443,10 +463,10 @@ static Token* skipDecl(Token* tok, std::vector* inner = nullptr) return Token::Match(tok, "%name%| ,|)"); }; - if (!Token::Match(tok->previous(), "( %name%")) + if (tok->varId() || !Token::Match(tok->previous(), "( %name%")) return tok; Token *vartok = tok; - while (Token::Match(vartok, "%name%|*|&|::|<")) { + while (Token::Match(vartok, "%name%|*|&|&&|::|<")) { if (vartok->str() == "<") { if (vartok->link()) vartok = vartok->link(); @@ -529,7 +549,7 @@ static bool iscast(const Token *tok, bool cpp) if (!Token::Match(tok2, "%name%|*|::")) return false; - if (tok2->isStandardType() && (tok2->strAt(1) != "(" || Token::Match(tok2->next(), "( * *| )"))) + if (tok2->isStandardType() && (tok2->strAt(1) != "(" || Token::simpleMatch(tok2->linkAt(1)->tokAt(-1), "* )"))) type = true; } @@ -591,7 +611,7 @@ static bool iscpp11init_impl(const Token * const tok) if (Token::Match(nameToken, "]|*")) { const Token* tok2 = nameToken; if (tok2->link()) { - while (tok2 && tok2->link()) + while (tok2 && precedes(tok2->link(), nameToken)) tok2 = tok2->link()->previous(); } else @@ -740,6 +760,8 @@ static void compileBinOp(Token *&tok, AST_state& state, void (*f)(Token *&tok, A binop->astOperand1(state.op.top()); state.op.pop(); } + if (!state.op.empty() && state.op.top() == binop) + throw InternalError(tok, "Syntax Error: Infinite loop when creating AST.", InternalError::AST); state.op.push(binop); } @@ -947,6 +969,21 @@ static bool isPrefixUnary(const Token* tok, bool cpp) return tok->strAt(-1) == ")" && iscast(tok->linkAt(-1), cpp); } +template )> +static T* skipTrailingReturnType(T* tok) +{ + if (!Token::Match(tok, ".|->")) // TODO: fix simplification (see garbageCode228) + return tok; + tok = tok->next(); + while (Token::Match(tok, "%type%|%name%|::|&|&&|*|<|(")) { + if (tok->link()) + tok = tok->link()->next(); + else + tok = tok->next(); + } + return tok; +} + /** * @throws InternalError thrown if unexpected tokens are encountered */ @@ -991,7 +1028,7 @@ static void compilePrecedence2(Token *&tok, AST_state& state) else compileUnaryOp(tok, state, compileScope); } else if (tok->str() == "[") { - if (state.cpp && isPrefixUnary(tok, /*cpp*/ true) && Token::Match(tok->link(), "] (|{|<")) { // Lambda + if (state.cpp && isPrefixUnary(tok, /*cpp*/ true) && Token::Match(tok->link(), "] [({<.]")) { // Lambda // What we do here: // - Nest the round bracket under the square bracket. // - Nest what follows the lambda (if anything) with the lambda opening [ @@ -1040,7 +1077,10 @@ static void compilePrecedence2(Token *&tok, AST_state& state) continue; } } else { - Token* const curlyBracket = squareBracket->link()->next(); + Token* curlyBracket = squareBracket->link()->next(); + curlyBracket = skipTrailingReturnType(curlyBracket); + if (!Token::simpleMatch(curlyBracket, "{")) + throw InternalError(tok, "Syntax error in lambda", InternalError::AST); squareBracket->astOperand1(curlyBracket); state.op.push(squareBracket); tok = curlyBracket->link() ? curlyBracket->link()->next() : nullptr; @@ -1485,15 +1525,7 @@ const Token* findLambdaEndTokenWithoutAST(const Token* tok) { tok = tok->link()->next(); if (Token::simpleMatch(tok, "mutable")) tok = tok->next(); - if (Token::Match(tok, ".|->")) { // trailing return type - tok = tok->next(); - while (Token::Match(tok, "%type%|%name%|::|&|&&|*|<|(")) { - if (tok->link()) - tok = tok->link()->next(); - else - tok = tok->next(); - } - } + tok = skipTrailingReturnType(tok); if (!(Token::simpleMatch(tok, "{") && tok->link())) return nullptr; return tok->link()->next(); @@ -1623,7 +1655,7 @@ static Token * createAstAtToken(Token *tok) if (Token::Match(tok, "%type%") && !Token::Match(tok, "return|throw|if|while|new|delete")) { bool isStandardTypeOrQualifier = false; Token* type = tok; - while (Token::Match(type, "%type%|*|&|<")) { + while (Token::Match(type, "%type%|*|&|&&|<")) { if (type->isName() && (type->isStandardType() || Token::Match(type, "const|mutable|static|volatile"))) isStandardTypeOrQualifier = true; if (type->str() == "<") { @@ -1676,6 +1708,8 @@ static Token * createAstAtToken(Token *tok) tok->next()->astOperand1(tok); tok->next()->astOperand2(colon); + createAstAtTokenInner(colon, tok->linkAt(1), cpp); + return decl; } } @@ -1817,7 +1851,7 @@ static Token * createAstAtToken(Token *tok) } typetok = typetok->next(); } - if (Token::Match(typetok, "%var% =") && typetok->varId()) + if (Token::Match(typetok, "%var% [={]")) tok = typetok; // Do not create AST for function declaration @@ -1925,12 +1959,18 @@ void TokenList::validateAst(bool print) const if ((tok->isAssignmentOp() || tok->isComparisonOp() || Token::Match(tok,"[|^/%]")) && tok->astOperand1() && !tok->astOperand2()) throw InternalError(tok, "Syntax Error: AST broken, binary operator has only one operand.", InternalError::AST); + if (!(tok->astOperand1() && tok->astOperand2()) && ((isC() && tok->str() == "&&") || Token::Match(tok, "%or%|%oror%"))) + throw InternalError(tok, "Syntax Error: AST broken, binary operator is missing operand(s).", InternalError::AST); + // Syntax error if we encounter "?" with operand2 that is not ":" if (tok->str() == "?") { if (!tok->astOperand1() || !tok->astOperand2()) throw InternalError(tok, "AST broken, ternary operator missing operand(s)", InternalError::AST); - if (tok->astOperand2()->str() != ":") + const Token* colon = tok->astOperand2(); + if (colon->str() != ":") throw InternalError(tok, "Syntax Error: AST broken, ternary operator lacks ':'.", InternalError::AST); + if ((colon->astOperand1() && !precedes(colon->astOperand1(), colon)) || !succeeds(colon->astOperand2(), colon)) + throw InternalError(tok, "AST broken, ternary operator has bad operand(s)", InternalError::AST); } // Check for endless recursion @@ -1980,7 +2020,7 @@ void TokenList::validateAst(bool print) const if (Token::simpleMatch(tok->previous(), "operator")) continue; // Skip incomplete code - if (!tok->astOperand1() && !tok->astOperand2() && !tok->astParent()) + if (!tok->astOperand1() && !tok->astOperand2() && !tok->astParent() && !(tok->str().size() == 2 && tok->str()[1] == '=')) continue; // Skip lambda assignment and/or initializer if (Token::Match(tok, "= {|^|[")) @@ -2004,9 +2044,9 @@ void TokenList::validateAst(bool print) const "' doesn't have two operands.", InternalError::AST); } - if (tok->str() == "case" && !tok->astOperand1()) { + if (!tok->astOperand1() && Token::Match(tok, "case|!")) { throw InternalError(tok, - "Syntax Error: AST broken, 'case' doesn't have an operand.", + "Syntax Error: AST broken, '" + tok->str() + "' doesn't have an operand.", InternalError::AST); } @@ -2307,7 +2347,7 @@ bool TokenList::isCPP() const return mLang == Standards::Language::CPP; } -const Token * TokenList::isFunctionHead(const Token *tok, const std::string &endsWith) +const Token * TokenList::isFunctionHead(const Token *tok, const std::string &suffix) { if (!tok) return nullptr; @@ -2320,11 +2360,11 @@ const Token * TokenList::isFunctionHead(const Token *tok, const std::string &end if (Token::Match(tok, ") ;|{|[")) { tok = tok->next(); while (tok && tok->str() == "[" && tok->link()) { - if (endsWith.find(tok->str()) != std::string::npos) + if (suffix.find(tok->str()) != std::string::npos) return tok; tok = tok->link()->next(); } - return (tok && endsWith.find(tok->str()) != std::string::npos) ? tok : nullptr; + return (tok && suffix.find(tok->str()) != std::string::npos) ? tok : nullptr; } if (tok->isCpp() && tok->str() == ")") { tok = tok->next(); @@ -2359,7 +2399,7 @@ const Token * TokenList::isFunctionHead(const Token *tok, const std::string &end } if (tok && tok->str() == ":" && !Token::Match(tok->next(), "%name%|::")) return nullptr; - return (tok && endsWith.find(tok->str()) != std::string::npos) ? tok : nullptr; + return (tok && suffix.find(tok->str()) != std::string::npos) ? tok : nullptr; } return nullptr; } diff --git a/lib/tokenlist.h b/lib/tokenlist.h index ae61fff6de4..e2cbb1b3cb7 100644 --- a/lib/tokenlist.h +++ b/lib/tokenlist.h @@ -206,10 +206,10 @@ class CPPCHECKLIB TokenList { /** * is token pointing at function head? * @param tok A '(' or ')' token in a possible function head - * @param endsWith string after function head - * @return token matching with endsWith if syntax seems to be a function head else nullptr + * @param suffix string after function head + * @return token matching with suffix if syntax seems to be a function head else nullptr */ - static const Token * isFunctionHead(const Token *tok, const std::string &endsWith); + static const Token * isFunctionHead(const Token *tok, const std::string &suffix); const Settings& getSettings() const { return mSettings; @@ -228,6 +228,7 @@ class CPPCHECKLIB TokenList { std::vector mOrigFiles; /** settings */ + // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members) - TokenList is move-only const Settings& mSettings; /** File is known to be C/C++ code */ diff --git a/lib/utils.h b/lib/utils.h index 14f5382a8bf..410bd7c6040 100644 --- a/lib/utils.h +++ b/lib/utils.h @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -220,7 +221,7 @@ bool strToInt(const std::string& str, T &num, std::string* err = nullptr) tmp = std::stoll(str, &idx); if (idx != str.size()) { if (err) - *err = "not an integer"; + *err = "not an integer (pos)"; return false; } } catch (const std::out_of_range&) { @@ -229,7 +230,7 @@ bool strToInt(const std::string& str, T &num, std::string* err = nullptr) return false; } catch (const std::invalid_argument &) { if (err) - *err = "not an integer"; + *err = "not an integer (invalid_argument)"; return false; } if (str.front() == '-' && std::numeric_limits::min() == 0) { @@ -237,6 +238,16 @@ bool strToInt(const std::string& str, T &num, std::string* err = nullptr) *err = "needs to be positive"; return false; } + if (str.front() != '+' && str.front() != '-' && isdigit(str.front()) == 0) { + if (err) + *err = "not an integer"; + return false; + } + if (str.size() > 1 && str.front() == '0') { + if (err) + *err = "not an integer"; + return false; + } if (tmp < std::numeric_limits::min() || tmp > std::numeric_limits::max()) { if (err) *err = "out of range (limits)"; @@ -255,7 +266,7 @@ bool strToInt(const std::string& str, T &num, std::string* err = nullptr) tmp = std::stoull(str, &idx); if (idx != str.size()) { if (err) - *err = "not an integer"; + *err = "not an integer (pos)"; return false; } } catch (const std::out_of_range&) { @@ -264,7 +275,7 @@ bool strToInt(const std::string& str, T &num, std::string* err = nullptr) return false; } catch (const std::invalid_argument &) { if (err) - *err = "not an integer"; + *err = "not an integer (invalid_argument)"; return false; } if (str.front() == '-') { @@ -272,6 +283,16 @@ bool strToInt(const std::string& str, T &num, std::string* err = nullptr) *err = "needs to be positive"; return false; } + if (str.front() != '+' && isdigit(str.front()) == 0) { + if (err) + *err = "not an integer"; + return false; + } + if (str.size() > 1 && str.front() == '0') { + if (err) + *err = "not an integer"; + return false; + } if (tmp > std::numeric_limits::max()) { if (err) *err = "out of range (limits)"; diff --git a/lib/valueflow.cpp b/lib/valueflow.cpp index 74763fde027..5dfe447cf68 100644 --- a/lib/valueflow.cpp +++ b/lib/valueflow.cpp @@ -89,6 +89,7 @@ #include "infer.h" #include "library.h" #include "mathlib.h" +#include "refthunk.h" #include "path.h" #include "platform.h" #include "programmemory.h" @@ -365,7 +366,7 @@ const Token *ValueFlow::parseCompareInt(const Token *tok, ValueFlow::Value &true }); } -static bool isEscapeScope(const Token* tok, const Settings& settings, bool unknown = false) +static bool isEscapeScope(const Token* tok, const Library& library, bool unknown = false) { if (!Token::simpleMatch(tok, "{")) return false; @@ -374,7 +375,7 @@ static bool isEscapeScope(const Token* tok, const Settings& settings, bool unkno if (termTok && termTok->scope() == tok->scope()) return true; std::string unknownFunction; - if (settings.library.isScopeNoReturn(tok->link(), &unknownFunction)) + if (library.isScopeNoReturn(tok->link(), &unknownFunction)) return unknownFunction.empty() || unknown; return false; } @@ -401,10 +402,14 @@ void ValueFlow::combineValueProperties(const ValueFlow::Value &value1, const Val result.valueType = value2.valueType; result.tokvalue = value2.tokvalue; } - if (value1.isIteratorValue()) + if (value1.isIteratorValue()) { result.valueType = value1.valueType; - if (value2.isIteratorValue()) + result.container = value1.container; + } + if (value2.isIteratorValue()) { result.valueType = value2.valueType; + result.container = value2.container; + } result.condition = value1.condition ? value1.condition : value2.condition; result.varId = (value1.varId != 0) ? value1.varId : value2.varId; result.varvalue = (result.varId == value1.varId) ? value1.varvalue : value2.varvalue; @@ -430,12 +435,7 @@ static void valueFlowNumber(TokenList &tokenlist, const Settings& settings) if (tokenlist.isCPP() || settings.standards.c >= Standards::C23) { for (Token *tok = tokenlist.front(); tok; tok = tok->next()) { - if (tok->isName() && !tok->varId() && Token::Match(tok, "%bool%")) { - ValueFlow::Value value(tok->str() == "true"); - if (!tok->isTemplateArg()) - value.setKnown(); - setTokenValue(tok, std::move(value), settings); - } else if (Token::Match(tok, "[(,] NULL [,)]")) { + if (Token::Match(tok, "[(,] NULL [,)]")) { // NULL function parameters are not simplified in the // normal tokenlist ValueFlow::Value value(0); @@ -692,57 +692,6 @@ static void valueFlowArray(TokenList& tokenlist, const Settings& settings) } } -static bool isNonZero(const Token* tok) -{ - return tok && (!tok->hasKnownIntValue() || tok->getKnownIntValue() != 0); -} - -static const Token* getOtherOperand(const Token* tok) -{ - if (!tok) - return nullptr; - if (!tok->astParent()) - return nullptr; - if (tok->astParent()->astOperand1() != tok) - return tok->astParent()->astOperand1(); - if (tok->astParent()->astOperand2() != tok) - return tok->astParent()->astOperand2(); - return nullptr; -} - -static void valueFlowArrayBool(TokenList& tokenlist, const Settings& settings) -{ - for (Token* tok = tokenlist.front(); tok; tok = tok->next()) { - if (tok->hasKnownIntValue()) - continue; - const Variable* var = nullptr; - bool known = false; - const auto val = - std::find_if(tok->values().cbegin(), tok->values().cend(), std::mem_fn(&ValueFlow::Value::isTokValue)); - if (val == tok->values().end()) { - var = tok->variable(); - known = true; - } else { - var = val->tokvalue->variable(); - known = val->isKnown(); - } - if (!var) - continue; - if (!var->isArray() || var->isArgument() || var->isStlType()) - continue; - if (isNonZero(getOtherOperand(tok)) && Token::Match(tok->astParent(), "%comp%")) - continue; - // TODO: Check for function argument - if ((astIsBool(tok->astParent()) && !Token::Match(tok->astParent(), "(|%name%")) || - (tok->astParent() && Token::Match(tok->astParent()->previous(), "if|while|for ("))) { - ValueFlow::Value value{1}; - if (known) - value.setKnown(); - setTokenValue(tok, std::move(value), settings); - } - } -} - static void valueFlowArrayElement(TokenList& tokenlist, const Settings& settings) { for (Token* tok = tokenlist.front(); tok; tok = tok->next()) { @@ -1170,7 +1119,7 @@ static void valueFlowImpossibleValues(TokenList& tokenList, const Settings& sett value.setImpossible(); setTokenValue(tok, std::move(value), settings); } else if (tok->variable() && tok->variable()->isArray() && !tok->variable()->isArgument() && - !tok->variable()->isStlType()) { + tok->variable()->getTypeName() != "std::array") { ValueFlow::Value value{0}; value.setImpossible(); setTokenValue(tok, std::move(value), settings); @@ -1179,6 +1128,10 @@ static void valueFlowImpossibleValues(TokenList& tokenList, const Settings& sett ValueFlow::Value value{0}; value.setImpossible(); setTokenValue(tok, std::move(value), settings); + } else if (tok->function() && tok->scope()->isExecutable()) { + ValueFlow::Value value{0}; + value.setImpossible(); + setTokenValue(tok, std::move(value), settings); } } } @@ -1520,6 +1473,19 @@ ValueFlow::Value ValueFlow::getLifetimeObjValue(const Token *tok, bool inconclus return std::move(values.front()); } +static bool checkDotLifetime(const Token* dot, const Token* tok) +{ + if (Token::simpleMatch(dot->astOperand1(), ".")) + return false; + if (!dot->astOperand2()) + return true; + if (dot->astOperand2()->varId() && tok->isUnaryOp("*")) + return dot->astOperand2()->variable() && dot->astOperand2()->variable()->isArray(); + if (dot->astOperand2()->valueType() && dot->astOperand2()->valueType()->reference != Reference::None) + return false; + return true; +} + template static std::vector getLifetimeTokens(const Token* tok, bool escape, @@ -1632,8 +1598,7 @@ static std::vector getLifetimeTokens(const Token* tok, if (vartok->str() == "[" || vartok->isUnaryOp("*")) vartok = vartok->astOperand1(); else if (vartok->str() == ".") { - if (!Token::simpleMatch(vartok->astOperand1(), ".") && - !(vartok->astOperand2() && vartok->astOperand2()->valueType() && vartok->astOperand2()->valueType()->reference != Reference::None)) + if (checkDotLifetime(vartok, tok)) vartok = vartok->astOperand1(); else break; @@ -1818,7 +1783,8 @@ static bool isNotEqual(std::pair x, std::pair x, const std::string& y, bool cpp, const Settings& settings) { TokenList tokenList(settings, cpp ? Standards::Language::CPP : Standards::Language::C); - tokenList.createTokensFromBuffer(y.data(), y.size()); // TODO: check result? + const std::string str(y + "\n"); + tokenList.createTokensFromBuffer(str.data(), str.size()); // TODO: check result? return isNotEqual(x, std::make_pair(tokenList.front(), tokenList.back())); } static bool isNotEqual(std::pair x, const ValueType* y, bool cpp, const Settings& settings) @@ -1957,6 +1923,35 @@ const Token* ValueFlow::getEndOfExprScope(const Token* tok, const Scope* default return end; } +static void getLhsLifetimeParentsImpl(const Token* lhs, const Library& library, std::vector& result) +{ + if (!lhs) + return; + + if (Token::simpleMatch(lhs, "[")) { + getLhsLifetimeParentsImpl(lhs->astOperand1(), library, result); + } else if (Token::simpleMatch(lhs, ".") && lhs->originalName() != "->") { + const Token* obj = lhs->astOperand1(); + if (Token::simpleMatch(obj, "[") && obj->exprId() > 0) + result.push_back(obj); + getLhsLifetimeParentsImpl(obj, library, result); + } else { + const Token* tok = getParentLifetime(lhs, library); + if (tok && tok->exprId() > 0) { + const Variable* var = tok->variable(); + if (!var || var->isLocal() || var->isArgument()) + result.push_back(tok); + } + } +} + +static std::vector getLhsLifetimeParents(const Token* lhs, const Library& library) +{ + std::vector result; + getLhsLifetimeParentsImpl(lhs, library, result); + return result; +} + static void valueFlowForwardLifetime(Token * tok, const TokenList &tokenlist, ErrorLogger &errorLogger, const Settings &settings) { // Forward lifetimes to constructed variable @@ -2009,14 +2004,8 @@ static void valueFlowForwardLifetime(Token * tok, const TokenList &tokenlist, Er if (val.lifetimeKind == ValueFlow::Value::LifetimeKind::Address) val.lifetimeKind = ValueFlow::Value::LifetimeKind::SubObject; } - // TODO: handle `[` - if (Token::simpleMatch(parent->astOperand1(), ".")) { - const Token* parentLifetime = - getParentLifetime(parent->astOperand1()->astOperand2(), settings.library); - if (parentLifetime && parentLifetime->exprId() > 0) { - valueFlowForward(nextExpression, endOfVarScope, parentLifetime, std::move(values), tokenlist, errorLogger, settings); - } - } + for (const Token *p : getLhsLifetimeParents(parent->astOperand1(), settings.library)) + valueFlowForward(nextExpression, endOfVarScope, p, values, tokenlist, errorLogger, settings); } // Constructor } else if (Token::simpleMatch(parent, "{") && !isScopeBracket(parent)) { @@ -2464,7 +2453,7 @@ static void valueFlowLifetimeFunction(Token *tok, const TokenList &tokenlist, Er const int returnContainer = settings.library.returnValueContainer(tok); if (returnContainer >= 0) { std::vector args = getArguments(tok); - for (int argnr = 1; argnr <= args.size(); ++argnr) { + for (size_t argnr = 1; argnr <= args.size(); ++argnr) { const Library::ArgumentChecks::IteratorInfo *i = settings.library.getArgIteratorInfo(tok, argnr); if (!i) continue; @@ -2524,6 +2513,8 @@ static void valueFlowLifetimeFunction(Token *tok, const TokenList &tokenlist, Er for (const Token* returnTok : returns) { if (returnTok == tok) continue; + if (!ValueFlow::isLifetimeBorrowed(returnTok, settings)) + return; const Variable *returnVar = ValueFlow::getLifetimeVariable(returnTok, settings); if (returnVar && returnVar->isArgument() && (returnVar->isConst() || !isVariableChanged(returnVar, settings))) { LifetimeStore ls = LifetimeStore::fromFunctionArg(f, tok, returnVar, tokenlist, settings, errorLogger); @@ -2596,7 +2587,7 @@ static void valueFlowLifetimeFunction(Token *tok, const TokenList &tokenlist, Er std::vector args = getArguments(tok); if (iArg > 0 && iArg <= args.size()) { const Token* varTok = args[iArg - 1]; - if (varTok->variable() && varTok->variable()->isLocal()) + if (varTok->variable() && varTok->variable()->isLocal() && varTok->variable()->isArray()) LifetimeStore{ varTok, "Passed to '" + tok->str() + "'.", ValueFlow::Value::LifetimeKind::Address }.byRef( tok->next(), tokenlist, errorLogger, settings); } @@ -3049,7 +3040,7 @@ static void valueFlowLifetime(TokenList &tokenlist, ErrorLogger &errorLogger, co // Skip if its a free function that doesnt yield an iterator to the container if (Token::Match(parent->previous(), "%name% (") && !contains({Library::Container::Yield::START_ITERATOR, Library::Container::Yield::END_ITERATOR}, - astFunctionYield(parent->previous(), settings))) + astFunctionYield(parent->previous(), settings.library))) continue; ValueFlow::Value master; @@ -3061,7 +3052,7 @@ static void valueFlowLifetime(TokenList &tokenlist, ErrorLogger &errorLogger, co master.lifetimeKind = ValueFlow::Value::LifetimeKind::Iterator; } else if (astIsIterator(parent) && Token::Match(parent->previous(), "%name% (") && contains({Library::Container::Yield::START_ITERATOR, Library::Container::Yield::END_ITERATOR}, - astFunctionYield(parent->previous(), settings))) { + astFunctionYield(parent->previous(), settings.library))) { master.errorPath.emplace_back(parent, "Iterator to container is created here."); master.lifetimeKind = ValueFlow::Value::LifetimeKind::Iterator; } else if ((astIsPointer(parent->tokAt(2)) && @@ -3122,7 +3113,7 @@ static void valueFlowLifetime(TokenList &tokenlist, ErrorLogger &errorLogger, co valueFlowLifetimeConstructor(tok->next(), tokenlist, errorLogger, settings); } // Check function calls - else if (Token::Match(tok, "%name% (") && !Token::simpleMatch(tok->linkAt(1), ") {")) { + else if (tok->scope()->isExecutable() && Token::Match(tok, "%name% (")) { valueFlowLifetimeFunction(tok, tokenlist, errorLogger, settings); } // Unique pointer lifetimes @@ -3301,8 +3292,11 @@ static void valueFlowAfterMove(const TokenList& tokenlist, const SymbolDatabase& ternaryColon = ternaryColon->astParent(); if (Token::simpleMatch(ternaryColon, ":")) { endOfFunctionCall = ternaryColon->astOperand2(); - if (Token::simpleMatch(endOfFunctionCall, "(")) - endOfFunctionCall = endOfFunctionCall->link(); + Token* next = nextAfterAstRightmostLeaf(endOfFunctionCall); + if (next) + endOfFunctionCall = next; + else + endOfFunctionCall = endOfFunctionCall->next(); } } ValueFlow::Value value; @@ -3484,7 +3478,7 @@ static void valueFlowConditionExpressions(const TokenList& tokenlist, } // Check if the block terminates early - if (isEscapeScope(blockTok, settings)) { + if (isEscapeScope(blockTok, settings.library)) { const Scope* scope2 = scope; // If escaping a loop then only use the loop scope if (isBreakOrContinueScope(blockTok->link())) { @@ -3585,6 +3579,9 @@ static void valueFlowSymbolic(const TokenList& tokenlist, const SymbolDatabase& continue; if (tok->astOperand2()->exprId() == 0) continue; + if (tok->astOperand2()->variable() && tok->astOperand2()->variable()->isArray() && + tok->astOperand1()->valueType() && tok->astOperand1()->valueType()->pointer) // array to pointer decay + continue; if (!isConstExpression(tok->astOperand2(), settings.library)) continue; if (tok->astOperand1()->valueType() && tok->astOperand2()->valueType()) { @@ -3823,12 +3820,27 @@ static void valueFlowForwardConst(Token* start, { if (!precedes(start, end)) throw InternalError(var->nameToken(), "valueFlowForwardConst: start token does not precede the end token."); + const bool hasContainerSizeValue = std::any_of(values.begin(), values.end(), [](const ValueFlow::Value& value) { + return value.isContainerSizeValue(); + }); for (Token* tok = start; tok != end; tok = tok->next()) { if (tok->varId() == var->declarationId()) { for (const ValueFlow::Value& value : values) setTokenValue(tok, value, settings); } else { [&] { + // Add the container size to iterators of the container (mirrors ContainerExpressionAnalyzer::match) + if (hasContainerSizeValue && isIteratorOf(tok, var->declarationId())) { + for (const ValueFlow::Value& value : values) { + if (!value.isContainerSizeValue()) + continue; + ValueFlow::Value sizeValue = value; + if (!sizeValue.container) + sizeValue.container = var->nameToken(); + setTokenValue(tok, std::move(sizeValue), settings); + } + return; + } // Follow references const auto& refs = tok->refs(); auto it = std::find_if(refs.cbegin(), refs.cend(), [&](const ReferenceToken& ref) { @@ -4081,7 +4093,7 @@ static std::list truncateValues(std::list va } if (value.isIntValue() && sz > 0 && sz < sizeof(MathLib::biguint)) - value.intvalue = ValueFlow::truncateIntValue(value.intvalue, sz, dst->sign); + value.intvalue = ValueFlow::truncateIntValue(value.intvalue, sz, dst, settings.platform); } return values; } @@ -4101,6 +4113,8 @@ static bool isVariableInit(const Token *tok) return false; if (var->nameToken() != tok->astOperand1()) return false; + if (var->isPointer()) + return true; const ValueType* vt = var->valueType(); if (!vt) return false; @@ -4172,11 +4186,15 @@ static void valueFlowAfterAssign(const TokenList &tokenlist, values.remove_if([&](const ValueFlow::Value& value) { return types.count(value.valueType) > 0; }); - // Remove container size if its not a container - if (!astIsContainer(tok->astOperand2())) + // Remove container size if its not a container - unless the size records its container + // and flows into a pointer to the container data (e.g. p = v.data()) + if (!astIsContainer(tok->astOperand2())) { + const bool lhsIsPointer = astIsPointer(tok->astOperand1()); values.remove_if([&](const ValueFlow::Value& value) { - return value.valueType == ValueFlow::Value::ValueType::CONTAINER_SIZE; + return value.valueType == ValueFlow::Value::ValueType::CONTAINER_SIZE && + (!value.container || !lhsIsPointer); }); + } // Remove symbolic values that are the same as the LHS values.remove_if([&](const ValueFlow::Value& value) { if (value.isSymbolicValue() && value.tokvalue) @@ -4236,6 +4254,7 @@ static void valueFlowAfterAssign(const TokenList &tokenlist, continue; const Token* expr = value.tokvalue; value.intvalue = -value.intvalue; + value.invertBound(); value.tokvalue = tok->astOperand1(); // Skip if it intersects with an already assigned symbol @@ -4627,6 +4646,14 @@ struct ConditionHandler { }); } + static void lowerToInconclusive(std::list& values) + { + for (ValueFlow::Value& v : values) { + if (!v.isImpossible()) + v.setInconclusive(); + } + } + void afterCondition(TokenList& tokenlist, const SymbolDatabase& symboldatabase, ErrorLogger& errorLogger, @@ -4859,10 +4886,13 @@ struct ConditionHandler { else if (!dead_if) dead_if = isReturnScope(after, settings.library, &unknownFunction); + // If the taken branch might not return (it ends in a call to an unknown, + // possibly noreturn function) then its values might not flow past the + // conditional code -> lower them to inconclusive. if (!dead_if && unknownFunction) { if (settings.debugwarnings) bailout(tokenlist, errorLogger, unknownFunction, "possible noreturn scope"); - return; + lowerToInconclusive(thenValues); } if (Token::simpleMatch(after, "} else {")) { @@ -4873,7 +4903,7 @@ struct ConditionHandler { if (!dead_else && unknownFunction) { if (settings.debugwarnings) bailout(tokenlist, errorLogger, unknownFunction, "possible noreturn scope"); - return; + lowerToInconclusive(elseValues); } } @@ -4889,11 +4919,15 @@ struct ConditionHandler { std::copy_if(thenValues.cbegin(), thenValues.cend(), std::back_inserter(values), - std::mem_fn(&ValueFlow::Value::isPossible)); + [](const ValueFlow::Value& v) { + return v.isPossible() || v.isInconclusive(); + }); std::copy_if(elseValues.cbegin(), elseValues.cend(), std::back_inserter(values), - std::mem_fn(&ValueFlow::Value::isPossible)); + [](const ValueFlow::Value& v) { + return v.isPossible() || v.isInconclusive(); + }); } if (values.empty()) @@ -4966,6 +5000,24 @@ static void valueFlowCondition(const ValuePtr& handler, handler->afterCondition(tokenlist, symboldatabase, errorLogger, settings, skippedFunctions); } +static const Token* getConditionVariable(const Token* tok) +{ + if (tok->str() == "!") + return tok->astOperand1(); + + if (const Token* parent = tok->astParent()) { + if (Token::Match(parent, "%oror%|&&|?") || + Token::Match(parent->previous(), "if|while (") || + (parent->str() == ";" && astIsLHS(tok) && Token::simpleMatch(parent->astParent(), ";"))) { // for loop condition + if (Token::simpleMatch(tok, "=")) + return tok->astOperand1(); + if (!Token::Match(tok, "%comp%|%assign%")) + return tok; + } + } + return nullptr; +} + struct SimpleConditionHandler : ConditionHandler { std::vector parse(const Token* tok, const Settings& /*settings*/) const override { @@ -4984,19 +5036,7 @@ struct SimpleConditionHandler : ConditionHandler { if (!conds.empty()) return conds; - const Token* vartok = nullptr; - - if (tok->str() == "!") { - vartok = tok->astOperand1(); - - } else if (tok->astParent() && (Token::Match(tok->astParent(), "%oror%|&&|?") || - Token::Match(tok->astParent()->previous(), "if|while ("))) { - if (Token::simpleMatch(tok, "=")) - vartok = tok->astOperand1(); - else if (!Token::Match(tok, "%comp%|%assign%")) - vartok = tok; - } - + const Token* vartok = getConditionVariable(tok); if (!vartok) return {}; Condition cond; @@ -5088,7 +5128,7 @@ static void valueFlowInferCondition(TokenList& tokenlist, const Settings& settin } } else if (Token::Match(tok->astParent(), "?|&&|!|%oror%") || Token::Match(tok->astParent()->previous(), "if|while (") || - (astIsPointer(tok) && isUsedAsBool(tok, settings))) { + ((astIsPointer(tok) || tok->function()) && isUsedAsBool(tok, settings))) { std::vector result = infer(makeIntegralInferModel(), "!=", tok->values(), 0); if (result.size() != 1) continue; @@ -5163,9 +5203,9 @@ struct SymbolicConditionHandler : SimpleConditionHandler { }; static bool valueFlowForLoop2(const Token *tok, - ProgramMemory *memory1, - ProgramMemory *memory2, - ProgramMemory *memoryAfter, + ProgramMemory &memory1, + ProgramMemory &memory2, + ProgramMemory &memoryAfter, const Settings& settings) { // for ( firstExpression ; secondExpression ; thirdExpression ) @@ -5180,8 +5220,18 @@ static bool valueFlowForLoop2(const Token *tok, if (error) return false; execute(secondExpression, programMemory, &result, &error, settings); - if (result == 0) // 2nd expression is false => no looping + if (result == 0) { + if (!error) { // 2nd expression is false => no looping + ProgramMemory startMemory(programMemory); + ProgramMemory endMemory(programMemory); + + memory1.swap(startMemory); + memory2.swap(endMemory); + memoryAfter.swap(programMemory); + return true; + } return false; + } if (error) { // If a variable is reassigned in second expression, return false bool reassign = false; @@ -5208,13 +5258,10 @@ static bool valueFlowForLoop2(const Token *tok, } // TODO: add bailout message - if (memory1) - memory1->swap(startMemory); + memory1.swap(startMemory); if (!error) { - if (memory2) - memory2->swap(endMemory); - if (memoryAfter) - memoryAfter->swap(programMemory); + memory2.swap(endMemory); + memoryAfter.swap(programMemory); } return true; @@ -5341,6 +5388,11 @@ static void valueFlowForLoopSimplifyAfter(Token* fortok, nonneg int varid, const endToken = fortok->scope()->bodyEnd; Token* blockTok = fortok->linkAt(1)->linkAt(1); + if (const Token* escape = findEscapeStatement(blockTok->scope(), settings.library)) { + if (settings.debugwarnings) + bailout(tokenlist, errorLogger, escape, "For loop variable bailout on escape statement"); + return; + } if (blockTok != endToken) { ValueFlow::Value v{num}; v.errorPath.emplace_back(fortok,"After for loop, " + var->name() + " has value " + v.infoString()); @@ -5389,24 +5441,33 @@ static void valueFlowForLoop(const TokenList &tokenlist, const SymbolDatabase& s valueFlowForLoopSimplifyAfter(tok, varid, afterValue, tokenlist, errorLogger, settings); } else { ProgramMemory mem1, mem2, memAfter; - if (valueFlowForLoop2(tok, &mem1, &mem2, &memAfter, settings)) { - for (const auto& p : mem1) { - if (!p.second.isIntValue()) - continue; - if (p.second.isImpossible()) - continue; - if (p.first.tok->varId() == 0) - continue; - valueFlowForLoopSimplify(bodyStart, p.first.tok, false, p.second.intvalue, tokenlist, errorLogger, settings); - } - for (const auto& p : mem2) { - if (!p.second.isIntValue()) - continue; - if (p.second.isImpossible()) - continue; - if (p.first.tok->varId() == 0) - continue; - valueFlowForLoopSimplify(bodyStart, p.first.tok, false, p.second.intvalue, tokenlist, errorLogger, settings); + if (valueFlowForLoop2(tok, mem1, mem2, memAfter, settings)) { + if (mem1 == memAfter) { // #8192 check if loop never runs + Token* condTok = getCondTok(tok); + if (condTok && !condTok->hasKnownIntValue()) { + ValueFlow::Value v(0); + v.setKnown(); + ValueFlow::setTokenValue(condTok, std::move(v), settings); + } + } else { + for (const auto& p : mem1) { + if (!p.second.isIntValue()) + continue; + if (p.second.isImpossible()) + continue; + if (p.first.tok->varId() == 0) + continue; + valueFlowForLoopSimplify(bodyStart, p.first.tok, false, p.second.intvalue, tokenlist, errorLogger, settings); + } + for (const auto& p : mem2) { + if (!p.second.isIntValue()) + continue; + if (p.second.isImpossible()) + continue; + if (p.first.tok->varId() == 0) + continue; + valueFlowForLoopSimplify(bodyStart, p.first.tok, false, p.second.intvalue, tokenlist, errorLogger, settings); + } } for (const auto& p : memAfter) { if (!p.second.isIntValue()) @@ -5699,7 +5760,7 @@ static void valueFlowSubFunction(const TokenList& tokenlist, // Remove uninit values if argument is passed by value if (argtok->variable() && !argtok->variable()->isPointer() && argvalues.size() == 1 && argvalues.front().isUninitValue()) { - if (CheckUninitVar::isVariableUsage(argtok, settings.library, false, CheckUninitVar::Alloc::NO_ALLOC, 0)) + if (CheckUninitVarImpl::isVariableUsage(argtok, settings.library, false, CheckUninitVarImpl::Alloc::NO_ALLOC, 0)) continue; } @@ -5738,7 +5799,7 @@ static void valueFlowFunctionDefaultParameter(const TokenList& tokenlist, const const Function* function = scope->function; if (!function) continue; - for (nonneg int arg = function->minArgCount(); arg < function->argCount(); arg++) { + for (size_t arg = function->minArgCount(); arg < function->argCount(); arg++) { const Variable* var = function->getArgumentVar(arg); if (var && var->hasDefault() && Token::Match(var->nameToken(), "%var% = %num%|%str%|%char%|%name% [,)]")) { const std::list &values = var->nameToken()->tokAt(2)->values(); @@ -5895,7 +5956,7 @@ static bool needsInitialization(const Variable* var) return true; if (var->valueType()->type == ValueType::Type::ITERATOR) return true; - if (var->isStlType() && var->isArray()) { + if (var->isStlType() && var->getTypeName() == "std::array") { if (const Token* ctt = var->valueType()->containerTypeToken) { if (ctt->isStandardType()) return true; @@ -6138,7 +6199,7 @@ static bool isContainerSizeChangedByFunction(const Token* tok, } bool inconclusive = false; - const bool isChanged = isVariableChangedByFunctionCall(tok, indirect, settings, &inconclusive); + const bool isChanged = isVariableChangedByFunctionCall(tok, indirect, settings.library, &inconclusive); return (isChanged || inconclusive); } @@ -6223,6 +6284,8 @@ bool ValueFlow::isContainerSizeChanged(const Token* tok, int indirect, const Set return true; if (astIsLHS(tok) && Token::simpleMatch(tok->astParent(), "[")) return tok->valueType()->container->stdAssociativeLike; + if (Token::simpleMatch(tok->astParent(), "*") && indirect > 0) + return isContainerSizeChanged(tok->astParent(), indirect - 1, settings, depth + 1); const Library::Container::Action action = astContainerAction(tok, settings.library); switch (action) { case Library::Container::Action::RESIZE: @@ -6341,9 +6404,9 @@ static void valueFlowSmartPointer(TokenList &tokenlist, ErrorLogger & errorLogge } } -static Library::Container::Yield findIteratorYield(Token* tok, const Token*& ftok, const Settings& settings) +static Library::Container::Yield findIteratorYield(Token* tok, const Token*& ftok, const Library& library) { - auto yield = astContainerYield(tok, settings.library, &ftok); + auto yield = astContainerYield(tok, library, &ftok); if (ftok) return yield; @@ -6351,7 +6414,7 @@ static Library::Container::Yield findIteratorYield(Token* tok, const Token*& fto return yield; // begin/end free functions - return astFunctionYield(tok->astParent()->previous(), settings, &ftok); + return astFunctionYield(tok->astParent()->previous(), library, &ftok); } static void valueFlowIterators(TokenList& tokenlist, const Settings& settings) @@ -6364,20 +6427,25 @@ static void valueFlowIterators(TokenList& tokenlist, const Settings& settings) if (!astIsContainer(tok)) continue; const Token* ftok = nullptr; - const Library::Container::Yield yield = findIteratorYield(tok, ftok, settings); + const Library::Container::Yield yield = findIteratorYield(tok, ftok, settings.library); if (!ftok) continue; - if (yield == Library::Container::Yield::START_ITERATOR) { - ValueFlow::Value v(0); - v.setKnown(); - v.valueType = ValueFlow::Value::ValueType::ITERATOR_START; - setTokenValue(const_cast(ftok)->next(), std::move(v), settings); - } else if (yield == Library::Container::Yield::END_ITERATOR) { - ValueFlow::Value v(0); - v.setKnown(); - v.valueType = ValueFlow::Value::ValueType::ITERATOR_END; - setTokenValue(const_cast(ftok)->next(), std::move(v), settings); - } + if (yield != Library::Container::Yield::START_ITERATOR && yield != Library::Container::Yield::END_ITERATOR) + continue; + // The iterator value records the container it iterates. A pointer or a reference only + // transports the iterator, so record the container it refers to instead. + const Token* containerTok = tok; + if (astIsPointer(containerTok) || (containerTok->variable() && containerTok->variable()->isReference())) { + const ValueFlow::Value lifetime = ValueFlow::getLifetimeObjValue(containerTok); + if (lifetime.tokvalue && astIsContainer(lifetime.tokvalue) && !astIsPointer(lifetime.tokvalue)) + containerTok = lifetime.tokvalue; + } + ValueFlow::Value v(0); + v.setKnown(); + v.valueType = yield == Library::Container::Yield::START_ITERATOR ? ValueFlow::Value::ValueType::ITERATOR_START + : ValueFlow::Value::ValueType::ITERATOR_END; + v.container = containerTok; + setTokenValue(const_cast(ftok)->next(), std::move(v), settings); } } @@ -6803,17 +6871,17 @@ static void valueFlowContainerSize(const TokenList& tokenlist, } else if (tok->str() == "+=" && astIsContainer(tok->astOperand1())) { const Token* containerTok = tok->astOperand1(); const Token* valueTok = tok->astOperand2(); - const MathLib::bigint size = ValueFlow::valueFlowGetStrLength(valueTok); + const MathLib::bigint size = ValueFlow::valueFlowGetStrLength(valueTok, settings.library); forwardMinimumContainerSize(size, tok, containerTok); } else if (tok->str() == "=" && Token::simpleMatch(tok->astOperand2(), "+") && astIsContainerString(tok)) { const Token* tok2 = tok->astOperand2(); MathLib::bigint size = 0; while (Token::simpleMatch(tok2, "+") && tok2->astOperand2()) { - size += ValueFlow::valueFlowGetStrLength(tok2->astOperand2()); + size += ValueFlow::valueFlowGetStrLength(tok2->astOperand2(), settings.library); tok2 = tok2->astOperand1(); } - size += ValueFlow::valueFlowGetStrLength(tok2); + size += ValueFlow::valueFlowGetStrLength(tok2, settings.library); forwardMinimumContainerSize(size, tok, tok->astOperand1()); } } @@ -6929,6 +6997,12 @@ static void valueFlowDynamicBufferSize(const TokenList& tokenlist, const SymbolD auto getBufferSizeFromNew = [&](const Token* newTok) -> MathLib::bigint { MathLib::bigint sizeValue = -1, numElem = -1; + // ::operator new(size_t size) + if (Token::Match(newTok->astOperand1(), "::| operatornew")) { + const Token *sizeTok = newTok->astOperand2(); + return sizeTok->hasKnownIntValue() ? sizeTok->getKnownIntValue() : -1; + } + if (newTok && newTok->astOperand1()) { // number of elements const Token* bracTok = nullptr, *typeTok = nullptr; if (newTok->astOperand1()->str() == "[") @@ -6975,7 +7049,7 @@ static void valueFlowDynamicBufferSize(const TokenList& tokenlist, const SymbolD if (!rhs) continue; - const bool isNew = rhs->isCpp() && rhs->str() == "new"; + const bool isNew = rhs->isCpp() && (rhs->str() == "new" || (rhs->str() == "(" && Token::Match(rhs->astOperand1(), "::| operatornew"))); if (!isNew && !Token::Match(rhs->previous(), "%name% (")) continue; @@ -6999,7 +7073,7 @@ static bool getMinMaxValues(const std::string& typestr, MathLib::bigint& maxvalue) { TokenList typeTokens(settings, cpp ? Standards::Language::CPP : Standards::Language::C); - const std::string str(typestr + ";"); + const std::string str(typestr + ";\n"); if (!typeTokens.createTokensFromBuffer(str.data(), str.size())) return false; typeTokens.simplifyPlatformTypes(); @@ -7190,10 +7264,10 @@ struct ValueFlowState { : tokenlist(tokenlist), symboldatabase(symboldatabase), errorLogger(errorLogger), settings(settings) {} - TokenList& tokenlist; - SymbolDatabase& symboldatabase; - ErrorLogger& errorLogger; - const Settings& settings; + RefThunk tokenlist; + RefThunk symboldatabase; + RefThunk errorLogger; + RefThunk settings; std::set skippedFunctions; }; @@ -7229,31 +7303,31 @@ struct ValueFlowPassRunner { bool run(std::initializer_list> passes) const { std::size_t values = 0; - std::size_t n = state.settings.vfOptions.maxIterations; - const bool doProgress = state.settings.reportProgress >= 0; + std::size_t n = state.settings().vfOptions.maxIterations; + const bool doProgress = state.settings().reportProgress >= 0; while (n > 0 && values != getTotalValues()) { values = getTotalValues(); - const std::size_t passnum = state.settings.vfOptions.maxIterations - n + 1; + const std::size_t passnum = state.settings().vfOptions.maxIterations - n + 1; const std::string passnum_s = std::to_string(passnum); if (std::any_of(passes.begin(), passes.end(), [&](const ValuePtr& pass) { // the string concatination is a hot spot in TestIO::testScanfArgument and TestIO::testPrintfArgumentVariables std::string stage = doProgress ? std::string("ValueFlow::") + pass->name() + (' ' + passnum_s) : ""; - ProgressReporter progressReporter(state.errorLogger, state.settings.reportProgress, state.tokenlist.getSourceFilePath(), std::move(stage)); + ProgressReporter progressReporter(state.errorLogger, state.settings().reportProgress, state.tokenlist().getSourceFilePath(), std::move(stage)); return run(pass, passnum); })) return true; --n; } - if (state.settings.debugwarnings) { + if (state.settings().debugwarnings) { if (n == 0 && values != getTotalValues()) { - ErrorMessage::FileLocation loc(state.tokenlist.getFiles()[0], 0, 0); + ErrorMessage::FileLocation loc(state.tokenlist().getFiles()[0], 0, 0); ErrorMessage errmsg({std::move(loc)}, "", Severity::debug, "ValueFlow maximum iterations exceeded", "valueFlowMaxIterations", Certainty::normal); - state.errorLogger.reportErr(errmsg); + state.errorLogger().reportErr(errmsg); } } return false; @@ -7266,7 +7340,7 @@ struct ValueFlowPassRunner { // TODO: add bailout message return true; } - if (!state.tokenlist.isCPP() && pass->cpp()) + if (!state.tokenlist().isCPP() && pass->cpp()) return false; if (timerResults) { std::string name = pass->name(); @@ -7274,7 +7348,7 @@ struct ValueFlowPassRunner { name += ' '; name += std::to_string(it); } - Timer t(name, state.settings.showtime, timerResults); + Timer t(name, timerResults); pass->run(state); } else { pass->run(state); @@ -7285,15 +7359,15 @@ struct ValueFlowPassRunner { std::size_t getTotalValues() const { std::size_t n = 1; - for (Token* tok = state.tokenlist.front(); tok; tok = tok->next()) + for (Token* tok = state.tokenlist().front(); tok; tok = tok->next()) n += tok->values().size(); return n; } void setSkippedFunctions() { - if (state.settings.vfOptions.maxIfCount > 0) { - for (const Scope* functionScope : state.symboldatabase.functionScopes) { + if (state.settings().vfOptions.maxIfCount > 0) { + for (const Scope* functionScope : state.symboldatabase().functionScopes) { int countIfScopes = 0; std::vector scopes{functionScope}; while (!scopes.empty()) { @@ -7305,22 +7379,22 @@ struct ValueFlowPassRunner { ++countIfScopes; } } - if (countIfScopes > state.settings.vfOptions.maxIfCount) { + if (countIfScopes > state.settings().vfOptions.maxIfCount) { state.skippedFunctions.emplace(functionScope); - if (state.settings.severity.isEnabled(Severity::information)) { + if (state.settings().severity.isEnabled(Severity::information)) { const std::string& functionName = functionScope->className; std::list callstack( 1, - ErrorMessage::FileLocation(functionScope->bodyStart, &state.tokenlist)); + ErrorMessage::FileLocation(functionScope->bodyStart, &state.tokenlist())); const ErrorMessage errmsg(std::move(callstack), - state.tokenlist.getSourceFilePath(), + state.tokenlist().getSourceFilePath(), Severity::information, "Limiting ValueFlow analysis in function '" + functionName + "' since it is too complex. " "Please specify --check-level=exhaustive to perform full analysis.", "checkLevelNormal", // TODO: use more specific ID Certainty::normal); - state.errorLogger.reportErr(errmsg); + state.errorLogger().reportErr(errmsg); } } } @@ -7329,8 +7403,8 @@ struct ValueFlowPassRunner { void setStopTime() { - if (state.settings.vfOptions.maxTime >= 0) - stop = Clock::now() + std::chrono::seconds{state.settings.vfOptions.maxTime}; + if (state.settings().vfOptions.maxTime >= 0) + stop = Clock::now() + std::chrono::seconds{state.settings().vfOptions.maxTime}; } ValueFlowState state; @@ -7428,7 +7502,6 @@ void ValueFlow::setValues(TokenList& tokenlist, VFA(valueFlowSymbolicOperators(symboldatabase, settings)), VFA(valueFlowCondition(SymbolicConditionHandler{}, tokenlist, symboldatabase, errorLogger, settings, skippedFunctions)), VFA(valueFlowSymbolicInfer(symboldatabase, settings)), - VFA(valueFlowArrayBool(tokenlist, settings)), VFA(valueFlowArrayElement(tokenlist, settings)), VFA(valueFlowRightShift(tokenlist, settings)), VFA_CPP( @@ -7439,6 +7512,7 @@ void ValueFlow::setValues(TokenList& tokenlist, VFA(valueFlowInferCondition(tokenlist, settings)), VFA(valueFlowSwitchVariable(tokenlist, symboldatabase, errorLogger, settings)), VFA(valueFlowForLoop(tokenlist, symboldatabase, errorLogger, settings)), + VFA(valueFlowDynamicBufferSize(tokenlist, symboldatabase, errorLogger, settings)), VFA(valueFlowSubFunction(tokenlist, symboldatabase, errorLogger, settings)), VFA(valueFlowFunctionReturn(tokenlist, errorLogger, settings)), VFA(valueFlowLifetime(tokenlist, errorLogger, settings)), @@ -7455,7 +7529,6 @@ void ValueFlow::setValues(TokenList& tokenlist, }); runner.run_once({ - VFA(valueFlowDynamicBufferSize(tokenlist, symboldatabase, errorLogger, settings)), VFA(valueFlowDebug(tokenlist, errorLogger, settings)), // TODO: add option to print it after each step/iteration }); } diff --git a/lib/valueptr.h b/lib/valueptr.h index 270b5f58fc6..b6f02ef1186 100644 --- a/lib/valueptr.h +++ b/lib/valueptr.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/lib/version.h b/lib/version.h index 2a64885ffa8..dba47774dce 100644 --- a/lib/version.h +++ b/lib/version.h @@ -20,8 +20,8 @@ #ifndef versionH #define versionH -#define CPPCHECK_VERSION_STRING "2.21 dev" -#define CPPCHECK_VERSION 2,20,99,0 +#define CPPCHECK_VERSION_STRING "2.23 dev" +#define CPPCHECK_VERSION 2,22,99,0 #define LEGALCOPYRIGHT L"Copyright (C) 2007-2026 Cppcheck team." diff --git a/lib/vf_analyzers.cpp b/lib/vf_analyzers.cpp index 58fbe0b6d61..27c27934f31 100644 --- a/lib/vf_analyzers.cpp +++ b/lib/vf_analyzers.cpp @@ -24,6 +24,7 @@ #include "config.h" #include "library.h" #include "mathlib.h" +#include "refthunk.h" #include "programmemory.h" #include "smallvector.h" #include "settings.h" @@ -59,10 +60,10 @@ static bool isDereferenceOp(const Token* tok) } struct ValueFlowAnalyzer : Analyzer { - const Settings& settings; + RefThunk settings; ProgramMemoryState pms; - explicit ValueFlowAnalyzer(const Settings& s) : settings(s), pms(settings) {} + explicit ValueFlowAnalyzer(const Settings& s) : settings(s), pms(s) {} virtual const ValueFlow::Value* getValue(const Token* tok) const = 0; virtual ValueFlow::Value* getValue(const Token* tok) = 0; @@ -222,7 +223,7 @@ struct ValueFlowAnalyzer : Analyzer { return Action::Read; } bool inconclusive = false; - if (isVariableChangedByFunctionCall(tok, getIndirect(tok), getSettings(), &inconclusive)) + if (isVariableChangedByFunctionCall(tok, getIndirect(tok), getSettings().library, &inconclusive)) return Action::Read | Action::Invalid; if (inconclusive) return Action::Read | Action::Inconclusive; @@ -357,7 +358,7 @@ struct ValueFlowAnalyzer : Analyzer { if (dst) { const size_t sz = dst->getSizeOf(settings, ValueType::Accuracy::ExactOrZero, ValueType::SizeOf::Pointer); if (sz > 0 && sz < sizeof(MathLib::biguint)) { - MathLib::bigint newvalue = ValueFlow::truncateIntValue(value->intvalue, sz, dst->sign); + MathLib::bigint newvalue = ValueFlow::truncateIntValue(value->intvalue, sz, dst, settings().platform); /* Handle overflow/underflow for value bounds */ if (value->bound != ValueFlow::Value::Bound::Point) { @@ -673,21 +674,25 @@ struct ValueFlowAnalyzer : Analyzer { } template - std::vector evaluateInt(const Token* tok, F getProgramMemory) const + std::vector evaluateInt(const Token* tok, F getProgramMemoryFunc) const { if (const ValueFlow::Value* v = tok->getKnownValue(ValueFlow::Value::ValueType::INT)) - return {static_cast(v->intvalue)}; + return {v->intvalue}; std::vector result; - ProgramMemory pm = getProgramMemory(); + // Pass the tracked values so a cached program-memory value that depends on one (e.g. 'h(p)' + // after 'p' was reassigned) is re-evaluated rather than served stale. The memory is built + // from the same state, so compute it once and hand it to the builder. + const ProgramState vars = getProgramState(); + ProgramMemory pm = getProgramMemoryFunc(vars); if (Token::Match(tok, "&&|%oror%")) { - if (conditionIsTrue(tok, pm, getSettings())) + if (conditionIsTrue(tok, pm, getSettings(), vars)) result.push_back(1); - if (conditionIsFalse(tok, std::move(pm), getSettings())) + if (conditionIsFalse(tok, std::move(pm), getSettings(), vars)) result.push_back(0); } else { MathLib::bigint out = 0; bool error = false; - execute(tok, pm, &out, &error, getSettings()); + execute(tok, pm, &out, &error, getSettings(), vars); if (!error) result.push_back(out); } @@ -696,16 +701,16 @@ struct ValueFlowAnalyzer : Analyzer { std::vector evaluateInt(const Token* tok) const { - return evaluateInt(tok, [&] { - return ProgramMemory{getProgramState()}; + return evaluateInt(tok, [](const ProgramState& vars) { + return ProgramMemory{vars}; }); } std::vector evaluate(Evaluate e, const Token* tok, const Token* ctx = nullptr) const override { if (e == Evaluate::Integral) { - return evaluateInt(tok, [&] { - return pms.get(tok, ctx, getProgramState()); + return evaluateInt(tok, [&](const ProgramState& vars) { + return pms.get(tok, ctx, vars); }); } if (e == Evaluate::ContainerEmpty) { @@ -717,36 +722,49 @@ struct ValueFlowAnalyzer : Analyzer { ProgramMemory pm = pms.get(tok, ctx, getProgramState()); MathLib::bigint out = 0; if (pm.getContainerEmptyValue(tok->exprId(), out)) - return {static_cast(out)}; + return {out}; return {}; } return {}; } - void assume(const Token* tok, bool state, unsigned int flags) override { - // Update program state - pms.removeModifiedVars(tok); - pms.addState(tok, getProgramState()); - pms.assume(tok, state, flags & Assume::ContainerEmpty); - + void assume(const Token* tok, bool state, unsigned int flags) override + { bool isCondBlock = false; const Token* parent = tok->astParent(); if (parent) { isCondBlock = Token::Match(parent->previous(), "if|while ("); } + const Token* endBlock = nullptr; if (isCondBlock) { const Token* startBlock = parent->link()->next(); if (Token::simpleMatch(startBlock, ";") && Token::simpleMatch(parent->tokAt(-2), "} while (")) startBlock = parent->linkAt(-2); - const Token* endBlock = startBlock->link(); - if (state) { - pms.removeModifiedVars(endBlock); - pms.addState(endBlock->previous(), getProgramState()); - } else { - if (Token::simpleMatch(endBlock, "} else {")) - pms.addState(endBlock->linkAt(2)->previous(), getProgramState()); - } + endBlock = startBlock->link(); + } + + // Without Pending the 'then' block has been traversed and control is leaving it, so anchor + // the assumed state at the block end instead of the condition. That keeps assumptions on + // variables modified inside the block (e.g. an 'if' narrowing a value computed there) from + // being discarded as "modified" once control leaves the block. + const bool scopeEnd = !(flags & Assume::Pending) && state && endBlock; + const Token* anchor = scopeEnd ? endBlock : tok; + const Token* origin = scopeEnd ? endBlock : nullptr; + + // Update program state + pms.removeModifiedVars(anchor); + pms.addState(anchor, getProgramState()); + pms.assume(tok, state, flags & Assume::ContainerEmpty, origin); + + // The false path (the true path uses scopeEnd above): record the assumed state where control + // continues - the end of the else block, or the closing brace when there is no else - so it + // reaches the enclosing scope. + if (isCondBlock && !(flags & Assume::Pending) && !state) { + if (Token::simpleMatch(endBlock, "} else {")) + pms.addState(endBlock->linkAt(2)->previous(), getProgramState()); + else + pms.addState(endBlock, getProgramState()); } if (!(flags & Assume::Quiet)) { @@ -875,7 +893,7 @@ static bool isAliasOf(const Variable * var, const Token *tok, nonneg int varid, return false; if (val.isLifetimeValue() && !val.isLocalLifetimeValue()) return false; - if (val.isLifetimeValue() && val.lifetimeKind != ValueFlow::Value::LifetimeKind::Address) + if (val.isLifetimeValue() && val.lifetimeKind != ValueFlow::Value::LifetimeKind::Address && val.lifetimeKind != ValueFlow::Value::LifetimeKind::SubObject) return false; if (!Token::Match(val.tokvalue, ".|&|*|%var%")) return false; @@ -981,7 +999,12 @@ struct MultiValueFlowAnalyzer : ValueFlowAnalyzer { void addErrorPath(const Token* tok, const std::string& s) override { for (auto&& p:values) { - p.second.errorPath.emplace_back(tok, s); + auto& ep = p.second.errorPath; + if (std::any_of(ep.begin(), ep.end(), [&](const ErrorPathItem& epi) { + return epi.first == tok && epi.second == s; + })) + continue; + ep.emplace_back(tok, s); } } @@ -1129,7 +1152,12 @@ struct SingleValueFlowAnalyzer : ValueFlowAnalyzer { } void addErrorPath(const Token* tok, const std::string& s) override { - value.errorPath.emplace_back(tok, s); + auto& ep = value.errorPath; + if (std::any_of(ep.begin(), ep.end(), [&](const ErrorPathItem& epi) { + return epi.first == tok && epi.second == s; + })) + return; + ep.emplace_back(tok, s); } template @@ -1150,7 +1178,7 @@ struct SingleValueFlowAnalyzer : ValueFlowAnalyzer { } bool isAlias(const Token* tok, bool& inconclusive) const override { - if (value.isLifetimeValue()) + if (value.isLifetimeValue() && value.lifetimeKind != ValueFlow::Value::LifetimeKind::SubObject) return false; for (const auto& m: { std::ref(getVars()), std::ref(getAliasedVars()) @@ -1198,18 +1226,40 @@ struct SingleValueFlowAnalyzer : ValueFlowAnalyzer { bool stopOnCondition(const Token* condTok) const override { - if (value.isNonValue()) - return false; if (value.isImpossible()) return false; - if (isConditional() && !value.isKnown()) + // lifetime values must keep flowing to properly track aliases + if (value.isLifetimeValue()) + return false; + // 'conditional' flag (uninit, or lowered after a modifying branch): may depend on a + // condition that doesn't mention the variable -> stop + if (value.conditional && !value.isKnown()) return true; - if (value.isSymbolicValue()) + if (value.isNonValue()) return false; + if (value.isSymbolicValue()) + return isConditional() && !value.isKnown(); + // conditional via the originating 'condition' (e.g. possible null after 'if (p && ...)'): only flow + // if the condition references the value, else a correlation we can't follow (e.g. + // 'bool ok = (p != nullptr); if (!ok)') could make a later deref safe -> stop + if (value.condition && !value.isKnown() && !conditionReferencesValue(condTok)) + return true; ConditionState cs = analyzeCondition(condTok); return cs.isUnknownDependent(); } + // Does the condition mention the tracked value, either directly or through a symbolic alias? + bool conditionReferencesValue(const Token* condTok) const + { + return findAstNode(condTok, [&](const Token* tok) { + if (match(tok)) + return true; + return std::any_of(tok->values().cbegin(), tok->values().cend(), [&](const ValueFlow::Value& v) { + return v.isSymbolicValue() && !v.isImpossible() && v.tokvalue && match(v.tokvalue); + }); + }) != nullptr; + } + bool updateScope(const Token* endBlock, bool /*modified*/) const override { const Scope* scope = endBlock->scope(); if (!scope) @@ -1260,6 +1310,12 @@ struct ExpressionAnalyzer : SingleValueFlowAnalyzer { dependOnThis |= exprDependsOnThis(value.tokvalue); setupExprVarIds(value.tokvalue); } + if (value.isContainerSizeValue() && value.container) { + // a container size tracked through another expression (e.g. a pointer obtained from + // data()) is invalidated by writes to the container it belongs to + dependOnThis |= exprDependsOnThis(value.container); + setupExprVarIds(value.container); + } uniqueExprId = expr->isUniqueExprId() && (Token::Match(expr, "%cop%") || !isVariableChanged(expr, 0, s)); } @@ -1270,7 +1326,7 @@ struct ExpressionAnalyzer : SingleValueFlowAnalyzer { } void setupExprVarIds(const Token* start, int depth = 0) { - if (depth > settings.vfOptions.maxExprVarIdDepth) { + if (depth > settings().vfOptions.maxExprVarIdDepth) { // TODO: add bailout message return; } @@ -1464,15 +1520,22 @@ ValuePtr makeMemberExpressionAnalyzer(std::string varname, const Token struct ContainerExpressionAnalyzer : ExpressionAnalyzer { ContainerExpressionAnalyzer(const Token* expr, ValueFlow::Value val, const Settings& s) : ExpressionAnalyzer(expr, std::move(val), s) - {} + { + // The size of a container expression belongs to that expression. Through a pointer the + // size keeps belonging to the container the pointer was obtained from. + if (astIsContainer(expr) && !astIsPointer(expr)) + value.container = expr; + } bool match(const Token* tok) const override { - return tok->exprId() == expr->exprId() || (astIsIterator(tok) && isAliasOf(tok, expr->exprId())); + return tok->exprId() == expr->exprId() || isIteratorOf(tok, expr->exprId()); } Action isWritable(const Token* tok, Direction /*d*/) const override { - if (astIsIterator(tok)) + // only writes to the container itself change its size - not writes through an iterator + // or to a default-inserted element + if (tok->exprId() != expr->exprId()) return Action::None; if (!getValue(tok)) return Action::None; @@ -1499,7 +1562,7 @@ struct ContainerExpressionAnalyzer : ExpressionAnalyzer { if (action == Library::Container::Action::PUSH || action == Library::Container::Action::POP || action == Library::Container::Action::APPEND) { // TODO: handle more actions? std::vector args = getArguments(tok->tokAt(3)); bool isVariadic = false; - if (const Library::Function* libFunc = settings.library.getFunction(tok->tokAt(2))) { + if (const Library::Function* libFunc = settings().library.getFunction(tok->tokAt(2))) { const auto& argChecks = libFunc->argumentChecks; isVariadic = argChecks.find(-1) != argChecks.end() && argChecks.at(-1).variadic; } @@ -1547,7 +1610,7 @@ struct ContainerExpressionAnalyzer : ExpressionAnalyzer { case Library::Container::Action::APPEND: { std::vector args = getArguments(tok->astParent()->tokAt(2)); if (args.size() == 1) // TODO: handle overloads - n = ValueFlow::valueFlowGetStrLength(tok->astParent()->tokAt(3)); + n = ValueFlow::valueFlowGetStrLength(tok->astParent()->tokAt(3), settings().library); if (n == 0) // TODO: handle known empty append val->setPossible(); break; diff --git a/lib/vf_common.cpp b/lib/vf_common.cpp index ab01acc0f16..9b1d0387f38 100644 --- a/lib/vf_common.cpp +++ b/lib/vf_common.cpp @@ -19,6 +19,7 @@ #include "vf_common.h" #include "astutils.h" +#include "library.h" #include "mathlib.h" #include "path.h" #include "platform.h" @@ -95,7 +96,7 @@ namespace ValueFlow return true; } - MathLib::bigint truncateIntValue(MathLib::bigint value, size_t value_size, const ValueType::Sign dst_sign) + MathLib::bigint truncateIntValue(MathLib::bigint value, size_t value_size, const ValueType* dst, const Platform& platform) { if (value_size == 0) return value; @@ -103,7 +104,7 @@ namespace ValueFlow const MathLib::biguint unsignedMaxValue = std::numeric_limits::max() >> ((sizeof(unsignedMaxValue) - value_size) * 8); const MathLib::biguint signBit = 1ULL << ((value_size * 8) - 1); value &= unsignedMaxValue; - if (dst_sign == ValueType::Sign::SIGNED && (value & signBit)) + if ((dst->sign == ValueType::Sign::SIGNED || (dst->sign == ValueType::Sign::UNKNOWN_SIGN && dst->type == ValueType::Type::CHAR && platform.defaultSign == 's')) && (value & signBit)) value |= ~unsignedMaxValue; return value; @@ -138,12 +139,16 @@ namespace ValueFlow // Bad character literal } } else if (tok->isNumber() && MathLib::isFloat(tok->str())) { - Value value; - value.valueType = Value::ValueType::FLOAT; - value.floatValue = MathLib::toDoubleNumber(tok); - if (!tok->isTemplateArg()) - value.setKnown(); - setTokenValue(tok, std::move(value), settings); + // A long double literal out of the range of double (1e400L) has no + // double value: infinity would make different literals compare equal. + if (!MathLib::isLongDoubleOutOfDoubleRange(tok->str())) { + Value value; + value.valueType = Value::ValueType::FLOAT; + value.floatValue = MathLib::toDoubleNumber(tok); + if (!tok->isTemplateArg()) + value.setKnown(); + setTokenValue(tok, std::move(value), settings); + } } else if (tok->enumerator() && tok->enumerator()->value_known) { Value value(tok->enumerator()->value); if (!tok->isTemplateArg()) @@ -154,12 +159,20 @@ namespace ValueFlow if (!tok->isTemplateArg()) value.setKnown(); setTokenValue(tok, std::move(value), settings); + } else if ((tok->isCpp() || settings.standards.c >= Standards::C23) && (tok->isName() && tok->varId() == 0 && Token::Match(tok, "%bool%"))) { + Value value(tok->str() == "true"); + if (!tok->isTemplateArg()) + value.setKnown(); + setTokenValue(tok, std::move(value), settings); } else if (Token::simpleMatch(tok, "sizeof (")) { - if (tok->next()->astOperand2() && !tok->next()->astOperand2()->isLiteral() && tok->next()->astOperand2()->valueType() && - (tok->next()->astOperand2()->valueType()->pointer == 0 || // <- TODO this is a bailout, abort when there are array->pointer conversions - (tok->next()->astOperand2()->variable() && !tok->next()->astOperand2()->variable()->isArray())) && - !tok->next()->astOperand2()->valueType()->isEnum()) { // <- TODO this is a bailout, handle enum with non-int types - const size_t sz = tok->next()->astOperand2()->valueType()->getSizeOf(settings, ValueType::Accuracy::ExactOrZero, ValueType::SizeOf::Pointer); + const Token* const obj = tok->next()->astOperand2(); + if (obj && !obj->isLiteral() && obj->valueType() && + (obj->valueType()->pointer == 0 || // <- TODO this is a bailout, abort when there are array->pointer conversions + (obj->variable() && !obj->variable()->isArray())) && + !obj->valueType()->isEnum() && // <- TODO this is a bailout, handle enum with non-int types + !(obj->valueType()->container && obj->valueType()->container->startPattern == "std :: array <")) { + const auto ptrPointee = obj->valueType()->pointer > 0 ? ValueType::SizeOf::Pointer : ValueType::SizeOf::Pointee; + const size_t sz = obj->valueType()->getSizeOf(settings, ValueType::Accuracy::ExactOrZero, ptrPointee); if (sz) { Value value(sz); value.setKnown(); @@ -238,7 +251,12 @@ namespace ValueFlow if (var->type()->classScope && var->type()->classScope->enumType) size = getSizeOfType(var->type()->classScope->enumType, settings); } else if (var->valueType()) { - size = var->valueType()->getSizeOf(settings, ValueType::Accuracy::ExactOrZero, ValueType::SizeOf::Pointer); + if (var->valueType()->container && var->valueType()->container->startPattern == "std :: array <") { + const ValueType vtElement = ValueType::parseDecl(var->valueType()->containerTypeToken, settings); + size = vtElement.getSizeOf(settings, ValueType::Accuracy::ExactOrZero, ValueType::SizeOf::Pointer); + } + else + size = var->valueType()->getSizeOf(settings, ValueType::Accuracy::ExactOrZero, ValueType::SizeOf::Pointer); } else if (!var->type()) { size = getSizeOfType(var->typeStartToken(), settings); } @@ -384,18 +402,24 @@ namespace ValueFlow v.debugPath.emplace_back(tok, std::move(s)); } - MathLib::bigint valueFlowGetStrLength(const Token* tok) + MathLib::bigint valueFlowGetStrLength(const Token* tok, const Library& library) { if (tok->tokType() == Token::eString) return Token::getStrLength(tok); if (astIsGenericChar(tok) || tok->tokType() == Token::eChar) return 1; - if (const Value* v = tok->getKnownValue(Value::ValueType::CONTAINER_SIZE)) - return v->intvalue; + if (const Value* v = tok->getKnownValue(Value::ValueType::CONTAINER_SIZE)) { + // on a pointer the size is the number of elements in the buffer (possibly including + // a null terminator), not the length of the string + if (!astIsPointer(tok)) + return v->intvalue; + } if (const Value* v = tok->getKnownValue(Value::ValueType::TOK)) { if (v->tokvalue != tok) - return valueFlowGetStrLength(v->tokvalue); + return valueFlowGetStrLength(v->tokvalue, library); } + if (const Token* cont = library.getContainerFromYield(tok, Library::Container::Yield::BUFFER_NT)) + return valueFlowGetStrLength(cont, library); return 0; } } diff --git a/lib/vf_common.h b/lib/vf_common.h index 3ed36e6c47f..448d14ccb41 100644 --- a/lib/vf_common.h +++ b/lib/vf_common.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,6 +30,7 @@ class Token; class Settings; class Platform; +class Library; namespace ValueFlow { @@ -40,7 +41,7 @@ namespace ValueFlow { bool getMinMaxValues(const ValueType* vt, const Platform& platform, MathLib::bigint& minValue, MathLib::bigint& maxValue); - MathLib::bigint truncateIntValue(MathLib::bigint value, size_t value_size, ValueType::Sign dst_sign); + MathLib::bigint truncateIntValue(MathLib::bigint value, size_t value_size, const ValueType* dst, const Platform& platform); Token * valueFlowSetConstantValue(Token *tok, const Settings &settings); @@ -53,7 +54,7 @@ namespace ValueFlow const Token* tok, SourceLocation local = SourceLocation::current()); - MathLib::bigint valueFlowGetStrLength(const Token* tok); + MathLib::bigint valueFlowGetStrLength(const Token* tok, const Library& library); } #endif // vfCommonH diff --git a/lib/vf_settokenvalue.cpp b/lib/vf_settokenvalue.cpp index 602d87e159d..e7b715ab36b 100644 --- a/lib/vf_settokenvalue.cpp +++ b/lib/vf_settokenvalue.cpp @@ -47,7 +47,7 @@ namespace ValueFlow { - static Library::Container::Yield getContainerYield(Token* tok, const Settings& settings, Token*& parent) + static Library::Container::Yield getContainerYield(Token* tok, const Library& library, Token*& parent) { if (Token::Match(tok, ". %name% (") && tok->astParent() == tok->tokAt(2) && tok->astOperand1() && tok->astOperand1()->valueType()) { @@ -57,7 +57,7 @@ namespace ValueFlow } if (Token::Match(tok->previous(), "%name% (")) { parent = tok; - if (const Library::Function* f = settings.library.getFunction(tok->previous())) { + if (const Library::Function* f = library.getFunction(tok->previous())) { return f->containerYield; } } @@ -95,7 +95,7 @@ namespace ValueFlow return v; } - static const Token *getCastTypeStartToken(const Token *parent, const Settings& settings) + static const Token *getCastTypeStartToken(const Token *parent, const Library& library) { // TODO: This might be a generic utility function? if (!Token::Match(parent, "{|(")) @@ -114,7 +114,7 @@ namespace ValueFlow ftok = ftok->next(); while (Token::Match(ftok, "%name% ::")) ftok = ftok->tokAt(2); - if (settings.library.isNotLibraryFunction(ftok)) + if (library.isNotLibraryFunction(ftok)) return parent->next(); } if (parent->astOperand2() && Token::Match(parent->astOperand1(), "const_cast|dynamic_cast|reinterpret_cast|static_cast <")) @@ -216,6 +216,25 @@ namespace ValueFlow return false; } + static bool isFunctionCall(const Token* tok, const Settings& settings) + { + if (!tok) + return false; + if (tok->str() == "{") + return true; + if (tok->str() == "(") { + if (tok->astOperand1() && tok->astOperand1()->isControlFlowKeyword()) + return false; + if (Token::Match(tok->next(), "%name%") && !settings.library.isNotLibraryFunction(tok->next())) + return true; + if (Token::Match(tok->previous(), "%name%|>")) + return true; + if (Token::Match(tok->astOperand1(), "%name%|(|[|*|.")) + return true; + } + return false; + } + /** set ValueFlow value and perform calculations if possible */ void setTokenValue(Token* tok, Value value, @@ -231,6 +250,11 @@ namespace ValueFlow if (!value.isImpossible() && value.isIntValue()) value = truncateImplicitConversion(tok->astParent(), value, settings); + // a container size value on a container expression belongs to that expression, while a + // pointer or an iterator only transports the size of the container it was obtained from + if (value.isContainerSizeValue() && !astIsPointer(tok) && astIsContainer(tok)) + value.container = tok; + if (settings.debugnormal) setSourceLocation(value, loc, tok); @@ -249,9 +273,7 @@ namespace ValueFlow return !Token::simpleMatch(p, ","); }); // Ensure that the comma isn't a function call - if (!callParent || (!Token::Match(callParent->previous(), "%name%|> (") && !Token::simpleMatch(callParent, "{") && - (!Token::Match(callParent, "( %name%") || settings.library.isNotLibraryFunction(callParent->next())) && - !(callParent->str() == "(" && (Token::simpleMatch(callParent->astOperand1(), "*") || Token::Match(callParent->astOperand1(), "%name%|("))))) { + if (!isFunctionCall(callParent, settings)) { setTokenValue(parent, std::move(value), settings); return; } @@ -291,24 +313,41 @@ namespace ValueFlow combineValueProperties(value1, value2, result); - if (Token::simpleMatch(parent, "==") && result.intvalue) + if (Token::simpleMatch(parent, "==") && result.intvalue && !(value1.intvalue == 0 && value2.intvalue == 0)) continue; - if (Token::simpleMatch(parent, "!=") && !result.intvalue) + if (Token::simpleMatch(parent, "!=") && !result.intvalue && !(value1.intvalue == 0 && value2.intvalue == 0)) continue; setTokenValue(parent, std::move(result), settings); } } } + // an empty associative container implies that its default-inserted elements are empty as well + if (Token::simpleMatch(parent, "[") && astIsLHS(tok) && astIsContainer(parent) && + tok->valueType()->container && tok->valueType()->container->stdAssociativeLike && + !value.isImpossible() && value.intvalue == 0) + setTokenValue(parent, value, settings); Token* next = nullptr; - const Library::Container::Yield yields = getContainerYield(parent, settings, next); + const Library::Container::Yield yields = getContainerYield(parent, settings.library, next); if (yields == Library::Container::Yield::SIZE) { value.valueType = Value::ValueType::INT; + value.container = nullptr; + setTokenValue(next, std::move(value), settings); + } else if (contains({Library::Container::Yield::BUFFER, + Library::Container::Yield::BUFFER_NT, + Library::Container::Yield::START_ITERATOR, + Library::Container::Yield::END_ITERATOR, + Library::Container::Yield::ITERATOR}, + yields)) { + // The returned pointer or iterator has as many elements available as the container + if (yields == Library::Container::Yield::BUFFER_NT) + value.intvalue += 1; // ..plus the null terminator setTokenValue(next, std::move(value), settings); } else if (yields == Library::Container::Yield::EMPTY) { const Value::Bound bound = value.bound; const long long intvalue = value.intvalue; value.valueType = Value::ValueType::INT; + value.container = nullptr; value.bound = Value::Bound::Point; if (value.isImpossible()) { if (intvalue == 0) @@ -371,7 +410,7 @@ namespace ValueFlow } // cast.. - if (const Token *castType = getCastTypeStartToken(parent, settings)) { + if (const Token *castType = getCastTypeStartToken(parent, settings.library)) { if (contains({Value::ValueType::INT, Value::ValueType::SYMBOLIC}, value.valueType) && Token::simpleMatch(parent->astOperand1(), "dynamic_cast")) return; @@ -384,7 +423,9 @@ namespace ValueFlow setTokenValueCast(parent, valueType, std::move(value), settings); } - else if (parent->str() == ":") { + else if (parent->str() == ":" && Token::simpleMatch(parent->astParent(), "?")) { + const std::string condStr(tok == parent->astOperand1() ? "true" : "false"); + value.errorPath.emplace_back(parent->astParent()->astOperand1(), "Assuming condition '" + parent->astParent()->astOperand1()->expressionString() + "' is " + condStr); setTokenValue(parent,std::move(value),settings); } @@ -593,7 +634,7 @@ namespace ValueFlow // ~ else if (parent->str() == "~") { for (const Value &val : tok->values()) { - if (!val.isIntValue()) + if (!val.isIntValue() || (val.isImpossible() && val.intvalue < 0)) continue; Value v(val); v.intvalue = ~v.intvalue; @@ -613,7 +654,7 @@ namespace ValueFlow } // unary minus - else if (parent->isUnaryOp("-")) { + else if (parent->isUnaryOp("-") && !astIsUnsigned(parent)) { for (const Value &val : tok->values()) { if (!val.isIntValue() && !val.isFloatValue()) continue; @@ -641,7 +682,7 @@ namespace ValueFlow const ValueType *dst = tok->valueType(); if (dst) { const size_t sz = dst->getSizeOf(settings, ValueType::Accuracy::ExactOrZero, ValueType::SizeOf::Pointer); - MathLib::bigint newvalue = ValueFlow::truncateIntValue(v.intvalue + 1, sz, dst->sign); + MathLib::bigint newvalue = ValueFlow::truncateIntValue(v.intvalue + 1, sz, dst, settings.platform); if (v.bound != ValueFlow::Value::Bound::Point) { if (newvalue < v.intvalue) { v.invertBound(); @@ -671,7 +712,7 @@ namespace ValueFlow const ValueType *dst = tok->valueType(); if (dst) { const size_t sz = dst->getSizeOf(settings, ValueType::Accuracy::ExactOrZero, ValueType::SizeOf::Pointer); - MathLib::bigint newvalue = ValueFlow::truncateIntValue(v.intvalue - 1, sz, dst->sign); + MathLib::bigint newvalue = ValueFlow::truncateIntValue(v.intvalue - 1, sz, dst, settings.platform); if (v.bound != ValueFlow::Value::Bound::Point) { if (newvalue > v.intvalue) { v.invertBound(); diff --git a/lib/vfvalue.h b/lib/vfvalue.h index 8a473143b68..9eab775de24 100644 --- a/lib/vfvalue.h +++ b/lib/vfvalue.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -322,6 +322,10 @@ namespace ValueFlow /** token value - the token that has the value. this is used for pointer aliases, strings, etc. */ const Token* tokvalue{}; + /** For CONTAINER_SIZE values: the container the size belongs to, when the value is + * attached to a token that is not the container itself (an iterator or a pointer) */ + const Token* container = nullptr; + /** float value */ double floatValue{}; diff --git a/man/checkers/AssignmentAddressToInteger.md b/man/checkers/AssignmentAddressToInteger.md new file mode 100644 index 00000000000..a482e0a516b --- /dev/null +++ b/man/checkers/AssignmentAddressToInteger.md @@ -0,0 +1,56 @@ +# AssignmentAddressToInteger and AssignmentIntegerToAddress + +**Message**: Assigning a pointer to an integer is not portable.
+**Category**: Portability
+**Severity**: Portability
+**Language**: C/C++ + +## Description + +Code silently narrows a pointer (address) down to a plain integer type, or the other way around, via a +plain assignment. On platforms where `sizeof(void*) != sizeof(int)` (most notably 64-bit platforms, +where a pointer is 8 bytes and `int` is usually still 4 bytes) this loses information: part of the +address is silently discarded. + +- `AssignmentAddressToInteger`: a pointer value is assigned to a plain integer variable, for example + `int i = p;`. +- `AssignmentIntegerToAddress`: a plain integer value is assigned to a pointer variable, for example + `int *p = i;`. + +This checks `char`/`short`/`int` variables (not `long`/`long long`, and not `bool`, which is a common, +intentional null-check idiom rather than a truncation bug). This checker only runs when the +`portability` severity is enabled. + +## Motivation + +Storing an address in a type that is narrower than a pointer is not portable: it works by accident on +platforms where the two types happen to be the same width, and silently truncates the address (or +sign-extends a small integer into a bogus address) on platforms where they are not, most notably when +porting 32-bit code to 64-bit. + +## How to fix + +Use a pointer type, or an integer type explicitly meant to hold a pointer (`intptr_t`/`uintptr_t` from +``), instead of a plain `int`/`char`/etc. + +Before: +```cpp +int foo(int *p) { + int a = p; // <- AssignmentAddressToInteger + return a; +} +``` + +After: +```cpp +#include +intptr_t foo(int *p) { + intptr_t a = reinterpret_cast(p); + return a; +} +``` + +## Related checkers + +- [CastAddressToIntegerAtReturn.md](CastAddressToIntegerAtReturn.md) - the same idea, but for a + function `return` rather than a plain assignment. diff --git a/man/checkers/CastAddressToIntegerAtReturn.md b/man/checkers/CastAddressToIntegerAtReturn.md new file mode 100644 index 00000000000..bc40eae6a81 --- /dev/null +++ b/man/checkers/CastAddressToIntegerAtReturn.md @@ -0,0 +1,55 @@ +# CastAddressToIntegerAtReturn and CastIntegerToAddressAtReturn + +**Message**: Returning an address value in a function with integer return type is not portable.
+**Category**: Portability
+**Severity**: Portability
+**Language**: C/C++ + +## Description + +A function silently narrows a pointer (address) down to a plain integer return type, or the other way +around. On platforms where `sizeof(void*) != sizeof(int)` (most notably 64-bit platforms, where a +pointer is 8 bytes and `int` is usually still 4 bytes) this loses information: part of the address is +silently discarded. + +- `CastAddressToIntegerAtReturn`: a function with an integer return type returns a pointer value, for + example `int foo(char *p) { return p; }`. +- `CastIntegerToAddressAtReturn`: a function with a pointer return type returns a plain integer value, + for example `void* foo(int i) { return i; }`. + +This checks `char`/`short`/`int` (not `long`/`long long`, and not `bool`, which is a common, +intentional idiom rather than a truncation bug), and only when analyzing for a 64-bit target - on a +32-bit target a pointer and an `int` are the same width, so returning one as the other isn't a +portability problem there. This checker only runs when the `portability` severity is enabled. + +## Motivation + +Storing an address in a type that is narrower than a pointer is not portable: it works by accident on +platforms where the two types happen to be the same width, and silently truncates the address (or +sign-extends a small integer into a bogus address) on platforms where they are not, most notably when +porting 32-bit code to 64-bit. + +## How to fix + +Use a pointer type, or an integer type explicitly meant to hold a pointer (`intptr_t`/`uintptr_t` from +``), instead of a plain `int`/`char`/etc. + +Before: +```cpp +void* foo(int i) { + return i; // <- CastIntegerToAddressAtReturn +} +``` + +After: +```cpp +#include +void* foo(intptr_t i) { + return reinterpret_cast(i); +} +``` + +## Related checkers + +- [AssignmentAddressToInteger.md](AssignmentAddressToInteger.md) - the same idea, but for a plain + assignment rather than a function `return`. diff --git a/man/checkers/IOWithoutPositioning.md b/man/checkers/IOWithoutPositioning.md new file mode 100644 index 00000000000..428eeb39c2f --- /dev/null +++ b/man/checkers/IOWithoutPositioning.md @@ -0,0 +1,57 @@ +# IOWithoutPositioning + +**Message**: Read and write operations without a call to a positioning function (fseek, fsetpos or rewind) or fflush in between result in undefined behaviour.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A read is immediately followed by a write (or vice versa) on a file opened for both, with no +`fseek()`/`fsetpos()`/`rewind()`/`fflush()` in between - the C standard says this is undefined +behaviour. + +## Motivation + +The C standard requires a positioning call (or an `fflush()`) between a read and a following write (or +vice versa) on the same read/write stream. Skipping it is undefined behaviour, even though many +implementations happen to do something predictable with it. cppcheck only follows a local `FILE*` +variable through straight-line code in the function that opened it; a global or member file handle, or +passing the handle to another function, is enough uncertainty that it stops checking rather than guess, +so this only catches the mismatches it can actually prove. + +## How to fix + +Before: +```cpp +#include +void f() { + FILE *fp = fopen("a.txt", "r+"); + if (!fp) return; + char buf[10]; + fread(buf, 1, 10, fp); + fwrite(buf, 1, 10, fp); // <- no seek/rewind/fflush since the read above + fclose(fp); +} +``` + +After: +```cpp +#include +void f() { + FILE *fp = fopen("a.txt", "r+"); + if (!fp) return; + char buf[10]; + fread(buf, 1, 10, fp); + fseek(fp, 0, SEEK_CUR); + fwrite(buf, 1, 10, fp); + fclose(fp); +} +``` + +## Related checkers + +- [useClosedFile.md](useClosedFile.md), [readWriteOnlyFile.md](readWriteOnlyFile.md), + [writeReadOnlyFile.md](writeReadOnlyFile.md), [seekOnAppendedFile.md](seekOnAppendedFile.md), + [incompatibleFileOpen.md](incompatibleFileOpen.md) - other checks that follow the same `FILE*` through + a function. diff --git a/man/checkers/StlMissingComparison.md b/man/checkers/StlMissingComparison.md new file mode 100644 index 00000000000..4e0f57bf089 --- /dev/null +++ b/man/checkers/StlMissingComparison.md @@ -0,0 +1,45 @@ +# StlMissingComparison + +**Message**: Missing bounds check for extra iterator increment in loop.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ + +## Description + +Inside a loop, the iterator is incremented a second time (in addition to the loop's own increment) +without any bounds check in between, risking incrementing it past `end()`. + +## Motivation + +An iterator that's advanced twice per iteration without checking for `end()` in between can walk right +past the end of the container - dereferencing it afterwards, or even just comparing it again, is then +undefined behaviour. This check flags the missing safety check itself, not a proven out-of-bounds +increment - if the container always happens to have enough elements left when the extra increment runs, +the loop never actually goes past `end()` in practice, even though the check that would guarantee this is +absent. + +## How to fix + +Before: +```cpp +#include +void f(std::set &ints, bool a) { + for (std::set::iterator it = ints.begin(); it != ints.end(); ++it) { + if (a) { + it++; // <- StlMissingComparison: might increment 'it' past end() + } + } +} +``` + +After: don't increment the iterator a second time inside the loop body. +```cpp +#include +void f(std::set &ints, bool a) { + for (std::set::iterator it = ints.begin(); it != ints.end(); ++it) { + if (a) { + } + } +} +``` diff --git a/man/checkers/UnionZeroInit.md b/man/checkers/UnionZeroInit.md new file mode 100644 index 00000000000..a7941aa5407 --- /dev/null +++ b/man/checkers/UnionZeroInit.md @@ -0,0 +1,43 @@ +# UnionZeroInit + +**Message**: You are using memset() to initialize a union that also contains a member with a bigger size.
+**Category**: Portability
+**Severity**: Portability
+**Language**: C/C++ + +## Description + +A union is zero-initialized (`= {0}` or `= {}`), but its largest member isn't declared first - only the +first member is guaranteed to be fully written by that initializer, so the rest of the union's storage +(beyond the first member's size) may not actually end up zeroed. + +## Motivation + +An aggregate initializer for a union only initializes the first named member. If a smaller member is +listed first, the initializer only guarantees that member's bytes are zeroed - the remaining bytes, +which are only reachable through a later, larger member, are left with whatever was already in memory. +Code that expects the whole union to be zero can then read uninitialized bytes through the larger +member. + +## How to fix + +Declare the union's largest member first, so a `{0}`/`{}` initializer zeroes its entire storage. + +Before: +```cpp +void foo() { + union { char c; int i; } bad0 = {0}; // <- 'i' (the larger member) isn't first +} +``` + +After: +```cpp +void foo() { + union { int i; char c; } good0 = {0}; +} +``` + +## Related checkers + +- [overlappingWriteUnion.md](overlappingWriteUnion.md) - a different union-related pitfall, about + reading and writing two overlapping members in the same expression. diff --git a/man/checkers/accessMoved.md b/man/checkers/accessMoved.md new file mode 100644 index 00000000000..cba7ab04298 --- /dev/null +++ b/man/checkers/accessMoved.md @@ -0,0 +1,70 @@ +# accessMoved and accessForwarded + +**Message**: Access of moved variable 'v'.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ only + +## Description + +A variable is read after it has been passed to `std::move()` (`accessMoved`) or `std::forward()` +(`accessForwarded`) - by convention, once a value has been moved/forwarded from, its contents are +unspecified and shouldn't be relied on (except to reset or destroy it). + +## Motivation + +`std::move()`/`std::forward()` don't themselves do anything except change how the compiler treats the +expression - the actual "move" happens in whatever constructor or assignment operator the moved-from +value is subsequently passed into, and its effect on the source object is entirely up to that type. By +convention a moved-from object is left in a valid but unspecified state, so any code that reads its +value afterwards (rather than just reassigning or destroying it) is relying on something the language +doesn't guarantee. + +## How to fix + +Before: +```cpp +#include +struct A {}; +void g(A a); +void f() { + A a; + g(std::move(a)); + g(std::move(a)); // <- accessMoved: 'a' was already moved from above +} +``` + +After: +```cpp +#include +struct A {}; +void g(A a); +void f() { + A a; + g(std::move(a)); +} +``` + +Before: +```cpp +#include +template +void g(T&&); +template +void f(T && t) { + g(std::forward(t)); + T s = t; // <- accessForwarded: 't' was already forwarded above +} +``` + +After: +```cpp +#include +template +void g(T&&); +template +void f(T && t) { + T s = t; + g(std::forward(t)); +} +``` diff --git a/man/checkers/algorithmOutOfBounds.md b/man/checkers/algorithmOutOfBounds.md new file mode 100644 index 00000000000..2a1b9d646fd --- /dev/null +++ b/man/checkers/algorithmOutOfBounds.md @@ -0,0 +1,69 @@ +# algorithmOutOfBounds + +**Message**: The algorithm 'std::copy' accesses 5 elements through the iterator 'v1.begin()' but only 3 elements are available.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +Many STL algorithms take an iterator that denotes the beginning of a second range (typically an output range) and +assume that this range is large enough. If it is not, the algorithm writes or reads past the end of the container, +which is undefined behavior. + +This checker compares the number of elements an algorithm accesses with the number of elements that +are actually available through the iterator, and warns when the access is out of bounds. Three groups +of algorithms are checked: + +- Algorithms that access exactly `last1 - first1` elements through the other iterator: `std::copy`, `std::move`, + `std::swap_ranges`, `std::transform`, `std::replace_copy`, `std::replace_copy_if`, `std::reverse_copy`, + `std::equal`, `std::mismatch`, `std::is_permutation`, `std::partial_sum`, `std::adjacent_difference` and + `std::inner_product`. +- Algorithms that access at most `last1 - first1` elements, depending on the values in the input range: + `std::copy_if`, `std::remove_copy`, `std::remove_copy_if` and `std::unique_copy`. Since the actual number of + accessed elements is not known, these are only reported as inconclusive warnings (with `--inconclusive`). +- Count-based algorithms that access as many elements as the count argument says: `std::copy_n`, `std::fill_n` and + `std::generate_n`. + +The severity is `error` when the out of bounds access always happens. When the analysis depends on an earlier +condition in the code, the severity is `warning` and the message has the form "Either the condition 'v.size()==3' +is redundant or the algorithm ... ". + +The checker does not warn when: + +- The second range is given with both a begin and an end iterator (for example the two-range overloads of + `std::equal`, `std::mismatch` and `std::is_permutation`), since such overloads do not access the second range out + of bounds. +- An iterator adaptor such as `std::back_inserter` or `std::inserter` is used, since those grow the container as + needed. +- The proof would rely on "possible" (non-known) values on both the accessed and the available side. + +## How to fix + +Make sure the destination range is large enough before calling the algorithm, or use an iterator adaptor such as +`std::back_inserter` that grows the container as needed. + +Before: +```cpp +void f(const std::vector& v0) { + std::vector v1(3); + // If v0 has more than 3 elements, this writes past the end of v1 + std::copy(v0.begin(), v0.end(), v1.begin()); +} +``` + +After: +```cpp +void f(const std::vector& v0) { + std::vector v1(v0.size()); + std::copy(v0.begin(), v0.end(), v1.begin()); +} +``` + +Or let the container grow: +```cpp +void f(const std::vector& v0) { + std::vector v1; + std::copy(v0.begin(), v0.end(), std::back_inserter(v1)); +} +``` diff --git a/man/checkers/allocaCalled.md b/man/checkers/allocaCalled.md new file mode 100644 index 00000000000..c7d55e296ad --- /dev/null +++ b/man/checkers/allocaCalled.md @@ -0,0 +1,70 @@ +# allocaCalled + +**Message**: Obsolete function 'alloca' called. In C99 and later it is recommended to use a variable length array instead.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +`alloca()` is called. cppcheck flags every call unconditionally, regardless of the context it's used +in - it does not check, for example, whether the call is inside a loop. + +## Motivation + +`alloca()` allocates memory on the stack, but that memory is only freed when the *entire calling +function* returns - not at the end of the block or loop iteration the call happens to be in, the way a +normal local variable would be. This makes `alloca()` inside a loop a common way to exhaust the stack: +each iteration adds another allocation on top of the previous ones, and none of them are released until +the function finally returns, even though the loop itself may look perfectly ordinary. + +Unlike `malloc()`, `alloca()` also has no way to report failure - if a request is too large (whether from +one oversized call, or many small ones accumulating in a loop), the result is undefined behaviour +(typically a stack overflow) rather than a clean, checkable error. + +## How to fix + +Before: +```cpp +#include +void f(int n, int count) { + for (int i = 0; i < count; i++) { + char *buf = alloca(n); // <- each iteration's allocation piles up; none are freed until f() returns + buf[0] = 0; + } +} +``` + +After (C99 and later): a variable length array declared inside the loop body *is* freed at the end of +each iteration, unlike `alloca()`. +```cpp +void f(int n, int count) { + for (int i = 0; i < count; i++) { + char buf[n]; + buf[0] = 0; + } +} +``` + +Before: +```cpp +#include +void f(int n) { + char *buf = alloca(n); // <- obsolete, no error handling if 'n' is too large +} +``` + +After (C99 and later): +```cpp +void f(int n) { + char buf[n]; // variable length array +} +``` + +After (C++11 and later): +```cpp +#include +void f() { + std::array buf; // fixed-size, or use a dynamically allocated container +} +``` diff --git a/man/checkers/argumentSize.md b/man/checkers/argumentSize.md new file mode 100644 index 00000000000..beff9bbf326 --- /dev/null +++ b/man/checkers/argumentSize.md @@ -0,0 +1,47 @@ +# argumentSize + +**Message**: Buffer 'a' is too small, the function 'f' expects a bigger buffer in 1st argument
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A function is declared with a fixed-size array parameter (`void f(char a[10])`), and is called with an +array that's known to be smaller than that. + +## Motivation + +A parameter declared as `char a[10]` documents (and, inside the function, is used as if) an array of at +least 10 elements. Calling it with a genuinely smaller array is passed silently - the function has no +way to know the actual array it received was too small. cppcheck only compares the declared parameter +size against the size of the array actually passed - it doesn't check whether the function's body goes +on to access an element near the end of the declared size. If it does, as it's entitled to assume it +can, that access reads or writes past the real (smaller) array, which is undefined behaviour; if the +function only ever touches the first few elements in practice, this particular call happens to be +harmless despite the size mismatch. + +## How to fix + +Before: +```cpp +void f(char a[10]); +void g() { + char a[2]; + f(a); // <- 'a' is smaller than what f() expects +} +``` + +After: +```cpp +void f(char a[10]); +void g() { + char a[10]; + f(a); +} +``` + +## Related checkers + +- [ctuArrayIndex.md](ctuArrayIndex.md) - a related, whole-program check for an out-of-bounds access + reached through a function argument, including via a plain pointer parameter. diff --git a/man/checkers/arithOperationsOnVoidPointer.md b/man/checkers/arithOperationsOnVoidPointer.md new file mode 100644 index 00000000000..26cf1a7d08a --- /dev/null +++ b/man/checkers/arithOperationsOnVoidPointer.md @@ -0,0 +1,40 @@ +# arithOperationsOnVoidPointer + +**Message**: 'x' is of type 'void *'. When using void pointers in calculations, the behaviour is undefined.
+**Category**: Portability
+**Severity**: Portability
+**Language**: C/C++ + +## Description + +Pointer arithmetic (`+`, `-`, `++`, `--`, `+=`, `-=`) is performed directly on a `void*`, which isn't +standard C/C++ (also only a GNU extension). + +## Motivation + +Pointer arithmetic advances a pointer by a number of elements, each `sizeof(*p)` bytes - but `void` has +no defined size in standard C/C++, so what "one element" means for a `void*` isn't standard either. Code +relying on this compiles under gcc/clang's extension (which defines it as if `sizeof(void)` were 1) but +isn't portable to a strictly-standard-conforming compiler, and is technically undefined behaviour under +the plain standard rather than the GNU extension cppcheck's own message refers to. + +## How to fix + +Before: +```cpp +void f(void* p) { + p = p + 1; // <- arithmetic directly on a void* +} +``` + +After: +```cpp +void f(char* p) { + p = p + 1; +} +``` + +## Related checkers + +- [sizeofVoid.md](sizeofVoid.md) - another `void`-specific, non-standard construct (`sizeof(void)` / + `sizeof(*voidPointer)`). diff --git a/man/checkers/arrayIndexOutOfBounds.md b/man/checkers/arrayIndexOutOfBounds.md new file mode 100644 index 00000000000..589acbcfa41 --- /dev/null +++ b/man/checkers/arrayIndexOutOfBounds.md @@ -0,0 +1,48 @@ +# arrayIndexOutOfBounds and arrayIndexOutOfBoundsCond + +**Message**: Array 'a[10]' accessed at index 20, which is out of bounds.
+**Category**: Undefined Behaviour
+**Severity**: Error/Warning
+**Language**: C/C++ + +## Description + +An array is indexed with a value outside its bounds: + +- `arrayIndexOutOfBounds`: cppcheck knows for certain the index is outside the array's bounds. +- `arrayIndexOutOfBoundsCond`: the out-of-bounds index only holds on one branch of a condition checked + on the index variable elsewhere in the code - so either that condition is redundant, or this access + is a bug. + +## Motivation + +Reading or writing outside the bounds of an array is undefined behaviour: at best the program crashes +immediately, at worst it silently corrupts nearby memory and fails much later, or in a different run, +in a way that's very hard to trace back to the actual cause. + +## How to fix + +Before: +```cpp +void f() { + int a[10]; + a[20] = 0; // <- arrayIndexOutOfBounds +} +``` + +After: +```cpp +void f() { + int a[20]; + a[19] = 0; +} +``` + +## Related checkers + +- [pointerOutOfBounds.md](pointerOutOfBounds.md) - the pointer-arithmetic equivalent of this check. +- [ctuArrayIndex.md](ctuArrayIndex.md) - the same idea, found by cppcheck's whole-program analysis + across function calls. +- [negativeIndex.md](negativeIndex.md) - the same idea, specifically for a negative index. +- [objectIndex.md](objectIndex.md) - a related out-of-bounds access, through the address of a specific + struct member instead of an array. diff --git a/man/checkers/arrayIndexThenCheck.md b/man/checkers/arrayIndexThenCheck.md new file mode 100644 index 00000000000..a697a3af402 --- /dev/null +++ b/man/checkers/arrayIndexThenCheck.md @@ -0,0 +1,38 @@ +# arrayIndexThenCheck + +**Message**: Array index 'i' is used before limits check.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +An array is indexed with a variable, and only *afterwards* is that same variable checked against its +limits (`a[i] && i < 10`) in a way that looks like it was meant to guard the access. Even where it's +not currently a bug, this ordering means a bounds check that no longer matches the access (after later +edits) won't protect anything. + +## Motivation + +Writing the bounds check after the access it's meant to guard defeats the purpose of a short-circuiting +`&&`/`||`: by the time the check runs, the access has already happened. Even in cases where the access +happens to be safe today, this ordering is fragile - it looks protective without actually being so, and +a later edit to the bounds is easy to get wrong without anything catching it. + +## How to fix + +Before: +```cpp +void f(const char s[], int i) { + if (s[i] == 'x' && i < 20) { // <- index used before the limit check + } +} +``` + +After: +```cpp +void f(const char s[], int i) { + if (i < 20 && s[i] == 'x') { + } +} +``` diff --git a/man/checkers/assertWithSideEffect.md b/man/checkers/assertWithSideEffect.md new file mode 100644 index 00000000000..647cc3c71eb --- /dev/null +++ b/man/checkers/assertWithSideEffect.md @@ -0,0 +1,69 @@ +# assertWithSideEffect + +**Message**: Assert statement calls a function which may have desired side effects: 'foo'.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +The expression inside an `assert(...)` call calls a function that may have a side effect - for example +a non-`const` member function, or a function whose body is seen to modify a reference/pointer argument +it was given. When the function's body isn't visible, cppcheck falls back to a narrower guess based on +whether it's declared `const`/`static`, rather than warning about every unresolvable call - so this +check only flags a call when it has at least some concrete reason to suspect a side effect. + +This checker only runs when the `warning` severity is enabled. + +## Motivation + +`assert()` is compiled out entirely in release builds (when `NDEBUG` is defined). Relying on a function +call inside it to actually do something only happens in debug builds and silently vanishes in release +builds. This is a classic source of code that "works when debugging" and breaks (or does nothing) in +the shipped build. + +## How to fix + +Perform the call outside the `assert()`, and assert on the already-computed result. + +Before: +```cpp +struct Stack { + int top = 0; + bool pop(int& out) { + if (top == 0) + return false; + out = --top; + return true; + } +}; + +void foo(Stack& s) { + int value; + assert(s.pop(value)); // <- pop() only runs in debug builds +} +``` + +After: +```cpp +struct Stack { + int top = 0; + bool pop(int& out) { + if (top == 0) + return false; + out = --top; + return true; + } +}; + +void foo(Stack& s) { + int value; + bool popped = s.pop(value); + assert(popped); +} +``` + +## Related checkers + +- [assignmentInAssert.md](assignmentInAssert.md) - the same idea, but for a direct assignment inside + `assert()` rather than a function call. diff --git a/man/checkers/assignBoolToFloat.md b/man/checkers/assignBoolToFloat.md new file mode 100644 index 00000000000..d4691c002c4 --- /dev/null +++ b/man/checkers/assignBoolToFloat.md @@ -0,0 +1,32 @@ +# assignBoolToFloat + +**Message**: Boolean value assigned to floating point variable.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +A `bool` value is assigned to a `float`/`double`/`long double` variable. + +## Motivation + +Assigning `true`/`false` to a floating-point variable relies on the implicit `bool`-to-float conversion +(`1.0`/`0.0`) and usually signals a typo or a variable of the wrong type, rather than an intentional +numeric `1.0`/`0.0`. + +## How to fix + +Before: +```cpp +void f() { + double d = true; // <- likely meant a numeric value +} +``` + +After: +```cpp +void f() { + double d = 1.0; +} +``` diff --git a/man/checkers/assignBoolToPointer.md b/man/checkers/assignBoolToPointer.md new file mode 100644 index 00000000000..cde6609a1bb --- /dev/null +++ b/man/checkers/assignBoolToPointer.md @@ -0,0 +1,46 @@ +# assignBoolToPointer + +**Message**: Boolean value assigned to pointer.
+**Category**: Correctness/Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +A `bool` value is assigned directly to a pointer variable. + +## Motivation + +Assigning a `bool` to a pointer almost never expresses what a reader would expect a pointer assignment +to mean, and is usually a typo for something like assigning through the pointer (`*p = flag;`) instead +of to it. + +The two possible values are not equally risky. Assigning `false` is harmless: it converts to `0`, which +is always a well-defined way to give the pointer a null value - purely a readability/intent problem, not +a safety one. Assigning `true` is different: in C++ this pattern is normally rejected outright by the +compiler; in C it is typically accepted (at most with a warning) as an implicit conversion of the value +`1` to a pointer, which the C standard leaves implementation-defined - the resulting pointer is not +guaranteed to be aligned or to point to anything real. If that pointer is later dereferenced, the +dereference itself is undefined behaviour, because the pointer doesn't actually point to a valid object +of its type. Since the assigned value is often a variable rather than a literal, either outcome is +possible depending on what the variable holds at runtime. + +## How to fix + +Before: +```cpp +bool flag; +bool *p; +void f() { + p = flag; // <- likely meant '*p = flag;' +} +``` + +After: +```cpp +bool flag; +bool *p; +void f() { + *p = flag; +} +``` diff --git a/man/checkers/assignIfError.md b/man/checkers/assignIfError.md new file mode 100644 index 00000000000..617724ffccd --- /dev/null +++ b/man/checkers/assignIfError.md @@ -0,0 +1,40 @@ +# assignIfError + +**Message**: Mismatching assignment and comparison, comparison 'y==1' is always false.
+**Category**: Correctness
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A variable that was just assigned via a bitmask operation (`x = y & 0xf0;`) is compared against a +constant that mask could never produce. + +## Motivation + +Once a variable has been masked with `& 0xf0`, its value is constrained to the multiples of 16 that mask +allows - comparing it against a value the mask rules out (like `1`) can never match, which usually means +either the mask or the comparison value is wrong. + +## How to fix + +Before: +```cpp +void f(int x) { + int y = x & 0xf0; + if (y == 1) {} // <- 'y' can never be 1, its low bits were just masked off +} +``` + +After: +```cpp +void f(int x) { + int y = x & 0xf0; + if (y == 0x10) {} +} +``` + +## Related checkers + +- [knownConditionTrueFalse.md](knownConditionTrueFalse.md) - the more general check for a condition + whose truth value cppcheck already knows in advance. diff --git a/man/checkers/assignmentInAssert.md b/man/checkers/assignmentInAssert.md new file mode 100644 index 00000000000..bf550ae7f38 --- /dev/null +++ b/man/checkers/assignmentInAssert.md @@ -0,0 +1,46 @@ +# assignmentInAssert + +**Message**: Assert statement modifies 'x'.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +The assert expression assigns to, or increments/decrements, a variable that is also used outside the +assert - for example `assert(x = compute());`. + +This checker only runs when the `warning` severity is enabled. + +## Motivation + +`assert()` is compiled out entirely in release builds (when `NDEBUG` is defined). Any effect placed +inside it - such as assigning to a variable that's relied on afterwards - only happens in debug builds +and silently vanishes in release builds. This is a classic source of code that "works when debugging" +and breaks (or does nothing) in the shipped build. + +## How to fix + +Perform the assignment outside the `assert()`, and assert on the already-computed result. + +Before: +```cpp +void f(int y) { + int x; + assert(x = y + 1); // <- 'x' only gets its value in debug builds +} +``` + +After: +```cpp +void f(int y) { + int x; + x = y + 1; + assert(x == 1); +} +``` + +## Related checkers + +- [assertWithSideEffect.md](assertWithSideEffect.md) - the same idea, but for a function call inside + `assert()` that may have a side effect, rather than a direct assignment. diff --git a/man/checkers/assignmentInCondition.md b/man/checkers/assignmentInCondition.md new file mode 100644 index 00000000000..75920adc6b1 --- /dev/null +++ b/man/checkers/assignmentInCondition.md @@ -0,0 +1,42 @@ +# assignmentInCondition + +**Message**: Suspicious assignment in condition. Condition 't=s' is always true.
+**Category**: Correctness
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A container or iterator is assigned (`=`) directly inside a condition, where `==` was probably meant - +as written, the condition is always true. + +## Motivation + +`if (t = s)` assigns `s` to `t` and then tests the (always-true, for a container/iterator) result of +that assignment - almost certainly a typo for `if (t == s)`. Because it's valid, compiling code, this is +easy to miss in review. + +## How to fix + +Before: +```cpp +#include +void f(const std::string& s) { + std::string t; + if (t = s) {} // <- always true, did you mean '=='? +} +``` + +After: +```cpp +#include +void f(const std::string& s) { + std::string t = s; + if (!t.empty()) {} +} +``` + +## Related checkers + +- [clarifyCondition.md](clarifyCondition.md) - a related but distinct mistake: an assignment or bitwise + operator next to a comparison with ambiguous precedence. diff --git a/man/checkers/autoVariables.md b/man/checkers/autoVariables.md new file mode 100644 index 00000000000..d51b69a3d7f --- /dev/null +++ b/man/checkers/autoVariables.md @@ -0,0 +1,46 @@ +# autoVariables + +**Message**: Address of local auto-variable assigned to a function parameter.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +The address of a local variable is assigned to a pointer function *parameter* (directly, through a +struct member, or through an array element reached via that parameter), so the address leaks back to +the caller. + +## Motivation + +The assignment itself just stores an address - the trouble starts once the function returns and `num` +goes out of scope: the caller is left holding a pointer to memory it doesn't own anymore, and using that +pointer (which is the entire reason it was written out through the parameter) is undefined behaviour. +Because the memory involved is usually still intact for a little while afterwards, this kind of bug +often "works" in testing and then fails unpredictably once something else reuses that memory - which +makes it worth catching at analysis time instead of at runtime. + +## How to fix + +Before: +```cpp +void foo(int **res) { + int num = 2; + *res = # // <- autoVariables: 'num' won't exist once foo() returns +} +``` + +After: return the value itself, or allocate storage that outlives the function. +```cpp +void foo(int *res) { + int num = 2; + *res = num; +} +``` + +## Related checkers + +- [danglingLifetime.md](danglingLifetime.md) - the same idea, but for a local variable's address + escaping into a global/static/member pointer instead of a function parameter. +- [returnDanglingLifetime.md](returnDanglingLifetime.md) - the same idea, but escaping through `return` + instead of a parameter. diff --git a/man/checkers/autovarInvalidDeallocation.md b/man/checkers/autovarInvalidDeallocation.md new file mode 100644 index 00000000000..f84a7b13164 --- /dev/null +++ b/man/checkers/autovarInvalidDeallocation.md @@ -0,0 +1,35 @@ +# autovarInvalidDeallocation + +**Message**: Deallocating a pointer that was not dynamically allocated: tmp
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +`free()`/`delete` is called on something that wasn't allocated dynamically - a local array, a string +literal, or the address of a local/global/static variable. + +## Motivation + +`free()`/`delete` are only valid on memory that was actually obtained from the matching allocation +function. Calling either on something else - stack memory, a string literal, an unrelated variable's +address - is undefined behaviour, typically corrupting the memory allocator's own bookkeeping. + +## How to fix + +Before: +```cpp +void foo() { + char tmp[256]; + free(tmp); // <- autovarInvalidDeallocation: 'tmp' isn't heap-allocated +} +``` + +After: +```cpp +void foo() { + char *tmp = malloc(256); + free(tmp); +} +``` diff --git a/man/checkers/badBitmaskCheck.md b/man/checkers/badBitmaskCheck.md new file mode 100644 index 00000000000..7e6c53c16b7 --- /dev/null +++ b/man/checkers/badBitmaskCheck.md @@ -0,0 +1,46 @@ +# badBitmaskCheck + +**Message**: Result of operator '|' is always true if one operand is non-zero. Did you intend to use '&'?
+**Category**: Correctness
+**Severity**: Warning/Style
+**Language**: C/C++ + +## Description + +A boolean/condition value is computed with `|` where `&` was probably meant - the result of `x | mask` +is true as soon as `x` is nonzero, regardless of `mask`. This message is also used for the opposite, +harmless mistake: an operand `| 0` that has no effect and can be removed (reported at `style` severity +instead of `warning`). + +## Motivation + +`|` and `&` look similar but behave very differently in a boolean context: `x | mask` is true whenever +`x` alone is nonzero, so the mask contributes nothing and the check silently always passes. This is easy +to miss because the code compiles and often "looks right" at a glance. + +This check may need `--check-level=exhaustive` to see every case. + +## How to fix + +Before: +```cpp +bool f(int x) { + bool b = x | 0x02; // <- always true if x is nonzero + return b; +} +``` + +After: +```cpp +bool f(int x) { + bool b = x & 0x02; + return b; +} +``` + +## Related checkers + +- [mismatchingBitAnd.md](mismatchingBitAnd.md) - a different bitmask mistake: chained `&` masks that + share no bits. +- [comparisonError.md](comparisonError.md) - a bitwise expression compared against a constant it can + never produce. diff --git a/man/checkers/bitwiseOnBoolean.md b/man/checkers/bitwiseOnBoolean.md new file mode 100644 index 00000000000..c463b6e4d51 --- /dev/null +++ b/man/checkers/bitwiseOnBoolean.md @@ -0,0 +1,43 @@ +# bitwiseOnBoolean + +**Message**: Boolean expression 'x' is used in bitwise operation. Did you mean '&&'?
+**Category**: Readability
+**Severity**: Style (Inconclusive)
+**Language**: C/C++ (also applies to C's `_Bool`) + +## Description + +`&`/`|`/`&=`/`|=` is used where at least one operand is boolean, when `&&`/`||` was likely intended. + +## Motivation + +This checker is about readability. Readability is subjective - opinions differ about what is more +readable. Please follow your own opinion. + +When both operands are `bool`, `&`/`|` on their `0`/`1` representation happens to produce the same +truth value as `&&`/`||`, so this code usually still works correctly today. The main reason to flag it +anyway is common practice: `&&`/`||` is the conventional, unambiguous way to write boolean logic in +C/C++, while `&`/`|` is understood to mean bitwise work - so a stray single `&`/`|` reads as a likely +typo even when it happens to be harmless. There is also one real behavioural difference: `&`/`|` always +evaluates both operands, so if the other side has a side effect, using `&`/`|` instead of `&&`/`||` +changes whether that side effect happens. + +When the *other* operand isn't itself boolean (e.g. an integer flag or count), `&`/`|` combines the +boolean's `0`/`1` value with it bit-by-bit, which generally is **not** the same truth value `&&`/`||` +would produce - in that case this points at an actual logic bug, not just a style preference. + +## How to fix + +Before: +```cpp +void f(bool a, bool b) { + if (a & b) {} // <- likely meant '&&' +} +``` + +After: +```cpp +void f(bool a, bool b) { + if (a && b) {} +} +``` diff --git a/man/checkers/bufferAccessOutOfBounds.md b/man/checkers/bufferAccessOutOfBounds.md new file mode 100644 index 00000000000..9280c54d554 --- /dev/null +++ b/man/checkers/bufferAccessOutOfBounds.md @@ -0,0 +1,37 @@ +# bufferAccessOutOfBounds + +**Message**: Buffer is accessed out of bounds: d
+**Category**: Undefined Behaviour
+**Severity**: Error/Warning
+**Language**: C/C++ + +## Description + +A call to a function cppcheck has size information for (`strcpy`, `strcat`, `memcpy`, `sprintf`, and +similar, via the library configuration) is passed a buffer that's too small for what the call can read +or write. + +## Motivation + +Many standard library functions read or write a caller-supplied buffer without any bounds checking of +their own - it's entirely up to the caller to make sure the buffer is large enough. Getting this wrong +is a very common, very old source of buffer overruns in C/C++ code. + +## How to fix + +Before: +```cpp +void f() { + char d[3] = {}; + strcat(d, "12345678"); // <- 'd' can't hold this much +} +``` + +After: +```cpp +void f() { + char d[10] = {}; + strcat(d, "12345678"); +} +``` + diff --git a/man/checkers/catchExceptionByValue.md b/man/checkers/catchExceptionByValue.md new file mode 100644 index 00000000000..80f058766ab --- /dev/null +++ b/man/checkers/catchExceptionByValue.md @@ -0,0 +1,45 @@ +# catchExceptionByValue + +**Message**: Exception should be caught by reference.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ only + +## Description + +A `catch` clause catches a class type by value instead of by (const) reference. + +## Motivation + +Catching by value makes a copy of the exception object (and, if the actual thrown type is more derived +than the `catch` clause's type, slices it down, losing information) every time an exception is caught - +catching by `const&` avoids the copy entirely and preserves the exception's real, most-derived type. + +## How to fix + +Before: +```cpp +void doWork(); +void f() { + try { + doWork(); + } catch (std::exception err) { // <- caught by value + } +} +``` + +After: +```cpp +void doWork(); +void f() { + try { + doWork(); + } catch (const std::exception& err) { + } +} +``` + +## Related checkers + +- [exceptRethrowCopy.md](exceptRethrowCopy.md) - a related `catch`-clause mistake: rethrowing with + `throw x;` instead of a bare `throw;`. diff --git a/man/checkers/charBitOp.md b/man/checkers/charBitOp.md new file mode 100644 index 00000000000..3d595982829 --- /dev/null +++ b/man/checkers/charBitOp.md @@ -0,0 +1,48 @@ +# charBitOp + +**Message**: When using 'char' variables in bit operations, sign extension can generate unexpected results.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A `char` variable is used as an operand of `&`/`|`/`^` and the result is stored in a wider type +(`short`/`int`/`long`) - if the `char` is negative, sign extension fills the extra bits with `1`s +instead of `0`s, so the resulting bit pattern isn't the one that was probably intended. + +## Motivation + +On many platforms `char` can hold negative values (typically -128..127 instead of 0..255). When a +negative `char` is used in a bitwise operation with a wider type, it's first sign-extended - the extra, +high-order bits of the wider type are filled with copies of the sign bit (`1` for a negative value) +rather than `0`. Code that expects a `char`'s bit pattern to occupy only its low 8 bits and leave the +rest zero gets a surprising result whenever the `char` happens to be negative. + +## How to fix + +Use `unsigned char` (or mask the operand) if the intent is to treat the byte as a small non-negative +number. + +Before: +```cpp +void foo(int a, int *result) { + signed char ch = -1; + *result = a | ch; // <- sign extension fills the high bits of 'ch' with 1s +} +``` + +After: +```cpp +void foo(int a, int *result) { + unsigned char ch = 0xff; + *result = a | ch; +} +``` + +## Related checkers + +- [signedCharArrayIndex.md](signedCharArrayIndex.md) - the same signed-`char` sign-extension pitfall, + but for using a `char` as an array index instead of a bitwise operand. +- [checkCastIntToCharAndBack.md](checkCastIntToCharAndBack.md) - a different `char`-narrowing pitfall, + about storing `getchar()`'s return value in a `char` before comparing it with `EOF`. diff --git a/man/checkers/checkCastIntToCharAndBack.md b/man/checkers/checkCastIntToCharAndBack.md new file mode 100644 index 00000000000..23f41bba4f9 --- /dev/null +++ b/man/checkers/checkCastIntToCharAndBack.md @@ -0,0 +1,58 @@ +# checkCastIntToCharAndBack + +**Message**: Storing getchar() return value in char variable and then comparing with EOF.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +The return value of `getchar()`/`getc()`/`fgetc()` (an `int`, so it can represent every possible byte +value plus the special value `EOF`) is stored in a `char` and *then* compared with `EOF` - once narrowed +to `char`, a real byte that happens to equal the platform's `EOF` value as a `char` can be mistaken for +end-of-file, or vice versa. + +## Motivation + +`getchar()` and friends return `int` specifically so that every possible `unsigned char` byte value +(0-255) and the distinct sentinel value `EOF` can all be told apart. Storing the result in a `char` +first throws away exactly the information needed to make that distinction - depending on the platform's +`char` signedness and `EOF`'s value, a legitimate byte can end up equal to `EOF` after narrowing, causing +input to be truncated early, or (less commonly) `EOF` itself to go unrecognized. + +## How to fix + +Keep the return value in an `int` until after it has been compared with `EOF`. + +Before: +```cpp +#include +void bar(char); +void f() { + unsigned char c; + c = getchar(); + while (c != EOF) { // <- 'c' can never actually equal EOF once narrowed + bar(c); + c = getchar(); + } +} +``` + +After: +```cpp +#include +void bar(int); +void f() { + int c; + c = getchar(); + while (c != EOF) { + bar(c); + c = getchar(); + } +} +``` + +## Related checkers + +- [charBitOp.md](charBitOp.md) - a different `char`-signedness pitfall, about sign extension in bitwise + operations rather than narrowing a stream-read result. diff --git a/man/checkers/checkLibraryUseIgnore.md b/man/checkers/checkLibraryUseIgnore.md new file mode 100644 index 00000000000..ace4e9ec3e6 --- /dev/null +++ b/man/checkers/checkLibraryUseIgnore.md @@ -0,0 +1,29 @@ +# checkLibraryUseIgnore + +**Message**: --check-library: Function f() should have / configuration
+**Category**: Configuration
+**Severity**: Information
+**Language**: C/C++ + +## Description + +Only produced with `--check-library`: a function call couldn't be classified as either using or +ignoring a tracked (allocated) variable passed into it. This is aimed at people writing library +configurations, not at application code. + +## Motivation + +cppcheck's leak-tracking checks ([memleak.md](memleak.md) and its siblings) rely on knowing, for every +function a tracked variable is passed to, whether that function takes ownership of it, merely reads it, +or does something else. When a library configuration doesn't say, `--check-library` flags the gap so +the configuration can be completed rather than silently guessing. + +## How to fix + +Add a `` or `` entry for the function in the relevant library configuration file, so +cppcheck knows how it treats the argument. + +## Related checkers + +- [memleak.md](memleak.md) and its siblings - the actual leak/double-free/use-after-free checks that + this configuration gap affects the accuracy of. diff --git a/man/checkers/clarifyCalculation.md b/man/checkers/clarifyCalculation.md new file mode 100644 index 00000000000..11d6da450e8 --- /dev/null +++ b/man/checkers/clarifyCalculation.md @@ -0,0 +1,41 @@ +# clarifyCalculation + +**Message**: Clarify calculation precedence for '*' and '?'.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A calculation (`a * b`, `a + b`, ...) sits directly to the left of `?` in a ternary expression +(`a * b ? c : d`) - it's easy to misread this as `a * (b ? c : d)`, but it's actually `(a * b) ? c : d`. + +## Motivation + +The code compiles exactly as written and does exactly what the operator precedence rules say, but that's +not always what a quick read suggests - a reader can easily assume the ternary binds to the calculation's +last operand rather than to the whole calculation. Adding parentheses costs nothing and removes the +ambiguity for the next reader. + +## How to fix + +Add parentheses that make the actual grouping explicit. + +Before: +```cpp +int f(char c) { + return 10 * (c == 0) ? 1 : 2; // <- looks like '10 * ((c == 0) ? 1 : 2)' +} +``` + +After: +```cpp +int f(char c) { + return (10 * (c == 0)) ? 1 : 2; +} +``` + +## Related checkers + +- [clarifyStatement.md](clarifyStatement.md) - a different easy-to-misread-precedence pitfall, about + `*p++;` rather than a calculation next to `?`. diff --git a/man/checkers/clarifyCondition.md b/man/checkers/clarifyCondition.md new file mode 100644 index 00000000000..cf5095956a3 --- /dev/null +++ b/man/checkers/clarifyCondition.md @@ -0,0 +1,42 @@ +# clarifyCondition + +**Message**: Suspicious condition (assignment + comparison); Clarify expression with parentheses.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +An assignment or a bitwise operator sits next to a comparison in a way whose precedence is easy to +misread (`x = a < 0`, `a & b == c`) - cppcheck suggests adding parentheses to make the intended meaning +explicit. + +## Motivation + +`x = a < 0` really means `x = (a < 0)` (assign a boolean), not `(x = a) < 0` - but a reader skimming the +line can easily assume the opposite. Adding parentheses costs nothing and removes any ambiguity about +which reading was intended. + +## How to fix + +Before: +```cpp +void f(int a) { + int x; + if (x = a < 0) {} // <- is this '(x = a) < 0' or 'x = (a < 0)'? +} +``` + +After: +```cpp +void f(int a) { + int x; + x = (a < 0); + if (x) {} +} +``` + +## Related checkers + +- [assignmentInCondition.md](assignmentInCondition.md) - a related but distinct mistake: a container or + iterator assignment written where a comparison was probably meant. diff --git a/man/checkers/clarifyStatement.md b/man/checkers/clarifyStatement.md new file mode 100644 index 00000000000..728b5f9dd53 --- /dev/null +++ b/man/checkers/clarifyStatement.md @@ -0,0 +1,43 @@ +# clarifyStatement + +**Message**: In expression like '*A++' the result of '*' is unused. Did you intend to write '(*A)++;'?
+**Category**: Code Quality
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A statement like `*p++;` looks like it dereferences the incremented pointer, but `++` binds tighter +than the dereference - the pointer is incremented and the dereferenced (old) value is simply discarded. + +## Motivation + +`*p++` is parsed as `*(p++)`: the pointer itself is incremented, and the value that was pointed to +before the increment is read and then thrown away, since the statement doesn't do anything with it. A +reader skimming the code can easily assume the intent was to modify what the pointer points to +(`(*p)++`), which is a different operation entirely. + +## How to fix + +Add parentheses to make the intended operation explicit. + +Before: +```cpp +char* f(char* c) { + *c++; // <- increments 'c' and discards the old *c, doesn't touch what 'c' points to + return c; +} +``` + +After: +```cpp +char* f(char* c) { + (*c)++; + return c; +} +``` + +## Related checkers + +- [clarifyCalculation.md](clarifyCalculation.md) - a different easy-to-misread-precedence pitfall, about + a calculation next to `?` rather than `*p++`. diff --git a/man/checkers/commaSeparatedReturn.md b/man/checkers/commaSeparatedReturn.md new file mode 100644 index 00000000000..faa1981c894 --- /dev/null +++ b/man/checkers/commaSeparatedReturn.md @@ -0,0 +1,48 @@ +# commaSeparatedReturn + +**Message**: Comma is used in return statement. The comma can easily be misread as a ';'.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A comma appears inside a `return` statement (`return a + 1, b++;`) - this is a single statement using +the comma operator, easily misread as `return a + 1;` followed by a separate `b++;`. + +**This check is currently switched off in cppcheck and never produces a warning**, even for the exact +code pattern it's meant to catch - it's kept in the error list only so its ID stays recognized (for +example in older suppression files), not because it's active. Don't rely on it to catch this mistake. + +## Motivation + +The comma operator lets one statement evaluate several expressions in sequence, discarding all but the +last one's value - `return a + 1, b++;` actually returns the value of `b++`, not `a + 1`. This reads +exactly like two separate statements with a typo'd semicolon, which is a very easy way to misjudge what +value a function returns. + +## How to fix + +Split the comma expression into separate statements, and make the intended return value explicit. + +Before: +```cpp +int f(int x, int a, int b) { + if (x) + return a + 1, // looks like this is returned... + b++; // ...but this is actually returned instead + return 0; +} +``` + +After: +```cpp +int f(int x, int a, int b) { + if (x) { + b++; + return b; + } + return 0; +} +``` + diff --git a/man/checkers/compareBoolExpressionWithInt.md b/man/checkers/compareBoolExpressionWithInt.md new file mode 100644 index 00000000000..5379e5b7d50 --- /dev/null +++ b/man/checkers/compareBoolExpressionWithInt.md @@ -0,0 +1,22 @@ +# compareBoolExpressionWithInt + +**Message**: Comparison of a boolean expression with an integer other than 0 or 1.
+**Category**: Code Quality
+**Severity**: Warning
+**Language**: C++ + +## Description + +A boolean expression (not necessarily a `bool` variable - for example `a && b`, or a comparison) is +compared against a number. + +## Motivation + +A boolean expression only ever evaluates to `0` or `1`, so comparing it against any other number is +always false, and comparing it against `0`/`1` with a relational operator is easy to get backwards - +either way, this is rarely what the code's author actually intended. + +## How to fix + +Compare the boolean expression directly, or with `==`/`!=` against `true`/`false`, instead of against +an arbitrary integer. diff --git a/man/checkers/comparePointers.md b/man/checkers/comparePointers.md new file mode 100644 index 00000000000..57de49f3b1e --- /dev/null +++ b/man/checkers/comparePointers.md @@ -0,0 +1,61 @@ +# comparePointers and subtractPointers + +**Message**: Comparing pointers that point to different objects
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ only + +## Description + +Two pointers that cppcheck can see point into two different, unrelated variables or objects are +compared (`<`, `>`, `<=`, `>=` - `comparePointers`) or subtracted (`-` - `subtractPointers`). Only +pointers into the same array/object have a meaningful order or distance between them. + +## Motivation + +Comparing or subtracting pointers that don't point into the same array or object is undefined +behaviour: there's no guarantee about how unrelated objects are laid out in memory relative to each +other, so the result of `<`/`>`/`-` between them isn't meaningful, even though the code compiles and +often looks reasonable. + +## How to fix + +Only compare or subtract pointers that point into the same array or object. + +Before: +```cpp +bool f() { + int x = 0; + int y = 0; + int* xp = &x; + int* yp = &y; + return xp > yp; // <- comparePointers: 'x' and 'y' are unrelated variables +} +``` + +After: +```cpp +bool f() { + int arr[2] = {0, 0}; + int* xp = &arr[0]; + int* yp = &arr[1]; + return xp > yp; // pointers into the same array can be meaningfully compared +} +``` + +Before: +```cpp +int f() { + int x = 0; + int y = 1; + return &x - &y; // <- subtractPointers: the distance between unrelated variables is meaningless +} +``` + +After: +```cpp +int f() { + int arr[2] = {0, 1}; + return &arr[0] - &arr[1]; +} +``` diff --git a/man/checkers/compareValueOutOfTypeRangeError.md b/man/checkers/compareValueOutOfTypeRangeError.md new file mode 100644 index 00000000000..e94e748a372 --- /dev/null +++ b/man/checkers/compareValueOutOfTypeRangeError.md @@ -0,0 +1,38 @@ +# compareValueOutOfTypeRangeError + +**Message**: Comparing expression of type 'unsigned char' against value 256. Condition is always false.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A variable is compared against a constant that lies outside the range its own type can represent (for +example, an `unsigned char` compared against `256`). + +## Motivation + +An `unsigned char` can never hold `256` - it wraps around before ever reaching it - so the comparison's +result is fixed regardless of the variable's actual value. This is usually a sign the variable's type is +too small for what it's meant to hold, or that the compared-against constant is wrong. + +## How to fix + +Before: +```cpp +void f(unsigned char c) { + if (c == 256) {} // <- 'c' can never reach 256 +} +``` + +After: +```cpp +void f(int c) { + if (c == 256) {} +} +``` + +## Related checkers + +- [knownConditionTrueFalse.md](knownConditionTrueFalse.md) - the more general check for a condition + whose truth value cppcheck already knows in advance. diff --git a/man/checkers/comparisonError.md b/man/checkers/comparisonError.md new file mode 100644 index 00000000000..dc0cab73b6c --- /dev/null +++ b/man/checkers/comparisonError.md @@ -0,0 +1,37 @@ +# comparisonError + +**Message**: Expression '(X & 0x7) == 0x8' is always false.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A bitwise `&`/`|` expression is compared against a constant that mask could never actually produce. + +## Motivation + +Masking a value with `& 0x07` can only ever produce a result in `0..7`; comparing that result against +`8` (or any value the mask can't produce) is either dead code or a sign the mask or the compared-against +constant is wrong. + +## How to fix + +Before: +```cpp +void f(int a) { + if ((a & 0x07) == 8) {} // <- masking with 0x07 can never produce 8 +} +``` + +After: +```cpp +void f(int a) { + if ((a & 0x0f) == 8) {} +} +``` + +## Related checkers + +- [badBitmaskCheck.md](badBitmaskCheck.md) - `|` used where `&` was probably meant. +- [mismatchingBitAnd.md](mismatchingBitAnd.md) - chained `&` masks that share no bits. diff --git a/man/checkers/comparisonFunctionIsAlwaysTrueOrFalse.md b/man/checkers/comparisonFunctionIsAlwaysTrueOrFalse.md new file mode 100644 index 00000000000..7ee231c0650 --- /dev/null +++ b/man/checkers/comparisonFunctionIsAlwaysTrueOrFalse.md @@ -0,0 +1,37 @@ +# comparisonFunctionIsAlwaysTrueOrFalse + +**Message**: Comparison of two identical variables with isless(x,x) always evaluates to false.
+**Category**: Code Quality
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +One of the C99 comparison macros (`isgreater`, `isless`, `islessgreater`, `isgreaterequal`, +`islessequal`) is called with the exact same variable as both arguments, which always evaluates to the +same result. + +## Motivation + +Comparing a value against itself with a strict-ordering macro always produces the same, fixed answer - +so the call doesn't test anything, and is almost always a copy-paste mistake where the second argument +should have been a different variable. + +## How to fix + +Before: +```cpp +#include +bool f(int x) { + return isless(x,x); // <- always false +} +``` + +After: +```cpp +#include +bool f(int x, int y) { + return isless(x,y); +} +``` + diff --git a/man/checkers/comparisonOfBoolWithBoolError.md b/man/checkers/comparisonOfBoolWithBoolError.md new file mode 100644 index 00000000000..918b60f8a28 --- /dev/null +++ b/man/checkers/comparisonOfBoolWithBoolError.md @@ -0,0 +1,37 @@ +# comparisonOfBoolWithBoolError + +**Message**: Comparison of a variable having boolean value using relational (<, >, <= or >=) operator.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +Two `bool` variables are compared with `<`, `>`, `<=` or `>=`. + +## Motivation + +`bool` only has two values, so ordering two of them doesn't express anything `==`/`!=` wouldn't say +more clearly, and is easy to get backwards since `false < true` is not always the intuitive direction a +reader expects. + +## How to fix + +Before: +```cpp +void f(bool a, bool b) { + if (a < b) {} // <- relational comparison between two bools +} +``` + +After: +```cpp +void f(bool a, bool b) { + if (a != b) {} +} +``` + +## Related checkers + +- [comparisonOfBoolWithInvalidComparator.md](comparisonOfBoolWithInvalidComparator.md) - the same kind + of relational comparison, but against a `true`/`false` literal instead of another `bool` variable. diff --git a/man/checkers/comparisonOfBoolWithInvalidComparator.md b/man/checkers/comparisonOfBoolWithInvalidComparator.md new file mode 100644 index 00000000000..4fc9dafd9c9 --- /dev/null +++ b/man/checkers/comparisonOfBoolWithInvalidComparator.md @@ -0,0 +1,40 @@ +# comparisonOfBoolWithInvalidComparator + +**Message**: Comparison of a boolean value using relational operator (<, >, <= or >=).
+**Category**: Code Quality
+**Severity**: Warning
+**Language**: C++ + +## Description + +A boolean literal (`true`/`false`) is compared to something using `<`, `>`, `<=` or `>=`. + +## Motivation + +`<`, `>`, `<=` and `>=` are well-defined against a `bool` literal (`false` is `0`, `true` is `1`), so +this code already compiles and evaluates correctly - there is no functional problem to fix. The reason +to flag it is readability: `bool` only has two values, so an ordering comparison against `true`/`false` +says nothing that `==`/`!=` wouldn't say more directly, and it forces the reader to work out which of +`false`/`true` is "smaller" instead of just reading the equality check. Preferring `==`/`!=` for +two-valued types is the clearer, more idiomatic style. + +## How to fix + +Before: +```cpp +void f(bool x) { + if (x > false) {} // <- relational comparison against a bool literal +} +``` + +After: +```cpp +void f(bool x) { + if (x == true) {} +} +``` + +## Related checkers + +- [comparisonOfBoolWithBoolError.md](comparisonOfBoolWithBoolError.md) - the same kind of relational + comparison, but between two `bool` variables instead of against a literal. diff --git a/man/checkers/comparisonOfFuncReturningBoolError.md b/man/checkers/comparisonOfFuncReturningBoolError.md new file mode 100644 index 00000000000..b01a4a09e25 --- /dev/null +++ b/man/checkers/comparisonOfFuncReturningBoolError.md @@ -0,0 +1,42 @@ +# comparisonOfFuncReturningBoolError and comparisonOfTwoFuncsReturningBoolError + +**Message**: Comparison of a function returning boolean value using relational (<, >, <= or >=) operator.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +The result of a function known to return `bool` is compared with `<`, `>`, `<=` or `>=` - +`comparisonOfFuncReturningBoolError` when one side is such a call, `comparisonOfTwoFuncsReturningBoolError` +when both sides are. + +## Motivation + +`<`, `>`, `<=` and `>=` are well-defined for `bool` results (`false` is `0`, `true` is `1`), so this code +already compiles and evaluates correctly - there is no functional problem to fix. The reason to flag it +is readability: `bool` only has two values, so ordering the result of a bool-returning function says +nothing that `==`/`!=` wouldn't say more directly, and it forces the reader to work out which of +`false`/`true` is "smaller" instead of just reading the equality/logical check. Preferring `==`/`!=` (or +plain `&&`/`!`) for two-valued results is the clearer, more idiomatic style. + +## How to fix + +Before: +```cpp +bool compare1(int x); +bool compare2(int x); +void f(int x) { + if (compare1(x) > compare2(x)) {} // <- relational comparison between two bool-returning calls +} +``` + +After: +```cpp +bool compare1(int x); +bool compare2(int x); +void f(int x) { + if (compare1(x) && !compare2(x)) {} +} +``` + diff --git a/man/checkers/complexPatternError.md b/man/checkers/complexPatternError.md new file mode 100644 index 00000000000..11f6f994e04 --- /dev/null +++ b/man/checkers/complexPatternError.md @@ -0,0 +1,38 @@ +# complexPatternError + +**Message**: Found complex pattern inside Token::simpleMatch() call: "%type%"
+**Category**: Code Quality
+**Severity**: Error
+**Language**: C++ (cppcheck's own source code only) + +## Description + +A pattern given to `Token::simpleMatch()`/`Token::findsimplematch()` contains wildcard syntax +(`%type%`, `[abc]`, `a|b`, ...) that those "simple" functions don't actually interpret; they only do a +literal, word-by-word text comparison. + +This is not a general-purpose checker for arbitrary C/C++ programs - see +[simplePatternError.md](simplePatternError.md) for the shared background on this internal, +cppcheck-source-only checker. + +## Motivation + +Mixing up `Match()` with `simpleMatch()` silently loses the wildcard matching a pattern was written to +rely on - the code compiles and runs, but the comparison never matches the way the author intended, +turning into a subtle logic bug in cppcheck's own analysis. + +## How to fix + +Before: +```cpp +Token::simpleMatch(tok, "%type%"); // <- simpleMatch() won't interpret this +``` + +After: +```cpp +Token::Match(tok, "%type%"); +``` + +## Related checkers + +- [simplePatternError.md](simplePatternError.md) - the opposite mistake, using `Match()` with a pattern that has no wildcard syntax at all. diff --git a/man/checkers/constParameter.md b/man/checkers/constParameter.md new file mode 100644 index 00000000000..7f5342ea7f2 --- /dev/null +++ b/man/checkers/constParameter.md @@ -0,0 +1,48 @@ +# constParameter + +**Message**: Parameter 'x' can be declared as const
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +An array-typed function parameter is never used to modify its contents, so it could be declared as an +array of `const` elements. + +## Motivation + +A missing `const` hides a guarantee the compiler could otherwise enforce and readers could otherwise +rely on: that the function only reads the array, never modifies it. + +## How to fix + +Before: +```cpp +void f(int n, int v[42]) { // <- 'v' is only read + int j = 0; + for (int i = 0; i < n; ++i) { + j += 1; + if (j == 1) {} + } +} +``` + +After: +```cpp +void f(int n, const int v[42]) { + int j = 0; + for (int i = 0; i < n; ++i) { + j += 1; + if (j == 1) {} + } +} +``` + +## Related checkers + +- [constVariable.md](constVariable.md) - the same idea, for a local array variable instead of a parameter. +- [constParameterReference.md](constParameterReference.md) - the reference-parameter equivalent. +- [constParameterPointer.md](constParameterPointer.md) - the pointer-parameter equivalent. +- [constParameterCallback.md](constParameterCallback.md) - the same idea, but for a parameter of a + function used as a callback. diff --git a/man/checkers/constParameterCallback.md b/man/checkers/constParameterCallback.md new file mode 100644 index 00000000000..bf2009e9846 --- /dev/null +++ b/man/checkers/constParameterCallback.md @@ -0,0 +1,44 @@ +# constParameterCallback + +**Message**: Parameter 'x' can be declared with const, however it seems that 'f' is a callback function.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +Same idea as [constParameter.md](constParameter.md)/[constParameterReference.md](constParameterReference.md)/ +[constParameterPointer.md](constParameterPointer.md): a parameter is never used to modify what it refers +to, so it could be declared `const`. This variant is for when the function is used as a callback (passed +as a function pointer) - fixing it may also require adjusting whatever calls through that function +pointer. + +## Motivation + +A missing `const` hides a guarantee the compiler could otherwise enforce and readers could otherwise +rely on. The callback case is called out separately because the fix isn't purely local: the function +pointer type it's assigned to also needs to change, or the cast at the call site needs adjusting. + +## How to fix + +Before: +```cpp +#include +void dostuff(int (*cb)(std::vector&)); +int callback(std::vector& x) { return x[0]; } // <- 'x' is only read +void f() { dostuff(callback); } +``` + +After: const-ify the parameter, and the function pointer type it's passed through. +```cpp +#include +void dostuff(int (*cb)(const std::vector&)); +int callback(const std::vector& x) { return x[0]; } +void f() { dostuff(callback); } +``` + +## Related checkers + +- [constParameter.md](constParameter.md), [constParameterReference.md](constParameterReference.md), + [constParameterPointer.md](constParameterPointer.md) - the same idea, for a parameter that isn't part + of a callback function's signature. diff --git a/man/checkers/constParameterReference.md b/man/checkers/constParameterReference.md new file mode 100644 index 00000000000..8a6be03d6cc --- /dev/null +++ b/man/checkers/constParameterReference.md @@ -0,0 +1,60 @@ +# constParameterReference + +**Message**: Parameter 'x' can be declared as reference to const
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +A reference parameter is never used to modify what it refers to, so it could be declared as a reference +to `const`. + +## Motivation + +A missing `const` hides a guarantee the compiler could otherwise enforce and readers could otherwise +rely on: that the function only reads through the reference, never modifies the caller's object through +it. + +## How to fix + +Before: +```cpp +#include +auto foo(std::vector& vec, bool flag) { // <- 'vec' is only read + std::vector dummy; + std::vector::iterator iter; + if (flag) + iter = vec.begin(); + else { + dummy.push_back(42); + iter = dummy.begin(); + } + return *iter; +} +``` + +After: +```cpp +#include +auto foo(const std::vector& vec, bool flag) { + std::vector dummy; + std::vector::iterator iter; + if (flag) + iter = dummy.begin(); + else { + dummy.push_back(42); + iter = dummy.begin(); + } + return *iter; +} +``` + +## Related checkers + +- [constParameter.md](constParameter.md) - the array-parameter equivalent. +- [constParameterPointer.md](constParameterPointer.md) - the pointer-parameter equivalent. +- [constVariableReference.md](constVariableReference.md) - the same idea, for a local reference + variable instead of a parameter. +- [constParameterCallback.md](constParameterCallback.md) - the same idea, but for a parameter of a + function used as a callback. diff --git a/man/checkers/constStatement.md b/man/checkers/constStatement.md new file mode 100644 index 00000000000..60c222a12fe --- /dev/null +++ b/man/checkers/constStatement.md @@ -0,0 +1,34 @@ +# constStatement + +**Message**: Redundant code: Found a statement that has no effect.
+**Category**: Code Quality
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A statement is just a constant, a variable name, or another expression with no side effect, and its +result is discarded - the statement does nothing. + +## Motivation + +A statement that's evaluated purely for its result, with that result then thrown away, is either dead +code left over from editing (for example a comparison that was meant to be an `if`) or a missing +function call (a `;` typed where `foo();` was meant). + +## How to fix + +Before: +```cpp +void f(int x) { + x; // <- result discarded, has no effect +} +``` + +After: +```cpp +#include +void f(int x) { + printf("%d", x); +} +``` diff --git a/man/checkers/constVariable.md b/man/checkers/constVariable.md new file mode 100644 index 00000000000..ca85e979228 --- /dev/null +++ b/man/checkers/constVariable.md @@ -0,0 +1,41 @@ +# constVariable + +**Message**: Variable 'x' can be declared as const
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A local array variable is never used to modify its contents, so it could be declared as an array of +`const` elements. + +## Motivation + +A missing `const` hides a guarantee the compiler could otherwise enforce and readers could otherwise +rely on: that this array, once initialized, is never written to again. + +## How to fix + +Before: +```cpp +int f() { + static int i[1] = {}; // <- 'i' is only read + return i[0]; +} +``` + +After: +```cpp +int f() { + static const int i[1] = {}; + return i[0]; +} +``` + +## Related checkers + +- [constParameter.md](constParameter.md) - the same idea, for an array-typed function parameter instead + of a local variable. +- [constVariableReference.md](constVariableReference.md) - the reference-variable equivalent. +- [constVariablePointer.md](constVariablePointer.md) - the pointer-variable equivalent. diff --git a/man/checkers/constVariablePointer.md b/man/checkers/constVariablePointer.md new file mode 100644 index 00000000000..617cf3486bc --- /dev/null +++ b/man/checkers/constVariablePointer.md @@ -0,0 +1,45 @@ +# constVariablePointer + +**Message**: Variable 'x' can be declared as pointer to const
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A local pointer variable is never used to modify what it points to, so it could be declared as a +pointer to `const`. + +## Motivation + +A missing `const` hides a guarantee the compiler could otherwise enforce and readers could otherwise +rely on: that this pointer is only ever used to read the data it points to. + +## How to fix + +Before: +```cpp +#include +void f() { + int x = 5; + int *tm = &x; // <- 'tm' is only used to read '*tm' + printf("%d\n", *tm); +} +``` + +After: +```cpp +#include +void f() { + int x = 5; + const int *tm = &x; + printf("%d\n", *tm); +} +``` + +## Related checkers + +- [constParameterPointer.md](constParameterPointer.md) - the same idea, for a pointer parameter instead + of a local variable. +- [constVariable.md](constVariable.md) - the array-variable equivalent. +- [constVariableReference.md](constVariableReference.md) - the reference-variable equivalent. diff --git a/man/checkers/constVariableReference.md b/man/checkers/constVariableReference.md new file mode 100644 index 00000000000..3c6c1c98f0b --- /dev/null +++ b/man/checkers/constVariableReference.md @@ -0,0 +1,43 @@ +# constVariableReference + +**Message**: Variable 'x' can be declared as reference to const
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +A local reference variable is never used to modify what it refers to, so it could be declared as a +reference to `const`. + +## Motivation + +A missing `const` hides a guarantee the compiler could otherwise enforce and readers could otherwise +rely on: that this reference is only ever used to read the object it refers to. + +## How to fix + +Before: +```cpp +#include +void f(int i) { + int &j = i; // <- 'j' is only read + printf("%d\n", j); +} +``` + +After: +```cpp +#include +void f(int i) { + const int &j = i; + printf("%d\n", j); +} +``` + +## Related checkers + +- [constParameterReference.md](constParameterReference.md) - the same idea, for a reference parameter + instead of a local variable. +- [constVariable.md](constVariable.md) - the array-variable equivalent. +- [constVariablePointer.md](constVariablePointer.md) - the pointer-variable equivalent. diff --git a/man/checkers/containerOutOfBounds.md b/man/checkers/containerOutOfBounds.md new file mode 100644 index 00000000000..06d6e50defe --- /dev/null +++ b/man/checkers/containerOutOfBounds.md @@ -0,0 +1,45 @@ +# containerOutOfBounds + +**Message**: Out of bounds access in 'v\[100\]', if 'v' size is 3 and '100' is 100
+**Category**: Undefined Behaviour
+**Severity**: Error/Warning
+**Language**: C++ + +## Description + +An index or `.at()` call on a container is out of bounds for what cppcheck knows about the container's +size. + +## Motivation + +Accessing a container out of bounds is undefined behaviour - the same class of bug as an out-of-bounds +array access, but easier to introduce by accident because the container's internal storage can move or +resize without any visible syntax change at the call site. + +## How to fix + +Before: +```cpp +#include +void f() { + std::vector v(3); + v[100] = 1; // <- containerOutOfBounds +} +``` + +After: +```cpp +#include +void f() { + std::vector v(3); + v[2] = 1; +} +``` + +## Related checkers + +- [containerOutOfBoundsIndexExpression.md](containerOutOfBoundsIndexExpression.md) - the same idea, but + specifically when the index expression itself provably reaches or exceeds the container's size. +- [stlOutOfBounds.md](stlOutOfBounds.md) - the same idea, but specifically for a loop condition using + `<=` instead of `<`. +- [negativeContainerIndex.md](negativeContainerIndex.md) - the same idea, but for a negative index. diff --git a/man/checkers/containerOutOfBoundsIndexExpression.md b/man/checkers/containerOutOfBoundsIndexExpression.md new file mode 100644 index 00000000000..a435318d92f --- /dev/null +++ b/man/checkers/containerOutOfBoundsIndexExpression.md @@ -0,0 +1,39 @@ +# containerOutOfBoundsIndexExpression + +**Message**: Out of bounds access of s, index 's.size()' is out of bounds.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +An index expression itself (for example `s[s.size()]`) provably reaches or exceeds the container's own +size. + +## Motivation + +Accessing a container out of bounds is undefined behaviour. This particular shape is easy to write by +accident - `s.size()` looks like it should be the last valid position, but it is actually one past it. + +## How to fix + +Before: +```cpp +#include +void f(std::string s) { + s[s.size()] = 1; // <- containerOutOfBoundsIndexExpression: one past the last character +} +``` + +After: +```cpp +#include +void f(std::string s) { + s[s.size() - 1] = 1; +} +``` + +## Related checkers + +- [containerOutOfBounds.md](containerOutOfBounds.md) - the more general out-of-bounds container access + check that this is a special case of. diff --git a/man/checkers/copyCtorAndEqOperator.md b/man/checkers/copyCtorAndEqOperator.md new file mode 100644 index 00000000000..75a706156c9 --- /dev/null +++ b/man/checkers/copyCtorAndEqOperator.md @@ -0,0 +1,28 @@ +# copyCtorAndEqOperator + +**Message**: The class 'A' has 'copy constructor' but lack of 'operator='.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ + +## Description + +A class defines a copy constructor without also defining `operator=`, or vice versa - having a working +copy in one direction and the compiler-generated (possibly wrong) one in the other is an easy way to end +up with a broken half-copy. + +**This check is currently switched off in cppcheck** (its message was found to need clarification), so +it will not appear in current output no matter what code you give it. It's documented here only because +it remains a real, registered ID. + +## Motivation + +If a class needs custom copying logic for its copy constructor, it almost always needs the same logic +for `operator=` (and vice versa) - the compiler-generated version of whichever one is missing just +copies each member's value, which usually doesn't match the custom behaviour the other one implements. + +## Related checkers + +- [noCopyConstructor.md](noCopyConstructor.md) and [noOperatorEq.md](noOperatorEq.md) - the more + commonly-seen checks for a class that's missing one of these special member functions entirely, + rather than defining one without the other. diff --git a/man/checkers/copyCtorNoAllocation.md b/man/checkers/copyCtorNoAllocation.md new file mode 100644 index 00000000000..5797ec775b0 --- /dev/null +++ b/man/checkers/copyCtorNoAllocation.md @@ -0,0 +1,27 @@ +# copyCtorNoAllocation + +**Message**: Copy constructor does not allocate memory for member 'p' although memory has been allocated in other constructors.
+**Category**: Undefined Behaviour
+**Severity**: Warning
+**Language**: C++ + +## Description + +The intended sibling of [copyCtorPointerCopying.md](copyCtorPointerCopying.md): a copy constructor +copies a pointer without allocating new memory, checked from the allocating constructor's side instead +of the copy constructor's side. + +**This check is dead code in current cppcheck.** Its implementation is commented out in the source +(citing an unresolved report about the message needing more work), so it can never actually fire, no +matter what code you give it. It's documented here only because it remains a real, registered ID. + +## Motivation + +If another constructor allocates memory for a pointer member, a copy constructor that doesn't do the +same for that member is expected to share the same shallow-copy/double-free risk that +[copyCtorPointerCopying.md](copyCtorPointerCopying.md) describes. + +## Related checkers + +- [copyCtorPointerCopying.md](copyCtorPointerCopying.md) - the actively-working check for the same + underlying shallow-copy risk, checked from the copy constructor's side. diff --git a/man/checkers/copyCtorPointerCopying.md b/man/checkers/copyCtorPointerCopying.md new file mode 100644 index 00000000000..a397c548394 --- /dev/null +++ b/man/checkers/copyCtorPointerCopying.md @@ -0,0 +1,66 @@ +# copyCtorPointerCopying + +**Message**: Value of pointer 'p', which points to allocated memory, is copied in copy constructor instead of allocating new memory.
+**Category**: Undefined Behaviour
+**Severity**: Warning
+**Language**: C++ + +## Description + +A copy constructor copies a pointer member's *value* (so both objects now point at the same allocated +memory) instead of allocating a fresh block and copying the pointed-to data - a classic shallow-copy +bug, usually followed by a double-free once both objects are destroyed. + +## Motivation + +Once two objects share the same pointer value for a member each believes it owns, whichever is +destroyed first frees the memory out from under the other - leaving the survivor with a dangling +pointer, and destroying both eventually frees the same block twice. + +## How to fix + +Allocate a fresh block in the copy constructor and copy the pointed-to data into it, rather than copying +the pointer itself. + +Before: +```cpp +#include +#include +class F { + char *p; + F(const F &f) { + p = f.p; // <- both objects now share the same allocated block + } +public: + F(char *str) { + p = malloc(strlen(str)+1); + } + ~F(); + F& operator=(const F&f); +}; +``` + +After: +```cpp +#include +#include +class F { + char *p; +public: + F(const F &f) { + p = (char*)malloc(strlen(f.p)+1); + strcpy(p, f.p); + } + F(char *str) { + p = (char*)malloc(strlen(str)+1); + strcpy(p, str); + } + ~F(); + F& operator=(const F&f); +}; +``` + +## Related checkers + +- [noCopyConstructor.md](noCopyConstructor.md) - the related check for when a class allocates a + resource but doesn't define a copy constructor at all. diff --git a/man/checkers/coutCerrMisusage.md b/man/checkers/coutCerrMisusage.md new file mode 100644 index 00000000000..12da5db7dab --- /dev/null +++ b/man/checkers/coutCerrMisusage.md @@ -0,0 +1,34 @@ +# coutCerrMisusage + +**Message**: Invalid usage of output stream: '<< std::cout'.
+**Category**: Correctness
+**Severity**: Error
+**Language**: C++ + +## Description + +`std::cout`/`std::cerr` is streamed into itself (`std::cout << std::cout;`) - almost always a typo for +a variable that was meant to be printed. + +## Motivation + +Streaming `std::cout` into itself doesn't print anything meaningful and is essentially always a typo - +usually a variable name was meant to be there instead of the stream's own name. + +## How to fix + +Before: +```cpp +#include +void f() { + std::cout << std::cout; // <- likely a typo'd variable name +} +``` + +After: +```cpp +#include +void f() { + std::cout << "hello"; +} +``` diff --git a/man/checkers/cstyleCast.md b/man/checkers/cstyleCast.md index f05a192ced8..58c33f06985 100644 --- a/man/checkers/cstyleCast.md +++ b/man/checkers/cstyleCast.md @@ -1,4 +1,3 @@ - # cstyleCast **Message**: C-style pointer casting
@@ -23,7 +22,7 @@ This checker is about C casts that converts to/from a pointer or reference. Dangerous conversions are covered by other warnings so this ID `cstyleCast` is primarily about writing warnings for casts that are currently safe. -# Motivation +## Motivation The motivation of this checker is to modernize c++ code. @@ -38,8 +37,8 @@ Before: ```cpp struct Base{}; struct Derived: public Base {}; -void foo(Base* base) { - Base *p = (Base*)derived; // <- cstyleCast, cast from derived object to base object is safe now +void foo(Derived* derived) { + Base *p = (Base*)derived; // <- cstyleCast: casting up to a base class is always safe } ``` @@ -47,8 +46,9 @@ After: ```cpp struct Base{}; struct Derived: public Base {}; -void foo(Base* base) { - Derived *p = static_cast(base); +void foo(Derived* derived) { + Base *p = static_cast(derived); } ``` -The `static_cast` ensures that there will not be loss of constness in the future. +The `static_cast` documents the intended direction of the cast and will fail to compile if the +class hierarchy ever changes in a way that makes it invalid. diff --git a/man/checkers/ctuArrayIndex.md b/man/checkers/ctuArrayIndex.md new file mode 100644 index 00000000000..1dcde49e62d --- /dev/null +++ b/man/checkers/ctuArrayIndex.md @@ -0,0 +1,49 @@ +# ctuArrayIndex + +**Message**: Array index out of bounds; 'p' buffer size is 4 and it is accessed at offset 40.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +The same idea as [arrayIndexOutOfBounds.md](arrayIndexOutOfBounds.md), but found by cppcheck's +whole-program ("cross translation unit") analysis, which follows a buffer across function calls that +the normal, per-function analysis doesn't always chain together. Because of this, the same bug can +sometimes be reported twice - once under the plain ID and once under this `ctu`-prefixed one - for the +same line. + +## Motivation + +An out-of-bounds array access is undefined behaviour whether it's visible directly inside one function +or only becomes apparent by following a buffer through a call into another function - the whole-program +analysis exists to catch the cases a single function's view can't. + +## How to fix + +Before: +```cpp +void f(char *p) { + p[9] = 0; +} +void g() { + char buf[5]; + f(buf); // <- 'buf' is too small for what f() does with it +} +``` + +After: +```cpp +void f(char *p) { + p[9] = 0; +} +void g() { + char buf[10]; + f(buf); +} +``` + +## Related checkers + +- [arrayIndexOutOfBounds.md](arrayIndexOutOfBounds.md) - the per-function version of this check. +- [ctuPointerArith.md](ctuPointerArith.md) - the whole-program pointer-arithmetic equivalent. diff --git a/man/checkers/ctuPointerArith.md b/man/checkers/ctuPointerArith.md new file mode 100644 index 00000000000..948d5810e7b --- /dev/null +++ b/man/checkers/ctuPointerArith.md @@ -0,0 +1,44 @@ +# ctuPointerArith + +**Message**: Pointer arithmetic overflow; 'p' buffer size is 12
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +The same idea as [pointerOutOfBounds.md](pointerOutOfBounds.md), but found by cppcheck's whole-program +("cross translation unit") analysis, which follows a buffer across function calls that the normal, +per-function analysis doesn't always chain together. Because of this, the same bug can sometimes be +reported twice - once under the plain ID and once under this `ctu`-prefixed one - for the same line. + +## Motivation + +Pointer arithmetic that goes out of bounds is undefined behaviour whether it's visible directly inside +one function or only becomes apparent by following a buffer through a call into another function - the +whole-program analysis exists to catch the cases a single function's view can't. + +## How to fix + +Before: +```cpp +void dostuff(int *p) { int x = *(p + 10); } +int main() { + int arr[3]; + dostuff(arr); // <- 'arr' is too small for what dostuff() does with it +} +``` + +After: +```cpp +void dostuff(int *p) { int x = *(p + 10); } +int main() { + int arr[11]; + dostuff(arr); +} +``` + +## Related checkers + +- [pointerOutOfBounds.md](pointerOutOfBounds.md) - the per-function version of this check. +- [ctuArrayIndex.md](ctuArrayIndex.md) - the whole-program array-indexing equivalent. diff --git a/man/checkers/ctunullpointer.md b/man/checkers/ctunullpointer.md new file mode 100644 index 00000000000..c52228cb6bc --- /dev/null +++ b/man/checkers/ctunullpointer.md @@ -0,0 +1,25 @@ +# ctunullpointer + +**Message**: Null pointer dereference: p
+**Category**: Undefined Behaviour
+**Severity**: Error/Warning
+**Language**: C/C++ + +## Description + +The same idea as [nullPointer](nullPointer.md), but found by cppcheck's whole-program ("cross +translation unit") analysis, which follows a null value across several function calls that the normal, +per-function analysis doesn't always chain together on its own. + +## Motivation + +Some null-pointer bugs only become visible when tracing a value across function boundaries - for +example, a function that's given a null pointer by one of its callers, several call levels away. This +whole-program analysis catches those cases at the cost of needing every relevant source file analyzed +together. Because of this, the same bug can sometimes be reported twice - once as a plain `nullPointer` +and once as `ctunullpointer` - for the same line. + +## Related checkers + +- [nullPointer.md](nullPointer.md) - the per-function analysis this whole-program analysis complements. +- [ctunullpointerOutOfMemory.md](ctunullpointerOutOfMemory.md) - the same idea for a pointer from a failable allocation function. diff --git a/man/checkers/ctunullpointerOutOfMemory.md b/man/checkers/ctunullpointerOutOfMemory.md new file mode 100644 index 00000000000..be69e2ddb2c --- /dev/null +++ b/man/checkers/ctunullpointerOutOfMemory.md @@ -0,0 +1,28 @@ +# ctunullpointerOutOfMemory and ctunullpointerOutOfResources + +**Message**: If memory allocation fails, then there is a possible null pointer dereference: p
+**Category**: Undefined Behaviour
+**Severity**: Error/Warning
+**Language**: C/C++ + +## Description + +- `ctunullpointerOutOfMemory`: the same idea as [nullPointerOutOfMemory](nullPointerOutOfMemory.md), + but found by cppcheck's whole-program ("cross translation unit") analysis, which follows the result + of a failable memory allocation across function calls that the normal, per-function analysis doesn't + always chain together on its own. +- `ctunullpointerOutOfResources`: the same idea for a failable resource-allocating function (`fopen`, + ...) rather than a memory allocator. + +## Motivation + +Some "forgot to check the allocation" bugs only become visible when tracing the allocated pointer/handle +across function boundaries. This whole-program analysis catches those cases at the cost of needing every +relevant source file analyzed together. Because of this, the same bug can sometimes be reported twice - +once as a plain `nullPointerOutOfMemory`/`nullPointerOutOfResources` and once with the `ctu` prefix - for +the same line. + +## Related checkers + +- [nullPointerOutOfMemory.md](nullPointerOutOfMemory.md) - the per-function analysis this whole-program analysis complements. +- [ctunullpointer.md](ctunullpointer.md) - the same whole-program analysis without a specific failable allocation involved. diff --git a/man/checkers/ctuuninitvar.md b/man/checkers/ctuuninitvar.md new file mode 100644 index 00000000000..865ed3e80c4 --- /dev/null +++ b/man/checkers/ctuuninitvar.md @@ -0,0 +1,47 @@ +# ctuuninitvar + +**Message**: Using argument p that points at uninitialized variable x
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +The same idea as [uninitvar.md](uninitvar.md), but found by cppcheck's whole-program ("cross +translation unit") analysis, which follows an uninitialized value across function calls that the +normal, per-function analysis doesn't always chain together. Because of this, the same bug can +sometimes be reported twice - once as `uninitvar` and once as `ctuuninitvar` - for the same line. + +## Motivation + +Reading an uninitialized variable is undefined behaviour whether it's visible directly inside one +function or only becomes apparent by following a pointer through a call into another function - the +whole-program analysis exists to catch the cases a single function's view can't. + +## How to fix + +Before: +```cpp +void f(int *p) { + a = *p; // <- if f() is ever called with an uninitialized argument +} +int main() { + int x; + f(&x); +} +``` + +After: +```cpp +void f(int *p) { + a = *p; +} +int main() { + int x = 0; + f(&x); +} +``` + +## Related checkers + +- [uninitvar.md](uninitvar.md) - the per-function version of this check. diff --git a/man/checkers/dangerousTypeCast.md b/man/checkers/dangerousTypeCast.md index 79538103db5..f4015cd52e5 100644 --- a/man/checkers/dangerousTypeCast.md +++ b/man/checkers/dangerousTypeCast.md @@ -1,4 +1,3 @@ - # dangerousTypeCast **Message**: Potentially invalid type conversion in old-style C cast, clarify/fix with C++ cast
@@ -6,6 +5,14 @@ **Severity**: Warning
**Language**: C++, not applicable for C code +## Description + +An old-style C cast (`(Type)expr`) is used to convert between two pointer or reference types in a way +that could be an invalid conversion - for example casting between two unrelated pointer types, or down +a class hierarchy without any guarantee the object is really of the target type. A C-style cast will +silently do whatever C++ cast is needed to make the code compile (including a `reinterpret_cast`), so it +gives no indication of, or protection against, this. + ## Motivation C style casts can be dangerous in many ways: diff --git a/man/checkers/danglingLifetime.md b/man/checkers/danglingLifetime.md new file mode 100644 index 00000000000..037806b0415 --- /dev/null +++ b/man/checkers/danglingLifetime.md @@ -0,0 +1,55 @@ +# danglingLifetime + +**Message**: Non-local variable 'p' will use pointer to local variable 'x'.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +The address of a local variable/array is assigned to a *global, static, or member* pointer, so it +outlives the local variable it points to. + +## Motivation + +Using a pointer after the local variable it points to has gone out of scope is undefined behaviour. +Because the memory involved is usually still intact for a little while afterwards, this kind of bug +often "works" in testing and then fails unpredictably once something else reuses that memory - which +makes it worth catching at analysis time instead of at runtime. + +## How to fix + +Before: +```cpp +int *p; +void f() { + int x; + p = &x; // <- danglingLifetime: 'p' outlives 'x' +} +``` + +After: +```cpp +int *p; +void f() { + static int x; + p = &x; +} +``` + +Alternatively, if `p` is only meant to point at `x` temporarily and is set back to something else before +`x` disappears, cppcheck already recognizes that as safe and won't warn: +```cpp +int *p; +void f() { + int x; + p = &x; + p = nullptr; // 'p' no longer points to 'x' by the time 'x' is destroyed - not flagged +} +``` + +## Related checkers + +- [autoVariables.md](autoVariables.md) - the same idea, but for a local variable's address escaping + through a function parameter instead of a global/static/member pointer. +- [danglingReference.md](danglingReference.md) - the same idea, for a reference instead of a pointer. diff --git a/man/checkers/danglingReference.md b/man/checkers/danglingReference.md new file mode 100644 index 00000000000..c5f82d7ec7e --- /dev/null +++ b/man/checkers/danglingReference.md @@ -0,0 +1,28 @@ +# danglingReference + +**Message**: Using reference to dangling temporary.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +A reference variable that itself outlives the function (`static`, or a non-local reference parameter) +is bound to a local variable. + +## Motivation + +A reference is just another name for the object it's bound to - it doesn't keep that object alive. If +the reference itself outlives the local variable it was bound to, using it afterwards is undefined +behaviour, in the same way a dangling pointer would be. + +## How to fix + +Bind the long-lived reference to something that actually outlives the function (a `static` variable, a +global, or something the caller owns), not a plain local variable. + +## Related checkers + +- [danglingLifetime.md](danglingLifetime.md) - the same idea, for a pointer instead of a reference. +- [danglingTempReference.md](danglingTempReference.md) - a related mistake where a reference is bound + to a temporary and used after that temporary has been destroyed, within the same function. diff --git a/man/checkers/danglingTempReference.md b/man/checkers/danglingTempReference.md new file mode 100644 index 00000000000..0b85ee2d818 --- /dev/null +++ b/man/checkers/danglingTempReference.md @@ -0,0 +1,31 @@ +# danglingTempReference + +**Message**: Using reference to dangling temporary.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +A reference bound to a temporary is used after that temporary has already been destroyed. + +## Motivation + +A temporary object is normally destroyed at the end of the full expression that created it. A small set +of lifetime-extension rules let a `const&`/`&&` binding stretch that lifetime in some cases, but not all +- using the reference outside the case where extension actually applies is a use of an already-destroyed +object. + +## How to fix + +Only rely on a reference to a temporary within the same full expression that created it, or store the +value itself (by copy or move) instead of a reference to it if it needs to outlive that expression. + +## Related checkers + +- [returnTempReference.md](returnTempReference.md) - the same underlying temporary-lifetime mistake, + specifically when the reference is returned from the function. +- [danglingTemporaryLifetime.md](danglingTemporaryLifetime.md) - the same idea, for a pointer or + iterator into a temporary instead of a reference. +- [danglingReference.md](danglingReference.md) - a related mistake where a long-lived reference is + bound to a local variable (rather than a temporary) that doesn't outlive it. diff --git a/man/checkers/danglingTemporaryLifetime.md b/man/checkers/danglingTemporaryLifetime.md new file mode 100644 index 00000000000..cbc05d7dde3 --- /dev/null +++ b/man/checkers/danglingTemporaryLifetime.md @@ -0,0 +1,28 @@ +# danglingTemporaryLifetime + +**Message**: Using pointer to dangling temporary.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +A pointer or iterator into a temporary object is used after that temporary has already been destroyed. + +## Motivation + +A temporary object is normally destroyed at the end of the full expression that created it. A pointer +or iterator into it doesn't extend its lifetime the way a reference binding sometimes can - so using +one afterwards is a use of an already-destroyed object. + +## How to fix + +Only use a pointer/iterator into a temporary within the same full expression that created it, or store +the value itself (by copy or move) instead if it needs to outlive that expression. + +## Related checkers + +- [danglingTempReference.md](danglingTempReference.md) - the same idea, for a reference instead of a + pointer/iterator. +- [invalidLifetime.md](invalidLifetime.md) - the same underlying "used after its referent's scope + ended" mistake, for a pointer into a named local variable rather than a temporary. diff --git a/man/checkers/deallocret.md b/man/checkers/deallocret.md new file mode 100644 index 00000000000..eab1c68e7ce --- /dev/null +++ b/man/checkers/deallocret.md @@ -0,0 +1,40 @@ +# deallocret + +**Message**: Returning/dereferencing 'p' after it is deallocated / released
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A locally-allocated variable is returned, or dereferenced as part of a `return`, after it has already +been freed. + +## Motivation + +Returning a pointer after it's been freed hands the caller a pointer that's already invalid - using a +pointer after it's been freed is undefined behaviour regardless of which statement does it. + +## How to fix + +Before: +```cpp +int* f() { + int *p = malloc(10); + free(p); + return p; // <- deallocret +} +``` + +After: +```cpp +int* f() { + int *p = malloc(10); + return p; +} +``` + +## Related checkers + +- [deallocuse.md](deallocuse.md) - the same idea, but the freed variable is dereferenced by an ordinary + statement rather than a `return`. diff --git a/man/checkers/deallocuse.md b/man/checkers/deallocuse.md new file mode 100644 index 00000000000..dd895c37294 --- /dev/null +++ b/man/checkers/deallocuse.md @@ -0,0 +1,43 @@ +# deallocuse + +**Message**: Dereferencing 'p' after it is deallocated / released
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A locally-allocated variable is dereferenced after it has already been freed. + +## Motivation + +Using a pointer after it's been freed is undefined behaviour, and one of the more common causes of +memory corruption - the memory often still looks intact for a while afterwards, which is exactly what +makes this bug easy to miss in testing. + +## How to fix + +Before: +```cpp +void f() { + int *ptr = new int; + delete(ptr); + *ptr = 0; // <- deallocuse +} +``` + +After: +```cpp +void f() { + int *ptr = new int; + *ptr = 0; + delete(ptr); +} +``` + +## Related checkers + +- [deallocret.md](deallocret.md) - the same idea, but the freed variable is dereferenced as part of a + `return` statement. +- [doubleFree.md](doubleFree.md) - the related mistake of freeing the same variable again, rather than + dereferencing it. diff --git a/man/checkers/derefInvalidIterator.md b/man/checkers/derefInvalidIterator.md new file mode 100644 index 00000000000..a64ecd182ba --- /dev/null +++ b/man/checkers/derefInvalidIterator.md @@ -0,0 +1,70 @@ +# derefInvalidIterator and derefInvalidIteratorRedundantCheck + +**Message**: Dereference of an invalid iterator: v.begin()-1
+**Category**: Undefined Behaviour
+**Severity**: Error/Warning
+**Language**: C++ + +## Description + +An iterator is dereferenced while it could be `end()`, before `begin()`, or otherwise invalid. + +- `derefInvalidIterator`: cppcheck can tell directly that the iterator may be invalid at the point it's + dereferenced. +- `derefInvalidIteratorRedundantCheck`: there's a nearby validity check on the same iterator, but the + dereference happens where that check doesn't actually apply (for example, after the `if` that checked + it, rather than inside it) - either the check is redundant, or the dereference is a bug. + +## Motivation + +Dereferencing an iterator that doesn't currently point at a real element is undefined behaviour, and is +easy to get subtly wrong when a validity check exists in the code but is written on the wrong side of +the dereference. + +## How to fix + +Before: +```cpp +#include +void f() { + std::vector v{ 1, 2, 3 }; + v.erase(v.begin() - 1); // <- derefInvalidIterator: 'v.begin()-1' is out of bounds +} +``` + +After: +```cpp +#include +void f() { + std::vector v{ 1, 2, 3 }; + v.erase(v.begin()); +} +``` + +Before: +```cpp +#include +#include +int f(std::vector v, int i) { + auto it = std::find(v.begin(), v.end(), i); + if (it != v.end()) {} + return *it; // <- derefInvalidIteratorRedundantCheck: dereferenced outside the 'if' that checked it +} +``` + +After: +```cpp +#include +#include +int f(std::vector v, int i) { + auto it = std::find(v.begin(), v.end(), i); + if (it != v.end()) + return *it; + return -1; +} +``` + +## Related checkers + +- [eraseIteratorOutOfBounds.md](eraseIteratorOutOfBounds.md) - the equivalent check for calling + `erase()` with (rather than dereferencing) an iterator that could be out of bounds. diff --git a/man/checkers/doubleFree.md b/man/checkers/doubleFree.md new file mode 100644 index 00000000000..c732469b99f --- /dev/null +++ b/man/checkers/doubleFree.md @@ -0,0 +1,43 @@ +# doubleFree + +**Message**: Memory pointed to by 'p' is freed twice.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +The same locally-allocated variable is freed/released twice. + +## Motivation + +Freeing the same memory twice is undefined behaviour, and a common way memory-allocator corruption bugs +are introduced - it's easy to miss when the two frees are far apart, or on different branches that both +happen to execute. cppcheck only tracks an allocation through straight-line code: as soon as a loop or +`goto` appears anywhere in the function, it stops checking that function rather than risk a wrong guess, +so silence on a function with a loop in it isn't proof the code is free of double frees. + +## How to fix + +Before: +```cpp +void f() { + char *p = malloc(10); + free(p); + free(p); // <- doubleFree +} +``` + +After: +```cpp +void f() { + char *p = malloc(10); + free(p); +} +``` + +## Related checkers + +- [deallocuse.md](deallocuse.md) - the related mistake of dereferencing (rather than freeing again) + something that's already been freed. +- [memleak.md](memleak.md) - the opposite mistake: an allocation that's never freed at all. diff --git a/man/checkers/duplInheritedMember.md b/man/checkers/duplInheritedMember.md new file mode 100644 index 00000000000..e2b6f4b7d2a --- /dev/null +++ b/man/checkers/duplInheritedMember.md @@ -0,0 +1,46 @@ +# duplInheritedMember + +**Message**: The class 'Derived' defines member variable with name 'x' also defined in its parent class 'Base'.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ + +## Description + +A derived class declares a data member or (non-virtual, non-constructor/destructor) member function +with the same name as one a base class already has - the derived one hides the base one, which is +confusing and easy to do by accident in a large class hierarchy. + +## Motivation + +When a derived class redeclares a name its base class already uses, the derived member hides the base +one for code that operates on the derived type - but code that only sees the base class (through a base +pointer/reference, or inside a base-class member function) still reaches the *base* member. The two +names look identical at each call site, so which one is actually being used depends entirely on the +static type in scope at that point, which is easy to get wrong. + +## How to fix + +Rename one of the two members so there's no ambiguity about which one a given piece of code refers to. + +Before: +```cpp +class Base { + protected: + int x; +}; +struct Derived : Base { + int x; // <- hides 'Base::x' +}; +``` + +After: +```cpp +class Base { + protected: + int x; +}; +struct Derived : Base { + int y; +}; +``` diff --git a/man/checkers/duplicateAssignExpression.md b/man/checkers/duplicateAssignExpression.md new file mode 100644 index 00000000000..07318644398 --- /dev/null +++ b/man/checkers/duplicateAssignExpression.md @@ -0,0 +1,45 @@ +# duplicateAssignExpression + +**Message**: Same expression used in consecutive assignments of 'i' and 'j'.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +Two variables, declared one after another, are assigned the exact same expression - often a copy-paste +mistake where the second line should refer to something else. + +## Motivation + +Two variables initialized to the same expression right next to each other is a common outcome of +copy-pasting one line and forgetting to change one of the operands - if it's not a mistake, computing +the same value twice (rather than reusing it) is also wasteful. + +## How to fix + +Before: +```cpp +int f() __attribute__((pure)); +int g() __attribute__((pure)); +void test() { + int i = f(); + int j = f(); // <- same expression as 'i', is this a copy-paste mistake? +} +``` + +After: +```cpp +int f() __attribute__((pure)); +int g() __attribute__((pure)); +void h(int, int); +void test() { + int i = f(); + int j = g(); + h(i, j); +} +``` + +## Related checkers + +- [duplicateExpression.md](duplicateExpression.md) - the same idea for one expression compared against itself, rather than two separate assignments. diff --git a/man/checkers/duplicateBranch.md b/man/checkers/duplicateBranch.md new file mode 100644 index 00000000000..51aca89f447 --- /dev/null +++ b/man/checkers/duplicateBranch.md @@ -0,0 +1,42 @@ +# duplicateBranch + +**Message**: Found duplicate branches for 'if' and 'else'.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +The `if` and `else` branches of a statement contain identical code, so the condition makes no +difference to what runs. + +## Motivation + +If both branches do the same thing, the condition is pointless - either the branches were meant to +differ and one was copy-pasted by mistake, or the `if`/`else` itself can be removed entirely. + +## How to fix + +Before: +```cpp +void f(int a, int &b) { + if (a) + b = 1; + else + b = 1; // <- identical to the 'if' branch +} +``` + +After: +```cpp +void f(int a, int &b) { + if (a) + b = 1; + else + b = 2; +} +``` + +## Related checkers + +- [duplicateExpression.md](duplicateExpression.md) - the same "is this a copy-paste mistake?" idea, for an expression instead of a whole branch. diff --git a/man/checkers/duplicateBreak.md b/man/checkers/duplicateBreak.md new file mode 100644 index 00000000000..aa1feef6e8e --- /dev/null +++ b/man/checkers/duplicateBreak.md @@ -0,0 +1,45 @@ +# duplicateBreak + +**Message**: Consecutive return, break, continue, goto or throw statements are unnecessary.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +Two `return`/`break`/`continue`/`goto`/`throw` statements appear back to back - the second one can +never be reached. + +## Motivation + +Once the first of these statements runs, control leaves the current block immediately, so nothing +written directly after it (in the same block) can ever execute. It's dead code that's safe to delete. + +## How to fix + +Before: +```cpp +void foo(int a) { + while (1) { + if (a++ >= 100) { + break; + continue; // <- can never be reached + } + } +} +``` + +After: +```cpp +void foo(int a) { + while (1) { + if (a++ >= 100) { + break; + } + } +} +``` + +## Related checkers + +- [unreachableCode.md](unreachableCode.md) - the more general case of any code (not just another jump statement) placed after one of these statements. diff --git a/man/checkers/duplicateCondition.md b/man/checkers/duplicateCondition.md new file mode 100644 index 00000000000..caea8610c9d --- /dev/null +++ b/man/checkers/duplicateCondition.md @@ -0,0 +1,39 @@ +# duplicateCondition + +**Message**: The if condition is the same as the previous if condition
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +Two consecutive `if` statements test the exact same condition - the second one is dead code. + +## Motivation + +If the first `if` didn't change anything the condition depends on, the second, identical `if` can never +newly become true or false compared to the first - it's either always dead code, or a sign that an +assignment that was supposed to happen in between is missing. + +## How to fix + +Before: +```cpp +void f(int x) { + if (x == 1) {} + if (x == 1) {} // <- dead code +} +``` + +After: +```cpp +void f(int x) { + if (x == 1) {} +} +``` + +## Related checkers + +- [multiCondition.md](multiCondition.md) - the same idea, but for `if`/`else if` chains. +- [identicalConditionAfterEarlyExit.md](identicalConditionAfterEarlyExit.md) - the same idea, but the + first check is an early `return`/`throw`/`break`/`continue` instead of a plain `if`. diff --git a/man/checkers/duplicateConditionalAssign.md b/man/checkers/duplicateConditionalAssign.md new file mode 100644 index 00000000000..007edf870eb --- /dev/null +++ b/man/checkers/duplicateConditionalAssign.md @@ -0,0 +1,42 @@ +# duplicateConditionalAssign + +**Message**: Assignment 'x=5' is redundant with condition 'x==5'.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +An `if` assigns a value to a variable that the condition already guarantees it has (`if (x == 5) x = 5;`) - +the assignment has no effect. + +## Motivation + +If the condition already established what the variable equals, assigning that same value again inside +the `if` changes nothing - it's leftover code from an edit, or a sign the assignment was meant to use a +different value or variable. + +This check may need `--check-level=exhaustive` to see every case. + +## How to fix + +Before: +```cpp +void f(int x) { + if (x == 5) + x = 5; // <- 'x' is already 5 here +} +``` + +After: +```cpp +void f(int x) { + if (x == 5) { + } +} +``` + +## Related checkers + +- [knownConditionTrueFalse.md](knownConditionTrueFalse.md) - the more general check for a condition + whose truth value cppcheck already knows in advance. diff --git a/man/checkers/duplicateExpression.md b/man/checkers/duplicateExpression.md new file mode 100644 index 00000000000..4fba20bdbc3 --- /dev/null +++ b/man/checkers/duplicateExpression.md @@ -0,0 +1,38 @@ +# duplicateExpression + +**Message**: Same expression on both sides of '=='.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +The exact same expression appears on both sides of a comparison or other binary operator (`a == a`, +`a - a`, ...), or more than once in a chain of the same operator. + +## Motivation + +Comparing or combining an expression with an identical copy of itself always produces the same, +predetermined result (`true`, `false`, or zero) regardless of the expression's actual value - this is +almost always a typo for what should have been two different variables. + +## How to fix + +Before: +```cpp +void foo(int a) { + if (a == a) { } // <- always true +} +``` + +After: +```cpp +void foo(int a, int b) { + if (a == b) { } +} +``` + +## Related checkers + +- [oppositeExpression.md](oppositeExpression.md) - the mirror-image mistake, comparing an expression against its logical/arithmetic opposite. +- [duplicateAssignExpression.md](duplicateAssignExpression.md) - the same idea across two separate variable assignments. diff --git a/man/checkers/duplicateExpressionTernary.md b/man/checkers/duplicateExpressionTernary.md index 706ad85e2ac..85a4bfeb967 100644 --- a/man/checkers/duplicateExpressionTernary.md +++ b/man/checkers/duplicateExpressionTernary.md @@ -17,26 +17,6 @@ The warning is triggered when: The same expression indicates that there might be some logic error or copy-paste mistake. -## Examples - -### Problematic code - -```cpp -// Same expression in both branches -int result = condition ? x : x; // Warning: duplicateExpressionTernary - -// Same variable referenced through alias -const int c = a; -int result = condition ? a : c; // Warning: duplicateExpressionTernary -``` - -### Fixed code - -```cpp -// Different expressions in branches -int result = condition ? x : y; // OK -``` - ## How to fix 1. **Check for copy-paste errors**: Verify that both branches are supposed to have the same expression diff --git a/man/checkers/duplicateValueTernary.md b/man/checkers/duplicateValueTernary.md index 06e9f8e9be7..a0ae2550293 100644 --- a/man/checkers/duplicateValueTernary.md +++ b/man/checkers/duplicateValueTernary.md @@ -23,30 +23,9 @@ However, no warning is generated when: The same value indicates that there might be some logic error or copy-paste mistake. -## Examples - -### Problematic code - -```cpp -// Different expressions, same value -int result = condition ? (int)1 : 1; // Warning: duplicateValueTernary - -// Different cast syntax, same value -int result = condition ? 1 : (int)1; // Warning: duplicateValueTernary -``` - -### Fixed code - -```cpp -// Different values in branches -int result = condition ? 1 : 2; // OK - -// Simplified - condition doesn't matter -int result = 1; // OK - removed unnecessary ternary - -// Platform-dependent values are allowed -int size = is_64bit ? sizeof(long) : sizeof(int); // OK - may differ on platforms -``` +Note that a ternary whose branches merely *could* differ depending on the platform, such as +`is_64bit ? sizeof(long) : sizeof(int)`, is not flagged: cppcheck only warns when it can tell both +branches are the same value on the platform being analyzed. ## How to fix diff --git a/man/checkers/eraseDereference.md b/man/checkers/eraseDereference.md new file mode 100644 index 00000000000..35312320b74 --- /dev/null +++ b/man/checkers/eraseDereference.md @@ -0,0 +1,51 @@ +# eraseDereference + +**Message**: Iterator 'iter' used after element has been erased.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +An iterator is dereferenced (or compared) after the element it pointed to has already been erased. + +## Motivation + +Once the element an iterator points to has been erased, the iterator itself is invalid - dereferencing +or comparing it afterwards is undefined behaviour, even though it often still appears to "work" because +the underlying memory hasn't been reused yet. + +## How to fix + +Before: +```cpp +#include +#include +void f() { + std::map ints; + std::map::iterator iter; + iter = ints.begin(); + ints.erase(iter); + std::cout << iter->first << std::endl; // <- eraseDereference: 'iter' was just erased +} +``` + +After: +```cpp +#include +#include +void f() { + std::map ints = {{1, 2}}; + std::map::iterator iter; + iter = ints.begin(); + std::cout << iter->first << std::endl; + ints.erase(iter); +} +``` + +## Related checkers + +- [invalidIterator1.md](invalidIterator1.md) - a related mistake where an already-erased iterator is + passed to `erase()`/`insert()` again, rather than dereferenced. +- [derefInvalidIterator.md](derefInvalidIterator.md) - the more general check for dereferencing an + iterator that could be invalid for any reason, not specifically because it was just erased. diff --git a/man/checkers/eraseIteratorOutOfBounds.md b/man/checkers/eraseIteratorOutOfBounds.md new file mode 100644 index 00000000000..ac24423fd7f --- /dev/null +++ b/man/checkers/eraseIteratorOutOfBounds.md @@ -0,0 +1,64 @@ +# eraseIteratorOutOfBounds and eraseIteratorOutOfBoundsCond + +**Message**: Calling function 'erase()' on the iterator 'v.begin()' which is out of bounds.
+**Category**: Undefined Behaviour
+**Severity**: Error/Warning
+**Language**: C++ + +## Description + +`erase()` is called with an iterator that is known to be `end()`, before `begin()`, or otherwise out of +the container's range. + +- `eraseIteratorOutOfBounds`: the iterator is unconditionally known to be out of bounds. +- `eraseIteratorOutOfBoundsCond`: the iterator is only out of bounds on one branch of a condition + tested nearby - either that condition is redundant, or this `erase()` call is a bug. + +## Motivation + +Calling `erase()` with an iterator that doesn't point at an actual element in the container (`end()`, +or the result of moving an iterator before `begin()` or past `end()`) is undefined behaviour. + +## How to fix + +Before: +```cpp +#include +void f() { + std::vector v; + v.erase(v.begin()); // <- eraseIteratorOutOfBounds: 'v' is empty, begin() == end() +} +``` + +After: +```cpp +#include +void f() { + std::vector v = {1, 2, 3}; + v.erase(v.begin()); +} +``` + +Before: +```cpp +#include +void f(std::vector& v, std::vector::iterator it) { + if (it == v.end()) {} + v.erase(it); // <- eraseIteratorOutOfBoundsCond: 'it' can be end() here +} +``` + +After: +```cpp +#include +void f(std::vector& v, std::vector::iterator it) { + if (it == v.end()) + return; + v.erase(it); +} +``` + +## Related checkers + +- [derefInvalidIterator.md](derefInvalidIterator.md) - the equivalent check for dereferencing (rather + than erasing through) an iterator that could be out of bounds. diff --git a/man/checkers/exceptDeallocThrow.md b/man/checkers/exceptDeallocThrow.md new file mode 100644 index 00000000000..53571ffc8f1 --- /dev/null +++ b/man/checkers/exceptDeallocThrow.md @@ -0,0 +1,42 @@ +# exceptDeallocThrow + +**Message**: Exception thrown in invalid state, 'p' points at deallocated memory.
+**Category**: Undefined Behaviour
+**Severity**: Warning
+**Language**: C++ only + +## Description + +A global/static pointer is `delete`d and an exception can then be thrown before the pointer is given a +new value, leaving a dangling pointer for any surviving code to use. + +## Motivation + +If an exception is thrown between freeing a pointer and resetting it, any exception handler (or later +code, if the exception is caught and execution continues) that touches the same global/static pointer +sees a dangling value - a use-after-free that's easy to miss because the code "looks" like it cleans up +properly, just not in a safe order. + +## How to fix + +Before: +```cpp +static int* p = nullptr; +void f(bool someCondition) { + delete p; + if (someCondition) + throw 1; // <- 'p' is left dangling if this throws + p = nullptr; +} +``` + +After: +```cpp +static int* p = nullptr; +void f(bool someCondition) { + delete p; + p = nullptr; + if (someCondition) + throw 1; +} +``` diff --git a/man/checkers/exceptRethrowCopy.md b/man/checkers/exceptRethrowCopy.md new file mode 100644 index 00000000000..99ca02987fe --- /dev/null +++ b/man/checkers/exceptRethrowCopy.md @@ -0,0 +1,50 @@ +# exceptRethrowCopy + +**Message**: Throwing a copy of the caught exception instead of rethrowing the original exception.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ only + +## Description + +A `catch` block rethrows the caught exception with `throw x;` instead of a bare `throw;`, which makes +an unnecessary copy (and can slice a derived exception down to its base type). + +## Motivation + +`throw x;` constructs a new exception object from `x`, sliced down to whatever type `x` was declared +as - if the actual thrown object was a more-derived type, that extra information is lost. A bare +`throw;` rethrows the original exception object exactly as it was, with no copy and no slicing. + +## How to fix + +Before: +```cpp +#include +void doWork(); +void f() { + try { + doWork(); + } catch (const std::exception& err) { + throw err; // <- copies (and can slice) the exception + } +} +``` + +After: +```cpp +#include +void doWork(); +void f() { + try { + doWork(); + } catch (const std::exception& err) { + throw; + } +} +``` + +## Related checkers + +- [catchExceptionByValue.md](catchExceptionByValue.md) - a related `catch`-clause mistake: catching by + value instead of by reference. diff --git a/man/checkers/exceptThrowInDestructor.md b/man/checkers/exceptThrowInDestructor.md new file mode 100644 index 00000000000..a529320267c --- /dev/null +++ b/man/checkers/exceptThrowInDestructor.md @@ -0,0 +1,42 @@ +# exceptThrowInDestructor + +**Message**: Class X is not safe, destructor throws exception
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ only + +## Description + +A destructor throws an exception. + +## Motivation + +Destructors are implicitly `noexcept` since C++11, and even before that, throwing from a destructor +during stack unwinding (while another exception is already propagating) calls `std::terminate()` +immediately, aborting the program without normal cleanup. + +## How to fix + +Before: +```cpp +class Resource { +public: + ~Resource() { + throw 1; // <- destructors are expected not to throw + } +}; +``` + +After: +```cpp +class Resource { +public: + ~Resource() { + } +}; +``` + +## Related checkers + +- [throwInNoexceptFunction.md](throwInNoexceptFunction.md) - the same underlying problem, for any + function explicitly marked `noexcept` rather than specifically a destructor. diff --git a/man/checkers/extraWhitespaceError.md b/man/checkers/extraWhitespaceError.md new file mode 100644 index 00000000000..5f635a0d776 --- /dev/null +++ b/man/checkers/extraWhitespaceError.md @@ -0,0 +1,25 @@ +# extraWhitespaceError + +**Message**: Found extra whitespace in the pattern.
+**Category**: Code Quality
+**Severity**: Warning
+**Language**: C++ (cppcheck's own source code only) + +## Description + +A pattern string has leading/trailing or doubled whitespace, which doesn't change what it matches but +suggests a typo. + +This is not a general-purpose checker for arbitrary C/C++ programs - see +[simplePatternError.md](simplePatternError.md) for the shared background on this internal, +cppcheck-source-only checker. + +## Motivation + +Stray whitespace in a pattern string is harmless to how the pattern matches, but it's a signal that the +string may have been edited carelessly (for example, a token accidentally deleted without removing its +surrounding space) - worth a second look even though it isn't a functional bug on its own. + +## How to fix + +Remove the extra leading, trailing, or doubled whitespace from the pattern string. diff --git a/man/checkers/fcloseInLoopCondition.md b/man/checkers/fcloseInLoopCondition.md new file mode 100644 index 00000000000..db7f0a584c6 --- /dev/null +++ b/man/checkers/fcloseInLoopCondition.md @@ -0,0 +1,40 @@ +# fcloseInLoopCondition + +**Message**: fclose() used as loop condition may skip loop body or double-close file handle.
+**Category**: Resource Management
+**Severity**: Warning
+**Language**: C and C++ + +## Description + +Using `fclose()` as a loop condition leads to two unwanted outcomes: + +- On **success**, the condition is false and the loop body never executes. The intent was likely to process the file inside the loop, but it is already closed. +- On **failure**, the condition is true and the loop body executes but `fclose()` is called again on the already-closed file handle on the next iteration, which is undefined behaviour. + +This pattern is almost always a misunderstanding of what `fclose()` returns, or confusion with a function that reads/processes data incrementally (like `fgets` or `fread`). Unlike those functions, `fclose()` is a one-shot teardown operation and has no meaningful retry or loop-until-done semantic. + +## How to fix + +Call `fclose()` outside the loop condition. If you need to check whether the close succeeded, store the return value and test it separately. + +Before: +```c +FILE *fp = fopen("data.txt", "r"); +while (fclose(fp)) { + /* process file */ +} +``` + +After: +```c +FILE *fp = fopen("data.txt", "r"); +/* process file */ +if (fclose(fp) != 0) { + /* handle close error */ +} +``` + +## Related checkers + +- `useClosedFile` - for using a file handle that has already been closed diff --git a/man/checkers/fflushOnInputStream.md b/man/checkers/fflushOnInputStream.md new file mode 100644 index 00000000000..b09ea8076b8 --- /dev/null +++ b/man/checkers/fflushOnInputStream.md @@ -0,0 +1,34 @@ +# fflushOnInputStream + +**Message**: fflush() called on input stream 'x' may result in undefined behaviour on non-linux systems.
+**Category**: Portability
+**Severity**: Portability
+**Language**: C/C++ + +## Description + +`fflush()` is called on an input stream (`stdin`, or a file opened for reading). + +## Motivation + +The C standard only defines `fflush()`'s effect for output streams; on an input stream it's +implementation-defined - some platforms discard unread input as a convenience, others don't define +anything useful, so relying on it isn't portable. + +## How to fix + +Before: +```cpp +#include +void f() { + fflush(stdin); // <- undefined/implementation-defined on an input stream +} +``` + +After: +```cpp +#include +void f() { + while (getchar() != '\n') {} +} +``` diff --git a/man/checkers/floatConversionOverflow.md b/man/checkers/floatConversionOverflow.md new file mode 100644 index 00000000000..77338965d7b --- /dev/null +++ b/man/checkers/floatConversionOverflow.md @@ -0,0 +1,45 @@ +# floatConversionOverflow + +**Message**: Undefined behaviour: float (1e+100) to integer conversion overflow.
+**Category**: Undefined Behaviour
+**Severity**: Error/Warning
+**Language**: C/C++ + +## Description + +This checker detects conversions from a floating point value to an integer type (via explicit cast, +assignment or `return`) where the floating point value is outside the range that the target integer +type can represent, based on the platform's configured integer widths. + +## Motivation + +Converting a floating point value to an integer type is undefined behaviour when the value does not +fit in the target type (for example, it is too large, too small, NaN, or infinite). Unlike integer +overflow, this cannot simply be assumed to "wrap around"; the actual result is unpredictable. + +## How to fix + +Make sure the floating point value fits in the target integer type before converting, for example by +clamping the value to the valid range, or by using a wider or floating point type instead. + +Note: cppcheck only warns when it can determine the actual floating point value (or a bound on it). +An unconstrained `double` parameter with no known or derivable value is not checked; the example +below uses a value cppcheck can trace so the warning actually fires. + +Before: +```cpp +int32_t foo() { + double d = 1E100; + return (int32_t)d; // <- floatConversionOverflow, 1E100 does not fit in a 32-bit int +} +``` + +After: +```cpp +int32_t foo() { + double d = 1E100; + if (d > (double)INT32_MAX || d < (double)INT32_MIN) + return 0; // handle out-of-range value + return (int32_t)d; +} +``` diff --git a/man/checkers/ftellTextModeFile.md b/man/checkers/ftellTextModeFile.md new file mode 100644 index 00000000000..847e23b6580 --- /dev/null +++ b/man/checkers/ftellTextModeFile.md @@ -0,0 +1,56 @@ +# ftellTextModeFile + +**Message**: ftell() result is unspecified when file is opened in mode "t".
+**Category**: Portability
+**Severity**: Portability
+**Language**: C/C++ + +## Description + +This checker detects the use of ftell() on a file open in text (or translate) mode. The text mode is not consistent + in between Linux and Windows system and may cause ftell() to return the wrong offset inside a text file. + +See section 7.21.9.4p2 of the C11 standard regarding the ftell function states for a text stream: + +- For a text stream, its file position indicator contains unspecified information, usable by the fseek function for returning the file position indicator for the stream to its position at the time of the ftell call; the difference between two such return values is not necessarily a meaningful measure of the number of characters written or read. + +This warning helps improve code quality by: +- Making the intent clear that the use of ftell() in "t" mode may cause portability problem. + +## Motivation + +This checker improves portability accross system. + +## How to fix + +According to C11, the file must be opened in binary mode 'b' to prevent this problem. + +Before: +```cpp + FILE *f = fopen("Example.txt", "rt"); + if (f) + { + fseek(f, 0, SEEK_END); + printf( "File size %d\n", ftell(f)); + fclose(f); + } + +``` + +After: +```cpp + + FILE *f = fopen("Example.txt", "rb"); + if (f) + { + fseek(f, 0, SEEK_END); + printf( "File size %d\n", ftell(f)); + fclose(f); + } + +``` + +## Notes + +See https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/fopen-wfopen?view=msvc-170 + diff --git a/man/checkers/funcArgNamesDifferent.md b/man/checkers/funcArgNamesDifferent.md new file mode 100644 index 00000000000..8e5841a4a38 --- /dev/null +++ b/man/checkers/funcArgNamesDifferent.md @@ -0,0 +1,49 @@ +# funcArgNamesDifferent and funcArgNamesDifferentUnnamed + +**Message**: Function 'f' argument 1 names different: declaration 'a' definition 'b'.
+**Category**: Code Quality
+**Severity**: Style (Inconclusive)
+**Language**: C/C++ + +## Description + +A function is declared in one place (for example a header) and defined in another, and an argument has +a different name between the two (`funcArgNamesDifferent`), or is named in one but not the other +(`funcArgNamesDifferentUnnamed`). + +## Motivation + +Readers of the declaration alone can be misled about what an argument means if the definition uses a +different (or more descriptive) name - the declaration is often the only thing visible from a header, +while the actual logic and its more meaningful names live in the definition. + +## How to fix + +Before: +```cpp +void func2(int a, int b, int c); +void func2(int A, int B, int C) { } // <- names don't match the declaration +``` + +After: +```cpp +void func2(int a, int b, int c); +void func2(int a, int b, int c) { } +``` + +Before: +```cpp +void f(int a); +void f(int) {} // <- the definition drops the declared name +``` + +After: +```cpp +void f(int a); +void f(int a) {} +``` + +## Related checkers + +- [funcArgOrderDifferent.md](funcArgOrderDifferent.md) - the same kind of declaration/definition + mismatch, but for the order of two arguments that keep their names. diff --git a/man/checkers/funcArgOrderDifferent.md b/man/checkers/funcArgOrderDifferent.md new file mode 100644 index 00000000000..0042f6964e4 --- /dev/null +++ b/man/checkers/funcArgOrderDifferent.md @@ -0,0 +1,36 @@ +# funcArgOrderDifferent + +**Message**: Function 'func2' argument order different: declaration 'a, b, c' definition 'c, b, a'
+**Category**: Code Quality
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A function's declaration and its definition give the same set of argument names, but in a different +order - a strong sign that a maintenance edit swapped two parameters in only one of the two places. + +## Motivation + +If the declaration and definition disagree about which parameter is which, calls written against the +declaration pass arguments in the wrong logical order relative to what the definition actually does with +them - a real, easy-to-miss bug, not just a style nit. + +## How to fix + +Before: +```cpp +void func2(int a, int b, int c); +void func2(int c, int b, int a) { } // <- 'a' and 'c' swapped places +``` + +After: +```cpp +void func2(int a, int b, int c); +void func2(int a, int b, int c) { } +``` + +## Related checkers + +- [funcArgNamesDifferent.md](funcArgNamesDifferent.md) - the same kind of declaration/definition + mismatch, but for an argument name changing (or disappearing) rather than two arguments swapping order. diff --git a/man/checkers/globalLockGuard.md b/man/checkers/globalLockGuard.md new file mode 100644 index 00000000000..05b1c4b3a07 --- /dev/null +++ b/man/checkers/globalLockGuard.md @@ -0,0 +1,43 @@ +# globalLockGuard + +**Message**: Lock guard is defined globally. Lock guards are intended to be local. A global lock guard could lead to a deadlock since it won't unlock until the end of the program.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ + +## Description + +A `std::lock_guard` (or similar RAII lock wrapper) is declared `static` or at namespace/global scope - +it's then held for the entire lifetime of the program, since its destructor (which releases the lock) +never runs until the program exits, defeating the purpose of the lock and risking a permanent deadlock. + +## Motivation + +A `lock_guard`'s whole purpose is to release the lock automatically when it goes out of scope. Giving +it static storage duration means it never goes out of scope until the program ends - the lock is taken +once and never released, so any other code that later tries to lock the same mutex blocks forever. + +## How to fix + +Before: +```cpp +#include +void f() { + static std::mutex m; + static std::lock_guard g(m); // <- never unlocked until program exit +} +``` + +After: +```cpp +#include +void f() { + static std::mutex m; + std::lock_guard g(m); +} +``` + +## Related checkers + +- [localMutex.md](localMutex.md) - the opposite mistake: a mutex and its lock declared in the same, + too-narrow scope, so the lock has no effect. diff --git a/man/checkers/identicalConditionAfterEarlyExit.md b/man/checkers/identicalConditionAfterEarlyExit.md new file mode 100644 index 00000000000..72974ef3612 --- /dev/null +++ b/man/checkers/identicalConditionAfterEarlyExit.md @@ -0,0 +1,43 @@ +# identicalConditionAfterEarlyExit + +**Message**: Identical condition 'x==1', second condition is always false
+**Category**: Code Quality
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +After `if (cond) return;` (or `throw`/`continue`/`break`), the same condition is tested again later in +the function - by that point it can only be false. + +## Motivation + +Once execution passes the early exit, the condition that would have triggered it is already known to be +false for the rest of the function - re-testing it is always false, so it's either dead code or a sign +the two conditions were meant to check different things. + +This check may need `--check-level=exhaustive` to see every case. + +## How to fix + +Before: +```cpp +void f(int x) { + if (x == 1) + return; + if (x == 1) {} // <- always false here +} +``` + +After: +```cpp +void f(int x) { + if (x == 1) + return; +} +``` + +## Related checkers + +- [duplicateCondition.md](duplicateCondition.md) - the same idea, but for two consecutive plain `if` + statements with no early exit in between. diff --git a/man/checkers/identicalInnerCondition.md b/man/checkers/identicalInnerCondition.md new file mode 100644 index 00000000000..c2fa1e0d681 --- /dev/null +++ b/man/checkers/identicalInnerCondition.md @@ -0,0 +1,47 @@ +# identicalInnerCondition + +**Message**: Identical inner 'if' condition is always true.
+**Category**: Code Quality
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +An `if` nested directly inside another `if` repeats the exact same condition as the outer one, so the +inner condition is always true. + +## Motivation + +If the outer `if` already tested the condition, testing it again immediately inside is redundant - the +inner `if` can never be false, so it adds nothing but confusion (and cost of a reader wondering whether +something subtle was intended). + +This check may need `--check-level=exhaustive` to see every case. + +## How to fix + +Before: +```cpp +void f(int x) { + if (x == 1) { + if (x == 1) {} // <- always true + } +} +``` + +After: remove the redundant inner check. +```cpp +void f(int x) { + if (x == 1) { + } +} +``` + +## Related checkers + +- [oppositeInnerCondition.md](oppositeInnerCondition.md) - the same idea, but the inner condition + contradicts the outer one instead of repeating it. +- [overlappingInnerCondition.md](overlappingInnerCondition.md) - the same idea, but the inner condition + is merely implied by (not identical to) the outer one. +- [multiCondition.md](multiCondition.md) - the same idea, but for an `if`/`else if` chain instead of + nested `if`s. diff --git a/man/checkers/ignoredReturnValue.md b/man/checkers/ignoredReturnValue.md new file mode 100644 index 00000000000..244888e4625 --- /dev/null +++ b/man/checkers/ignoredReturnValue.md @@ -0,0 +1,39 @@ +# ignoredReturnValue and ignoredReturnErrorCode + +**Message**: Return value of function $symbol() is not used.
+**Category**: Correctness
+**Severity**: Warning/Style
+**Language**: C/C++ + +## Description + +The return value of a function that must be checked is discarded: + +- `ignoredReturnValue`: the function is marked `[[nodiscard]]`, or is known to be pure/const, or + returns newly allocated memory that would otherwise leak. +- `ignoredReturnErrorCode`: the function's return value follows a "returns an error code" convention + (per its library configuration), so discarding it means a failure could go unnoticed. + +## Motivation + +If a function's whole purpose is its return value (a pure calculation), or its return value is the only +way to learn it failed, throwing that value away either wastes the call entirely or hides a possible +failure that will only surface later, in a more confusing way. + +## How to fix + +Before: +```cpp +#include +void f(char* a, char* b) { + strcmp(a, b); // <- the comparison result is thrown away +} +``` + +After: +```cpp +#include +void f(char* a, char* b) { + if (strcmp(a, b) == 0) {} +} +``` diff --git a/man/checkers/incompatibleFileOpen.md b/man/checkers/incompatibleFileOpen.md new file mode 100644 index 00000000000..bc0e7fd52d6 --- /dev/null +++ b/man/checkers/incompatibleFileOpen.md @@ -0,0 +1,44 @@ +# incompatibleFileOpen + +**Message**: The file 'a.txt' is opened for read and write access at the same time on different streams
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +The same filename is opened for reading on one stream while another stream already has it open for +writing (or vice versa). + +## Motivation + +Two independent streams onto the same file, one reading and one writing, can observe an inconsistent +view of the file's contents depending on buffering and timing - a portability and correctness hazard +that's easy to introduce without noticing, since each stream on its own looks fine. + +## How to fix + +Before: +```cpp +#include +void f() { + FILE *f1 = fopen("a.txt", "w"); + FILE *f2 = fopen("a.txt", "r"); // <- 'a.txt' is already open for writing +} +``` + +After: +```cpp +#include +void f() { + FILE *f1 = fopen("a.txt", "r"); + if (f1) fclose(f1); +} +``` + +## Related checkers + +- [useClosedFile.md](useClosedFile.md), [readWriteOnlyFile.md](readWriteOnlyFile.md), + [writeReadOnlyFile.md](writeReadOnlyFile.md), [IOWithoutPositioning.md](IOWithoutPositioning.md), + [seekOnAppendedFile.md](seekOnAppendedFile.md) - other checks that follow the same `FILE*` through a + function. diff --git a/man/checkers/incompleteArrayFill.md b/man/checkers/incompleteArrayFill.md new file mode 100644 index 00000000000..41f3ce4f584 --- /dev/null +++ b/man/checkers/incompleteArrayFill.md @@ -0,0 +1,45 @@ +# incompleteArrayFill + +**Message**: Array 'a[5]' might be filled incompletely. Did you forget to multiply the size given to 'memset()' with 'sizeof(*a)'?
+**Category**: Correctness
+**Severity**: Warning/Portability
+**Language**: C/C++ + +## Description + +`memset()`/`memcpy()`/`memmove()` is given a byte count equal to the *number of elements* in an array, +rather than their combined size in bytes - unless each element happens to be exactly one byte, this only +fills part of the array. When the array's element type is specifically `bool`, this is reported at +`portability` severity instead: `sizeof(bool)` happens to be `1` on most common platforms (so the code +"works" there), but the C/C++ standard doesn't guarantee it, so the exact same code is a real, silent +bug on a platform where `bool` is wider. + +## Motivation + +These functions take their size argument in bytes, not element count. Passing the element count directly +is an easy mistake - it compiles fine, and on typical platforms with 1-byte types it can even happen to +be correct, but for any array element wider than one byte it silently leaves most of the array +untouched. + +## How to fix + +Multiply the element count by `sizeof(*array)` (or the element type's size) to get the correct byte +count. + +Before: +```cpp +#include +void f() { + int a[5]; + memset(a, 123, 5); // <- fills 5 bytes, not 5 ints +} +``` + +After: +```cpp +#include +void f() { + int a[5]; + memset(a, 123, 5 * sizeof(*a)); +} +``` diff --git a/man/checkers/incorrectLogicOperator.md b/man/checkers/incorrectLogicOperator.md new file mode 100644 index 00000000000..7acd2d8c735 --- /dev/null +++ b/man/checkers/incorrectLogicOperator.md @@ -0,0 +1,42 @@ +# incorrectLogicOperator + +**Message**: Logical disjunction always evaluates to true: x != 1 || x != 2.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +Two comparisons on the same variable, joined by `&&`/`||`, combine to something that's always true or +always false, regardless of the variable's actual value. + +## Motivation + +`x == 1 && x == 2` can never be true (a variable can't equal both 1 and 2 at once), and +`x != 1 || x != 2` can never be false (it's always at least one of those). Writing this is a strong +signal the wrong logical operator was used - `&&` instead of `||`, or vice versa - or that one of the +compared values is wrong. + +This check may need `--check-level=exhaustive` to see every case. + +## How to fix + +Before: +```cpp +void f(int x) { + if (x == 1 && x == 2) {} // <- can never be true +} +``` + +After: +```cpp +void f(int x) { + if (x == 1 || x == 2) {} +} +``` + +## Related checkers + +- [redundantCondition.md](redundantCondition.md) - the same style of two-comparisons-on-one-variable + analysis, but for when one comparison is already implied by the other rather than making the whole + expression always true/false. diff --git a/man/checkers/incorrectStringBooleanError.md b/man/checkers/incorrectStringBooleanError.md new file mode 100644 index 00000000000..cdfc503ea7f --- /dev/null +++ b/man/checkers/incorrectStringBooleanError.md @@ -0,0 +1,36 @@ +# incorrectStringBooleanError and incorrectCharBooleanError + +**Message**: Conversion of string literal "Hello" to bool always evaluates to true.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A string or char literal is used directly where a boolean is expected (`if ("Hello")`, +`x ? "a" : "b"` as the whole condition) - this is always `true` unless the literal is exactly +`""`/`'\0'`. + +## Motivation + +A non-empty string or character literal converts to `true` every time - if the intent was to check a +variable's value, using the literal itself instead is a typo that silently makes the condition +constant, so the code inside always (or never) runs regardless of any real input. + +## How to fix + +Before: +```cpp +int f() { + if ("Hello") {} // <- always true + return 0; +} +``` + +After: +```cpp +int f(bool flag) { + if (flag) {} + return 0; +} +``` diff --git a/man/checkers/incorrectStringCompare.md b/man/checkers/incorrectStringCompare.md new file mode 100644 index 00000000000..a03b379d62a --- /dev/null +++ b/man/checkers/incorrectStringCompare.md @@ -0,0 +1,35 @@ +# incorrectStringCompare + +**Message**: String literal "Hello" doesn't match length argument for substr().
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +`x.substr(pos, len) == "literal"` where `len` doesn't match the length of `"literal"`. + +## Motivation + +`substr(pos, len)` only ever returns a string of (at most) `len` characters, so comparing it against a +literal of a different length can never be true (if `len` is too short) or is comparing more characters +than were actually extracted (if `len` is too long) - either way, the comparison doesn't test what it +looks like it tests. + +## How to fix + +Before: +```cpp +#include +int f(std::string test) { + return test.substr(0, 4) == "Hello" ? 0 : 1; // <- "Hello" is 5 chars +} +``` + +After: +```cpp +#include +int f(std::string test) { + return test.substr(0, 5) == "Hello" ? 0 : 1; +} +``` diff --git a/man/checkers/incrementboolean.md b/man/checkers/incrementboolean.md new file mode 100644 index 00000000000..a096446d0a1 --- /dev/null +++ b/man/checkers/incrementboolean.md @@ -0,0 +1,34 @@ +# incrementboolean + +**Message**: Incrementing a variable of type 'bool' with postfix operator++ is deprecated by the C++ Standard. You should assign it the value 'true' instead.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +A `bool` variable is incremented with `++`. + +## Motivation + +Incrementing a `bool` is deprecated by the C++ standard (Annex D) and always just sets it to `true` - +writing `= true` says the same thing without relying on deprecated, integer-flavored behaviour of a +type that isn't really a number. + +## How to fix + +Before: +```cpp +bool ready = true; +void f() { + ready++; // <- deprecated +} +``` + +After: +```cpp +bool ready = true; +void f() { + ready = true; +} +``` diff --git a/man/checkers/initializerList.md b/man/checkers/initializerList.md new file mode 100644 index 00000000000..989627d1f71 --- /dev/null +++ b/man/checkers/initializerList.md @@ -0,0 +1,77 @@ +# initializerList + +**Message**: Member variable 'c' is in the wrong order in the initialization list.
+**Category**: Correctness
+**Severity**: Style
+**Language**: C++ + +## Description + +A constructor's member-initializer list either lists members in a different order than they're declared +in the class (misleading, since members are always initialized in declaration order, not +initializer-list order), or initializes one member using another member as a source value when that +source member is declared *after* it - so, despite appearing earlier in the list, the source hasn't +actually been initialized yet at that point. + +## Motivation + +C++ always initializes members in the order they're *declared* in the class, regardless of what order +they're written in the initializer list. cppcheck flags any mismatch between the two orders purely as a +defensive-programming measure - most of the time (as in the first example below, where every member is +just initialized from a constant) writing the list out of order is only misleading to a reader, with no +real consequence. But because members are genuinely initialized in declaration order, a list written out +of order can also hide a real bug: if one member's initializer reads another member that's declared +*later*, the value it reads is indeterminate (that member's own storage hasn't been given a value yet), +and reading an indeterminate scalar value is undefined behaviour - the same underlying problem as reading +any other uninitialized variable, just harder to spot because the initializer list makes it look like the +source was already set up. cppcheck doesn't distinguish the two cases - it always suggests reordering the +list to match declaration order, which prevents the harmful case from ever being possible. + +## How to fix + +Write the initializer list in the same order the members are declared in the class. + +Before: +```cpp +class Fred { + int a, b, c; +public: + Fred() : c(0), b(0), a(0) { } // <- listed in the reverse of declaration order +}; +``` + +After: +```cpp +class Fred { + int a, b, c; +public: + Fred() : a(0), b(0), c(0) { } +}; +``` + +Before: +```cpp +class Foo { +public: + Foo(int arg) : a(b), b(arg) {} // <- 'a' is initialized from 'b', but 'b' isn't set yet + int a; + int b; +}; +``` + +After: +```cpp +class Foo { +public: + explicit Foo(int arg) : a(arg), b(arg) {} + int a; + int b; +}; +``` + +## Related checkers + +- [useInitializationList.md](useInitializationList.md) - a different constructor-initializer-list + pitfall, about whether the list is used at all rather than the order members appear in it. +- [selfInitialization.md](selfInitialization.md) - the more specific case where a member is initialized + from itself. diff --git a/man/checkers/intToPointerCast.md b/man/checkers/intToPointerCast.md new file mode 100644 index 00000000000..b72749a994e --- /dev/null +++ b/man/checkers/intToPointerCast.md @@ -0,0 +1,44 @@ +# intToPointerCast + +**Message**: Casting non-zero decimal integer literal to pointer.
+**Category**: Portability
+**Severity**: Portability
+**Language**: C/C++ + +## Description + +A non-zero integer literal (not a hex address like `0x7000`, which is common in embedded code) is cast +directly to a pointer. + +## Motivation + +A decimal, octal, or binary integer literal cast to a pointer looks like it's meant to be a specific +memory address, but writing addresses in those bases is unusual and easy to mistype or misread compared +to the conventional hexadecimal form - a small typo can silently produce a very different address. + +## How to fix + +Write the address in hexadecimal, which is the conventional and clearer way to express a raw address. + +Before: +```cpp +#include +uint8_t* f() { + uint8_t* ptr = (uint8_t*)7; // <- 7 isn't a real address + return ptr; +} +``` + +After: +```cpp +#include +uint8_t* f() { + uint8_t* ptr = (uint8_t*)0x7000; // an actual address is fine + return ptr; +} +``` + +## Related checkers + +- [invalidPointerCast.md](invalidPointerCast.md) - a different pointer-cast portability issue, about + casting between two pointer types whose values aren't laid out the same way in memory. diff --git a/man/checkers/integerOverflow.md b/man/checkers/integerOverflow.md new file mode 100644 index 00000000000..1c426f5a7ca --- /dev/null +++ b/man/checkers/integerOverflow.md @@ -0,0 +1,57 @@ +# integerOverflow and integerOverflowCond + +**Message**: Signed integer overflow for expression 'x*y'.
+**Category**: Undefined Behaviour
+**Severity**: Error/Warning
+**Language**: C/C++ + +## Description + +This checker detects when a signed integer arithmetic expression (`+`, `-`, `*`, `/`, `<<`, etc.) can +overflow or underflow the range of its result type, based on the platform's configured integer +widths. + +- `integerOverflow`: the overflow/underflow is unconditional - it happens for every value cppcheck can + see reaching that point. +- `integerOverflowCond`: the overflow/underflow only happens under a certain condition elsewhere in the + code; the message explains that "Either the condition ... is redundant or there is signed integer + overflow/underflow ...". + +As a special case, left-shifting into the sign bit (for example `1 << 31` for a 32-bit int) is not +reported, since this is common practice even though it is technically undefined behaviour; that is +instead covered by the [shiftTooManyBits](shiftTooManyBits.md) checker family. + +## Motivation + +Signed integer overflow is undefined behaviour in C and C++. In practice this often means the +calculation silently produces a wrong (wrapped or truncated) result, and with optimizations enabled +the compiler is allowed to assume overflow never happens, which can eliminate or reorder code in +surprising ways. + +## How to fix + +You can fix these warnings by: +1. Using a wider integer type for the calculation +2. Rewriting the calculation to avoid the overflow (for example checking bounds before multiplying) +3. Using an unsigned type, if wraparound behaviour is actually intended + +Note: cppcheck only warns when it can actually determine that the calculation overflows - either from +a known value (as below) or from a condition elsewhere in the code (see `integerOverflowCond` above). +A plain `a * b` of two otherwise-unconstrained parameters gives cppcheck nothing to prove an overflow +with, so it is not reported. + +Before: +```cpp +int32_t f() { + int32_t intmax = 0x7fffffff; // INT32_MAX + return intmax + 1; // <- integerOverflow, known to overflow 32-bit int +} +``` + +After: +```cpp +int64_t f() { + int32_t intmax = 0x7fffffff; + return (int64_t)intmax + 1; // <- widen before adding +} +``` diff --git a/man/checkers/invalidContainer.md b/man/checkers/invalidContainer.md new file mode 100644 index 00000000000..402ff69f4bb --- /dev/null +++ b/man/checkers/invalidContainer.md @@ -0,0 +1,71 @@ +# invalidContainer and invalidContainerReference + +**Message**: Using pointer to local variable 'v' that may be invalid.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +A pointer or reference taken from inside a container (an element, `.front()`, an iterator, ...) is used +after a call (`push_back()`, `insert()`, `clear()`, ...) that may have invalidated it. + +- `invalidContainer`: the invalidated thing is a pointer or iterator value. +- `invalidContainerReference`: the invalidated thing is a reference. + +## Motivation + +Many container operations can reallocate or otherwise invalidate previously-obtained pointers, +references, and iterators into that container's data, without any visible change at the call site of +the operation that invalidates them. Using one afterwards is undefined behaviour. + +## How to fix + +Before: +```cpp +#include +#include +void f(std::vector &v) { + int *v0 = &v[0]; + v.push_back(123); + std::cout << *v0 << std::endl; // <- invalidContainer: push_back() may have reallocated 'v' +} +``` + +After: +```cpp +#include +#include +void f(std::vector &v) { + v.push_back(123); + std::cout << v[0] << std::endl; +} +``` + +Before: +```cpp +#include +#include +void f() { + std::vector v = {1}; + int &v0 = v.front(); + v.push_back(123); + std::cout << v0 << std::endl; // <- invalidContainerReference: push_back() may have reallocated 'v' +} +``` + +After: +```cpp +#include +#include +void f() { + std::vector v = {1}; + v.push_back(123); + std::cout << v.front() << std::endl; +} +``` + +## Related checkers + +- [invalidContainerLoop.md](invalidContainerLoop.md) - the equivalent problem when the invalidating + call happens while a loop is actively iterating over the same container. diff --git a/man/checkers/invalidContainerLoop.md b/man/checkers/invalidContainerLoop.md new file mode 100644 index 00000000000..d6bce6e098a --- /dev/null +++ b/man/checkers/invalidContainerLoop.md @@ -0,0 +1,52 @@ +# invalidContainerLoop + +**Message**: Calling 'push_back' while iterating the container is invalid.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +A container is modified with a call that can invalidate iterators (such as `push_back()` on a +`vector`) while a loop is iterating over that same container. + +## Motivation + +Modifying a container while a range-based (or iterator-based) loop is walking over it risks invalidating +the loop's own iterator mid-iteration - for `push_back()` on a `vector`, this happens whenever the call +needs to reallocate storage, which isn't certain from any single call but is guaranteed to happen +eventually as the container grows. Once that happens, the loop machinery's next step (comparing, +dereferencing, or incrementing the now-invalid iterator) is undefined behaviour - even though it often +appears to "work" for a while before the container's storage actually needs to move. + +## How to fix + +Before: +```cpp +#include +void f(std::vector v) { + for (auto i : v) { + if (i < 5) + v.push_back(i * 2); // <- invalidContainerLoop: modifying 'v' while iterating over it + } +} +``` + +After: +```cpp +#include +void f(std::vector v) { + std::vector toAdd; + for (auto i : v) { + if (i < 5) + toAdd.push_back(i * 2); + } + v.insert(v.end(), toAdd.begin(), toAdd.end()); +} +``` + +## Related checkers + +- [invalidContainer.md](invalidContainer.md) - the more general check for using a pointer, reference, + or iterator into a container after some other call may have invalidated it (not necessarily while + iterating). diff --git a/man/checkers/invalidFree.md b/man/checkers/invalidFree.md new file mode 100644 index 00000000000..57bff5c0198 --- /dev/null +++ b/man/checkers/invalidFree.md @@ -0,0 +1,43 @@ +# invalidFree + +**Message**: Mismatching address is not returned from malloc(). The address you get from malloc() must be freed without offset.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +`free()`/`delete`/`delete[]` is called on a pointer that has been moved by pointer arithmetic since it +was allocated - the allocator only recognizes the exact address it originally returned. + +## Motivation + +Freeing an address other than the exact one an allocator returned is undefined behaviour - typically a +crash, but potentially memory corruption that surfaces somewhere else entirely. This is easy to +introduce by accident once a pointer is advanced (for example while parsing or iterating through a +buffer) and then freed without first restoring it to the original address. cppcheck only tracks a +pointer as long as it can be sure nothing else changed it; passing it to another function is enough +uncertainty that cppcheck stops checking it rather than guess, so this catches only the mismatches it +can prove, not every one that exists. + +## How to fix + +Free the exact pointer the allocator returned, not one that's been offset since. + +Before: +```cpp +#include +void foo() { + char *a; a = malloc(1024); + free(a + 10); // <- this isn't the address malloc() returned +} +``` + +After: +```cpp +#include +void foo() { + char *a; a = malloc(1024); + free(a); +} +``` diff --git a/man/checkers/invalidFunctionArg.md b/man/checkers/invalidFunctionArg.md new file mode 100644 index 00000000000..d102bfdce9d --- /dev/null +++ b/man/checkers/invalidFunctionArg.md @@ -0,0 +1,40 @@ +# invalidFunctionArg + +**Message**: Invalid $symbol() argument nr 1. The value is 0 but the valid values are '1:'.
+**Category**: Correctness
+**Severity**: Error/Warning
+**Language**: C/C++ + +## Description + +An argument value falls outside the numeric range a function actually accepts, based on what cppcheck +knows about the function (from a library configuration) and about the value passed. + +## Motivation + +Many standard library functions only behave correctly for a specific range of an argument (a base for +`strtol()`, a mode for a file function, ...); passing a value outside that range is undefined or +implementation-defined behaviour that a compiler will not catch. + +## How to fix + +Before: +```cpp +void f(char *a, char **b) { + strtol(a, b, 1); // <- base 1 is not valid +} +``` + +After: +```cpp +void f(char *a, char **b) { + strtol(a, b, 10); +} +``` + +## Related checkers + +- [invalidFunctionArgBool.md](invalidFunctionArgBool.md) - the same idea, but for an argument that + needs a real boolean rather than a plain `0`/`1`. +- [invalidFunctionArgStr.md](invalidFunctionArgStr.md) - the same idea, but for an argument that needs + a null-terminated C-string. diff --git a/man/checkers/invalidFunctionArgBool.md b/man/checkers/invalidFunctionArgBool.md new file mode 100644 index 00000000000..af6e3d691dd --- /dev/null +++ b/man/checkers/invalidFunctionArgBool.md @@ -0,0 +1,41 @@ +# invalidFunctionArgBool + +**Message**: Invalid $symbol() argument nr 1. A non-boolean value is required.
+**Category**: Correctness
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A plain `0`/`1` is passed where a function (per its library configuration) actually requires a real +boolean argument. + +## Motivation + +Some functions distinguish a genuine boolean argument from an arbitrary integer that happens to be `0` +or `1` - passing the wrong kind of value can be accepted silently but not do what's intended. + +## How to fix + +Before: +```cpp +void setFlag(bool enabled); +void f() { + setFlag(1); // <- treat this as a real boolean, not an arbitrary int +} +``` + +After: +```cpp +void setFlag(bool enabled); +void f() { + setFlag(true); +} +``` + +## Related checkers + +- [invalidFunctionArg.md](invalidFunctionArg.md) - the same idea, but for an argument whose numeric + value must fall in a specific range. +- [invalidFunctionArgStr.md](invalidFunctionArgStr.md) - the same idea, but for an argument that needs + a null-terminated C-string. diff --git a/man/checkers/invalidFunctionArgStr.md b/man/checkers/invalidFunctionArgStr.md new file mode 100644 index 00000000000..a68e95ad57a --- /dev/null +++ b/man/checkers/invalidFunctionArgStr.md @@ -0,0 +1,46 @@ +# invalidFunctionArgStr + +**Message**: Invalid $symbol() argument nr 1. A NUL-terminated string is required.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A `char`/`wchar_t` buffer that isn't null-terminated (for example, the address of a single character) +is passed where a function (per its library configuration) requires a null-terminated C-string. + +## Motivation + +Functions like `strlen()`/`strcmp()` scan forward until they find a `'\0'` byte - if the argument isn't +actually a null-terminated string, whether this reads past the end of whatever memory was passed depends +on what byte values happen to follow it. If a `'\0'` byte never turns up before the buffer's real end, +the scan reads out of bounds, which is undefined behaviour; there's no way to guarantee that in advance +from the code alone, which is exactly why passing a non-null-terminated buffer is dangerous even when a +particular run happens not to crash. + +## How to fix + +Before: +```cpp +#include +size_t f(char x) { + return strlen(&x); // <- 'x' is a single char, not a null-terminated string +} +``` + +After: +```cpp +#include +size_t f() { + char x[] = "a"; + return strlen(x); +} +``` + +## Related checkers + +- [invalidFunctionArg.md](invalidFunctionArg.md) - the same idea, but for an argument whose numeric + value must fall in a specific range. +- [invalidFunctionArgBool.md](invalidFunctionArgBool.md) - the same idea, but for an argument that + needs a real boolean rather than a plain `0`/`1`. diff --git a/man/checkers/invalidIterator1.md b/man/checkers/invalidIterator1.md new file mode 100644 index 00000000000..811bf31d678 --- /dev/null +++ b/man/checkers/invalidIterator1.md @@ -0,0 +1,43 @@ +# invalidIterator1 + +**Message**: Invalid iterator: aIt
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +An iterator that hasn't been given a value yet (or is no longer valid) is passed to +`insert()`/`erase()`. + +## Motivation + +Passing an iterator that doesn't currently point anywhere valid into a container method is undefined +behaviour, and is easy to miss when the same iterator was previously used (and invalidated) earlier in +the function. + +## How to fix + +Before: +```cpp +#include +void f(const std::list& m) { + std::list::iterator aIt = m.begin(); + m.erase(*aIt); + m.erase(aIt); // <- invalidIterator1: 'aIt' was already erased by value above +} +``` + +After: +```cpp +#include +void f(std::list& m) { + std::list::iterator aIt = m.begin(); + m.erase(aIt); +} +``` + +## Related checkers + +- [eraseDereference.md](eraseDereference.md) - a related mistake where an iterator is dereferenced, + rather than passed to `erase()`, after the element it pointed to was already erased. diff --git a/man/checkers/invalidLengthModifierError.md b/man/checkers/invalidLengthModifierError.md new file mode 100644 index 00000000000..55c339067e2 --- /dev/null +++ b/man/checkers/invalidLengthModifierError.md @@ -0,0 +1,38 @@ +# invalidLengthModifierError + +**Message**: 'I' in format string (no. 1) is a length modifier and cannot be used without a conversion specifier.
+**Category**: Undefined Behaviour
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A length modifier (`h`, `l`, `I64`, ...) appears in the format string with no conversion specifier +after it, so it doesn't actually modify anything. + +## Motivation + +A length modifier only means something when it's immediately followed by a conversion specifier like +`d` or `u` - on its own it's either a leftover from editing the format string, or a sign that a +specifier letter was accidentally dropped. It also leaves the format string with no valid conversion +specification at that point, and the C standard says using an invalid conversion specification is +undefined behaviour - in practice this usually just misreads the argument list, but nothing prevents a +library from doing something worse with it. + +## How to fix + +Before: +```cpp +#include +void f() { + printf("%I", 5); // <- 'I' with no conversion specifier after it +} +``` + +After: +```cpp +#include +void f() { + printf("%Id", (ptrdiff_t)5); +} +``` diff --git a/man/checkers/invalidLifetime.md b/man/checkers/invalidLifetime.md new file mode 100644 index 00000000000..938b531fe70 --- /dev/null +++ b/man/checkers/invalidLifetime.md @@ -0,0 +1,48 @@ +# invalidLifetime + +**Message**: Using object that is out of scope.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A pointer (or a lambda capturing one) that refers to a variable is used after that variable's scope has +already ended - for example, it was only set inside an `if` block, and is used after the block closes. + +## Motivation + +A variable declared inside a nested block stops existing once that block ends, even though the pointer +that referred to it is still sitting in an outer scope. Using that pointer afterwards is undefined +behaviour, and it's easy to overlook because the pointer variable itself is still very much "in scope." + +## How to fix + +Before: +```cpp +void f(bool cond) { + int* p; + if (cond) { + int x = 1; + p = &x; + } + *p = 2; // <- invalidLifetime: 'x' is out of scope here on the 'cond' path +} +``` + +After: +```cpp +void f(bool cond) { + int local = 1; + int* p = &local; + if (cond) { + *p = 1; + } + *p = 2; +} +``` + +## Related checkers + +- [danglingTemporaryLifetime.md](danglingTemporaryLifetime.md) - the same idea, but for a pointer or + iterator into a temporary object instead of a named local variable. diff --git a/man/checkers/invalidPointerCast.md b/man/checkers/invalidPointerCast.md new file mode 100644 index 00000000000..6da56bf09a9 --- /dev/null +++ b/man/checkers/invalidPointerCast.md @@ -0,0 +1,47 @@ +# invalidPointerCast + +**Message**: Casting between float * and double * which have an incompatible binary data representation.
+**Category**: Portability
+**Severity**: Portability
+**Language**: C/C++ + +## Description + +A pointer is cast to a pointer of another type whose values aren't laid out the same way in memory (for +example `float*` to `double*`, or a pointer to a floating-point type cast to/from an integer pointer) - +reading through the new pointer doesn't reinterpret the same bytes the same way on every platform. + +## Motivation + +Reinterpreting the bytes of one type as if they were another only makes sense when both types share the +same binary layout. Floating-point types in particular can have very different sizes and bit layouts +across platforms, so code that happens to "work" during development can silently misbehave once built +for a different target. + +cppcheck flags the cast itself, based only on the two pointer types involved - it doesn't check whether +the resulting pointer is ever actually read through (or otherwise used in a way that depends on the +bytes matching up). Doing so - reading an object through a pointer to an incompatible type - is where +the undefined behaviour actually is; forming the pointer alone is not. + +## How to fix + +Before: +```cpp +void test() { + float *f = new float[10]; + delete [] (double*)f; // <- float and double aren't stored the same way +} +``` + +After: +```cpp +void test() { + float *f = new float[10]; + delete [] f; +} +``` + +## Related checkers + +- [intToPointerCast.md](intToPointerCast.md) - a different pointer-cast portability issue, about casting + a plain (non-hex) integer literal directly to a pointer. diff --git a/man/checkers/invalidPrintfArgType_float.md b/man/checkers/invalidPrintfArgType_float.md new file mode 100644 index 00000000000..939dfc9c987 --- /dev/null +++ b/man/checkers/invalidPrintfArgType_float.md @@ -0,0 +1,40 @@ +# invalidPrintfArgType_float + +**Message**: %f in format string (no. 1) requires 'double' but the argument type is 'signed int'
+**Category**: Undefined Behaviour
+**Severity**: Warning/Portability
+**Language**: C/C++ + +## Description + +The argument for a floating-point specifier (`%f`, `%e`, `%g`, ...) isn't a floating-point value. + +## Motivation + +Because `printf` is variadic, a `float` argument is promoted to `double` and read back out as +`double` by the corresponding specifier - passing an integer instead means `printf` reads the wrong +number of bytes and reinterprets them as a floating-point value. That mismatched read is undefined +behaviour in its own right, not just a meaningless printed number. + +## How to fix + +Before: +```cpp +#include +void f(int x) { + printf("%f", x); // <- needs 'double', not 'int' +} +``` + +After: +```cpp +#include +void f(double x) { + printf("%f", x); +} +``` + +## Related checkers + +- [invalidPrintfArgType_uint.md](invalidPrintfArgType_uint.md) / [invalidPrintfArgType_sint.md](invalidPrintfArgType_sint.md) - the integer equivalents. +- [invalidScanfArgType_float.md](invalidScanfArgType_float.md) - the `scanf`-side equivalent. diff --git a/man/checkers/invalidPrintfArgType_n.md b/man/checkers/invalidPrintfArgType_n.md new file mode 100644 index 00000000000..c569da99501 --- /dev/null +++ b/man/checkers/invalidPrintfArgType_n.md @@ -0,0 +1,40 @@ +# invalidPrintfArgType_n + +**Message**: %n in format string (no. 1) requires 'int \*' but the argument type is 'signed int'
+**Category**: Undefined Behaviour
+**Severity**: Warning/Portability
+**Language**: C/C++ + +## Description + +For a `printf`-family call, a `%n` specifier is given something that clearly isn't a writable pointer at +all - a plain integer, or a `const`-qualified pointer. It checks for *some* writable pointer, not +specifically a pointer to `int`, which is what `%n` actually requires. + +## Motivation + +`%n` writes the number of characters printed so far back through the pointer it's given - if the +argument isn't actually a pointer, this writes to whatever address that value happens to represent, +which is undefined behaviour (and is disabled outright by some platforms/libraries as a security risk). + +## How to fix + +Before: +```cpp +#include +void f(int x) { + printf("%n", x); // <- needs an 'int *', not 'int' +} +``` + +After: +```cpp +#include +void f(int *x) { + printf("%n", x); +} +``` + +## Related checkers + +- [invalidPrintfArgType_s.md](invalidPrintfArgType_s.md) / [invalidPrintfArgType_p.md](invalidPrintfArgType_p.md) - the same idea for `%s` and `%p`. diff --git a/man/checkers/invalidPrintfArgType_p.md b/man/checkers/invalidPrintfArgType_p.md new file mode 100644 index 00000000000..26cdff2ef33 --- /dev/null +++ b/man/checkers/invalidPrintfArgType_p.md @@ -0,0 +1,39 @@ +# invalidPrintfArgType_p + +**Message**: %p in format string (no. 1) requires an address but the argument type is 'signed int'
+**Category**: Undefined Behaviour
+**Severity**: Warning/Portability
+**Language**: C/C++ + +## Description + +For a `printf`-family call, a `%p` specifier isn't given a pointer. + +## Motivation + +`%p` is meant to print the value of a pointer. Passing a non-pointer argument means `printf` reads it as +if it were a pointer-sized value from the variadic argument list, which is undefined behaviour the +moment that mismatched read happens - not just a meaningless value, since on some calling conventions it +also reads the wrong argument size entirely, throwing off every argument read after it. + +## How to fix + +Before: +```cpp +#include +void f(int x) { + printf("%p", x); // <- needs an address, not 'int' +} +``` + +After: +```cpp +#include +void f(void *x) { + printf("%p", x); +} +``` + +## Related checkers + +- [invalidPrintfArgType_s.md](invalidPrintfArgType_s.md) / [invalidPrintfArgType_n.md](invalidPrintfArgType_n.md) - the same idea for `%s` and `%n`. diff --git a/man/checkers/invalidPrintfArgType_s.md b/man/checkers/invalidPrintfArgType_s.md new file mode 100644 index 00000000000..7616098dfe4 --- /dev/null +++ b/man/checkers/invalidPrintfArgType_s.md @@ -0,0 +1,39 @@ +# invalidPrintfArgType_s + +**Message**: %s in format string (no. 1) requires 'char \*' but the argument type is 'signed int'
+**Category**: Undefined Behaviour
+**Severity**: Warning/Portability
+**Language**: C/C++ + +## Description + +For a `printf`-family call, a `%s` specifier isn't given a `char*`. + +## Motivation + +`printf` reads a `%s` argument as a pointer to a null-terminated string - if the argument is actually a +plain integer (or any other non-pointer value), `printf` dereferences whatever address that value +happens to represent, which is undefined behaviour. + +## How to fix + +Before: +```cpp +#include +void f(int x) { + printf("%s", x); // <- needs a 'char *', not 'int' +} +``` + +After: +```cpp +#include +void f(const char *x) { + printf("%s", x); +} +``` + +## Related checkers + +- [invalidScanfArgType_s.md](invalidScanfArgType_s.md) - the `scanf`-side equivalent. +- [invalidPrintfArgType_n.md](invalidPrintfArgType_n.md) / [invalidPrintfArgType_p.md](invalidPrintfArgType_p.md) - the same idea for `%n` and `%p`. diff --git a/man/checkers/invalidPrintfArgType_sint.md b/man/checkers/invalidPrintfArgType_sint.md new file mode 100644 index 00000000000..b36668c81f4 --- /dev/null +++ b/man/checkers/invalidPrintfArgType_sint.md @@ -0,0 +1,42 @@ +# invalidPrintfArgType_sint + +**Message**: %d in format string (no. 1) requires 'int' but the argument type is 'unsigned int'
+**Category**: Undefined Behaviour
+**Severity**: Warning/Portability
+**Language**: C/C++ + +## Description + +The argument for a signed-integer specifier (`%d`, `%i`) doesn't match that specifier's expected +type/size (including the `h`/`hh`/`l`/`ll`/`z`/`j`/`t` length modifiers). + +## Motivation + +`printf` reads its arguments according to the type the format specifier implies, not the type the +argument actually has - a signedness or size mismatch means the value is read using the wrong number of +bytes or the wrong interpretation of the sign bit. Reading a variadic argument through a mismatched type +like this is undefined behaviour in its own right, not just a display quirk - the printed value being +nonsensical is only the most visible symptom. + +## How to fix + +Before: +```cpp +#include +void f(unsigned int x) { + printf("%d", x); // <- needs signed 'int', not 'unsigned int' +} +``` + +After: +```cpp +#include +void f(int x) { + printf("%d", x); +} +``` + +## Related checkers + +- [invalidPrintfArgType_uint.md](invalidPrintfArgType_uint.md) - the unsigned-integer equivalent. +- [invalidPrintfArgType_float.md](invalidPrintfArgType_float.md) - the floating-point equivalent. diff --git a/man/checkers/invalidPrintfArgType_uint.md b/man/checkers/invalidPrintfArgType_uint.md new file mode 100644 index 00000000000..72c12476361 --- /dev/null +++ b/man/checkers/invalidPrintfArgType_uint.md @@ -0,0 +1,43 @@ +# invalidPrintfArgType_uint + +**Message**: %u in format string (no. 1) requires 'unsigned int' but the argument type is 'signed int'
+**Category**: Undefined Behaviour
+**Severity**: Warning/Portability
+**Language**: C/C++ + +## Description + +The argument for an unsigned-integer specifier (`%u`, `%x`, `%o`, ...) doesn't match that specifier's +expected type/size (including the `h`/`hh`/`l`/`ll`/`z`/`j`/`t` length modifiers). + +## Motivation + +`printf` reads its arguments according to the type the format specifier implies, not the type the +argument actually has - a signedness or size mismatch means the value is read using the wrong number of +bytes or the wrong interpretation of the sign bit. Reading a variadic argument through a mismatched type +like this is undefined behaviour in its own right, not just a display quirk - the printed value being +nonsensical is only the most visible symptom. + +## How to fix + +Before: +```cpp +#include +void f(int x) { + printf("%u", x); // <- needs 'unsigned int', not signed 'int' +} +``` + +After: +```cpp +#include +void f(unsigned int x) { + printf("%u", x); +} +``` + +## Related checkers + +- [invalidPrintfArgType_sint.md](invalidPrintfArgType_sint.md) - the signed-integer equivalent. +- [invalidPrintfArgType_float.md](invalidPrintfArgType_float.md) - the floating-point equivalent. +- [invalidScanfArgType_int.md](invalidScanfArgType_int.md) - the `scanf`-side equivalent. diff --git a/man/checkers/invalidScanfArgType_float.md b/man/checkers/invalidScanfArgType_float.md new file mode 100644 index 00000000000..a99f12811eb --- /dev/null +++ b/man/checkers/invalidScanfArgType_float.md @@ -0,0 +1,42 @@ +# invalidScanfArgType_float + +**Message**: %f in format string (no. 1) requires 'float \*' but the argument type is 'signed int \*'
+**Category**: Undefined Behaviour
+**Severity**: Warning/Portability
+**Language**: C/C++ + +## Description + +For a `scanf`-family call, the argument for a floating-point specifier (`%f`, `%e`, `%g`, ...) isn't a +pointer to the matching floating-point type. + +## Motivation + +`scanf` writes the parsed floating-point value through the pointer it's given, in the binary +floating-point representation that pointer's type implies - a mismatched pointer type (for example an +`int*`) means the write is the wrong size and reinterprets those bytes incorrectly. Writing through a +pointer typed differently than the object it actually points to is undefined behaviour in its own +right, not just a corrupted value. + +## How to fix + +Before: +```cpp +#include +void f(int *x) { + scanf("%f", x); // <- needs a 'float *', not 'int *' +} +``` + +After: +```cpp +#include +void f(float *x) { + scanf("%f", x); +} +``` + +## Related checkers + +- [invalidScanfArgType_s.md](invalidScanfArgType_s.md) / [invalidScanfArgType_int.md](invalidScanfArgType_int.md) - the same idea for string and integer `scanf` conversions. +- [invalidPrintfArgType_float.md](invalidPrintfArgType_float.md) - the `printf`-side equivalent. diff --git a/man/checkers/invalidScanfArgType_int.md b/man/checkers/invalidScanfArgType_int.md new file mode 100644 index 00000000000..ffc22b9db3d --- /dev/null +++ b/man/checkers/invalidScanfArgType_int.md @@ -0,0 +1,42 @@ +# invalidScanfArgType_int + +**Message**: %d in format string (no. 1) requires 'int \*' but the argument type is 'float \*'
+**Category**: Undefined Behaviour
+**Severity**: Warning/Portability
+**Language**: C/C++ + +## Description + +For a `scanf`-family call, the argument for an integer specifier (`%d`, `%u`, `%x`, ...) isn't a +pointer to the matching integer type (including the `h`/`hh`/`l`/`ll`/`z`/`j`/`t` length modifiers). + +## Motivation + +`scanf` writes the parsed integer through the pointer it's given, using exactly as many bytes as the +specifier's type implies - a mismatched pointer type means the write is the wrong size for what it +actually points to. Writing through a pointer typed differently than the object it actually points to is +undefined behaviour in its own right, not just a matter of corrupting adjacent memory or only partially +updating the intended variable. + +## How to fix + +Before: +```cpp +#include +void f(float *x) { + scanf("%d", x); // <- needs an 'int *', not 'float *' +} +``` + +After: +```cpp +#include +void f(int *x) { + scanf("%d", x); +} +``` + +## Related checkers + +- [invalidScanfArgType_s.md](invalidScanfArgType_s.md) / [invalidScanfArgType_float.md](invalidScanfArgType_float.md) - the same idea for string and floating-point `scanf` conversions. +- [invalidPrintfArgType_uint.md](invalidPrintfArgType_uint.md) / [invalidPrintfArgType_sint.md](invalidPrintfArgType_sint.md) - the `printf`-side equivalents. diff --git a/man/checkers/invalidScanfArgType_s.md b/man/checkers/invalidScanfArgType_s.md new file mode 100644 index 00000000000..be631260712 --- /dev/null +++ b/man/checkers/invalidScanfArgType_s.md @@ -0,0 +1,42 @@ +# invalidScanfArgType_s + +**Message**: %s in format string (no. 1) requires a 'char \*' but the argument type is 'signed int \*'
+**Category**: Undefined Behaviour
+**Severity**: Warning/Portability
+**Language**: C/C++ + +## Description + +For a `scanf`-family call, the argument for a `%s`/`%[...]` specifier isn't a pointer to the matching +type (remember every `scanf` conversion writes through a pointer). + +## Motivation + +`scanf` writes the characters it reads directly through the pointer it's given - if that pointer +doesn't actually point to a `char`/`wchar_t` buffer, the write corrupts whatever memory it does point +to. Writing through a pointer typed differently than the object it actually points to is undefined +behaviour in its own right, not just a corrupted value. + +## How to fix + +Before: +```cpp +#include +void f(int x) { + scanf("%s", &x); // <- needs a 'char *', not 'int *' +} +``` + +After: +```cpp +#include +void f() { + char x[32]; + scanf("%31s", x); +} +``` + +## Related checkers + +- [invalidScanfArgType_int.md](invalidScanfArgType_int.md) / [invalidScanfArgType_float.md](invalidScanfArgType_float.md) - the same idea for numeric `scanf` conversions. +- [invalidPrintfArgType_s.md](invalidPrintfArgType_s.md) - the `printf`-side equivalent for `%s`. diff --git a/man/checkers/invalidScanfFormatWidth.md b/man/checkers/invalidScanfFormatWidth.md new file mode 100644 index 00000000000..8b65103f95a --- /dev/null +++ b/man/checkers/invalidScanfFormatWidth.md @@ -0,0 +1,61 @@ +# invalidScanfFormatWidth and invalidScanfFormatWidth_smaller + +**Message**: Width 9 given in format string (no. 1) is larger than destination buffer 'str[8]', use %8c to prevent overflowing it.
+**Category**: Undefined Behaviour
+**Severity**: Error/Warning
+**Language**: C/C++ + +## Description + +- `invalidScanfFormatWidth`: a `%s`/`%c` field width is *larger* than the destination + array - `scanf` doesn't stop at the array's bound, only at the field width, so this overflows the + buffer. +- `invalidScanfFormatWidth_smaller`: the field width is *smaller* than the destination array - not a + bug, but a hint that the width might have been computed wrong (this variant needs `--inconclusive` to + show, since it's just a suspicious-looking number, not a proven mistake). + +## Motivation + +`scanf` writes up to as many characters as the field width says, regardless of how big the destination +buffer actually is - if the width is larger than the buffer, the read overflows it as soon as the input +actually supplies that many characters (given short enough input, it may not overflow on a particular +run, which is what makes this easy to miss in testing); if the width is smaller than the buffer, it +just leaves the buffer holding less than expected. Neither mistake is visible from the call site alone. + +## How to fix + +Before: +```cpp +#include +void f() { + char str[8]; + scanf("%9c", str); // <- invalidScanfFormatWidth: 9 > 8, overflows 'str' +} +``` + +After: +```cpp +#include +void f() { + char str[8]; + scanf("%8c", str); +} +``` + +Before: +```cpp +#include +void f() { + char str[10]; + scanf("%5s", str); // <- invalidScanfFormatWidth_smaller: 5 is well under 10, worth double-checking +} +``` + +After: +```cpp +#include +void f() { + char str[10]; + scanf("%9s", str); +} +``` diff --git a/man/checkers/invalidTestForOverflow.md b/man/checkers/invalidTestForOverflow.md new file mode 100644 index 00000000000..769d417d2ba --- /dev/null +++ b/man/checkers/invalidTestForOverflow.md @@ -0,0 +1,40 @@ +# invalidTestForOverflow + +**Message**: Invalid test for overflow 'x+100<x'; signed integer overflow is undefined behavior.
+**Category**: Undefined Behaviour
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A classic overflow check (`x + c < x`) that is itself undefined behaviour for signed integers and +pointers - optimizing compilers are allowed to, and do, remove such checks. + +## Motivation + +Signed integer overflow is undefined behaviour in C/C++, so a compiler is allowed to assume it never +happens - which means it can optimize away a check whose only purpose is to detect that overflow just +occurred. Code that relies on this pattern can pass in a debug build and silently stop working once +optimizations are turned on. + +## How to fix + +Before: +```cpp +void f(int x) { + if (x + 100 < x) {} // <- relies on signed overflow, which is UB +} +``` + +After: +```cpp +#include +void f(int x) { + if (x > std::numeric_limits::max() - 100) {} +} +``` + +## Related checkers + +- [pointerAdditionResultNotNull.md](pointerAdditionResultNotNull.md) - a related undefined-behaviour + trap, relying on pointer overflow instead of signed integer overflow. diff --git a/man/checkers/invalidscanf.md b/man/checkers/invalidscanf.md new file mode 100644 index 00000000000..e1a832509b1 --- /dev/null +++ b/man/checkers/invalidscanf.md @@ -0,0 +1,37 @@ +# invalidscanf + +**Message**: scanf() without field width limits can crash with huge input data.
+**Category**: Undefined Behaviour
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A `scanf`-family call reads a string (`%s`, or a character set `%[...]`) with no maximum field width - +given long enough input, this overflows the destination buffer. + +## Motivation + +Without a field width, `scanf("%s", buf)` (and its relatives) will write as much input as it's given, +with no regard for the size of `buf` - a classic, easily-exploitable buffer overflow driven entirely by +the input data. + +## How to fix + +Before: +```cpp +#include +void f() { + char c[5]; + scanf("%s", c); // <- no width limit, can overflow 'c' +} +``` + +After: +```cpp +#include +void f() { + char c[5]; + scanf("%4s", c); +} +``` diff --git a/man/checkers/iterateByValue.md b/man/checkers/iterateByValue.md new file mode 100644 index 00000000000..ac8881d6704 --- /dev/null +++ b/man/checkers/iterateByValue.md @@ -0,0 +1,47 @@ +# iterateByValue + +**Message**: Variable 'x' is used to iterate by value. It could be declared as a const reference which is usually faster and recommended in C++.
+**Category**: Code Quality
+**Severity**: Performance
+**Language**: C++ + +## Description + +A range-based `for` loop's variable (`for (auto x : container)`) copies each element - copying is +wasteful when a `const` reference would do. + +## Motivation + +Copying every element of a container just to read it wastes time and memory proportional to the +element's size and the container's length, for no benefit over a `const` reference. + +## How to fix + +Before: +```cpp +#include +#include +void f() { + const std::set ss = { "a", "b", "c" }; + for (auto s : ss) // <- each string is copied + (void)s.size(); +} +``` + +After: +```cpp +#include +#include +void f() { + const std::set ss = { "a", "b", "c" }; + for (const auto& s : ss) + (void)s.size(); +} +``` + +## Related checkers + +- [passedByValue.md](passedByValue.md) - the same idea, for a function parameter instead of a loop + variable. +- [iterateByValueCallback.md](iterateByValueCallback.md) - the same idea, when the loop is inside a + callback function. diff --git a/man/checkers/iterateByValueCallback.md b/man/checkers/iterateByValueCallback.md new file mode 100644 index 00000000000..66584610c45 --- /dev/null +++ b/man/checkers/iterateByValueCallback.md @@ -0,0 +1,28 @@ +# iterateByValueCallback + +**Message**: Variable 'x' is used to iterate by value. It could be declared as a const reference which is usually faster and recommended in C++. However it seems that 'f' is a callback function.
+**Category**: Code Quality
+**Severity**: Performance
+**Language**: C++ + +## Description + +Same idea as [iterateByValue.md](iterateByValue.md): a range-based `for` loop copies each element +needlessly. This variant is for when the loop is inside a function used as a callback. + +## Motivation + +Copying every element of a container just to read it wastes time and memory proportional to the +element's size and the container's length, for no benefit over a `const` reference. + +## How to fix + +Declare the loop variable as a `const` reference instead of copying each element, the same as for +[iterateByValue.md](iterateByValue.md). + +## Related checkers + +- [iterateByValue.md](iterateByValue.md) - the same idea, for a loop that isn't inside a callback + function. +- [passedByValueCallback.md](passedByValueCallback.md) - the same idea, for a callback's parameter + instead of a loop variable. diff --git a/man/checkers/iterators1.md b/man/checkers/iterators1.md new file mode 100644 index 00000000000..397b34f794f --- /dev/null +++ b/man/checkers/iterators1.md @@ -0,0 +1,48 @@ +# iterators1 + +**Message**: Same iterator is used with different containers 'l1' and 'l2'.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +An iterator that belongs to one container is passed to an `insert()`/`erase()`-style method of a +different container. + +## Motivation + +An iterator only makes sense in the context of the container it came from. Passing it to a different +container's method is meaningless and undefined behaviour - almost always a copy-paste mistake where +the wrong container variable was used. + +## How to fix + +Before: +```cpp +#include +void foo() { + std::list l1; + std::list l2; + std::list::iterator it = l1.begin(); + l2.insert(it, 0); // <- iterators1: 'it' belongs to l1, not l2 +} +``` + +After: +```cpp +#include +void foo() { + std::list l1; + std::list::iterator it = l1.begin(); + l1.insert(it, 0); +} +``` + +## Related checkers + +- [mismatchingContainerIterator.md](mismatchingContainerIterator.md) - the same code shape is also + caught by this related, differently-worded check; both messages can appear together on the same + line. +- [mismatchingContainers.md](mismatchingContainers.md) - the equivalent mistake when the mismatched + iterators are compared (`!=`/`==`) rather than passed to a container method. diff --git a/man/checkers/iterators3.md b/man/checkers/iterators3.md new file mode 100644 index 00000000000..d37a7b9cdcc --- /dev/null +++ b/man/checkers/iterators3.md @@ -0,0 +1,46 @@ +# iterators3 + +**Message**: Same iterator is used with containers 'l1' that are temporaries or defined in different scopes.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +An iterator is compared against, or used with, a container of the same name that's actually a +different variable - a temporary, or one declared in a different, unrelated scope that happens to +shadow the container the iterator really belongs to. + +## Motivation + +Two variables sharing a name in different scopes are still two entirely different objects. An iterator +taken from one of them has no valid relationship to the other, even though the code reads as if it does +because the name is identical - comparing or using it against the wrong one is undefined behaviour. + +## How to fix + +Before: +```cpp +#include +std::vector f(); +bool foo() { + return f().begin() != f().end(); // <- iterators3: each call to f() returns a different temporary +} +``` + +After: call the function once and compare against the same instance. +```cpp +#include +std::vector f(); +bool foo() { + std::vector v = f(); + return v.begin() != v.end(); +} +``` + +## Related checkers + +- [iterators1.md](iterators1.md) - the same idea, but for two containers that are genuinely + different variables (not just same-named ones in different scopes). +- [mismatchingContainerIterator.md](mismatchingContainerIterator.md) - the same idea, for passing the + iterator into another container's method instead of comparing it. diff --git a/man/checkers/knownArgument.md b/man/checkers/knownArgument.md new file mode 100644 index 00000000000..23eca5dbdfe --- /dev/null +++ b/man/checkers/knownArgument.md @@ -0,0 +1,56 @@ +# knownArgument and knownArgumentHiddenVariableExpression + +**Message**: Argument 'x-x' to function 'func' is always 0. It does not matter what value 'x' has.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +- `knownArgument`: a function/constructor argument is an expression whose value cppcheck can already + compute at compile time, regardless of what a variable inside it holds - so passing that variable in + was pointless. +- `knownArgumentHiddenVariableExpression`: same idea, but specifically when a constant part of the + expression (`&& false`, `* 0`, `|| true`) completely masks a variable that looks like it should have + mattered - a common sign of a stray/leftover condition. + +## Motivation + +When an argument's value doesn't actually depend on a variable that's visibly part of its expression, +that's either dead code (the variable can be removed) or a sign the expression doesn't do what its +author expected - especially in the "hidden variable" case, where a constant operator silently cancels +out something that looks meaningful. + +## How to fix + +Before: +```cpp +void g(int); +void f(int x) { + g((x & 0x01) >> 7); // <- always 0, no matter what 'x' is +} +``` + +After: +```cpp +void g(int); +void f(int x) { + g(x >> 7); +} +``` + +Before: +```cpp +void dostuff(int); +void f(int x) { + dostuff(x * 0); // <- 'x' never actually matters here +} +``` + +After: +```cpp +void dostuff(int); +void f(int x) { + dostuff(x); +} +``` diff --git a/man/checkers/knownConditionTrueFalse.md b/man/checkers/knownConditionTrueFalse.md new file mode 100644 index 00000000000..51b652c7b90 --- /dev/null +++ b/man/checkers/knownConditionTrueFalse.md @@ -0,0 +1,72 @@ +# knownConditionTrueFalse + +**Message**: Condition 'x==5' is always true
+**Category**: Code cleanup
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A condition is always true or always false. + +Note: a warning is not written for obvious cases like `if (false)`. + +If a condition is always true then technically the condition is redundant. It +can be removed so that the conditional code will be unconditionally executed. +This reduces complexity. + +If a condition is always false then the conditional code is unreachable and can +be removed. + +It is however also possible that the intended check never actually happens and +a real bug (a wrong comparison, a typo'd variable, a value that was supposed to +vary but doesn't) slips through unnoticed. + +## Motivation + +The condition may be invariant (always true or always false) by mistake, +otherwise it is possible to cleanup redundant code to reduce complexity. + +## How to fix + +Before (condition is always true): +```cpp +void f() { + int x = 5; + if (x == 5) { // <- always true + dostuff(); + } +} +``` + +After: The condition is technically redundant, this code is logically the same. +```cpp +void f() { + dostuff(); +} +``` + +Before (condition is always false): +```cpp +void f() { + int x = 5; + if (x < 3) { // <- always false + dostuff(); + } +} +``` + +After: The conditional code is unreachable, this code is logically the same. +```cpp +void f() { +} +``` + +## Related checkers + +- [assignIfError.md](assignIfError.md) - the same idea, specifically for a value just narrowed down by + a bitmask assignment. +- [moduloAlwaysTrueFalse.md](moduloAlwaysTrueFalse.md), [compareValueOutOfTypeRangeError.md](compareValueOutOfTypeRangeError.md) - + the same idea, for a `%` result or a type's value range instead of a general known value. +- [duplicateConditionalAssign.md](duplicateConditionalAssign.md) - a related, narrower case: an + assignment that repeats a value a condition already guarantees. diff --git a/man/checkers/knownEmptyContainer.md b/man/checkers/knownEmptyContainer.md new file mode 100644 index 00000000000..4e2d20c952a --- /dev/null +++ b/man/checkers/knownEmptyContainer.md @@ -0,0 +1,35 @@ +# knownEmptyContainer + +**Message**: Iterating over container 'v' that is always empty.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +A container or iterator that cppcheck knows is always empty is iterated over or otherwise used as if +it might contain elements. + +## Motivation + +Code that iterates over a container cppcheck can prove is empty at that point is dead code - the loop +body never runs, which is usually not what the author intended. + +## How to fix + +Before: +```cpp +#include +void f() { + std::vector v; + for (auto x : v) {} // <- knownEmptyContainer: 'v' is provably empty here +} +``` + +After: +```cpp +#include +void f(std::vector v) { + for (auto x : v) {} +} +``` diff --git a/man/checkers/knownPointerToBool.md b/man/checkers/knownPointerToBool.md new file mode 100644 index 00000000000..9e5cb7f68df --- /dev/null +++ b/man/checkers/knownPointerToBool.md @@ -0,0 +1,37 @@ +# knownPointerToBool + +**Message**: Pointer expression 'p' converted to bool is always true.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A pointer already known to be non-null (its address was just taken, or it was just checked) is +converted to `bool` - the result can only ever be `true`. + +## Motivation + +Converting a pointer that's already known to be non-null to `bool` doesn't test anything - it always +evaluates to `true`, so the code reads as if it's checking something that it isn't. + +## How to fix + +Before: +```cpp +void g(bool); +void f() { + int i = 5; + int* p = &i; + g(p); // <- 'p' can't be null here, so this is always true +} +``` + +After: +```cpp +void g(bool); +void f(int* p) { + g(p); +} +``` + diff --git a/man/checkers/leakNoVarFunctionCall.md b/man/checkers/leakNoVarFunctionCall.md new file mode 100644 index 00000000000..0753e19a0d0 --- /dev/null +++ b/man/checkers/leakNoVarFunctionCall.md @@ -0,0 +1,44 @@ +# leakNoVarFunctionCall + +**Message**: Allocation with malloc, strcpy doesn't release it.
+**Category**: Correctness
+**Severity**: Error
+**Language**: C/C++ + +## Description + +The result of an allocating call is passed directly into another function that is not known to store, +free, or otherwise take ownership of it (`strcpy(dst, strdup(src))`) - once that outer call returns, +nothing holds onto the allocated memory. + +## Motivation + +Passing an allocation straight into another call without ever storing it in a variable only works if +that outer call takes ownership (stores or frees the pointer itself). Most functions - like `strcpy()`, +which only reads through the pointer it's given - don't; once the statement finishes, the allocated +memory is unreachable and leaked. This is mainly reliable for calls to well-known library functions; +cppcheck does not reliably work out ownership for a user-defined function, even one whose body is +visible and plainly doesn't take ownership, so it stays quiet there rather than guess. + +## How to fix + +Before: +```cpp +void f() { + strcpy(a, strdup(p)); // <- strcpy() doesn't free its 2nd argument +} +``` + +After: +```cpp +void f() { + char* tmp = strdup(p); + strcpy(a, tmp); + free(tmp); +} +``` + +## Related checkers + +- [leakReturnValNotUsed.md](leakReturnValNotUsed.md) - the simpler case where the allocation's result + isn't passed anywhere at all, just discarded. diff --git a/man/checkers/leakReturnValNotUsed.md b/man/checkers/leakReturnValNotUsed.md new file mode 100644 index 00000000000..246d8656934 --- /dev/null +++ b/man/checkers/leakReturnValNotUsed.md @@ -0,0 +1,40 @@ +# leakReturnValNotUsed + +**Message**: Return value of allocation function 'malloc' is not stored.
+**Category**: Correctness
+**Severity**: Error
+**Language**: C/C++ + +## Description + +The result of an allocating call (`malloc(10);`, `new Foo;`) is discarded immediately - there was never +anywhere to store it, so it's an instant, guaranteed leak. + +## Motivation + +An allocating call's whole purpose is to hand back a pointer to the new memory/object - discarding that +return value immediately means the memory is allocated and then instantly unreachable, with no way for +anything to ever free it. Unlike most leaks, which depend on some later code path forgetting to free +something, this one is a leak the moment the line runs. + +## How to fix + +Before: +```cpp +void f() { + malloc(10); // <- return value discarded +} +``` + +After: +```cpp +void f() { + char* p = (char*)malloc(10); + free(p); +} +``` + +## Related checkers + +- [memleak.md](memleak.md) - the more general "allocated but never freed" check, for allocations that + are stored somewhere but still never freed. diff --git a/man/checkers/leakUnsafeArgAlloc.md b/man/checkers/leakUnsafeArgAlloc.md new file mode 100644 index 00000000000..fd3f9ba2049 --- /dev/null +++ b/man/checkers/leakUnsafeArgAlloc.md @@ -0,0 +1,42 @@ +# leakUnsafeArgAlloc + +**Message**: Unsafe allocation. If shared_ptr() throws, memory could be leaked. Use make_shared() instead.
+**Category**: Correctness
+**Severity**: Warning (inconclusive)
+**Language**: C++ + +## Description + +A call constructs a `shared_ptr`/`unique_ptr` around a `new` expression as one argument, while another +argument is a function call that might throw - if that other call throws before the smart pointer is +constructed, the raw `new` can leak (a classic pre-C++17 evaluation-order hazard). This is only reported +with `--inconclusive`, since cppcheck can't tell whether the other argument can actually throw. + +## Motivation + +Before C++17, the order in which a function call's arguments (and the work needed to construct them) +run relative to each other was unspecified. If `new int(42)` runs, and then the *other* argument's +function call throws before `shared_ptr(...)` gets to wrap it, the raw pointer from `new` is never +adopted by anything and leaks when the exception propagates. `make_shared`/`make_unique` avoid the +hazard entirely, since there's no separate raw pointer that can be orphaned this way. + +## How to fix + +Before: +```cpp +void g(); +void f(shared_ptr p, int x); +void x() { + f(shared_ptr(new int(42)), g()); // <- leaks if g() throws +} +``` + +After: +```cpp +void g(); +void f(shared_ptr p, int x); +void x() { + f(make_shared(42), g()); +} +``` + diff --git a/man/checkers/literalWithCharPtrCompare.md b/man/checkers/literalWithCharPtrCompare.md new file mode 100644 index 00000000000..ff7e724b518 --- /dev/null +++ b/man/checkers/literalWithCharPtrCompare.md @@ -0,0 +1,39 @@ +# literalWithCharPtrCompare and charLiteralWithCharPtrCompare + +**Message**: String literal compared with variable 'c'. Did you intend to use strcmp() instead?
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A `char*`/`wchar_t*` variable is compared directly against a string or character literal with `==`/`!=` +- this compares the pointer itself (its address), not the string's contents. + +## Motivation + +`c == "x"` compiles fine in C/C++ but almost never does what it looks like: it asks whether `c` happens +to point at the exact same memory as the literal `"x"`, not whether the characters match. This is one of +the most common C/C++ beginner mistakes, and the compiler gives no warning of its own. + +## How to fix + +Before: +```cpp +bool foo(char* c) { + return c == "x"; // <- compares addresses, not contents +} +``` + +After: +```cpp +#include +bool foo(char* c) { + return strcmp(c, "x") == 0; +} +``` + +## Related checkers + +- [staticStringCompare.md](staticStringCompare.md) - a related string-comparison mistake: comparing two + string literals (or two identical variables) with a string-comparison function. diff --git a/man/checkers/localMutex.md b/man/checkers/localMutex.md new file mode 100644 index 00000000000..e7b50cc0092 --- /dev/null +++ b/man/checkers/localMutex.md @@ -0,0 +1,44 @@ +# localMutex + +**Message**: The lock is ineffective because the mutex is locked at the same scope as the mutex itself.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ + +## Description + +A `std::mutex` and the `lock_guard`/`.lock()` call that locks it are declared in the very same scope - +the mutex is a fresh, unshared object every time that scope is entered, so nothing else can ever be +contending for it and the lock has no effect. + +## Motivation + +A mutex only protects shared data if the same mutex instance is reachable from every place that might +access that data concurrently. A mutex that's a local variable, locked in the same function it's +declared in, is a brand-new object on every call - no other thread (or even another call to the same +function) can ever see or contend for that exact instance, so the "protection" is illusory. + +## How to fix + +Before: +```cpp +#include +void f() { + std::mutex m; + std::lock_guard g(m); // <- 'm' is a fresh, private mutex every call +} +``` + +After: +```cpp +#include +std::mutex m; +void f() { + std::lock_guard g(m); +} +``` + +## Related checkers + +- [globalLockGuard.md](globalLockGuard.md) - the opposite mistake: a lock guard given static/global + storage, so it never releases the lock. diff --git a/man/checkers/mallocOnClassError.md b/man/checkers/mallocOnClassError.md new file mode 100644 index 00000000000..cc3cb0e0a5c --- /dev/null +++ b/man/checkers/mallocOnClassError.md @@ -0,0 +1,62 @@ +# mallocOnClassError and mallocOnClassWarning + +**Message**: Memory for class instance allocated with malloc(), but class contains a virtual function.
+**Category**: Undefined Behaviour
+**Severity**: Error/Warning
+**Language**: C++ + +## Description + +A C++ class/struct instance is allocated with `malloc()`/`calloc()`/`realloc()` instead of `new` - the +constructor never runs. + +- `mallocOnClassError`: the class clearly needs its constructor to run (it has a virtual function, so + its vtable pointer is never set up, or a non-trivial member type that itself needs construction). +- `mallocOnClassWarning`: a constructor exists but the class might still happen to work out by luck + (for example if the constructor only sets members to values that also happen to match zeroed memory). + +## Motivation + +`malloc()` only allocates raw memory - it never calls a C++ constructor, so no object of that class type +actually exists in that memory yet. The `malloc()` call itself doesn't crash or misbehave; the risk is +in what happens next - calling any member function through the pointer (especially a virtual one, which +reads a vtable pointer that was never set up), or letting a destructor run on it, is undefined behaviour, +because the standard's rules for using an object of a type only apply once that object's lifetime has +actually begun via a constructor call. + +## How to fix + +Before: +```cpp +#include +struct C { virtual void bar(); }; +void foo(C*& p) { + p = malloc(sizeof(C)); // <- mallocOnClassError: no constructor/vtable set up +} +``` + +After: +```cpp +struct C { virtual void bar(); }; +void foo(C*& p) { + p = new C(); +} +``` + +Before: +```cpp +#include +class C { public: C() {} }; +void foo(C*& p) { + p = malloc(sizeof(C)); // <- mallocOnClassWarning: C()'s body never runs +} +``` + +After: +```cpp +class C { public: C() {} }; +void foo(C*& p) { + p = new C(); +} +``` + diff --git a/man/checkers/memleak.md b/man/checkers/memleak.md new file mode 100644 index 00000000000..2fde8a78d43 --- /dev/null +++ b/man/checkers/memleak.md @@ -0,0 +1,62 @@ +# memleak and resourceLeak + +**Message**: Memory leak: p
+**Category**: Correctness
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A locally-allocated variable is allocated (`malloc`/`new`/`fopen`/and other functions described the +same way in the library configuration), but no path frees/releases it before it goes out of scope, is +reassigned, or the function returns. + +- `memleak`: the allocation is memory (`malloc`, `new`, ...). +- `resourceLeak`: the allocation is some other kind of resource (a file handle from `fopen`, ...). + +## Motivation + +A memory or resource leak wastes memory/handles until the process exits, which can degrade or crash +long-running programs. This is easy for a human reviewer to miss, especially across multiple `if`/`else` +branches, but mechanical enough for cppcheck to track precisely in straightforward code. cppcheck only +tracks an allocation through straight-line code: once a loop or `goto` appears anywhere in the function, +it stops checking that function entirely rather than risk a wrong guess, so its silence on a function +with a loop in it isn't proof the code has no leak. + +## How to fix + +Before: +```cpp +void f() { + char *p = malloc(10); // <- memleak: never freed +} +``` + +After: +```cpp +void f() { + char *p = malloc(10); + free(p); +} +``` + +## False positives to be aware of + +- **A false positive is possible when a variable is freed only through a reference alias to it.** The + checker doesn't always recognize that freeing the alias also frees the original variable, and can + report a leak that doesn't actually exist: + ```cpp + void f() { + char *p; + char *&ref = p; + p = malloc(10); + free(ref); // frees 'p', but is reported as "Memory leak: p" anyway + } + ``` + +## Related checkers + +- [mismatchAllocDealloc.md](mismatchAllocDealloc.md) - for when the allocation *is* freed, but with the + wrong deallocation function. +- [doubleFree.md](doubleFree.md) / [deallocuse.md](deallocuse.md) / [deallocret.md](deallocret.md) - the + related mistakes of freeing something twice, or using it afterwards, once it has been freed. diff --git a/man/checkers/memleakOnRealloc.md b/man/checkers/memleakOnRealloc.md new file mode 100644 index 00000000000..ba99fab784f --- /dev/null +++ b/man/checkers/memleakOnRealloc.md @@ -0,0 +1,45 @@ +# memleakOnRealloc + +**Message**: Common realloc mistake: 'a' nulled but not freed upon failure
+**Category**: Correctness
+**Severity**: Error
+**Language**: C/C++ + +## Description + +The classic `p = realloc(p, newSize);` mistake - if `realloc()` fails, it returns `NULL` without +freeing the original block, but this code has just overwritten `p` with that `NULL`, losing the only +pointer to the original (still allocated) memory. + +## Motivation + +`realloc()` only frees the original block on success; on failure the original block is untouched and +still needs freeing, but the return value is `NULL`. Assigning the return value straight back onto the +only variable that pointed to the original block throws that pointer away too, permanently leaking the +original allocation whenever `realloc()` fails. + +## How to fix + +Before: +```cpp +void foo() { + char *a = (char *)malloc(10); + a = (char *)realloc(a, 100); // <- original block lost if this fails + free(a); +} +``` + +After: +```cpp +void foo() { + char *a = (char *)malloc(10); + char *tmp = (char *)realloc(a, 100); + if (tmp) + a = tmp; + free(a); +} +``` + +## Related checkers + +- [memleak.md](memleak.md) - the more general "allocated but never freed" check this one complements. diff --git a/man/checkers/memsetClass.md b/man/checkers/memsetClass.md new file mode 100644 index 00000000000..a41159d00e6 --- /dev/null +++ b/man/checkers/memsetClass.md @@ -0,0 +1,53 @@ +# memsetClass + +**Message**: Using 'memset' on class that contains a 'std::string'.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +`memset()`/`memcpy()`/`memmove()` is used on an object of a type that isn't POD (for example it +contains a `std::string`) - the constructor, destructor, and copy semantics that type relies on are all +bypassed. + +## Motivation + +Raw memory functions like `memset()` don't know anything about C++ object semantics - they just +overwrite bytes. Using one on an object that has its own constructor/destructor/internal invariants +(like `std::string`'s internal pointer/length bookkeeping) corrupts that object instead of resetting it. + +## How to fix + +Before: +```cpp +#include +#include +class Fred { +public: + std::string b; +}; +void f() { + Fred fred; + memset(&fred, 0, sizeof(Fred)); // <- memsetClass: bypasses std::string's own management +} +``` + +After: +```cpp +#include +class Fred { +public: + std::string b; +}; +void f() { + Fred fred; +} +``` + +## Related checkers + +- [memsetClassFloat.md](memsetClassFloat.md) - the same underlying mistake, specifically for a type + containing a `float`/`double` member. +- [memsetClassReference.md](memsetClassReference.md) - the same underlying mistake, specifically for a + type containing a reference member. diff --git a/man/checkers/memsetClassFloat.md b/man/checkers/memsetClassFloat.md new file mode 100644 index 00000000000..2fb1c7b8e08 --- /dev/null +++ b/man/checkers/memsetClassFloat.md @@ -0,0 +1,54 @@ +# memsetClassFloat + +**Message**: Using 'memset' on struct which contains a floating point number.
+**Category**: Portability
+**Severity**: Warning
+**Language**: C++ + +## Description + +`memset()`/`memcpy()`/`memmove()` is used on a type containing a `float`/`double` member - whether an +all-zero-bytes pattern actually means `0.0` on the target platform isn't something the C++ standard +guarantees. + +## Motivation + +Zeroing a struct's bytes to reset a floating-point member to `0.0` relies on the platform's +floating-point representation matching all-zero-bytes with the value zero. This is true in practice on +essentially all mainstream hardware (IEEE 754), but it's not guaranteed by the language, so it's a +portability risk rather than a certainty. + +## How to fix + +Before: +```cpp +#include +typedef float realnum; +struct multilevel_data { + realnum *GammaInv; + realnum data[1]; +}; +void f() { + multilevel_data d; + memset(&d, 0, sizeof(multilevel_data)); // <- memsetClassFloat +} +``` + +After: +```cpp +typedef float realnum; +struct multilevel_data { + realnum *GammaInv; + realnum data[1]; +}; +void f() { + multilevel_data d = {}; +} +``` + +## Related checkers + +- [memsetClass.md](memsetClass.md) - the same underlying mistake, specifically for a type that isn't + POD at all (for example it contains a `std::string`). +- [memsetClassReference.md](memsetClassReference.md) - the same underlying mistake, specifically for a + type containing a reference member. diff --git a/man/checkers/memsetClassReference.md b/man/checkers/memsetClassReference.md new file mode 100644 index 00000000000..e9514686462 --- /dev/null +++ b/man/checkers/memsetClassReference.md @@ -0,0 +1,54 @@ +# memsetClassReference + +**Message**: Using 'memset' on class that contains a reference.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +`memset()`/`memcpy()`/`memmove()` is used on a type containing a reference member - a reference can't +be reseated after it's bound, so overwriting its bytes doesn't do what an assignment would. + +## Motivation + +A reference member is bound once, at construction, and can never be made to refer to something else. +Overwriting its bytes with `memset()` doesn't rebind it - it corrupts whatever internal representation +the compiler uses for references, which is undefined behaviour. + +## How to fix + +Before: +```cpp +#include +#include +class A { +public: + std::string &s; + A(std::string &str) : s(str) {} +}; +void f(std::string &str) { + A a(str); + memset(&a, 0, sizeof(a)); // <- memsetClassReference: can't overwrite a bound reference like this +} +``` + +After: +```cpp +#include +class A { +public: + std::string &s; + A(std::string &str) : s(str) {} +}; +void f(std::string &str) { + A a(str); +} +``` + +## Related checkers + +- [memsetClass.md](memsetClass.md) - the same underlying mistake, specifically for a type that isn't + POD at all (for example it contains a `std::string`). +- [memsetClassFloat.md](memsetClassFloat.md) - the same underlying mistake, specifically for a type + containing a `float`/`double` member. diff --git a/man/checkers/memsetFloat.md b/man/checkers/memsetFloat.md new file mode 100644 index 00000000000..4433bcb7195 --- /dev/null +++ b/man/checkers/memsetFloat.md @@ -0,0 +1,58 @@ +# memsetFloat and memsetValueOutOfRange + +**Message**: The 2nd memset() argument 'x' is a float, its representation is implementation defined.
+**Category**: Correctness
+**Severity**: Portability/Warning
+**Language**: C/C++ + +## Description + +`memset()`'s fill value (2nd argument): + +- `memsetFloat`: is a `float`, whose byte representation is implementation-defined - the actual bytes + written will vary across platforms. +- `memsetValueOutOfRange`: is a literal integer that doesn't fit in an `unsigned char` - `memset()` + only ever writes the `unsigned char` conversion of this value, so anything outside `0..255` (or the + platform's signed-char range) doesn't mean what it looks like. + +## Motivation + +`memset()` fills memory byte-by-byte using the value converted to `unsigned char`. A `float` argument +has no portable byte-for-byte meaning in that context, and an integer literal outside a single byte's +range is silently truncated - both make the call's actual effect different from what the source code +suggests. + +## How to fix + +Before: +```cpp +void f(void* p, size_t n) { + memset(p, 1.0f, n); // <- float, implementation-defined byte pattern +} +``` + +After: +```cpp +void f(void* p, size_t n) { + memset(p, 0, n); +} +``` + +Before: +```cpp +void f(void* p, size_t n) { + memset(p, 300, n); // <- 300 doesn't fit in an unsigned char +} +``` + +After: +```cpp +void f(void* p, size_t n) { + memset(p, 0xff, n); +} +``` + +## Related checkers + +- [memsetZeroBytes.md](memsetZeroBytes.md) - a different `memset()` misuse, about the length (3rd) + argument being zero rather than the fill value. diff --git a/man/checkers/memsetZeroBytes.md b/man/checkers/memsetZeroBytes.md new file mode 100644 index 00000000000..d6ecb11d80e --- /dev/null +++ b/man/checkers/memsetZeroBytes.md @@ -0,0 +1,37 @@ +# memsetZeroBytes + +**Message**: memset() called to fill 0 bytes.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +`memset(ptr, value, 0)` - the 2nd and 3rd arguments look swapped, since filling 0 bytes has no effect. + +## Motivation + +`memset()`'s signature is `memset(void *ptr, int value, size_t num)` - a literal `0` as the last +argument means the call does nothing at all, which is almost always a sign the fill value and the +length were written in the wrong order. + +## How to fix + +Before: +```cpp +void f(void* p) { + memset(p, sizeof(p), 0); // <- value and length look swapped +} +``` + +After: +```cpp +void f(void* p, size_t n) { + memset(p, 0, n); +} +``` + +## Related checkers + +- [memsetFloat.md](memsetFloat.md) - other `memset()` misuses about the fill-value (2nd) argument + rather than the length. diff --git a/man/checkers/mismatchAllocDealloc.md b/man/checkers/mismatchAllocDealloc.md new file mode 100644 index 00000000000..4119fa1963e --- /dev/null +++ b/man/checkers/mismatchAllocDealloc.md @@ -0,0 +1,40 @@ +# mismatchAllocDealloc + +**Message**: Mismatching allocation and deallocation: f
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A locally-allocated variable is freed with a function that doesn't match how it was allocated (for +example `new[]` freed with `delete`, or `fopen` freed with `free`). + +## Motivation + +Freeing something with the wrong function is undefined behaviour: `new[]`/`delete` mismatches can +corrupt the heap, and freeing a `FILE*` with `free()` instead of `fclose()` skips flushing/closing the +underlying file descriptor. This is easy for a human reviewer to miss, but mechanical enough for +cppcheck to track precisely in straightforward code. + +## How to fix + +Before: +```cpp +void f() { + FILE *f = fopen(fname, mode); + free(f); // <- mismatchAllocDealloc: fopen() must be matched with fclose() +} +``` + +After: +```cpp +void f() { + FILE *f = fopen(fname, mode); + fclose(f); +} +``` + +## Related checkers + +- [memleak.md](memleak.md) - for the related, simpler case of an allocation that is never freed at all. diff --git a/man/checkers/mismatchingBitAnd.md b/man/checkers/mismatchingBitAnd.md new file mode 100644 index 00000000000..61cf0f780c7 --- /dev/null +++ b/man/checkers/mismatchingBitAnd.md @@ -0,0 +1,43 @@ +# mismatchingBitAnd + +**Message**: Mismatching bitmasks. Result is always 0 (X = Y & 0xf0; Z = X & 0x1; => Z=0).
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A variable is masked with `& 0xf0`, and the result is masked again later with a value that shares no +bits with the first mask - so the final result is always `0`. + +## Motivation + +Chaining two bitmasks that share no bits is either dead code (the second mask can be removed since the +result is always `0`) or a sign that one of the two masks is wrong - either way it's worth a second +look. + +## How to fix + +Before: +```cpp +void f(int x) { + int a = x & 0xf0; + int b = a & 0x1; // <- always 0, no bits are shared with 0xf0 + if (b) {} +} +``` + +After: +```cpp +void f(int x) { + int a = x & 0xf0; + int b = a & 0x10; + if (b) {} +} +``` + +## Related checkers + +- [badBitmaskCheck.md](badBitmaskCheck.md) - `|` used where `&` was probably meant. +- [comparisonError.md](comparisonError.md) - a bitwise expression compared against a constant it can + never produce. diff --git a/man/checkers/mismatchingContainerExpression.md b/man/checkers/mismatchingContainerExpression.md new file mode 100644 index 00000000000..0ff9c8f35e3 --- /dev/null +++ b/man/checkers/mismatchingContainerExpression.md @@ -0,0 +1,46 @@ +# mismatchingContainerExpression + +**Message**: Iterators to containers from different expressions 'f()' and 'g()' are used together.
+**Category**: Undefined Behaviour
+**Severity**: Warning
+**Language**: C++ + +## Description + +Two iterators come from two separate calls to same-looking expressions (for example `begin(f())` and +`end(g())`) that cppcheck can't prove refer to the same container. + +## Motivation + +When the container itself is the result of a function call rather than a plain variable, it's easy to +accidentally call two different functions (or the same function twice, if it returns a different +container each time) for what was meant to be a single container's begin/end pair. + +## How to fix + +Before: +```cpp +#include +#include +std::vector& f(); +std::vector& g(); +void foo() { + (void)std::find(begin(f()), end(g()), 0); // <- mismatchingContainerExpression +} +``` + +After: +```cpp +#include +#include +std::vector& f(); +void foo() { + (void)std::find(begin(f()), end(f()), 0); +} +``` + +## Related checkers + +- [mismatchingContainers.md](mismatchingContainers.md) - the stronger version of this check, for when + cppcheck can tell the two iterators definitely belong to different containers (rather than merely + being unable to prove they're the same one). diff --git a/man/checkers/mismatchingContainerIterator.md b/man/checkers/mismatchingContainerIterator.md new file mode 100644 index 00000000000..60c4a2e7f57 --- /dev/null +++ b/man/checkers/mismatchingContainerIterator.md @@ -0,0 +1,47 @@ +# mismatchingContainerIterator + +**Message**: Iterator 'it' referring to container 'l1' is used with container 'l2'.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +An iterator that belongs to one container is passed to an `insert()`/`erase()`-style method of a +different container. + +## Motivation + +An iterator only makes sense in the context of the container it came from. Passing it to a different +container's method is meaningless and undefined behaviour - almost always a copy-paste mistake where +the wrong container variable was used. + +## How to fix + +Before: +```cpp +#include +void foo() { + std::list l1; + std::list l2; + std::list::iterator it = l1.begin(); + l2.insert(it, 0); // <- mismatchingContainerIterator: 'it' belongs to l1, not l2 +} +``` + +After: +```cpp +#include +void foo() { + std::list l1; + std::list::iterator it = l1.begin(); + l1.insert(it, 0); +} +``` + +## Related checkers + +- [iterators1.md](iterators1.md) - the same code shape is also caught by this related, differently + worded check; both messages can appear together on the same line. +- [mismatchingContainers.md](mismatchingContainers.md) - the equivalent mistake when the mismatched + iterators are compared (`!=`/`==`) rather than passed to a container method. diff --git a/man/checkers/mismatchingContainers.md b/man/checkers/mismatchingContainers.md new file mode 100644 index 00000000000..163c8a1c8c8 --- /dev/null +++ b/man/checkers/mismatchingContainers.md @@ -0,0 +1,47 @@ +# mismatchingContainers + +**Message**: Iterators of different containers 'l1' and 'l2' are used together.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +An iterator that belongs to one container is compared (with `!=`/`==`, or via a relational operator) +against an iterator from a different container - most often seen as the loop condition of a `for` loop +that begins with one container's iterator and ends with another's. + +## Motivation + +Comparing iterators from two different containers is meaningless - there's no relationship between +their positions - and is almost always a copy-paste mistake, typically a loop's end condition that +still refers to the wrong container. + +## How to fix + +Before: +```cpp +#include +void f() { + std::list l1; + std::list l2; + for (std::list::iterator it = l1.begin(); it != l2.end(); ++it) { } // <- mismatchingContainers +} +``` + +After: +```cpp +#include +void f() { + std::list l1; + for (std::list::iterator it = l1.begin(); it != l1.end(); ++it) { } +} +``` + +## Related checkers + +- [iterators1.md](iterators1.md) / [mismatchingContainerIterator.md](mismatchingContainerIterator.md) - + the equivalent mistake when the mismatched iterator is passed to a container method instead of + compared. +- [mismatchingContainerExpression.md](mismatchingContainerExpression.md) - a related, weaker check for + when cppcheck can't prove two iterator-producing expressions refer to the same container at all. diff --git a/man/checkers/missingMemberCopy.md b/man/checkers/missingMemberCopy.md new file mode 100644 index 00000000000..a30223ebd71 --- /dev/null +++ b/man/checkers/missingMemberCopy.md @@ -0,0 +1,46 @@ +# missingMemberCopy + +**Message**: Member variable 'classname::varname' is not initialized in the copy constructor.
+**Category**: Correctness
+**Severity**: Warning (Inconclusive)
+**Language**: C++ + +## Description + +A copy or move constructor is defined (with a body), but one particular member is never assigned +anywhere in it - everything else about the class suggests it should have been copied. + +## Motivation + +A hand-written copy/move constructor that forgets one member produces objects whose copies silently +diverge from the original in that one field - a subtle bug, especially in a class with many members, +since the constructor still compiles and mostly "looks right." + +## How to fix + +Copy (or move) the missing member too. + +Before: +```cpp +struct S { + int i{}; + S() = default; + S(const S& s) {} // <- 'i' isn't copied +}; +``` + +After: +```cpp +struct S { + int i{}; + S() = default; + S(const S& s) : i(s.i) {} +}; +``` + +## Related checkers + +- [operatorEqVarError.md](operatorEqVarError.md) - the same idea, for a hand-written `operator=` instead + of a copy/move constructor. +- [uninitMemberVar.md](uninitMemberVar.md) - the related family of checks for a member that's never + initialized by a regular constructor at all. diff --git a/man/checkers/missingOverride.md b/man/checkers/missingOverride.md new file mode 100644 index 00000000000..5ed810334d5 --- /dev/null +++ b/man/checkers/missingOverride.md @@ -0,0 +1,37 @@ +# missingOverride + +**Message**: The function 'f' overrides a function in a base class but is not marked with a 'override' specifier.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +A derived class's function has the same signature as a virtual function in its base class, but isn't +marked `override`. + +## Motivation + +Without `override`, if a later edit to the base class's signature accidentally stops a derived function +from being an override (a parameter type change, a typo in the name, ...), the derived function quietly +becomes an unrelated new function instead - and nothing warns about it, since it's valid C++ either way. +Marking overrides explicitly turns that silent mismatch into a compile error. + +## How to fix + +Before: +```cpp +class Base { virtual void f(); }; +class Derived : Base { virtual void f(); }; // <- missingOverride: no 'override' +``` + +After: +```cpp +class Base { virtual void f(); }; +class Derived : Base { void f() override; }; +``` + +## Related checkers + +- [uselessOverride.md](uselessOverride.md) - for when a function *is* correctly overriding a base one, + but the override doesn't actually change any behaviour. diff --git a/man/checkers/missingPercentCharacter.md b/man/checkers/missingPercentCharacter.md new file mode 100644 index 00000000000..e89362e67d8 --- /dev/null +++ b/man/checkers/missingPercentCharacter.md @@ -0,0 +1,36 @@ +# missingPercentCharacter + +**Message**: Missing percent character in Token::Match() pattern: "%type"
+**Category**: Code Quality
+**Severity**: Error
+**Language**: C++ (cppcheck's own source code only) + +## Description + +A `%something` placeholder is missing its closing `%`. + +This is not a general-purpose checker for arbitrary C/C++ programs - see +[simplePatternError.md](simplePatternError.md) for the shared background on this internal, +cppcheck-source-only checker. + +## Motivation + +Without the closing `%`, the pattern-matching engine doesn't recognize the text as a placeholder at +all, and instead tries to match it as literal characters - silently breaking the match the pattern was +written to perform. + +## How to fix + +Before: +```cpp +Token::Match(tok, "%type"); // <- missing closing '%' +``` + +After: +```cpp +Token::Match(tok, "%type%"); +``` + +## Related checkers + +- [unknownPattern.md](unknownPattern.md) - a `%something%` placeholder that's correctly closed but isn't a recognized name. diff --git a/man/checkers/missingReturn.md b/man/checkers/missingReturn.md new file mode 100644 index 00000000000..cfaa7fde670 --- /dev/null +++ b/man/checkers/missingReturn.md @@ -0,0 +1,42 @@ +# missingReturn + +**Message**: Found an exit path from function with non-void return type that has missing return statement
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A function with a non-`void` return type has a path that reaches the end of the function (or a +`case`/`if` branch) without returning a value. + +## Motivation + +Falling off the end of a value-returning function without a `return` is undefined behaviour the moment +that code path is actually taken - the standard says so unconditionally, regardless of whether the +caller ever reads the returned value. Since the checker flags the mere existence of such a path, whether +this is reached in practice depends on the arguments/state a real call ends up using; when it is +reached, the caller receives whatever happened to be in the return-value register/location, which is +unpredictable and can differ between builds, platforms, or optimization levels. + +## How to fix + +Before: +```cpp +int f(int x) { + if (x) { + return 1; + } +} // <- no return if 'x' is 0 +``` + +After: +```cpp +int f(int x) { + if (x) { + return 1; + } + return 0; +} +``` + diff --git a/man/checkers/moduloAlwaysTrueFalse.md b/man/checkers/moduloAlwaysTrueFalse.md new file mode 100644 index 00000000000..b82103616d6 --- /dev/null +++ b/man/checkers/moduloAlwaysTrueFalse.md @@ -0,0 +1,36 @@ +# moduloAlwaysTrueFalse + +**Message**: Comparison of modulo result is predetermined, because it is always less than 5.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A `%` result is compared against a constant outside the range that modulo operation could ever produce. + +## Motivation + +`x % 5` can only ever be `0..4` - comparing it against `5` or anything larger makes the comparison +predetermined, which usually means the modulo divisor or the compared-against constant is wrong. + +## How to fix + +Before: +```cpp +void f(int x) { + if (x % 5 == 5) {} // <- 'x % 5' is always 0..4 +} +``` + +After: +```cpp +void f(int x) { + if (x % 5 == 0) {} +} +``` + +## Related checkers + +- [knownConditionTrueFalse.md](knownConditionTrueFalse.md) - the more general check for a condition + whose truth value cppcheck already knows in advance. diff --git a/man/checkers/moduloofone.md b/man/checkers/moduloofone.md new file mode 100644 index 00000000000..6b3d3ee6db8 --- /dev/null +++ b/man/checkers/moduloofone.md @@ -0,0 +1,33 @@ +# moduloofone + +**Message**: Modulo of one is always equal to zero
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +An expression is reduced modulo `1`, which is always `0` no matter what the other operand is. + +## Motivation + +`x % 1` is always `0` for any integer `x` - the computation is pointless and almost always signals a +typo (a `1` that should have been some other number, or a variable that should have been used instead +of a literal `1`). + +## How to fix + +Before: +```cpp +void f(unsigned int x) { + int y = x % 1; // <- always 0 +} +``` + +After: +```cpp +void f(unsigned int x) { + int y = x % 2; + (void)y; +} +``` diff --git a/man/checkers/multiCondition.md b/man/checkers/multiCondition.md new file mode 100644 index 00000000000..1d68492c290 --- /dev/null +++ b/man/checkers/multiCondition.md @@ -0,0 +1,58 @@ +# multiCondition + +**Message**: Expression is always false because 'else if' condition matches previous condition at line 2.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +An `else if` condition is the same as (dead code), or the exact opposite of (always true), the +condition already tested by the `if` before it. + +## Motivation + +By the time an `else if` is reached, the `if` before it is already known to be false. If the `else if` +condition is identical to that `if`, it can never be true either (dead code); if it's the exact +opposite, it's guaranteed to be true (redundant, since `else` alone would do the same thing). + +## How to fix + +Before: +```cpp +void f(int x) { + if (x == 1) {} + else if (x == 1) {} // <- can never be reached +} +``` + +After: +```cpp +void f(int x) { + if (x == 1) {} + else if (x == 2) {} +} +``` + +Before: +```cpp +void f(int x) { + if (x == 1) {} + else if (x != 1) {} // <- always true, we're already past the 'x == 1' branch +} +``` + +After: +```cpp +void f(int x) { + if (x == 1) {} + else {} +} +``` + +## Related checkers + +- [duplicateCondition.md](duplicateCondition.md) - the same idea, but for two consecutive plain `if` + statements rather than an `if`/`else if` chain. +- [oppositeInnerCondition.md](oppositeInnerCondition.md), [identicalInnerCondition.md](identicalInnerCondition.md) - + the same idea again, but for an `if` nested directly inside another `if`. diff --git a/man/checkers/multiplySizeof.md b/man/checkers/multiplySizeof.md new file mode 100644 index 00000000000..8b27e5f16e9 --- /dev/null +++ b/man/checkers/multiplySizeof.md @@ -0,0 +1,60 @@ +# multiplySizeof and divideSizeof + +**Message**: Multiplying sizeof() with sizeof() indicates a logic error.
+**Category**: Correctness
+**Severity**: Warning (inconclusive)
+**Language**: C/C++ + +## Description + +- `multiplySizeof`: `sizeof(a) * sizeof(b)` - multiplying two `sizeof` results together almost never + makes sense dimensionally (the result would be "bytes squared"). +- `divideSizeof`: dividing the result of `sizeof()` on a pointer type by another `sizeof()` - since + `sizeof(pointer)` is the size of the pointer itself, not of the data it points to, this computes a + meaningless ratio. + +Both require `--inconclusive` to be enabled. + +## Motivation + +Both patterns are dimensionally suspicious: multiplying two byte-counts together, or dividing a +pointer's fixed size by an unrelated element size, essentially never produces the number a program +actually needs. They're the kind of typo (an extra `sizeof`, or a `sizeof` applied to the wrong +variable) that's easy to introduce and easy to miss, since the code still compiles. + +## How to fix + +Before: +```cpp +void f() { + int a = 1, b = 1; + int s = sizeof(a) * sizeof(b); // <- multiplying two sizeof() results +} +``` + +After: +```cpp +void f() { + int a = 1, b = 1; + int s = sizeof(a) * b; // multiply by the count, not another sizeof() +} +``` + +Before: +```cpp +void f(int *p) { + int n = 100 / sizeof(p); // <- dividing by the pointer's own size +} +``` + +After: +```cpp +void f(int *p) { + int n = 100 / sizeof(*p); +} +``` + +## Related checkers + +- [pointerSize.md](pointerSize.md) - the same "size of pointer, not of data" mistake, specifically + when used as (or to compute) an argument to `malloc`/`memcpy`/`memset`-family functions. diff --git a/man/checkers/nanInArithmeticExpression.md b/man/checkers/nanInArithmeticExpression.md new file mode 100644 index 00000000000..5f8134be309 --- /dev/null +++ b/man/checkers/nanInArithmeticExpression.md @@ -0,0 +1,42 @@ +# nanInArithmeticExpression + +**Message**: Using NaN/Inf in a computation.
+**Category**: Correctness
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A computation divides by `0.0` and immediately adds or subtracts another value from the result - the +addition/subtraction is pointless once the division has already produced `NaN`/`Inf`. + +## Motivation + +Once an expression is `NaN` or `Inf`, further arithmetic on it (other than a handful of operations +specifically meant to detect/handle that case) can't recover a meaningful value - so code that keeps +computing with it is either dead weight or a sign the `NaN`/`Inf` case wasn't actually intended. + +## How to fix + +Before: +```cpp +#include +void f() { + double x = 3.0 / 0.0 + 1.0; // <- pointless once the division already produced NaN/Inf + printf("%f", x); +} +``` + +After: +```cpp +#include +void f(double divisor) { + double x = 3.0 / divisor + 1.0; + printf("%f", x); +} +``` + +## Related checkers + +- [zerodiv.md](zerodiv.md) - integer division by zero, which is undefined behaviour rather than the + well-defined `NaN`/`Inf` result of floating-point division by zero. diff --git a/man/checkers/negativeArraySize.md b/man/checkers/negativeArraySize.md new file mode 100644 index 00000000000..97ca1b25336 --- /dev/null +++ b/man/checkers/negativeArraySize.md @@ -0,0 +1,60 @@ +# negativeArraySize and negativeMemoryAllocationSize + +**Message**: Declaration of array 'a' with negative size is undefined behaviour
+**Category**: Correctness/Undefined Behaviour
+**Severity**: Error/Warning
+**Language**: C/C++ + +## Description + +Something that determines the size of an array-like allocation is known to be negative: + +- `negativeArraySize`: a variable-length array is declared with a size cppcheck knows is negative. +- `negativeMemoryAllocationSize`: a `new[]` expression is given a size cppcheck knows is negative. + +## Motivation + +- `negativeArraySize`: a variable-length array's size, once converted to the unsigned type it's ultimately + represented as, becomes an enormous positive number - so instead of failing cleanly, the declaration + either crashes outright or succeeds with a wildly wrong, huge size. This is undefined behaviour. +- `negativeMemoryAllocationSize`: in C++11 and later, `new[]` is specifically required to detect this and + throw `std::bad_array_new_length` instead - so this specific case is a well-defined, catchable error + rather than undefined behaviour, but it's still a bug worth flagging: an uncaught exception terminates + the program, and any earlier C++ standard leaves the size negotiation undefined instead. + +## How to fix + +Before: +```cpp +void f() { + int n = -1; + int a[n]; // <- negativeArraySize +} +``` + +After: +```cpp +void f() { + int n = 1; + int a[n]; +} +``` + +Before: +```cpp +void f() { + int n = -1; + int *p = new int[n]; // <- negativeMemoryAllocationSize +} +``` + +After: +```cpp +void f() { + int n = 1; + int *p = new int[n]; + p[0] = 0; + delete[] p; +} +``` + diff --git a/man/checkers/negativeContainerIndex.md b/man/checkers/negativeContainerIndex.md new file mode 100644 index 00000000000..a628bc99c74 --- /dev/null +++ b/man/checkers/negativeContainerIndex.md @@ -0,0 +1,38 @@ +# negativeContainerIndex + +**Message**: Array index -11 is out of bounds.
+**Category**: Undefined Behaviour
+**Severity**: Error/Warning
+**Language**: C++ + +## Description + +A container is indexed with a value known to be negative. + +## Motivation + +Indexing an array-like container with a negative value is undefined behaviour - there is no valid +element "before the beginning" to access. + +## How to fix + +Before: +```cpp +#include +void f(const std::vector &v) { + v[-11] = 123; // <- negativeContainerIndex +} +``` + +After: +```cpp +#include +void f(const std::vector &v) { + v[11] = 123; +} +``` + +## Related checkers + +- [containerOutOfBounds.md](containerOutOfBounds.md) - the more general out-of-bounds container access + check, for indices that are too large rather than negative. diff --git a/man/checkers/negativeIndex.md b/man/checkers/negativeIndex.md new file mode 100644 index 00000000000..2b7477f8af0 --- /dev/null +++ b/man/checkers/negativeIndex.md @@ -0,0 +1,41 @@ +# negativeIndex + +**Message**: Array index -11 is out of bounds.
+**Category**: Undefined Behaviour
+**Severity**: Error/Warning
+**Language**: C/C++ + +## Description + +An array is indexed with a value cppcheck knows is negative. + +## Motivation + +Indexing an array with a negative value reads or writes memory before the start of the array, which is +undefined behaviour - it can silently corrupt unrelated data instead of crashing, making the actual +cause hard to trace back to. + +## How to fix + +Before: +```cpp +void f(int i) { + int a[10]; + if (i == -1) + a[i] = 0; // <- negative index +} +``` + +After: +```cpp +void f(int i) { + int a[10]; + if (i >= 0 && i < 10) + a[i] = 0; +} +``` + +## Related checkers + +- [arrayIndexOutOfBounds.md](arrayIndexOutOfBounds.md) - the general out-of-bounds-index check this + one complements, for indices known to be too large rather than negative. diff --git a/man/checkers/noConstructor.md b/man/checkers/noConstructor.md new file mode 100644 index 00000000000..76616aeaabb --- /dev/null +++ b/man/checkers/noConstructor.md @@ -0,0 +1,51 @@ +# noConstructor + +**Message**: The struct 'x' does not have a constructor although it has private member variables.
+**Category**: Correctness
+**Severity**: Style
+**Language**: C++ + +## Description + +A class/struct has private member variables (of a native type, pointer, or a type that itself needs +initialization) but declares no constructor at all - those members are left with whatever bytes happened +to already be there when the object is created. + +## Motivation + +Without a constructor, a class's native-type, pointer, or reference members are left uninitialized when +an object is created - reading one of them before it's explicitly assigned would be undefined behaviour. +This checker only looks at the class's shape (private members, no constructor, no default member +initializer); it doesn't verify that any member is actually read before being set anywhere in the +program, so it flags classes that are technically at risk even if none of them are ever used that way in +practice. This check is also deliberately reported at `style` severity rather than `warning`: for +performance reasons a constructor might be intentionally left out in some cases, so it's presented as a +suggestion rather than a certain bug. + +## How to fix + +Add a constructor that initializes every member. + +Before: +```cpp +class Fred { + int i; +public: + void setValue(int i_) { i = i_; } // <- no constructor initializes 'i' +}; +``` + +After: +```cpp +class Fred { + int i; +public: + Fred() : i(0) {} + void setValue(int i_) { i = i_; } +}; +``` + +## Related checkers + +- [uninitMemberVar.md](uninitMemberVar.md) - the related family of checks for when a constructor exists + but still misses initializing one or more specific members. diff --git a/man/checkers/noCopyConstructor.md b/man/checkers/noCopyConstructor.md new file mode 100644 index 00000000000..afbd4850f2c --- /dev/null +++ b/man/checkers/noCopyConstructor.md @@ -0,0 +1,59 @@ +# noCopyConstructor + +**Message**: Struct 'F' does not have a copy constructor which is recommended since it has dynamic memory/resource management.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ + +## Description + +The class allocates a resource itself (`new`/`malloc`-family in a constructor, `delete`/`free`-family +in a destructor) but doesn't define its own copy constructor (or only defaults it) - the +compiler-generated version would copy the raw pointer, not the resource. + +## Motivation + +The compiler-generated copy constructor just copies each member's value. For a raw pointer, that means +the copy ends up pointing at the exact same allocated block as the original - both objects now believe +they own it, so destroying either one leaves the other holding a dangling pointer, and destroying both +frees the same memory twice. Using the dangling copy afterward, or freeing the same block twice, is +undefined behaviour - though which of those actually happens, if either, depends on what the rest of the +program goes on to do with the two objects. This check fires on the class's shape alone (it allocates a +resource but has no copy constructor of its own) - it doesn't verify that the class is ever actually +copied anywhere in the analyzed code, so a class that's never copied is flagged just the same as one that +is. + +## How to fix + +Write a copy constructor that allocates a fresh block for the copy (or use a container/smart pointer +that already manages this correctly, avoiding the need for a hand-written one at all). + +Before: +```cpp +class F { + char *p; +public: + F() { p = new char[10]; } // <- no copy constructor + ~F() { delete[] p; } +}; +``` + +After: +```cpp +class F { + char *p; +public: + F() { p = new char[10]; } + F(const F& other) { p = new char[10]; } + F& operator=(const F& other) { p = other.p; return *this; } + ~F() { delete[] p; } +}; +``` + +## Related checkers + +- [noOperatorEq.md](noOperatorEq.md) and [noDestructor.md](noDestructor.md) - the same underlying + "allocates a resource but is missing a special member function" pattern, for `operator=` and the + destructor respectively. +- [copyCtorPointerCopying.md](copyCtorPointerCopying.md) - the more specific case where a copy + constructor does exist, but shallow-copies a pointer instead of allocating a fresh block. diff --git a/man/checkers/noDestructor.md b/man/checkers/noDestructor.md new file mode 100644 index 00000000000..cd9d3df2337 --- /dev/null +++ b/man/checkers/noDestructor.md @@ -0,0 +1,48 @@ +# noDestructor + +**Message**: Class 'F' does not have a destructor which is recommended since it has dynamic memory/resource management.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ + +## Description + +The class allocates a resource itself (`new`/`malloc`-family in a constructor) but doesn't define its +own destructor. + +## Motivation + +Without a destructor, whatever a constructor allocated is never released when the object is destroyed - +a straightforward memory/resource leak every time an object of this class goes out of scope or is +deleted. + +## How to fix + +Add a destructor that releases what the constructor allocated. + +Before: +```cpp +struct F { + char *p; + F() { p = new char[10]; } // <- no destructor + F(const F&); + F& operator=(const F&); +}; +``` + +After: +```cpp +struct F { + char *p; + F() { p = new char[10]; } + F(const F&); + F& operator=(const F&); + ~F() { delete[] p; } +}; +``` + +## Related checkers + +- [noCopyConstructor.md](noCopyConstructor.md) and [noOperatorEq.md](noOperatorEq.md) - the same + underlying "allocates a resource but is missing a special member function" pattern, for the copy + constructor and `operator=` respectively. diff --git a/man/checkers/noExplicitConstructor.md b/man/checkers/noExplicitConstructor.md new file mode 100644 index 00000000000..2d3f4fbbce6 --- /dev/null +++ b/man/checkers/noExplicitConstructor.md @@ -0,0 +1,39 @@ +# noExplicitConstructor + +**Message**: Class 'Class' has a constructor with 1 argument that is not explicit.
+**Category**: Correctness
+**Severity**: Style
+**Language**: C++ + +## Description + +A constructor that takes exactly one argument (and so can be used for an implicit conversion) isn't +marked `explicit` - so a value of that argument's type can silently convert into the class wherever the +class type is expected, which is rarely intended. + +## Motivation + +A single-argument constructor that isn't `explicit` doubles as an implicit conversion: anywhere the +class type is expected, a value of the argument's type is silently accepted and converted, without the +reader seeing any indication a conversion happened at all. This can produce confusing overload +resolution and surprising implicit conversions that the author never intended. + +## How to fix + +Mark the constructor `explicit`, unless the implicit conversion is genuinely intended. + +Before: +```cpp +class Class { +public: + Class(int i) {} // <- not explicit +}; +``` + +After: +```cpp +class Class { +public: + explicit Class(int i) {} +}; +``` diff --git a/man/checkers/noOperatorEq.md b/man/checkers/noOperatorEq.md new file mode 100644 index 00000000000..482455f5d42 --- /dev/null +++ b/man/checkers/noOperatorEq.md @@ -0,0 +1,57 @@ +# noOperatorEq + +**Message**: Struct 'F' does not have a operator= which is recommended since it has dynamic memory/resource management.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ + +## Description + +The class allocates a resource itself (`new`/`malloc`-family in a constructor, `delete`/`free`-family +in a destructor) but doesn't define its own `operator=` (or only defaults it) - the compiler-generated +version would copy the raw pointer, not the resource. + +## Motivation + +The compiler-generated assignment operator just copies each member's value. For a raw pointer, that +means the assigned-to object ends up pointing at the same allocated block as the source - both objects +now believe they own it, and the assigned-to object's original allocation is leaked in the process, +since nothing frees it first. This is more than a leak waiting to happen: if an object of this class is +ever assigned to another, both are eventually destroyed, and their destructors each free the same block, +that is a double-free - undefined behaviour. This check fires on the class's shape alone (it allocates a +resource but has no `operator=` of its own) - it doesn't verify that the class is ever actually assigned +anywhere in the analyzed code, so a class that's never copy-assigned is flagged just the same as one that +is. + +## How to fix + +Write an `operator=` that allocates a fresh block for the assigned-to object (or use a +container/smart pointer that already manages this correctly). + +Before: +```cpp +class F { + char *p; +public: + F() { p = new char[10]; } // <- no operator= + ~F() { delete[] p; } +}; +``` + +After: +```cpp +class F { + char *p; +public: + F() { p = new char[10]; } + F(const F& other) { p = new char[10]; } + F& operator=(const F& other) { p = other.p; return *this; } + ~F() { delete[] p; } +}; +``` + +## Related checkers + +- [noCopyConstructor.md](noCopyConstructor.md) and [noDestructor.md](noDestructor.md) - the same + underlying "allocates a resource but is missing a special member function" pattern, for the copy + constructor and destructor respectively. diff --git a/man/checkers/nullPointer.md b/man/checkers/nullPointer.md new file mode 100644 index 00000000000..b0519b3f46a --- /dev/null +++ b/man/checkers/nullPointer.md @@ -0,0 +1,63 @@ +# nullPointer + +**Message**: Null pointer dereference
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A pointer is definitely, or very likely, null at the point it is dereferenced - for example, it was +just assigned `0`/`nullptr`, or a variable known to be null was passed in. + +## Motivation + +Dereferencing a null pointer is undefined behaviour and one of the most common causes of crashes in +C/C++ programs. cppcheck reports this only when it can actually determine the pointer is null (or very +likely null); it does not attempt to trace every possible path a pointer's value could have taken to +get there, so the absence of a warning does not by itself mean a pointer can never be null at that +point. + +## How to fix + +Before: +```cpp +void f() { + int *p = 0; + *p = 1; // <- nullPointer +} +``` + +After: +```cpp +void f() { + int x = 0; + int *p = &x; + *p = 1; +} +``` + +## False positives to be aware of + +- **A false positive is possible when a guard condition depends on a different parameter than the one + being dereferenced.** If a function only dereferences one pointer when another argument has a + specific value, cppcheck does not always verify that a given call site actually triggers that + value, and can warn even when the guard makes the dereference unreachable for that call: + ```cpp + void f(int* p, const int* q) { + if (*q == -1) + *p = 0; + } + void g() { + int x = -2; + f(nullptr, &x); // reported as a possible null dereference of 'p', even though *q==-1 is false here + } + ``` + +## Related checkers + +- [nullPointerRedundantCheck.md](nullPointerRedundantCheck.md) - the same idea, but where a null check on the same pointer exists elsewhere and either it or the dereference is misplaced. +- [nullPointerDefaultArg.md](nullPointerDefaultArg.md) - the same idea for a pointer parameter that defaults to null. +- [nullPointerOutOfMemory.md](nullPointerOutOfMemory.md) - the same idea for a pointer that came from an allocation function that can fail. +- [nullPointerArithmetic.md](nullPointerArithmetic.md) - the same idea for pointer arithmetic instead of a direct dereference. +- [ctunullpointer.md](ctunullpointer.md) - the same idea, found by whole-program analysis across function calls. diff --git a/man/checkers/nullPointerArithmetic.md b/man/checkers/nullPointerArithmetic.md new file mode 100644 index 00000000000..c2987c4132f --- /dev/null +++ b/man/checkers/nullPointerArithmetic.md @@ -0,0 +1,43 @@ +# nullPointerArithmetic + +**Message**: Pointer addition with NULL pointer.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +Pointer arithmetic (`p + n`, `p - n`, `p++`, `--p`, ...) is performed on a pointer that is null (or +could be). Adding to or subtracting from a null pointer is undefined behaviour even though no memory is +actually touched. + +## Motivation + +It's easy to assume pointer arithmetic is only dangerous once the result is dereferenced, but the C/C++ +standards make forming an out-of-bounds pointer - including any arithmetic on a null pointer - undefined +behaviour in its own right, regardless of whether the result is ever used. + +## How to fix + +Before: +```cpp +void foo(char *s) { + char *p = s + 20; // <- if foo() is ever called with a null 's' +} +void bar() { foo(0); } +``` + +After: +```cpp +void foo(char *s) { + if (s) + char *p = s + 20; +} +void bar() { foo(0); } +``` + +## Related checkers + +- [nullPointer.md](nullPointer.md) - the direct-dereference equivalent of this check. +- [nullPointerArithmeticRedundantCheck.md](nullPointerArithmeticRedundantCheck.md) - the same idea, refined by a nearby null check. +- [nullPointerArithmeticOutOfMemory.md](nullPointerArithmeticOutOfMemory.md) - the same idea for a pointer from a failable allocation function. diff --git a/man/checkers/nullPointerArithmeticOutOfMemory.md b/man/checkers/nullPointerArithmeticOutOfMemory.md new file mode 100644 index 00000000000..7219f2826ef --- /dev/null +++ b/man/checkers/nullPointerArithmeticOutOfMemory.md @@ -0,0 +1,22 @@ +# nullPointerArithmeticOutOfMemory + +**Message**: If memory allocation fails: pointer addition with NULL pointer.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +Pointer arithmetic is performed on a pointer that came from a memory-allocation function that can fail +and return null (`malloc`, `new` in some configurations, ...), without checking for that failure first. + +## Motivation + +Just like [nullPointerOutOfMemory](nullPointerOutOfMemory.md), assuming an allocation always succeeds +means that under memory pressure, the very next operation on the result - even pointer arithmetic that +doesn't dereference anything - is undefined behaviour. + +## Related checkers + +- [nullPointerArithmetic.md](nullPointerArithmetic.md) - the same idea without a specific failable allocation involved. +- [nullPointerOutOfMemory.md](nullPointerOutOfMemory.md) - the direct-dereference equivalent of this check. diff --git a/man/checkers/nullPointerArithmeticRedundantCheck.md b/man/checkers/nullPointerArithmeticRedundantCheck.md new file mode 100644 index 00000000000..d965cabee06 --- /dev/null +++ b/man/checkers/nullPointerArithmeticRedundantCheck.md @@ -0,0 +1,23 @@ +# nullPointerArithmeticRedundantCheck + +**Message**: Either the condition 'p' is redundant or there is overflow in pointer addition.
+**Category**: Undefined Behaviour
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +Pointer arithmetic is performed on a pointer that could be null, and elsewhere in the code there is a +`NULL`/`nullptr` check on the very same pointer - so either that check is redundant, or the arithmetic +is a bug. + +## Motivation + +Just like [nullPointerRedundantCheck](nullPointerRedundantCheck.md), a null check that exists somewhere +in the code but doesn't actually guard the pointer arithmetic is a strong sign of a misplaced check or +an unnecessary one. + +## Related checkers + +- [nullPointerArithmetic.md](nullPointerArithmetic.md) - the same idea without a nearby check to cross-reference. +- [nullPointerRedundantCheck.md](nullPointerRedundantCheck.md) - the direct-dereference equivalent of this check. diff --git a/man/checkers/nullPointerDefaultArg.md b/man/checkers/nullPointerDefaultArg.md new file mode 100644 index 00000000000..5a6b99b1d29 --- /dev/null +++ b/man/checkers/nullPointerDefaultArg.md @@ -0,0 +1,39 @@ +# nullPointerDefaultArg + +**Message**: Possible null pointer dereference if the default parameter value is used.
+**Category**: Undefined Behaviour
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A pointer parameter that defaults to `nullptr`/`0` is dereferenced without a null check, so calling the +function with no argument for that parameter crashes. + +## Motivation + +A default argument value is one every caller is implicitly allowed to rely on - if that default is +null and the function dereferences the parameter unconditionally, calling the function the "normal", +argument-omitted way is itself the bug trigger. + +## How to fix + +Before: +```cpp +void f(int *p = 0) { + *p = 1; // <- crashes if called as f() +} +``` + +After: +```cpp +void f(int *p = 0) { + if (!p) + return; + *p = 1; +} +``` + +## Related checkers + +- [nullPointer.md](nullPointer.md) - the general null-dereference check. diff --git a/man/checkers/nullPointerOutOfMemory.md b/man/checkers/nullPointerOutOfMemory.md new file mode 100644 index 00000000000..964ab3d2f72 --- /dev/null +++ b/man/checkers/nullPointerOutOfMemory.md @@ -0,0 +1,46 @@ +# nullPointerOutOfMemory and nullPointerOutOfResources + +**Message**: Null pointer dereference
+**Category**: Undefined Behaviour
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +- `nullPointerOutOfMemory`: a pointer came from a memory-allocation function that can fail and return + null (`malloc`, `new` in some configurations, ...) and is used without checking for that failure. +- `nullPointerOutOfResources`: the same idea for a handle from a resource-allocating function that can + fail (`fopen`, ...) rather than a memory allocator. + +## Motivation + +`malloc()` and similar functions are documented to return `NULL` on failure - assuming they always +succeed means that, under memory or resource pressure, the very next dereference crashes instead of the +program handling the failure gracefully. + +## How to fix + +Before: +```cpp +void f() { + int *p = malloc(10); + *p = 1; // <- malloc() can return NULL + free(p); +} +``` + +After: +```cpp +void f() { + int *p = malloc(10); + if (p) { + *p = 1; + free(p); + } +} +``` + +## Related checkers + +- [nullPointer.md](nullPointer.md) - the general null-dereference check. +- [ctunullpointer.md](ctunullpointer.md) - the whole-program-analysis counterpart, which has its own `ctunullpointerOutOfMemory`/`ctunullpointerOutOfResources` variants. diff --git a/man/checkers/nullPointerRedundantCheck.md b/man/checkers/nullPointerRedundantCheck.md new file mode 100644 index 00000000000..5b314bfeb11 --- /dev/null +++ b/man/checkers/nullPointerRedundantCheck.md @@ -0,0 +1,47 @@ +# nullPointerRedundantCheck + +**Message**: Either the condition 'p' is redundant or there is possible null pointer dereference.
+**Category**: Undefined Behaviour
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A pointer is dereferenced, and elsewhere in the code there is a `NULL`/`nullptr` check on the very same +pointer - so either that check is redundant, or the dereference is a bug. This is often more useful +than a plain null-dereference warning, since it also catches "check after use" and "check the wrong +branch" mistakes, not just a directly-assigned null value. + +## Motivation + +A null check that exists somewhere in the code, but doesn't actually guard the dereference, is a strong +sign of a logic error: either the check was misplaced relative to the code it was meant to protect, or +the check itself is unnecessary and hides the fact that the pointer can never legitimately be null. +cppcheck only connects a check to a dereference when it can follow that the two really refer to the +same pointer value; once the pointer is reassigned, cached in a `bool`, or handed to another function +in between, that connection can be lost, so this check finding nothing is not proof the pointer is +always guarded correctly. + +## How to fix + +Before: +```cpp +void f(int *p) { + *p = 1; // <- dereferenced here... + if (p) {} // ...but only checked for null here +} +``` + +After: +```cpp +void f(int *p) { + if (!p) + return; + *p = 1; +} +``` + +## Related checkers + +- [nullPointer.md](nullPointer.md) - the general null-dereference check this one refines with a nearby-check cross-reference. +- [nullPointerArithmeticRedundantCheck.md](nullPointerArithmeticRedundantCheck.md) - the same idea for pointer arithmetic instead of a direct dereference. diff --git a/man/checkers/objectIndex.md b/man/checkers/objectIndex.md new file mode 100644 index 00000000000..18766ae527d --- /dev/null +++ b/man/checkers/objectIndex.md @@ -0,0 +1,44 @@ +# objectIndex + +**Message**: The address of variable 's.x' is accessed at non-zero index.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +The address of one specific member of a variable is taken and then indexed with a nonzero value, +reaching past that member into the rest of the object. + +## Motivation + +Indexing past the address of a single member relies on that member happening to be followed by other +data with a compatible layout - this isn't something the language guarantees (padding, member reordering +by the compiler in some cases, and unrelated following members all make it unreliable), so it's +undefined behaviour dressed up as if it worked. + +## How to fix + +Before: +```cpp +struct S { int x; int y; }; +void f() { + S s; + int *p = &s.x; + p[3] = 0; // <- reaches past 'x' into the rest of 's' +} +``` + +After: +```cpp +struct S { int x; int y; }; +void f() { + S s; + s.y = 0; +} +``` + +## Related checkers + +- [arrayIndexOutOfBounds.md](arrayIndexOutOfBounds.md) - the more general out-of-bounds-access check, + for a plain array rather than the address of a single struct member. diff --git a/man/checkers/obsoleteFunctionCalled.md b/man/checkers/obsoleteFunctionCalled.md new file mode 100644 index 00000000000..4c9bd920c84 --- /dev/null +++ b/man/checkers/obsoleteFunctionCalled.md @@ -0,0 +1,40 @@ +# <name>Called (for example getsCalled, bsd_signalCalled, or any other function marked this way in a library configuration) + +**Message**: Obsolete function 'gets' called. It is recommended to use 'fgets' or 'gets_s' instead.
+**Category**: Correctness
+**Severity**: Error/Warning/Style/Portability
+**Language**: C/C++ + +## Description + +A function known to be obsolete, dangerous, or non-reentrant is called. The message names a safer +replacement. The exact error ID is built from the function's own name plus `Called` (`getsCalled`, +`bsd_signalCalled`, ...), and which functions are covered - and at what severity - comes entirely from +the loaded library configuration (`std.cfg`, `posix.cfg`, and similar), not from a fixed list built into +cppcheck itself. `alloca()` is handled separately - see [allocaCalled.md](allocaCalled.md). + +## Motivation + +Some standard library functions are obsolete or unsafe for reasons that aren't visible from their +signature alone: `gets()` can't bound how much it reads and will happily overflow any buffer, some +functions from `` are unreliable across platforms, and so on. A caller has no way to know this +without already being aware of the function's history. + +## How to fix + +Before: +```cpp +#include +void f(char *a) { + char *x = gets(a); // <- gets() cannot bound the input, buffer overflow risk +} +``` + +After: +```cpp +#include +void f(char* buf, int n) { + char *x = fgets(buf, n, stdin); +} +``` + diff --git a/man/checkers/operatorEqMissingReturnStatement.md b/man/checkers/operatorEqMissingReturnStatement.md new file mode 100644 index 00000000000..8a74f7201d1 --- /dev/null +++ b/man/checkers/operatorEqMissingReturnStatement.md @@ -0,0 +1,47 @@ +# operatorEqMissingReturnStatement + +**Message**: No 'return' statement in non-void function causes undefined behavior.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +An `operator=` reaches the end of its body with no `return` at all, and isn't one of the deliberate +"never returns" cases covered by +[operatorEqShouldBeLeftUnimplemented.md](operatorEqShouldBeLeftUnimplemented.md) - undefined behaviour, +since the (non-`void`) function is expected to produce a value. + +## Motivation + +Falling off the end of a value-returning function without a `return` is undefined behaviour in C++ in +its own right, per the standard - it doesn't require the caller to go on and use the "returned" value; +merely reaching the closing `}` without having returned anything is already the undefined action. + +## How to fix + +Add the missing `return *this;`. + +Before: +```cpp +class szp +{ + szp &operator =(int *other) {} // <- no return statement +}; +``` + +After: +```cpp +class szp +{ + szp &operator =(int *other) { return *this; } +}; +``` + +## Related checkers + +- [operatorEqRetRefThis.md](operatorEqRetRefThis.md) - the general check for `operator=` not returning + `*this`. +- [operatorEqShouldBeLeftUnimplemented.md](operatorEqShouldBeLeftUnimplemented.md) - the case where + `operator=` deliberately never returns (for example, always throwing), and should instead be declared + unimplemented/deleted rather than just missing a `return`. diff --git a/man/checkers/operatorEqRetRefThis.md b/man/checkers/operatorEqRetRefThis.md new file mode 100644 index 00000000000..271b89f208a --- /dev/null +++ b/man/checkers/operatorEqRetRefThis.md @@ -0,0 +1,45 @@ +# operatorEqRetRefThis + +**Message**: 'operator=' should return reference to 'this' instance.
+**Category**: Correctness
+**Severity**: Style
+**Language**: C++ + +## Description + +A hand-written `operator=` doesn't `return *this;` - code like `a = b = c;` or chaining `.` after an +assignment then breaks, since the return value isn't what everyone expects. + +## Motivation + +Idiomatic C++ assignment operators return a reference to the assigned-to object so that assignments can +be chained (`a = b = c;`) and the result can be used directly. An `operator=` that returns something +else (or nothing meaningful) silently breaks that convention for any caller who relies on it, which is +easy to miss since a single, non-chained `a = b;` still compiles and works either way. + +## How to fix + +Return `*this` from `operator=`. + +Before: +```cpp +class A { +public: + A & operator=(const A &a) { return a; } // <- returns 'a', not '*this' +}; +``` + +After: +```cpp +class A { +public: + A & operator=(const A &a) { return *this; } +}; +``` + +## Related checkers + +- [operatorEqMissingReturnStatement.md](operatorEqMissingReturnStatement.md) - the more severe sibling + case where `operator=` has no `return` statement at all. +- [operatorEqShouldBeLeftUnimplemented.md](operatorEqShouldBeLeftUnimplemented.md) - the case where + `operator=` deliberately never returns, and should instead be declared unimplemented/deleted. diff --git a/man/checkers/operatorEqShouldBeLeftUnimplemented.md b/man/checkers/operatorEqShouldBeLeftUnimplemented.md new file mode 100644 index 00000000000..9a49a5a3822 --- /dev/null +++ b/man/checkers/operatorEqShouldBeLeftUnimplemented.md @@ -0,0 +1,54 @@ +# operatorEqShouldBeLeftUnimplemented + +**Message**: 'operator=' should either return reference to 'this' instance or be declared private and left unimplemented.
+**Category**: Correctness
+**Severity**: Style
+**Language**: C++ + +## Description + +An `operator=` with no `return` statement, whose entire body is a `throw` (or a call to a function that +never returns) - a common way to try to make an assignment operator uncallable, but the idiomatic way is +to declare it `private`/`= delete` instead of giving it a body that never returns. + +## Motivation + +Giving `operator=` a body that always throws is a roundabout, easy-to-misread way of saying "this class +can't be assigned" - it still compiles as if it were a normal, callable assignment operator, and the +"can't be assigned" part is only enforced at runtime, when it's too late to catch at compile time. The +idiomatic ways (`= delete`, or a private declaration with no definition) reject the attempt to assign at +compile time instead. + +## How to fix + +Declare the assignment operator `= delete` (or `private` with no body) rather than giving it a body +that always throws. + +Before: +```cpp +#include +#include +class A { +public: + A & operator=(const A &a) { + rand(); + throw std::exception(); // <- always throws instead of assigning + } +}; +``` + +After: +```cpp +class A { +public: + A & operator=(const A &a) = delete; +}; +``` + +## Related checkers + +- [operatorEqRetRefThis.md](operatorEqRetRefThis.md) - the general check for `operator=` not returning + `*this`, of which this is a specific variant. +- [operatorEqMissingReturnStatement.md](operatorEqMissingReturnStatement.md) - the sibling case where + `operator=` reaches the end of its body with no return, but isn't one of these deliberate + "never returns" cases. diff --git a/man/checkers/operatorEqToSelf.md b/man/checkers/operatorEqToSelf.md new file mode 100644 index 00000000000..f0b029ed32e --- /dev/null +++ b/man/checkers/operatorEqToSelf.md @@ -0,0 +1,59 @@ +# operatorEqToSelf + +**Message**: 'operator=' should check for assignment to self to avoid problems with dynamic memory.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ + +## Description + +`operator=` allocates/frees a resource but never checks for `a = a;` (self-assignment) - freeing a +resource and then trying to copy from the same, now-freed object corrupts the object. + +## Motivation + +`a = a;` is valid, if unusual, code - and an `operator=` that frees its own current resource before +copying from the source object can, on self-assignment, free the very resource it's about to read from, +leaving the object corrupted (a use of freed memory, which is undefined behaviour) - though this depends +on the new value actually being derived from the freed data; cppcheck flags any allocate-without-a- +self-check pattern in `operator=`, not just the ones it can confirm would read something already freed. +Since self-assignment is rare in normal code (it usually happens indirectly, through an alias or a +container operation), this bug can go unnoticed for a long time. + +## How to fix + +Check for self-assignment (`this == &a`) before freeing anything, and return early if it's true. + +Before: +```cpp +#include +#include +class A { +public: + char *s; + A & operator=(const A &a) + { + free(s); // <- breaks if 'a' is '*this' + s = strdup(a.s); + return *this; + } +}; +``` + +After: +```cpp +#include +#include +class A { +public: + char *s; + A & operator=(const A &a) + { + if (this == &a) + return *this; + free(s); + s = strdup(a.s); + return *this; + } +}; +``` diff --git a/man/checkers/operatorEqVarError.md b/man/checkers/operatorEqVarError.md new file mode 100644 index 00000000000..aef42c8758c --- /dev/null +++ b/man/checkers/operatorEqVarError.md @@ -0,0 +1,45 @@ +# operatorEqVarError + +**Message**: Member variable 'classname::varname' is not assigned a value in 'classname::operator='.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ + +## Description + +Same idea as [missingMemberCopy.md](missingMemberCopy.md), but for a hand-written `operator=` instead +of a copy constructor: one particular member is never assigned anywhere in it, even though everything +else about the class suggests it should have been. + +## Motivation + +A hand-written `operator=` that forgets one member leaves that member holding its old value after an +assignment that's supposed to make the object equal to another - a subtle bug that's easy to miss since +the operator still compiles and mostly "looks right." + +## How to fix + +Assign the missing member too. + +Before: +```cpp +struct S { + int i{}; + S() = default; + S& operator=(const S& s) { return *this; } // <- 'i' isn't assigned +}; +``` + +After: +```cpp +struct S { + int i{}; + S() = default; + S& operator=(const S& s) { i = s.i; return *this; } +}; +``` + +## Related checkers + +- [missingMemberCopy.md](missingMemberCopy.md) - the same idea, for a hand-written copy/move constructor + instead of `operator=`. diff --git a/man/checkers/oppositeExpression.md b/man/checkers/oppositeExpression.md new file mode 100644 index 00000000000..f251057e3f2 --- /dev/null +++ b/man/checkers/oppositeExpression.md @@ -0,0 +1,37 @@ +# oppositeExpression + +**Message**: Opposite expression on both sides of '&&'.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +An expression and its logical/arithmetic opposite (`a` and `!a`, `x` and `-x`, ...) appear on both +sides of the same operator - for `&&`/`||` in particular, this makes the whole condition always +false/true. + +## Motivation + +Combining an expression with its own negation can never depend on the expression's actual value - the +result is fixed in advance, which usually means one side was meant to refer to something else. + +## How to fix + +Before: +```cpp +void f(bool a) { + if (a && !a) {} // <- always false +} +``` + +After: +```cpp +void f(bool a, bool b) { + if (a && !b) {} +} +``` + +## Related checkers + +- [duplicateExpression.md](duplicateExpression.md) - the mirror-image mistake, comparing an expression against an identical copy of itself. diff --git a/man/checkers/oppositeInnerCondition.md b/man/checkers/oppositeInnerCondition.md new file mode 100644 index 00000000000..fe4b7b9453d --- /dev/null +++ b/man/checkers/oppositeInnerCondition.md @@ -0,0 +1,47 @@ +# oppositeInnerCondition + +**Message**: Opposite inner 'if' condition leads to a dead code block.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +An `if` nested directly inside another `if` has a condition that contradicts the outer one, so its body +is dead code. + +## Motivation + +Once the outer condition is true, the value(s) it depends on are already narrowed down; an inner +condition that could only be true when the outer one is false can never actually run. This usually means +either dead code left over from an edit, or a comparison that doesn't say what the author meant. + +This check may need `--check-level=exhaustive` to see every case. + +## How to fix + +Before: +```cpp +void f(int x) { + if (x == 1) { + if (x == 2) {} // <- dead code, x is already 1 here + } +} +``` + +After: remove the dead inner check. +```cpp +void f(int x) { + if (x == 1) { + } +} +``` + +## Related checkers + +- [identicalInnerCondition.md](identicalInnerCondition.md) - the same idea, but the inner condition + repeats the outer one exactly instead of contradicting it. +- [overlappingInnerCondition.md](overlappingInnerCondition.md) - the same idea, but the inner condition + is already implied by (not identical to) the outer one. +- [multiCondition.md](multiCondition.md) - the same idea, but for an `if`/`else if` chain instead of + nested `if`s. diff --git a/man/checkers/orInComplexPattern.md b/man/checkers/orInComplexPattern.md new file mode 100644 index 00000000000..1d8684f6756 --- /dev/null +++ b/man/checkers/orInComplexPattern.md @@ -0,0 +1,31 @@ +# orInComplexPattern + +**Message**: Found \"|\" in complex pattern. You probably intend to use \"%or%\".
+**Category**: Code Quality
+**Severity**: Error
+**Language**: C++ (cppcheck's own source code only) + +## Description + +A pattern uses a literal `|`/`||` instead of the pattern language's `%or%`/`%oror%`. + +This is not a general-purpose checker for arbitrary C/C++ programs - see +[simplePatternError.md](simplePatternError.md) for the shared background on this internal, +cppcheck-source-only checker. + +## Motivation + +A literal `|`/`||` character in a pattern string matches the literal token `|`/`||` in the code being +analyzed - it does not mean "either of these two alternatives" the way `%or%`/`%oror%` does. This is an +easy habit to slip into for anyone used to regular-expression syntax, and it silently changes what the +pattern matches. + +## How to fix + +Replace the literal `|`/`||` with `%or%`/`%oror%` when the intent is "match either of these token +types", keeping the literal form only when the code being analyzed should actually contain a `|`/`||` +character. + +## Related checkers + +- [unknownPattern.md](unknownPattern.md) - a different kind of placeholder mistake in the same pattern language. diff --git a/man/checkers/overlappingInnerCondition.md b/man/checkers/overlappingInnerCondition.md new file mode 100644 index 00000000000..763edbf7512 --- /dev/null +++ b/man/checkers/overlappingInnerCondition.md @@ -0,0 +1,47 @@ +# overlappingInnerCondition + +**Message**: Overlapping inner 'if' condition is always true.
+**Category**: Code Quality
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +An `if` nested directly inside another `if` has a condition that's already guaranteed by the outer one +(a bitwise overlap, for example `x == 1` outside and `x & 7` inside), so the inner condition is always +true. + +## Motivation + +If the outer condition already guarantees the inner one, the inner `if` can never be false - it adds +nothing but a false impression that there's a real extra check happening. + +This check may need `--check-level=exhaustive` to see every case. + +## How to fix + +Before: +```cpp +void f(int x) { + if (x == 1) { + if (x & 7) {} // <- always true, implied by x == 1 + } +} +``` + +After: remove the redundant inner check. +```cpp +void f(int x) { + if (x == 1) { + } +} +``` + +## Related checkers + +- [identicalInnerCondition.md](identicalInnerCondition.md) - the same idea, but the inner condition is + identical to the outer one rather than merely implied by it. +- [oppositeInnerCondition.md](oppositeInnerCondition.md) - the same idea, but the inner condition + contradicts the outer one instead. +- [multiCondition.md](multiCondition.md) - the same idea, but for an `if`/`else if` chain instead of + nested `if`s. diff --git a/man/checkers/overlappingStrcmp.md b/man/checkers/overlappingStrcmp.md new file mode 100644 index 00000000000..e10f84108cb --- /dev/null +++ b/man/checkers/overlappingStrcmp.md @@ -0,0 +1,39 @@ +# overlappingStrcmp + +**Message**: The expression 'strcmp(x, "b") != 0' is suspicious. It overlaps 'strcmp(x, "a") == 0'.
+**Category**: Code Quality
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +`strcmp(x, "a") == 0 || strcmp(x, "b") != 0` - the `!= 0` half is always true whenever the `== 0` half +is false, since `x` can't equal `"a"` and simultaneously differ from `"a"` in the case that makes the +first half false. In other words, the second condition doesn't add anything meaningful and is likely a +copy-paste mistake. + +## Motivation + +An `||` of two `strcmp()` checks like this always evaluates to true regardless of what `x` actually is, +which is easy to miss since each half looks like a reasonable, independent check on its own. + +## How to fix + +Before: +```cpp +void f(const char *str) { + if (strcmp(str, "a") == 0 || strcmp(str, "b") != 0) {} // <- always true +} +``` + +After: +```cpp +void f(const char *str) { + if (strcmp(str, "a") == 0 || strcmp(str, "c") == 0) {} +} +``` + +## Related checkers + +- [sprintfOverlappingData.md](sprintfOverlappingData.md) - an unrelated string-function misuse in the + same checker, about `sprintf()`'s destination and source arguments overlapping. diff --git a/man/checkers/overlappingWriteFunction.md b/man/checkers/overlappingWriteFunction.md new file mode 100644 index 00000000000..44e8ac2f3f5 --- /dev/null +++ b/man/checkers/overlappingWriteFunction.md @@ -0,0 +1,49 @@ +# overlappingWriteFunction + +**Message**: Overlapping read/write in memcpy() is undefined behavior
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A call to a function like `memcpy()` is given source and destination ranges that overlap - only +`memmove()`-family functions are allowed to have overlapping ranges; `memcpy()` and similar are +undefined behaviour if the ranges overlap. + +## Motivation + +`memcpy()` (unlike `memmove()`) is free to copy in whatever order is fastest for the platform - forwards, +backwards, or in chunks - on the assumption that the source and destination never overlap. If they do +overlap, the copy can partially overwrite data it still needs to read, corrupting the result in a way +that can differ between compilers, optimization levels, or standard library implementations. cppcheck +only reports this when it can work out the exact size being copied; it does not warn just because two +ranges look suspiciously close together with a size it can't pin down. + +## How to fix + +Use `memmove()` instead of `memcpy()` when the source and destination ranges might overlap. + +Before: +```cpp +#include +void foo() { + char a[10]; + memcpy(a, a+1, 2u); // <- source and destination overlap +} +``` + +After: +```cpp +#include +void foo() { + char a[10]; + memmove(a, a+1, 2u); // memmove() is defined to handle overlap safely +} +``` + +## Related checkers + +- [overlappingWriteUnion.md](overlappingWriteUnion.md) - the same underlying overlapping-read/write + hazard, but for reading and writing two overlapping members of a union in one expression instead of a + function call. diff --git a/man/checkers/overlappingWriteUnion.md b/man/checkers/overlappingWriteUnion.md new file mode 100644 index 00000000000..f9adca82f0f --- /dev/null +++ b/man/checkers/overlappingWriteUnion.md @@ -0,0 +1,51 @@ +# overlappingWriteUnion + +**Message**: Overlapping read/write of union is undefined behavior
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A union member is written using the value of a different, overlapping member of the same union in the +same expression (for example `u.i = u.f;`) - the read and write overlap in memory in a way the compiler +isn't required to sequence safely. + +## Motivation + +All members of a union share the same storage. Reading one member while writing a different, overlapping +one in the same expression is undefined behaviour: the compiler is free to assume the read and write +don't alias, and can reorder or optimize the expression in ways that don't match what the code visually +appears to do. + +## How to fix + +Read the source member into a separate variable first, then write the destination member from that +variable. + +Before: +```cpp +void foo() { + union { int i; float f; } u; + u.i = 0; + u.i = u.f; // <- reads and writes the same union storage in one expression +} +``` + +After: +```cpp +void foo() { + union { int i; float f; } u; + u.i = 0; + float g = u.f; + u.i = (int)g; +} +``` + +## Related checkers + +- [overlappingWriteFunction.md](overlappingWriteFunction.md) - the same underlying overlapping-read/write + hazard, but for a function call (like `memcpy()`) given overlapping source/destination ranges instead + of a union member access. +- [UnionZeroInit.md](UnionZeroInit.md) - a different union-related pitfall, about a union not being + fully zeroed by its initializer. diff --git a/man/checkers/passedByValue.md b/man/checkers/passedByValue.md new file mode 100644 index 00000000000..b48a9a0f13c --- /dev/null +++ b/man/checkers/passedByValue.md @@ -0,0 +1,43 @@ +# passedByValue + +**Message**: Parameter 'x' should be passed by const reference.
+**Category**: Code Quality
+**Severity**: Performance
+**Language**: C++ + +## Description + +A function parameter of a class/struct/container type (not a small built-in type) is passed by value +and never modified, so it's copied for nothing - it could be a `const` reference instead. + +## Motivation + +Passing a non-trivial object by value makes an unnecessary copy every time the function is called; a +`const` reference avoids the copy while still preventing the function from modifying the caller's +object. + +## How to fix + +Before: +```cpp +#include +#include +void f(std::string str) { // <- an unnecessary copy of 'str' is made + printf("%s\n", str.c_str()); +} +``` + +After: +```cpp +#include +#include +void f(const std::string& str) { + printf("%s\n", str.c_str()); +} +``` + +## Related checkers + +- [passedByValueCallback.md](passedByValueCallback.md) - the same idea, but for a parameter of a + function used as a callback. +- [iterateByValue.md](iterateByValue.md) - the same idea, for a range-based `for` loop's variable. diff --git a/man/checkers/passedByValueCallback.md b/man/checkers/passedByValueCallback.md new file mode 100644 index 00000000000..46561cd4c2c --- /dev/null +++ b/man/checkers/passedByValueCallback.md @@ -0,0 +1,43 @@ +# passedByValueCallback + +**Message**: Parameter 'x' should be passed by const reference. However it seems that 'f' is a callback function.
+**Category**: Code Quality
+**Severity**: Performance
+**Language**: C++ + +## Description + +Same idea as [passedByValue.md](passedByValue.md): a non-trivial parameter is passed by value and never +modified, so it's copied for nothing. This variant is for when the function is used as a callback - +fixing it may also require adjusting the function pointer type it's assigned to. + +## Motivation + +Passing a non-trivial object by value makes an unnecessary copy every time the function is called. The +callback case is called out separately because the fix isn't purely local: the function pointer type +also needs to change. + +## How to fix + +Before: +```cpp +#include +#include +void setCb(void (*cb)(std::string)); +void cb(std::string s) { printf("%s", s.c_str()); } // <- an unnecessary copy of 's' is made +void f() { setCb(cb); } +``` + +After: +```cpp +#include +#include +void setCb(void (*cb)(const std::string&)); +void cb(const std::string& s) { printf("%s", s.c_str()); } +void f() { setCb(cb); } +``` + +## Related checkers + +- [passedByValue.md](passedByValue.md) - the same idea, for a parameter that isn't part of a callback + function's signature. diff --git a/man/checkers/pointerAdditionResultNotNull.md b/man/checkers/pointerAdditionResultNotNull.md new file mode 100644 index 00000000000..35e1971a102 --- /dev/null +++ b/man/checkers/pointerAdditionResultNotNull.md @@ -0,0 +1,38 @@ +# pointerAdditionResultNotNull + +**Message**: Comparison is wrong. Result of 'ptr+1' can't be 0 unless there is pointer overflow, and pointer overflow is undefined behaviour.
+**Category**: Undefined Behaviour
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +Pointer arithmetic is compared against `NULL`/`0`, which can only be true through undefined-behaviour +pointer overflow. + +## Motivation + +Adding a positive offset to a valid, non-null pointer can never legitimately produce a null pointer - the +only way the comparison could be true is via pointer overflow, which is itself undefined behaviour. A +check written this way doesn't do what it looks like it does. + +## How to fix + +Before: +```cpp +void f(char *p) { + if (p + 12 == 0) {} // <- relies on pointer overflow, which is UB +} +``` + +After: +```cpp +void f(char *p) { + if (p == nullptr) {} +} +``` + +## Related checkers + +- [invalidTestForOverflow.md](invalidTestForOverflow.md) - a related undefined-behaviour trap, relying + on signed integer overflow instead of pointer overflow. diff --git a/man/checkers/pointerArithBool.md b/man/checkers/pointerArithBool.md new file mode 100644 index 00000000000..a14042e59a9 --- /dev/null +++ b/man/checkers/pointerArithBool.md @@ -0,0 +1,33 @@ +# pointerArithBool + +**Message**: Converting pointer arithmetic result to bool. The bool is always true unless there is undefined behaviour.
+**Category**: Correctness
+**Severity**: Error
+**Language**: C++ + +## Description + +The result of pointer arithmetic (`p + 1`, `p - 1`, ...) is used directly as a boolean condition. + +## Motivation + +A pointer produced by arithmetic is essentially always non-null (unless the arithmetic itself is +undefined behaviour), so converting it straight to `bool` is always `true` and rarely what the code's +author meant - usually a dereference was intended instead. + +## How to fix + +Before: +```cpp +void f(char *p) { + if (p + 1) {} // <- always true unless undefined behaviour +} +``` + +After: +```cpp +void f(char *p) { + if (p && *(p + 1)) {} +} +``` + diff --git a/man/checkers/pointerLessThanZero.md b/man/checkers/pointerLessThanZero.md new file mode 100644 index 00000000000..eb696d0d1b4 --- /dev/null +++ b/man/checkers/pointerLessThanZero.md @@ -0,0 +1,52 @@ +# pointerLessThanZero and pointerPositive + +**Message**: A pointer can not be negative so it is either pointless or an error to check if it is.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A pointer is compared against `0` with `<` (`pointerLessThanZero`) or `>=` (`pointerPositive`) - a +pointer value can't be negative, so the first can never be true and the second is always true. + +## Motivation + +Both comparisons are tautological for a pointer, so the branch they guard either never runs or always +runs - not what the code visibly appears to be testing, and usually a sign that `== nullptr`/`!= +nullptr` was intended instead. + +## How to fix + +Before: +```cpp +void foo(const int* x) { + if (x < 0) {} // <- can never be true +} +``` + +After: +```cpp +void foo(const int* x) { + if (x == nullptr) {} +} +``` + +Before: +```cpp +void foo(const int* x) { + if (x >= 0) {} // <- always true +} +``` + +After: +```cpp +void foo(const int* x) { + if (x != nullptr) {} +} +``` + +## Related checkers + +- [unsignedLessThanZero.md](unsignedLessThanZero.md) - the same idea, for an unsigned integer compared + against `0` instead of a pointer. diff --git a/man/checkers/pointerOutOfBounds.md b/man/checkers/pointerOutOfBounds.md new file mode 100644 index 00000000000..850a4926710 --- /dev/null +++ b/man/checkers/pointerOutOfBounds.md @@ -0,0 +1,47 @@ +# pointerOutOfBounds and pointerOutOfBoundsCond + +**Message**: Undefined behaviour, pointer arithmetic 'a+20' is out of bounds.
+**Category**: Undefined Behaviour
+**Severity**: Portability/Warning
+**Language**: C/C++ + +## Description + +Pointer arithmetic (`p + n`, `p - n`, `p += n`, `p++`, ...) produces a pointer that lands outside the +array/buffer `p` points into: + +- `pointerOutOfBounds`: cppcheck knows for certain the result is out of bounds. This is undefined + behaviour even before the resulting pointer is dereferenced. +- `pointerOutOfBoundsCond`: the out-of-bounds result only holds on one branch of a condition checked + elsewhere - so either that condition is redundant, or this arithmetic is a bug. + +## Motivation + +Forming a pointer that lands outside the bounds of the array it points into is undefined behaviour in +C/C++, even if that pointer is never dereferenced - the language only guarantees pointer arithmetic +stays valid up to one-past-the-end of an array. Compilers are allowed to (and do) optimize based on +this assumption, which can make the resulting bug manifest in surprising, hard-to-reproduce ways. + +## How to fix + +Before: +```cpp +void f() { + int a[10]; + int *p = a + 20; // <- pointerOutOfBounds +} +``` + +After: +```cpp +void f() { + int a[20]; + int *p = a + 19; +} +``` + +## Related checkers + +- [arrayIndexOutOfBounds.md](arrayIndexOutOfBounds.md) - the array-indexing equivalent of this check. +- [ctuPointerArith.md](ctuPointerArith.md) - the same idea, found by cppcheck's whole-program analysis + across function calls. diff --git a/man/checkers/pointerSize.md b/man/checkers/pointerSize.md new file mode 100644 index 00000000000..518278655d1 --- /dev/null +++ b/man/checkers/pointerSize.md @@ -0,0 +1,59 @@ +# pointerSize and sizeofDivisionMemfunc + +**Message**: Size of pointer 'x' used instead of size of its data.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +`sizeof(ptr)` (the size of the pointer itself) is used where the size of what it points to was clearly +intended, as an argument to `malloc`/`calloc`/`memset`/`memcpy`/`memmove`/`strncpy`/`strncmp`/`strncat`: + +- `pointerSize`: `sizeof(ptr)` is passed directly as (or as part of) the size argument. +- `sizeofDivisionMemfunc`: the size argument is computed by *dividing* by `sizeof(ptr)`, when + multiplying was clearly intended. + +## Motivation + +`sizeof(ptr)` is the fixed size of the pointer itself (typically 4 or 8 bytes), not the size of the +buffer it points to. Using it to size a `malloc`/`memset`/`memcpy`-family call is a classic copy-paste +bug: it usually still compiles and often still "sort of works" for small, coincidentally-sized cases, +while silently under- or over-sizing the real operation. The line itself is just a miscalculated size - +the actual undefined behaviour comes later, if something then reads or writes the buffer assuming it's +the size that was intended rather than the (usually too small) size it actually got. + +## How to fix + +Before: +```cpp +void f() { + int *x = (int*)malloc(sizeof(x)); // <- size of the pointer, not what it points to +} +``` + +After: +```cpp +void f() { + int *x = (int*)malloc(sizeof(*x)); +} +``` + +Before: +```cpp +void f(char* dst, char* src, int size) { + memcpy(dst, src, size / sizeof(dst)); // <- dividing by the pointer's size, not the data's +} +``` + +After: +```cpp +void f(char* dst, char* src, int size) { + memcpy(dst, src, size); +} +``` + +## Related checkers + +- [multiplySizeof.md](multiplySizeof.md) - a related but more general `sizeof(a) * sizeof(b)` / + `sizeof(a) / sizeof(b)` mistake, not specific to these memory functions. diff --git a/man/checkers/postfixOperator.md b/man/checkers/postfixOperator.md new file mode 100644 index 00000000000..99c249ef73e --- /dev/null +++ b/man/checkers/postfixOperator.md @@ -0,0 +1,51 @@ +# postfixOperator + +**Message**: Prefer prefix ++/-- operators for non-primitive types.
+**Category**: Performance
+**Severity**: Performance
+**Language**: C++ only + +## Description + +This checker suggests using the prefix `++`/`--` operator instead of postfix on a variable (or member +variable) of a non-primitive type - a class, struct or union instance, or a type whose name ends in +`iterator`, `const_iterator`, `reverse_iterator` or `const_reverse_iterator` - whenever the old value +produced by the postfix operator is not actually used, for example as a whole statement (`k++;`) or in +a loop's increment clause (`for (...; ...; i++)`). + +It does not warn when the postfix result is genuinely used, for example when passed as a function +argument (`foo(a++)`), since switching to prefix there would change the program's behavior, not just +its performance. + +This checker only runs when the `performance` severity is enabled, and only applies to C++ (there are +no classes in C). + +## Motivation + +For a non-primitive type, the postfix operator typically has to make a copy of the object's previous +value before modifying it, so that the old value can be returned - even when nothing uses that old +value. The prefix operator does not need this copy. Built-in types like `int` and pointers don't pay +for this copy, which is why the checker never warns about those. + +## How to fix + +Use the prefix operator when the previous value isn't needed. + +Before: +```cpp +class BigNumber { /* ... */ }; + +void f(BigNumber& n) { + n++; // <- postfixOperator: the returned old value is discarded +} +``` + +After: +```cpp +class BigNumber { /* ... */ }; + +void f(BigNumber& n) { + ++n; +} +``` + diff --git a/man/checkers/preprocessorErrorDirective.md b/man/checkers/preprocessorErrorDirective.md index 44fcb4c1a28..c1a9a2c49c2 100644 --- a/man/checkers/preprocessorErrorDirective.md +++ b/man/checkers/preprocessorErrorDirective.md @@ -1,4 +1,3 @@ - # preprocessorErrorDirective **Message**: #error message
diff --git a/man/checkers/publicAllocationError.md b/man/checkers/publicAllocationError.md new file mode 100644 index 00000000000..8426a7eba4c --- /dev/null +++ b/man/checkers/publicAllocationError.md @@ -0,0 +1,49 @@ +# publicAllocationError + +**Message**: Possible leak in public function. The pointer 'x' is not deallocated before it is allocated.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ + +## Description + +A public member function's first action is to allocate a new value directly into a member pointer, +without checking or freeing whatever it already pointed to - calling this function on an +already-initialized object leaks the old value. + +## Motivation + +A public function can be called at any time, including on an object that already holds a previous +allocation in that member. Overwriting the member with a fresh allocation, without freeing what it +pointed to first, leaks the old value every time the function is called on an object that already has +one - which working code calling this function more than once will do. + +## How to fix + +Before: +```cpp +class A { + int *p; +public: + A() : p(nullptr) {} + void init() { p = new int; } // <- leaks the old 'p' if init() is called twice +}; +``` + +After: +```cpp +class A { + int *p; +public: + A() : p(nullptr) {} + void init() { + delete p; + p = new int; + } +}; +``` + +## Related checkers + +- [unsafeClassCanLeak.md](unsafeClassCanLeak.md) - a related class-ownership leak, where nothing ever + frees an allocated member at all. diff --git a/man/checkers/raceAfterInterlockedDecrement.md b/man/checkers/raceAfterInterlockedDecrement.md new file mode 100644 index 00000000000..f2d24288143 --- /dev/null +++ b/man/checkers/raceAfterInterlockedDecrement.md @@ -0,0 +1,49 @@ +# raceAfterInterlockedDecrement + +**Message**: Race condition: non-interlocked access after InterlockedDecrement(). Use InterlockedDecrement() return value instead.
+**Category**: Correctness
+**Severity**: Error
+**Language**: Windows platform only + +## Description + +Code checks a variable's value directly right after calling the Windows `InterlockedDecrement()` API on +it - between the API call and the check, another thread could have already changed the variable again, +so only the value `InterlockedDecrement()` itself returned can be trusted. + +## Motivation + +`InterlockedDecrement()` exists specifically to make the decrement-and-check atomic across threads. If +the code decrements the variable and then reads it again as a separate step, another thread can run in +between and change the value - the whole point of using the interlocked API is defeated, reintroducing +the exact race condition it was meant to prevent. cppcheck recognizes this purely from the code shape (an +`InterlockedDecrement()` call immediately followed by a plain re-read of the same variable) - it has no +way to confirm another thread genuinely touches that variable, so this is a strong hint of a real race, +not a proof that one exists in every case it's reported. + +## How to fix + +Use the value `InterlockedDecrement()` itself returns, instead of re-reading the variable afterwards. + +Before: +```cpp +void destroy(); +void f() { + int counter = 0; + InterlockedDecrement(&counter); + if (counter) // <- another thread could change 'counter' in between + return; + destroy(); +} +``` + +After: +```cpp +void destroy(); +void f() { + int counter = 0; + if (InterlockedDecrement(&counter) == 0) + return; + destroy(); +} +``` diff --git a/man/checkers/readWriteOnlyFile.md b/man/checkers/readWriteOnlyFile.md new file mode 100644 index 00000000000..2b4f3f5da30 --- /dev/null +++ b/man/checkers/readWriteOnlyFile.md @@ -0,0 +1,52 @@ +# readWriteOnlyFile + +**Message**: Read operation on a file that was opened only for writing.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +The file was opened in a mode (`"w"`) that only allows writing, and the code then reads from it. + +## Motivation + +Reading from a stream that was opened write-only is undefined behaviour in the C standard - even where +an implementation happens to do something predictable with it, code relying on that isn't portable. +cppcheck follows a `FILE*`'s open mode only through straight-line code in a single function; once it's +passed to another function, or is a global/member variable, the tracking is dropped rather than +guessed at, so this check finding nothing does not mean a file is necessarily being used consistently +with how it was opened. + +## How to fix + +Before: +```cpp +#include +void f() { + FILE *fp = fopen("a.txt", "w"); + if (!fp) return; + char buf[10]; + fread(buf, 1, 10, fp); // <- 'fp' was only opened for writing + fclose(fp); +} +``` + +After: +```cpp +#include +void f() { + FILE *fp = fopen("a.txt", "r"); + if (!fp) return; + char buf[10]; + fread(buf, 1, 10, fp); + fclose(fp); +} +``` + +## Related checkers + +- [writeReadOnlyFile.md](writeReadOnlyFile.md) - the opposite mismatch: writing to a read-only file. +- [useClosedFile.md](useClosedFile.md), [IOWithoutPositioning.md](IOWithoutPositioning.md), + [seekOnAppendedFile.md](seekOnAppendedFile.md), [incompatibleFileOpen.md](incompatibleFileOpen.md) - + other checks that follow the same `FILE*` through a function. diff --git a/man/checkers/redundantAssignInSwitch.md b/man/checkers/redundantAssignInSwitch.md new file mode 100644 index 00000000000..b033ccabd95 --- /dev/null +++ b/man/checkers/redundantAssignInSwitch.md @@ -0,0 +1,55 @@ +# redundantAssignInSwitch + +**Message**: Variable 'x' is reassigned a value before the old one has been used.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A variable is assigned a value in one `switch` `case`, and that value is overwritten by another +assignment in a later `case` that it falls through into, without the value ever being read in between. + +## Motivation + +This is the `switch`-fallthrough flavor of a redundant assignment, and it's usually a sign of a missing +`break;` rather than an intentional fallthrough - if the fallthrough really is intentional, the first +assignment is still pointless and worth removing for clarity. + +## How to fix + +Before: +```cpp +void bar(int); +void foo(int a) { + int y = 1; + switch (a) { + case 2: + y = 2; // <- falls through into case 3, missing 'break;'? + case 3: + y = 3; + } + bar(y); +} +``` + +After: +```cpp +void bar(int); +void foo(int a) { + int y = 1; + switch (a) { + case 2: + y = 2; + break; + case 3: + y = 3; + } + bar(y); +} +``` + +## Related checkers + +- [redundantAssignment.md](redundantAssignment.md) - the same idea outside of a `switch`. +- [redundantBitwiseOperationInSwitch.md](redundantBitwiseOperationInSwitch.md) - the bitwise-assignment equivalent in a `switch`. diff --git a/man/checkers/redundantAssignment.md b/man/checkers/redundantAssignment.md new file mode 100644 index 00000000000..7952393b49e --- /dev/null +++ b/man/checkers/redundantAssignment.md @@ -0,0 +1,41 @@ +# redundantAssignment + +**Message**: Variable 'x' is reassigned a value before the old one has been used.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A variable is given a value, but that value is completely overwritten by another assignment before +it's ever read. + +## Motivation + +This isn't wrong in the sense of producing an incorrect result today, but it signals that something +didn't happen the way the author intended: a value that was supposed to be used got clobbered first. +It's also a prime candidate for dead-code cleanup, and occasionally hides an outright bug (for example +a copy-pasted assignment that should have targeted a different variable). + +## How to fix + +Before: +```cpp +void f(int i) { + i = 1; + i = 1; // <- the first assignment is pointless +} +``` + +After: +```cpp +void f(int i) { + i = 1; +} +``` + +## Related checkers + +- [redundantAssignInSwitch.md](redundantAssignInSwitch.md) - the same idea, but across fall-through `switch` cases. +- [redundantInitialization.md](redundantInitialization.md) - the same idea, but for a variable's initializer value. +- [redundantCopyLocalConst.md](redundantCopyLocalConst.md) - a related redundant-copy check for `const` variables. diff --git a/man/checkers/redundantBitwiseOperationInSwitch.md b/man/checkers/redundantBitwiseOperationInSwitch.md new file mode 100644 index 00000000000..23d61a5d227 --- /dev/null +++ b/man/checkers/redundantBitwiseOperationInSwitch.md @@ -0,0 +1,52 @@ +# redundantBitwiseOperationInSwitch + +**Message**: Redundant bitwise operation on 'x' in 'switch' statement. 'break;' missing?
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +The same bitwise assignment (`x |= 1`, `x &= 1`, ...) is applied to a variable twice across +fall-through `switch` cases, with nothing reading the value in between. + +## Motivation + +Just like a redundant plain assignment, this is usually a sign of a missing `break;` between two +`case` labels - the first bitwise operation has no effect once the identical one runs again in the +next case. + +## How to fix + +Before: +```cpp +void foo(int a) { + int y = 1; + switch (a) { + case 2: + y |= 3; // <- same operation repeated in case 3 + case 3: + y |= 3; + break; + } +} +``` + +After: +```cpp +void foo(int a) { + int y = 1; + switch (a) { + case 2: + y |= 3; + break; + case 3: + y |= 3; + break; + } +} +``` + +## Related checkers + +- [redundantAssignInSwitch.md](redundantAssignInSwitch.md) - the plain-assignment equivalent in a `switch`. diff --git a/man/checkers/redundantCondition.md b/man/checkers/redundantCondition.md new file mode 100644 index 00000000000..e6066811f32 --- /dev/null +++ b/man/checkers/redundantCondition.md @@ -0,0 +1,42 @@ +# redundantCondition + +**Message**: Redundant condition: The condition 'x != 4' is redundant since 'x == 3' is sufficient.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +Two comparisons on the same variable, joined by `&&`/`||`, where one comparison is already implied by +the other and adds nothing. + +## Motivation + +If `x == 3` is true, `x != 4` is automatically true too - so `(x == 3) && (x != 4)` behaves exactly like +`x == 3` alone. The extra comparison doesn't change what the code does, only makes it longer and harder +to read, and can suggest a stricter check was intended than what's actually enforced. + +This check may need `--check-level=exhaustive` to see every case. + +## How to fix + +Before: +```cpp +void f(int x, int a) { + if ((x==3) && (x!=4)) // <- 'x != 4' adds nothing once 'x == 3' is true + a++; +} +``` + +After: +```cpp +void f(int x, int a) { + if (x==3) + a++; +} +``` + +## Related checkers + +- [incorrectLogicOperator.md](incorrectLogicOperator.md) - the same style of two-comparisons-on-one-variable + analysis, but for when the combination is always entirely true or entirely false. diff --git a/man/checkers/redundantContinue.md b/man/checkers/redundantContinue.md new file mode 100644 index 00000000000..fe5208c340e --- /dev/null +++ b/man/checkers/redundantContinue.md @@ -0,0 +1,44 @@ +# redundantContinue + +**Message**: 'continue' is redundant since it is the last statement in a loop.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A `continue;` is the last statement in a loop body - the loop was about to move to its next iteration +anyway, so it has no effect. + +## Motivation + +A `continue;` in this position doesn't change what the code does, but it can mislead a reader into +thinking it matters, or that it's guarding something that comes after it in the loop body (when nothing +does). + +## How to fix + +Before: +```cpp +#include +void f() { + for (int i = 0; i < 10; ++i) { + printf("i = %d\n", i); + continue; // <- this is already the end of the loop body + } +} +``` + +After: +```cpp +#include +void f() { + for (int i = 0; i < 10; ++i) { + printf("i = %d\n", i); + } +} +``` + +## Related checkers + +- [unreachableCode.md](unreachableCode.md) - code that can never run at all, rather than a no-op statement. diff --git a/man/checkers/redundantCopy.md b/man/checkers/redundantCopy.md new file mode 100644 index 00000000000..909454d8747 --- /dev/null +++ b/man/checkers/redundantCopy.md @@ -0,0 +1,41 @@ +# redundantCopy + +**Message**: Buffer 'x' is being written before its old content has been used.
+**Category**: Code Quality
+**Severity**: Performance
+**Language**: C/C++ + +## Description + +A buffer is written, then written again before its first content is ever read - the buffer equivalent +of a redundant variable assignment. In current versions of cppcheck, this specific message is not +actually produced for any input: the code path that would emit it exists in the source but isn't +reachable in practice, so don't expect a warning here even for code that matches the pattern below. + +## Motivation + +Writing to a buffer twice with nothing reading it in between wastes the work of the first write, which +is the real, intended idea behind this check - a buffer written twice (by `memset`/`strcpy`/similar) +with no read in between, including across fall-through `switch` cases. If you're looking for the +closest actively-working equivalent, see [redundantCopyLocalConst.md](redundantCopyLocalConst.md) (for +`const` variables) or [redundantAssignment.md](redundantAssignment.md) (the same idea for a plain +variable instead of a buffer). + +## How to fix + +Remove or combine the redundant write so the buffer is only written once before it's read: + +```cpp +#include +void bar(); +void f() { + char a[10]; + memset(a, 0, 10); + bar(); +} +``` + +## Related checkers + +- [redundantAssignment.md](redundantAssignment.md) - the same idea for a plain variable instead of a buffer. +- [redundantCopyLocalConst.md](redundantCopyLocalConst.md) - a different, currently-working redundant-copy check for `const` variables. diff --git a/man/checkers/redundantCopyLocalConst.md b/man/checkers/redundantCopyLocalConst.md new file mode 100644 index 00000000000..3ffb3f4214f --- /dev/null +++ b/man/checkers/redundantCopyLocalConst.md @@ -0,0 +1,39 @@ +# redundantCopyLocalConst + +**Message**: Use const reference for 'x' to avoid unnecessary data copying.
+**Category**: Performance
+**Severity**: Performance
+**Language**: C/C++ + +## Description + +A local variable is declared `const` and initialized as a copy of an existing object that outlives it - +since the copy is never modified, a `const` reference would do the same job without copying. + +## Motivation + +Copying an object (a `std::string`, a container, any non-trivial type) costs time and memory for no +benefit when the copy is only ever read. A `const&` avoids the copy entirely while behaving identically +from the reader's point of view. + +## How to fix + +Before: +```cpp +#include +void f(std::string str) { + std::string s2 = str; // <- 's2' is never modified +} +``` + +After: +```cpp +#include +void f(const std::string& str) { + const std::string& s2 = str; +} +``` + +## Related checkers + +- [redundantCopy.md](redundantCopy.md) - the buffer-write equivalent of this idea. diff --git a/man/checkers/redundantIfRemove.md b/man/checkers/redundantIfRemove.md new file mode 100644 index 00000000000..7fc75a16340 --- /dev/null +++ b/man/checkers/redundantIfRemove.md @@ -0,0 +1,43 @@ +# redundantIfRemove + +**Message**: Redundant checking of STL container element existence before removing it.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +`container.find(x) != container.end()` (or `container.find(x) != container.npos`) is checked before +calling `container.remove(x)` - `remove()` on an absent element is a harmless no-op, so the check adds +nothing. + +## Motivation + +The existence check reads as if it's guarding against a problem, but `remove()` on an element that +isn't there simply does nothing - it's not an error condition that needs to be avoided. The check is +pure overhead: an extra lookup, and an extra branch for a reader to understand, for no behavioral +difference. + +## How to fix + +Before: +```cpp +#include +void f(std::string haystack, std::string needle) { + if (haystack.find(needle) != haystack.end()) // <- remove() is safe on a miss + haystack.remove(needle); +} +``` + +After: +```cpp +#include +void f(std::string haystack, std::string needle) { + haystack.remove(needle); +} +``` + +## Related checkers + +- [stlFindInsert.md](stlFindInsert.md) - the same kind of redundant-check-before-a-safe-operation + pattern, for `insert()` instead of `remove()`. diff --git a/man/checkers/redundantInitialization.md b/man/checkers/redundantInitialization.md new file mode 100644 index 00000000000..734012a41a5 --- /dev/null +++ b/man/checkers/redundantInitialization.md @@ -0,0 +1,41 @@ +# redundantInitialization + +**Message**: Redundant initialization for 'x'. The initialized value is overwritten before it is read.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A variable's initializer value is overwritten before it's read - the same idea as a redundant +assignment, but for the value given at declaration. + +## Motivation + +Giving a variable an initial value only for it to be immediately replaced wastes the work of computing +that initial value, and can mislead a reader into thinking the initializer matters. + +## How to fix + +Before: +```cpp +#include +std::string f() { + std::string s = "abc"; + s = "def"; // <- the "abc" initializer is never read + return s; +} +``` + +After: +```cpp +#include +std::string f() { + std::string s = "def"; + return s; +} +``` + +## Related checkers + +- [redundantAssignment.md](redundantAssignment.md) - the same idea for a later assignment instead of the initializer. diff --git a/man/checkers/redundantNextPrevious.md b/man/checkers/redundantNextPrevious.md new file mode 100644 index 00000000000..78790345927 --- /dev/null +++ b/man/checkers/redundantNextPrevious.md @@ -0,0 +1,35 @@ +# redundantNextPrevious + +**Message**: Statement is redundant, code can be simplified.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ (cppcheck's own source code only) + +## Description + +A chain like `tok->next()->previous()` can be simplified (here, to just `tok`). + +This is not a general-purpose checker for arbitrary C/C++ programs - see +[simplePatternError.md](simplePatternError.md) for the shared background on this internal, +cppcheck-source-only checker. + +## Motivation + +Walking forward and then immediately back (or vice versa) always returns to the starting token - the +extra calls add nothing but noise and a small amount of wasted work. + +## How to fix + +Before: +```cpp +return tok->next()->previous(); // <- redundant +``` + +After: +```cpp +return tok; +``` + +## Related checkers + +- [redundantTokCheck.md](redundantTokCheck.md) - a different kind of redundant code involving the same `Token`/pattern-matching API. diff --git a/man/checkers/redundantPointerOp.md b/man/checkers/redundantPointerOp.md new file mode 100644 index 00000000000..716d09d1995 --- /dev/null +++ b/man/checkers/redundantPointerOp.md @@ -0,0 +1,32 @@ +# redundantPointerOp + +**Message**: Redundant pointer operation on 'p' - it's already a pointer.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +`&*p` or `*&p` is used where `p` is already exactly what's needed - taking the address of a dereference +(or vice versa) just to undo it. + +## Motivation + +This pattern has no effect beyond what writing `p` directly would achieve, and only adds noise that +makes a reader wonder if there's a subtlety being expressed that isn't actually there. + +## How to fix + +Before: +```cpp +int *f(int *x) { + return &*x; // <- 'x' is already a pointer +} +``` + +After: +```cpp +int *f(int *x) { + return x; +} +``` diff --git a/man/checkers/redundantTokCheck.md b/man/checkers/redundantTokCheck.md new file mode 100644 index 00000000000..73ce134ed3e --- /dev/null +++ b/man/checkers/redundantTokCheck.md @@ -0,0 +1,36 @@ +# redundantTokCheck + +**Message**: Unnecessary check of token; 'Token::Match()' already checks if it is a nullptr.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ (cppcheck's own source code only) + +## Description + +Code like `if (tok && Token::Match(tok, "..."))` - the `tok &&` part is redundant, since these +`Token::*` functions already return false/no-match when given a null token. + +This is not a general-purpose checker for arbitrary C/C++ programs - see +[simplePatternError.md](simplePatternError.md) for the shared background on this internal, +cppcheck-source-only checker. + +## Motivation + +Since `Token::Match()` and its relatives already handle a null token safely by returning "no match", an +explicit null check right before calling one of them adds nothing but visual clutter. + +## How to fix + +Before: +```cpp +if (tok && Token::Match(tok, "foo")) {} // <- 'tok &&' is unnecessary +``` + +After: +```cpp +if (Token::Match(tok, "foo")) {} +``` + +## Related checkers + +- [redundantNextPrevious.md](redundantNextPrevious.md) - a different kind of redundant code involving the same `Token` API. diff --git a/man/checkers/rethrowNoCurrentException.md b/man/checkers/rethrowNoCurrentException.md new file mode 100644 index 00000000000..8fd0e859bae --- /dev/null +++ b/man/checkers/rethrowNoCurrentException.md @@ -0,0 +1,39 @@ +# rethrowNoCurrentException + +**Message**: Rethrowing current exception with 'throw;', it seems there is no current exception to rethrow. If there is no current exception this calls std::terminate().
+**Category**: Correctness
+**Severity**: Error
+**Language**: C++ only + +## Description + +A bare `throw;` appears somewhere that isn't inside a `catch` block. If there's no exception currently +being handled, this calls `std::terminate()`. + +## Motivation + +A bare `throw;` only makes sense while an exception is actively being handled (inside a `catch` block, +or a function called from one, with the exception still propagating) - used anywhere else, there's no +exception to rethrow, and the C++ standard says the result is a call to `std::terminate()`, aborting the +program. + +## How to fix + +Before: +```cpp +void f() { + throw; // <- not inside a catch block +} +``` + +After: +```cpp +void doWork(); +void f() { + try { + doWork(); + } catch (...) { + throw; // fine here: there is a current exception being handled + } +} +``` diff --git a/man/checkers/returnByReference.md b/man/checkers/returnByReference.md new file mode 100644 index 00000000000..8909020f844 --- /dev/null +++ b/man/checkers/returnByReference.md @@ -0,0 +1,37 @@ +# returnByReference + +**Message**: Function returns a copy instead of returning by const reference.
+**Category**: Performance
+**Severity**: Performance
+**Language**: C++ + +## Description + +A getter function returns a large member (or a container/string) by value, making an unnecessary copy +on every call, when it could return a `const` reference instead. + +## Motivation + +Returning a container or string by value copies its entire contents on every call, even though the +caller almost always just wants to look at the member that's already sitting inside the object. +Returning a `const&` instead avoids that copy entirely. + +## How to fix + +Before: +```cpp +#include +struct S { + std::string s; + std::string getS() const { return s; } // <- returnByReference: copies 's' on every call +}; +``` + +After: +```cpp +#include +struct S { + std::string s; + const std::string& getS() const { return s; } +}; +``` diff --git a/man/checkers/returnDanglingLifetime.md b/man/checkers/returnDanglingLifetime.md new file mode 100644 index 00000000000..9d2cb31e02a --- /dev/null +++ b/man/checkers/returnDanglingLifetime.md @@ -0,0 +1,43 @@ +# returnDanglingLifetime + +**Message**: Returning pointer to local variable 'num' that will be invalid when returning.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A function returns a pointer, iterator, or a lambda that captures one, which refers to a local +variable, array, or container. + +## Motivation + +Using a pointer after the local variable it points to has gone out of scope is undefined behaviour. +Because the memory involved is usually still intact for a little while afterwards, this kind of bug +often "works" in testing and then fails unpredictably once something else reuses that memory - which +makes it worth catching at analysis time instead of at runtime. + +## How to fix + +Before: +```cpp +int* foo() { + int num = 2; + return # // <- returnDanglingLifetime +} +``` + +After: +```cpp +int* foo() { + static int num = 2; + return # +} +``` + +## Related checkers + +- [returnReference.md](returnReference.md) - the same idea, but for a function whose declared return + type is a reference rather than a pointer. +- [autoVariables.md](autoVariables.md) - the same idea, but escaping through a function parameter + instead of `return`. diff --git a/man/checkers/returnNonBoolInBooleanFunction.md b/man/checkers/returnNonBoolInBooleanFunction.md new file mode 100644 index 00000000000..6e882f490ce --- /dev/null +++ b/man/checkers/returnNonBoolInBooleanFunction.md @@ -0,0 +1,33 @@ +# returnNonBoolInBooleanFunction + +**Message**: Non-boolean value returned from function returning bool
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +A function declared to return `bool` returns a value that cppcheck can determine is not `0` or `1`. + +## Motivation + +A `bool`-returning function is expected to always produce `true`/`false`; returning a value outside +that range (after the implicit conversion, anything nonzero collapses to `true` anyway) usually signals +that the wrong expression was returned, or that the function's intent was actually to return a count or +status code. + +## How to fix + +Before: +```cpp +bool f() { + return 2; // <- collapses to 'true', likely not intended +} +``` + +After: +```cpp +bool f() { + return true; +} +``` diff --git a/man/checkers/returnReference.md b/man/checkers/returnReference.md new file mode 100644 index 00000000000..874afee7887 --- /dev/null +++ b/man/checkers/returnReference.md @@ -0,0 +1,41 @@ +# returnReference + +**Message**: Reference to local variable returned.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +A function declared to return a reference returns a reference to a local variable. + +## Motivation + +A reference to a local variable becomes invalid the moment the function returns. Using the returned +reference afterwards is undefined behaviour, even though the memory is often still intact for a little +while, which is why this bug can appear to "work" in casual testing. + +## How to fix + +Before: +```cpp +int& foo() { + int num = 2; + return num; // <- returnReference +} +``` + +After: +```cpp +int foo() { + int num = 2; + return num; +} +``` + +## Related checkers + +- [returnTempReference.md](returnTempReference.md) - the same idea, but returning a reference to a + temporary object instead of a named local variable. +- [returnDanglingLifetime.md](returnDanglingLifetime.md) - the equivalent problem for a function that + returns a pointer/iterator instead of a reference. diff --git a/man/checkers/returnStdMoveLocal.md b/man/checkers/returnStdMoveLocal.md new file mode 100644 index 00000000000..00ac3f318cc --- /dev/null +++ b/man/checkers/returnStdMoveLocal.md @@ -0,0 +1,44 @@ +# returnStdMoveLocal + +**Message**: Using std::move for returning object by-value from function will affect copy elision optimization.
+**Category**: Code Quality
+**Severity**: Performance
+**Language**: C++ only + +## Description + +`return std::move(x);` is used for a local variable or temporary that's returned by value - this +defeats the compiler's copy elision (RVO/NRVO), which would otherwise avoid the copy entirely without +`std::move`. + +## Motivation + +When a local variable is returned by value, the compiler is allowed (and, since C++17 in many cases, +required) to construct it directly in the caller's storage, skipping any copy or move entirely. Wrapping +the return in `std::move()` defeats this optimization by forcing a move instead, which is strictly worse +than the elision the compiler would have done on its own. + +## How to fix + +Before: +```cpp +struct A{}; +A f() { + A var; + return std::move(var); // <- defeats copy elision +} +``` + +After: +```cpp +struct A{}; +A f() { + A var; + return var; +} +``` + +## Related checkers + +- [useStandardLibrary.md](useStandardLibrary.md) - an unrelated performance suggestion in the same + checker: replacing a hand-written copy loop with a standard library call. diff --git a/man/checkers/returnTempReference.md b/man/checkers/returnTempReference.md new file mode 100644 index 00000000000..13b3842108d --- /dev/null +++ b/man/checkers/returnTempReference.md @@ -0,0 +1,42 @@ +# returnTempReference + +**Message**: Reference to temporary returned.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +A function declared to return a reference returns a reference to a temporary object. + +## Motivation + +A temporary object is destroyed at the end of the full expression that created it (with some narrow +lifetime-extension exceptions that don't apply once the reference is returned out of the function). +Returning a reference to one, and then using it, is a use of an already-destroyed object. + +## How to fix + +Before: +```cpp +int get_value(); +const int &get_reference() { + const int &x = get_value(); // binds to a temporary + return x; // <- returnTempReference +} +``` + +After: +```cpp +int get_value(); +int get_reference() { + return get_value(); +} +``` + +## Related checkers + +- [returnReference.md](returnReference.md) - the same idea, but returning a reference to a named local + variable instead of a temporary. +- [danglingTempReference.md](danglingTempReference.md) - the same underlying temporary-lifetime + mistake, used within the same function rather than returned from it. diff --git a/man/checkers/sameIteratorExpression.md b/man/checkers/sameIteratorExpression.md new file mode 100644 index 00000000000..60181cd66c4 --- /dev/null +++ b/man/checkers/sameIteratorExpression.md @@ -0,0 +1,40 @@ +# sameIteratorExpression + +**Message**: Same iterators expression are used for algorithm.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +The exact same iterator expression is passed as both the "begin" and "end" argument of an algorithm, +making the range empty. + +## Motivation + +A range whose begin and end are the same iterator is always empty, so the algorithm call does nothing - +this is normally a sign that one of the two arguments was meant to be different (for example the end of +the container, not its beginning again). + +## How to fix + +Before: +```cpp +#include +void f(int a[10]) { + if (std::any_of(&a[0], &a[0], [](int x){return x > 0;})) {} // <- sameIteratorExpression: an empty range +} +``` + +After: +```cpp +#include +void f(int a[10]) { + if (std::any_of(&a[0], &a[10], [](int x){return x > 0;})) {} +} +``` + +## Related checkers + +- [mismatchingContainers.md](mismatchingContainers.md) - the opposite mistake: two iterators that + belong to different containers being used together. diff --git a/man/checkers/seekOnAppendedFile.md b/man/checkers/seekOnAppendedFile.md new file mode 100644 index 00000000000..869c589a0ce --- /dev/null +++ b/man/checkers/seekOnAppendedFile.md @@ -0,0 +1,49 @@ +# seekOnAppendedFile + +**Message**: Repositioning operation performed on a file opened in append mode has no effect.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A repositioning call (`fseek()`, `fsetpos()`, `rewind()`) is made on a file that was opened in append +mode (`"a"`) - every write in append mode always goes to the end of the file regardless, so +repositioning has no effect. + +## Motivation + +Code that repositions before writing to an append-mode file is misleading: the reposition is silently +ignored, so the code doesn't do what it looks like it does, and the call is dead weight. + +## How to fix + +Before: +```cpp +#include +void f() { + FILE *fp = fopen("a.txt", "a"); + if (!fp) return; + fseek(fp, 0, SEEK_SET); // <- has no effect in append mode + fwrite("x", 1, 1, fp); + fclose(fp); +} +``` + +After: +```cpp +#include +void f() { + FILE *fp = fopen("a.txt", "a"); + if (!fp) return; + fwrite("x", 1, 1, fp); + fclose(fp); +} +``` + +## Related checkers + +- [useClosedFile.md](useClosedFile.md), [readWriteOnlyFile.md](readWriteOnlyFile.md), + [writeReadOnlyFile.md](writeReadOnlyFile.md), [IOWithoutPositioning.md](IOWithoutPositioning.md), + [incompatibleFileOpen.md](incompatibleFileOpen.md) - other checks that follow the same `FILE*` through + a function. diff --git a/man/checkers/selfAssignment.md b/man/checkers/selfAssignment.md new file mode 100644 index 00000000000..1023a59297c --- /dev/null +++ b/man/checkers/selfAssignment.md @@ -0,0 +1,34 @@ +# selfAssignment + +**Message**: Redundant assignment of 'x' to itself.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A variable is assigned to itself (`x = x;`), which has no effect. + +## Motivation + +An assignment that reads and writes the exact same variable does nothing useful, and is often a +leftover from a refactor or a typo for an assignment that was meant to involve a different variable. + +## How to fix + +Before: +```cpp +void foo() { + int x = 1; + x = x; // <- has no effect +} +``` + +After: +```cpp +#include +void foo() { + int x = 1; + printf("%d", x); +} +``` diff --git a/man/checkers/selfInitialization.md b/man/checkers/selfInitialization.md new file mode 100644 index 00000000000..1fecc4c2a79 --- /dev/null +++ b/man/checkers/selfInitialization.md @@ -0,0 +1,47 @@ +# selfInitialization + +**Message**: Member variable 'i' is initialized by itself.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +A member is initialized with itself in the initializer list (`Fred() : i(i) {}` with no same-named +constructor parameter) - it reads its own not-yet-initialized value. + +## Motivation + +`i(i)` in a member-initializer list refers to the member `i` on both sides (there's no constructor +parameter with that name to shadow it) - the member ends up "initialized" by reading its own +uninitialized value, which is undefined behaviour and almost certainly a typo for a differently-named +constructor parameter. + +## How to fix + +Give the constructor parameter a distinct name, or otherwise supply a real initial value. + +Before: +```cpp +class Fred { + int i; +public: + Fred() : i(i) { // <- reads its own uninitialized value + } +}; +``` + +After: +```cpp +class Fred { + int i; +public: + Fred() : i(0) { + } +}; +``` + +## Related checkers + +- [initializerList.md](initializerList.md) - the related, broader family of member-initializer-list + ordering pitfalls. diff --git a/man/checkers/shadowArgument.md b/man/checkers/shadowArgument.md new file mode 100644 index 00000000000..6556f2449d6 --- /dev/null +++ b/man/checkers/shadowArgument.md @@ -0,0 +1,36 @@ +# shadowArgument + +**Message**: Local variable 'x' shadows outer argument
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +A local variable has the same name as one of the function's own parameters, which is still in scope - +the local one hides the parameter for the rest of its scope. + +## Motivation + +A shadowed name invites confusion about which variable a later line actually refers to, and is a +standing invitation to edit the wrong one. + +## How to fix + +Before: +```cpp +#include +void f(int x) { { int x = 1; printf("%d", x); } } // <- hides the parameter 'x' +``` + +After: +```cpp +#include +void f(int x) { { int y = 1; printf("%d", y); } } +``` + +## Related checkers + +- [shadowVariable.md](shadowVariable.md) - the same idea, when the outer name is a variable. +- [shadowFunction.md](shadowFunction.md) - the same idea, when the outer name is a function. +- [shadowMember.md](shadowMember.md) - the same idea, when the outer name is a class/struct member. diff --git a/man/checkers/shadowFunction.md b/man/checkers/shadowFunction.md new file mode 100644 index 00000000000..39ee40e7385 --- /dev/null +++ b/man/checkers/shadowFunction.md @@ -0,0 +1,39 @@ +# shadowFunction + +**Message**: Local variable 'x' shadows outer function
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +A local variable (or parameter) has the same name as a function that is still visible - the local one +hides the function for the rest of its scope. + +## Motivation + +A shadowed name invites confusion about which one a later line actually refers to, and is a standing +invitation to edit the wrong thing - or to be surprised that the function can no longer be called by +its plain name in that scope. + +## How to fix + +Before: +```cpp +#include +int getA(); +void f() { int getA = 1; printf("%d", getA); } // <- hides the function 'getA' +``` + +After: +```cpp +#include +int getA(); +void f() { int result = 1; printf("%d", result); } +``` + +## Related checkers + +- [shadowVariable.md](shadowVariable.md) - the same idea, when the outer name is a variable. +- [shadowArgument.md](shadowArgument.md) - the same idea, when the outer name is a function parameter. +- [shadowMember.md](shadowMember.md) - the same idea, when the outer name is a class/struct member. diff --git a/man/checkers/shadowMember.md b/man/checkers/shadowMember.md new file mode 100644 index 00000000000..e85f9f66829 --- /dev/null +++ b/man/checkers/shadowMember.md @@ -0,0 +1,42 @@ +# shadowMember + +**Message**: Local variable 'x' shadows outer member
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +A local variable inside a member function has the same name as a class/struct member - the local one +hides the member for the rest of its scope. + +## Motivation + +A shadowed name invites confusion about which one a later line actually refers to, and is a standing +invitation to edit the local variable when the member was intended, or vice versa. + +## How to fix + +Before: +```cpp +#include +struct S { + int i{}; + void f() { int i = 1; printf("%d", i); } // <- hides the member 'i' +}; +``` + +After: +```cpp +#include +struct S { + int i{}; + void f() { int localCount = 1; printf("%d", localCount); } +}; +``` + +## Related checkers + +- [shadowVariable.md](shadowVariable.md) - the same idea, when the outer name is a variable. +- [shadowArgument.md](shadowArgument.md) - the same idea, when the outer name is a function parameter. +- [shadowFunction.md](shadowFunction.md) - the same idea, when the outer name is a function. diff --git a/man/checkers/shadowVariable.md b/man/checkers/shadowVariable.md new file mode 100644 index 00000000000..ff589e7b428 --- /dev/null +++ b/man/checkers/shadowVariable.md @@ -0,0 +1,38 @@ +# shadowVariable + +**Message**: Local variable 'x' shadows outer variable
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +A local variable (or parameter) has the same name as an outer variable that is still in scope - the +inner one hides the outer one for the rest of its scope. + +## Motivation + +A shadowed name invites confusion about which variable a later line actually refers to, and is a +standing invitation to edit the wrong one. + +## How to fix + +Before: +```cpp +#include +int x; +void f() { int x = 1; printf("%d", x); } // <- hides the global 'x' +``` + +After: +```cpp +#include +int x; +void f() { int y = 1; printf("%d", y); } +``` + +## Related checkers + +- [shadowArgument.md](shadowArgument.md) - the same idea, when the outer name is a function parameter. +- [shadowFunction.md](shadowFunction.md) - the same idea, when the outer name is a function. +- [shadowMember.md](shadowMember.md) - the same idea, when the outer name is a class/struct member. diff --git a/man/checkers/shiftNegative.md b/man/checkers/shiftNegative.md new file mode 100644 index 00000000000..403812b86c5 --- /dev/null +++ b/man/checkers/shiftNegative.md @@ -0,0 +1,38 @@ +# shiftNegative + +**Message**: Shifting by a negative value is undefined behaviour
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A bitwise shift (`<<`/`>>`) is done by a negative amount, which is undefined behaviour. + +## Motivation + +The C/C++ standards leave the result of shifting by a negative amount undefined - the program's actual +behaviour is not guaranteed and can change with the compiler, optimization level, or platform. + +## How to fix + +Before: +```cpp +void foo() { + int a; a = 123; + (void)(a << -1); // <- shifting by a negative amount +} +``` + +After: +```cpp +void foo() { + int a; a = 123; + (void)(a << 1); +} +``` + +## Related checkers + +- [shiftNegativeLHS.md](shiftNegativeLHS.md) - the same kind of undefined behaviour, but for shifting a + negative value rather than shifting by a negative amount. diff --git a/man/checkers/shiftNegativeLHS.md b/man/checkers/shiftNegativeLHS.md new file mode 100644 index 00000000000..0b2e0ecf7da --- /dev/null +++ b/man/checkers/shiftNegativeLHS.md @@ -0,0 +1,40 @@ +# shiftNegativeLHS + +**Message**: Shifting a negative value is technically undefined behaviour
+**Category**: Undefined Behaviour
+**Severity**: Portability
+**Language**: C/C++ + +## Description + +The value being shifted (rather than the shift amount) is negative, which is technically undefined +behaviour, even though many compilers implement it as a predictable sign-preserving shift. + +## Motivation + +The C/C++ standards leave the result of shifting a negative value undefined, even though in practice +most compilers implement a predictable arithmetic (sign-preserving) shift - code relying on that isn't +guaranteed to behave the same way on every compiler or platform. + +## How to fix + +Before: +```cpp +void foo() { + int x; + x = (-10+2) << 3; // <- shifting a negative value +} +``` + +After: +```cpp +void foo() { + int x; + x = (10-2) << 3; +} +``` + +## Related checkers + +- [shiftNegative.md](shiftNegative.md) - the same kind of undefined behaviour, but for shifting by a + negative amount rather than shifting a negative value. diff --git a/man/checkers/shiftTooManyBits.md b/man/checkers/shiftTooManyBits.md new file mode 100644 index 00000000000..8b33fb08a67 --- /dev/null +++ b/man/checkers/shiftTooManyBits.md @@ -0,0 +1,52 @@ +# shiftTooManyBits and shiftTooManyBitsSigned + +**Message**: Shifting 32-bit value by 40 bits is undefined behaviour
+**Category**: Undefined Behaviour
+**Severity**: Error/Warning/Portability
+**Language**: C/C++ + +## Description + +This checker warns when a bitwise shift (`<<`, `>>`, `<<=`, `>>=`) shifts a value by a number of bits +that is greater than or equal to the width of the (promoted) left-hand side type. + +There are two related warnings: +- `shiftTooManyBits`: the shift amount is greater than or equal to the bit width of the type. This is + undefined behaviour according to the C/C++ standard. +- `shiftTooManyBitsSigned`: the left-hand side type is signed and the shift amount is exactly + `bits - 1`. Shifting a signed type this far is undefined behaviour before C++14, and + implementation-defined behaviour from C++14 onwards - in the C++14-and-later case this is reported + as a `portability` message instead of a `warning`, since the behaviour is now defined, just not the + same on every platform. + +The number of bits of the left-hand side type is determined from the platform settings +(`int_bit`, `long_bit`, `long_long_bit`), so this checker requires a platform to be configured. + +## Motivation + +Shifting a value by more bits than its type contains is undefined behaviour. The result is +unpredictable and can vary between compilers, compiler versions and optimization settings. + +cppcheck only warns when it can work out the shift amount directly from the code - a literal value, or +a condition earlier in the function that pins it down. If the amount can't be determined that way (for +example it's guarded by several combined conditions, or hidden inside a macro-like call), no warning is +given even though the shift could still be too large at runtime. + +## How to fix + +Make sure the shift amount is smaller than the bit width of the left-hand side type. This often means +casting the left-hand side to a wider type before shifting, or fixing a wrong shift amount. + +Before: +```cpp +int32_t foo(int32_t x) { + return x << 40; // <- shiftTooManyBits, 'int' is only 32 bits +} +``` + +After: +```cpp +int64_t foo(int32_t x) { + return (int64_t)x << 40; // <- widen the operand before shifting +} +``` diff --git a/man/checkers/signConversion.md b/man/checkers/signConversion.md new file mode 100644 index 00000000000..d371b35ac3e --- /dev/null +++ b/man/checkers/signConversion.md @@ -0,0 +1,80 @@ +# signConversion + +**Message**: Expression 'x' can have a negative value. That is converted to an unsigned value and used in an unsigned calculation.
+**Category**: Type Safety
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +This checker detects arithmetic expressions (other than `+`/`-`) whose result type is unsigned, where +one of the operands can have a negative value. When that happens, the negative operand is implicitly +converted to an unsigned value before the calculation, which can produce a very large value instead +of the intended negative one. + +If the negative value is a known constant, the message states the operand "has" a negative value; +otherwise it states the operand "can have" a negative value, based on cppcheck's analysis of the +surrounding code. + +This checker only runs when the `warning` severity is enabled. + +## Motivation + +Implicit conversion of a negative value to an unsigned type is well-defined (it wraps around to a +large positive value), but it rarely matches programmer intent and is a common source of bugs, for +example in loop conditions, index calculations and buffer size calculations. + +## Design note: `+` and `-` are intentionally not checked + +This checker does not warn about `+` and `-`, even though the same implicit negative-to-unsigned +conversion happens there too. This is intentional, not an oversight: for `+` and `-`, adding an +explicit cast would not change the computed result at all. + +```cpp +void f(int x) { // x can be negative + unsigned int a = x + 5U; // implicit conversion + unsigned int b = (unsigned int)x + 5U; // explicit cast - identical result +} +``` + +Two's-complement addition and subtraction commute with truncation to an unsigned width, so the +implicit conversion already computes exactly what an explicit `(unsigned int)` cast would. There is +nothing for an explicit cast to fix or clarify, so a warning here would not be actionable. Other +arithmetic operators (`*`, `/`, `%`, shifts, etc.) do not have this property in the same way and are +still checked. + +## How to fix + +You can fix these warnings by: +1. Making sure the operand cannot be negative in that context (fix upstream logic) +2. Using a signed type for the calculation +3. Adding an explicit check or cast to make the intended behaviour clear + +Note: cppcheck only warns when it can actually determine that the operand can be negative - either +from a known value at the call site (as below), or from a condition earlier in the same function. A +plain `int` parameter with no callers and no surrounding condition gives cppcheck no evidence that it +can be negative, so it is not reported. + +Before: +```cpp +unsigned int calcSize(int count, unsigned int itemSize) { + return count * itemSize; // <- signConversion, count can have a negative value +} + +void caller() { + calcSize(-4, 4); +} +``` + +After: +```cpp +unsigned int calcSize(int count, unsigned int itemSize) { + if (count < 0) + return 0; + return count * itemSize; +} + +void caller() { + calcSize(-4, 4); +} +``` diff --git a/man/checkers/signedCharArrayIndex.md b/man/checkers/signedCharArrayIndex.md new file mode 100644 index 00000000000..9f42f4c4e3f --- /dev/null +++ b/man/checkers/signedCharArrayIndex.md @@ -0,0 +1,55 @@ +# signedCharArrayIndex and unknownSignCharArrayIndex + +**Message**: Signed 'char' type used as array index.
+**Category**: Undefined Behaviour/Portability
+**Severity**: Warning/Portability
+**Language**: C/C++ + +## Description + +- `signedCharArrayIndex` (warning): an array is indexed with a `char` known to be signed, and the index + can be 128 or more - once sign-extended, a value like `200` becomes negative, so the access lands + before the start of the array instead of near its expected position. This ID specifically covers the + case where cppcheck knows the index is a signed `char` that *can* reach 128 or more, but can't work + out its exact wrapped-around value; when the concrete wrapped value (or a condition it depends on) is + known, the same bug is instead reported as the more specific [negativeIndex.md](negativeIndex.md) or + [arrayIndexOutOfBounds.md](arrayIndexOutOfBounds.md), which is what happens in most real code. +- `unknownSignCharArrayIndex` (portability): the same situation, but for a plain `char` whose signedness + the C/C++ standard leaves up to the compiler - the exact same source code indexes the array + differently depending on which platform/compiler it's built with. + +## Motivation + +On many platforms `char` can hold negative values (typically -128..127 instead of 0..255). A byte value +of 128 or more, once treated as a signed `char`, becomes negative - so using it directly as an array +index produces a negative index instead of the expected large-but-positive one, reading or writing +before the start of the array. + +## How to fix + +Use `unsigned char` for a value that's meant to index into a table by its raw byte value. + +Before: +```cpp +int buf[256]; +void foo() { + char ch = 0x80; + buf[ch] = 0; // <- negative on platforms where 'char' is signed +} +``` + +After: +```cpp +int buf[256]; +void foo() { + unsigned char ch = 0x80; + buf[ch] = 0; +} +``` + +## Related checkers + +- [negativeIndex.md](negativeIndex.md) and [arrayIndexOutOfBounds.md](arrayIndexOutOfBounds.md) - the + more specific IDs this checker's finding is usually superseded by. +- [charBitOp.md](charBitOp.md) - the same signed-`char` sign-extension pitfall, but for a bitwise + operation instead of an array index. diff --git a/man/checkers/simplePatternError.md b/man/checkers/simplePatternError.md new file mode 100644 index 00000000000..96e4a9aac26 --- /dev/null +++ b/man/checkers/simplePatternError.md @@ -0,0 +1,42 @@ +# simplePatternError + +**Message**: Found simple pattern inside Token::Match() call: ";"
+**Category**: Code Quality
+**Severity**: Warning
+**Language**: C++ (cppcheck's own source code only) + +## Description + +A pattern given to `Token::Match()`/`Token::findmatch()` doesn't actually use any wildcard syntax, so +the cheaper `Token::simpleMatch()`/`Token::findsimplematch()` could be used instead. + +This is not a general-purpose checker for arbitrary C/C++ programs. It only looks for calls to +cppcheck's own internal pattern-matching API, and reports style/correctness issues in how that API is +used. It exists to help cppcheck's own contributors write correct and efficient code in the cppcheck +codebase, not to check any project being analyzed by cppcheck. + +It is only compiled in when cppcheck itself is built with the `CHECK_INTERNAL` macro defined - a normal +end-user build of cppcheck does not have it at all, and even a build that has it still needs +`--enable=internal` to turn it on. + +## Motivation + +`Token::Match()` interprets a small wildcard syntax in its pattern string, which costs more than a +plain text comparison. When a pattern doesn't use any of that syntax, `Token::simpleMatch()` does the +identical comparison faster. + +## How to fix + +Before: +```cpp +Token::Match(tok, ";"); // <- no wildcard syntax used +``` + +After: +```cpp +Token::simpleMatch(tok, ";"); +``` + +## Related checkers + +- [complexPatternError.md](complexPatternError.md) - the opposite mistake, using `simpleMatch()` with a pattern that needs wildcard interpretation. diff --git a/man/checkers/sizeofCalculation.md b/man/checkers/sizeofCalculation.md new file mode 100644 index 00000000000..012a6d4855d --- /dev/null +++ b/man/checkers/sizeofCalculation.md @@ -0,0 +1,34 @@ +# sizeofCalculation + +**Message**: Found calculation inside sizeof().
+**Category**: Correctness
+**Severity**: Warning (inconclusive)
+**Language**: C/C++ + +## Description + +An arithmetic/increment/decrement calculation appears inside `sizeof`, e.g. `sizeof(a + b)` or +`sizeof(x++)` - it looks like it runs, but it never does. + +## Motivation + +The operand of `sizeof` is normally not evaluated at all - only its type is used to compute the result. +A calculation written inside `sizeof` reads as if it executes (especially `x++`, which looks like it +must have a side effect), but it silently never runs, which is confusing for anyone who later expects +that side effect to have happened. + +## How to fix + +Before: +```cpp +void f(int a, int b) { + int s = sizeof(a + b); // <- the addition never actually happens +} +``` + +After: +```cpp +void f(int a, int b) { + int s = sizeof(a); +} +``` diff --git a/man/checkers/sizeofFunctionCall.md b/man/checkers/sizeofFunctionCall.md new file mode 100644 index 00000000000..9f3a55e320b --- /dev/null +++ b/man/checkers/sizeofFunctionCall.md @@ -0,0 +1,34 @@ +# sizeofFunctionCall + +**Message**: Found function call inside sizeof().
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A function call appears inside `sizeof`, e.g. `sizeof(foo())` - `foo()` is never actually called. + +## Motivation + +The operand of `sizeof` is normally not evaluated at all - only its type is used to compute the result. +A function call written inside `sizeof` reads as if it runs (and, worse, hides a call whose return +value or side effect might have been genuinely needed), but it silently never executes. + +## How to fix + +Before: +```cpp +int compute(); +void f() { + int s = sizeof(compute()); // <- compute() is never called +} +``` + +After: +```cpp +int compute(); +void f() { + int s = sizeof(decltype(compute())); +} +``` diff --git a/man/checkers/sizeofVoid.md b/man/checkers/sizeofVoid.md new file mode 100644 index 00000000000..a8fc7c79899 --- /dev/null +++ b/man/checkers/sizeofVoid.md @@ -0,0 +1,40 @@ +# sizeofVoid and sizeofDereferencedVoidPointer + +**Message**: Behaviour of 'sizeof(void)' is not covered by the ISO C standard.
+**Category**: Portability
+**Severity**: Portability
+**Language**: C/C++ + +## Description + +- `sizeofVoid`: `sizeof(void)` is used directly - this isn't defined by the C/C++ standard (only by a + GNU extension, which defines it as `1`). +- `sizeofDereferencedVoidPointer`: `sizeof(*p)` where `p` is `void*` - the same underlying problem, + reached by dereferencing a `void*` instead of naming `void` directly. + +## Motivation + +`void` has no size in standard C/C++ - `sizeof(void)` is only meaningful under a GNU extension (where +it's defined as `1`), so code relying on it is not portable to a strictly-standard-conforming compiler, +even though gcc/clang will happily accept it. + +## How to fix + +Before: +```cpp +void f(void *p) { + int s = sizeof(*p); // <- 'p' is void*, so this is sizeof(void) +} +``` + +After: +```cpp +void f(char *p) { + int s = sizeof(*p); +} +``` + +## Related checkers + +- [arithOperationsOnVoidPointer.md](arithOperationsOnVoidPointer.md) - another `void*`-specific, + non-standard construct (pointer arithmetic directly on `void*`). diff --git a/man/checkers/sizeofsizeof.md b/man/checkers/sizeofsizeof.md new file mode 100644 index 00000000000..86e2c17c4a9 --- /dev/null +++ b/man/checkers/sizeofsizeof.md @@ -0,0 +1,34 @@ +# sizeofsizeof + +**Message**: Calling 'sizeof' on 'sizeof'.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +`sizeof(sizeof(x))` - always the same as `sizeof(size_t)`, regardless of what `x` is. + +## Motivation + +`sizeof(...)` itself evaluates to a `size_t` value, so `sizeof` of that result is always just +`sizeof(size_t)` - a fixed, platform-defined constant that has nothing to do with `x`. Writing this +almost always means one `sizeof` too many was typed. + +## How to fix + +Before: +```cpp +void f() { + int a; + int s = sizeof(sizeof(a)); // <- always sizeof(size_t), regardless of 'a' +} +``` + +After: +```cpp +void f() { + int a; + int s = sizeof(a); +} +``` diff --git a/man/checkers/sizeofwithnumericparameter.md b/man/checkers/sizeofwithnumericparameter.md new file mode 100644 index 00000000000..5dabab68532 --- /dev/null +++ b/man/checkers/sizeofwithnumericparameter.md @@ -0,0 +1,33 @@ +# sizeofwithnumericparameter + +**Message**: Suspicious usage of 'sizeof' with a numeric constant as parameter.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +`sizeof` is applied to a plain numeric constant, e.g. `sizeof(10)`. + +## Motivation + +`sizeof` is nearly always resolved at compile time from the *type* of its operand, not its value - a +numeric literal's type is whatever the compiler infers for it (usually `int`), not something meaningful +to size a buffer or count elements by. This is almost always a mistake for a type name, or a variable +whose size was actually intended. + +## How to fix + +Before: +```cpp +void f() { + int size = sizeof(10); // <- probably meant a type or a variable, not the literal 10 +} +``` + +After: +```cpp +void f() { + int size = sizeof(int); +} +``` diff --git a/man/checkers/sizeofwithsilentarraypointer.md b/man/checkers/sizeofwithsilentarraypointer.md new file mode 100644 index 00000000000..edcd3674a65 --- /dev/null +++ b/man/checkers/sizeofwithsilentarraypointer.md @@ -0,0 +1,34 @@ +# sizeofwithsilentarraypointer + +**Message**: Using 'sizeof' on array given as function argument returns size of a pointer.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +`sizeof` is applied to an array that was received as a function parameter - which has already decayed +to a plain pointer, so the result is the pointer's size, not the array's. + +## Motivation + +An array parameter (`int a[10]`) is really just a pointer as far as the function body is concerned - the +size information from the declaration is not available at runtime. `sizeof(a)` inside the function +silently returns the size of the pointer, not the number of bytes the caller's array actually occupies, +which is easy to miss since the declaration still visually looks like an array. + +## How to fix + +Before: +```cpp +void f(int a[10]) { + int n = sizeof(a) / sizeof(int); // <- 'a' decayed to int*, this is not 10 +} +``` + +After: +```cpp +void f(int a[10], int n) { + int m = n / sizeof(int); // pass the real element count instead +} +``` diff --git a/man/checkers/sprintfOverlappingData.md b/man/checkers/sprintfOverlappingData.md new file mode 100644 index 00000000000..ed710f46009 --- /dev/null +++ b/man/checkers/sprintfOverlappingData.md @@ -0,0 +1,43 @@ +# sprintfOverlappingData + +**Message**: Undefined behavior: Variable is used as parameter and destination in s[n]printf().
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +The same variable is used as both the destination and a source argument of `sprintf`/`snprintf`/ +`swprintf`, which is undefined behaviour. + +## Motivation + +`sprintf()` and its relatives may write to the destination buffer while still reading from it if a +source argument aliases the destination - the C standard leaves this undefined, so the actual result +(correct output, garbled output, or a crash) depends on the specific library implementation. + +## How to fix + +Before: +```cpp +#include +void foo() { + char buf[100]; + sprintf(buf, "%s", buf); // <- source and destination overlap +} +``` + +After: +```cpp +#include +void foo() { + char buf[100] = "hi"; + char tmp[100]; + sprintf(tmp, "%s", buf); // write to a different buffer +} +``` + +## Related checkers + +- [overlappingStrcmp.md](overlappingStrcmp.md) - an unrelated string-function misuse in the same + checker, about a redundant/contradictory pair of `strcmp()` checks. diff --git a/man/checkers/staticStringCompare.md b/man/checkers/staticStringCompare.md new file mode 100644 index 00000000000..b2e5a49bc95 --- /dev/null +++ b/man/checkers/staticStringCompare.md @@ -0,0 +1,41 @@ +# staticStringCompare and stringCompare + +**Message**: Unnecessary comparison of static strings.
+**Category**: Code Quality
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +Two string literals (`staticStringCompare`), or two identical-looking string variables +(`stringCompare`), are compared with a string-comparison function (`strcmp`, `stricmp`, `wcscmp`, ...) - +the result is already known at compile time either way. + +## Motivation + +Comparing two string literals, or a variable against itself under a different name, can never vary at +runtime - the comparison's result is fixed regardless of any input, which usually means a variable name +was typed wrong, or the comparison is leftover from earlier code that used to compare something real. + +## How to fix + +Before: +```cpp +#include +int main() { + if (strcmp("00FF00", "00FF00") == 0) {} // <- always true +} +``` + +After: +```cpp +#include +int main(const char* value) { + if (strcmp(value, "00FF00") == 0) {} // compare the actual variable +} +``` + +## Related checkers + +- [literalWithCharPtrCompare.md](literalWithCharPtrCompare.md) - a related string-comparison mistake: + comparing a pointer against a literal with `==` instead of a string-comparison function. diff --git a/man/checkers/stlBoundaries.md b/man/checkers/stlBoundaries.md new file mode 100644 index 00000000000..0174b5abb67 --- /dev/null +++ b/man/checkers/stlBoundaries.md @@ -0,0 +1,36 @@ +# stlBoundaries + +**Message**: Dangerous comparison using operator< on iterator.
+**Category**: Correctness
+**Severity**: Error
+**Language**: C++ + +## Description + +An iterator that doesn't support ordering (for example a `std::list` or `std::set` iterator) is +compared with `<`/`>` instead of `!=`/`==` - the relative order of two such iterators isn't guaranteed +to mean anything. + +## Motivation + +Only random-access iterators (like a `std::vector` or `std::deque` iterator) have a well-defined +"less than" relationship. For other container types, the underlying storage isn't laid out in a way +that makes iterator order meaningful, so comparing with `<`/`>` is not portable and not reliable. + +## How to fix + +Before: +```cpp +#include +bool foo(std::list::iterator it1, std::list::iterator it2) { + return it1 < it2; // <- stlBoundaries: order of list iterators isn't guaranteed +} +``` + +After: +```cpp +#include +bool foo(std::list::iterator it1, std::list::iterator it2) { + return it1 != it2; +} +``` diff --git a/man/checkers/stlFindInsert.md b/man/checkers/stlFindInsert.md new file mode 100644 index 00000000000..af4a47e0ea2 --- /dev/null +++ b/man/checkers/stlFindInsert.md @@ -0,0 +1,46 @@ +# stlFindInsert + +**Message**: Searching before insertion is not necessary.
+**Category**: Performance
+**Severity**: Performance
+**Language**: C++ + +## Description + +An associative container is searched with `.find()`, and if nothing was found, the same key is +inserted right afterwards - the search is redundant since `insert()`/`emplace()` already report whether +the key existed. + +## Motivation + +Searching a container and then inserting into it based on the result does the lookup twice: once +explicitly with `.find()`, and again internally inside `insert()`/`emplace()` to find the right place +(or detect the key is already present). Going straight to `insert()`/`emplace()` gets the same behavior +in one lookup instead of two. + +## How to fix + +Before: +```cpp +#include +void f1(std::set& s, unsigned x) { + if (s.find(x) == s.end()) { // <- redundant, insert() already knows if x is present + s.insert(x); + } +} +``` + +After: +```cpp +#include +void f1(std::set& s, unsigned x) { + s.insert(x); +} +``` + +## Related checkers + +- [redundantIfRemove.md](redundantIfRemove.md) - a similar redundant-check-before-a-safe-operation + pattern, for `remove()` instead of `insert()`. +- [stlIfFind.md](stlIfFind.md) - a different `find()`-related mistake, where the result is misread as a + boolean. diff --git a/man/checkers/stlIfFind.md b/man/checkers/stlIfFind.md new file mode 100644 index 00000000000..a3e022b8db8 --- /dev/null +++ b/man/checkers/stlIfFind.md @@ -0,0 +1,63 @@ +# stlIfFind and stlIfStrFind + +**Message**: Suspicious condition. The result of find() is an iterator, but it is not properly checked.
+**Category**: Correctness
+**Severity**: Warning/Performance
+**Language**: C++ + +## Description + +The result of a container's `.find()` is used directly as a boolean condition: + +- `stlIfFind`: this tests whether the resulting iterator happens to be "truthy", not whether the + element was found, which is virtually never what's intended. +- `stlIfStrFind`: the same mistake specifically for `std::string::find()`, which returns a *position*, + not an iterator - comparing it directly as a boolean is wrong far more often than not (position 0 - + a match at the very start of the string - is falsy). With a C++20-or-later standard, this is instead + reported as a performance suggestion to use `string::starts_with()`. + +## Motivation + +`find()` on most containers returns an iterator (or, for `std::string`, a position) that must be +compared against `.end()` (or `std::string::npos`) to know whether something was actually found. Using +the raw result as a boolean condition compiles cleanly and can even look like it works in quick testing, +but it is testing the wrong thing - a genuine logic bug hiding behind code that reads as correct. + +## How to fix + +Before: +```cpp +#include +void f(std::set s) { + if (s.find(12)) { } // <- stlIfFind: this checks the iterator's "truthiness", not whether 12 was found +} +``` + +After: +```cpp +#include +void f(std::set s) { + if (s.find(12) != s.end()) { } +} +``` + +Before: +```cpp +#include +void f(const std::string &s) { + if (s.find("abc")) { } // <- stlIfStrFind: position 0 (a match at the start) is falsy here +} +``` + +After: +```cpp +#include +void f(const std::string &s) { + if (s.find("abc") != std::string::npos) { } +} +``` + +## Related checkers + +- [stlFindInsert.md](stlFindInsert.md) - a different `find()`-related redundancy, checking before + inserting into an associative container. diff --git a/man/checkers/stlOutOfBounds.md b/man/checkers/stlOutOfBounds.md new file mode 100644 index 00000000000..e8cf07a6ac8 --- /dev/null +++ b/man/checkers/stlOutOfBounds.md @@ -0,0 +1,44 @@ +# stlOutOfBounds + +**Message**: When ii==foo.size(), foo.at(ii) is out of bounds.
+**Category**: Correctness
+**Severity**: Error
+**Language**: C++ + +## Description + +A loop of the form `for (i = 0; i <= container.size(); ++i)` uses `<=` instead of `<`, so the last +iteration indexes one past the end. + +## Motivation + +This is one of the most common off-by-one mistakes in loops that walk a container by index: the loop +condition looks like a natural "up to and including the size" check, but the valid indices only go up +to `size() - 1`. + +## How to fix + +Before: +```cpp +#include +void f(std::vector foo) { + for (unsigned int ii = 0; ii <= foo.size(); ++ii) { // <- stlOutOfBounds + foo.at(ii) = 0; + } +} +``` + +After: +```cpp +#include +void f(std::vector foo) { + for (unsigned int ii = 0; ii < foo.size(); ++ii) { + foo.at(ii) = 0; + } +} +``` + +## Related checkers + +- [containerOutOfBounds.md](containerOutOfBounds.md) - the more general out-of-bounds container access + check that this loop-condition mistake is a specific cause of. diff --git a/man/checkers/stlSize.md b/man/checkers/stlSize.md new file mode 100644 index 00000000000..8dddc868132 --- /dev/null +++ b/man/checkers/stlSize.md @@ -0,0 +1,45 @@ +# stlSize + +**Message**: Possible inefficient checking for 'x' emptiness.
+**Category**: Performance
+**Severity**: Performance
+**Language**: C++ (only checked with a pre-C++11 standard, and only for `std::list`) + +## Description + +`.size()` is compared against `0`/`1` on a container whose `.size()` isn't guaranteed to be +constant-time (only checked pre-C++11, and only for `std::list`) - `.empty()` is always at least as +fast. + +## Motivation + +Before C++11, `std::list::size()` was allowed to take time proportional to the number of elements +(implementations were free to compute it by walking the list), while `.empty()` is always O(1). +Comparing `.size()` against `0`/`1` where `.empty()` would do is therefore a real, if usually small, +performance cost, and easy to write out of habit from code that works with other containers. + +## How to fix + +Before: +```cpp +#include +struct Fred { + void foo(); + std::list x; +}; +void Fred::foo() { + if (x.size() == 0) {} // <- only with a pre-C++11 standard: size() isn't guaranteed O(1) +} +``` + +After: +```cpp +#include +struct Fred { + void foo(); + std::list x; +}; +void Fred::foo() { + if (x.empty()) {} +} +``` diff --git a/man/checkers/stlcstr.md b/man/checkers/stlcstr.md new file mode 100644 index 00000000000..9e823ae922e --- /dev/null +++ b/man/checkers/stlcstr.md @@ -0,0 +1,69 @@ +# stlcstr and stlcstrthrow + +**Message**: Dangerous usage of c_str(). The value returned by c_str() is invalid after this call.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +The pointer from `.c_str()` (or an implicit conversion to `std::string` followed by `.c_str()`) escapes +the function it was taken in, in a way that leaves it dangling: + +- `stlcstr`: the pointer is returned from a function - the string it points into was a temporary or + local variable that no longer exists once the function returns. +- `stlcstrthrow`: the pointer is thrown as an exception - the string it points into is destroyed as the + stack unwinds, so the caught pointer is immediately dangling. + +## Motivation + +`std::string::c_str()` only stays valid for as long as the `std::string` it came from is still alive +(and hasn't been modified). Returning or throwing that pointer instead of the string itself is a +dangling-pointer bug: the pointer looks fine at the point it's produced, and only misbehaves later, +wherever it's eventually used. + +## How to fix + +Before: +```cpp +#include +const char *get_msg() { + std::string errmsg; + return errmsg.c_str(); // <- stlcstr: dangling as soon as 'errmsg' is destroyed +} +``` + +After: +```cpp +#include +std::string get_msg() { + std::string errmsg; + return errmsg; +} +``` + +Before: +```cpp +#include +void f() { + std::string errmsg; + throw errmsg.c_str(); // <- stlcstrthrow: dangling once the exception propagates past 'errmsg' +} +``` + +After: +```cpp +#include +void f() { + std::string errmsg; + throw errmsg; +} +``` + +## Related checkers + +- [stlcstrReturn.md](stlcstrReturn.md), [stlcstrParam.md](stlcstrParam.md), + [stlcstrConstructor.md](stlcstrConstructor.md), [stlcstrAssignment.md](stlcstrAssignment.md), + [stlcstrConcat.md](stlcstrConcat.md), [stlcstrStream.md](stlcstrStream.md) - the same + `.c_str()`-when-a-`std::string`-would-do idea, in other call-site shapes; those are inefficient + rather than dangerous. diff --git a/man/checkers/stlcstrAssignment.md b/man/checkers/stlcstrAssignment.md new file mode 100644 index 00000000000..44460758c79 --- /dev/null +++ b/man/checkers/stlcstrAssignment.md @@ -0,0 +1,45 @@ +# stlcstrAssignment + +**Message**: Assigning the result of c_str() to a std::string is slow and redundant.
+**Category**: Performance
+**Severity**: Performance
+**Language**: C++ + +## Description + +A `std::string` is assigned the result of `.c_str()` called on another `std::string` - going through +`c_str()` forces an unnecessary `strlen()` call that assigning directly from the source `std::string` +would avoid. + +## Motivation + +Assigning a `const char*` to a `std::string` requires calling `strlen()` to find its length, even +though the source `std::string` already knows its own length. Assigning from the `std::string` directly +skips that redundant scan. + +## How to fix + +Before: +```cpp +#include +std::string f(const std::string& a) { + std::string b = a.c_str(); // <- forces a strlen() that's already known + return b; +} +``` + +After: +```cpp +#include +std::string f(const std::string& a) { + std::string b = a; + return b; +} +``` + +## Related checkers + +- [stlcstr.md](stlcstr.md) - the dangerous (not just inefficient) version of this mistake. +- [stlcstrReturn.md](stlcstrReturn.md), [stlcstrParam.md](stlcstrParam.md), + [stlcstrConstructor.md](stlcstrConstructor.md), [stlcstrConcat.md](stlcstrConcat.md), + [stlcstrStream.md](stlcstrStream.md) - the same idea in other call-site shapes. diff --git a/man/checkers/stlcstrConcat.md b/man/checkers/stlcstrConcat.md new file mode 100644 index 00000000000..d06a1b1c9cd --- /dev/null +++ b/man/checkers/stlcstrConcat.md @@ -0,0 +1,43 @@ +# stlcstrConcat + +**Message**: Concatenating the result of c_str() and a std::string is slow and redundant.
+**Category**: Performance
+**Severity**: Performance
+**Language**: C++ + +## Description + +`.c_str()` called on a `std::string` is concatenated (`+`) with another `std::string` - going through +`c_str()` forces an unnecessary `strlen()` call that concatenating the two `std::string`s directly +would avoid. + +## Motivation + +Concatenating a `const char*` with a `std::string` requires calling `strlen()` on the `const char*` to +find its length, even though the `std::string` it came from already knew its own length. Concatenating +the two `std::string`s directly skips that redundant scan. + +## How to fix + +Before: +```cpp +#include +std::string g(const std::string& a, const std::string& b) { + return a + b.c_str(); // <- forces a strlen() that's already known +} +``` + +After: +```cpp +#include +std::string g(const std::string& a, const std::string& b) { + return a + b; +} +``` + +## Related checkers + +- [stlcstr.md](stlcstr.md) - the dangerous (not just inefficient) version of this mistake. +- [stlcstrReturn.md](stlcstrReturn.md), [stlcstrParam.md](stlcstrParam.md), + [stlcstrConstructor.md](stlcstrConstructor.md), [stlcstrAssignment.md](stlcstrAssignment.md), + [stlcstrStream.md](stlcstrStream.md) - the same idea in other call-site shapes. diff --git a/man/checkers/stlcstrConstructor.md b/man/checkers/stlcstrConstructor.md new file mode 100644 index 00000000000..0985d821688 --- /dev/null +++ b/man/checkers/stlcstrConstructor.md @@ -0,0 +1,45 @@ +# stlcstrConstructor + +**Message**: Constructing a std::string from the result of c_str() is slow and redundant.
+**Category**: Performance
+**Severity**: Performance
+**Language**: C++ + +## Description + +A `std::string` is constructed from the result of `.c_str()` called on another `std::string` - going +through `c_str()` forces an unnecessary `strlen()` call that constructing directly from the source +`std::string` would avoid. + +## Motivation + +Constructing a `std::string` from a `const char*` requires calling `strlen()` to find its length, even +though the source `std::string` already knows its own length. Constructing from the `std::string` +directly skips that redundant scan. + +## How to fix + +Before: +```cpp +#include +std::string f(const std::string& a) { + std::string b(a.c_str()); // <- forces a strlen() that's already known + return b; +} +``` + +After: +```cpp +#include +std::string f(const std::string& a) { + std::string b(a); + return b; +} +``` + +## Related checkers + +- [stlcstr.md](stlcstr.md) - the dangerous (not just inefficient) version of this mistake. +- [stlcstrReturn.md](stlcstrReturn.md), [stlcstrParam.md](stlcstrParam.md), + [stlcstrAssignment.md](stlcstrAssignment.md), [stlcstrConcat.md](stlcstrConcat.md), + [stlcstrStream.md](stlcstrStream.md) - the same idea in other call-site shapes. diff --git a/man/checkers/stlcstrParam.md b/man/checkers/stlcstrParam.md new file mode 100644 index 00000000000..2345c14c784 --- /dev/null +++ b/man/checkers/stlcstrParam.md @@ -0,0 +1,49 @@ +# stlcstrParam + +**Message**: Passing the result of c_str() to a function that takes std::string as argument no. 1 is slow and redundant.
+**Category**: Performance
+**Severity**: Performance
+**Language**: C++ + +## Description + +`.c_str()` is used purely to feed a `const char*` into a function parameter that's typed `std::string` +- going through `c_str()` forces an unnecessary `strlen()`/copy that passing the `std::string` directly +would avoid. + +## Motivation + +Calling `.c_str()` just to hand the result to a parameter that would have happily accepted the +`std::string` itself throws away the length information the `std::string` already had, forcing a +`strlen()` scan (and a copy) to reconstruct it. Passing the `std::string` directly is both simpler and +faster. + +## How to fix + +Before: +```cpp +#include +void Foo1(const std::string& s); +void f() { + std::string str = "bar"; + Foo1(str.c_str()); // <- Foo1() already accepts a std::string +} +``` + +After: +```cpp +#include +void Foo1(const std::string& s); +void f() { + std::string str = "bar"; + Foo1(str); +} +``` + +## Related checkers + +- [stlcstr.md](stlcstr.md) - the dangerous (not just inefficient) version of this mistake, where the + pointer ends up dangling. +- [stlcstrReturn.md](stlcstrReturn.md), [stlcstrConstructor.md](stlcstrConstructor.md), + [stlcstrAssignment.md](stlcstrAssignment.md), [stlcstrConcat.md](stlcstrConcat.md), + [stlcstrStream.md](stlcstrStream.md) - the same idea in other call-site shapes. diff --git a/man/checkers/stlcstrReturn.md b/man/checkers/stlcstrReturn.md new file mode 100644 index 00000000000..f74f72ff420 --- /dev/null +++ b/man/checkers/stlcstrReturn.md @@ -0,0 +1,47 @@ +# stlcstrReturn + +**Message**: Returning the result of c_str() in a function that returns std::string is slow and redundant.
+**Category**: Performance
+**Severity**: Performance
+**Language**: C++ + +## Description + +`.c_str()` is used purely to feed a `const char*` into a `return` statement of a function that returns +`std::string` - going through `c_str()` forces an unnecessary `strlen()`/copy that constructing the +`std::string` result directly would avoid. + +## Motivation + +Calling `.c_str()` just to hand the result back to something that would have happily accepted the +`std::string` itself throws away the length information the `std::string` already had, forcing a +`strlen()` scan (and a copy) to reconstruct it. Passing/returning the `std::string` directly is both +simpler and faster. + +## How to fix + +Before: +```cpp +#include +std::string get_msg() { + std::string errmsg; + return errmsg.c_str(); // <- forces an unnecessary strlen()/copy +} +``` + +After: +```cpp +#include +std::string get_msg() { + std::string errmsg; + return errmsg; +} +``` + +## Related checkers + +- [stlcstr.md](stlcstr.md) - the dangerous (not just inefficient) version of this mistake, where the + function returns `const char*` and the pointer ends up dangling. +- [stlcstrParam.md](stlcstrParam.md), [stlcstrConstructor.md](stlcstrConstructor.md), + [stlcstrAssignment.md](stlcstrAssignment.md), [stlcstrConcat.md](stlcstrConcat.md), + [stlcstrStream.md](stlcstrStream.md) - the same idea in other call-site shapes. diff --git a/man/checkers/stlcstrStream.md b/man/checkers/stlcstrStream.md new file mode 100644 index 00000000000..2653e328853 --- /dev/null +++ b/man/checkers/stlcstrStream.md @@ -0,0 +1,44 @@ +# stlcstrStream + +**Message**: Passing the result of c_str() to a stream is slow and redundant.
+**Category**: Performance
+**Severity**: Performance
+**Language**: C++ + +## Description + +`.c_str()` called on a `std::string` is streamed (`<<`) into an output stream - going through `c_str()` +forces an unnecessary `strlen()` call that streaming the `std::string` directly would avoid. + +## Motivation + +Streaming a `const char*` requires the stream to call `strlen()` on it to find its length, even though +the `std::string` it came from already knew its own length. Streaming the `std::string` directly skips +that redundant scan. + +## How to fix + +Before: +```cpp +#include +#include +void f(std::stringstream& strm, const std::string& s) { + strm << s.c_str(); // <- forces a strlen() that's already known +} +``` + +After: +```cpp +#include +#include +void f(std::stringstream& strm, const std::string& s) { + strm << s; +} +``` + +## Related checkers + +- [stlcstr.md](stlcstr.md) - the dangerous (not just inefficient) version of this mistake. +- [stlcstrReturn.md](stlcstrReturn.md), [stlcstrParam.md](stlcstrParam.md), + [stlcstrConstructor.md](stlcstrConstructor.md), [stlcstrAssignment.md](stlcstrAssignment.md), + [stlcstrConcat.md](stlcstrConcat.md) - the same idea in other call-site shapes. diff --git a/man/checkers/strPlusChar.md b/man/checkers/strPlusChar.md new file mode 100644 index 00000000000..97ceed0388b --- /dev/null +++ b/man/checkers/strPlusChar.md @@ -0,0 +1,45 @@ +# strPlusChar + +**Message**: Unusual pointer arithmetic. A value of type 'char' is added to a string literal.
+**Category**: Correctness
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A string literal has a `char`/`wchar_t` value added to it with `+`, with the literal written on the +left-hand side (`"text" + ch`) - the same expression with the operands swapped (`ch + "text"`) is not +covered by this check. + +## Motivation + +`"/usr" + '/'` doesn't concatenate the character onto the string - a string literal decays to a +`const char*`, so `+` here is pointer arithmetic: it adds the character's numeric value to the pointer, +producing a pointer into the middle of (or past the end of) the literal. This looks like string +concatenation but is nothing like it. + +This can lead to undefined behaviour by itself, separate from whatever happens if the pointer is later +used: the C++ standard only allows pointer arithmetic to land inside the array or exactly one past its +end, and a character value large enough to land outside that range (as `'/'` does against the 5-byte +`"/usr"`) makes the addition itself undefined behaviour, whether or not the resulting pointer is ever +dereferenced. cppcheck doesn't check whether the specific character value would actually stay in range - +this is flagged purely because the syntactic pattern (a string literal plus a `char`) is essentially +always a concatenation mistake, not because cppcheck has determined that this particular addition goes +out of bounds. + +## How to fix + +Before: +```cpp +void foo() { + const char *p = "/usr" + '/'; // <- pointer arithmetic on a string literal +} +``` + +After: +```cpp +#include +void foo() { + std::string p = std::string("/usr") + '/'; +} +``` diff --git a/man/checkers/stringLiteralWrite.md b/man/checkers/stringLiteralWrite.md new file mode 100644 index 00000000000..ee38a753098 --- /dev/null +++ b/man/checkers/stringLiteralWrite.md @@ -0,0 +1,35 @@ +# stringLiteralWrite + +**Message**: Modifying string literal "abc" directly or indirectly is undefined behaviour.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A string literal is modified through a pointer to it (directly, or via a pointer passed to another +function). + +## Motivation + +String literals are typically stored in read-only memory, so writing to one crashes or corrupts memory +unpredictably depending on the platform. `char *p = "abc";` compiles without warning even though `p` +points at memory that must not be written to. + +## How to fix + +Before: +```cpp +void f() { + char *abc = "abc"; + abc[0] = 'a'; // <- undefined behaviour +} +``` + +After: +```cpp +void f() { + char abc[] = "abc"; // a real, modifiable array + abc[0] = 'a'; +} +``` diff --git a/man/checkers/suspiciousCase.md b/man/checkers/suspiciousCase.md new file mode 100644 index 00000000000..1dbd9932ddc --- /dev/null +++ b/man/checkers/suspiciousCase.md @@ -0,0 +1,45 @@ +# suspiciousCase + +**Message**: Found suspicious case label in switch(). Operator '&&' probably doesn't work as intended.
+**Category**: Correctness
+**Severity**: Warning (Inconclusive)
+**Language**: C/C++ + +## Description + +A `switch` `case` label contains `&&`/`||` (`case A&&B:`) - a case label must be a single constant, so +this doesn't test both `A` and `B`; it's normally a sign that `if`/`else` or several `case` labels were +intended instead. + +## Motivation + +A `case` label is required to be a single constant expression - `A && B` is still just one expression +(evaluating to `0` or `1`), not a test of "when `A` and `B`." Code written this way almost never means +what a reader would guess from the `&&`/`||` at a glance, and usually indicates the author meant to test +multiple values or conditions in a way `switch` doesn't directly support. + +## How to fix + +Use separate `case` labels (to match several values) or an `if`/`else` chain (to test a real logical +condition) instead. + +Before: +```cpp +void foo(int a, int A, int B) { + switch(a) { + case A&&B: // <- a case label must be one constant, not a logical expression + foo(a, A, B); + } +} +``` + +After: +```cpp +void foo(int a, int A, int B) { + switch(a) { + case A: + case B: + foo(a, A, B); + } +} +``` diff --git a/man/checkers/suspiciousFloatingPointCast.md b/man/checkers/suspiciousFloatingPointCast.md new file mode 100644 index 00000000000..9e405c42efa --- /dev/null +++ b/man/checkers/suspiciousFloatingPointCast.md @@ -0,0 +1,34 @@ +# suspiciousFloatingPointCast + +**Message**: Floating-point cast causes loss of precision.
+**Category**: Correctness
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A `double`/`long double` value is cast down to a narrower floating type (`float`, or `double` from +`long double`) and then used somewhere that expected the original, wider type back - the narrowing +silently throws away precision for no benefit. + +## Motivation + +Casting to a narrower floating type and then immediately using the result as the wider type again +gains nothing - the precision lost in the cast can't come back, and the code would behave identically +(with better precision) if the cast were simply removed. + +## How to fix + +Before: +```cpp +double f(double a, double b, float c) { + return a + (float)b + c; // <- 'b' loses precision for no reason +} +``` + +After: +```cpp +double f(double a, double b, float c) { + return a + b + c; +} +``` diff --git a/man/checkers/suspiciousSemicolon.md b/man/checkers/suspiciousSemicolon.md new file mode 100644 index 00000000000..0de8287cb67 --- /dev/null +++ b/man/checkers/suspiciousSemicolon.md @@ -0,0 +1,43 @@ +# suspiciousSemicolon + +**Message**: Suspicious use of ; at the end of 'if' statement.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A stray `;` immediately follows an `if`/`for`/`while` condition, right before a `{ ... }` block - the +block is then unconditional, but reads as if it were controlled by the condition. + +## Motivation + +`if (cond);` is a complete statement on its own (an empty statement, executed only when `cond` is true), +so a `{ ... }` block written right after it is a separate, always-executed statement - not the body of +the `if` at all. This is a classic typo that silently turns a conditional block into unconditional code, +and it's easy to miss on a quick read since the block still looks indented as if it belonged to the +`if`. + +## How to fix + +Remove the stray semicolon. + +Before: +```cpp +void do_something(); +void foo(bool quit) { + while (!quit); { // <- the loop body is just ';' - this block always runs once + do_something(); + } +} +``` + +After: +```cpp +void do_something(); +void foo(bool quit) { + while (!quit) { + do_something(); + } +} +``` diff --git a/man/checkers/terminateStrncpy.md b/man/checkers/terminateStrncpy.md new file mode 100644 index 00000000000..c554ade8d81 --- /dev/null +++ b/man/checkers/terminateStrncpy.md @@ -0,0 +1,40 @@ +# terminateStrncpy + +**Message**: The buffer 'dest' may not be null-terminated after the call to strncpy().
+**Category**: Undefined Behaviour
+**Severity**: Warning (inconclusive)
+**Language**: C/C++ + +## Description + +`strncpy()` is called with a length equal to (or larger than) the destination buffer's size, and the +source may be at least that long - in that case `strncpy()` does not null-terminate the destination, so +it may not be a valid C string afterwards. Since this is a "may not be null-terminated" finding rather +than a certainty, it is only reported when `--inconclusive` is enabled. + +## Motivation + +`strncpy()` only writes a trailing `'\0'` if the source string is shorter than the given length; if the +source is at least as long as the length, the destination is left completely full with no terminator. +Any later code that treats the buffer as a normal null-terminated C string then reads past its end. + +## How to fix + +Before: +```cpp +#include +void f() { + char dest[10]; + strncpy(dest, "abcdefghijklmnop", sizeof(dest)); // <- may not be null-terminated +} +``` + +After: +```cpp +#include +void f() { + char dest[10]; + strncpy(dest, "abcdefghijklmnop", sizeof(dest) - 1); + dest[sizeof(dest) - 1] = '\0'; +} +``` diff --git a/man/checkers/thisSubtraction.md b/man/checkers/thisSubtraction.md new file mode 100644 index 00000000000..ec12d91dbf9 --- /dev/null +++ b/man/checkers/thisSubtraction.md @@ -0,0 +1,48 @@ +# thisSubtraction + +**Message**: Suspicious pointer subtraction. Did you forget to dereference it?
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ + +## Description + +The code contains `this-x` - almost always a typo for `this->x`, since subtracting an arbitrary value +from `this` as a pointer is rarely meaningful. + +## Motivation + +`this-x` and `this->x` look almost identical but mean completely different things: one does pointer +arithmetic on `this` itself (rarely intended), the other accesses member `x`. This is an easy typo to +make and an easy one to miss when reading over code quickly. + +It's also not merely a readability problem: `this` points to a single object, not an array, so subtracting +any nonzero value from it produces a pointer outside that object's bounds, which the standard's pointer +arithmetic rules make undefined behaviour to even form, before it's ever dereferenced or compared. cppcheck +doesn't evaluate `x` here, though - it flags the `this - x` syntax on sight, regardless of what `x` actually +is, so this is reported purely as a likely typo for `this->x`, not because cppcheck has confirmed the +subtraction itself is unsafe. + +## How to fix + +Before: +```cpp +class C { +public: + int x; + void f() { + this-x; // <- thisSubtraction: likely meant 'this->x' + } +}; +``` + +After: +```cpp +class C { +public: + int x; + void f() { + this->x; + } +}; +``` diff --git a/man/checkers/thisUseAfterFree.md b/man/checkers/thisUseAfterFree.md new file mode 100644 index 00000000000..0713de349b8 --- /dev/null +++ b/man/checkers/thisUseAfterFree.md @@ -0,0 +1,42 @@ +# thisUseAfterFree + +**Message**: Class member 'x' is accessed after deleting 'this' (or a smart pointer holding it).
+**Category**: Undefined Behaviour
+**Severity**: Warning
+**Language**: C++ + +## Description + +A member function `delete`s (or `.reset()`s) a pointer/smart-pointer that holds `this` itself, and then +goes on to call another method or use another member - by that point the object has already been +destroyed. + +## Motivation + +Once an object has deleted itself (typically via a `static` "instance" pointer, or a smart pointer that +owns it), continuing to use `this` - or anything reached through it, including other member functions - +is a use-after-free, even though the memory may still look intact for a little while afterwards. + +## How to fix + +Before: +```cpp +class C { +public: + void dostuff() { delete mInstance; hello(); } // <- thisUseAfterFree: 'this' is gone after the delete +private: + static C *mInstance; + void hello() {} +}; +``` + +After: +```cpp +class C { +public: + void dostuff() { hello(); delete mInstance; } + void hello() {} +private: + static C *mInstance; +}; +``` diff --git a/man/checkers/throwInEntryPoint.md b/man/checkers/throwInEntryPoint.md new file mode 100644 index 00000000000..e4c1f9d3acd --- /dev/null +++ b/man/checkers/throwInEntryPoint.md @@ -0,0 +1,47 @@ +# throwInEntryPoint + +**Message**: Unhandled exception thrown in function that is an entry point.
+**Category**: Correctness
+**Severity**: Error
+**Language**: C++ only + +## Description + +An exception can escape a recognized program entry point (`main`, or others such as `_init`/`_fini` +when the matching library configuration is loaded). + +## Motivation + +An exception that escapes `main()` (or another entry point) is not caught by anything, so the C++ +runtime calls `std::terminate()` - the program aborts, typically without the cleanup a caught, +handled exception would have allowed. + +## How to fix + +Before: +```cpp +void doWork(); +int main() { + doWork(); // <- if this throws, nothing will catch it + return 0; +} +``` + +After: +```cpp +#include +void doWork(); +int main() { + try { + doWork(); + } catch (const std::exception&) { + return 1; + } + return 0; +} +``` + +## Related checkers + +- [throwInNoexceptFunction.md](throwInNoexceptFunction.md) - the same underlying problem, for a + function explicitly marked `noexcept` instead of a recognized entry point. diff --git a/man/checkers/throwInNoexceptFunction.md b/man/checkers/throwInNoexceptFunction.md new file mode 100644 index 00000000000..d69cddfb7a8 --- /dev/null +++ b/man/checkers/throwInNoexceptFunction.md @@ -0,0 +1,39 @@ +# throwInNoexceptFunction + +**Message**: Unhandled exception thrown in function declared not to throw exceptions.
+**Category**: Correctness
+**Severity**: Error
+**Language**: C++ only + +## Description + +A function declared `noexcept`, `throw()`, or with an equivalent `__attribute__((nothrow))`/ +`__declspec(nothrow)`, throws (or calls something that throws) anyway. + +## Motivation + +If an exception escapes a function promised not to throw, `std::terminate()` is called immediately - +the program aborts without any of the normal stack-unwinding cleanup (destructors further up the call +stack are not guaranteed to run). + +## How to fix + +Before: +```cpp +void f() noexcept { + throw 1; // <- contradicts the noexcept promise +} +``` + +After: +```cpp +void f() noexcept { +} +``` + +## Related checkers + +- [exceptThrowInDestructor.md](exceptThrowInDestructor.md) - the same underlying problem, specifically + for a destructor (implicitly `noexcept`). +- [throwInEntryPoint.md](throwInEntryPoint.md) - the same underlying problem, for a recognized program + entry point instead of an explicitly `noexcept` function. diff --git a/man/checkers/truncLongCast.md b/man/checkers/truncLongCast.md index f6ecd6ea77c..01d9400d0dc 100644 --- a/man/checkers/truncLongCast.md +++ b/man/checkers/truncLongCast.md @@ -7,12 +7,17 @@ ## Description -This checker warns when a calculation has type 'int' and it could potentially overflow that and the result is implicitly or explicitly converted to a larger -integer type after the loss of information has already occurred. +A multiplication (`*`) or left-shift (`<<`) is computed using `int` arithmetic, and only afterwards - +once any overflow has already happened - is the result widened to a larger integer type by an implicit +or explicit conversion. Widening after the fact doesn't recover information that `int` arithmetic already +lost. ## Motivation -The motivation of this checker is to catch bugs. Unintentional loss of information. +Declaring a wider result type (`long`, `int64_t`, ...) is often meant to give a calculation more room, so +it doesn't overflow. That only works if the wider type is used for the calculation itself; if the +multiplication or shift is still done in `int` and only the final result is widened, the overflow already +happened before the conversion, and the wider type just carries forward a truncated value. ## How to fix @@ -47,4 +52,4 @@ After (change type of assigned variable): void foo(int32_t y) { int32_t x = y * y; } -``` \ No newline at end of file +``` diff --git a/man/checkers/unassignedVariable.md b/man/checkers/unassignedVariable.md new file mode 100644 index 00000000000..cec8da2a0ee --- /dev/null +++ b/man/checkers/unassignedVariable.md @@ -0,0 +1,44 @@ +# unassignedVariable + +**Message**: Variable 'i' is not assigned a value.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A variable is read (or its address is taken) without ever having been given a value first. + +## Motivation + +Reading a variable that's never been assigned is a sign of a bug: a forgotten initialization, or a +variable name that was meant to refer to something else. (Note this checker flags the code pattern +itself; see [uninitvar.md](uninitvar.md) for the related, more precise check on genuinely reading an +uninitialized value.) + +## How to fix + +Give the variable a value before reading it. + +Before: +```cpp +int foo() { + int i; // <- read below without ever being assigned + return i; +} +``` + +After: +```cpp +int foo() { + int i = 0; + return i; +} +``` + +## Related checkers + +- [uninitvar.md](uninitvar.md) - the closely related, more precise family of checks specifically about + reading an uninitialized value. +- [unusedVariable.md](unusedVariable.md) and [unreadVariable.md](unreadVariable.md) - the sibling + findings for a variable that's never used at all, or assigned but never read. diff --git a/man/checkers/unhandledExceptionSpecification.md b/man/checkers/unhandledExceptionSpecification.md new file mode 100644 index 00000000000..a63abe03e69 --- /dev/null +++ b/man/checkers/unhandledExceptionSpecification.md @@ -0,0 +1,39 @@ +# unhandledExceptionSpecification + +**Message**: Unhandled exception specification when calling function thrower().
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ only + +## Description + +A function with no exception specification of its own calls another function that has an (old-style, +C++17-removed) `throw(SomeType)` dynamic exception specification, without any `try`/`catch` in between. + +## Motivation + +A dynamic exception specification like `throw(int)` documents that a function can only throw that +particular type - calling it without handling that possibility is worth a second look, especially since +this old-style specification was removed from the language in C++17 and callers may not realize it's +still meaningful in the code they're reading. + +## How to fix + +Before: +```cpp +void thrower() throw(int); +void f() { + thrower(); // <- unhandled 'throw(int)' specification +} +``` + +After: +```cpp +void thrower() throw(int); +void f() { + try { + thrower(); + } catch (int) { + } +} +``` diff --git a/man/checkers/uninitMemberVar.md b/man/checkers/uninitMemberVar.md new file mode 100644 index 00000000000..b7d498df8a2 --- /dev/null +++ b/man/checkers/uninitMemberVar.md @@ -0,0 +1,142 @@ +# uninitMemberVar, uninitMemberVarPrivate, uninitDerivedMemberVar, uninitDerivedMemberVarPrivate, uninitMemberVarNoCtor, uninitMemberVarPrivateNoCtor, uninitDerivedMemberVarNoCtor and uninitDerivedMemberVarPrivateNoCtor + +**Message**: Member variable 'x' is not initialized in the constructor.
+**Category**: Undefined Behaviour
+**Severity**: Warning
+**Language**: C++ + +## Description + +A constructor (or copy/move constructor) doesn't give a member variable any value. All eight IDs here +are the exact same underlying finding, distinguished only by three independent, combinable details of +the situation: + +- **`Derived`** appears in the ID when the uninitialized member actually belongs to a *base* class + rather than the class whose constructor is being looked at. +- **`Private`** appears in the ID when either there's no constructor at all and only some of the + class's members happen to have in-class default values (so the ones without one are flagged this + way), or the specific constructor that's missing the initialization is itself `private`. +- **`NoCtor`** appears in the ID when the class has no constructor at all, but *some* of its members do + have in-class default initializers - which means cppcheck can tell that whoever wrote the class was + thinking about initialization, making it worth flagging any other member that has no default. + +Of the eight combinations, three (`uninitMemberVarPrivateNoCtor`, `uninitDerivedMemberVarNoCtor`, +`uninitDerivedMemberVarPrivateNoCtor`) are only theoretically possible - in the current version of +cppcheck, none of the code paths that would build these particular three ID strings are actually +reachable, so you will not see them in practice. + +## Motivation + +Reading a member before it's ever been given a value is undefined behaviour, whether or not the class +manages any resource - the member's contents are simply whatever bytes happened to already be in that +memory. + +## How to fix + +Give every member a value, either via an in-class default initializer or in every constructor's +member-initializer list. + +Before: +```cpp +class Fred { +public: + Fred() {} // <- 'i' is never given a value + int i; +}; +``` + +After: +```cpp +class Fred { +public: + Fred() : i(0) {} + int i; +}; +``` + +Before: +```cpp +class C { +private: + int i1 = 0; + int i2; // <- no constructor, and 'i2' has no default like 'i1' does +}; +``` + +After: +```cpp +class C { +private: + int i1 = 0; + int i2 = 0; +}; +``` + +Before: +```cpp +class Base { +public: + virtual void foo() = 0; + int x; // <- left uninitialized by every class that derives from Base +}; +class Derived: public Base { +public: + Derived() {} + void foo() override; +}; +``` + +After: +```cpp +class Base { +public: + Base() : x(0) {} + virtual void foo() = 0; + int x; +}; +class Derived: public Base { +public: + Derived() {} + void foo() override; +}; +``` + +Before: +```cpp +class B { int i; }; // <- B's own (private) constructor doesn't init 'i' +class D : B { + explicit D(int) {} +}; +``` + +After: +```cpp +class B { + int i; +public: + B() : i(0) {} +}; +class D : B { + explicit D(int) {} +}; +``` + +Before: +```cpp +struct S { + int a = 0, b; // <- 'a' has a default, 'b' doesn't +}; +``` + +After: +```cpp +struct S { + int a = 0, b = 0; +}; +``` + +## Related checkers + +- [noConstructor.md](noConstructor.md) - the related check for when a class has no constructor at all. +- [missingMemberCopy.md](missingMemberCopy.md) - the analogous finding for a copy/move constructor that + simply forgets to copy one particular member, rather than never initializing it. diff --git a/man/checkers/uninitStructMember.md b/man/checkers/uninitStructMember.md new file mode 100644 index 00000000000..f705c1acede --- /dev/null +++ b/man/checkers/uninitStructMember.md @@ -0,0 +1,47 @@ +# uninitStructMember + +**Message**: Uninitialized struct member: x
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A specific struct/class member is read before that particular member has been set, even if other +members of the same variable have been. + +## Motivation + +Initializing some members of a struct doesn't initialize the rest - reading a member that was never +assigned is undefined behaviour, the same as reading an uninitialized plain variable, and easy to miss +when other, nearby members of the same variable were set correctly. + +## How to fix + +Before: +```cpp +#include +#include +struct ABC { int a; int b; }; +void f() { + struct ABC *abc = (struct ABC*)malloc(sizeof(struct ABC)); + printf("%d", abc->a); // <- uninitStructMember +} +``` + +After: +```cpp +#include +#include +struct ABC { int a; int b; }; +void f() { + struct ABC *abc = (struct ABC*)malloc(sizeof(struct ABC)); + abc->a = 0; + printf("%d", abc->a); +} +``` + +## Related checkers + +- [uninitvar.md](uninitvar.md) - the general "read before assignment" check this one specializes for + one particular struct member. diff --git a/man/checkers/uninitdata.md b/man/checkers/uninitdata.md new file mode 100644 index 00000000000..66e0b74ecd0 --- /dev/null +++ b/man/checkers/uninitdata.md @@ -0,0 +1,48 @@ +# uninitdata + +**Message**: Memory is allocated but not initialized: x
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +Like [uninitvar.md](uninitvar.md), but specifically for memory obtained from an allocation function +(`malloc`, etc.) - the block exists, but its contents haven't been written yet. + +## Motivation + +Memory returned by `malloc()` and similar functions is not zero-initialized - it holds whatever bytes +happened to already be there. Reading through it before writing to it is undefined behaviour, just like +reading an uninitialized plain variable, but easier to overlook since the allocation itself looks like +it "created" the value. + +## How to fix + +Before: +```cpp +#include +#include +struct ABC { int a; int b; }; +void f() { + struct ABC *abc = (struct ABC*)malloc(sizeof(struct ABC)); + printf("%d", abc->a); // <- uninitdata +} +``` + +After: +```cpp +#include +#include +struct ABC { int a; int b; }; +void f() { + struct ABC *abc = (struct ABC*)malloc(sizeof(struct ABC)); + abc->a = 0; + printf("%d", abc->a); +} +``` + +## Related checkers + +- [uninitvar.md](uninitvar.md) - the general "read before assignment" check this one specializes for + allocated memory. diff --git a/man/checkers/uninitvar.md b/man/checkers/uninitvar.md new file mode 100644 index 00000000000..507c368a47c --- /dev/null +++ b/man/checkers/uninitvar.md @@ -0,0 +1,82 @@ +# uninitvar and legacyUninitvar + +**Message**: Uninitialized variable: x
+**Category**: Undefined Behaviour
+**Severity**: Error/Warning
+**Language**: C/C++ + +## Description + +A local variable, pointer, or struct member is read (or its uninitialized value is passed along) +before any assignment reaches that point: + +- `uninitvar`: the main check. +- `legacyUninitvar`: an older, separate analysis that catches some patterns the main `uninitvar` check + doesn't (and vice versa) - both run, so the same kind of bug can be reported under either ID depending + on the exact code shape. + +## Motivation + +Reading a variable before it has been given a value is undefined behaviour in C/C++: the variable's +contents are whatever bytes happened to already be in that memory, which is unpredictable and can +differ between runs, builds, or optimization levels. This is one of the most common sources of subtle, +hard-to-reproduce bugs. + +## How to fix + +Before: +```cpp +#include +void f() { + int x; + printf("%d", x); // <- uninitvar +} +``` + +After: +```cpp +#include +void f() { + int x = 0; + printf("%d", x); +} +``` + +## False positives to be aware of + +- **Passing a member-access expression as an argument to an unresolved, all-uppercase macro-like call** + can be wrongly flagged, since cppcheck can't tell whether the macro actually evaluates its argument + at runtime (many such macros, like size- or offset-computing ones, don't): + ```cpp + void f() { + struct SData * s; + TYPEOF(s->status); // flagged as reading uninitialized 's', even if TYPEOF never evaluates it + } + ``` +- **A cast applied to `&variable` can be misread as reading the variable itself**, when it's actually + ambiguous whether `&` means "address of" (which never requires the variable to hold a value) or a + bitwise AND: + ```cpp + int main() { + int done; + dostuff(1, (AuPointer) &done); // flagged, even though '&done' alone doesn't read 'done' + } + ``` +- **Casting a variable of a type cppcheck doesn't recognize to a pointer type can be wrongly treated as + reading it**, particularly in C code using an opaque/typedef'd type: + ```c + void f() { + DES_cblock d; // unknown type + char *dp; + dp = (char *)d; // flagged as reading uninitialized 'd', even though the type is opaque to cppcheck + } + ``` + +## Related checkers + +- [uninitdata.md](uninitdata.md) - the same idea, for memory obtained from an allocation function + rather than a plain variable. +- [uninitStructMember.md](uninitStructMember.md) - the same idea, narrowed to one specific struct + member. +- [ctuuninitvar.md](ctuuninitvar.md) - the same idea, found by whole-program analysis across function + calls. diff --git a/man/checkers/unknownEvaluationOrder.md b/man/checkers/unknownEvaluationOrder.md new file mode 100644 index 00000000000..20684d90228 --- /dev/null +++ b/man/checkers/unknownEvaluationOrder.md @@ -0,0 +1,49 @@ +# unknownEvaluationOrder + +**Message**: Expression 'x = x++;' depends on order of evaluation of side effects
+**Category**: Undefined Behaviour/Portability
+**Severity**: Error/Portability
+**Language**: C/C++ + +## Description + +An expression reads and modifies the same variable more than once with no defined order between the +two (for example `x = x++;` or `x++, x++`). This is reported at two different severities because the +language rules genuinely differ between the two shapes: + +- **Error** (for example `x = x++;`): the read and the modification aren't just unordered, they actively + conflict - this is undefined behaviour in both C and C++. +- **Portability**: the specific expression shape is one where C++17 tightened the sequencing rules + enough that the order is merely *unspecified* (one of a few valid outcomes, not anything-goes) - not + undefined behaviour under C++17, but still worth flagging since older compilers/standards, or C, may + treat the same code as undefined instead. + +## Motivation + +When the same variable is both read and modified more than once in one expression with no sequencing +between the two, different compilers (or the same compiler at different optimization levels) can +legally produce different results. For the `Error` shape this is undefined behaviour outright; for the +`Portability` shape, C++17 guarantees the result is at least one of a small set of valid outcomes, but +which one is still compiler-dependent, so the code isn't portable even though it's not undefined. + +## How to fix + +Before: +```cpp +int dostuff(); +void f() { + int x = dostuff(); + return x + x++; // <- depends on evaluation order +} +``` + +After: +```cpp +int dostuff(); +int f() { + int x = dostuff(); + int y = x++; + return x + y; +} +``` + diff --git a/man/checkers/unknownMacro.md b/man/checkers/unknownMacro.md index 52599215d89..e0b352eab63 100644 --- a/man/checkers/unknownMacro.md +++ b/man/checkers/unknownMacro.md @@ -1,8 +1,6 @@ - # unknownMacro -**Message**: There is an unknown macro here somewhere. Configuration is required. If AAA is a macro then please configure it. [unknownMacro] -
+**Message**: There is an unknown macro here somewhere. Configuration is required. If AAA is a macro then please configure it.
**Category**: Configuration
**Severity**: Error
**Language**: C and C++ diff --git a/man/checkers/unknownPattern.md b/man/checkers/unknownPattern.md new file mode 100644 index 00000000000..f8748e03fb2 --- /dev/null +++ b/man/checkers/unknownPattern.md @@ -0,0 +1,29 @@ +# unknownPattern + +**Message**: Unknown pattern used: "%typex%"
+**Category**: Code Quality
+**Severity**: Error
+**Language**: C++ (cppcheck's own source code only) + +## Description + +A `%something%` placeholder isn't one of the pattern language's recognized names. + +This is not a general-purpose checker for arbitrary C/C++ programs - see +[simplePatternError.md](simplePatternError.md) for the shared background on this internal, +cppcheck-source-only checker. + +## Motivation + +An unrecognized placeholder name is almost always a typo for a real one (`%type%` misspelled as +`%typex%`, for example) - since the pattern engine doesn't know what it means, the match silently fails +to do what was intended. + +## How to fix + +Correct the placeholder name to one the pattern language actually recognizes (`%type%`, `%var%`, +`%num%`, ...). + +## Related checkers + +- [missingPercentCharacter.md](missingPercentCharacter.md) - a placeholder that's missing its closing `%` entirely, rather than misspelled. diff --git a/man/checkers/unpreciseMathCall.md b/man/checkers/unpreciseMathCall.md new file mode 100644 index 00000000000..daf657114bd --- /dev/null +++ b/man/checkers/unpreciseMathCall.md @@ -0,0 +1,40 @@ +# unpreciseMathCall + +**Message**: Expression 'exp(x) - 1' can be replaced by 'expm1(x)' to avoid loss of precision.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +An expression like `exp(x) - 1` or `log(1 + x)` should be written as `expm1(x)`/`log1p(x)` to avoid +losing precision for small `x`. + +## Motivation + +For small `x`, `exp(x)` is very close to `1`, so `exp(x) - 1` subtracts two nearly-equal +floating-point numbers - a classic way to lose most of the significant digits of the result. +`expm1()`/`log1p()` are designed to compute the same mathematical result without this cancellation. + +## How to fix + +Before: +```cpp +#include +void f() { + print(exp(3.5) - 1); // <- loses precision for small arguments +} +``` + +After: +```cpp +#include +void f() { + print(expm1(3.5)); +} +``` + +## Related checkers + +- [wrongmathcall.md](wrongmathcall.md) - a related but distinct math-function issue: passing a literal + value outside a function's valid domain. diff --git a/man/checkers/unreachableCode.md b/man/checkers/unreachableCode.md new file mode 100644 index 00000000000..4ed841196fb --- /dev/null +++ b/man/checkers/unreachableCode.md @@ -0,0 +1,42 @@ +# unreachableCode + +**Message**: Statements following return, break, continue, goto or throw will never be executed.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +Code appears right after a `return`/`break`/`continue`/`goto`/`throw`, or after a call to a function +that never returns - none of it can ever execute. + +## Motivation + +This is dead code by definition: control leaves the enclosing block before reaching it, so it can be +deleted with no change in behavior. It's also worth double-checking that the surrounding logic is +actually correct, since unreachable code sometimes reveals a misplaced statement. + +## How to fix + +Before: +```cpp +void bar(); +void foo() { + return; + bar(); // <- can never run +} +``` + +After: +```cpp +void bar(); +void foo() { + bar(); + return; +} +``` + +## Related checkers + +- [duplicateBreak.md](duplicateBreak.md) - the specific case where the unreachable statement is itself another `return`/`break`/`continue`/`goto`/`throw`. +- [unreachableSwitchCase.md](unreachableSwitchCase.md) - a `switch` `case` that can never be selected, rather than code placed after an exit statement. diff --git a/man/checkers/unreachableSwitchCase.md b/man/checkers/unreachableSwitchCase.md new file mode 100644 index 00000000000..1a1f61a53b0 --- /dev/null +++ b/man/checkers/unreachableSwitchCase.md @@ -0,0 +1,51 @@ +# unreachableSwitchCase + +**Message**: Switch case 'x' can never be selected because the switch condition is known to be 'y'.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A `switch` is given a value cppcheck already knows, and a `case` label in it can never match that +value. + +## Motivation + +A `case` that can never be reached is dead code, and can indicate a mistake nearby - for example, that +the `switch`'s value was meant to still be variable at this point but has already been narrowed down by +an earlier check. + +## How to fix + +Before: +```cpp +enum T { A, B }; +void f(const T &t) { + if (t == A) { + switch (t) { + case A: + break; + case B: // <- 't' is known to be A here + break; + } + } +} +``` + +After: +```cpp +enum T { A, B }; +void f(const T &t) { + switch (t) { + case A: + break; + case B: + break; + } +} +``` + +## Related checkers + +- [unreachableCode.md](unreachableCode.md) - code placed after an exit statement, rather than a `case` that can't be selected. diff --git a/man/checkers/unreadVariable.md b/man/checkers/unreadVariable.md new file mode 100644 index 00000000000..b90d870e3ad --- /dev/null +++ b/man/checkers/unreadVariable.md @@ -0,0 +1,55 @@ +# unreadVariable + +**Message**: Variable 'x' is assigned a value that is never used.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A variable is assigned a value (or modified), but that value is never read afterwards - so the +assignment has no effect. + +## Motivation + +An assignment whose value is never read is pointless code that adds noise for readers, and is often a +sign of a bug: a forgotten follow-up use of the variable, a typo'd variable name on the next line, or +dead code left over from a refactor. + +## How to fix + +Remove the pointless assignment, or actually read the value afterwards. + +Before: +```cpp +void f() { + int x = 5; // <- never read afterwards +} +``` + +After: +```cpp +void f() { + int x = 5; + print(x); +} +``` + +## False positives to be aware of + +- **An RAII guard object bound through `auto&&` can be wrongly reported as an unused assignment.** An + object whose entire purpose is its constructor/destructor side effect (for example a + `std::lock_guard`) has no "value" to read back, but is not just an unused assignment either: + ```cpp + #include + void f(std::mutex& mutex) { + auto&& g = std::lock_guard{ mutex }; // reported as unread, but 'g' locks/unlocks the mutex + } + ``` + +## Related checkers + +- [unusedVariable.md](unusedVariable.md) - the sibling finding for a variable that's never read *or* + written at all. +- [unassignedVariable.md](unassignedVariable.md) - the opposite situation: a variable read before it's + ever assigned. diff --git a/man/checkers/unsafeClassCanLeak.md b/man/checkers/unsafeClassCanLeak.md new file mode 100644 index 00000000000..4b3cd53823b --- /dev/null +++ b/man/checkers/unsafeClassCanLeak.md @@ -0,0 +1,41 @@ +# unsafeClassCanLeak + +**Message**: Class 'A' is unsafe, 'A::b' can leak by wrong usage.
+**Category**: Correctness
+**Severity**: Style
+**Language**: C++ + +## Description + +A class allocates a member (typically in its constructor) but has no visible destructor logic that +frees it - so every place that creates one of these objects has to remember to clean it up manually, +which is easy to get wrong. + +## Motivation + +A class that owns an allocation but doesn't free it in its own destructor pushes the cleanup +responsibility out onto every single place that uses the class - if even one of those places forgets, or +an exception skips the manual cleanup, the allocation leaks. Freeing what the class owns, inside its own +destructor, makes the class safe to use without every caller having to think about it. + +## How to fix + +Before: +```cpp +class A { + int *b; +public: + A() { b = new int; } // <- no destructor frees 'b' +}; +``` + +After: +```cpp +class A { + int *b; +public: + A() { b = new int; } + ~A() { delete b; } +}; +``` + diff --git a/man/checkers/unsafeClassRefMember.md b/man/checkers/unsafeClassRefMember.md new file mode 100644 index 00000000000..2c1381d993a --- /dev/null +++ b/man/checkers/unsafeClassRefMember.md @@ -0,0 +1,31 @@ +# unsafeClassRefMember + +**Message**: Storing reference to member argument in the class member is unsafe.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ + +## Description + +A `const&` member is initialized directly from a `const&` constructor argument - if a caller ever +passes a temporary or a short-lived local variable there, the member ends up referring to something +that's already been destroyed. + +This check only runs when analysis is configured through a Cppcheck GUI/project file that turns on +"safe checks" for classes (``) - there is no plain +command-line flag for it, so it will not appear in an ordinary `--enable=...` run. + +## Motivation + +Storing a reference member straight from a constructor's reference parameter looks harmless, but it +makes the object's validity depend entirely on the lifetime of whatever the caller happened to pass in - +if that argument was a temporary, the member becomes a dangling reference the moment the constructor +call's statement finishes, and every later use of the member is undefined behaviour. This check fires on +the constructor's signature alone - it doesn't (and can't, from the class definition by itself) know what +every caller actually passes in, so a class that's only ever constructed with a long-lived object is +flagged just the same as one that's handed a temporary. + +## How to fix + +Store a copy of the value instead of a reference to it, unless the class's contract explicitly requires +the referred-to object to outlive it. diff --git a/man/checkers/unsignedLessThanZero.md b/man/checkers/unsignedLessThanZero.md new file mode 100644 index 00000000000..26a93cc129c --- /dev/null +++ b/man/checkers/unsignedLessThanZero.md @@ -0,0 +1,52 @@ +# unsignedLessThanZero and unsignedPositive + +**Message**: Checking if unsigned expression 'x' is less than zero.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +An unsigned expression is compared against `0` in a way that can never be true (`unsignedLessThanZero`: +`x < 0`) or is always true (`unsignedPositive`: `x >= 0`), since an unsigned value can't be negative. + +## Motivation + +Both comparisons are tautological for an unsigned type, so the branch they guard either never runs or +always runs - not what the code visibly appears to be testing, and a common leftover from a variable +that used to be signed. + +## How to fix + +Before: +```cpp +void foo(unsigned int x) { + if (x < 0) {} // <- can never be true +} +``` + +After: +```cpp +void foo(int x) { + if (x < 0) {} +} +``` + +Before: +```cpp +void foo() { + for(unsigned char i = 10; i >= 0; i--) {} // <- always true, infinite loop risk +} +``` + +After: +```cpp +void foo() { + for(int i = 10; i >= 0; i--) {} +} +``` + +## Related checkers + +- [pointerLessThanZero.md](pointerLessThanZero.md) - the same idea, for a pointer compared against `0` + instead of an unsigned integer. diff --git a/man/checkers/unusedAllocatedMemory.md b/man/checkers/unusedAllocatedMemory.md new file mode 100644 index 00000000000..5f1d053694d --- /dev/null +++ b/man/checkers/unusedAllocatedMemory.md @@ -0,0 +1,42 @@ +# unusedAllocatedMemory + +**Message**: Variable 'p' is allocated memory that is never used.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A variable is assigned the result of an allocation (`malloc`, `new`, ...), but the allocated memory +itself is never read or written - only, at most, freed again. + +## Motivation + +Allocating memory that's never actually used is pointless, and usually a sign that some code that was +meant to fill or use the buffer was never written, or was removed during a refactor without noticing the +allocation. + +## How to fix + +Actually use the allocated memory, or remove the allocation. + +Before: +```cpp +void f() { + char* p = (char*)malloc(10); // <- allocated but never touched +} +``` + +After: +```cpp +void f() { + char* p = (char*)malloc(10); + p[0] = 'x'; + free(p); +} +``` + +## Related checkers + +- [unusedVariable.md](unusedVariable.md) - the general form of this finding, for any variable that's + never read or written. diff --git a/man/checkers/unusedFunction.md b/man/checkers/unusedFunction.md new file mode 100644 index 00000000000..0b878137dd0 --- /dev/null +++ b/man/checkers/unusedFunction.md @@ -0,0 +1,75 @@ +# unusedFunction and staticFunction + +**Message**: The function 'foo' is never used.
+**Category**: Unused Code
+**Severity**: Style
+**Language**: C/C++ + +## Description + +- `unusedFunction`: a function is defined but never called (and its address is never taken) anywhere + in the code cppcheck was given. +- `staticFunction` (C only): a function is only ever called from within its own file, and could be + given internal linkage by declaring it `static`. + +Both are only enabled through `--enable=unusedFunction` - not through `--enable=style`, even though +the messages are reported at `style` severity - and cppcheck explicitly recommends only enabling this +check when the whole program (every source file of the project) is being analyzed together, not a +single file in isolation. + +Both only consider a function "used" through a direct, by-name call (or its address being taken) +somewhere in that whole-program analysis. A virtual function is never checked, since it may only ever +be reached polymorphically through a base-class pointer/reference, which cppcheck doesn't attempt to +trace; operator overloads are likewise never checked, since they're often invoked implicitly through +operator syntax or required by generic code, making usage hard to establish reliably by name alone. + +## Motivation + +An unused function is dead code: it adds to what has to be read and maintained without doing +anything. For `staticFunction`, giving an internal-only function `static` linkage documents that it +is not part of the file's public interface, and can also help the compiler optimize it. + +## How to fix + +Remove a function that's genuinely unused, or make sure the code that calls it is included in the +same whole-program analysis. Add `static` to a C function that's only called from its own file. + +Before: +```cpp +void helper() { /* ... */ } // <- unusedFunction: never called anywhere in the analyzed code + +int main() { + return 0; +} +``` + +After: +```cpp +void helper() { /* ... */ } + +int main() { + helper(); + return 0; +} +``` + +Before (C): +```c +void helper(void) { /* ... */ } // <- staticFunction: only called from this file + +int main(void) { + helper(); + return 0; +} +``` + +After: +```c +static void helper(void) { /* ... */ } + +int main(void) { + helper(); + return 0; +} +``` + diff --git a/man/checkers/unusedLabel.md b/man/checkers/unusedLabel.md new file mode 100644 index 00000000000..3452ee3f0b4 --- /dev/null +++ b/man/checkers/unusedLabel.md @@ -0,0 +1,94 @@ +# unusedLabel, unusedLabelSwitch, unusedLabelConfiguration and unusedLabelSwitchConfiguration + +**Message**: Label 'x' is not used.
+**Category**: Code Quality
+**Severity**: Style/Warning
+**Language**: C/C++ + +## Description + +A `goto` label is declared but no `goto` anywhere in the file jumps to it. The four IDs are the same +finding, refined by context: + +- `unusedLabel`: the plain case - style severity. +- `unusedLabelSwitch`: the label sits directly where a `case` inside a `switch` was probably meant - + warning severity, since this is a more likely sign of an actual typo. +- `unusedLabelConfiguration` / `unusedLabelSwitchConfiguration`: the same two situations, but the file + also contains `#if`/`#ifdef`, so the missing `goto` might simply be in a preprocessor branch that + wasn't analyzed this time. + +## Motivation + +An unused label is either dead code left over from a refactor, or - especially when it appears right +where a `case` would fit inside a `switch` - a typo for a different keyword entirely. Either way it's +worth a second look: removing genuinely dead labels keeps the code honest about what it does, and +catching a `case`/label typo can fix a real logic bug. + +## How to fix + +Either remove the label, or actually jump to it - or, if it was meant to be a `case`, fix the typo. + +Before: +```cpp +void f() { + label: // <- nothing 'goto's here +} +``` + +After: either remove the label, or actually jump to it. +```cpp +void f() { + label: + ; + goto label; +} +``` + +Before: +```cpp +int test(char art) { + switch (art) { + caseZERO: // <- looks like a typo for 'case 0:' + return 0; + case 2: + return 2; + } + return -1; +} +``` + +After: +```cpp +int test(char art) { + switch (art) { + case 0: + return 0; + case 2: + return 2; + } + return -1; +} +``` + +Before: +```cpp +void f() { +#ifdef X + goto END; +#endif +END: // <- only reachable through code hidden behind '#ifdef X' + ; +} +``` + +After: keep the label's only `goto` in the same preprocessor branch as the label, or remove the label if +it's genuinely unused. +```cpp +void f() { +#ifdef X + goto END; +END: +#endif + ; +} +``` diff --git a/man/checkers/unusedPrivateFunction.md b/man/checkers/unusedPrivateFunction.md new file mode 100644 index 00000000000..e4809534a2c --- /dev/null +++ b/man/checkers/unusedPrivateFunction.md @@ -0,0 +1,42 @@ +# unusedPrivateFunction + +**Message**: Unused private function: 'Fred::f'
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +A private member function is never called from anywhere in the class (or a friend of it). + +## Motivation + +Since a private function can only ever be called from inside its own class (or a friend), one that's +never called anywhere in the visible code is dead code - it can be removed without affecting any +caller outside the class. + +## How to fix + +Before: +```cpp +class Fred { +private: + unsigned int f(); // <- unusedPrivateFunction: never called +public: + Fred(); +}; +Fred::Fred() { } +unsigned int Fred::f() { return 1; } +``` + +After: +```cpp +class Fred { +private: + unsigned int f(); +public: + Fred(); +}; +Fred::Fred() { f(); } +unsigned int Fred::f() { return 1; } +``` diff --git a/man/checkers/unusedScopedObject.md b/man/checkers/unusedScopedObject.md new file mode 100644 index 00000000000..6a29c74e621 --- /dev/null +++ b/man/checkers/unusedScopedObject.md @@ -0,0 +1,52 @@ +# unusedScopedObject + +**Message**: Instance of 'x' object is destroyed immediately.
+**Category**: Correctness
+**Severity**: Style
+**Language**: C++ + +## Description + +An object is constructed as a temporary (`Lock(mutex);` instead of `Lock lock(mutex);`) and destroyed +again immediately at the end of the same statement - if the class's constructor/destructor pair matters +for its side effects (as with a scope guard), this defeats the purpose, since the "lock" is released +before the very next statement runs. + +## Motivation + +RAII types like lock guards, scoped timers, or transaction guards rely on their destructor running at +the end of a *scope*, not at the end of the single statement that constructed them. Forgetting to give +the object a name accidentally destroys it right away, silently disabling whatever protection it was +supposed to provide for the following code. + +## How to fix + +Before: +```cpp +#include +class Lock { +public: + Lock(int i) { std::cout << "Lock " << i << std::endl; } + ~Lock() { std::cout << "~Lock" << std::endl; } +}; +int main() { + Lock(123); // <- constructed and destroyed on this line alone + std::cout << "hello" << std::endl; + return 0; +} +``` + +After: +```cpp +#include +class Lock { +public: + explicit Lock(int i) { std::cout << "Lock " << i << std::endl; } + ~Lock() { std::cout << "~Lock" << std::endl; } +}; +int main() { + Lock lock(123); + std::cout << "hello" << std::endl; + return 0; +} +``` diff --git a/man/checkers/unusedStructMember.md b/man/checkers/unusedStructMember.md new file mode 100644 index 00000000000..5c08ef7b65f --- /dev/null +++ b/man/checkers/unusedStructMember.md @@ -0,0 +1,45 @@ +# unusedStructMember + +**Message**: struct member 'Point::unusedField' is never used.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A struct/class/union member is never referenced anywhere in the file. + +This checker performs a single-file analysis, it checks structs declared in the source file. This +checker does not work properly if you analyze a header file directly. You are not recommended to +run cppcheck on header files directly. + +## Motivation + +An unused struct member usually means the field can be removed, or - just as often - that it *should* +be used somewhere and isn't, which is worth a second look. + +## How to fix + +Remove the field, or actually use it. + +Before: +```cpp +struct Point { + int x; + int y; + int unusedField; // <- never referenced anywhere +}; +``` + +After: +```cpp +struct Point { + int x; + int y; +}; +``` + +## Related checkers + +- [unusedVariable.md](unusedVariable.md) - the same idea, for a local variable instead of a struct + member. diff --git a/man/checkers/unusedVariable.md b/man/checkers/unusedVariable.md new file mode 100644 index 00000000000..858875ecca7 --- /dev/null +++ b/man/checkers/unusedVariable.md @@ -0,0 +1,46 @@ +# unusedVariable + +**Message**: Unused variable: x
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A variable is declared but never read or written at all. + +## Motivation + +A variable that's never touched after being declared is dead code: it adds to what has to be read and +maintained without doing anything, and is often a sign of a bug - a typo'd variable name, or code left +over from a refactor that no longer does what it used to. + +## How to fix + +Remove the variable, or actually use it. + +Before: +```cpp +void f() { + int x = 5; // <- declared and never read or written again +} +``` + +After: +```cpp +void f() { + int x = 5; + print(x); +} +``` + +## Related checkers + +- [unreadVariable.md](unreadVariable.md) - the sibling finding for a variable that *is* assigned a + value, but that value is never read afterwards. +- [unusedAllocatedMemory.md](unusedAllocatedMemory.md) - the same idea, specifically for memory obtained + from an allocation function. +- [unassignedVariable.md](unassignedVariable.md) - the opposite situation: a variable read before it's + ever assigned. +- [unusedStructMember.md](unusedStructMember.md) - the same idea, for a struct/class/union member + instead of a local variable. diff --git a/man/checkers/useClosedFile.md b/man/checkers/useClosedFile.md new file mode 100644 index 00000000000..aa01de27a37 --- /dev/null +++ b/man/checkers/useClosedFile.md @@ -0,0 +1,48 @@ +# useClosedFile + +**Message**: Used file that is not opened.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A read, write, or positioning call is made on a `FILE*` after it's already been closed. + +## Motivation + +Using a file handle after `fclose()` is undefined behaviour - the underlying resource is gone, so any +further operation on it can't be relied on to do anything sensible. + +## How to fix + +Before: +```cpp +#include +void f() { + FILE *f1 = fopen("a.txt", "r"); + if (!f1) return; + fclose(f1); + char buf[1]; + fread(buf, 1, 1, f1); // <- 'f1' is already closed +} +``` + +After: +```cpp +#include +void f() { + FILE *f1 = fopen("a.txt", "r"); + if (!f1) return; + char buf[1]; + fread(buf, 1, 1, f1); + fclose(f1); +} +``` + +## Related checkers + +- [readWriteOnlyFile.md](readWriteOnlyFile.md), [writeReadOnlyFile.md](writeReadOnlyFile.md), + [IOWithoutPositioning.md](IOWithoutPositioning.md), [seekOnAppendedFile.md](seekOnAppendedFile.md), + [incompatibleFileOpen.md](incompatibleFileOpen.md) - other checks that follow the same `FILE*` through + a function to catch a different kind of open-mode/state mismatch. diff --git a/man/checkers/useInitializationList.md b/man/checkers/useInitializationList.md new file mode 100644 index 00000000000..94975d622e0 --- /dev/null +++ b/man/checkers/useInitializationList.md @@ -0,0 +1,49 @@ +# useInitializationList + +**Message**: Variable 's' is assigned in constructor body. Consider performing initialization in initialization list.
+**Category**: Performance
+**Severity**: Performance
+**Language**: C++ + +## Description + +A constructor assigns a member a value in its body, where the exact same value could have been passed +via the member-initializer list instead - doing it in the list avoids first default-constructing the +member and then immediately overwriting it. + +## Motivation + +Assigning a member in the constructor body means the member is first default-constructed and then +immediately reassigned - for a type with a nontrivial default constructor (like `std::string`), that's +extra, avoidable work done on every single object construction. + +## How to fix + +Move the assignment into the member-initializer list. + +Before: +```cpp +#include +class C { + std::string s; +public: + explicit C(const std::string& str) { + s = str; // <- could be done in the initializer list instead + } +}; +``` + +After: +```cpp +#include +class C { + std::string s; +public: + explicit C(const std::string& str) : s(str) {} +}; +``` + +## Related checkers + +- [initializerList.md](initializerList.md) - a different constructor-initializer-list pitfall, about + the *order* members are listed in, rather than whether the list is used at all. diff --git a/man/checkers/useStandardLibrary.md b/man/checkers/useStandardLibrary.md new file mode 100644 index 00000000000..6eac1d7aa66 --- /dev/null +++ b/man/checkers/useStandardLibrary.md @@ -0,0 +1,41 @@ +# useStandardLibrary + +**Message**: Consider using std::memcpy instead of loop.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ only + +## Description + +A hand-written byte-by-byte copy or fill loop that could be replaced with `memcpy`/`memset`. + +## Motivation + +A standard library function like `memcpy()` is at least as fast as a hand-written loop (the library or +compiler can vectorize/optimize it directly), is shorter to read, and doesn't need to be double-checked +for off-by-one mistakes the way a raw loop does. + +## How to fix + +Before: +```cpp +#include +void f(void* dst, const void* src, const size_t count) { + size_t i; + for (i = 0; count > i; ++i) // <- hand-written copy loop + (reinterpret_cast(dst))[i] = (reinterpret_cast(src))[i]; +} +``` + +After: +```cpp +#include +void f(void* dst, const void* src, const size_t count) { + std::memcpy(dst, src, count); +} +``` + +## Related checkers + +- [returnStdMoveLocal.md](returnStdMoveLocal.md) - an unrelated performance suggestion in the same + checker: avoiding a `std::move()` that defeats copy elision. diff --git a/man/checkers/useStlAlgorithm.md b/man/checkers/useStlAlgorithm.md new file mode 100644 index 00000000000..c5fe6431be9 --- /dev/null +++ b/man/checkers/useStlAlgorithm.md @@ -0,0 +1,49 @@ +# useStlAlgorithm + +**Message**: Consider using std::any_of algorithm instead of a raw loop.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +A hand-written loop follows a shape (accumulate a value, count matches, find the first/last match, copy +matching elements, ...) that a single call to a standard `` function would do more clearly +and with less room for an off-by-one mistake. + +## Motivation + +A raw loop that reimplements a standard algorithm is more code to read and more room for a subtle bug +(an off-by-one, a missed edge case) than calling the algorithm that already exists for exactly this +purpose. Naming the intent directly (`std::any_of`, `std::count_if`, `std::find_if`, `std::copy_if`, +`std::transform`, ...) also tells a reader what the loop is *for* without having to trace through its +body. + +## How to fix + +Before: +```cpp +#include +bool f(bool b) { + std::vector v; + if (b) + v.push_back(0); + for (auto i : v) // <- consider std::any_of instead of this raw loop + if (v[i] > 0) + return true; + return false; +} +``` + +After: +```cpp +#include +#include +bool f(bool b) { + std::vector v; + if (b) + v.push_back(0); + return std::any_of(v.begin(), v.end(), [](int i){ return v[i] > 0; }); +} +``` + diff --git a/man/checkers/uselessAssignmentArg.md b/man/checkers/uselessAssignmentArg.md new file mode 100644 index 00000000000..e19d6f5be23 --- /dev/null +++ b/man/checkers/uselessAssignmentArg.md @@ -0,0 +1,38 @@ +# uselessAssignmentArg and uselessAssignmentPtrArg + +**Message**: Assignment of function parameter has no effect outside the function.
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A function parameter (plain or pointer) is assigned a new value that is never used afterwards - since +parameters are passed by value in C/C++, this change is invisible to the caller and has no effect. + +- `uselessAssignmentArg`: the parameter is an ordinary by-value parameter. +- `uselessAssignmentPtrArg`: the parameter is a pointer itself being reassigned (not what it points to). + +## Motivation + +Assigning to a by-value parameter without reading it again afterwards looks like it's meant to +communicate something back to the caller, but it can't - parameters are local copies. This usually +means either the assignment is pointless leftover code, or the author actually needed a pointer/ +reference parameter (or a return value) to get the result back out. + +## How to fix + +Before: +```cpp +void foo(int b) { + b = 5; // <- uselessAssignmentArg: caller never sees this +} +``` + +After: change the return type instead of the parameter, or drop the assignment. +```cpp +int foo() { + return 5; +} +``` + diff --git a/man/checkers/uselessCallsCompare.md b/man/checkers/uselessCallsCompare.md new file mode 100644 index 00000000000..cf0bcd6677a --- /dev/null +++ b/man/checkers/uselessCallsCompare.md @@ -0,0 +1,43 @@ +# uselessCallsCompare + +**Message**: It is inefficient to call 'x.compare(x)' as it always returns 0.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ + +## Description + +`x.compare(x)` - calling `.compare()` with the object itself as the argument always returns `0`, which +looks like a meaningful check but never is - usually a copy-paste mistake where the wrong variable was +used for one side. + +## Motivation + +Comparing a value against itself is always true (or, for `.compare()`, always `0`) - the call is dead +code that adds nothing, and the fact that it was written at all suggests the intent was to compare two +different values, one of which was mistyped. + +## How to fix + +Before: +```cpp +#include +void f() { + std::string s1, s2; + s2.compare(s2); // <- always returns 0 +} +``` + +After: +```cpp +#include +void f() { + std::string s1, s2; + s1.compare(s2); +} +``` + +## Related checkers + +- [uselessCallsSwap.md](uselessCallsSwap.md) - the same self-argument mistake, for `.swap()` instead of + `.compare()`. diff --git a/man/checkers/uselessCallsConstructor.md b/man/checkers/uselessCallsConstructor.md new file mode 100644 index 00000000000..8643abf433c --- /dev/null +++ b/man/checkers/uselessCallsConstructor.md @@ -0,0 +1,38 @@ +# uselessCallsConstructor + +**Message**: Inefficient constructor call: container 'x' is assigned a partial copy of itself. Use erase() or resize() instead.
+**Category**: Performance
+**Severity**: Performance
+**Language**: C++ + +## Description + +A container is assigned a range constructed from its own begin/(begin+offset) - this makes an +unnecessary temporary copy of part of the container's own data, when `erase()`/`resize()` would do the +same trim in place. + +## Motivation + +Building a whole new range-constructed container from a slice of a container's own elements, just to +assign it back over the original, copies data that's already sitting in the right place - an in-place +trim (`erase()`/`resize()`) achieves the same final content without the temporary copy. + +## How to fix + +Before: +```cpp +#include +std::string f(std::string s, std::size_t end) { + s = { s.begin(), s.begin() + end }; // <- an unnecessary partial self-copy + return s; +} +``` + +After: +```cpp +#include +std::string f(std::string s, std::size_t end) { + s.resize(end); + return s; +} +``` diff --git a/man/checkers/uselessCallsEmpty.md b/man/checkers/uselessCallsEmpty.md new file mode 100644 index 00000000000..50c7d9479b2 --- /dev/null +++ b/man/checkers/uselessCallsEmpty.md @@ -0,0 +1,36 @@ +# uselessCallsEmpty + +**Message**: Ineffective call of function 'empty()'. Did you intend to call 'clear()' instead?
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ + +## Description + +`.empty()` is called and its result is thrown away - since `.empty()` has no side effect, this almost +certainly should have been `.clear()`. + +## Motivation + +`.empty()` only reports whether a container has any elements; it never removes anything. Calling it and +discarding the result does nothing at all, which is a strong sign the intended call was `.clear()` +(which does have the effect the code was presumably trying to achieve). + +## How to fix + +Before: +```cpp +#include +bool foo(std::vector& v) { + v.empty(); // <- result discarded, has no effect + return v.empty(); +} +``` + +After: +```cpp +#include +bool foo(std::vector& v) { + return v.empty(); +} +``` diff --git a/man/checkers/uselessCallsRemove.md b/man/checkers/uselessCallsRemove.md new file mode 100644 index 00000000000..753c3bb2e99 --- /dev/null +++ b/man/checkers/uselessCallsRemove.md @@ -0,0 +1,41 @@ +# uselessCallsRemove + +**Message**: Return value of std::remove() ignored. Elements remain in container.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C++ + +## Description + +The return value of `std::remove()`/`std::remove_if()`/`std::unique()` is ignored - these algorithms +don't actually shrink the container, they only move the elements to keep to the front and return the +new logical end; without also calling the container's `erase()` with that return value, the "removed" +elements are still there. + +## Motivation + +`std::remove()`/`std::remove_if()`/`std::unique()` cannot resize a container themselves (they only see +a pair of iterators, not the container), so they can only rearrange elements and report where the "new +end" is. Ignoring that return value leaves the container at its original size, with the elements that +were supposed to be removed still physically present (just in an unspecified state) between the new +logical end and the true end - the classic "erase-remove idiom" exists precisely to complete this. + +## How to fix + +Before: +```cpp +#include +#include +void f(std::vector a, int val) { + std::remove(a.begin(), a.end(), val); // <- elements aren't actually removed +} +``` + +After: +```cpp +#include +#include +void f(std::vector a, int val) { + a.erase(std::remove(a.begin(), a.end(), val), a.end()); +} +``` diff --git a/man/checkers/uselessCallsSubstr.md b/man/checkers/uselessCallsSubstr.md new file mode 100644 index 00000000000..a5c0825a6dc --- /dev/null +++ b/man/checkers/uselessCallsSubstr.md @@ -0,0 +1,39 @@ +# uselessCallsSubstr + +**Message**: Ineffective call of function 'substr' because it returns a copy of the object. Use operator= instead.
+**Category**: Performance
+**Severity**: Performance
+**Language**: C++ + +## Description + +A `substr()` call whose arguments make it a no-op (returns an unmodified copy of the whole string), +always return an empty string, or (when assigned back to the same string) just a slower way to write +`resize()`/`pop_back()`/`replace()`. + +## Motivation + +`substr()` copies characters into a new string - when the requested range happens to be the whole +string, an empty range, or a prefix/suffix of the string it's being assigned back into, that copy is +pure overhead compared to the more direct operation (`operator=`, `resize()`, `pop_back()`, `replace()`) +that achieves the same result. + +## How to fix + +Before: +```cpp +#include +void f() { + std::string s1, s2; + s2 = s1.substr(); // <- substr() with no arguments just copies the whole string +} +``` + +After: +```cpp +#include +void f() { + std::string s1, s2; + s2 = s1; +} +``` diff --git a/man/checkers/uselessCallsSwap.md b/man/checkers/uselessCallsSwap.md new file mode 100644 index 00000000000..d34c5cb1a55 --- /dev/null +++ b/man/checkers/uselessCallsSwap.md @@ -0,0 +1,42 @@ +# uselessCallsSwap + +**Message**: It is inefficient to swap a object with itself by calling 'x.swap(x)'
+**Category**: Performance
+**Severity**: Performance
+**Language**: C++ + +## Description + +`x.swap(x)` - calling `.swap()` with the object itself as the argument is an inefficient no-op - +usually a copy-paste mistake where the wrong variable was used for one side. + +## Motivation + +Swapping a value with itself has no effect: the call still does the work of a swap (temporaries, +moves) for a result that's guaranteed to be identical to not calling it at all. The fact that it was +written at all suggests the intent was to swap with a different variable, one of which was mistyped. + +## How to fix + +Before: +```cpp +#include +void f() { + std::string s1, s2; + s2.swap(s2); // <- no effect, just wasted work +} +``` + +After: +```cpp +#include +void f() { + std::string s1, s2; + s1.swap(s2); +} +``` + +## Related checkers + +- [uselessCallsCompare.md](uselessCallsCompare.md) - the same self-argument mistake, for `.compare()` + instead of `.swap()`. diff --git a/man/checkers/uselessOverride.md b/man/checkers/uselessOverride.md new file mode 100644 index 00000000000..3907a5b01a9 --- /dev/null +++ b/man/checkers/uselessOverride.md @@ -0,0 +1,39 @@ +# uselessOverride + +**Message**: The function 'f' is an unnecessary overload; it is identical to the parent function
+**Category**: Code Quality
+**Severity**: Style
+**Language**: C++ + +## Description + +An overriding function's body is either functionally identical to the base version, or just calls the +base version and returns its result - the override adds a function call for no behavioural difference. + +## Motivation + +An override that does nothing differently from the base class is dead weight: it adds an indirection +and a place for the two versions to accidentally drift apart later, without changing what the program +does today. + +## How to fix + +Before: +```cpp +struct B { virtual int f() { return 5; } }; +struct D : B { + int f() override { return B::f(); } // <- uselessOverride: identical to the base version +}; +``` + +After: remove the pointless override entirely. +```cpp +struct B { virtual int f() { return 5; } }; +struct D : B { +}; +``` + +## Related checkers + +- [missingOverride.md](missingOverride.md) - for a function that overrides a base one without being + marked `override`, regardless of whether the override is useless. diff --git a/man/checkers/va_end_missing.md b/man/checkers/va_end_missing.md new file mode 100644 index 00000000000..80ac6e0ea26 --- /dev/null +++ b/man/checkers/va_end_missing.md @@ -0,0 +1,42 @@ +# va_end_missing + +**Message**: va_list 'args' was opened but not closed by va_end().
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A local `va_list` variable is opened with `va_start()`/`va_copy()`, but the function can finish without +it being closed by `va_end()`. + +## Motivation + +Every `va_list` opened with `va_start()`/`va_copy()` must be matched with exactly one `va_end()` call +before it goes out of scope. Leaving one open is undefined behaviour on some implementations, and can +also leak resources the implementation associated with iterating the variadic arguments. + +## How to fix + +Call `va_end()` on every path that leaves the function after `va_start()`. + +Before: +```cpp +void log(const char* fmt, ...) { + va_list args; + va_start(args, fmt); +} // <- va_end() never called +``` + +After: +```cpp +void log(const char* fmt, ...) { + va_list args; + va_start(args, fmt); + va_end(args); +} +``` + +## Related checkers + +- [va_start_wrongParameter.md](va_start_wrongParameter.md), [va_start_referencePassed.md](va_start_referencePassed.md), [va_list_usedBeforeStarted.md](va_list_usedBeforeStarted.md), [va_start_subsequentCalls.md](va_start_subsequentCalls.md) - other misuses of the same `va_list`/`va_start()`/`va_end()` facility. diff --git a/man/checkers/va_list_usedBeforeStarted.md b/man/checkers/va_list_usedBeforeStarted.md new file mode 100644 index 00000000000..7ddb4844cce --- /dev/null +++ b/man/checkers/va_list_usedBeforeStarted.md @@ -0,0 +1,46 @@ +# va_list_usedBeforeStarted + +**Message**: va_list 'args' used before va_start() was called.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A `va_list` variable is read - passed to `va_arg()`, `va_copy()` or `va_end()` - before `va_start()` +(or `va_copy()`) was called on it, or after it was already closed with `va_end()`. + +## Motivation + +A `va_list` only refers to a valid argument sequence between a matching `va_start()`/`va_copy()` and the +`va_end()` that closes it. Reading it outside that window - too early, or again after it's already been +closed - is undefined behaviour, since there's no argument sequence for it to actually point to. + +## How to fix + +Only read a `va_list` after it has been opened with `va_start()`/`va_copy()`, and before it is closed +with `va_end()`. + +Before: +```cpp +void log(const char* fmt, ...) { + va_list args; + int first = va_arg(args, int); // <- used before va_start() + va_start(args, fmt); + va_end(args); +} +``` + +After: +```cpp +void log(const char* fmt, ...) { + va_list args; + va_start(args, fmt); + int first = va_arg(args, int); + va_end(args); +} +``` + +## Related checkers + +- [va_start_wrongParameter.md](va_start_wrongParameter.md), [va_start_referencePassed.md](va_start_referencePassed.md), [va_start_subsequentCalls.md](va_start_subsequentCalls.md), [va_end_missing.md](va_end_missing.md) - other misuses of the same `va_list`/`va_start()`/`va_end()` facility. diff --git a/man/checkers/va_start_referencePassed.md b/man/checkers/va_start_referencePassed.md new file mode 100644 index 00000000000..f877bb76df4 --- /dev/null +++ b/man/checkers/va_start_referencePassed.md @@ -0,0 +1,25 @@ +# va_start_referencePassed + +**Message**: Using reference 'x' as parameter for va_start() results in undefined behaviour.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +A reference parameter is passed as the second argument to `va_start()`. This is undefined behaviour. + +## Motivation + +`va_start()` needs the actual last named parameter as it's stored on the stack, not a reference to it - +a reference is a different object from what the calling convention expects at that point, so using one +here doesn't reliably locate the variadic arguments that follow. + +## How to fix + +Avoid declaring the last named parameter before `...` as a reference, or otherwise ensure `va_start()` +is given the real parameter rather than a reference to it. + +## Related checkers + +- [va_start_wrongParameter.md](va_start_wrongParameter.md), [va_list_usedBeforeStarted.md](va_list_usedBeforeStarted.md), [va_start_subsequentCalls.md](va_start_subsequentCalls.md), [va_end_missing.md](va_end_missing.md) - other misuses of the same `va_list`/`va_start()`/`va_end()` facility. diff --git a/man/checkers/va_start_subsequentCalls.md b/man/checkers/va_start_subsequentCalls.md new file mode 100644 index 00000000000..6cd8fb14dac --- /dev/null +++ b/man/checkers/va_start_subsequentCalls.md @@ -0,0 +1,25 @@ +# va_start_subsequentCalls + +**Message**: va_start() or va_copy() called subsequently on 'args' without va_end() in between.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +`va_start()` or `va_copy()` is called again on a `va_list` that is already open, without an intervening +`va_end()`. + +## Motivation + +Reopening a `va_list` that's still open, without closing it first, is undefined behaviour on many +implementations - the two calls to `va_start()`/`va_copy()` can leave the `va_list` in an inconsistent +state, and whatever `va_arg()` reads afterward is unreliable. + +## How to fix + +Call `va_end()` on a `va_list` before opening it again with `va_start()`/`va_copy()`. + +## Related checkers + +- [va_start_wrongParameter.md](va_start_wrongParameter.md), [va_start_referencePassed.md](va_start_referencePassed.md), [va_list_usedBeforeStarted.md](va_list_usedBeforeStarted.md), [va_end_missing.md](va_end_missing.md) - other misuses of the same `va_list`/`va_start()`/`va_end()` facility. diff --git a/man/checkers/va_start_wrongParameter.md b/man/checkers/va_start_wrongParameter.md new file mode 100644 index 00000000000..32ecd73ce4c --- /dev/null +++ b/man/checkers/va_start_wrongParameter.md @@ -0,0 +1,44 @@ +# va_start_wrongParameter + +**Message**: 'level' given to va_start() is not last named argument of the function. Did you intend to pass 'fmt'?
+**Category**: Undefined Behaviour
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +The parameter passed as the second argument to `va_start()` is not the function's actual last named +parameter (the one right before `...`). + +## Motivation + +`va_start()` needs the last named parameter to locate where the variadic arguments begin on the stack. +Passing any other parameter is undefined behaviour - `va_arg()` calls that follow can then read garbage +instead of the actual variadic arguments, and the exact symptom depends on the compiler and calling +convention. + +## How to fix + +Pass the function's true last named parameter to `va_start()`. + +Before: +```cpp +void log(const char* fmt, const char* tag, int level, ...) { + va_list args; + va_start(args, fmt); // <- 'level' is the actual last named parameter + va_end(args); +} +``` + +After: +```cpp +void log(const char* fmt, const char* tag, int level, ...) { + va_list args; + va_start(args, level); + va_end(args); +} +``` + +## Related checkers + +- [va_start_referencePassed.md](va_start_referencePassed.md), [va_list_usedBeforeStarted.md](va_list_usedBeforeStarted.md), [va_start_subsequentCalls.md](va_start_subsequentCalls.md), [va_end_missing.md](va_end_missing.md) - other misuses of the same `va_list`/`va_start()`/`va_end()` facility. diff --git a/man/checkers/varFuncNullUB.md b/man/checkers/varFuncNullUB.md new file mode 100644 index 00000000000..41694c22cb0 --- /dev/null +++ b/man/checkers/varFuncNullUB.md @@ -0,0 +1,37 @@ +# varFuncNullUB + +**Message**: Passing NULL after the last typed argument to a variadic function leads to undefined behaviour.
+**Category**: Undefined Behaviour
+**Severity**: Portability
+**Language**: C/C++ + +## Description + +`NULL` is passed as the last argument to a variadic function (`...`) - on platforms where `NULL` is +defined as a plain `0` rather than a pointer-sized constant, the function reading its arguments through +`va_arg()` may misinterpret it, since the two constants aren't guaranteed to be the same size. + +## Motivation + +`NULL`'s definition (`0`, `0L`, or `(void*)0`) is implementation-defined. On a platform where +`sizeof(int) != sizeof(void*)` and `NULL` expands to a plain integer `0`, a variadic function expecting +to read a pointer-sized sentinel through `va_arg()` reads the wrong number of bytes - which can crash or +silently read garbage for the following arguments. The bug is invisible on platforms where `NULL` +happens to be pointer-sized, so it can go unnoticed for a long time. + +## How to fix + +Cast the sentinel to the pointer type the function actually expects, instead of relying on `NULL`'s +platform-specific definition. + +Before: +```cpp +void a(...); +void b() { a(NULL); } // <- passing NULL as the last variadic argument +``` + +After: +```cpp +void a(...); +void b() { a((void*)0); } +``` diff --git a/man/checkers/variableScope.md b/man/checkers/variableScope.md new file mode 100644 index 00000000000..1a0d82f93c3 --- /dev/null +++ b/man/checkers/variableScope.md @@ -0,0 +1,45 @@ +# variableScope + +**Message**: The scope of the variable 'x' can be reduced.
+**Category**: Readability
+**Severity**: Style
+**Language**: C/C++ + +## Description + +A variable is declared in an outer scope, it can be declared in an inner scope. + +## Motivation + +It is common practice, especially in C++ code, to declare variables in inner scope when +possible. + +The motivation is to make the code more readable. + +Opinions about what is more readable can differ so make your own decisions - these warnings +can be easily suppressed. + +## How to fix + +Before: +```cpp +#include +void f(bool x) { + int i = 0; // <- 'i' is only used inside the 'if' below + if (x) { + i = 10; + printf("%d\n", i); + } +} +``` + +After: +```cpp +#include +void f(bool x) { + if (x) { + int i = 10; + printf("%d\n", i); + } +} +``` diff --git a/man/checkers/virtualCallInConstructor.md b/man/checkers/virtualCallInConstructor.md new file mode 100644 index 00000000000..8e8a2a55aa6 --- /dev/null +++ b/man/checkers/virtualCallInConstructor.md @@ -0,0 +1,74 @@ +# virtualCallInConstructor and pureVirtualCall + +**Message**: Call of pure virtual function 'pure' in constructor.
+**Category**: Correctness/Undefined Behaviour
+**Severity**: Warning/Error
+**Language**: C++ + +## Description + +A constructor or destructor calls a virtual function on `this`. During construction/destruction the +object's dynamic type is only ever the class currently running, so this never dispatches to a derived +override the way it looks like it should. + +- `virtualCallInConstructor`: the called function does have a body in the current class, so the call is + well-defined - it's just probably not calling what the author expected. +- `pureVirtualCall`: the called function has no implementation at all (pure virtual), so the call is + undefined behaviour outright. + +## Motivation + +Code that calls a virtual function from a constructor or destructor, expecting a derived class's +override to run, is a common misunderstanding of C++'s object-construction model. `pureVirtualCall` in +particular is a crash waiting to happen, since there is no function body to call at all at that point. + +## How to fix + +Before: +```cpp +class A { + virtual int f() { return 1; } +public: + A(); +}; +A::A() { + f(); // <- virtualCallInConstructor: dynamic binding doesn't apply here +} +``` + +After: +```cpp +class A { + virtual int f() { return 1; } + int init() { return 1; } +public: + A(); +}; +A::A() { + init(); +} +``` + +Before: +```cpp +class A { + virtual void pure() = 0; +public: + A(); +}; +A::A() { + pure(); // <- pureVirtualCall: 'pure' has no body to call +} +``` + +After: +```cpp +class A { + virtual void pure() = 0; +public: + A(); +}; +A::A() { +} +``` + diff --git a/man/checkers/virtualDestructor.md b/man/checkers/virtualDestructor.md new file mode 100644 index 00000000000..0990b45c78d --- /dev/null +++ b/man/checkers/virtualDestructor.md @@ -0,0 +1,54 @@ +# virtualDestructor + +**Message**: Class 'Base' which is inherited by class 'Derived' does not have a virtual destructor.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C++ + +## Description + +A base class has virtual member functions (so it's clearly meant to be used polymorphically) but a +non-virtual destructor - deleting a derived object through a base-class pointer then only runs the base +class's destructor, leaking whatever the derived part owned. + +## Motivation + +This is outright undefined behaviour that can appear to "work" (the memory for the derived part is +still freed by the base's `delete`, even though its destructor never ran) until an unrelated change - +adding a derived class that owns a resource, for instance - makes it actually leak or corrupt memory. + +## How to fix + +Before: +```cpp +class Base { +public: + virtual void f() {} + ~Base() {} // <- virtualDestructor: not virtual +}; +class Derived : public Base { +public: + ~Derived() { (void)11; } +}; +void f() { + Base *base = new Derived; + delete base; +} +``` + +After: +```cpp +class Base { +public: + virtual void f() {} + virtual ~Base() {} +}; +class Derived : public Base { +public: + ~Derived() override { (void)11; } +}; +void f() { + Base *base = new Derived; + delete base; +} +``` diff --git a/man/checkers/writeReadOnlyFile.md b/man/checkers/writeReadOnlyFile.md new file mode 100644 index 00000000000..2a1c2c167bf --- /dev/null +++ b/man/checkers/writeReadOnlyFile.md @@ -0,0 +1,46 @@ +# writeReadOnlyFile + +**Message**: Write operation on a file that was opened only for reading.
+**Category**: Undefined Behaviour
+**Severity**: Error
+**Language**: C/C++ + +## Description + +The file was opened in a mode (`"r"`) that only allows reading, and the code then writes to it. + +## Motivation + +Writing to a stream that was opened read-only is undefined behaviour in the C standard - even where an +implementation happens to do something predictable with it, code relying on that isn't portable. + +## How to fix + +Before: +```cpp +#include +void f() { + FILE *fp = fopen("a.txt", "r"); + if (!fp) return; + fwrite("x", 1, 1, fp); // <- 'fp' was only opened for reading + fclose(fp); +} +``` + +After: +```cpp +#include +void f() { + FILE *fp = fopen("a.txt", "w"); + if (!fp) return; + fwrite("x", 1, 1, fp); + fclose(fp); +} +``` + +## Related checkers + +- [readWriteOnlyFile.md](readWriteOnlyFile.md) - the opposite mismatch: reading from a write-only file. +- [useClosedFile.md](useClosedFile.md), [IOWithoutPositioning.md](IOWithoutPositioning.md), + [seekOnAppendedFile.md](seekOnAppendedFile.md), [incompatibleFileOpen.md](incompatibleFileOpen.md) - + other checks that follow the same `FILE*` through a function. diff --git a/man/checkers/wrongPrintfScanfArgNum.md b/man/checkers/wrongPrintfScanfArgNum.md new file mode 100644 index 00000000000..1455678b6b6 --- /dev/null +++ b/man/checkers/wrongPrintfScanfArgNum.md @@ -0,0 +1,40 @@ +# wrongPrintfScanfArgNum + +**Message**: printf format string requires 2 parameters but only 1 is given.
+**Category**: Undefined Behaviour
+**Severity**: Error/Warning
+**Language**: C/C++ + +## Description + +The format string's conversion specifiers (`%d`, `%s`, ...) don't match the number of arguments +actually given to a `printf`/`scanf`-family call - too many or too few. + +## Motivation + +`printf`/`scanf` format strings are not type- or count-checked by the C or C++ language itself - the +compiler trusts that whatever conversion specifiers you wrote match whatever arguments follow. Too few +arguments means a conversion reads garbage memory as if it were a real argument; too many just means +wasted arguments, but often signals a forgotten `%` specifier. + +## How to fix + +Before: +```cpp +#include +void f() { + printf("%d%s", 1); // <- format string needs 2 arguments, only 1 given +} +``` + +After: +```cpp +#include +void f() { + printf("%d%s", 1, "x"); +} +``` + +## Related checkers + +- [wrongPrintfScanfParameterPositionError.md](wrongPrintfScanfParameterPositionError.md) - a related mistake with POSIX positional specifiers (`%2$d`). diff --git a/man/checkers/wrongPrintfScanfParameterPositionError.md b/man/checkers/wrongPrintfScanfParameterPositionError.md new file mode 100644 index 00000000000..c3ea86c32b3 --- /dev/null +++ b/man/checkers/wrongPrintfScanfParameterPositionError.md @@ -0,0 +1,39 @@ +# wrongPrintfScanfParameterPositionError + +**Message**: printf: referencing parameter 4 while 3 arguments given.
+**Category**: Undefined Behaviour
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A POSIX positional specifier (`%2$d`) references an argument number that doesn't exist, or numbering +starts at `0` instead of `1`. + +## Motivation + +POSIX positional format specifiers let a format string reference its arguments out of order, but that +also means a typo in the position number silently reads the wrong argument, or - as checked here - one +that isn't even there. + +## How to fix + +Before: +```cpp +#include +void foo() { + printf("%1$d, %d, %4$d\n", 1, 2, 3); // <- no 4th argument +} +``` + +After: +```cpp +#include +void foo() { + printf("%1$d, %d, %3$d\n", 1, 2, 3); +} +``` + +## Related checkers + +- [wrongPrintfScanfArgNum.md](wrongPrintfScanfArgNum.md) - the plain (non-positional) argument-count mismatch. diff --git a/man/checkers/wrongfeofUsage.md b/man/checkers/wrongfeofUsage.md new file mode 100644 index 00000000000..9a3d0bbf5f6 --- /dev/null +++ b/man/checkers/wrongfeofUsage.md @@ -0,0 +1,42 @@ +# wrongfeofUsage + +**Message**: Using feof() as a loop condition causes the last line to be processed twice.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +`feof()` returns non-zero only after a read operation has failed because the end of file was reached. When used as the sole condition of a loop, the loop body executes one extra time after the last successful read: the read fails silently (or returns partial data), and only then does `feof()` return true and terminate the loop. + +This checker matches `while (!feof(fp))` and `do { ... } while (!feof(fp))` loops and warns when all of the following are true: + +- The loop body contains at least one file-read call (`fgets`, `fgetc`, `getc`, `fread`, or `fscanf`) on the same file pointer. +- The destination (return value or output buffer) of the **last** file-read call in the loop is used after that call within the same loop iteration. + +The checker skips loops that contain a control-flow statement (`return`, `break`, `goto`, `continue`, `throw`) as those are too complex to analyze reliably, and loops where the file pointer appears in a context other than a recognised I/O function (`fgets`, `fgetc`, `getc`, `fread`, `fscanf`, `fprintf`, `fwrite`, `fputs`, `fputc`, `putc`). + +## How to fix + +Check the return value of the read function directly in the loop condition. + +Before: +```c +void process(FILE *fp) { + char line[256]; + while (!feof(fp)) { /* wrong: processes last line twice */ + fgets(line, sizeof(line), fp); + puts(line); + } +} +``` + +After: +```c +void process(FILE *fp) { + char line[256]; + while (fgets(line, sizeof(line), fp) != NULL) { + puts(line); + } +} +``` \ No newline at end of file diff --git a/man/checkers/wrongmathcall.md b/man/checkers/wrongmathcall.md new file mode 100644 index 00000000000..7aff324d117 --- /dev/null +++ b/man/checkers/wrongmathcall.md @@ -0,0 +1,42 @@ +# wrongmathcall + +**Message**: Passing value -2 to log() leads to implementation-defined result.
+**Category**: Correctness
+**Severity**: Warning
+**Language**: C/C++ + +## Description + +A literal value outside a math function's valid domain is passed directly (for example a negative +number to `log()`, or `0` as the divisor to `fmod()`). This check only looks at a literal numeric value +written directly in the call - it does not evaluate variables or computed expressions, even when their +value is otherwise known to be out of range. + +## Motivation + +Math functions like `log()`, `sqrt()`, or `asin()` are only defined for part of the real numbers; calling +them with a value outside that domain is undefined or implementation-defined and typically produces +`NaN` or a platform-specific result rather than a clean error. + +## How to fix + +Before: +```cpp +#include +void f() { + double y = log(-2); // <- log() of a negative number +} +``` + +After: +```cpp +#include +void f(double x) { + double y = (x > 0) ? log(x) : 0.0; +} +``` + +## Related checkers + +- [unpreciseMathCall.md](unpreciseMathCall.md) - a related but distinct math-function issue: a + precision-losing way of writing a calculation that a more precise function already covers. diff --git a/man/checkers/zerodiv.md b/man/checkers/zerodiv.md new file mode 100644 index 00000000000..5585bf188ec --- /dev/null +++ b/man/checkers/zerodiv.md @@ -0,0 +1,60 @@ +# zerodiv and zerodivcond + +**Message**: Division by zero.
+**Category**: Undefined Behaviour
+**Severity**: Error/Warning
+**Language**: C/C++ + +## Description + +- `zerodiv`: an integer division or `%` has a divisor that's known to be zero. +- `zerodivcond`: the same problem, but the zero divisor only holds on one branch of a condition + tested elsewhere - so either that condition is redundant, or this is a genuine division by zero. + +## Motivation + +Integer division by zero is undefined behaviour: the program's actual behaviour (a crash, a trap, or +something else) is not guaranteed by the language standard and can change with the compiler, +optimization level, or platform. + +## How to fix + +Before: +```cpp +#include +void foo() { + std::cout << 42 / (int)0; // <- zerodiv +} +``` + +After: +```cpp +#include +void foo(int divisor) { + std::cout << 42 / divisor; +} +``` + +Before: +```cpp +int f(int x, int y) { + if (x == y) {} + return 1 / (x-y); // <- zerodivcond: division by zero if x == y +} +``` + +After: +```cpp +int f(int x, int y) { + if (x == y) + return 0; + return 1 / (x-y); +} +``` + +## Design notes + +- **Dividing a floating-point value by `0.0` is not reported as `zerodiv`.** Unlike integer division, + floating-point division by zero is well-defined by IEEE 754 (it produces `Inf` or `NaN` rather than + undefined behaviour), so cppcheck only flags it separately, and only in the narrow shape described in + [nanInArithmeticExpression.md](nanInArithmeticExpression.md). diff --git a/man/cppcheck.1.xml b/man/cppcheck.1.xml index 8579d375bdf..1a63b86e200 100644 --- a/man/cppcheck.1.xml +++ b/man/cppcheck.1.xml @@ -259,6 +259,14 @@ man(1), man(7), http://www.tldp.org/HOWTO/Man-Page/ + + + + + + Do not remove comments during preprocessing (requires -E). + + @@ -293,6 +301,14 @@ Example: -DDEBUG=1 -D__cplusplus Example: '-UDEBUG' + + + + + + Print preprocessor output on stdout and don't do any further processing. + + diff --git a/man/manual-ja.docbook b/man/manual-ja.docbook index 1c13d3d0526..0b2a39cc59e 100644 --- a/man/manual-ja.docbook +++ b/man/manual-ja.docbook @@ -1702,11 +1702,11 @@ Checking unusedvar.cpp... - Cppcheck プロジェクトはいくつかのアドオンを以下の場所で提供しています。: http://github.com/danmar/cppcheck/blob/master/addons + Cppcheck プロジェクトはいくつかのアドオンを以下の場所で提供しています。: http://github.com/cppcheck-opensource/cppcheck/blob/master/addons - ublinterは規格で定義されていない未定義動作に注力した"lint"です。: http://github.com/danmar/ublinter + ublinterは規格で定義されていない未定義動作に注力した"lint"です。: http://github.com/cppcheck-opensource/ublinter diff --git a/man/manual-premium.md b/man/manual-premium.md index 9f2e5992537..3553acc1b91 100644 --- a/man/manual-premium.md +++ b/man/manual-premium.md @@ -7,8 +7,8 @@ documentclass: report # Introduction -Cppcheck is an analysis tool for C/C++ code. It provides unique code analysis to detect bugs and focuses on detecting -undefined behaviour and dangerous coding constructs. The goal is to detect only real errors in the code, and generate +Cppcheck is an analysis tool for C/C++ code. It provides unique code analysis to detect bugs and focuses on detecting +undefined behaviour and dangerous coding constructs. The goal is to detect only real errors in the code, and generate as few false positives (wrongly reported warnings) as possible. Cppcheck is designed to analyze your C/C++ code even if it has non-standard syntax, as is common in for example embedded projects. @@ -16,7 +16,7 @@ Supported code and platforms: - Cppcheck checks non-standard code that contains various compiler extensions, inline assembly code, etc. - Cppcheck should be compilable by any compiler that supports C++11 or later. -- Cppcheck is cross platform and is used in various posix/windows/etc environments. +- Cppcheck is cross-platform and is used in various posix/windows/etc environments. The checks in Cppcheck are not perfect. There are bugs that should be found, that Cppcheck fails to detect. @@ -29,11 +29,11 @@ The kinds of bugs that you can find with static analysis are: - Coding style There are many bugs that you can not find with static analysis. Static analysis tools do not have human knowledge about -what your program is intended to do. If the output from your program is valid but unexpected then in most cases this is -not detected by static analysis tools. For instance, if your small program writes "Helo" on the screen instead of "Hello" +what your program is intended to do. If the output from your program is valid but unexpected, then in most cases this is +not detected by static analysis tools. For instance, if your small program writes "Helo" on the screen instead of "Hello", it is unlikely that any tool will complain about that. -Static analysis should be used as a complement in your quality assurance. It does not replace any of; +Static analysis should be used as a complement in your quality assurance. It does not replace any of: - Careful design - Testing @@ -69,7 +69,7 @@ Here is some simple code: return 0; } -If you save that into file1.c and execute: +If you save that into `file1.c` and execute: cppcheck file1.c @@ -84,7 +84,7 @@ Normally a program has many source files. Cppcheck can check all source files in cppcheck path -If "path" is a folder, then Cppcheck will recursively check all source files in this folder: +If `path` is a folder, then Cppcheck will recursively check all source files in this folder: Checking path/file1.cpp... 1/2 files checked 50% done @@ -93,21 +93,22 @@ If "path" is a folder, then Cppcheck will recursively check all source files in ### Check files manually or use project file -With Cppcheck you can check files manually by specifying files/paths to check and settings. Or you can use a build +With Cppcheck you can check files manually by specifying files/paths to check and settings. Or you can use a build environment, such as CMake or Visual Studio. -We don't know which approach (project file or manual configuration) will give you the best results. It is recommended -that you try both. It is possible that you will get different results so that to find the largest amount of bugs you +We don't know which approach (project file or manual configuration) will give you the best results. It is recommended +that you try both. It is possible that you will get different results so that to find the largest amount of bugs you need to use both approaches. Later chapters will describe this in more detail. ### Check files matching a given file filter With `--file-filter=` you can configure file filter(s) and then only those files matching the filter will be checked. -You can use `**`, `*` and `?` in the file filter pattern. -`**`: matches zero or more characters, including path separators -`*`: matches zero or more characters, excluding path separators -`?`: matches any single character except path separators +You can use `**`, `*` and `?` in the file filter pattern. + +- `**` matches zero or more characters, including path separators. +- `*` matches zero or more characters, excluding path separators. +- `?` matches any single character except path separators. For example, this command below means that `src/test1.cpp` could be checked, but `src/file2.cpp` and `src/test/file1.cpp` will not be checked: @@ -121,30 +122,33 @@ A common use case for `--file-filter` is to check a project, but only check cert cppcheck --project=compile_commands.json --file-filter=src/*.c Typically a `compile_commands.json` contains absolute paths. However no matter if `compile_commands.json` contains absolute paths or relative paths, the option `--file-filter=src/*.c` would mean that: - * a file with relative path `test1.c` is not checked. - * a file with relative path `src/test2.c` can be checked. - * a file with relative path `src/test3.cpp` is not checked. + +- a file with relative path `test1.c` is not checked. +- a file with relative path `src/test2.c` can be checked. +- a file with relative path `src/test3.cpp` is not checked. ### Ignore files matching a given pattern With `-i ` you can configure filename/directory patterns that should be ignored. -A file that is ignored will not be checked directly (the complete translation unit is skipped). Any header #include'd from a source file which is not ignored is checked indirectly, regardless if the header is ignored. +A file that is ignored will not be checked directly (the complete translation unit is skipped). Any header `#include`'d from a source file which is not ignored is checked indirectly, regardless if the header is ignored. + +> *Note*: If you want to filter out warnings for a header file, then `-i` will not work. Use suppressions instead. -> *Note*: If you want to filter out warnings for a header file then `-i` will not work. Use suppressions instead. +You can use `**`, `*` and `?` in the pattern to specify excluded folders/files. -You can use `**`, `*` and `?` in the pattern to specify excluded folders/files. -`**`: matches zero or more characters, including path separators -`*`: matches zero or more characters, excluding path separators -`?`: matches any single character except path separators +- `**` matches zero or more characters, including path separators. +- `*` matches zero or more characters, excluding path separators. +- `?` matches any single character except path separators. A use case for `-i` is to check a project, but exclude certain files/folders: cppcheck --project=compile_commands.json -itest Typically a `compile_commands.json` contains absolute paths. However no matter if `compile_commands.json` contains absolute paths or relative paths, the option `-itest` would mean that: - * a file with relative path `test1.cpp` can be checked. - * a file with relative path `test/somefile.cpp` is not checked + +- a file with relative path `test1.cpp` can be checked. +- a file with relative path `test/somefile.cpp` is not checked ### Clang parser (experimental) @@ -155,7 +159,7 @@ Install `clang`. Then use Cppcheck option `--clang`. Cppcheck executes clang with the -ast-dump option, imports the output, converts it to Cppcheck's internal format, and then performs standard analysis. -You can also pass a custom Clang executable to the option by using for example `--clang=clang-10`. You can also pass it +You can also pass a custom Clang executable to the option by using for example `--clang=clang-10`. You can also pass it with a path. On Windows it will append the `.exe` extension unless you use a path. ## Severities @@ -176,17 +180,17 @@ stylistic issues, such as unused functions, redundant code, constness, operator **performance** -run time performance suggestions based on common knowledge, though it is not certain any measurable speed difference +run time performance suggestions based on common knowledge, though it is not certain any measurable speed difference will be achieved by fixing these messages. **portability** -portability warnings. Implementation defined behavior. 64-bit portability. Some undefined behavior that probably works +portability warnings. Implementation defined behavior. 64-bit portability. Some undefined behavior that probably works "as you want", etc. **information** -configuration problems, which does not relate to the syntactical correctness, but the used Cppcheck configuration could +configuration problems, which does not relate to the syntactical correctness, but the used Cppcheck configuration could be improved. ## Possible speedup analysis of template code @@ -220,7 +224,7 @@ Cppcheck output: As you can see Cppcheck has instantiated `a` until `a<101>` was reached and then it bails out. -To limit template recursion you can: +To limit template recursion, you can: - add template specialisation - configure Cppcheck, which can be done in the GUI project file dialog @@ -244,7 +248,6 @@ Example code with template specialisation: You can pass `-D__cppcheck__` when checking this code. - # Cppcheck build folder Using a Cppcheck build folder is not mandatory but it is recommended. @@ -278,36 +281,55 @@ You can import and use Cppcheck GUI project files in the command line tool: cppcheck --project=foobar.cppcheck -The Cppcheck GUI has a few options that are not available in the command line directly. To use these options you can import a GUI project file. +The Cppcheck GUI has a few options that are not available in the command line directly. To use these options, you can import a GUI project file. The command line tool usage is kept intentionally simple and the options are therefore limited. -To ignore certain folders in the project you can use `-i`. This will skip the analysis of source files in the `foo` folder. +To ignore certain folders in the project, you can use `-i`. This will skip the analysis of source files in the `foo` folder. cppcheck --project=foobar.cppcheck -ifoo -## CMake +## Compilation database (CMake etc.) -Generate a compile database (a JSON file containing compilation commands for each source file): +Many build systems can generate a compilation database (a JSON file containing compilation commands for each source file). +Example `cmake` command to generate the file: cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON . -The file `compile_commands.json` is created in the current folder. Now run Cppcheck like this: +When you have a `compile_commands.json` file, you can run Cppcheck like this: cppcheck --project=compile_commands.json +By default only 1 configuration is checked because that is consistent with the compilation. If you want to check more configurations, you can use `--max-configs` or `--force`. For example: + + cppcheck --project=compile_commands.json --force + To ignore certain folders you can use `-i`. This will skip analysis of source files in the `foo` folder. cppcheck --project=compile_commands.json -ifoo +### `-isystem`, `-I`, `--sysroot` + +We have a [script](https://github.com/cppcheck-opensource/cppcheck/blob/main/tools/tweak-compile-commands.py) that tweaks compile_commands.json. + +You can use it to: + * use `--sysroot` flags in Cppcheck analysis + * use `-isystem` paths in Cppcheck analysis + * remove `-I` paths from the compile_commands.json + +See [script documentation](https://github.com/cppcheck-opensource/cppcheck/blob/main/tools/tweak-compile-commands.md). ## Visual Studio -You can run Cppcheck on individual project files (\*.vcxproj) or on a whole solution (\*.sln) +You can run Cppcheck on individual project files (`*.vcxproj`) or on a whole solution (`*.sln`) or (`*.slnx`). Running Cppcheck on an entire Visual Studio solution: cppcheck --project=foobar.sln +Running Cppcheck on an entire Visual Studio 2026 solution: + + cppcheck --project=foobar.slnx + Running Cppcheck on a Visual Studio project: cppcheck --project=foobar.vcxproj @@ -319,7 +341,7 @@ Limiting on a single configuration: In the `Cppcheck GUI` you have the option to only analyze a single debug configuration. If you want to use this option on the command line, then create a `Cppcheck GUI` project with this activated and then import the GUI project file on the command line. -To ignore certain folders in the project you can use `-i`. This will skip analysis of source files in the `foo` folder. +To ignore certain folders in the project, you can use `-i`. This will skip analysis of source files in the `foo` folder. cppcheck --project=foobar.vcxproj -ifoo @@ -329,25 +351,28 @@ Running Cppcheck on a C++ Builder 6 project: cppcheck --project=foobar.bpr - -To ignore certain folders in the project you can use `-i`. This will skip analysis of source files in the `foo` folder. +To ignore certain folders in the project, you can use `-i`. This will skip analysis of source files in the `foo` folder. cppcheck --project=foobar.bpr -ifoo ## Other -If you can generate a compile database, then it is possible to import that in Cppcheck. +If you generate a compilation database, then it is possible to import that in Cppcheck. -In Linux you can use for instance the `bear` (build ear) utility to generate a compile database from arbitrary build tools: +### Makefile + +In Linux you can convert a Makefile to a `compile_commands.json` using for instance `bear` (build ear) utility: bear -- make -# Preprocessor Settings +If you don't use Linux, there are Python scripts that converts a Makefile into a compilation database. + +# Preprocessor settings -If you use `--project` then Cppcheck will automatically use the preprocessor settings in the imported project file and +If you use `--project`, then Cppcheck will automatically use the preprocessor settings in the imported project file and likely you don't have to configure anything extra. -If you don't use `--project` then a bit of manual preprocessor configuration might be required. However Cppcheck has +If you don't use `--project`, then a bit of manual preprocessor configuration might be required. However Cppcheck has automatic configuration of defines. ## Automatic configuration of preprocessor defines @@ -355,7 +380,7 @@ automatic configuration of defines. Cppcheck automatically test different combinations of preprocessor defines to achieve as high coverage in the analysis as possible. -Here is a file that has 3 bugs (when x,y,z are assigned). +Here is a file that has 3 bugs (when `x`, `y`, `z` are assigned). #ifdef A x=100/0; @@ -386,14 +411,14 @@ Example: cppcheck test.c # only test configuration "-DA" - # No bug is found (#error) + # No bug is found; because C is not defined the #error will cause a preprocessor error cppcheck -DA test.c # only test configuration "-DA -DC" # The first bug is found cppcheck -DA -DC test.c - # The configuration "-DC" is tested + # Test all configurations that does not define "A" # The last bug is found cppcheck -UA test.c @@ -401,20 +426,27 @@ Example: # The two first bugs are found cppcheck --force -DA test.c + # only test 1 valid configuration + # Bug(s) will be found + cppcheck --max-configs=1 test.c + + # test 2 valid configurations with "X" defined. + # Bug(s) will be found + cppcheck --max-configs=2 -DX test.c ## Include paths -To add an include path, use `-I`, followed by the path. +To add an include path, use `-I` followed by the path. -Cppcheck's preprocessor basically handles includes like any other preprocessor. However, while other preprocessors -stop working when they encounter a missing header, Cppcheck will just print an information message and continues +Cppcheck's preprocessor basically handles includes like any other preprocessor. However, while other preprocessors +stop working when they encounter a missing header, Cppcheck will just print an information message and continues parsing the code. -The purpose of this behaviour is that Cppcheck is meant to work without necessarily seeing the entire code. -Actually, it is recommended to not give all include paths. -While it is useful for Cppcheck to see the declaration of a class when checking the implementation of its members, -passing standard library headers is discouraged, because the analysis will not work fully and lead to a longer checking -time. For such cases, .cfg files are the preferred way to provide information about the implementation of functions and +The purpose of this behaviour is that Cppcheck is meant to work without necessarily seeing the entire code. +Actually, it is recommended to not give all include paths. +While it is useful for Cppcheck to see the declaration of a class when checking the implementation of its members, +passing standard library headers is discouraged, because the analysis will not work fully and lead to a longer checking +time. For such cases, `.cfg` files are the preferred way to provide information about the implementation of functions and types to Cppcheck, see below for more information. # Platform @@ -445,7 +477,7 @@ You can also create your own custom platform configuration in a XML file. Here i -# C/C++ Standard +# C/C++ standard Use `--std` on the command line to specify a C/C++ standard. @@ -474,7 +506,7 @@ the GUI, the build dir is configured in the project options. Rechecking code will be much faster. Cppcheck does not analyse unchanged code. The old warnings are loaded from the build dir and reported again. -Whole program analysis does not work when multiple threads are used; unless you use a cppcheck +Whole program analysis does not work when multiple threads are used unless you use a Cppcheck build dir. For instance, the unusedFunction warnings require whole program analysis. # Suppressions @@ -492,12 +524,13 @@ The format for an error suppression is one of: [error id]:[filename2] [error id] -The `error id` is the id that you want to suppress. The id of a warning is shown in brackets in the normal cppcheck text output. +The `error id` is the id that you want to suppress. The id of a warning is shown in brackets in the normal Cppcheck text output. + +The `error id` and `filename` patterns may contain `**`, `*` or `?`. -The `error id` and `filename` patterns may contain `**`, `*` or `?`. -`**`: matches zero or more characters, including path separators -`*`: matches zero or more characters, excluding path separators -`?`: matches any single character except path separators +- `**` matches zero or more characters, including path separators. +- `*` matches zero or more characters, excluding path separators. +- `?` matches any single character except path separators. It is recommended to use forward-slash `/` in the filename pattern as path separator on all operating systems. @@ -539,9 +572,10 @@ You can specify suppressions in a XML file, for example as follows: The `id` and `fileName` patterns may contain `**`, `*` or `?`. -`**`: matches zero or more characters, including path separators -`*`: matches zero or more characters, excluding path separators -`?`: matches any single character except path separators + +- `**` matches zero or more characters, including path separators. +- `*` matches zero or more characters, excluding path separators. +- `?` matches any single character except path separators. The XML format is extensible and may be extended with further attributes in the future. @@ -549,9 +583,37 @@ The usage of the suppressions file is as follows: cppcheck --suppress-xml=suppressions.xml src/ +### The `` element + +Cppcheck calculates a unique ID for each error, called the hash. The hash depends on the code that +is related to the error, not on where that code is located. This means that the hash for an error +stays the same even when unrelated code elsewhere in the file is added, removed or moved, shifting +the line numbers around. The hash only changes if the related code itself is modified. + +This makes hash-based suppressions more robust than line-based suppressions: once you have +reviewed and suppressed a specific warning, the suppression keeps working even after the file is +edited, as long as the offending code is not changed. + +The hash for an error is included as the `hash` attribute in the [XML output](#the-error-element). +You can copy that value into a `` element in a suppressions XML file: + + + + + uninitvar + src/file1.c + 12345678 + + + +A suppression can use `` on its own, without ``, to suppress a specific error regardless +of its id. It is also possible to combine `` with ``, ``, `` and +``; when several of these are specified, all of them must match for the suppression to +apply. + ## Inline suppressions -Suppressions can also be added directly in the code by adding comments that contain special keywords. +Suppressions can also be added directly in the code by adding comments that contain special keywords. Note that adding comments sacrifices the readability of the code somewhat. This code will normally generate an error message: @@ -576,7 +638,7 @@ You can suppress a warning `aaaa` with: // cppcheck-suppress aaaa -Suppressing multiple ids in one comment by using []: +Suppressing multiple ids in one comment by using `[]`: // cppcheck-suppress [aaaa, bbbb] @@ -607,7 +669,6 @@ Suppressing warnings `aaaa` where macro is used: ... x = MACRO; // <- aaaa warnings are suppressed here - Suppressing multiple ids where macro is used: // cppcheck-suppress-macro [aaaa, bbbb] @@ -636,7 +697,21 @@ Or at the same line as the code: arr[10] = 0; // cppcheck-suppress arrayIndexOutOfBounds } -In this example there are 2 lines with code and 1 suppression comment. The suppression comment only applies to 1 line: `a = b + c;`. +The suppression comment and the line of code may be separated by additional comments or empty lines: + + void f() { + char arr[5]; + + // cppcheck-suppress arrayIndexOutOfBounds + + arr[10] = 0; + + // cppcheck-suppress arrayIndexOutOfBounds + // Set the tenth element of arr to zero + arr[10] = 0; + } + +In the example below there are 2 lines with code and 1 suppression comment. The suppression comment only applies to 1 line: `a = b + c;`. void f() { a = b + c; // cppcheck-suppress abc @@ -683,7 +758,6 @@ Suppression comment on the same line as the code: arr[10] = arr[10] / 0; // cppcheck-suppress[arrayIndexOutOfBounds,zerodiv] } - ### Symbol name You can specify that the inline suppression only applies to a specific symbol: @@ -702,6 +776,120 @@ You can write comments about a suppression as follows: // cppcheck-suppress warningid ; some comment // cppcheck-suppress warningid // some comment +# Generating and using a baseline + +When you first run Cppcheck on an existing codebase it's common to get a +large number of warnings. A "baseline" lets you suppress all of today's +warnings and see only *new* warnings introduced from now on. + +This works by asking Cppcheck to include a content-based `hash` for every warning +(computed from the surrounding code, not the line number), then converting today's +warnings into an XML suppressions file keyed on `id` + `fileName` + `hash`. Because +the hash is based on content rather than line number, warnings stay suppressed even +after unrelated lines above them are added or removed. A warning only reappears if +the code it actually points at changes, or a new warning shows up elsewhere. + +## 1. Generate the baseline + +Run Cppcheck with `--xml` and capture stderr (where Cppcheck writes its XML) to a +file: + +```sh +cppcheck --enable=style --xml src 2> baseline-results.xml +``` + +Use whatever combination of `--enable`/defines/include paths you normally +analyze the project with — the suppressions you get out only cover the +checks you ran. + +## 2. Convert the results into a suppressions file + +A [script](https://github.com/cppcheck-opensource/cppcheck/blob/main/tools/generate-baseline-suppressions.py) can be used to generate the baseline: +```sh +python3 generate-baseline-suppressions.py baseline-results.xml suppressions.xml +``` + +This produces a `suppressions.xml` like: + +```xml + + + + uninitvar + src/file1.c + 12345678 + + +``` + +`suppressions.xml` needs to be shared by everyone who runs Cppcheck on this +codebase, including CI. + +Having a script instead of using some special cppcheck flags has the advantages: + * it's flexible. You can tweak it if needed. + * there is fewer flags for us to maintain and document, and for you to learn. + +## 3. Use the baseline on future runs + +```sh +cppcheck --enable=style --xml --suppress-xml=suppressions.xml src +``` + +Only warnings that aren't in the baseline are reported: new warnings in changed +code, and warnings for checks/files that weren't covered when the baseline was +generated. + +## 4. Refreshing the baseline + +Re-run steps 1-2 whenever you want to accept the current state as the new +baseline (e.g. after cleaning up a batch of warnings, or deliberately accepting a +new one). Regenerating overwrites `suppressions.xml` with an entry for every +warning present at that time. + +## The baseline doesn't need to be kept in sync + +Once code that a baselined warning pointed at is fixed, refactored away, or +deleted, its `suppress` entry in `suppressions.xml` becomes dead: nothing will +ever match it again. You don't need to go find and remove it. + +Cppcheck's `unmatchedSuppression` check (part of `--enable=all`) normally warns +about suppressions that never matched anything, on the theory that a +suppression nobody needs is probably a mistake. But it does *not* fire for +`suppress` entries that carry a `` — which is every entry the baseline +script generates. So a baseline file with plenty of dead entries produces no +noise, and developers never need to prune it by hand — it only needs to be +regenerated (step 4) when you deliberately want to reset what's accepted. + +## Caveats + +- A warning can only be suppressed this way if it carries a `hash` attribute in + the XML output. Almost all checks compute one. Critical errors, i.e. syntax + errors do not get hash and must be fixed. Certain information messages do not + get hash neither. +- Changed Cppcheck options might produce new warnings that are not suppressed + by the baseline. +- Cppcheck upgrades don't affect the hash directly (it isn't version-tagged), + but if a new release for instance changes a check's message wording, the hash + changes with it — so upgrading Cppcheck can resurrect baselined warnings for + checks whose messages were reworded, even though nothing in the analyzed code + changed. + +## Strategies for gradually shrinking the baseline + +A baseline makes it possible to adopt Cppcheck in CI immediately without +fixing everything first, but nothing about it enforces that the accepted set +of warnings actually shrinks over time. + +Generic advice: + +- **Prioritize by severity** Checks like `uninitvar` or + `nullPointer` are more valuable to clear than `style` warnings; a baseline + makes it possible to drive the highest-severity checks to zero first while + deliberately leaving lower-risk ones suppressed longer. +- **Be careful** Every fix is a code change, and every code change carries some + risk of introducing a new bug. It can make sense to leave some things suppressed + to minimize the risk that bugs are introduced in working code. + # XML output Cppcheck can generate output in XML format. Use `--xml` to enable this format. @@ -783,7 +971,7 @@ If you want to reformat the output so that it looks different, then you can use ## Predefined output formats -To get Visual Studio compatible output you can use --template=vs: +To get Visual Studio compatible output, you can use `--template=vs`: cppcheck --template=vs samples/arrayIndexOutOfBounds/bad.c @@ -793,7 +981,7 @@ This output will look like this: samples/arrayIndexOutOfBounds/bad.c(6): error: Array 'a[2]' accessed at index 2, which is out of bounds. -To get gcc compatible output you can use --template=gcc: +To get GCC-compatible output, you can use `--template=gcc`: cppcheck --template=gcc samples/arrayIndexOutOfBounds/bad.c @@ -831,7 +1019,7 @@ The output will look like this: samples/arrayIndexOutOfBounds/bad.c,6,error,arrayIndexOutOfBounds, Array 'a[2]' accessed at index 2, which is out of bounds. -## User defined output format (multi line) +## User defined output format (multi-line) Many warnings have multiple locations. Example code: @@ -847,9 +1035,9 @@ Many warnings have multiple locations. Example code: return 0; } -There is a possible null pointer dereference at line 3. -Cppcheck can show how it came to that conclusion by showing extra location information. -You need to use both --template and --template-location at the command line, for example: +There is a possible null pointer dereference at line 3. +Cppcheck can show how it came to that conclusion by showing extra location information. +You need to use both `--template` and `--template-location` at the command line, for example: cppcheck \ --template="{file}:{line}: {severity}: {message}\n{code}" \ @@ -871,13 +1059,13 @@ The output from Cppcheck is: *p = 3; ^ -The first line in the warning is formatted by the --template format. +The first line in the warning is formatted by the `--template` format. -The other lines in the warning are formatted by the --template-location format. +The other lines in the warning are formatted by the `--template-location` format. -### Format specifiers for --template +### Format specifiers for `--template` -The available specifiers for --template are: +The available specifiers for `--template` are: **{file}** @@ -893,11 +1081,11 @@ Column number **{callstack}** -Write all locations. Each location is written in [{file}:{line}] format and the locations are separated by ->. For instance it might look like: [multiline.c:8] -> [multiline.c:9] -> [multiline.c:3] +Write all locations. Each location is written in `[{file}:{line}]` format and the locations are separated by `->`. For instance it might look like: `[multiline.c:8] -> [multiline.c:9] -> [multiline.c:3]` **{inconclusive:text}** -If warning is inconclusive, then the given text is written. The given text can be any text that does not contain }. Example: {inconclusive:inconclusive,} +If warning is inconclusive, then the given text is written. The given text can be any text that does not contain `}`. Example: `{inconclusive:inconclusive,}` **{severity}** @@ -931,7 +1119,7 @@ Newline Carriage return -### Format specifiers for --template-location +### Format specifiers for `--template-location` The available specifiers for `--template-location` are: @@ -983,7 +1171,7 @@ Example code: int x = 0; } -In Cppcheck text output the remarks are not shown by default, you can use `--template` option `{remark}` to show remarks: +In Cppcheck text output, the remarks are not shown by default, you can use `--template` option `{remark}` to show remarks: $ ./cppcheck --enable=style \ --template="{file}:{line}: {message} [{id}]\\n{remark}" test1.c @@ -992,12 +1180,12 @@ In Cppcheck text output the remarks are not shown by default, you can use `--tem test1.c:4: Variable 'x' is assigned a value that is never used. [unreadVariable] Initialize x with 0 -In xml output the comment text is provided in a "remark" attribute: +In XML output, the comment text is provided in a `remark` attribute: $ ./cppcheck --enable=style --xml test1.c - .... + ... remark="Initialize x with 0" - .... + ... # Addons @@ -1007,52 +1195,51 @@ Cppcheck is distributed with a few addons which are listed below. ## Supported addons -### namingng.py +### `namingng.py` -[namingng.py](https://github.com/danmar/cppcheck/blob/main/addons/namingng.py) allows you to configure and check naming conventions. +[`namingng.py`](https://github.com/cppcheck-opensource/cppcheck/blob/main/addons/namingng.py) allows you to configure and check naming conventions. You need to have a configuration file that defines your naming conventions. By default the filename `namingng.config.json` is used but there is an option so you can use any filename you want. Example configuration of naming conventions: -``` -{ - "RE_VARNAME": ["[a-z]*[a-zA-Z0-9_]*\\Z"], - "RE_PRIVATE_MEMBER_VARIABLE": null, - "RE_FUNCTIONNAME": ["[a-z0-9A-Z]*\\Z"], - "_comment": "comments can be added to the config with underscore-prefixed keys", - "include_guard": { - "input": "path", - "prefix": "GUARD_", - "case": "upper", - "max_linenr": 5, - "RE_HEADERFILE": "[^/].*\\.h\\Z", - "required": true - }, - "var_prefixes": {"uint32_t": "ui32"}, - "function_prefixes": {"uint16_t": "ui16", - "uint32_t": "ui32"} -} -``` -### threadsafety.py + { + "RE_VARNAME": ["[a-z]*[a-zA-Z0-9_]*\\Z"], + "RE_PRIVATE_MEMBER_VARIABLE": null, + "RE_FUNCTIONNAME": ["[a-z0-9A-Z]*\\Z"], + "_comment": "comments can be added to the config with underscore-prefixed keys", + "include_guard": { + "input": "path", + "prefix": "GUARD_", + "case": "upper", + "max_linenr": 5, + "RE_HEADERFILE": "[^/].*\\.h\\Z", + "required": true + }, + "var_prefixes": {"uint32_t": "ui32"}, + "function_prefixes": {"uint16_t": "ui16", + "uint32_t": "ui32"} + } + +### `threadsafety.py` -[threadsafety.py](https://github.com/danmar/cppcheck/blob/main/addons/threadsafety.py) analyses Cppcheck dump files to locate thread safety issues like static local objects used by multiple threads. +[`threadsafety.py`](https://github.com/cppcheck-opensource/cppcheck/blob/main/addons/threadsafety.py) analyses Cppcheck dump files to locate thread safety issues like static local objects used by multiple threads. -### y2038.py +### `y2038.py` -[y2038.py](https://github.com/danmar/cppcheck/blob/main/addons/y2038.py) checks source code for [year 2038 problem](https://en.wikipedia.org/wiki/Year_2038_problem) safety. +[`y2038.py`](https://github.com/cppcheck-opensource/cppcheck/blob/main/addons/y2038.py) checks source code for [year 2038 problem](https://en.wikipedia.org/wiki/Year_2038_problem) safety. -## Running Addons +## Running addons Addons can be executed with the `--addon` option: cppcheck --addon=namingng.py somefile.c -Likewise, if you have created your own script you can execute that: +Likewise, if you have created your own script, you can execute that: cppcheck --addon=mychecks.py somefile.c -You can configure how you want to execute an addon in a json file. For example: +You can configure how you want to execute an addon in a JSON file. For example: { "script": "mychecks.py", @@ -1062,7 +1249,7 @@ You can configure how you want to execute an addon in a json file. For example: "ctu": false } -To use that json file to execute your addon use the --addon option: +To use that JSON file to execute your addon, use the `--addon` option: cppcheck --addon=mychecks.json somefile.c @@ -1072,75 +1259,75 @@ Cppcheck search for addons in the local folder first and then in the installatio # Library configuration -When external libraries are used, such as WinAPI, POSIX, gtk, Qt, etc, Cppcheck has no information about functions, types, or macros contained in those libraries. Cppcheck then fails to detect various problems in the code, or might even abort the analysis. But this can be fixed by using the appropriate configuration files. - -Cppcheck already contains configurations for several libraries. They can be loaded as described below. Note that the configuration for the standard libraries of C and C++, std.cfg, is always loaded by cppcheck. If you create or update a configuration file for a popular library, we would appreciate if you supplied it to the cppcheck project. - -## Using a .cfg file - -To use a .cfg file shipped with cppcheck, pass the `--library=` option. The table below shows the currently existing libraries: - -| .cfg file | Library | Comment | -| ----------------- | ------------- | ------------- | -| avr.cfg | | | -| bento4.cfg | [Bento4](http://www.bento4.com/) | | -| boost.cfg | [Boost](http://www.boost.org/) | | -| bsd.cfg | [BSD](https://www.freebsd.org/) | | -| cairo.cfg | [cairo](https://www.cairographics.org/) | | -| cppcheck-lib.cfg | [Cppcheck](http://cppcheck.net/) | Used in selfcheck of | -| | |the Cppcheck code base | -| cppunit.cfg | [CppUnit](https://sourceforge.net/projects/cppunit/) | | -| dpdk.cfg | | | -| embedded_sql.cfg | | | -| emscripten.cfg | | | -| ginac.cfg | | | -| gnu.cfg | [GNU](https://www.gnu.org/) | | -| googletest.cfg | [GoogleTest](https://github.com/google/googletest) | | -| gtk.cfg | [GTK](https://www.gtk.org/) | | -| icu.cfg | | | -| kde.cfg | [KDE](https://kde.org/) | | -| libcerror.cfg | [libcerror](https://github.com/libyal/libcerror) | | -| libcurl.cfg | [libcurl](https://curl.se/libcurl/) | | -| libsigc++.cfg | [libsigc++](https://github.com/libsigcplusplus/libsigcplusplus) | | -| lua.cfg | | | -| mfc.cfg | [MFC](https://learn.microsoft.com/en-us/cpp/mfc/mfc-desktop-applications) | | -| microsoft_atl.cfg | [ATL](https://learn.microsoft.com/en-us/cpp/atl/active-template-library-atl-concepts) | | -| microsoft_sal.cfg | [SAL annotations](https://learn.microsoft.com/en-us/cpp/c-runtime-library/sal-annotations) | | -| microsoft_unittest.cfg | [CppUnitTest](https://learn.microsoft.com/en-us/visualstudio/test/microsoft-visualstudio-testtools-cppunittestframework-api-reference) | | -| motif.cfg | | | -| nspr.cfg | | | -| ntl.cfg | | | -| opencv2.cfg | [OpenCV](https://opencv.org/) | | -| opengl.cfg | [OpenGL](https://opengl.org/) | | -| openmp.cfg | [OpenMP](https://www.openmp.org/) | | -| openssl.cfg | [OpenSSL](https://www.openssl.org/) | | -| pcre.cfg | [PCRE](https://pcre.org/) | | -| posix.cfg | [POSIX](https://pubs.opengroup.org/onlinepubs/9699919799/) | | -| python.cfg | | | -| qt.cfg | [Qt](https://doc.qt.io/qt.html) | | -| ruby.cfg | | | -| sdl.cfg | | | -| sfml.cfg | | | -| sqlite3.cfg | [SQLite](https://www.sqlite.org/) | | -| std.cfg | C/C++ standard library | Loaded by default | -| tinyxml2.cfg | [TinyXML-2](https://github.com/leethomason/tinyxml2) | | -| vcl.cfg | | | -| windows.cfg | [Win32 API](https://learn.microsoft.com/en-us/windows/win32/) | | -| wxsqlite3.cfg | | | -| wxsvg.cfg | | | -| wxwidgets.cfg | [wxWidgets](https://www.wxwidgets.org/) | | -| zephyr.cfg | | | -| zlib.cfg | [zlib](https://www.zlib.net) | | - -## Creating a custom .cfg file - -You can create and use your own .cfg files for your projects. Use `--check-library` to get hints about what you should configure. +When external libraries are used, such as WinAPI, POSIX, gtk, Qt, etc., Cppcheck has no information about functions, types, or macros contained in those libraries. Cppcheck then fails to detect various problems in the code, or might even abort the analysis. But this can be fixed by using the appropriate configuration files. + +Cppcheck already contains configurations for several libraries. They can be loaded as described below. Note that the configuration for the standard libraries of C and C++, `std.cfg`, is always loaded by Cppcheck. If you create or update a configuration file for a popular library, we would appreciate if you supplied it to the Cppcheck project. + +## Using a `.cfg` file + +To use a `.cfg` file shipped with Cppcheck, pass the `--library=` option. The table below shows the currently existing libraries: + +| `.cfg` file | Library | Comment | +| --- | --- | --- | +| `avr.cfg` | | | +| `bento4.cfg` | [Bento4](http://www.bento4.com/) | | +| `boost.cfg` | [Boost](http://www.boost.org/) | | +| `bsd.cfg` | [BSD](https://www.freebsd.org/) | | +| `cairo.cfg` | [cairo](https://www.cairographics.org/) | | +| `cppcheck-lib.cfg` | [Cppcheck](http://cppcheck.net/) | Used in selfcheck of the Cppcheck code base | +| `cppunit.cfg` | [CppUnit](https://sourceforge.net/projects/cppunit/) | | +| `dpdk.cfg` | | | +| `embedded_sql.cfg` | | | +| `emscripten.cfg` | | | +| `ginac.cfg` | | | +| `gnu.cfg` | [GNU](https://www.gnu.org/) | | +| `googletest.cfg` | [GoogleTest](https://github.com/google/googletest) | | +| `gtk.cfg` | [GTK](https://www.gtk.org/) | | +| `icu.cfg` | | | +| `kde.cfg` | [KDE](https://kde.org/) | | +| `libcerror.cfg` | [libcerror](https://github.com/libyal/libcerror) | | +| `libcurl.cfg` | [libcurl](https://curl.se/libcurl/) | | +| `libsigc++.cfg` | [libsigc++](https://github.com/libsigcplusplus/libsigcplusplus) | | +| `lua.cfg` | | | +| `mfc.cfg` | [MFC](https://learn.microsoft.com/en-us/cpp/mfc/mfc-desktop-applications) | | +| `microsoft_atl.cfg` | [ATL](https://learn.microsoft.com/en-us/cpp/atl/active-template-library-atl-concepts) | | +| `microsoft_gsl.cfg` | [Microsoft.GSL](https://github.com/microsoft/gsl) | | +| `microsoft_sal.cfg` | [SAL annotations](https://learn.microsoft.com/en-us/cpp/c-runtime-library/sal-annotations) | | +| `microsoft_unittest.cfg` | [CppUnitTest](https://learn.microsoft.com/en-us/visualstudio/test/microsoft-visualstudio-testtools-cppunittestframework-api-reference) | | +| `motif.cfg` | | | +| `nspr.cfg` | | | +| `ntl.cfg` | | | +| `opencv2.cfg` | [OpenCV](https://opencv.org/) | | +| `opengl.cfg` | [OpenGL](https://opengl.org/) | | +| `openmp.cfg` | [OpenMP](https://www.openmp.org/) | | +| `openssl.cfg` | [OpenSSL](https://www.openssl.org/) | | +| `pcre.cfg` | [PCRE](https://pcre.org/) | | +| `posix.cfg` | [POSIX](https://pubs.opengroup.org/onlinepubs/9699919799/) | | +| `python.cfg` | | | +| `qt.cfg` | [Qt](https://doc.qt.io/qt.html) | | +| `ruby.cfg` | | | +| `sdl.cfg` | | | +| `sfml.cfg` | | | +| `sqlite3.cfg` | [SQLite](https://www.sqlite.org/) | | +| `std.cfg` | C/C++ standard library | Loaded by default | +| `tinyxml2.cfg` | [TinyXML-2](https://github.com/leethomason/tinyxml2) | | +| `vcl.cfg` | | | +| `windows.cfg` | [Win32 API](https://learn.microsoft.com/en-us/windows/win32/) | | +| `wxsqlite3.cfg` | | | +| `wxsvg.cfg` | | | +| `wxwidgets.cfg` | [wxWidgets](https://www.wxwidgets.org/) | | +| `zephyr.cfg` | | | +| `zlib.cfg` | [zlib](https://www.zlib.net) | | + +## Creating a custom `.cfg` file + +You can create and use your own `.cfg` files for your projects. Use `--check-library` to get hints about what you should configure. You can use the `Library Editor` in the `Cppcheck GUI` to edit configuration files. It is available in the `View` menu. -The .cfg file format is documented in the `Reference: Cppcheck .cfg format` (https://cppcheck.sourceforge.io/reference-cfg-format.pdf) document. +The `.cfg` file format is documented in the `Reference: Cppcheck .cfg format` () document. -# HTML Report +# HTML report You can convert the XML output from Cppcheck into a HTML report. You'll need Python and the pygments module () for this to work. In the Cppcheck source tree there is a folder htmlreport that contains a script that transforms a Cppcheck XML file into HTML output. @@ -1170,15 +1357,17 @@ Example usage: cppcheck-htmlreport --file=err.xml --report-dir=test1 --source-dir=. or + cppcheck gui/test.cpp --xml 2> err.xml cppcheck-htmlreport --file=err.xml --report-dir=test1 \ --source-dir=https://github.com///blob// -## Choosing Between Local Annotated HTML and Remote Repository Links +## Choosing between local annotated HTML and remote repository links + +`cppcheck-htmlreport` supports two modes for linking to source files: -cppcheck-htmlreport supports two modes for linking to source files: - - Local annotated HTML files (default when `--source-dir` is a filesystem path) - - Remote GitHub/GitLab links (when `--source-dir` is a URL) +- Local annotated HTML files (default when `--source-dir` is a filesystem path) +- Remote GitHub/GitLab links (when `--source-dir` is a URL) Pointing `--source-dir` to a filesystem path generates local annotated HTML files. This is useful when you need a fully self-contained report that works offline, @@ -1193,13 +1382,13 @@ handled by the hosting service. In general, local mode fits air-gapped environments, while remote mode works best for CI workflows and large or private repositories. -# Check Level +# Check level ## Reduced The "reduced" check level performs a limited data flow analysis. If developers -want to run cppcheck directly during development and require faster results -than "normal" provides then this reduced checking can be an option. +want to run Cppcheck directly during development and require faster results +than "normal" provides, then this reduced checking can be an option. ## Normal @@ -1209,7 +1398,7 @@ The "normal" check level should be useful during active development: - checking files while you edit them. - block changes to the repo -- etc +- etc. ## Exhaustive @@ -1218,7 +1407,7 @@ When you can wait longer for the results you can enable the "exhaustive" checkin Exhaustive checking level should be useful for scenarios where you can wait for results. For instance: - nightly builds -- etc +- etc. # Speeding up analysis @@ -1226,18 +1415,17 @@ Exhaustive checking level should be useful for scenarios where you can wait for For performance reasons it might be a good idea to limit preprocessor configurations to check. -## Limit ValueFlow: max if count +## Limit ValueFlow: max `if` count -The command line option `--performance-valueflow-max-if-count` adjusts the max count for number of if in a function. +The command line option `--performance-valueflow-max-if-count` adjusts the max count for number of `if` in a function. -When that limit is exceeded there is a limitation of data flow in that function. It is not drastic: +When that limit is exceeded, there is a limitation of data flow in that function. It is not drastic: - Analysis of other functions are not affected. - It's only for some specific data flow analysis, we have data flow analysis that is always executed. - All checks are always executed. There can still be plenty of warnings in the limited function. -There is data flow analysis that slows down exponentially when number of if increase. And the limit is intended to avoid that -analysis time explodes. +There is data flow analysis that slows down exponentially when number of `if` increase. And the limit is intended to avoid that analysis time explodes. ## GUI options @@ -1248,13 +1436,13 @@ In the GUI: - Open the project dialog. - In the "Analysis" tab there are several options. -If you want to use these limitations on the command line also you can import the GUI project file with --project. +If you want to use these limitations on the command line too, you can import the GUI project file with `--project`. # Cppcheck Premium ## Bug hunting -This is analysis that is more noisy than normal analysis. Most warnings will be false positives (cppcheck will wrongly claim that there are bugs). The design goal is to not have more than roughly 5 - 10 false positives in each file. +This is analysis that is more noisy than normal analysis. Most warnings will be false positives (Cppcheck will wrongly claim that there are bugs). The design goal is to not have more than roughly 5 to 10 false positives in each file. It is not intended to be used in normal CI or regular static analysis by developers. The noise makes it useless for that. @@ -1262,7 +1450,7 @@ It is intended to be used when you are looking for bugs and you really can accep - You have developed a brand new feature and want to ensure that there are no bugs. - Maybe as part of release testing your product you can run bug hunting on modified files. -- Etc +- Etc. Technically, analysis that is "sound" will detect all bugs. Analysis that is "soundy" has the goal to detect most bugs and it tries to keep the noise at an reasonable level. @@ -1270,41 +1458,41 @@ The Cppcheck bug hunting analysis is "soundy". Command: - cppcheck --premium=bughunting .... + cppcheck --premium=bughunting ... ## Coding standards Command to activate Autosar checkers: - cppcheck --premium=autosar .... + cppcheck --premium=autosar ... Command to activate Cert C checkers: - cppcheck --premium=cert-c .... + cppcheck --premium=cert-c ... Command to activate Cert C++ checkers: - cppcheck --premium=cert-c++ .... + cppcheck --premium=cert-c++ ... Command to activate Misra C 2012 checkers: - cppcheck --premium=misra-c-2012 .... + cppcheck --premium=misra-c-2012 ... Command to activate Misra C 2023 checkers: - cppcheck --premium=misra-c-2023 .... + cppcheck --premium=misra-c-2023 ... Command to activate Misra C 2025 checkers: - cppcheck --premium=misra-c-2025 .... + cppcheck --premium=misra-c-2025 ... Command to activate Misra C++ 2008 checkers: - cppcheck --premium=misra-c++-2008 .... + cppcheck --premium=misra-c++-2008 ... Command to activate Misra C++ 2023 checkers: - cppcheck --premium=misra-c++-2023 .... + cppcheck --premium=misra-c++-2023 ... ### Checking all C and C++ files @@ -1312,7 +1500,7 @@ The `cert-c` and `misra-c-*` coding standards target C and therefore the checker The `autosar`, `cert-c++` and `misra-c++-*` coding standards target C++ and therefore the checkers only check C++ files by default. -If you want to check all files you can append ":all" to the coding standard. Example: +If you want to check all files, you can append `:all` to the coding standard. Example: # Misra C checkers are executed on C files, not on C++ files cppcheck --premium=misra-c-2025 path @@ -1320,18 +1508,12 @@ If you want to check all files you can append ":all" to the coding standard. Exa # Misra C checkers are executed on C and C++ files cppcheck --premium=misra-c-2025:all path - ## Compliance report -### Graphical user interface - -Run a analysis with some coding standards enabled. After that you can click on -the `Compliance report...` in the `File` menu. - ### Command line There is a tool `compliance-report` that is distributed with Cppcheck Premium. To see -all available options run `compliance-report` in a terminal with no options. +all available options, run `compliance-report` in a terminal with no options. Below is example usage to generate a Misra C 2012 compliance report. @@ -1344,12 +1526,12 @@ Description of the options: - `--misra-c-2012`: Generate a compliance report for misra-c-2012 - `--project-name`: The name of the project - `--project-version`: The version of the project -- `--output-file`: html filename that the report should be written to -- `results.xml`: The xml output from cppcheck +- `--output-file`: HTML filename that the report should be written to +- `results.xml`: The XML output from Cppcheck ## Metrics -To generate metrics add option `--premium=metrics`. The metrics are saved in the xml v3 report. +To generate metrics, add option `--premium=metrics`. The metrics are saved in the XML v3 report. Example: cppcheck --premium=metrics test.c --xml-version=3 2> res.xml @@ -1358,41 +1540,41 @@ We provide a small simple python script that creates a metrics report in CSV for python3 HISReport.py -f res.xml -j path/to/cppcheck-id-mapping.json -o test.csv -the `cppcheck-id-mapping.json` is provided in the cppcheck premium installation folder, i.e. +The `cppcheck-id-mapping.json` is provided in the Cppcheck premium installation folder, i.e. `/opt/cppcheckpremium` or `C:\Program Files\Cppcheck Premium`. -We do not have a ready-made solution to generate a html/pdf report. You can easily tweak our -HISReport.py script so that it generates html and get the report exactly as you want. +We do not have a ready-made solution to generate a HTML/pdf report. You can easily tweak our +`HISReport.py` script so that it generates HTML and get the report exactly as you want. ## Licenses -### Commercial Terms +### Commercial terms Information about Cppcheck Premium licenses: -https://www.cppcheck.com/plans-pricing + -### Installation / Registration +### Installation / registration This is described on the Cppcheck Premium website: -https://www.cppcheck.com + ### License file path There are predefined paths where the premium addon search for license files. If you want to -provide an arbitrary license file path on the command line you can use the option +provide an arbitrary license file path on the command line, you can use the option `--premium-license-file`. Example: cppcheck --premium-license-file=path/to/file.lic test.cpp -If an explicit path is provided like this then premium addon does not search for license +If an explicit path is provided like this, then premium addon does not search for license files in the predefined paths. ### Troubleshooting -#### Step 1: check premiumaddon debug output +#### Step 1: check `premiumaddon` debug output -If your license does not work you can get some details about the license validation by executing -premiumaddon binary with the `--debug` option. +If your license does not work, you can get some details about the license validation by executing +`premiumaddon` binary with the `--debug` option. Windows: @@ -1404,18 +1586,17 @@ Linux/Mac: These commands can be executed from an arbitrary folder. -#### Step 2: clean up cppcheck build dir +#### Step 2: clean up Cppcheck build dir Command line: -If you use --cppcheck-build-dir then remove all files in the specified folder and recheck. +If you use `--cppcheck-build-dir`, then remove all files in the specified folder and recheck. Cppcheck GUI: -The GUI normally by default creates a cppcheck build dir. Clear all results and recheck. +The GUI normally by default creates a Cppcheck build dir. Clear all results and recheck. You can clear all results by clicking on the brush icon in the toolbar. Or by open `Edit` menu and selecting menu item `Clear results`. -#### Step 3: remove cppcheck-premium-loc files +#### Step 3: remove `cppcheck-premium-loc` files -If you have cppcheck-premium-loc files in your project folders those should be removed. +If you have `cppcheck-premium-loc` files in your project folders, those should be removed. If such files are generated during analysis, then review your scripts to check why those files are generated. - diff --git a/man/manual.md b/man/manual.md index 5b2f43eb047..85dad95f8bb 100644 --- a/man/manual.md +++ b/man/manual.md @@ -1,6 +1,6 @@ --- title: Cppcheck manual -subtitle: Version 2.21 dev +subtitle: Version 2.23 dev author: Cppcheck team lang: en documentclass: report @@ -8,8 +8,8 @@ documentclass: report # Introduction -Cppcheck is an analysis tool for C/C++ code. It provides unique code analysis to detect bugs and focuses on detecting -undefined behaviour and dangerous coding constructs. The goal is to detect only real errors in the code, and generate +Cppcheck is an analysis tool for C/C++ code. It provides unique code analysis to detect bugs and focuses on detecting +undefined behaviour and dangerous coding constructs. The goal is to detect only real errors in the code, and generate as few false positives (wrongly reported warnings) as possible. Cppcheck is designed to analyze your C/C++ code even if it has non-standard syntax, as is common in for example embedded projects. @@ -17,7 +17,7 @@ Supported code and platforms: - Cppcheck checks non-standard code that contains various compiler extensions, inline assembly code, etc. - Cppcheck should be compilable by any compiler that supports C++11 or later. -- Cppcheck is cross platform and is used in various posix/windows/etc environments. +- Cppcheck is cross-platform and is used in various posix/windows/etc environments. The checks in Cppcheck are not perfect. There are bugs that should be found, that Cppcheck fails to detect. @@ -30,11 +30,11 @@ The kinds of bugs that you can find with static analysis are: - Coding style There are many bugs that you can not find with static analysis. Static analysis tools do not have human knowledge about -what your program is intended to do. If the output from your program is valid but unexpected then in most cases this is -not detected by static analysis tools. For instance, if your small program writes "Helo" on the screen instead of "Hello" +what your program is intended to do. If the output from your program is valid but unexpected, then in most cases this is +not detected by static analysis tools. For instance, if your small program writes "Helo" on the screen instead of "Hello", it is unlikely that any tool will complain about that. -Static analysis should be used as a complement in your quality assurance. It does not replace any of; +Static analysis should be used as a complement in your quality assurance. It does not replace any of: - Careful design - Testing @@ -70,7 +70,7 @@ Here is some simple code: return 0; } -If you save that into file1.c and execute: +If you save that into `file1.c` and execute: cppcheck file1.c @@ -85,7 +85,7 @@ Normally a program has many source files. Cppcheck can check all source files in cppcheck path -If "path" is a folder, then Cppcheck will recursively check all source files in this folder: +If `path` is a folder, then Cppcheck will recursively check all source files in this folder: Checking path/file1.cpp... 1/2 files checked 50% done @@ -94,21 +94,22 @@ If "path" is a folder, then Cppcheck will recursively check all source files in ### Check files manually or use project file -With Cppcheck you can check files manually by specifying files/paths to check and settings. Or you can use a build +With Cppcheck you can check files manually by specifying files/paths to check and settings. Or you can use a build environment, such as CMake or Visual Studio. -We don't know which approach (project file or manual configuration) will give you the best results. It is recommended -that you try both. It is possible that you will get different results so that to find the largest amount of bugs you +We don't know which approach (project file or manual configuration) will give you the best results. It is recommended +that you try both. It is possible that you will get different results so that to find the largest amount of bugs you need to use both approaches. Later chapters will describe this in more detail. ### Check files matching a given file filter With `--file-filter=` you can configure file filter(s) and then only those files matching the filter will be checked. -You can use `**`, `*` and `?` in the file filter pattern. -`**`: matches zero or more characters, including path separators -`*`: matches zero or more characters, excluding path separators -`?`: matches any single character except path separators +You can use `**`, `*` and `?` in the file filter pattern. + +- `**` matches zero or more characters, including path separators. +- `*` matches zero or more characters, excluding path separators. +- `?` matches any single character except path separators. For example, this command below means that `src/test1.cpp` could be checked, but `src/file2.cpp` and `src/test/file1.cpp` will not be checked: @@ -122,22 +123,24 @@ A common use case for `--file-filter` is to check a project, but only check cert cppcheck --project=compile_commands.json --file-filter=src/*.c Typically a `compile_commands.json` contains absolute paths. However no matter if `compile_commands.json` contains absolute paths or relative paths, the option `--file-filter=src/*.c` would mean that: - * a file with relative path `test1.c` is not checked. - * a file with relative path `src/test2.c` can be checked. - * a file with relative path `src/test3.cpp` is not checked. + +- a file with relative path `test1.c` is not checked. +- a file with relative path `src/test2.c` can be checked. +- a file with relative path `src/test3.cpp` is not checked. ### Ignore files matching a given pattern With `-i ` you can configure filename/directory patterns that should be ignored. -A file that is ignored will not be checked directly (the complete translation unit is skipped). Any header #include'd from a source file which is not ignored is checked indirectly, regardless if the header is ignored. +A file that is ignored will not be checked directly (the complete translation unit is skipped). Any header `#include`'d from a source file which is not ignored is checked indirectly, regardless if the header is ignored. -> *Note*: If you want to filter out warnings for a header file then `-i` will not work. Use suppressions instead. +> *Note*: If you want to filter out warnings for a header file, then `-i` will not work. Use suppressions instead. -You can use `**`, `*` and `?` in the pattern to specify excluded folders/files. -`**`: matches zero or more characters, including path separators -`*`: matches zero or more characters, excluding path separators -`?`: matches any single character except path separators +You can use `**`, `*` and `?` in the pattern to specify excluded folders/files. + +- `**` matches zero or more characters, including path separators. +- `*` matches zero or more characters, excluding path separators. +- `?` matches any single character except path separators. A use case for `-i` is to check a project, but exclude certain files/folders: @@ -145,8 +148,8 @@ A use case for `-i` is to check a project, but exclude certain files/folders: Typically a `compile_commands.json` contains absolute paths. However no matter if `compile_commands.json` contains absolute paths or relative paths, the option `-itest` would mean that: - * a file with relative path `test1.cpp` can be checked. - * a file with relative path `test/somefile.cpp` is not checked +- a file with relative path `test1.cpp` can be checked. +- a file with relative path `test/somefile.cpp` is not checked ### Clang parser (experimental) @@ -157,7 +160,7 @@ Install `clang`. Then use Cppcheck option `--clang`. Cppcheck executes clang with the -ast-dump option, imports the output, converts it to Cppcheck's internal format, and then performs standard analysis. -You can also pass a custom Clang executable to the option by using for example `--clang=clang-10`. You can also pass it +You can also pass a custom Clang executable to the option by using for example `--clang=clang-10`. You can also pass it with a path. On Windows it will append the `.exe` extension unless you use a path. ## Severities @@ -178,17 +181,17 @@ stylistic issues, such as unused functions, redundant code, constness, operator **performance** -run time performance suggestions based on common knowledge, though it is not certain any measurable speed difference +run time performance suggestions based on common knowledge, though it is not certain any measurable speed difference will be achieved by fixing these messages. **portability** -portability warnings. Implementation defined behavior. 64-bit portability. Some undefined behavior that probably works +portability warnings. Implementation defined behavior. 64-bit portability. Some undefined behavior that probably works "as you want", etc. **information** -configuration problems, which does not relate to the syntactical correctness, but the used Cppcheck configuration could +configuration problems, which does not relate to the syntactical correctness, but the used Cppcheck configuration could be improved. ## Possible speedup analysis of template code @@ -222,7 +225,7 @@ Cppcheck output: As you can see Cppcheck has instantiated `a` until `a<101>` was reached and then it bails out. -To limit template recursion you can: +To limit template recursion, you can: - add template specialisation - configure Cppcheck, which can be done in the GUI project file dialog @@ -246,7 +249,6 @@ Example code with template specialisation: You can pass `-D__cppcheck__` when checking this code. - # Cppcheck build folder Using a Cppcheck build folder is not mandatory but it is recommended. @@ -280,25 +282,25 @@ You can import and use Cppcheck GUI project files in the command line tool: cppcheck --project=foobar.cppcheck -The Cppcheck GUI has a few options that are not available in the command line directly. To use these options you can import a GUI project file. +The Cppcheck GUI has a few options that are not available in the command line directly. To use these options, you can import a GUI project file. The command line tool usage is kept intentionally simple and the options are therefore limited. -To ignore certain folders in the project you can use `-i`. This will skip the analysis of source files in the `foo` folder. +To ignore certain folders in the project, you can use `-i`. This will skip the analysis of source files in the `foo` folder. cppcheck --project=foobar.cppcheck -ifoo -## Compilation database (cmake etc) +## Compilation database (CMake etc.) Many build systems can generate a compilation database (a JSON file containing compilation commands for each source file). Example `cmake` command to generate the file: cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON . -When you have a `compile_commands.json` file you can run Cppcheck like this: +When you have a `compile_commands.json` file, you can run Cppcheck like this: cppcheck --project=compile_commands.json -By default only 1 configuration is checked because that is consistent with the compilation. If you want to check more configurations you can use `--max-configs` or `--force`. For example: +By default only 1 configuration is checked because that is consistent with the compilation. If you want to check more configurations, you can use `--max-configs` or `--force`. For example: cppcheck --project=compile_commands.json --force @@ -306,10 +308,20 @@ To ignore certain folders you can use `-i`. This will skip analysis of source fi cppcheck --project=compile_commands.json -ifoo +### `-isystem`, `-I`, `--sysroot` + +We have a [script](https://github.com/cppcheck-opensource/cppcheck/blob/main/tools/tweak-compile-commands.py) that tweaks compile_commands.json. + +You can use it to: + * use `--sysroot` flags in Cppcheck analysis + * use `-isystem` paths in Cppcheck analysis + * remove `-I` paths from the compile_commands.json + +See [script documentation](https://github.com/cppcheck-opensource/cppcheck/blob/main/tools/tweak-compile-commands.md). ## Visual Studio -You can run Cppcheck on individual project files (\*.vcxproj) or on a whole solution (\*.sln) or (\*.slnx) +You can run Cppcheck on individual project files (`*.vcxproj`) or on a whole solution (`*.sln`) or (`*.slnx`). Running Cppcheck on an entire Visual Studio solution: @@ -330,7 +342,7 @@ Limiting on a single configuration: In the `Cppcheck GUI` you have the option to only analyze a single debug configuration. If you want to use this option on the command line, then create a `Cppcheck GUI` project with this activated and then import the GUI project file on the command line. -To ignore certain folders in the project you can use `-i`. This will skip analysis of source files in the `foo` folder. +To ignore certain folders in the project, you can use `-i`. This will skip analysis of source files in the `foo` folder. cppcheck --project=foobar.vcxproj -ifoo @@ -340,8 +352,7 @@ Running Cppcheck on a C++ Builder 6 project: cppcheck --project=foobar.bpr - -To ignore certain folders in the project you can use `-i`. This will skip analysis of source files in the `foo` folder. +To ignore certain folders in the project, you can use `-i`. This will skip analysis of source files in the `foo` folder. cppcheck --project=foobar.bpr -ifoo @@ -351,18 +362,18 @@ If you generate a compilation database, then it is possible to import that in Cp ### Makefile -In Linux you can convert a Makefile to a compile_commands.json using for instance `bear` (build ear) utility: +In Linux you can convert a Makefile to a `compile_commands.json` using for instance `bear` (build ear) utility: bear -- make -If you don't use Linux; there are python scripts that converts a Makefile into a compilation database. +If you don't use Linux, there are Python scripts that converts a Makefile into a compilation database. -# Preprocessor Settings +# Preprocessor settings -If you use `--project` then Cppcheck will automatically use the preprocessor settings in the imported project file and +If you use `--project`, then Cppcheck will automatically use the preprocessor settings in the imported project file and likely you don't have to configure anything extra. -If you don't use `--project` then a bit of manual preprocessor configuration might be required. However Cppcheck has +If you don't use `--project`, then a bit of manual preprocessor configuration might be required. However Cppcheck has automatic configuration of defines. ## Automatic configuration of preprocessor defines @@ -370,7 +381,7 @@ automatic configuration of defines. Cppcheck automatically test different combinations of preprocessor defines to achieve as high coverage in the analysis as possible. -Here is a file that has 3 bugs (when x,y,z are assigned). +Here is a file that has 3 bugs (when `x`, `y`, `z` are assigned). #ifdef A x=100/0; @@ -426,17 +437,17 @@ Example: ## Include paths -To add an include path, use `-I`, followed by the path. +To add an include path, use `-I` followed by the path. -Cppcheck's preprocessor basically handles includes like any other preprocessor. However, while other preprocessors -stop working when they encounter a missing header, Cppcheck will just print an information message and continues +Cppcheck's preprocessor basically handles includes like any other preprocessor. However, while other preprocessors +stop working when they encounter a missing header, Cppcheck will just print an information message and continues parsing the code. -The purpose of this behaviour is that Cppcheck is meant to work without necessarily seeing the entire code. -Actually, it is recommended to not give all include paths. -While it is useful for Cppcheck to see the declaration of a class when checking the implementation of its members, -passing standard library headers is discouraged, because the analysis will not work fully and lead to a longer checking -time. For such cases, .cfg files are the preferred way to provide information about the implementation of functions and +The purpose of this behaviour is that Cppcheck is meant to work without necessarily seeing the entire code. +Actually, it is recommended to not give all include paths. +While it is useful for Cppcheck to see the declaration of a class when checking the implementation of its members, +passing standard library headers is discouraged, because the analysis will not work fully and lead to a longer checking +time. For such cases, `.cfg` files are the preferred way to provide information about the implementation of functions and types to Cppcheck, see below for more information. # Platform @@ -467,7 +478,7 @@ You can also create your own custom platform configuration in a XML file. Here i -# C/C++ Standard +# C/C++ standard Use `--std` on the command line to specify a C/C++ standard. @@ -496,7 +507,7 @@ the GUI, the build dir is configured in the project options. Rechecking code will be much faster. Cppcheck does not analyse unchanged code. The old warnings are loaded from the build dir and reported again. -Whole program analysis does not work when multiple threads are used; unless you use a cppcheck +Whole program analysis does not work when multiple threads are used unless you use a Cppcheck build dir. For instance, the unusedFunction warnings require whole program analysis. # Suppressions @@ -514,12 +525,13 @@ The format for an error suppression is one of: [error id]:[filename2] [error id] -The `error id` is the id that you want to suppress. The id of a warning is shown in brackets in the normal cppcheck text output. +The `error id` is the id that you want to suppress. The id of a warning is shown in brackets in the normal Cppcheck text output. + +The `error id` and `filename` patterns may contain `**`, `*` or `?`. -The `error id` and `filename` patterns may contain `**`, `*` or `?`. -`**`: matches zero or more characters, including path separators -`*`: matches zero or more characters, excluding path separators -`?`: matches any single character except path separators +- `**` matches zero or more characters, including path separators. +- `*` matches zero or more characters, excluding path separators. +- `?` matches any single character except path separators. It is recommended to use forward-slash `/` in the filename pattern as path separator on all operating systems. @@ -561,9 +573,10 @@ You can specify suppressions in a XML file, for example as follows: The `id` and `fileName` patterns may contain `**`, `*` or `?`. -`**`: matches zero or more characters, including path separators -`*`: matches zero or more characters, excluding path separators -`?`: matches any single character except path separators + +- `**` matches zero or more characters, including path separators. +- `*` matches zero or more characters, excluding path separators. +- `?` matches any single character except path separators. The XML format is extensible and may be extended with further attributes in the future. @@ -571,9 +584,37 @@ The usage of the suppressions file is as follows: cppcheck --suppress-xml=suppressions.xml src/ +### The `` element + +Cppcheck calculates a unique ID for each error, called the hash. The hash depends on the code that +is related to the error, not on where that code is located. This means that the hash for an error +stays the same even when unrelated code elsewhere in the file is added, removed or moved, shifting +the line numbers around. The hash only changes if the related code itself is modified. + +This makes hash-based suppressions more robust than line-based suppressions: once you have +reviewed and suppressed a specific warning, the suppression keeps working even after the file is +edited, as long as the offending code is not changed. + +The hash for an error is included as the `hash` attribute in the [XML output](#the-error-element). +You can copy that value into a `` element in a suppressions XML file: + + + + + uninitvar + src/file1.c + 12345678 + + + +A suppression can use `` on its own, without ``, to suppress a specific error regardless +of its id. It is also possible to combine `` with ``, ``, `` and +``; when several of these are specified, all of them must match for the suppression to +apply. + ## Inline suppressions -Suppressions can also be added directly in the code by adding comments that contain special keywords. +Suppressions can also be added directly in the code by adding comments that contain special keywords. Note that adding comments sacrifices the readability of the code somewhat. This code will normally generate an error message: @@ -598,7 +639,7 @@ You can suppress a warning `aaaa` with: // cppcheck-suppress aaaa -Suppressing multiple ids in one comment by using []: +Suppressing multiple ids in one comment by using `[]`: // cppcheck-suppress [aaaa, bbbb] @@ -629,7 +670,6 @@ Suppressing warnings `aaaa` where macro is used: ... x = MACRO; // <- aaaa warnings are suppressed here - Suppressing multiple ids where macro is used: // cppcheck-suppress-macro [aaaa, bbbb] @@ -719,7 +759,6 @@ Suppression comment on the same line as the code: arr[10] = arr[10] / 0; // cppcheck-suppress[arrayIndexOutOfBounds,zerodiv] } - ### Symbol name You can specify that the inline suppression only applies to a specific symbol: @@ -738,6 +777,120 @@ You can write comments about a suppression as follows: // cppcheck-suppress warningid ; some comment // cppcheck-suppress warningid // some comment +# Generating and using a baseline + +When you first run Cppcheck on an existing codebase it's common to get a +large number of warnings. A "baseline" lets you suppress all of today's +warnings and see only *new* warnings introduced from now on. + +This works by asking Cppcheck to include a content-based `hash` for every warning +(computed from the surrounding code, not the line number), then converting today's +warnings into an XML suppressions file keyed on `id` + `fileName` + `hash`. Because +the hash is based on content rather than line number, warnings stay suppressed even +after unrelated lines above them are added or removed. A warning only reappears if +the code it actually points at changes, or a new warning shows up elsewhere. + +## 1. Generate the baseline + +Run Cppcheck with `--xml` and capture stderr (where Cppcheck writes its XML) to a +file: + +```sh +cppcheck --enable=style --xml src 2> baseline-results.xml +``` + +Use whatever combination of `--enable`/defines/include paths you normally +analyze the project with — the suppressions you get out only cover the +checks you ran. + +## 2. Convert the results into a suppressions file + +A [script](https://github.com/cppcheck-opensource/cppcheck/blob/main/tools/generate-baseline-suppressions.py) can be used to generate the baseline: +```sh +python3 generate-baseline-suppressions.py baseline-results.xml suppressions.xml +``` + +This produces a `suppressions.xml` like: + +```xml + + + + uninitvar + src/file1.c + 12345678 + + +``` + +`suppressions.xml` needs to be shared by everyone who runs Cppcheck on this +codebase, including CI. + +Having a script instead of using some special cppcheck flags has the advantages: + * it's flexible. You can tweak it if needed. + * there is fewer flags for us to maintain and document, and for you to learn. + +## 3. Use the baseline on future runs + +```sh +cppcheck --enable=style --xml --suppress-xml=suppressions.xml src +``` + +Only warnings that aren't in the baseline are reported: new warnings in changed +code, and warnings for checks/files that weren't covered when the baseline was +generated. + +## 4. Refreshing the baseline + +Re-run steps 1-2 whenever you want to accept the current state as the new +baseline (e.g. after cleaning up a batch of warnings, or deliberately accepting a +new one). Regenerating overwrites `suppressions.xml` with an entry for every +warning present at that time. + +## The baseline doesn't need to be kept in sync + +Once code that a baselined warning pointed at is fixed, refactored away, or +deleted, its `suppress` entry in `suppressions.xml` becomes dead: nothing will +ever match it again. You don't need to go find and remove it. + +Cppcheck's `unmatchedSuppression` check (part of `--enable=all`) normally warns +about suppressions that never matched anything, on the theory that a +suppression nobody needs is probably a mistake. But it does *not* fire for +`suppress` entries that carry a `` — which is every entry the baseline +script generates. So a baseline file with plenty of dead entries produces no +noise, and developers never need to prune it by hand — it only needs to be +regenerated (step 4) when you deliberately want to reset what's accepted. + +## Caveats + +- A warning can only be suppressed this way if it carries a `hash` attribute in + the XML output. Almost all checks compute one. Critical errors, i.e. syntax + errors do not get hash and must be fixed. Certain information messages do not + get hash neither. +- Changed Cppcheck options might produce new warnings that are not suppressed + by the baseline. +- Cppcheck upgrades don't affect the hash directly (it isn't version-tagged), + but if a new release for instance changes a check's message wording, the hash + changes with it — so upgrading Cppcheck can resurrect baselined warnings for + checks whose messages were reworded, even though nothing in the analyzed code + changed. + +## Strategies for gradually shrinking the baseline + +A baseline makes it possible to adopt Cppcheck in CI immediately without +fixing everything first, but nothing about it enforces that the accepted set +of warnings actually shrinks over time. + +Generic advice: + +- **Prioritize by severity** Checks like `uninitvar` or + `nullPointer` are more valuable to clear than `style` warnings; a baseline + makes it possible to drive the highest-severity checks to zero first while + deliberately leaving lower-risk ones suppressed longer. +- **Be careful** Every fix is a code change, and every code change carries some + risk of introducing a new bug. It can make sense to leave some things suppressed + to minimize the risk that bugs are introduced in working code. + # XML output Cppcheck can generate output in XML format. Use `--xml` to enable this format. @@ -819,7 +972,7 @@ If you want to reformat the output so that it looks different, then you can use ## Predefined output formats -To get Visual Studio compatible output you can use --template=vs: +To get Visual Studio compatible output, you can use `--template=vs`: cppcheck --template=vs samples/arrayIndexOutOfBounds/bad.c @@ -829,7 +982,7 @@ This output will look like this: samples/arrayIndexOutOfBounds/bad.c(6): error: Array 'a[2]' accessed at index 2, which is out of bounds. -To get gcc compatible output you can use --template=gcc: +To get GCC-compatible output, you can use `--template=gcc`: cppcheck --template=gcc samples/arrayIndexOutOfBounds/bad.c @@ -867,7 +1020,7 @@ The output will look like this: samples/arrayIndexOutOfBounds/bad.c,6,error,arrayIndexOutOfBounds, Array 'a[2]' accessed at index 2, which is out of bounds. -## User defined output format (multi line) +## User defined output format (multi-line) Many warnings have multiple locations. Example code: @@ -883,9 +1036,9 @@ Many warnings have multiple locations. Example code: return 0; } -There is a possible null pointer dereference at line 3. -Cppcheck can show how it came to that conclusion by showing extra location information. -You need to use both --template and --template-location at the command line, for example: +There is a possible null pointer dereference at line 3. +Cppcheck can show how it came to that conclusion by showing extra location information. +You need to use both `--template` and `--template-location` at the command line, for example: cppcheck \ --template="{file}:{line}: {severity}: {message}\n{code}" \ @@ -907,13 +1060,13 @@ The output from Cppcheck is: *p = 3; ^ -The first line in the warning is formatted by the --template format. +The first line in the warning is formatted by the `--template` format. -The other lines in the warning are formatted by the --template-location format. +The other lines in the warning are formatted by the `--template-location` format. -### Format specifiers for --template +### Format specifiers for `--template` -The available specifiers for --template are: +The available specifiers for `--template` are: **{file}** @@ -929,11 +1082,11 @@ Column number **{callstack}** -Write all locations. Each location is written in [{file}:{line}] format and the locations are separated by ->. For instance it might look like: [multiline.c:8] -> [multiline.c:9] -> [multiline.c:3] +Write all locations. Each location is written in `[{file}:{line}]` format and the locations are separated by `->`. For instance it might look like: `[multiline.c:8] -> [multiline.c:9] -> [multiline.c:3]` **{inconclusive:text}** -If warning is inconclusive, then the given text is written. The given text can be any text that does not contain }. Example: {inconclusive:inconclusive,} +If warning is inconclusive, then the given text is written. The given text can be any text that does not contain `}`. Example: `{inconclusive:inconclusive,}` **{severity}** @@ -967,7 +1120,7 @@ Newline Carriage return -### Format specifiers for --template-location +### Format specifiers for `--template-location` The available specifiers for `--template-location` are: @@ -1019,7 +1172,7 @@ Example code: int x = 0; } -In Cppcheck text output the remarks are not shown by default, you can use `--template` option `{remark}` to show remarks: +In Cppcheck text output, the remarks are not shown by default, you can use `--template` option `{remark}` to show remarks: $ ./cppcheck --enable=style \ --template="{file}:{line}: {message} [{id}]\\n{remark}" test1.c @@ -1028,12 +1181,12 @@ In Cppcheck text output the remarks are not shown by default, you can use `--tem test1.c:4: Variable 'x' is assigned a value that is never used. [unreadVariable] Initialize x with 0 -In xml output the comment text is provided in a "remark" attribute: +In XML output, the comment text is provided in a `remark` attribute: $ ./cppcheck --enable=style --xml test1.c - .... + ... remark="Initialize x with 0" - .... + ... # Addons @@ -1043,11 +1196,11 @@ Cppcheck is distributed with a few addons which are listed below. ## Supported addons -### misra.py +### `misra.py` -[misra.py](https://github.com/danmar/cppcheck/blob/main/addons/misra.py) is used to verify compliance with MISRA C 2012, a proprietary set of guidelines to avoid questionable code, developed for embedded systems. +[`misra.py`](https://github.com/cppcheck-opensource/cppcheck/blob/main/addons/misra.py) is used to verify compliance with MISRA C 2012, a proprietary set of guidelines to avoid questionable code, developed for embedded systems. -The misra.py script does not provide rule texts, those should be downloaded from [MISRA](https://gitlab.com/MISRA/MISRA-C/MISRA-C-2012/tools) +The `misra.py` script does not provide rule texts, those should be downloaded from [MISRA](https://gitlab.com/MISRA/MISRA-C/MISRA-C-2012/tools). To load the rule texts, create a configuration file. Example `misra.json`: @@ -1065,52 +1218,51 @@ To use that `misra.json` in Cppcheck analysis, use option `--addon=misra.json`: Misra checkers in open source Cppcheck only cover MISRA rules partially and for full coverage use Cppcheck Premium. -### namingng.py +### `namingng.py` -[namingng.py](https://github.com/danmar/cppcheck/blob/main/addons/namingng.py) allows you to configure and check naming conventions. +[`namingng.py`](https://github.com/cppcheck-opensource/cppcheck/blob/main/addons/namingng.py) allows you to configure and check naming conventions. You need to have a configuration file that defines your naming conventions. By default the filename `namingng.config.json` is used but there is an option so you can use any filename you want. Example configuration of naming conventions: -``` -{ - "RE_VARNAME": ["[a-z]*[a-zA-Z0-9_]*\\Z"], - "RE_PRIVATE_MEMBER_VARIABLE": null, - "RE_FUNCTIONNAME": ["[a-z0-9A-Z]*\\Z"], - "_comment": "comments can be added to the config with underscore-prefixed keys", - "include_guard": { - "input": "path", - "prefix": "GUARD_", - "case": "upper", - "max_linenr": 5, - "RE_HEADERFILE": "[^/].*\\.h\\Z", - "required": true - }, - "var_prefixes": {"uint32_t": "ui32"}, - "function_prefixes": {"uint16_t": "ui16", - "uint32_t": "ui32"} -} -``` -### threadsafety.py + { + "RE_VARNAME": ["[a-z]*[a-zA-Z0-9_]*\\Z"], + "RE_PRIVATE_MEMBER_VARIABLE": null, + "RE_FUNCTIONNAME": ["[a-z0-9A-Z]*\\Z"], + "_comment": "comments can be added to the config with underscore-prefixed keys", + "include_guard": { + "input": "path", + "prefix": "GUARD_", + "case": "upper", + "max_linenr": 5, + "RE_HEADERFILE": "[^/].*\\.h\\Z", + "required": true + }, + "var_prefixes": {"uint32_t": "ui32"}, + "function_prefixes": {"uint16_t": "ui16", + "uint32_t": "ui32"} + } + +### `threadsafety.py` -[threadsafety.py](https://github.com/danmar/cppcheck/blob/main/addons/threadsafety.py) analyses Cppcheck dump files to locate thread safety issues like static local objects used by multiple threads. +[`threadsafety.py`](https://github.com/cppcheck-opensource/cppcheck/blob/main/addons/threadsafety.py) analyses Cppcheck dump files to locate thread safety issues like static local objects used by multiple threads. -### y2038.py +### `y2038.py` -[y2038.py](https://github.com/danmar/cppcheck/blob/main/addons/y2038.py) checks source code for [year 2038 problem](https://en.wikipedia.org/wiki/Year_2038_problem) safety. +[`y2038.py`](https://github.com/cppcheck-opensource/cppcheck/blob/main/addons/y2038.py) checks source code for [year 2038 problem](https://en.wikipedia.org/wiki/Year_2038_problem) safety. -## Running Addons +## Running addons Addons can be executed with the `--addon` option: cppcheck --addon=namingng.py somefile.c -Likewise, if you have created your own script you can execute that: +Likewise, if you have created your own script, you can execute that: cppcheck --addon=mychecks.py somefile.c -You can configure how you want to execute an addon in a json file. For example: +You can configure how you want to execute an addon in a JSON file. For example: { "script": "mychecks.py", @@ -1120,7 +1272,7 @@ You can configure how you want to execute an addon in a json file. For example: "ctu": false } -To use that json file to execute your addon use the --addon option: +To use that JSON file to execute your addon, use the `--addon` option: cppcheck --addon=mychecks.json somefile.c @@ -1130,75 +1282,75 @@ Cppcheck search for addons in the local folder first and then in the installatio # Library configuration -When external libraries are used, such as WinAPI, POSIX, gtk, Qt, etc, Cppcheck has no information about functions, types, or macros contained in those libraries. Cppcheck then fails to detect various problems in the code, or might even abort the analysis. But this can be fixed by using the appropriate configuration files. - -Cppcheck already contains configurations for several libraries. They can be loaded as described below. Note that the configuration for the standard libraries of C and C++, std.cfg, is always loaded by cppcheck. If you create or update a configuration file for a popular library, we would appreciate if you supplied it to the cppcheck project. - -## Using a .cfg file - -To use a .cfg file shipped with cppcheck, pass the `--library=` option. The table below shows the currently existing libraries: - -| .cfg file | Library | Comment | -| ----------------- | ------------- | ------------- | -| avr.cfg | | | -| bento4.cfg | [Bento4](http://www.bento4.com/) | | -| boost.cfg | [Boost](http://www.boost.org/) | | -| bsd.cfg | [BSD](https://www.freebsd.org/) | | -| cairo.cfg | [cairo](https://www.cairographics.org/) | | -| cppcheck-lib.cfg | [Cppcheck](http://cppcheck.net/) | Used in selfcheck of | -| | |the Cppcheck code base | -| cppunit.cfg | [CppUnit](https://sourceforge.net/projects/cppunit/) | | -| dpdk.cfg | | | -| embedded_sql.cfg | | | -| emscripten.cfg | | | -| ginac.cfg | | | -| gnu.cfg | [GNU](https://www.gnu.org/) | | -| googletest.cfg | [GoogleTest](https://github.com/google/googletest) | | -| gtk.cfg | [GTK](https://www.gtk.org/) | | -| icu.cfg | | | -| kde.cfg | [KDE](https://kde.org/) | | -| libcerror.cfg | [libcerror](https://github.com/libyal/libcerror) | | -| libcurl.cfg | [libcurl](https://curl.se/libcurl/) | | -| libsigc++.cfg | [libsigc++](https://github.com/libsigcplusplus/libsigcplusplus) | | -| lua.cfg | | | -| mfc.cfg | [MFC](https://learn.microsoft.com/en-us/cpp/mfc/mfc-desktop-applications) | | -| microsoft_atl.cfg | [ATL](https://learn.microsoft.com/en-us/cpp/atl/active-template-library-atl-concepts) | | -| microsoft_sal.cfg | [SAL annotations](https://learn.microsoft.com/en-us/cpp/c-runtime-library/sal-annotations) | | -| microsoft_unittest.cfg | [CppUnitTest](https://learn.microsoft.com/en-us/visualstudio/test/microsoft-visualstudio-testtools-cppunittestframework-api-reference) | | -| motif.cfg | | | -| nspr.cfg | | | -| ntl.cfg | | | -| opencv2.cfg | [OpenCV](https://opencv.org/) | | -| opengl.cfg | [OpenGL](https://opengl.org/) | | -| openmp.cfg | [OpenMP](https://www.openmp.org/) | | -| openssl.cfg | [OpenSSL](https://www.openssl.org/) | | -| pcre.cfg | [PCRE](https://pcre.org/) | | -| posix.cfg | [POSIX](https://pubs.opengroup.org/onlinepubs/9699919799/) | | -| python.cfg | | | -| qt.cfg | [Qt](https://doc.qt.io/qt.html) | | -| ruby.cfg | | | -| sdl.cfg | | | -| sfml.cfg | | | -| sqlite3.cfg | [SQLite](https://www.sqlite.org/) | | -| std.cfg | C/C++ standard library | Loaded by default | -| tinyxml2.cfg | [TinyXML-2](https://github.com/leethomason/tinyxml2) | | -| vcl.cfg | | | -| windows.cfg | [Win32 API](https://learn.microsoft.com/en-us/windows/win32/) | | -| wxsqlite3.cfg | | | -| wxsvg.cfg | | | -| wxwidgets.cfg | [wxWidgets](https://www.wxwidgets.org/) | | -| zephyr.cfg | | | -| zlib.cfg | [zlib](https://www.zlib.net) | | - -## Creating a custom .cfg file - -You can create and use your own .cfg files for your projects. Use `--check-library` to get hints about what you should configure. +When external libraries are used, such as WinAPI, POSIX, gtk, Qt, etc., Cppcheck has no information about functions, types, or macros contained in those libraries. Cppcheck then fails to detect various problems in the code, or might even abort the analysis. But this can be fixed by using the appropriate configuration files. + +Cppcheck already contains configurations for several libraries. They can be loaded as described below. Note that the configuration for the standard libraries of C and C++, `std.cfg`, is always loaded by Cppcheck. If you create or update a configuration file for a popular library, we would appreciate if you supplied it to the Cppcheck project. + +## Using a `.cfg` file + +To use a `.cfg` file shipped with Cppcheck, pass the `--library=` option. The table below shows the currently existing libraries: + +| `.cfg` file | Library | Comment | +| --- | --- | --- | +| `avr.cfg` | | | +| `bento4.cfg` | [Bento4](http://www.bento4.com/) | | +| `boost.cfg` | [Boost](http://www.boost.org/) | | +| `bsd.cfg` | [BSD](https://www.freebsd.org/) | | +| `cairo.cfg` | [cairo](https://www.cairographics.org/) | | +| `cppcheck-lib.cfg` | [Cppcheck](http://cppcheck.net/) | Used in selfcheck of the Cppcheck code base | +| `cppunit.cfg` | [CppUnit](https://sourceforge.net/projects/cppunit/) | | +| `dpdk.cfg` | | | +| `embedded_sql.cfg` | | | +| `emscripten.cfg` | | | +| `ginac.cfg` | | | +| `gnu.cfg` | [GNU](https://www.gnu.org/) | | +| `googletest.cfg` | [GoogleTest](https://github.com/google/googletest) | | +| `gtk.cfg` | [GTK](https://www.gtk.org/) | | +| `icu.cfg` | | | +| `kde.cfg` | [KDE](https://kde.org/) | | +| `libcerror.cfg` | [libcerror](https://github.com/libyal/libcerror) | | +| `libcurl.cfg` | [libcurl](https://curl.se/libcurl/) | | +| `libsigc++.cfg` | [libsigc++](https://github.com/libsigcplusplus/libsigcplusplus) | | +| `lua.cfg` | | | +| `mfc.cfg` | [MFC](https://learn.microsoft.com/en-us/cpp/mfc/mfc-desktop-applications) | | +| `microsoft_atl.cfg` | [ATL](https://learn.microsoft.com/en-us/cpp/atl/active-template-library-atl-concepts) | | +| `microsoft_gsl.cfg` | [Microsoft.GSL](https://github.com/microsoft/gsl) | | +| `microsoft_sal.cfg` | [SAL annotations](https://learn.microsoft.com/en-us/cpp/c-runtime-library/sal-annotations) | | +| `microsoft_unittest.cfg` | [CppUnitTest](https://learn.microsoft.com/en-us/visualstudio/test/microsoft-visualstudio-testtools-cppunittestframework-api-reference) | | +| `motif.cfg` | | | +| `nspr.cfg` | | | +| `ntl.cfg` | | | +| `opencv2.cfg` | [OpenCV](https://opencv.org/) | | +| `opengl.cfg` | [OpenGL](https://opengl.org/) | | +| `openmp.cfg` | [OpenMP](https://www.openmp.org/) | | +| `openssl.cfg` | [OpenSSL](https://www.openssl.org/) | | +| `pcre.cfg` | [PCRE](https://pcre.org/) | | +| `posix.cfg` | [POSIX](https://pubs.opengroup.org/onlinepubs/9699919799/) | | +| `python.cfg` | | | +| `qt.cfg` | [Qt](https://doc.qt.io/qt.html) | | +| `ruby.cfg` | | | +| `sdl.cfg` | | | +| `sfml.cfg` | | | +| `sqlite3.cfg` | [SQLite](https://www.sqlite.org/) | | +| `std.cfg` | C/C++ standard library | Loaded by default | +| `tinyxml2.cfg` | [TinyXML-2](https://github.com/leethomason/tinyxml2) | | +| `vcl.cfg` | | | +| `windows.cfg` | [Win32 API](https://learn.microsoft.com/en-us/windows/win32/) | | +| `wxsqlite3.cfg` | | | +| `wxsvg.cfg` | | | +| `wxwidgets.cfg` | [wxWidgets](https://www.wxwidgets.org/) | | +| `zephyr.cfg` | | | +| `zlib.cfg` | [zlib](https://www.zlib.net) | | + +## Creating a custom `.cfg` file + +You can create and use your own `.cfg` files for your projects. Use `--check-library` to get hints about what you should configure. You can use the `Library Editor` in the `Cppcheck GUI` to edit configuration files. It is available in the `View` menu. -The .cfg file format is documented in the `Reference: Cppcheck .cfg format` (https://cppcheck.sourceforge.io/reference-cfg-format.pdf) document. +The `.cfg` file format is documented in the `Reference: Cppcheck .cfg format` () document. -# HTML Report +# HTML report You can convert the XML output from Cppcheck into a HTML report. You'll need Python and the pygments module () for this to work. In the Cppcheck source tree there is a folder htmlreport that contains a script that transforms a Cppcheck XML file into HTML output. @@ -1228,15 +1380,17 @@ Example usage: cppcheck-htmlreport --file=err.xml --report-dir=test1 --source-dir=. or + cppcheck gui/test.cpp --xml 2> err.xml cppcheck-htmlreport --file=err.xml --report-dir=test1 \ --source-dir=https://github.com///blob// -## Choosing Between Local Annotated HTML and Remote Repository Links +## Choosing between local annotated HTML and remote repository links + +`cppcheck-htmlreport` supports two modes for linking to source files: -cppcheck-htmlreport supports two modes for linking to source files: - - Local annotated HTML files (default when `--source-dir` is a filesystem path) - - Remote GitHub/GitLab links (when `--source-dir` is a URL) +- Local annotated HTML files (default when `--source-dir` is a filesystem path) +- Remote GitHub/GitLab links (when `--source-dir` is a URL) Pointing `--source-dir` to a filesystem path generates local annotated HTML files. This is useful when you need a fully self-contained report that works offline, @@ -1251,13 +1405,13 @@ handled by the hosting service. In general, local mode fits air-gapped environments, while remote mode works best for CI workflows and large or private repositories. -# Check Level +# Check level ## Reduced The "reduced" check level performs a limited data flow analysis. If developers -want to run cppcheck directly during development and require faster results -than "normal" provides then this reduced checking can be an option. +want to run Cppcheck directly during development and require faster results +than "normal" provides, then this reduced checking can be an option. ## Normal @@ -1267,7 +1421,7 @@ The "normal" check level should be useful during active development: - checking files while you edit them. - block changes to the repo -- etc +- etc. ## Exhaustive @@ -1276,7 +1430,7 @@ When you can wait longer for the results you can enable the "exhaustive" checkin Exhaustive checking level should be useful for scenarios where you can wait for results. For instance: - nightly builds -- etc +- etc. # Speeding up analysis @@ -1284,18 +1438,17 @@ Exhaustive checking level should be useful for scenarios where you can wait for For performance reasons it might be a good idea to limit preprocessor configurations to check. -## Limit ValueFlow: max if count +## Limit ValueFlow: max `if` count -The command line option `--performance-valueflow-max-if-count` adjusts the max count for number of if in a function. +The command line option `--performance-valueflow-max-if-count` adjusts the max count for number of `if` in a function. -When that limit is exceeded there is a limitation of data flow in that function. It is not drastic: +When that limit is exceeded, there is a limitation of data flow in that function. It is not drastic: - Analysis of other functions are not affected. - It's only for some specific data flow analysis, we have data flow analysis that is always executed. - All checks are always executed. There can still be plenty of warnings in the limited function. -There is data flow analysis that slows down exponentially when number of if increase. And the limit is intended to avoid that -analysis time explodes. +There is data flow analysis that slows down exponentially when number of `if` increase. And the limit is intended to avoid that analysis time explodes. ## GUI options @@ -1306,4 +1459,4 @@ In the GUI: - Open the project dialog. - In the "Analysis" tab there are several options. -If you want to use these limitations on the command line also you can import the GUI project file with --project. +If you want to use these limitations on the command line too, you can import the GUI project file with `--project`. diff --git a/man/reference-cfg-format.md b/man/reference-cfg-format.md index 3067d0a8f2c..596fd6cb856 100644 --- a/man/reference-cfg-format.md +++ b/man/reference-cfg-format.md @@ -1,6 +1,6 @@ --- title: Cppcheck .cfg format -subtitle: Version 2.21 dev +subtitle: Version 2.23 dev author: Cppcheck team lang: en documentclass: report diff --git a/man/writing-addons.md b/man/writing-addons.md index e593c2e9cce..6bc7b124e76 100644 --- a/man/writing-addons.md +++ b/man/writing-addons.md @@ -1,6 +1,6 @@ --- title: Writing addons -subtitle: Version 2.21 dev +subtitle: Version 2.23 dev author: Cppcheck team lang: en documentclass: report diff --git a/oss-fuzz/Makefile b/oss-fuzz/Makefile index 1dfa482ae95..081aa035cdc 100644 --- a/oss-fuzz/Makefile +++ b/oss-fuzz/Makefile @@ -45,7 +45,6 @@ LIBOBJ = $(libcppdir)/valueflow.o \ $(libcppdir)/addoninfo.o \ $(libcppdir)/analyzerinfo.o \ $(libcppdir)/astutils.o \ - $(libcppdir)/check.o \ $(libcppdir)/check64bit.o \ $(libcppdir)/checkassert.o \ $(libcppdir)/checkautovariables.o \ @@ -58,6 +57,7 @@ LIBOBJ = $(libcppdir)/valueflow.o \ $(libcppdir)/checkersreport.o \ $(libcppdir)/checkexceptionsafety.o \ $(libcppdir)/checkfunctions.o \ + $(libcppdir)/checkimpl.o \ $(libcppdir)/checkinternal.o \ $(libcppdir)/checkio.o \ $(libcppdir)/checkleakautovar.o \ @@ -65,6 +65,7 @@ LIBOBJ = $(libcppdir)/valueflow.o \ $(libcppdir)/checknullpointer.o \ $(libcppdir)/checkother.o \ $(libcppdir)/checkpostfixoperator.o \ + $(libcppdir)/checks.o \ $(libcppdir)/checksizeof.o \ $(libcppdir)/checkstl.o \ $(libcppdir)/checkstring.o \ @@ -152,46 +153,43 @@ simplecpp.o: ../externals/simplecpp/simplecpp.cpp ../externals/simplecpp/simplec tinyxml2.o: ../externals/tinyxml2/tinyxml2.cpp ../externals/tinyxml2/tinyxml2.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -w -D_LARGEFILE_SOURCE -c -o $@ ../externals/tinyxml2/tinyxml2.cpp -$(libcppdir)/valueflow.o: ../lib/valueflow.cpp ../lib/addoninfo.h ../lib/analyzer.h ../lib/astutils.h ../lib/calculate.h ../lib/check.h ../lib/checkers.h ../lib/checkuninitvar.h ../lib/config.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/findtoken.h ../lib/forwardanalyzer.h ../lib/infer.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/programmemory.h ../lib/regex.h ../lib/reverseanalyzer.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/timer.h ../lib/token.h ../lib/tokenlist.h ../lib/utils.h ../lib/valueflow.h ../lib/valueptr.h ../lib/vf_analyzers.h ../lib/vf_common.h ../lib/vf_settokenvalue.h ../lib/vfvalue.h +$(libcppdir)/valueflow.o: ../lib/valueflow.cpp ../lib/analyzer.h ../lib/astutils.h ../lib/calculate.h ../lib/check.h ../lib/checkers.h ../lib/checkimpl.h ../lib/checkuninitvar.h ../lib/config.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/findtoken.h ../lib/forwardanalyzer.h ../lib/infer.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/programmemory.h ../lib/refthunk.h ../lib/reverseanalyzer.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/timer.h ../lib/token.h ../lib/tokenlist.h ../lib/utils.h ../lib/valueflow.h ../lib/valueptr.h ../lib/vf_analyzers.h ../lib/vf_common.h ../lib/vf_settokenvalue.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/valueflow.cpp -$(libcppdir)/tokenize.o: ../lib/tokenize.cpp ../externals/simplecpp/simplecpp.h ../lib/addoninfo.h ../lib/astutils.h ../lib/checkers.h ../lib/config.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/preprocessor.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/summaries.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/timer.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/valueflow.h ../lib/vfvalue.h +$(libcppdir)/tokenize.o: ../lib/tokenize.cpp ../externals/simplecpp/simplecpp.h ../lib/astutils.h ../lib/checkers.h ../lib/config.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/preprocessor.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/summaries.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/timer.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/valueflow.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/tokenize.cpp -$(libcppdir)/symboldatabase.o: ../lib/symboldatabase.cpp ../lib/addoninfo.h ../lib/astutils.h ../lib/checkers.h ../lib/config.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/keywords.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/valueflow.h ../lib/vfvalue.h +$(libcppdir)/symboldatabase.o: ../lib/symboldatabase.cpp ../lib/astutils.h ../lib/checkers.h ../lib/config.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/keywords.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/valueflow.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/symboldatabase.cpp $(libcppdir)/addoninfo.o: ../lib/addoninfo.cpp ../externals/picojson/picojson.h ../lib/addoninfo.h ../lib/config.h ../lib/json.h ../lib/path.h ../lib/standards.h ../lib/utils.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/addoninfo.cpp -$(libcppdir)/analyzerinfo.o: ../lib/analyzerinfo.cpp ../externals/tinyxml2/tinyxml2.h ../lib/analyzerinfo.h ../lib/config.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/filesettings.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/standards.h ../lib/utils.h ../lib/xml.h +$(libcppdir)/analyzerinfo.o: ../lib/analyzerinfo.cpp ../externals/tinyxml2/tinyxml2.h ../lib/analyzerinfo.h ../lib/config.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/filesettings.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/refthunk.h ../lib/standards.h ../lib/utils.h ../lib/xml.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/analyzerinfo.cpp -$(libcppdir)/astutils.o: ../lib/astutils.cpp ../lib/addoninfo.h ../lib/astutils.h ../lib/check.h ../lib/checkclass.h ../lib/checkers.h ../lib/config.h ../lib/errortypes.h ../lib/findtoken.h ../lib/infer.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/utils.h ../lib/valueflow.h ../lib/valueptr.h ../lib/vfvalue.h +$(libcppdir)/astutils.o: ../lib/astutils.cpp ../lib/astutils.h ../lib/check.h ../lib/checkclass.h ../lib/checkers.h ../lib/checkimpl.h ../lib/config.h ../lib/errortypes.h ../lib/findtoken.h ../lib/infer.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/utils.h ../lib/valueflow.h ../lib/valueptr.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/astutils.cpp -$(libcppdir)/check.o: ../lib/check.cpp ../lib/addoninfo.h ../lib/check.h ../lib/checkers.h ../lib/config.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/standards.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h - $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/check.cpp - -$(libcppdir)/check64bit.o: ../lib/check64bit.cpp ../lib/addoninfo.h ../lib/check.h ../lib/check64bit.h ../lib/checkers.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h +$(libcppdir)/check64bit.o: ../lib/check64bit.cpp ../lib/check.h ../lib/check64bit.h ../lib/checkers.h ../lib/checkimpl.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/check64bit.cpp -$(libcppdir)/checkassert.o: ../lib/checkassert.cpp ../lib/addoninfo.h ../lib/astutils.h ../lib/check.h ../lib/checkassert.h ../lib/checkers.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h +$(libcppdir)/checkassert.o: ../lib/checkassert.cpp ../lib/astutils.h ../lib/check.h ../lib/checkassert.h ../lib/checkers.h ../lib/checkimpl.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkassert.cpp -$(libcppdir)/checkautovariables.o: ../lib/checkautovariables.cpp ../lib/addoninfo.h ../lib/astutils.h ../lib/check.h ../lib/checkautovariables.h ../lib/checkers.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/valueflow.h ../lib/vfvalue.h +$(libcppdir)/checkautovariables.o: ../lib/checkautovariables.cpp ../lib/astutils.h ../lib/check.h ../lib/checkautovariables.h ../lib/checkers.h ../lib/checkimpl.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/valueflow.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkautovariables.cpp -$(libcppdir)/checkbool.o: ../lib/checkbool.cpp ../lib/addoninfo.h ../lib/astutils.h ../lib/check.h ../lib/checkbool.h ../lib/checkers.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h +$(libcppdir)/checkbool.o: ../lib/checkbool.cpp ../lib/astutils.h ../lib/check.h ../lib/checkbool.h ../lib/checkers.h ../lib/checkimpl.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkbool.cpp -$(libcppdir)/checkbufferoverrun.o: ../lib/checkbufferoverrun.cpp ../externals/tinyxml2/tinyxml2.h ../lib/addoninfo.h ../lib/astutils.h ../lib/check.h ../lib/checkbufferoverrun.h ../lib/checkers.h ../lib/config.h ../lib/ctu.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/valueflow.h ../lib/vfvalue.h ../lib/xml.h +$(libcppdir)/checkbufferoverrun.o: ../lib/checkbufferoverrun.cpp ../externals/tinyxml2/tinyxml2.h ../lib/astutils.h ../lib/check.h ../lib/checkbufferoverrun.h ../lib/checkers.h ../lib/checkimpl.h ../lib/config.h ../lib/ctu.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/valueflow.h ../lib/vf_common.h ../lib/vfvalue.h ../lib/xml.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkbufferoverrun.cpp -$(libcppdir)/checkclass.o: ../lib/checkclass.cpp ../externals/tinyxml2/tinyxml2.h ../lib/addoninfo.h ../lib/astutils.h ../lib/check.h ../lib/checkclass.h ../lib/checkers.h ../lib/config.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h ../lib/xml.h +$(libcppdir)/checkclass.o: ../lib/checkclass.cpp ../externals/tinyxml2/tinyxml2.h ../lib/astutils.h ../lib/check.h ../lib/checkclass.h ../lib/checkers.h ../lib/checkimpl.h ../lib/config.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h ../lib/xml.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkclass.cpp -$(libcppdir)/checkcondition.o: ../lib/checkcondition.cpp ../lib/addoninfo.h ../lib/astutils.h ../lib/check.h ../lib/checkcondition.h ../lib/checkers.h ../lib/checkother.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h +$(libcppdir)/checkcondition.o: ../lib/checkcondition.cpp ../lib/astutils.h ../lib/check.h ../lib/checkcondition.h ../lib/checkers.h ../lib/checkimpl.h ../lib/checkother.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkcondition.cpp $(libcppdir)/checkers.o: ../lib/checkers.cpp ../lib/checkers.h ../lib/config.h @@ -200,73 +198,79 @@ $(libcppdir)/checkers.o: ../lib/checkers.cpp ../lib/checkers.h ../lib/config.h $(libcppdir)/checkersidmapping.o: ../lib/checkersidmapping.cpp ../lib/checkers.h ../lib/config.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkersidmapping.cpp -$(libcppdir)/checkersreport.o: ../lib/checkersreport.cpp ../lib/addoninfo.h ../lib/checkers.h ../lib/checkersreport.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/standards.h ../lib/utils.h +$(libcppdir)/checkersreport.o: ../lib/checkersreport.cpp ../lib/addoninfo.h ../lib/checkers.h ../lib/checkersreport.h ../lib/config.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/standards.h ../lib/utils.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkersreport.cpp -$(libcppdir)/checkexceptionsafety.o: ../lib/checkexceptionsafety.cpp ../lib/addoninfo.h ../lib/astutils.h ../lib/check.h ../lib/checkers.h ../lib/checkexceptionsafety.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h +$(libcppdir)/checkexceptionsafety.o: ../lib/checkexceptionsafety.cpp ../lib/astutils.h ../lib/check.h ../lib/checkers.h ../lib/checkexceptionsafety.h ../lib/checkimpl.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkexceptionsafety.cpp -$(libcppdir)/checkfunctions.o: ../lib/checkfunctions.cpp ../lib/addoninfo.h ../lib/astutils.h ../lib/check.h ../lib/checkers.h ../lib/checkfunctions.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/valueflow.h ../lib/vfvalue.h +$(libcppdir)/checkfunctions.o: ../lib/checkfunctions.cpp ../lib/astutils.h ../lib/check.h ../lib/checkers.h ../lib/checkfunctions.h ../lib/checkimpl.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/valueflow.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkfunctions.cpp -$(libcppdir)/checkinternal.o: ../lib/checkinternal.cpp ../lib/addoninfo.h ../lib/astutils.h ../lib/check.h ../lib/checkers.h ../lib/checkinternal.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h +$(libcppdir)/checkimpl.o: ../lib/checkimpl.cpp ../lib/checkers.h ../lib/checkimpl.h ../lib/config.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/standards.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h + $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkimpl.cpp + +$(libcppdir)/checkinternal.o: ../lib/checkinternal.cpp ../lib/astutils.h ../lib/check.h ../lib/checkers.h ../lib/checkimpl.h ../lib/checkinternal.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkinternal.cpp -$(libcppdir)/checkio.o: ../lib/checkio.cpp ../lib/addoninfo.h ../lib/astutils.h ../lib/check.h ../lib/checkers.h ../lib/checkio.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h +$(libcppdir)/checkio.o: ../lib/checkio.cpp ../lib/astutils.h ../lib/check.h ../lib/checkers.h ../lib/checkimpl.h ../lib/checkio.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkio.cpp -$(libcppdir)/checkleakautovar.o: ../lib/checkleakautovar.cpp ../lib/addoninfo.h ../lib/astutils.h ../lib/check.h ../lib/checkers.h ../lib/checkleakautovar.h ../lib/checkmemoryleak.h ../lib/checknullpointer.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h +$(libcppdir)/checkleakautovar.o: ../lib/checkleakautovar.cpp ../lib/astutils.h ../lib/check.h ../lib/checkers.h ../lib/checkimpl.h ../lib/checkleakautovar.h ../lib/checkmemoryleak.h ../lib/checknullpointer.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkleakautovar.cpp -$(libcppdir)/checkmemoryleak.o: ../lib/checkmemoryleak.cpp ../lib/addoninfo.h ../lib/astutils.h ../lib/check.h ../lib/checkers.h ../lib/checkmemoryleak.h ../lib/config.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h +$(libcppdir)/checkmemoryleak.o: ../lib/checkmemoryleak.cpp ../lib/astutils.h ../lib/check.h ../lib/checkers.h ../lib/checkimpl.h ../lib/checkmemoryleak.h ../lib/config.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkmemoryleak.cpp -$(libcppdir)/checknullpointer.o: ../lib/checknullpointer.cpp ../lib/addoninfo.h ../lib/astutils.h ../lib/check.h ../lib/checkers.h ../lib/checknullpointer.h ../lib/config.h ../lib/ctu.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/findtoken.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/valueflow.h ../lib/vfvalue.h +$(libcppdir)/checknullpointer.o: ../lib/checknullpointer.cpp ../lib/astutils.h ../lib/check.h ../lib/checkers.h ../lib/checkimpl.h ../lib/checknullpointer.h ../lib/config.h ../lib/ctu.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/findtoken.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/valueflow.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checknullpointer.cpp -$(libcppdir)/checkother.o: ../lib/checkother.cpp ../lib/addoninfo.h ../lib/astutils.h ../lib/check.h ../lib/checkers.h ../lib/checkother.h ../lib/config.h ../lib/errortypes.h ../lib/fwdanalysis.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/valueflow.h ../lib/vfvalue.h +$(libcppdir)/checkother.o: ../lib/checkother.cpp ../lib/astutils.h ../lib/check.h ../lib/checkers.h ../lib/checkimpl.h ../lib/checkother.h ../lib/config.h ../lib/errortypes.h ../lib/fwdanalysis.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/valueflow.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkother.cpp -$(libcppdir)/checkpostfixoperator.o: ../lib/checkpostfixoperator.cpp ../lib/addoninfo.h ../lib/check.h ../lib/checkers.h ../lib/checkpostfixoperator.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h +$(libcppdir)/checkpostfixoperator.o: ../lib/checkpostfixoperator.cpp ../lib/check.h ../lib/checkers.h ../lib/checkimpl.h ../lib/checkpostfixoperator.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkpostfixoperator.cpp -$(libcppdir)/checksizeof.o: ../lib/checksizeof.cpp ../lib/addoninfo.h ../lib/check.h ../lib/checkers.h ../lib/checksizeof.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h +$(libcppdir)/checks.o: ../lib/checks.cpp ../lib/check.h ../lib/check64bit.h ../lib/checkassert.h ../lib/checkautovariables.h ../lib/checkbool.h ../lib/checkbufferoverrun.h ../lib/checkclass.h ../lib/checkcondition.h ../lib/checkexceptionsafety.h ../lib/checkfunctions.h ../lib/checkimpl.h ../lib/checkinternal.h ../lib/checkio.h ../lib/checkleakautovar.h ../lib/checkmemoryleak.h ../lib/checknullpointer.h ../lib/checkother.h ../lib/checkpostfixoperator.h ../lib/checks.h ../lib/checksizeof.h ../lib/checkstl.h ../lib/checkstring.h ../lib/checktype.h ../lib/checkuninitvar.h ../lib/checkunusedvar.h ../lib/checkvaarg.h ../lib/config.h ../lib/ctu.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/refthunk.h ../lib/standards.h ../lib/vfvalue.h + $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checks.cpp + +$(libcppdir)/checksizeof.o: ../lib/checksizeof.cpp ../lib/check.h ../lib/checkers.h ../lib/checkimpl.h ../lib/checksizeof.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checksizeof.cpp -$(libcppdir)/checkstl.o: ../lib/checkstl.cpp ../lib/addoninfo.h ../lib/astutils.h ../lib/check.h ../lib/checkers.h ../lib/checknullpointer.h ../lib/checkstl.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/pathanalysis.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/valueflow.h ../lib/vfvalue.h +$(libcppdir)/checkstl.o: ../lib/checkstl.cpp ../lib/astutils.h ../lib/calculate.h ../lib/check.h ../lib/checkers.h ../lib/checkimpl.h ../lib/checknullpointer.h ../lib/checkstl.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/pathanalysis.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/valueflow.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkstl.cpp -$(libcppdir)/checkstring.o: ../lib/checkstring.cpp ../lib/addoninfo.h ../lib/astutils.h ../lib/check.h ../lib/checkers.h ../lib/checkstring.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h +$(libcppdir)/checkstring.o: ../lib/checkstring.cpp ../lib/astutils.h ../lib/check.h ../lib/checkers.h ../lib/checkimpl.h ../lib/checkstring.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkstring.cpp -$(libcppdir)/checktype.o: ../lib/checktype.cpp ../lib/addoninfo.h ../lib/astutils.h ../lib/check.h ../lib/checkers.h ../lib/checktype.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/valueflow.h ../lib/vfvalue.h +$(libcppdir)/checktype.o: ../lib/checktype.cpp ../lib/astutils.h ../lib/check.h ../lib/checkers.h ../lib/checkimpl.h ../lib/checktype.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/valueflow.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checktype.cpp -$(libcppdir)/checkuninitvar.o: ../lib/checkuninitvar.cpp ../lib/addoninfo.h ../lib/astutils.h ../lib/check.h ../lib/checkers.h ../lib/checknullpointer.h ../lib/checkuninitvar.h ../lib/config.h ../lib/ctu.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h +$(libcppdir)/checkuninitvar.o: ../lib/checkuninitvar.cpp ../lib/astutils.h ../lib/check.h ../lib/checkers.h ../lib/checkimpl.h ../lib/checknullpointer.h ../lib/checkuninitvar.h ../lib/config.h ../lib/ctu.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkuninitvar.cpp -$(libcppdir)/checkunusedfunctions.o: ../lib/checkunusedfunctions.cpp ../externals/tinyxml2/tinyxml2.h ../lib/addoninfo.h ../lib/analyzerinfo.h ../lib/astutils.h ../lib/checkers.h ../lib/checkunusedfunctions.h ../lib/config.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h ../lib/xml.h +$(libcppdir)/checkunusedfunctions.o: ../lib/checkunusedfunctions.cpp ../externals/tinyxml2/tinyxml2.h ../lib/analyzerinfo.h ../lib/astutils.h ../lib/checkers.h ../lib/checkunusedfunctions.h ../lib/config.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h ../lib/xml.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkunusedfunctions.cpp -$(libcppdir)/checkunusedvar.o: ../lib/checkunusedvar.cpp ../lib/addoninfo.h ../lib/astutils.h ../lib/check.h ../lib/checkers.h ../lib/checkunusedvar.h ../lib/config.h ../lib/errortypes.h ../lib/fwdanalysis.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/valueflow.h ../lib/vfvalue.h +$(libcppdir)/checkunusedvar.o: ../lib/checkunusedvar.cpp ../lib/astutils.h ../lib/check.h ../lib/checkers.h ../lib/checkimpl.h ../lib/checkunusedvar.h ../lib/config.h ../lib/errortypes.h ../lib/fwdanalysis.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/valueflow.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkunusedvar.cpp -$(libcppdir)/checkvaarg.o: ../lib/checkvaarg.cpp ../lib/addoninfo.h ../lib/astutils.h ../lib/check.h ../lib/checkers.h ../lib/checkvaarg.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h +$(libcppdir)/checkvaarg.o: ../lib/checkvaarg.cpp ../lib/astutils.h ../lib/check.h ../lib/checkers.h ../lib/checkimpl.h ../lib/checkvaarg.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/checkvaarg.cpp -$(libcppdir)/clangimport.o: ../lib/clangimport.cpp ../lib/clangimport.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h +$(libcppdir)/clangimport.o: ../lib/clangimport.cpp ../lib/clangimport.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/refthunk.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/clangimport.cpp $(libcppdir)/color.o: ../lib/color.cpp ../lib/color.h ../lib/config.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/color.cpp -$(libcppdir)/cppcheck.o: ../lib/cppcheck.cpp ../externals/picojson/picojson.h ../externals/simplecpp/simplecpp.h ../lib/addoninfo.h ../lib/analyzerinfo.h ../lib/check.h ../lib/checkers.h ../lib/checkunusedfunctions.h ../lib/clangimport.h ../lib/color.h ../lib/config.h ../lib/cppcheck.h ../lib/ctu.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/filesettings.h ../lib/json.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/preprocessor.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/suppressions.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/timer.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/valueflow.h ../lib/version.h ../lib/vfvalue.h +$(libcppdir)/cppcheck.o: ../lib/cppcheck.cpp ../externals/picojson/picojson.h ../externals/simplecpp/simplecpp.h ../lib/addoninfo.h ../lib/analyzerinfo.h ../lib/check.h ../lib/checkers.h ../lib/checks.h ../lib/checkunusedfunctions.h ../lib/clangimport.h ../lib/color.h ../lib/config.h ../lib/cppcheck.h ../lib/ctu.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/filesettings.h ../lib/json.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/preprocessor.h ../lib/refthunk.h ../lib/regex.h ../lib/rule.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/suppressions.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/timer.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/valueflow.h ../lib/version.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/cppcheck.cpp -$(libcppdir)/ctu.o: ../lib/ctu.cpp ../externals/tinyxml2/tinyxml2.h ../lib/astutils.h ../lib/check.h ../lib/config.h ../lib/ctu.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h ../lib/xml.h +$(libcppdir)/ctu.o: ../lib/ctu.cpp ../externals/tinyxml2/tinyxml2.h ../lib/astutils.h ../lib/check.h ../lib/config.h ../lib/ctu.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/refthunk.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h ../lib/xml.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/ctu.cpp -$(libcppdir)/errorlogger.o: ../lib/errorlogger.cpp ../externals/tinyxml2/tinyxml2.h ../lib/addoninfo.h ../lib/check.h ../lib/checkers.h ../lib/color.h ../lib/config.h ../lib/cppcheck.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/standards.h ../lib/suppressions.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h ../lib/xml.h +$(libcppdir)/errorlogger.o: ../lib/errorlogger.cpp ../externals/tinyxml2/tinyxml2.h ../lib/check.h ../lib/checkers.h ../lib/color.h ../lib/config.h ../lib/cppcheck.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/suppressions.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h ../lib/xml.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/errorlogger.cpp $(libcppdir)/errortypes.o: ../lib/errortypes.cpp ../lib/config.h ../lib/errortypes.h ../lib/utils.h @@ -275,13 +279,13 @@ $(libcppdir)/errortypes.o: ../lib/errortypes.cpp ../lib/config.h ../lib/errortyp $(libcppdir)/findtoken.o: ../lib/findtoken.cpp ../lib/astutils.h ../lib/config.h ../lib/errortypes.h ../lib/findtoken.h ../lib/library.h ../lib/mathlib.h ../lib/smallvector.h ../lib/standards.h ../lib/templatesimplifier.h ../lib/token.h ../lib/utils.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/findtoken.cpp -$(libcppdir)/forwardanalyzer.o: ../lib/forwardanalyzer.cpp ../lib/addoninfo.h ../lib/analyzer.h ../lib/astutils.h ../lib/checkers.h ../lib/config.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/forwardanalyzer.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenlist.h ../lib/utils.h ../lib/valueptr.h ../lib/vfvalue.h +$(libcppdir)/forwardanalyzer.o: ../lib/forwardanalyzer.cpp ../lib/analyzer.h ../lib/astutils.h ../lib/checkers.h ../lib/config.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/forwardanalyzer.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenlist.h ../lib/utils.h ../lib/valueptr.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/forwardanalyzer.cpp -$(libcppdir)/fwdanalysis.o: ../lib/fwdanalysis.cpp ../lib/addoninfo.h ../lib/astutils.h ../lib/checkers.h ../lib/config.h ../lib/errortypes.h ../lib/fwdanalysis.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/utils.h ../lib/vfvalue.h +$(libcppdir)/fwdanalysis.o: ../lib/fwdanalysis.cpp ../lib/astutils.h ../lib/checkers.h ../lib/config.h ../lib/errortypes.h ../lib/fwdanalysis.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/utils.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/fwdanalysis.cpp -$(libcppdir)/importproject.o: ../lib/importproject.cpp ../externals/picojson/picojson.h ../externals/tinyxml2/tinyxml2.h ../lib/addoninfo.h ../lib/checkers.h ../lib/config.h ../lib/errortypes.h ../lib/filesettings.h ../lib/importproject.h ../lib/json.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/pathmatch.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/standards.h ../lib/suppressions.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h ../lib/xml.h +$(libcppdir)/importproject.o: ../lib/importproject.cpp ../externals/picojson/picojson.h ../externals/tinyxml2/tinyxml2.h ../lib/checkers.h ../lib/config.h ../lib/filesettings.h ../lib/importproject.h ../lib/json.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/pathmatch.h ../lib/platform.h ../lib/settings.h ../lib/standards.h ../lib/suppressions.h ../lib/utils.h ../lib/xml.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/importproject.cpp $(libcppdir)/infer.o: ../lib/infer.cpp ../lib/calculate.h ../lib/config.h ../lib/errortypes.h ../lib/infer.h ../lib/mathlib.h ../lib/smallvector.h ../lib/templatesimplifier.h ../lib/token.h ../lib/utils.h ../lib/valueptr.h ../lib/vfvalue.h @@ -290,7 +294,7 @@ $(libcppdir)/infer.o: ../lib/infer.cpp ../lib/calculate.h ../lib/config.h ../lib $(libcppdir)/keywords.o: ../lib/keywords.cpp ../lib/config.h ../lib/keywords.h ../lib/standards.h ../lib/utils.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/keywords.cpp -$(libcppdir)/library.o: ../lib/library.cpp ../externals/tinyxml2/tinyxml2.h ../lib/astutils.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h ../lib/xml.h +$(libcppdir)/library.o: ../lib/library.cpp ../externals/tinyxml2/tinyxml2.h ../lib/astutils.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/refthunk.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h ../lib/xml.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/library.cpp $(libcppdir)/mathlib.o: ../lib/mathlib.cpp ../externals/simplecpp/simplecpp.h ../lib/config.h ../lib/errortypes.h ../lib/mathlib.h ../lib/smallvector.h ../lib/templatesimplifier.h ../lib/token.h ../lib/utils.h ../lib/vfvalue.h @@ -299,7 +303,7 @@ $(libcppdir)/mathlib.o: ../lib/mathlib.cpp ../externals/simplecpp/simplecpp.h .. $(libcppdir)/path.o: ../lib/path.cpp ../externals/simplecpp/simplecpp.h ../lib/config.h ../lib/path.h ../lib/standards.h ../lib/utils.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/path.cpp -$(libcppdir)/pathanalysis.o: ../lib/pathanalysis.cpp ../lib/astutils.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/pathanalysis.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/utils.h ../lib/vfvalue.h +$(libcppdir)/pathanalysis.o: ../lib/pathanalysis.cpp ../lib/astutils.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/pathanalysis.h ../lib/refthunk.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/utils.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/pathanalysis.cpp $(libcppdir)/pathmatch.o: ../lib/pathmatch.cpp ../lib/config.h ../lib/path.h ../lib/pathmatch.h ../lib/standards.h @@ -308,55 +312,55 @@ $(libcppdir)/pathmatch.o: ../lib/pathmatch.cpp ../lib/config.h ../lib/path.h ../ $(libcppdir)/platform.o: ../lib/platform.cpp ../externals/tinyxml2/tinyxml2.h ../lib/config.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/standards.h ../lib/utils.h ../lib/xml.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/platform.cpp -$(libcppdir)/preprocessor.o: ../lib/preprocessor.cpp ../externals/simplecpp/simplecpp.h ../lib/addoninfo.h ../lib/checkers.h ../lib/config.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/preprocessor.h ../lib/regex.h ../lib/settings.h ../lib/standards.h ../lib/suppressions.h ../lib/utils.h +$(libcppdir)/preprocessor.o: ../lib/preprocessor.cpp ../externals/simplecpp/simplecpp.h ../lib/checkers.h ../lib/config.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/preprocessor.h ../lib/refthunk.h ../lib/settings.h ../lib/standards.h ../lib/suppressions.h ../lib/utils.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/preprocessor.cpp -$(libcppdir)/programmemory.o: ../lib/programmemory.cpp ../lib/addoninfo.h ../lib/astutils.h ../lib/calculate.h ../lib/checkers.h ../lib/config.h ../lib/errortypes.h ../lib/infer.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/programmemory.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenlist.h ../lib/utils.h ../lib/valueflow.h ../lib/valueptr.h ../lib/vfvalue.h +$(libcppdir)/programmemory.o: ../lib/programmemory.cpp ../lib/astutils.h ../lib/calculate.h ../lib/checkers.h ../lib/config.h ../lib/errortypes.h ../lib/infer.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/programmemory.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenlist.h ../lib/utils.h ../lib/valueflow.h ../lib/valueptr.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/programmemory.cpp $(libcppdir)/regex.o: ../lib/regex.cpp ../lib/config.h ../lib/regex.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/regex.cpp -$(libcppdir)/reverseanalyzer.o: ../lib/reverseanalyzer.cpp ../lib/addoninfo.h ../lib/analyzer.h ../lib/astutils.h ../lib/checkers.h ../lib/config.h ../lib/errortypes.h ../lib/forwardanalyzer.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/regex.h ../lib/reverseanalyzer.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/utils.h ../lib/valueptr.h ../lib/vfvalue.h +$(libcppdir)/reverseanalyzer.o: ../lib/reverseanalyzer.cpp ../lib/analyzer.h ../lib/astutils.h ../lib/checkers.h ../lib/config.h ../lib/errortypes.h ../lib/forwardanalyzer.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/refthunk.h ../lib/reverseanalyzer.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/utils.h ../lib/valueptr.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/reverseanalyzer.cpp -$(libcppdir)/sarifreport.o: ../lib/sarifreport.cpp ../externals/picojson/picojson.h ../lib/addoninfo.h ../lib/check.h ../lib/checkers.h ../lib/config.h ../lib/cppcheck.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/json.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/regex.h ../lib/sarifreport.h ../lib/settings.h ../lib/standards.h ../lib/utils.h +$(libcppdir)/sarifreport.o: ../lib/sarifreport.cpp ../externals/picojson/picojson.h ../lib/check.h ../lib/checkers.h ../lib/config.h ../lib/cppcheck.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/json.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/refthunk.h ../lib/sarifreport.h ../lib/settings.h ../lib/standards.h ../lib/utils.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/sarifreport.cpp -$(libcppdir)/settings.o: ../lib/settings.cpp ../externals/picojson/picojson.h ../lib/addoninfo.h ../lib/checkers.h ../lib/config.h ../lib/errortypes.h ../lib/json.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/standards.h ../lib/summaries.h ../lib/suppressions.h ../lib/utils.h ../lib/vfvalue.h +$(libcppdir)/settings.o: ../lib/settings.cpp ../externals/picojson/picojson.h ../lib/addoninfo.h ../lib/checkers.h ../lib/config.h ../lib/errortypes.h ../lib/json.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/rule.h ../lib/settings.h ../lib/standards.h ../lib/summaries.h ../lib/suppressions.h ../lib/utils.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/settings.cpp $(libcppdir)/standards.o: ../lib/standards.cpp ../externals/simplecpp/simplecpp.h ../lib/config.h ../lib/standards.h ../lib/utils.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/standards.cpp -$(libcppdir)/summaries.o: ../lib/summaries.cpp ../lib/addoninfo.h ../lib/analyzerinfo.h ../lib/checkers.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/summaries.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h +$(libcppdir)/summaries.o: ../lib/summaries.cpp ../lib/analyzerinfo.h ../lib/checkers.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/summaries.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/summaries.cpp -$(libcppdir)/suppressions.o: ../lib/suppressions.cpp ../externals/tinyxml2/tinyxml2.h ../lib/addoninfo.h ../lib/checkers.h ../lib/config.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/filesettings.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/pathmatch.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/standards.h ../lib/suppressions.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h ../lib/xml.h +$(libcppdir)/suppressions.o: ../lib/suppressions.cpp ../externals/tinyxml2/tinyxml2.h ../lib/addoninfo.h ../lib/checkers.h ../lib/config.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/filesettings.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/pathmatch.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/standards.h ../lib/suppressions.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h ../lib/xml.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/suppressions.cpp -$(libcppdir)/templatesimplifier.o: ../lib/templatesimplifier.cpp ../lib/addoninfo.h ../lib/checkers.h ../lib/config.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/standards.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h +$(libcppdir)/templatesimplifier.o: ../lib/templatesimplifier.cpp ../lib/checkers.h ../lib/config.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/standards.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenize.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/templatesimplifier.cpp $(libcppdir)/timer.o: ../lib/timer.cpp ../lib/config.h ../lib/timer.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/timer.cpp -$(libcppdir)/token.o: ../lib/token.cpp ../externals/simplecpp/simplecpp.h ../lib/addoninfo.h ../lib/astutils.h ../lib/checkers.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenlist.h ../lib/tokenrange.h ../lib/utils.h ../lib/valueflow.h ../lib/vfvalue.h +$(libcppdir)/token.o: ../lib/token.cpp ../externals/simplecpp/simplecpp.h ../lib/astutils.h ../lib/checkers.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenlist.h ../lib/tokenrange.h ../lib/utils.h ../lib/valueflow.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/token.cpp -$(libcppdir)/tokenlist.o: ../lib/tokenlist.cpp ../externals/simplecpp/simplecpp.h ../lib/addoninfo.h ../lib/astutils.h ../lib/checkers.h ../lib/config.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/keywords.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/standards.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h +$(libcppdir)/tokenlist.o: ../lib/tokenlist.cpp ../externals/simplecpp/simplecpp.h ../lib/astutils.h ../lib/checkers.h ../lib/config.h ../lib/errorlogger.h ../lib/errortypes.h ../lib/keywords.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/standards.h ../lib/templatesimplifier.h ../lib/token.h ../lib/tokenlist.h ../lib/utils.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/tokenlist.cpp $(libcppdir)/utils.o: ../lib/utils.cpp ../lib/config.h ../lib/utils.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/utils.cpp -$(libcppdir)/vf_analyzers.o: ../lib/vf_analyzers.cpp ../lib/addoninfo.h ../lib/analyzer.h ../lib/astutils.h ../lib/calculate.h ../lib/checkers.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/programmemory.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/utils.h ../lib/valueflow.h ../lib/valueptr.h ../lib/vf_analyzers.h ../lib/vf_common.h ../lib/vf_settokenvalue.h ../lib/vfvalue.h +$(libcppdir)/vf_analyzers.o: ../lib/vf_analyzers.cpp ../lib/analyzer.h ../lib/astutils.h ../lib/calculate.h ../lib/checkers.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/programmemory.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/utils.h ../lib/valueflow.h ../lib/valueptr.h ../lib/vf_analyzers.h ../lib/vf_common.h ../lib/vf_settokenvalue.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/vf_analyzers.cpp -$(libcppdir)/vf_common.o: ../lib/vf_common.cpp ../lib/addoninfo.h ../lib/astutils.h ../lib/checkers.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/utils.h ../lib/vf_common.h ../lib/vf_settokenvalue.h ../lib/vfvalue.h +$(libcppdir)/vf_common.o: ../lib/vf_common.cpp ../lib/astutils.h ../lib/checkers.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/path.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/utils.h ../lib/vf_common.h ../lib/vf_settokenvalue.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/vf_common.cpp -$(libcppdir)/vf_settokenvalue.o: ../lib/vf_settokenvalue.cpp ../lib/addoninfo.h ../lib/astutils.h ../lib/calculate.h ../lib/checkers.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/regex.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/utils.h ../lib/valueflow.h ../lib/vf_common.h ../lib/vf_settokenvalue.h ../lib/vfvalue.h +$(libcppdir)/vf_settokenvalue.o: ../lib/vf_settokenvalue.cpp ../lib/astutils.h ../lib/calculate.h ../lib/checkers.h ../lib/config.h ../lib/errortypes.h ../lib/library.h ../lib/mathlib.h ../lib/platform.h ../lib/refthunk.h ../lib/settings.h ../lib/smallvector.h ../lib/sourcelocation.h ../lib/standards.h ../lib/symboldatabase.h ../lib/templatesimplifier.h ../lib/token.h ../lib/utils.h ../lib/valueflow.h ../lib/vf_common.h ../lib/vf_settokenvalue.h ../lib/vfvalue.h $(CXX) ${LIB_FUZZING_ENGINE} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/vf_settokenvalue.cpp $(libcppdir)/vfvalue.o: ../lib/vfvalue.cpp ../lib/config.h ../lib/errortypes.h ../lib/mathlib.h ../lib/smallvector.h ../lib/templatesimplifier.h ../lib/token.h ../lib/utils.h ../lib/vfvalue.h diff --git a/platforms/arm64-linux.xml b/platforms/arm64-linux.xml new file mode 100644 index 00000000000..d8b6a4e7cf6 --- /dev/null +++ b/platforms/arm64-linux.xml @@ -0,0 +1,19 @@ + + + + 8 + unsigned + + 1 + 2 + 4 + 8 + 8 + 4 + 8 + 16 + 8 + 8 + 4 + + diff --git a/platforms/arm64-macos.xml b/platforms/arm64-macos.xml new file mode 100644 index 00000000000..b5713159d2d --- /dev/null +++ b/platforms/arm64-macos.xml @@ -0,0 +1,19 @@ + + + + 8 + signed + + 1 + 2 + 4 + 8 + 8 + 4 + 8 + 8 + 8 + 8 + 4 + + diff --git a/platforms/arm64-wchar_t2.xml b/platforms/arm64-wchar_t2.xml index 2b0cff4989f..32d17d926d2 100644 --- a/platforms/arm64-wchar_t2.xml +++ b/platforms/arm64-wchar_t2.xml @@ -12,7 +12,7 @@ 8 8 8 - 4 + 8 2 diff --git a/platforms/arm64-wchar_t4.xml b/platforms/arm64-wchar_t4.xml index 35183bb089c..6842ff977df 100644 --- a/platforms/arm64-wchar_t4.xml +++ b/platforms/arm64-wchar_t4.xml @@ -12,7 +12,7 @@ 8 8 8 - 4 + 8 4 diff --git a/platforms/arm64-windows.xml b/platforms/arm64-windows.xml new file mode 100644 index 00000000000..f1ea3f39daf --- /dev/null +++ b/platforms/arm64-windows.xml @@ -0,0 +1,19 @@ + + + + 8 + signed + + 1 + 2 + 4 + 4 + 8 + 4 + 8 + 8 + 8 + 8 + 2 + + diff --git a/readme.md b/readme.md index a34dad2a57a..6671fdd4827 100644 --- a/readme.md +++ b/readme.md @@ -1,9 +1,10 @@ -# **Cppcheck** +# **Cppcheck** + +NOTE: This repository was recently moved from https://github.com/danmar/cppcheck to https://github.com/cppcheck-opensource/cppcheck. Please make sure to update all your references to it accordingly. |release-windows|OSS-Fuzz|Coverity Scan Build Status|include-what-you-use|License| |:--:|:--:|:--:|:--:|:--:| -|[![release-windows](https://github.com/danmar/cppcheck/actions/workflows/release-windows.yml/badge.svg?branch=main)](https://github.com/danmar/cppcheck/actions/workflows/release-windows.yml)|[![OSS-Fuzz](https://oss-fuzz-build-logs.storage.googleapis.com/badges/cppcheck.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:cppcheck)|[![Coverity Scan Build Status](https://img.shields.io/coverity/scan/512.svg)](https://scan.coverity.com/projects/512)|[![include-what-you-use](https://github.com/danmar/cppcheck/actions/workflows/iwyu.yml/badge.svg?branch=main)](https://github.com/danmar/cppcheck/actions/workflows/iwyu.yml)|[![License](https://img.shields.io/badge/license-GPL3.0-blue.svg)](https://opensource.org/licenses/GPL-3.0) - +|[![release-windows](https://github.com/cppcheck-opensource/cppcheck/actions/workflows/release-windows.yml/badge.svg?branch=main)](https://github.com/cppcheck-opensource/cppcheck/actions/workflows/release-windows.yml)|[![OSS-Fuzz](https://oss-fuzz-build-logs.storage.googleapis.com/badges/cppcheck.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:cppcheck)|[![Coverity Scan Build Status](https://img.shields.io/coverity/scan/512.svg)](https://scan.coverity.com/projects/512)|[![include-what-you-use](https://github.com/cppcheck-opensource/cppcheck/actions/workflows/iwyu.yml/badge.svg?branch=main)](https://github.com/cppcheck-opensource/cppcheck/actions/workflows/iwyu.yml)|[![License](https://img.shields.io/badge/license-GPL3.0-blue.svg)](https://opensource.org/licenses/GPL-3.0)| ## About the name @@ -21,7 +22,8 @@ Cppcheck is a hobby project with limited resources. You can help us by donating 1. Download (and extract) Cppcheck source code. 2. Run (Linux/MacOS example): - ``` + + ```shell cd cppcheck/ python3 -m venv .venv source .venv/bin/activate @@ -30,9 +32,9 @@ Cppcheck is a hobby project with limited resources. You can help us by donating ./tools/donate-cpu.py ``` -The script will analyse debian source code and upload the results to a cppcheck server. We need these results both to improve Cppcheck and to detect regressions. +The script will analyse Debian source code and upload the results to a Cppcheck server. We need these results both to improve Cppcheck and to detect regressions. -You can stop the script whenever you like with Ctrl C. +You can stop the script whenever you like with Ctrl+C. ## Compiling @@ -43,6 +45,7 @@ To build the GUI application, you need to use the CMake build system. When building the command line tool, [PCRE](http://www.pcre.org/) is optional. It is used if you build with rules. There are multiple compilation choices: + * CMake - cross platform build tool * (Windows) Visual Studio * (Windows) Qt Creator + MinGW @@ -62,22 +65,22 @@ cmake --build build ``` If you want to compile the GUI you can use the flag. --DBUILD_GUI=ON +`-DBUILD_GUI=ON` For rules support (requires pcre) use the flag. --DHAVE_RULES=ON +`-DHAVE_RULES=ON` For release builds it is recommended that you use: --DUSE_MATCHCOMPILER=ON +`-DUSE_MATCHCOMPILER=ON` For building the tests use the flag. --DBUILD_TESTING=ON +`-DBUILD_TESTING=ON` -Using cmake you can generate project files for Visual Studio,XCode,etc. +Using CMake you can generate project files for Visual Studio, XCode, etc. #### Building a specific configuration -For single-configuration generators (like "Unix Makefiles") you can generate and build a specific configuration (e.g. "RelWithDebInfo") using: +For single-configuration generators (like "Unix Makefiles") you can generate and build a specific configuration (e.g. "`RelWithDebInfo`") using: ```shell cmake -S . -B build_RelWithDebInfo -DCMAKE_BUILD_TYPE=RelWithDebInfo .. @@ -93,88 +96,26 @@ cmake --build build --config RelWithDebInfo ### Visual Studio -Use the cppcheck.sln file. The file is configured for Visual Studio 2019, but the platform toolset can be changed easily to older or newer versions. The solution contains platform targets for both x86 and x64. +Use the `cppcheck.sln` file. The file is configured for Visual Studio 2019, but the platform toolset can be changed easily to older or newer versions. The solution contains platform targets for both x86 and x64. -To compile with rules, select "Release-PCRE" or "Debug-PCRE" configuration. pcre.lib (pcre64.lib for x64 builds) and pcre.h are expected to be in /externals then. A current version of PCRE for Visual Studio can be obtained using [vcpkg](https://github.com/microsoft/vcpkg). +To compile with rules, select "`Release-PCRE`" or "`Debug-PCRE`" configuration. `pcre.lib` (`pcre64.lib` for x64 builds) and `pcre.h` are expected to be in `/externals` then. A current version of PCRE for Visual Studio can be obtained using [vcpkg](https://github.com/microsoft/vcpkg). ### Visual Studio (from command line) -If you do not wish to use the Visual Studio IDE, you can compile cppcheck from the command line the following command. +If you do not wish to use the Visual Studio IDE, you can compile Cppcheck from the command line the following command. ```shell msbuild cppcheck.sln ``` -### VS Code (on Windows) - -Install MSYS2 to get GNU toolchain with g++ and gdb (https://www.msys2.org/). -Create a settings.json file in the .vscode folder with the following content (adjust path as necessary): - -``` -{ - "terminal.integrated.shell.windows": "C:\\msys64\\usr\\bin\\bash.exe", - "terminal.integrated.shellArgs.windows": [ - "--login", - ], - "terminal.integrated.env.windows": { - "CHERE_INVOKING": "1", - "MSYSTEM": "MINGW64", - } -} -``` - -Run "make" in the terminal to build cppcheck. +### VS Code -For debugging create a launch.json file in the .vscode folder with the following content, which covers configuration for debugging cppcheck and misra.py: - -``` -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "cppcheck", - "type": "cppdbg", - "request": "launch", - "program": "${workspaceFolder}/cppcheck.exe", - "args": [ - "--dump", - "${workspaceFolder}/addons/test/misra/misra-test.c" - ], - "stopAtEntry": false, - "cwd": "${workspaceFolder}", - "environment": [], - "externalConsole": true, - "MIMode": "gdb", - "miDebuggerPath": "C:/msys64/mingw64/bin/gdb.exe", - "setupCommands": [ - { - "description": "Enable pretty-printing for gdb", - "text": "-enable-pretty-printing", - "ignoreFailures": true - } - ] - }, - { - "name": "misra.py", - "type": "python", - "request": "launch", - "program": "${workspaceFolder}/addons/misra.py", - "console": "integratedTerminal", - "args": [ - "${workspaceFolder}/addons/test/misra/misra-test.c.dump" - ] - } - ] -} -``` +Cppcheck Official is an extension officially supported by the cppcheck team allowing you to easily use cppcheck in VS Code. You can find it in VS Code Marketplace through the extension tab in your IDE. For instructions on how to set it up and use it, see the readme on the github page: ### Qt Creator + MinGW The PCRE dll is needed to build the CLI. It can be downloaded here: -http://software-download.name/pcre-library-windows/ + ### GNU compilers @@ -204,22 +145,22 @@ g++ -o cppcheck -std=c++11 -Iexternals -Iexternals/simplecpp -Iexternals/tinyxml #### Flags -- `MATCHCOMPILER=yes` +* `MATCHCOMPILER=yes` Several `Token` matching patterns are converted into more efficient C++ code at compile time (requires Python to be installed). -- `FILESDIR=/usr/share/cppcheck` - Specifies the folder where cppcheck files (addons, cfg, platform) are installed to. +* `FILESDIR=/usr/share/cppcheck` + Specifies the folder where Cppcheck files (addons, cfg, platform) are installed to. -- `HAVE_RULES=yes` +* `HAVE_RULES=yes` Enables rules (requires PCRE to be installed). -- `CXXOPTS="-O2"` +* `CXXOPTS="-O2"` Enables most compiler optimizations. -- `CPPOPTS="-DNDEBUG"` +* `CPPOPTS="-DNDEBUG"` Disables assertions. -- `HAVE_BOOST=yes` +* `HAVE_BOOST=yes` Enables usage of more efficient container from Boost (requires Boost to be installed). ### MinGW @@ -230,7 +171,7 @@ mingw32-make If you encounter the following error with `MATCHCOMPILER=yes` you need to specify your Python interpreter via `PYTHON_INTERPRETER`. -``` +```text process_begin: CreateProcess(NULL, which python3, ...) failed. makefile:24: pipe: No error process_begin: CreateProcess(NULL, which python, ...) failed. @@ -238,10 +179,10 @@ makefile:27: pipe: No error makefile:30: *** Did not find a Python interpreter. Stop. ``` -### Other Compiler/IDE +### Other compiler/IDE 1. Create an empty project file / makefile. -2. Add all cpp files in the cppcheck cli and lib folders to the project file / makefile. +2. Add all cpp files in the Cppcheck cli and lib folders to the project file / makefile. 3. Add all cpp files in the externals folders to the project file / makefile. 4. Compile. @@ -255,17 +196,17 @@ mv cppcheck cppcheck.exe ## Packages -Besides building yourself on the platform of your choice there are also several ways to obtain pre-built packages.
+Besides building yourself on the platform of your choice there are also several ways to obtain pre-built packages. ### Official Official packages are maintained by the Cppcheck team. -- (Windows) An official Windows installer is available via the official Cppcheck SourceForge page: https://cppcheck.sourceforge.io. -- (Windows) Official builds of the current development versions are available via the [release-windows](https://github.com/danmar/cppcheck/actions/workflows/release-windows.yml) workflow. They are built nightly for the `main` branch and for each commit for release branches. As these are development versions please refrain from using these in production environments! - - A portable package (i.e. does not require installation) is available as the `portable` artifact. This is still a work-in-progress - see https://trac.cppcheck.net/ticket/10771 for details. - - An installer is available via the `installer` artifact. -- (Multi-Platform) A premium version with additional features provided by the original author of Cppcheck is available for purchase via https://www.cppcheck.com. +* (Windows) An official Windows installer is available via the official Cppcheck SourceForge page: . +* (Windows) Official builds of the current development versions are available via the [release-windows](https://github.com/cppcheck-opensource/cppcheck/actions/workflows/release-windows.yml) workflow. They are built nightly for the `main` branch and for each commit for release branches. As these are development versions please refrain from using these in production environments! + * A portable package (i.e. does not require installation) is available as the `portable` artifact. This is still a work-in-progress - see for details. + * An installer is available via the `installer` artifact. +* (Multi-Platform) A premium version with additional features provided by the original author of Cppcheck is available for purchase via . ### Third-party @@ -273,23 +214,23 @@ Third-party packages are ***not*** maintained by the Cppcheck team but their res *Note:* The following list is purely informational and listed in no particular order. -*Note:* Please always try to obtain the package from the primary official source of your operating system/distro first and make sure you are getting the latest released/tagged version (see https://github.com/danmar/cppcheck/tags). Some packages might not carry the latest patch version though. +*Note:* Please always try to obtain the package from the primary official source of your operating system/distro first and make sure you are getting the latest released/tagged version (see ). Some packages might not carry the latest patch version though. *Note:* Some issues might be related to additional patches carried by the builds in these packages or by the packaging itself. Please try to verify the issue with an official build before reporting it upstream. Otherwise you might need toreport it to the respective maintainer of the package. -- (Windows / Outdated) A portable package is available via https://portableapps.com/apps/development/cppcheck-portable. -- (Windows / Outdated) A package is available via https://community.chocolatey.org/packages/cppcheck. -- (Windows / Outdated) A package is available via https://winget.run/pkg/Cppcheck/Cppcheck. -- (Windows) A package is available via https://scoop.sh/#/apps?q=cppcheck. -- (Linux/Unix) Many major distros offer Cppcheck packages via their integrated package managers (`yum`, `apt`, `pacman`, etc.). See https://pkgs.org/search/?q=cppcheck or https://repology.org/project/cppcheck for an overview. -- (Linux/Unix) Unless you are using a "rolling" distro, it is likely that they are not carrying the latest version. There are several external (mainly unsupported) repositories like AUR (ArchLinux), PPA (ubuntu), EPEL (CentOS/Fedora) etc. which might provide up-to-date packages. -- (Linux/Unix / Outdated) The Canonical Snapcraft packages (https://snapcraft.io/cppcheck / https://snapcraft.io/cppcheckgui) are unmaintained and contain very old (development) versions. Please refrain from using them! See https://trac.cppcheck.net/ticket/11641 for more details. -- (MacOS) A package is available via Homebrew (`brew`). See https://formulae.brew.sh/formula/cppcheck. -- (MacOS) A package is available via https://ports.macports.org/port/cppcheck. -- (Multi-Platform) A package is available via https://anaconda.org/conda-forge/cppcheck. -- (Multi-Platform) A package is available via https://conan.io/center/recipes/cppcheck. -- Packages are also available from various download portals (mainly the Windows installer - sometimes re-packaged). +* (Windows / Outdated) A portable package is available via . +* (Windows / Outdated) A package is available via . +* (Windows / Outdated) A package is available via . +* (Windows) A package is available via . +* (Linux/Unix) Many major distros offer Cppcheck packages via their integrated package managers (`yum`, `apt`, `pacman`, etc.). See or for an overview. +* (Linux/Unix) Unless you are using a "rolling" distro, it is likely that they are not carrying the latest version. There are several external (mainly unsupported) repositories like AUR (ArchLinux), PPA (Ubuntu), EPEL (CentOS/Fedora) etc. which might provide up-to-date packages. +* (Linux/Unix / Outdated) The Canonical Snapcraft packages ( / ) are unmaintained and contain very old (development) versions. Please refrain from using them! See for more details. +* (MacOS) A package is available via Homebrew (`brew`). See . +* (MacOS) A package is available via . +* (Multi-Platform) A package is available via . +* (Multi-Platform) A package is available via . +* Packages are also available from various download portals (mainly the Windows installer - sometimes re-packaged). ## Webpage -https://cppcheck.sourceforge.io/ + diff --git a/readmeja.md b/readmeja.md index 6d9dae60c95..7299d61745c 100644 --- a/readmeja.md +++ b/readmeja.md @@ -2,7 +2,7 @@ | Linux ビルド状態 | Windows ビルド状態 | Coverity Scan Build 状態 | |:--:|:--:|:--:| -| [![Linux ビルド状態](https://img.shields.io/travis/danmar/cppcheck/master.svg?label=Linux%20build)](https://travis-ci.org/danmar/cppcheck) | [![Windows ビルド状態](https://img.shields.io/appveyor/ci/danmar/cppcheck/master.svg?label=Windows%20build)](https://ci.appveyor.com/project/danmar/cppcheck/branch/master) | [![Coverity Scan Build 状態](https://img.shields.io/coverity/scan/512.svg)](https://scan.coverity.com/projects/512) | +| [![Linux ビルド状態](https://img.shields.io/travis/cppcheck-opensource/cppcheck/master.svg?label=Linux%20build)](https://travis-ci.org/cppcheck-opensource/cppcheck) | [![Windows ビルド状態](https://img.shields.io/appveyor/ci/cppcheck-opensource/cppcheck/master.svg?label=Windows%20build)](https://ci.appveyor.com/project/cppcheck-opensource/cppcheck/branch/master) | [![Coverity Scan Build 状態](https://img.shields.io/coverity/scan/512.svg)](https://scan.coverity.com/projects/512) | ## 名前について diff --git a/releasenotes.txt b/releasenotes.txt index 6a5d917fa10..33fd4106800 100644 --- a/releasenotes.txt +++ b/releasenotes.txt @@ -1,11 +1,11 @@ -Release Notes for Cppcheck 2.21 +Release Notes for Cppcheck 2.23 Major bug fixes & crashes: - New checks: -- MISRA C 2012 rule 10.3 now warns on assigning integer literals 0 and 1 to bool in C99 and later while preserving the existing C89 behavior. +- C/C++ support: - @@ -13,14 +13,15 @@ C/C++ support: GUI: - -Changed interface: +Suppressions: - -Infrastructure & dependencies: +Import project: - +Infrastructure & dependencies: +- Added arm64 platform files arm64-windows.xml, arm64-linux.xml and arm64-macos.xml (Apple Silicon). + These describe the default setup for that OS. The arm64-wchar_t2.xml and arm64-wchar_t4.xml files are kept. + Other: -- Make it possible to specify the regular expression engine using the `engine` element in a rule XML. -- Added CLI option `--exitcode-suppress` to specify an error ID which should not result in a non-zero exitcode. -- - diff --git a/selfcheck.sh b/selfcheck.sh index 7f919334c18..e9a39ec43cb 100755 --- a/selfcheck.sh +++ b/selfcheck.sh @@ -1,14 +1,16 @@ #!/bin/sh +cmake_output=cmake.output +selfcheck_options_extra="$1" + +cppcheck_bin=./cppcheck + selfcheck_options="-q -j$(nproc) --std=c++11 --template=selfcheck --showtime=file-total -D__GNUC__ --error-exitcode=1 --inline-suppr --suppressions-list=.selfcheck_suppressions --library=gnu --inconclusive --enable=style,performance,portability,warning,missingInclude,information --exception-handling --debug-warnings --check-level=exhaustive" +selfcheck_options="$selfcheck_options $selfcheck_options_extra" cppcheck_options="-D__CPPCHECK__ -DCHECK_INTERNAL -DHAVE_RULES --library=cppcheck-lib -Ilib -Iexternals/simplecpp/ -Iexternals/tinyxml2" -gui_options="-DQT_VERSION=0x060000 -DQ_MOC_OUTPUT_REVISION=68 -DQT_CHARTS_LIB -DQT_MOC_HAS_STRINGDATA --library=qt" +qt_options="--library=qt -DQT_VERSION=0x060000 -DQ_MOC_OUTPUT_REVISION=68 -DQT_MOC_HAS_STRINGDATA" # TODO: use 69 as revision naming_options="--addon-python=$(command -v python) --addon=naming.json" -if [ -n "$1" ]; then - selfcheck_options="$selfcheck_options $1" -fi - mkdir_cmd=$(command -v mkdir) rm_cmd=$(command -v rm) @@ -17,21 +19,51 @@ export PATH= ec=0 -# self check externals -./cppcheck $selfcheck_options externals || ec=1 -# self check lib/cli +$cppcheck_bin $selfcheck_options \ + externals \ + || ec=1 + $mkdir_cmd b1 -./cppcheck $selfcheck_options $cppcheck_options --cppcheck-build-dir=b1 $naming_options frontend || ec=1 -./cppcheck $selfcheck_options $cppcheck_options --cppcheck-build-dir=b1 $naming_options -Ifrontend cli || ec=1 -./cppcheck $selfcheck_options $cppcheck_options --cppcheck-build-dir=b1 $naming_options --enable=internal lib || ec=1 -# check gui with qt settings + +$cppcheck_bin $selfcheck_options $cppcheck_options $naming_options \ + --cppcheck-build-dir=b1 \ + frontend \ + || ec=1 + +$cppcheck_bin $selfcheck_options $cppcheck_options $naming_options \ + --cppcheck-build-dir=b1 \ + -Ifrontend \ + cli \ + || ec=1 + +$cppcheck_bin $selfcheck_options $cppcheck_options $naming_options \ + --cppcheck-build-dir=b1 --enable=internal \ + lib \ + || ec=1 + $mkdir_cmd b2 -./cppcheck $selfcheck_options $cppcheck_options $gui_options --cppcheck-build-dir=b2 $naming_options -Icmake.output/gui -Ifrontend -Igui gui/*.cpp cmake.output/gui || ec=1 -# self check test and tools -./cppcheck $selfcheck_options $cppcheck_options -Ifrontend -Icli test/*.cpp || ec=1 -./cppcheck $selfcheck_options $cppcheck_options -Icli tools/dmake/*.cpp || ec=1 -# triage -./cppcheck $selfcheck_options $cppcheck_options $gui_options -Icmake.output/tools/triage -Igui tools/triage/*.cpp cmake.output/tools/triage || ec=1 + +$cppcheck_bin $selfcheck_options $cppcheck_options $naming_options $qt_options \ + --cppcheck-build-dir=b2 \ + -DQT_CHARTS_LIB \ + -I$cmake_output/gui -Ifrontend -Igui \ + gui/*.cpp $cmake_output/gui \ + || ec=1 + +$cppcheck_bin $selfcheck_options $cppcheck_options \ + -Ifrontend -Icli \ + test/*.cpp \ + || ec=1 + +$cppcheck_bin $selfcheck_options $cppcheck_options \ + -Icli \ + tools/dmake/*.cpp \ + || ec=1 + +$cppcheck_bin $selfcheck_options $cppcheck_options $qt_options \ + -I$cmake_output/tools/triage -Igui \ + tools/triage/*.cpp $cmake_output/tools/triage \ + || ec=1 $rm_cmd -rf b2 $rm_cmd -rf b1 diff --git a/selfcheck_san.sh b/selfcheck_san.sh index 5c8fef353c2..92e34ba6e4f 100755 --- a/selfcheck_san.sh +++ b/selfcheck_san.sh @@ -3,6 +3,8 @@ cmake_output="$1" selfcheck_options_extra="$2" +cppcheck_bin=$cmake_output/bin/cppcheck + selfcheck_options="-q -j$(nproc) --std=c++11 --template=selfcheck --showtime=file-total -D__GNUC__ --error-exitcode=1 --inline-suppr --suppressions-list=.selfcheck_suppressions --library=gnu --inconclusive --enable=style,performance,portability,warning,missingInclude,information --exception-handling --debug-warnings --check-level=exhaustive" selfcheck_options="$selfcheck_options $selfcheck_options_extra" cppcheck_options="-D__CPPCHECK__ -DCHECK_INTERNAL -DHAVE_RULES --library=cppcheck-lib -Ilib -Iexternals/simplecpp/ -Iexternals/tinyxml2" @@ -15,42 +17,42 @@ export PATH= ec=0 -$cmake_output/bin/cppcheck $selfcheck_options \ +$cppcheck_bin $selfcheck_options \ externals \ || ec=1 -$cmake_output/bin/cppcheck $selfcheck_options $cppcheck_options $naming_options \ +$cppcheck_bin $selfcheck_options $cppcheck_options $naming_options \ frontend \ || ec=1 -$cmake_output/bin/cppcheck $selfcheck_options $cppcheck_options $naming_options \ +$cppcheck_bin $selfcheck_options $cppcheck_options $naming_options \ -Ifrontend \ cli \ || ec=1 -$cmake_output/bin/cppcheck $selfcheck_options $cppcheck_options $naming_options \ +$cppcheck_bin $selfcheck_options $cppcheck_options $naming_options \ --enable=internal \ lib \ || ec=1 -$cmake_output/bin/cppcheck $selfcheck_options $cppcheck_options $naming_options $qt_options \ +$cppcheck_bin $selfcheck_options $cppcheck_options $naming_options $qt_options \ --suppress=constVariablePointer:*/moc_*.cpp \ -DQT_CHARTS_LIB \ -I$cmake_output/gui -Ifrontend -Igui \ gui/*.cpp $cmake_output/gui \ || ec=1 -$cmake_output/bin/cppcheck $selfcheck_options $cppcheck_options \ - -Icli -Ifrontend \ +$cppcheck_bin $selfcheck_options $cppcheck_options \ + -Ifrontend -Icli \ test/*.cpp \ || ec=1 -$cmake_output/bin/cppcheck $selfcheck_options $cppcheck_options \ +$cppcheck_bin $selfcheck_options $cppcheck_options \ -Icli \ tools/dmake/*.cpp \ || ec=1 -$cmake_output/bin/cppcheck $selfcheck_options $cppcheck_options $qt_options \ +$cppcheck_bin $selfcheck_options $cppcheck_options $qt_options \ -I$cmake_output/tools/triage -Igui \ tools/triage/*.cpp $cmake_output/tools/triage \ || ec=1 diff --git a/snap/gui/cppcheck-gui.desktop b/snap/gui/cppcheck-gui.desktop deleted file mode 100644 index bd540376da2..00000000000 --- a/snap/gui/cppcheck-gui.desktop +++ /dev/null @@ -1,9 +0,0 @@ -[Desktop Entry] -Type=Application -Name=Cppcheck -Comment=A tool for static C/C++ code analysis -Exec=cppcheck-gui -Icon=${SNAP}/meta/gui/cppcheck-gui.png -Terminal=true -StartupNotify=true -Categories=Development;Debugger;Qt; diff --git a/snap/gui/cppcheck-gui.png b/snap/gui/cppcheck-gui.png deleted file mode 100644 index 25d7aef5f31..00000000000 Binary files a/snap/gui/cppcheck-gui.png and /dev/null differ diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml deleted file mode 100644 index 030a2048afe..00000000000 --- a/snap/snapcraft.yaml +++ /dev/null @@ -1,50 +0,0 @@ -name: cppcheckgui -version: '1.81.99' -summary: A tool for static C/C++ code analysis -description: | - A tool for static C/C++ code analysis -grade: stable -confinement: strict -icon: snap/gui/cppcheck-gui.png -type: app - -apps: - cppcheckgui: - command: desktop-launch ${SNAP}/bin/cppcheck-gui - plugs: [home, unity7, x11, network-bind, network-control] - -parts: - cppcheckgui: - source-type: git - plugin: cmake - configflags: - - -DBUILD_GUI=ON - after: [desktop-qt5] - build-packages: - # A list of Ubuntu packages to be installed on the host to aid in building the part. - # These packages will not go into the final snap. - - build-essential - - qt5-default - - qtbase5-dev - - dpkg-dev - # For Qt5LinguistTools - - qttools5-dev - - qttools5-dev-tools - desktop-qt5: - stage-packages: - # A set of Ubuntu packages to be downloaded and unpacked to join the part before it’s built. - # Note that these packages are not installed on the host. - # Like the rest of the part, all files from these packages will make it into the final snap unless filtered out via the prime keyword. - - libqt5gui5 - - libqt5svg5 # for loading icon themes which are svg - - libtiff5-dev - - libjpeg8-dev - - libxkbcommon0 - - ttf-ubuntu-font-family - - dmz-cursor-theme - - light-themes - - shared-mime-info - - libgdk-pixbuf2.0-0 - - locales-all - - xcb - - libxcb1 diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 14f3a8eac17..f1e59e17daf 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -47,6 +47,8 @@ if (BUILD_TESTING) if (REGISTER_TESTS) # CMAKE_MATCH_ usage for if (MATCHES) requires CMake 3.9 + add_subdirectory(addon) + find_package(Threads REQUIRED) include(ProcessorCount) ProcessorCount(N) diff --git a/test/addon/CMakeLists.txt b/test/addon/CMakeLists.txt new file mode 100644 index 00000000000..bb04afb6c51 --- /dev/null +++ b/test/addon/CMakeLists.txt @@ -0,0 +1,41 @@ +if (NOT Python_Interpreter_FOUND) + message(WARNING "Python interpreter not found - skipping addon tests.") + return() +endif() + +# creating a virtual environment needs the venv and ensurepip modules - some +# distributions ship them separately from the interpreter (e.g. the Debian/Ubuntu +# python3-venv package) +execute_process(COMMAND ${Python_EXECUTABLE} -c "import venv, ensurepip" + RESULT_VARIABLE PYTHON_VENV_RESULT + OUTPUT_QUIET + ERROR_QUIET) +if (NOT PYTHON_VENV_RESULT EQUAL 0) + message(WARNING "Python venv module not available (e.g. install the python3-venv package) - skipping addon tests.") + return() +endif() + +set(VENV_DIR ${CMAKE_CURRENT_BINARY_DIR}/venv) +if (WIN32) + set(VENV_PYTHON ${VENV_DIR}/Scripts/python.exe) +else() + set(VENV_PYTHON ${VENV_DIR}/bin/python) +endif() + +# fixture: create a virtual environment and install the python dependencies into it +add_test(NAME addon-venv-create + COMMAND ${Python_EXECUTABLE} -m venv ${VENV_DIR}) +set_tests_properties(addon-venv-create PROPERTIES + FIXTURES_SETUP addon-venv-dir) + +add_test(NAME addon-venv-install + COMMAND ${VENV_PYTHON} -m pip install -r ${CMAKE_CURRENT_SOURCE_DIR}/requirements.txt) +set_tests_properties(addon-venv-install PROPERTIES + FIXTURES_REQUIRED addon-venv-dir + FIXTURES_SETUP addon-venv + TIMEOUT 300) + +add_test(NAME addon-cppcheckdata + COMMAND ${VENV_PYTHON} -m pytest --cppcheck-binary=$ ${CMAKE_CURRENT_SOURCE_DIR}) +set_tests_properties(addon-cppcheckdata PROPERTIES + FIXTURES_REQUIRED addon-venv) diff --git a/test/addon/conftest.py b/test/addon/conftest.py new file mode 100644 index 00000000000..87012983951 --- /dev/null +++ b/test/addon/conftest.py @@ -0,0 +1,201 @@ +"""pytest configuration for the addon tests. + +The tests exercise addons/cppcheckdata.py against dump files that are +generated on the fly with the cppcheck binary given by --cppcheck-binary. +""" +import os +import shutil +import subprocess +import sys + +import pytest + +# Make 'import cppcheckdata' resolve to /addons/cppcheckdata.py +_ADDONS_DIR = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', 'addons')) +if _ADDONS_DIR not in sys.path: + sys.path.insert(0, _ADDONS_DIR) + + +def pytest_addoption(parser): + parser.addoption('--cppcheck-binary', + default='cppcheck', + help='path to the cppcheck binary used to generate dump files ' + '(default: cppcheck found in PATH)') + + +@pytest.fixture(scope='session') +def cppcheck_binary(request): + binary = request.config.getoption('--cppcheck-binary') + resolved = shutil.which(binary) + if resolved is None: + pytest.fail("cppcheck binary '%s' not found - point --cppcheck-binary at a cppcheck executable" % binary) + return os.path.abspath(resolved) + + +class DumpFactory: + """Runs 'cppcheck --dump' on a source snippet and parses the result.""" + + def __init__(self, binary, tmp_path_factory): + self.binary = binary + self.tmp_path_factory = tmp_path_factory + + def create(self, code, filename='test.c', extra_args=()): + """Write the code to a file, dump it and return the dump file path.""" + directory = self.tmp_path_factory.mktemp('cppcheckdata') + path = directory / filename + path.write_text(code) + cmd = [self.binary, '--dump', '--quiet', str(path)] + list(extra_args) + proc = subprocess.run(cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=True, + universal_newlines=True) + assert proc.returncode == 0, \ + 'cppcheck failed with exit code %d:\n%s\n%s' % (proc.returncode, proc.stdout, proc.stderr) + return str(path) + '.dump' + + def parse(self, code, filename='test.c', extra_args=()): + """Write the code to a file, dump it and return the parsed CppcheckData.""" + import cppcheckdata + return cppcheckdata.parsedump(self.create(code, filename, extra_args)) + + +@pytest.fixture(scope='session') +def dump_factory(cppcheck_binary, tmp_path_factory): + return DumpFactory(cppcheck_binary, tmp_path_factory) + + +SAMPLE_C = """#define ANSWER 42 +static int add(int a, int b) +{ + return a + b; +} + +double half(double d) +{ + return d / 2.0; +} + +int main(void) +{ + int x = ANSWER; + int arr[10]; + arr[0] = add(x, 1); + int neg = -x; + return arr[0] + neg; +} +""" + +SAMPLE_CPP = """namespace ns { + int twice(int v) { return 2 * v; } +} + +class Shape { +public: + virtual ~Shape() {} + virtual double area() const = 0; +protected: + double mScale; +}; + +enum Color { RED, GREEN }; + +const int limit = 5; +bool flag = true; +const char *msg = "hello"; +char ch = 'x'; + +int run() +{ + Color color = RED; + if (flag && limit > 1) { + return ns::twice(21); + } + return static_cast(color); +} +""" + +MULTI_CFG_C = """typedef int myint; +typedef float myfloat; +#if defined(FOO) && FOO > 1 +int foo(void) { return 1; } +#endif +myint bar(void) { myint y = 3; return y; } +""" + +MATCH_C = """struct Point { + int x; + int y; +}; + +int calc(int a, int b) +{ + int bit_or = a | b; + int log_or = a || b; + int mod = a % b; + int mul = a * b; + int not_a = !a; + int neq = a != b; + int arr[3]; + arr[0] = a; + a += 1; + if (a > b) { + return a; + } + return calc(a, b); +} +""" + +MATCH_CPP = """class Widget {}; + +bool use(int i, int j) +{ + std::vector v; + bool less = i < j; + return less && v.empty(); +} +""" + + +@pytest.fixture(scope='session') +def sample_data(dump_factory): + """Parsed dump of the canonical C sample.""" + return dump_factory.parse(SAMPLE_C, filename='sample.c') + + +@pytest.fixture(scope='session') +def sample_cfg(sample_data): + cfgs = sample_data.configurations + assert len(cfgs) == 1 + return cfgs[0] + + +@pytest.fixture(scope='session') +def sample_cpp_data(dump_factory): + """Parsed dump of the canonical C++ sample.""" + return dump_factory.parse(SAMPLE_CPP, filename='sample.cpp') + + +@pytest.fixture(scope='session') +def sample_cpp_cfg(sample_cpp_data): + cfgs = sample_cpp_data.configurations + assert len(cfgs) == 1 + return cfgs[0] + + +@pytest.fixture(scope='session') +def multi_cfg_data(dump_factory): + """Parsed dump of a file with two preprocessor configurations.""" + return dump_factory.parse(MULTI_CFG_C, filename='multi.c') + + +@pytest.fixture(scope='session') +def match_cfg(dump_factory): + """Configuration of a C sample covering the match() pattern syntax.""" + return dump_factory.parse(MATCH_C, filename='match.c').configurations[0] + + +@pytest.fixture(scope='session') +def match_cpp_cfg(dump_factory): + """Configuration of a C++ sample with linked '<' tokens for match().""" + return dump_factory.parse(MATCH_CPP, filename='match.cpp').configurations[0] diff --git a/test/addon/requirements.txt b/test/addon/requirements.txt new file mode 100644 index 00000000000..e079f8a6038 --- /dev/null +++ b/test/addon/requirements.txt @@ -0,0 +1 @@ +pytest diff --git a/test/addon/test_cppcheckdata.py b/test/addon/test_cppcheckdata.py new file mode 100644 index 00000000000..fc5d553e78b --- /dev/null +++ b/test/addon/test_cppcheckdata.py @@ -0,0 +1,934 @@ +"""Unit tests for addons/cppcheckdata.py + +Most tests parse dump files generated with the cppcheck binary +(see conftest.py); tests of pure helper classes construct the +objects directly from dicts (which provide the same .get() API +as the XML elements). +""" +import json +import sys + +import pytest + +import cppcheckdata + + +def find_tokens(cfg, token_str): + return [tok for tok in cfg.tokenlist if tok.str == token_str] + + +def find_token(cfg, token_str, skip=0): + tokens = find_tokens(cfg, token_str) + assert len(tokens) > skip, "token '%s' (skip=%d) not found" % (token_str, skip) + return tokens[skip] + + +def find_function(cfg, name): + for function in cfg.functions: + if function.name == name: + return function + assert False, "function '%s' not found" % name + + +def find_scope(cfg, scope_type, className=None): + for scope in cfg.scopes: + if scope.type == scope_type and (className is None or scope.className == className): + return scope + assert False, "scope '%s' not found" % scope_type + + +def find_variable(cfg, name): + for variable in cfg.variables: + if variable.nameToken and variable.nameToken.str == name: + return variable + assert False, "variable '%s' not found" % name + + +class TestCppcheckData: + def test_language(self, sample_data, sample_cpp_data): + assert sample_data.language == 'c' + assert sample_cpp_data.language == 'cpp' + + def test_platform(self, sample_data): + platform = sample_data.platform + assert platform.name + assert platform.char_bit == 8 + assert platform.short_bit >= 16 + assert platform.int_bit >= 16 + assert platform.long_bit >= 32 + assert platform.long_long_bit >= 64 + assert platform.pointer_bit > 0 + assert 'char_bit=8' in repr(platform) + + def test_files(self, sample_data): + assert len(sample_data.files) == 1 + assert sample_data.files[0].endswith('sample.c') + + def test_rawtokens(self, sample_data): + raw = sample_data.rawTokens + # the not yet preprocessed code contains the directive tokens + assert [tok.str for tok in raw[:4]] == ['#', 'define', 'ANSWER', '42'] + assert raw[-1].str == '}' + assert raw[0].file.endswith('sample.c') + assert raw[0].linenr == 1 + # next/previous chain + assert raw[0].previous is None + assert raw[-1].next is None + for i in range(len(raw) - 1): + assert raw[i].next is raw[i + 1] + assert raw[i + 1].previous is raw[i] + + def test_configurations(self, sample_data): + cfgs = sample_data.configurations + assert len(cfgs) == 1 + assert cfgs[0].name == '' + + def test_iterconfigurations(self, multi_cfg_data): + it = multi_cfg_data.iterconfigurations() + cfgs = list(it) + assert len(cfgs) == 2 + assert cfgs[0].name == '' + assert 'FOO' in cfgs[1].name + # 'foo()' is only tokenized in the FOO configuration + assert not find_tokens(cfgs[0], 'foo') + assert find_tokens(cfgs[1], 'foo') + + def test_configurations_kept(self, multi_cfg_data): + data = cppcheckdata.parsedump(multi_cfg_data.filename) + cfgs = data.configurations + assert len(cfgs) == 2 + # later accesses and iterconfigurations() return the same objects + assert data.configurations is cfgs + iterated = list(data.iterconfigurations()) + assert len(iterated) == 2 + assert iterated[0] is cfgs[0] + assert iterated[1] is cfgs[1] + + def test_iterconfigurations_not_kept(self, multi_cfg_data): + data = cppcheckdata.parsedump(multi_cfg_data.filename) + first = next(data.iterconfigurations()) + # each call parses the dump file again + assert next(data.iterconfigurations()) is not first + assert len(data.configurations) == 2 + assert data.configurations[0] is not first + + def test_standards(self, sample_cfg, sample_cpp_cfg): + assert sample_cfg.standards.c.startswith('c') + assert sample_cpp_cfg.standards.cpp.startswith('c++') + assert sample_cfg.standards.posix is False + assert 'c=' in repr(sample_cfg.standards) + + +class TestToken: + def test_tokenlist(self, sample_cfg): + code = ' '.join(tok.str for tok in sample_cfg.tokenlist) + assert 'static int add ( int a , int b )' in code + # the macro has been expanded + assert 'x = 42' in code + + def test_next_previous(self, sample_cfg): + tokens = sample_cfg.tokenlist + assert tokens[0].previous is None + assert tokens[-1].next is None + for i in range(len(tokens) - 1): + assert tokens[i].next is tokens[i + 1] + assert tokens[i + 1].previous is tokens[i] + + def test_location(self, sample_cfg): + tok = sample_cfg.tokenlist[0] + assert tok.str == 'static' + assert tok.file.endswith('sample.c') + assert tok.linenr == 2 + assert tok.column == 1 + + def test_link(self, sample_cfg): + parenthesis = find_token(sample_cfg, '(') + assert parenthesis.link.str == ')' + assert parenthesis.link.link is parenthesis + bracket = find_token(sample_cfg, '[') + assert bracket.link.str == ']' + brace = find_token(sample_cfg, '{') + assert brace.link.str == '}' + + def test_scope(self, sample_cfg): + assert sample_cfg.tokenlist[0].scope.type == 'Global' + return_tok = find_token(sample_cfg, 'return') + assert return_tok.scope.type == 'Function' + assert return_tok.scope.className == 'add' + + def test_name_number_flags(self, sample_cfg): + name_tok = find_token(sample_cfg, 'main') + assert name_tok.isName + assert not name_tok.isNumber + num_tok = find_token(sample_cfg, '42') + assert num_tok.isNumber + assert num_tok.isInt + assert not num_tok.isFloat + float_tok = find_token(sample_cfg, '2.0') + assert float_tok.isNumber + assert float_tok.isFloat + + def test_operator_flags(self, sample_cfg, sample_cpp_cfg): + plus = find_token(sample_cfg, '+') + assert plus.isOp + assert plus.isArithmeticalOp + assign = find_token(sample_cfg, '=') + assert assign.isOp + assert assign.isAssignmentOp + logical = find_token(sample_cpp_cfg, '&&') + assert logical.isOp + assert logical.isLogicalOp + comparison = find_token(sample_cpp_cfg, '>') + assert comparison.isOp + assert comparison.isComparisonOp + + def test_string_char(self, sample_cpp_cfg): + string_tok = find_token(sample_cpp_cfg, '"hello"') + assert string_tok.isString + assert string_tok.strlen == 5 + char_tok = find_token(sample_cpp_cfg, "'x'") + assert char_tok.isChar + + @pytest.mark.xfail(strict=False, + reason='Tokenizer::dump checks Token::isName() before Token::isBoolean() and ' + 'eBoolean tokens are names, so type="boolean" is never dumped') + def test_boolean(self, sample_cpp_cfg): + bool_tok = find_token(sample_cpp_cfg, 'true') + assert bool_tok.isBoolean + + def test_cast(self, sample_cpp_cfg): + # static_cast(...) is simplified to a C-style cast + assert any(tok.isCast for tok in sample_cpp_cfg.tokenlist) + + def test_macro_expansion(self, sample_cfg): + tok = find_token(sample_cfg, '42') + assert tok.isExpandedMacro + assert tok.macroName == 'ANSWER' + + def test_removed_void_parameter(self, sample_cfg): + main_tok = find_token(sample_cfg, 'main') + assert main_tok.next.isRemovedVoidParameter + + def test_splitted_var_decl(self, sample_cfg): + # 'int x = ANSWER;' is simplified to 'int x ; x = 42 ;' + assert any(tok.isSplittedVarDeclEq for tok in sample_cfg.tokenlist) + + def test_variable_and_var_id(self, sample_cfg): + x_decl = find_token(sample_cfg, 'x') + assert x_decl.varId + assert x_decl.variable.nameToken is x_decl + # all 'x' tokens refer to the same variable and varId + for tok in find_tokens(sample_cfg, 'x'): + assert tok.varId == x_decl.varId + assert tok.variable is x_decl.variable + + def test_function(self, sample_cfg): + add_call = find_token(sample_cfg, 'add', skip=1) + assert add_call.function + assert add_call.function.name == 'add' + + def test_value_type(self, sample_cfg): + x_tok = find_token(sample_cfg, 'x') + assert x_tok.valueType.type == 'int' + assert x_tok.valueType.sign == 'signed' + assert x_tok.valueType.pointer == 0 + assert x_tok.valueType.isIntegral() + assert not x_tok.valueType.isFloat() + assert not x_tok.valueType.isEnum() + d_tok = find_token(sample_cfg, 'd') + assert d_tok.valueType.type == 'double' + assert d_tok.valueType.isFloat() + # in 'arr[0] = ...' the array decays to a pointer + arr_use = find_token(sample_cfg, 'arr', skip=1) + assert arr_use.valueType.pointer == 1 + + def test_value_type_enum(self, sample_cpp_cfg): + color_tok = find_token(sample_cpp_cfg, 'color') + assert color_tok.valueType.isEnum() + assert color_tok.valueType.typeScope.className == 'Color' + + def test_ast(self, sample_cfg): + plus = find_token(sample_cfg, '+') + assert plus.astOperand1.str == 'a' + assert plus.astOperand2.str == 'b' + assert plus.astParent.str == 'return' + assert plus.isBinaryOp() + assert not plus.isUnaryOp('+') + + def test_ast_unary_op(self, sample_cfg): + # the '-' in 'int neg = -x;' + minus = find_token(sample_cfg, '-') + assert minus.isUnaryOp('-') + assert not minus.isBinaryOp() + assert minus.astOperand1.str == 'x' + assert minus.astOperand2 is None + + def test_ast_parents_top(self, sample_cfg): + a_tok = find_token(sample_cfg, 'a', skip=1) # the 'a' in 'return a + b;' + assert [tok.str for tok in a_tok.astParents()] == ['+', 'return'] + assert a_tok.astTop().str == 'return' + + def test_values(self, sample_cfg): + # 'x' has the known value 42 when it is used + x_use = find_token(sample_cfg, 'x', skip=2) + assert x_use.getKnownIntValue() == 42 + value = x_use.getValue(42) + assert value.intvalue == 42 + assert value.isKnown() + assert not value.isPossible() + assert x_use.getValue(43) is None + assert 'intvalue=42' in repr(value) + + def test_values_possible(self, sample_cfg): + # inside add() the argument 'a' has the possible value 42 + a_use = find_token(sample_cfg, 'a', skip=1) + assert a_use.getKnownIntValue() is None + value = a_use.getValue(42) + assert value is not None + assert value.isPossible() + + def test_impossible_values(self, sample_cfg): + # impossible values are separated from the possible/known ones + arr_tokens = find_tokens(sample_cfg, 'arr') + impossible = [v for tok in arr_tokens for v in tok.impossible_values] + assert impossible + assert all(v.isImpossible() for v in impossible) + possible_or_known = [v for tok in arr_tokens for v in tok.values] + assert all(not v.isImpossible() for v in possible_or_known) + + def test_forward_backward(self, sample_cfg): + add_def = find_token(sample_cfg, 'add') + strs = [tok.str for tok in add_def.forward()] + assert strs[:4] == ['add', '(', 'int', 'a'] + end = add_def.tokAt(2) + assert [tok.str for tok in add_def.forward(end=end)] == ['add', '('] + strs = [tok.str for tok in add_def.backward()] + assert strs[:3] == ['add', 'int', 'static'] + start = sample_cfg.tokenlist[0] + assert [tok.str for tok in add_def.backward(start=start)] == ['add', 'int'] + + def test_tokAt_linkAt(self, sample_cfg): + add_def = find_token(sample_cfg, 'add') + assert add_def.tokAt(0) is add_def + assert add_def.tokAt(1).str == '(' + assert add_def.tokAt(-1).str == 'int' + assert add_def.linkAt(1).str == ')' + + def test_repr(self, sample_cfg): + tok = sample_cfg.tokenlist[0] + assert "str='static'" in repr(tok) + + +class TestScope: + def test_scopes(self, sample_cfg): + types = [scope.type for scope in sample_cfg.scopes] + assert types.count('Global') == 1 + assert types.count('Function') == 3 + + def test_global_scope(self, sample_cfg): + global_scope = find_scope(sample_cfg, 'Global') + assert global_scope.nestedIn is None + assert not global_scope.isExecutable + assert len(global_scope.nestedList) == 3 + + def test_function_scope(self, sample_cfg): + scope = find_scope(sample_cfg, 'Function', className='add') + assert scope.bodyStart.str == '{' + assert scope.bodyEnd.str == '}' + assert scope.bodyStart.link is scope.bodyEnd + assert scope.nestedIn.type == 'Global' + assert scope.function.name == 'add' + assert scope.isExecutable + + def test_varlist(self, sample_cfg): + scope = find_scope(sample_cfg, 'Function', className='main') + names = [var.nameToken.str for var in scope.varlist] + assert names == ['x', 'arr', 'neg'] + + def test_cpp_scopes(self, sample_cpp_cfg): + types = {scope.type for scope in sample_cpp_cfg.scopes} + assert {'Global', 'Namespace', 'Class', 'Enum', 'Function', 'If'} <= types + assert find_scope(sample_cpp_cfg, 'Namespace', className='ns') + assert find_scope(sample_cpp_cfg, 'Class', className='Shape') + if_scope = find_scope(sample_cpp_cfg, 'If') + assert if_scope.isExecutable + class_scope = find_scope(sample_cpp_cfg, 'Class') + assert not class_scope.isExecutable + + +class TestFunction: + def test_functions(self, sample_cfg): + names = {function.name for function in sample_cfg.functions} + assert names == {'add', 'half', 'main'} + + def test_arguments(self, sample_cfg): + add = find_function(sample_cfg, 'add') + assert sorted(add.argument.keys()) == [1, 2] + assert add.argument[1].nameToken.str == 'a' + assert add.argument[2].nameToken.str == 'b' + main = find_function(sample_cfg, 'main') + assert main.argument == {} + + def test_attributes(self, sample_cfg): + add = find_function(sample_cfg, 'add') + assert add.isStatic + assert add.type == 'Function' + assert add.tokenDef.str == 'add' + assert add.token.str == 'add' + assert add.nestedIn.type == 'Global' + main = find_function(sample_cfg, 'main') + assert not main.isStatic + + def test_virtual(self, sample_cpp_cfg): + area = find_function(sample_cpp_cfg, 'area') + assert area.hasVirtualSpecifier + twice = find_function(sample_cpp_cfg, 'twice') + assert not twice.hasVirtualSpecifier + assert twice.nestedIn.className == 'ns' + + +class TestVariable: + def test_local(self, sample_cfg): + x = find_variable(sample_cfg, 'x') + assert x.access == 'Local' + assert x.isLocal + assert not x.isArgument + assert not x.isGlobal + assert not x.isArray + assert x.typeStartToken.str == 'int' + assert x.typeEndToken.str == 'int' + assert x.scope.className == 'main' + + def test_argument(self, sample_cfg): + a = find_variable(sample_cfg, 'a') + assert a.access == 'Argument' + assert a.isArgument + assert not a.isLocal + + def test_array(self, sample_cfg): + arr = find_variable(sample_cfg, 'arr') + assert arr.isArray + assert not arr.isPointer + + def test_global_const(self, sample_cpp_cfg): + limit = find_variable(sample_cpp_cfg, 'limit') + assert limit.access == 'Global' + assert limit.isGlobal + assert limit.isConst + assert not limit.isStatic + + def test_pointer(self, sample_cpp_cfg): + msg = find_variable(sample_cpp_cfg, 'msg') + assert msg.isPointer + assert not msg.isArray + + def test_class_member(self, sample_cpp_cfg): + member = find_variable(sample_cpp_cfg, 'mScale') + assert member.access == 'Protected' + assert member.isClass is False + + +class TestPreprocessor: + def test_directives(self, sample_cfg): + directives = sample_cfg.directives + assert len(directives) == 1 + assert directives[0].str == '#define ANSWER 42' + assert directives[0].file.endswith('sample.c') + assert directives[0].linenr == 1 + assert "#define ANSWER 42" in repr(directives[0]) + + def test_macro_usage(self, sample_cfg): + macros = sample_cfg.macro_usage + assert len(macros) == 1 + macro = macros[0] + assert macro.name == 'ANSWER' + assert macro.usefile.endswith('sample.c') + assert int(macro.useline) == 14 + assert macro.isKnownValue + assert "name='ANSWER'" in repr(macro) + + def test_if_conditions(self, multi_cfg_data): + cfgs = multi_cfg_data.configurations + for cfg in cfgs: + assert len(cfg.preprocessor_if_conditions) == 1 + assert cfg.preprocessor_if_conditions[0].linenr == 3 + assert cfgs[0].preprocessor_if_conditions[0].result == 0 + assert cfgs[1].preprocessor_if_conditions[0].result == 1 + assert 'result=' in repr(cfgs[0].preprocessor_if_conditions[0]) + + def test_typedef_info(self, multi_cfg_data): + cfg = multi_cfg_data.configurations[0] + typedefs = {info.name: info for info in cfg.typedefInfo} + assert set(typedefs.keys()) == {'myint', 'myfloat'} + assert typedefs['myint'].used + assert not typedefs['myfloat'].used + assert typedefs['myint'].linenr == 1 + + +class TestHelperFunctions: + def test_getArguments(self, sample_cfg): + add_call = find_token(sample_cfg, 'add', skip=1) + args = cppcheckdata.getArguments(add_call) + assert [tok.str for tok in args] == ['x', '1'] + + def test_getArguments_no_call(self, sample_cfg): + int_tok = find_token(sample_cfg, 'int') + assert cppcheckdata.getArguments(int_tok) is None + + def test_get_function_call_name_args(self, sample_cfg): + add_call = find_token(sample_cfg, 'add', skip=1) + name, args = cppcheckdata.get_function_call_name_args(add_call) + assert name == 'add' + assert [tok.str for tok in args] == ['x', '1'] + + def test_get_function_call_name_args_namespace(self, sample_cpp_cfg): + twice_call = find_token(sample_cpp_cfg, 'twice', skip=1) + name, args = cppcheckdata.get_function_call_name_args(twice_call) + assert name == 'ns::twice' + assert [tok.str for tok in args] == ['21'] + + def test_get_function_call_name_args_not_a_call(self, sample_cfg): + # the function definition is not a call + add_def = find_token(sample_cfg, 'add') + name, args = cppcheckdata.get_function_call_name_args(add_def) + assert name is None + assert args is None + + def test_astIsFloat(self, sample_cfg): + division = find_token(sample_cfg, '/') + assert cppcheckdata.astIsFloat(division) + plus = find_token(sample_cfg, '+') # a + b with int operands + assert not cppcheckdata.astIsFloat(plus) + assert not cppcheckdata.astIsFloat(None) + + +class TestMatch: + """Tests for the cppcheckdata.match()/simpleMatch() pattern matching.""" + + def test_simpleMatch(self, match_cfg): + calc_def = find_token(match_cfg, 'calc') + assert cppcheckdata.simpleMatch(calc_def, 'calc') + assert cppcheckdata.simpleMatch(calc_def, 'calc ( int a , int b )') + assert not cppcheckdata.simpleMatch(calc_def, 'calc ( int b') + assert not cppcheckdata.simpleMatch(None, 'calc') + + def test_literal_sequence(self, match_cfg): + calc_def = find_token(match_cfg, 'calc') + assert cppcheckdata.match(calc_def, 'calc ( int') + assert cppcheckdata.match(calc_def, 'calc ( int a , int b )') + assert not cppcheckdata.match(calc_def, 'calc ( char') + # the match is anchored at the given token + assert not cppcheckdata.match(calc_def, 'int calc') + + def test_empty_pattern_and_no_token(self, match_cfg): + calc_def = find_token(match_cfg, 'calc') + assert not cppcheckdata.match(calc_def, '') + assert not cppcheckdata.match(None, 'calc') + + def test_end(self, match_cfg): + calc_def = find_token(match_cfg, 'calc') + res = cppcheckdata.match(calc_def, 'calc') + assert res.end is calc_def + res = cppcheckdata.match(calc_def, 'calc ( int') + assert res.end.str == 'int' + + # ---- literal operator tokens ---- + + def test_literal_bitwise_or(self, match_cfg): + # a literal '|' in the pattern matches a '|' token ... + bit_or = find_token(match_cfg, '|') + assert cppcheckdata.match(bit_or, '|') + assert cppcheckdata.match(bit_or.previous, '%var% | %var% ;') + # ... but not a '||' token, and it is not an either-or alternation + log_or = find_token(match_cfg, '||') + assert not cppcheckdata.match(log_or, '|') + + def test_literal_logical_or(self, match_cfg): + log_or = find_token(match_cfg, '||') + assert cppcheckdata.match(log_or, '||') + assert cppcheckdata.match(log_or.previous, '%var% || %var% ;') + bit_or = find_token(match_cfg, '|') + assert not cppcheckdata.match(bit_or, '||') + + def test_literal_not(self, match_cfg): + not_tok = find_token(match_cfg, '!') + assert cppcheckdata.match(not_tok, '! %var% ;') + assert not cppcheckdata.match(find_token(match_cfg, '!='), '!') + + def test_literal_not_equal(self, match_cfg): + neq = find_token(match_cfg, '!=') + assert cppcheckdata.match(neq, '!=') + assert cppcheckdata.match(neq.previous, '%var% != %var% ;') + assert not cppcheckdata.match(find_token(match_cfg, '='), '!=') + assert not cppcheckdata.match(neq, '=') + + def test_literal_star(self, match_cfg): + mul = find_token(match_cfg, '*') + assert cppcheckdata.match(mul, '*') + assert cppcheckdata.match(mul.previous, '%var% * %var% ;') + + def test_literal_percent(self, match_cfg): + mod = find_token(match_cfg, '%') + assert cppcheckdata.match(mod, '%') + assert cppcheckdata.match(mod.previous, '%var% % %var% ;') + + def test_literal_parentheses(self, match_cfg): + calc_call = find_token(match_cfg, 'calc', skip=1) + assert cppcheckdata.match(calc_call, 'calc ( %var% , %var% ) ;') + + # ---- %keyword% patterns ---- + + def test_any(self, match_cfg): + for token_str in ('calc', '(', '3', ';', '|', '{'): + assert cppcheckdata.match(find_token(match_cfg, token_str), '%any%') + plus_assign = find_token(match_cfg, '+=') + assert cppcheckdata.match(plus_assign, '%assign% %any% ;') + + def test_assign(self, match_cfg): + assert cppcheckdata.match(find_token(match_cfg, '='), '%assign%') + assert cppcheckdata.match(find_token(match_cfg, '+='), '%assign%') + assert not cppcheckdata.match(find_token(match_cfg, '!='), '%assign%') + assert not cppcheckdata.match(find_token(match_cfg, '|'), '%assign%') + + def test_comp(self, match_cfg): + assert cppcheckdata.match(find_token(match_cfg, '>'), '%comp%') + assert cppcheckdata.match(find_token(match_cfg, '!='), '%comp%') + assert not cppcheckdata.match(find_token(match_cfg, '='), '%comp%') + assert not cppcheckdata.match(find_token(match_cfg, '|'), '%comp%') + + def test_name(self, match_cfg): + assert cppcheckdata.match(find_token(match_cfg, 'calc'), '%name%') + assert cppcheckdata.match(find_token(match_cfg, 'int'), '%name%') + assert not cppcheckdata.match(find_token(match_cfg, '3'), '%name%') + assert not cppcheckdata.match(find_token(match_cfg, '('), '%name%') + + def test_op(self, match_cfg): + for op in ('|', '||', '*', '%', '!=', '>', '=', '+='): + assert cppcheckdata.match(find_token(match_cfg, op), '%op%'), op + assert not cppcheckdata.match(find_token(match_cfg, ';'), '%op%') + assert not cppcheckdata.match(find_token(match_cfg, 'calc'), '%op%') + + def test_or(self, match_cfg): + assert cppcheckdata.match(find_token(match_cfg, '|'), '%or%') + assert not cppcheckdata.match(find_token(match_cfg, '||'), '%or%') + + def test_oror(self, match_cfg): + assert cppcheckdata.match(find_token(match_cfg, '||'), '%oror%') + assert not cppcheckdata.match(find_token(match_cfg, '|'), '%oror%') + + def test_var(self, match_cfg): + a_use = find_token(match_cfg, 'a', skip=1) + assert cppcheckdata.match(a_use, '%var%') + # a function name is a %name% but not a %var% + assert not cppcheckdata.match(find_token(match_cfg, 'calc'), '%var%') + assert not cppcheckdata.match(find_token(match_cfg, '3'), '%var%') + + # ---- link patterns ---- + + def test_link_parentheses(self, match_cfg): + calc_call = find_token(match_cfg, 'calc', skip=1) + res = cppcheckdata.match(calc_call.next, '(*)') + assert res + assert res.end.str == ')' + assert res.end is calc_call.next.link + # the pattern continues after the linked token + assert cppcheckdata.match(calc_call, '%name% (*) ;') + # '(*)' only matches at a '(' token + assert not cppcheckdata.match(calc_call, '(*)') + + def test_link_brackets(self, match_cfg): + arr_decl = find_token(match_cfg, 'arr') + assert cppcheckdata.match(arr_decl, 'arr [*] ;') + arr_use = find_token(match_cfg, 'arr', skip=1) + res = cppcheckdata.match(arr_use, '%name% [*] %assign% %var% ;') + assert res + + def test_link_braces(self, match_cfg): + if_scope = find_scope(match_cfg, 'If') + res = cppcheckdata.match(if_scope.bodyStart, '{*}') + assert res + assert res.end is if_scope.bodyEnd + + def test_link_combined(self, match_cfg): + if_tok = find_token(match_cfg, 'if') + res = cppcheckdata.match(if_tok, 'if (*) {*}') + assert res + assert res.end.str == '}' + + def test_link_angle_brackets(self, match_cpp_cfg): + # the '<' of 'std::vector' is linked to the '>' + template_lt = find_token(match_cpp_cfg, '<') + assert template_lt.link + res = cppcheckdata.match(template_lt, '<*>') + assert res + assert res.end.str == '>' + assert cppcheckdata.match(template_lt.previous, 'vector <*> %var%') + # the '<' in 'i < j' is a comparison without a link + comparison_lt = find_token(match_cpp_cfg, '<', skip=1) + assert comparison_lt.link is None + assert not cppcheckdata.match(comparison_lt, '<*>') + + # ---- either-or alternation ---- + + def test_alternatives(self, match_cfg): + struct_tok = find_token(match_cfg, 'struct') + assert cppcheckdata.match(struct_tok, 'struct|class %name% {') + assert not cppcheckdata.match(struct_tok, 'union|enum %name% {') + + def test_alternatives_cpp(self, match_cpp_cfg): + class_tok = find_token(match_cpp_cfg, 'class') + assert cppcheckdata.match(class_tok, 'struct|class %name% {') + + def test_alternatives_with_keyword(self, match_cfg): + struct_tok = find_token(match_cfg, 'struct') + assert cppcheckdata.match(struct_tok, '%op%|struct') + assert not cppcheckdata.match(struct_tok, '%op%|%comp%') + + # ---- negation ---- + + def test_negation(self, match_cfg): + struct_tok = find_token(match_cfg, 'struct') + assert cppcheckdata.match(struct_tok, 'struct !!;') + assert not cppcheckdata.match(struct_tok, 'struct !!Point') + + def test_negation_keyword(self, match_cfg): + calc_call = find_token(match_cfg, 'calc', skip=1) + assert cppcheckdata.match(calc_call, '%name% !!%op%') + assert not cppcheckdata.match(calc_call, '%name% !!(') + + @pytest.mark.xfail(strict=False, + reason='in the C++ Token::Match a negation also matches when there is no token, ' + 'in match() it does not') + def test_negation_no_token(self, match_cfg): + last = match_cfg.tokenlist[-1] + assert last.str == '}' + assert cppcheckdata.match(last, '} !!x') + + # ---- bindings ---- + + def test_bindings(self, match_cfg): + calc_call = find_token(match_cfg, 'calc', skip=1) + res = cppcheckdata.match(calc_call, '%name%@ftok (*)') + assert res + assert res.ftok is calc_call + assert res.ftok.str == 'calc' + assert res.end.str == ')' + + def test_multiple_bindings(self, match_cfg): + bit_or_lhs = find_token(match_cfg, 'bit_or', skip=1) + res = cppcheckdata.match(bit_or_lhs, '%var%@lhs = %var%@op1 | %var%@op2 ;') + assert res + assert res.lhs.str == 'bit_or' + assert res.op1.str == 'a' + assert res.op2.str == 'b' + + def test_binding_on_link_pattern(self, match_cfg): + calc_call = find_token(match_cfg, 'calc', skip=1) + # the binding is the '(' token, end is behind the linked ')' + res = cppcheckdata.match(calc_call, '%name% (*)@paren') + assert res + assert res.paren.str == '(' + assert res.paren.link is res.end + + def test_bindings_on_failure(self, match_cfg): + calc_call = find_token(match_cfg, 'calc', skip=1) + res = cppcheckdata.match(calc_call, '%name%@ftok [*]@brackets') + assert not res + # all bindings (including 'end') read as None on a failed match + assert res.ftok is None + assert res.brackets is None + assert res.end is None + + def test_unknown_binding_raises(self, match_cfg): + calc_call = find_token(match_cfg, 'calc', skip=1) + res = cppcheckdata.match(calc_call, '%name%@ftok (*)') + assert res + with pytest.raises(AttributeError): + res.nosuchbinding # pylint: disable=W0104 + res = cppcheckdata.match(calc_call, '%name%@ftok [*]') + assert not res + with pytest.raises(AttributeError): + res.nosuchbinding # pylint: disable=W0104 + + # ---- '**' forward search ---- + + def test_find_forward(self, match_cfg): + calc_def = find_token(match_cfg, 'calc') + # '**' searches forward, skipping linked groups: the '{' found here + # is the function body, not a '{' inside the parameter list + res = cppcheckdata.match(calc_def, 'calc **{') + assert res + assert res.end.str == '{' + assert not cppcheckdata.match(calc_def, 'calc **nosuchtoken') + + +class TestSuppressions: + def make_suppression(self, **kwargs): + return cppcheckdata.Suppression(kwargs) + + def test_line_suppression(self): + supp = self.make_suppression(errorId='zerodiv', lineNumber='5') + assert supp.isMatch('a.c', '5', 'msg', 'zerodiv') + assert not supp.isMatch('a.c', '5', 'msg', 'nullPointer') + + @pytest.mark.xfail(strict=False, + reason='the "other suppression" fallback in Suppression.isMatch() does not check ' + 'that lineNumber is unset, so a line suppression matches every line') + def test_line_suppression_other_line(self): + supp = self.make_suppression(errorId='zerodiv', lineNumber='5') + assert not supp.isMatch('a.c', '6', 'msg', 'zerodiv') + + def test_wildcard_errorId(self): + supp = self.make_suppression(errorId='*', lineNumber='5') + assert supp.isMatch('a.c', '5', 'msg', 'anything') + + def test_file_suppression(self): + supp = self.make_suppression(errorId='zerodiv', fileName='a.c', type='file') + assert supp.isMatch('a.c', '1', 'msg', 'zerodiv') + assert supp.isMatch('a.c', '99', 'msg', 'zerodiv') + assert not supp.isMatch('b.c', '1', 'msg', 'zerodiv') + + def test_block_suppression(self): + supp = self.make_suppression(errorId='zerodiv', type='block', lineBegin='3', lineEnd='7') + assert supp.isMatch('a.c', '5', 'msg', 'zerodiv') + assert not supp.isMatch('a.c', '3', 'msg', 'zerodiv') + assert not supp.isMatch('a.c', '7', 'msg', 'zerodiv') + + def test_global_suppression(self): + supp = self.make_suppression(errorId='zerodiv') + assert supp.isMatch('a.c', '1', 'msg', 'zerodiv') + assert supp.isMatch('b.c', '99', 'other', 'zerodiv') + + def test_symbolName(self): + supp = self.make_suppression(errorId='zerodiv', symbolName='xyz') + assert supp.isMatch('a.c', '1', 'error on xyz here', 'zerodiv') + assert not supp.isMatch('a.c', '1', 'other message', 'zerodiv') + + def test_dumpfile_suppressions(self, dump_factory): + code = ('void f(void)\n' + '{\n' + ' int a;\n' + ' // cppcheck-suppress uninitvar\n' + ' a++;\n' + '}\n') + data = dump_factory.parse(code, extra_args=['--inline-suppr']) + assert len(data.suppressions) == 1 + supp = data.suppressions[0] + assert supp.errorId == 'uninitvar' + assert supp.fileName.endswith('test.c') + assert int(supp.lineNumber) == 5 + # parsedump() also sets the suppressions used by is_suppressed() + location = cppcheckdata.Location({'file': supp.fileName, 'line': '5', 'column': '5'}) + assert cppcheckdata.is_suppressed(location, 'msg', 'uninitvar') + assert not cppcheckdata.is_suppressed(location, 'msg', 'nullPointer') + # NOTE: a non-matching line is not checked here - see test_line_suppression_other_line + + +class TestPureHelpers: + """Tests that construct objects from dicts and do not need a dump file.""" + + def test_location(self): + loc = cppcheckdata.Location({'file': 'a.c', 'line': '3', 'column': '7'}) + assert loc.file == 'a.c' + assert loc.linenr == 3 + assert loc.column == 7 + + def test_location_defaults(self): + loc = cppcheckdata.Location({'file': 'a.c'}) + assert loc.linenr == 0 + assert loc.column == 0 + + def test_location_linenr_alias(self): + loc = cppcheckdata.Location({'file': 'a.c', 'linenr': '4'}) + assert loc.linenr == 4 + + def test_value_kinds(self): + known = cppcheckdata.Value({'intvalue': '42', 'known': 'true'}) + assert known.intvalue == 42 + assert known.isKnown() + assert not known.isPossible() + possible = cppcheckdata.Value({'intvalue': '1', 'possible': 'true'}) + assert possible.isPossible() + impossible = cppcheckdata.Value({'intvalue': '0', 'impossible': 'true'}) + assert impossible.isImpossible() + inconclusive = cppcheckdata.Value({'intvalue': '2', 'inconclusive': 'true'}) + assert inconclusive.isInconclusive() + + def test_value_condition(self): + value = cppcheckdata.Value({'intvalue': '1', 'possible': 'true', 'condition-line': '12'}) + assert value.condition == 12 + + def test_argument_parser(self): + parser = cppcheckdata.ArgumentParser() + args = parser.parse_args(['a.dump', 'b.ctu-info', '--cli', '-q']) + assert args.dumpfile == ['a.dump', 'b.ctu-info'] + assert args.cli + assert args.quiet + assert args.file_list is None + assert '{severity}' in args.template + + def test_get_files(self, tmp_path): + file_list = tmp_path / 'files.txt' + file_list.write_text('c.dump\nd.ctu-info\n') + parser = cppcheckdata.ArgumentParser() + args = parser.parse_args(['a.dump', 'b.ctu-info', '--file-list', str(file_list)]) + dump_files, ctu_info_files = cppcheckdata.get_files(args) + assert dump_files == ['a.dump', 'c.dump'] + assert ctu_info_files == ['b.ctu-info', 'd.ctu-info'] + + +class TestReporting: + def test_reportError_cli(self, monkeypatch, capsys): + monkeypatch.setattr(sys, 'argv', ['myaddon.py', '--cli']) + location = cppcheckdata.Location({'file': 'a.c', 'line': '3', 'column': '7'}) + cppcheckdata.reportError(location, 'error', 'the message', 'myaddon', 'myid', extra='extra info') + msg = json.loads(capsys.readouterr().out) + assert msg == {'file': 'a.c', + 'linenr': 3, + 'column': 7, + 'severity': 'error', + 'message': 'the message', + 'addon': 'myaddon', + 'errorId': 'myid', + 'extra': 'extra info'} + + def test_reportError_cli_column_override(self, monkeypatch, capsys): + monkeypatch.setattr(sys, 'argv', ['myaddon.py', '--cli']) + location = cppcheckdata.Location({'file': 'a.c', 'line': '3', 'column': '7'}) + cppcheckdata.reportError(location, 'error', 'the message', 'myaddon', 'myid', columnOverride=42) + msg = json.loads(capsys.readouterr().out) + assert msg['column'] == 42 + + def test_reportError_stderr(self, monkeypatch, capsys): + monkeypatch.setattr(sys, 'argv', ['myaddon.py']) + monkeypatch.setattr(cppcheckdata, 'current_dumpfile_suppressions', []) + monkeypatch.setattr(cppcheckdata, 'EXIT_CODE', 0) + location = cppcheckdata.Location({'file': 'a.c', 'line': '3', 'column': '7'}) + cppcheckdata.reportError(location, 'style', 'the message', 'myaddon', 'myid') + stderr = capsys.readouterr().err + assert stderr == '[a.c:3] (style) the message [myaddon-myid]\n' + assert cppcheckdata.EXIT_CODE == 1 + + def test_reportError_stderr_suppressed(self, monkeypatch, capsys): + monkeypatch.setattr(sys, 'argv', ['myaddon.py']) + suppression = cppcheckdata.Suppression({'errorId': 'myaddon-myid'}) + monkeypatch.setattr(cppcheckdata, 'current_dumpfile_suppressions', [suppression]) + monkeypatch.setattr(cppcheckdata, 'EXIT_CODE', 0) + location = cppcheckdata.Location({'file': 'a.c', 'line': '3', 'column': '7'}) + cppcheckdata.reportError(location, 'style', 'the message', 'myaddon', 'myid') + assert capsys.readouterr().err == '' + assert cppcheckdata.EXIT_CODE == 0 + + def test_log_checker_cli(self, monkeypatch, capsys): + monkeypatch.setattr(sys, 'argv', ['myaddon.py', '--cli']) + cppcheckdata.log_checker('SomeChecker', 'myaddon') + msg = json.loads(capsys.readouterr().out) + assert msg == {'addon': 'myaddon', + 'severity': 'none', + 'message': 'SomeChecker', + 'errorId': 'logChecker'} + + def test_log_checker_no_cli(self, monkeypatch, capsys): + monkeypatch.setattr(sys, 'argv', ['myaddon.py']) + cppcheckdata.log_checker('SomeChecker', 'myaddon') + assert capsys.readouterr().out == '' diff --git a/test/cfg/boost.cpp b/test/cfg/boost.cpp index 7e724a8b472..27fae71b499 100644 --- a/test/cfg/boost.cpp +++ b/test/cfg/boost.cpp @@ -22,6 +22,8 @@ #include // IWYU pragma: keep #include #include // IWYU pragma: keep +#include +#include #include #include @@ -155,6 +157,7 @@ void test_BOOST_FOREACH_5() { std::set data; BOOST_FOREACH(const int& i, get_data()) + // cppcheck-suppress useStlAlgorithm data.insert(i); } @@ -199,3 +202,46 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(my_tuple_test, T, test_types_w_tuples) } BOOST_AUTO_TEST_SUITE_END() + +// https://www.boost.org/doc/libs/latest/libs/test/doc/html/boost_test/tests_organization/test_cases/test_case_generation/datasets.html +namespace bdata = boost::unit_test::data; + +// Dataset generating a Fibonacci sequence +struct fibonacci_dataset { + // the type of the samples is deduced + // cppcheck-suppress unusedStructMember // FP #14795, used in template is_dataset + static const int arity = 1; + + struct iterator { + int operator*() const { return b; } + void operator++() { + a = a + b; + std::swap(a, b); + } + private: + int a = 1; + int b = 1; // b is the output + }; + + // size is infinite + bdata::size_t size() const { return bdata::BOOST_TEST_DS_INFINITE_SIZE; } + + // iterator + static iterator begin() { return iterator(); } +}; + +namespace boost { namespace unit_test { namespace data { namespace monomorphic { + // registering fibonacci_dataset as a proper dataset + template <> + struct is_dataset : boost::mpl::true_ {}; +}}}} + +// Creating a test-driven dataset, the zip is for checking +BOOST_DATA_TEST_CASE( + test1, + fibonacci_dataset() ^ bdata::make( { 1, 2, 3, 5, 8, 13, 21, 35, 56 } ), + fib_sample, exp) +{ + // cppcheck-suppress valueFlowBailoutIncompleteVar // TODO: fib_sample declared in test case + BOOST_TEST(fib_sample == exp); +} diff --git a/test/cfg/bsd.c b/test/cfg/bsd.c index 3cb39c14810..e5a749ebb8f 100644 --- a/test/cfg/bsd.c +++ b/test/cfg/bsd.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -180,3 +181,9 @@ void reallocarray_notused(void) // cppcheck-suppress [leakReturnValNotUsed, ignoredReturnValue] reallocarray(NULL, 10, 10); } + +void mmap_fixed(void *addr) // No constParameterPointer: mmap() may replace the mapping at addr +{ + // cppcheck-suppress valueFlowBailoutIncompleteVar + (void)mmap(addr, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0); +} diff --git a/test/cfg/gnu.c b/test/cfg/gnu.c index 14dfcc7700f..7a2abd8aef5 100644 --- a/test/cfg/gnu.c +++ b/test/cfg/gnu.c @@ -12,6 +12,7 @@ #define _GNU_SOURCE #include +#include #include #include #include @@ -157,6 +158,132 @@ int nullPointer_getopt_long_only(int argc, char* const* argv, const char* optstr return getopt_long_only(argc, argv, optstring, longopts, longindex); } +bool nullPointer_builtin_add_overflow(char a, char b) +{ + // cppcheck-suppress nullPointer + return __builtin_add_overflow(a, b, (char *)0); +} + +bool nullPointer_builtin_sadd_overflow(int a, int b) +{ + // cppcheck-suppress nullPointer + return __builtin_sadd_overflow(a, b, (int *)0); +} + +bool nullPointer_builtin_saddl_overflow(long int a, long int b) +{ + // cppcheck-suppress nullPointer + return __builtin_saddl_overflow(a, b, (long int *)0); +} + +bool nullPointer_builtin_saddll_overflow(long long int a, long long int b) +{ + // cppcheck-suppress nullPointer + return __builtin_saddll_overflow(a, b, (long long int *)0); +} + +bool nullPointer_builtin_uadd_overflow(unsigned int a, unsigned int b) +{ + // cppcheck-suppress nullPointer + return __builtin_uadd_overflow(a, b, (unsigned int *)0); +} + +bool nullPointer_builtin_uaddl_overflow(unsigned long int a, unsigned long int b) +{ + // cppcheck-suppress nullPointer + return __builtin_uaddl_overflow(a, b, (unsigned long int *)0); +} + +bool nullPointer_builtin_uaddll_overflow(unsigned long long int a, unsigned long long int b) +{ + // cppcheck-suppress nullPointer + return __builtin_uaddll_overflow(a, b, (unsigned long long int *)0); +} + +bool nullPointer_builtin_sub_overflow(char a, char b) +{ + // cppcheck-suppress nullPointer + return __builtin_sub_overflow(a, b, (char *)0); +} + +bool nullPointer_builtin_ssub_overflow(int a, int b) +{ + // cppcheck-suppress nullPointer + return __builtin_ssub_overflow(a, b, (int *)0); +} + +bool nullPointer_builtin_ssubl_overflow(long int a, long int b) +{ + // cppcheck-suppress nullPointer + return __builtin_ssubl_overflow(a, b, (long int *)0); +} + +bool nullPointer_builtin_ssubll_overflow(long long int a, long long int b) +{ + // cppcheck-suppress nullPointer + return __builtin_ssubll_overflow(a, b, (long long int *)0); +} + +bool nullPointer_builtin_usub_overflow(unsigned int a, unsigned int b) +{ + // cppcheck-suppress nullPointer + return __builtin_usub_overflow(a, b, (unsigned int *)0); +} + +bool nullPointer_builtin_usubl_overflow(unsigned long int a, unsigned long int b) +{ + // cppcheck-suppress nullPointer + return __builtin_usubl_overflow(a, b, (unsigned long int *)0); +} + +bool nullPointer_builtin_usubll_overflow(unsigned long long int a, unsigned long long int b) +{ + // cppcheck-suppress nullPointer + return __builtin_usubll_overflow(a, b, (unsigned long long int *)0); +} + +bool nullPointer_builtin_mul_overflow(char a, char b) +{ + // cppcheck-suppress nullPointer + return __builtin_mul_overflow(a, b, (char *)0); +} + +bool nullPointer_builtin_smul_overflow(int a, int b) +{ + // cppcheck-suppress nullPointer + return __builtin_smul_overflow(a, b, (int *)0); +} + +bool nullPointer_builtin_smull_overflow(long int a, long int b) +{ + // cppcheck-suppress nullPointer + return __builtin_smull_overflow(a, b, (long int *)0); +} + +bool nullPointer_builtin_smulll_overflow(long long int a, long long int b) +{ + // cppcheck-suppress nullPointer + return __builtin_smulll_overflow(a, b, (long long int *)0); +} + +bool nullPointer_builtin_umul_overflow(unsigned int a, unsigned int b) +{ + // cppcheck-suppress nullPointer + return __builtin_umul_overflow(a, b, (unsigned int *)0); +} + +bool nullPointer_builtin_umull_overflow(unsigned long int a, unsigned long int b) +{ + // cppcheck-suppress nullPointer + return __builtin_umull_overflow(a, b, (unsigned long int *)0); +} + +bool nullPointer_builtin_umulll_overflow(unsigned long long int a, unsigned long long int b) +{ + // cppcheck-suppress nullPointer + return __builtin_umulll_overflow(a, b, (unsigned long long int *)0); +} + #if !defined(__APPLE__) int nullPointer_getservent_r(struct servent *restrict result_buf, const char *restrict buf, size_t buflen, struct servent **restrict result) { @@ -197,6 +324,31 @@ void knownConditionTrueFalse_ffsll(long long i) if (ffsll(i) == 0) {} } +int uninitvar_popcount() { + unsigned int a; + // cppcheck-suppress uninitvar + int b = __builtin_popcount(a); // popcount requires init arg + return b; +} + +int invalidFunctionArg_clz() { + // cppcheck-suppress invalidFunctionArg + return __builtin_clz(0); +} + +int invalidFunctionArgBool_ctzg(unsigned int a, unsigned int b) { + // cppcheck-suppress invalidFunctionArgBool + return __builtin_ctzg(a, a % b == 0); +} + +#if __GNUC__ > 14 +int ignoredReturnValue_stdc_width(size_t n) { + // cppcheck-suppress ignoredReturnValue + __builtin_stdc_bit_width(n); + return 0; +} +#endif + #if !defined(__APPLE__) int nullPointer_semtimedop(int semid, struct sembuf *sops, size_t nsops, const struct timespec *timeout) { @@ -224,6 +376,13 @@ wchar_t *nullPointer_wmempcpy(wchar_t *dest, const wchar_t *src, size_t n) return wmempcpy(dest,src,n); } +void bufferAccessOutOfBounds_wmempcpy(const wchar_t *src) +{ + wchar_t buf[3]; + // cppcheck-suppress bufferAccessOutOfBounds + wmempcpy(buf, src, 5); +} + int uninitvar_getpw(uid_t uid, char *buf) { uid_t someUid; @@ -461,6 +620,216 @@ void uninitvar__builtin_memset(void) (void)__builtin_memset(s,c,n); } +void uninitvar__builtin_add_overflow(void) +{ + char a; + char b; + char c; + + // cppcheck-suppress uninitvar + (void)__builtin_add_overflow(a, b, &c); +} + +void uninitvar__builtin_sadd_overflow(void) +{ + int a; + int b; + int c; + + // cppcheck-suppress uninitvar + (void)__builtin_sadd_overflow(a, b, &c); +} + +void uninitvar__builtin_saddl_overflow(void) +{ + long int a; + long int b; + long int c; + + // cppcheck-suppress uninitvar + (void)__builtin_saddl_overflow(a, b, &c); +} + +void uninitvar__builtin_saddll_overflow(void) +{ + long long int a; + long long int b; + long long int c; + + // cppcheck-suppress uninitvar + (void)__builtin_saddll_overflow(a, b, &c); +} + +void uninitvar__builtin_uadd_overflow(void) +{ + unsigned int a; + unsigned int b; + unsigned int c; + + // cppcheck-suppress uninitvar + (void)__builtin_uadd_overflow(a, b, &c); +} + +void uninitvar__builtin_uaddl_overflow(void) +{ + unsigned long int a; + unsigned long int b; + unsigned long int c; + + // cppcheck-suppress uninitvar + (void)__builtin_uaddl_overflow(a, b, &c); +} + +void uninitvar__builtin_uaddll_overflow(void) +{ + unsigned long long int a; + unsigned long long int b; + unsigned long long int c; + + // cppcheck-suppress uninitvar + (void)__builtin_uaddll_overflow(a, b, &c); +} + +void uninitvar__builtin_sub_overflow(void) +{ + char a; + char b; + char c; + + // cppcheck-suppress uninitvar + (void)__builtin_sub_overflow(a, b, &c); +} + +void uninitvar__builtin_ssub_overflow(void) +{ + int a; + int b; + int c; + + // cppcheck-suppress uninitvar + (void)__builtin_ssub_overflow(a, b, &c); +} + +void uninitvar__builtin_ssubl_overflow(void) +{ + long int a; + long int b; + long int c; + + // cppcheck-suppress uninitvar + (void)__builtin_ssubl_overflow(a, b, &c); +} + +void uninitvar__builtin_ssubll_overflow(void) +{ + long long int a; + long long int b; + long long int c; + + // cppcheck-suppress uninitvar + (void)__builtin_ssubll_overflow(a, b, &c); +} + +void uninitvar__builtin_usub_overflow(void) +{ + unsigned int a; + unsigned int b; + unsigned int c; + + // cppcheck-suppress uninitvar + (void)__builtin_usub_overflow(a, b, &c); +} + +void uninitvar__builtin_usubl_overflow(void) +{ + unsigned long int a; + unsigned long int b; + unsigned long int c; + + // cppcheck-suppress uninitvar + (void)__builtin_usubl_overflow(a, b, &c); +} + +void uninitvar__builtin_usubll_overflow(void) +{ + unsigned long long int a; + unsigned long long int b; + unsigned long long int c; + + // cppcheck-suppress uninitvar + (void)__builtin_usubll_overflow(a, b, &c); +} + +void uninitvar__builtin_mul_overflow(void) +{ + char a; + char b; + char c; + + // cppcheck-suppress uninitvar + (void)__builtin_mul_overflow(a, b, &c); +} + +void uninitvar__builtin_smul_overflow(void) +{ + int a; + int b; + int c; + + // cppcheck-suppress uninitvar + (void)__builtin_smul_overflow(a, b, &c); +} + +void uninitvar__builtin_smull_overflow(void) +{ + long int a; + long int b; + long int c; + + // cppcheck-suppress uninitvar + (void)__builtin_smull_overflow(a, b, &c); +} + +void uninitvar__builtin_smulll_overflow(void) +{ + long long int a; + long long int b; + long long int c; + + // cppcheck-suppress uninitvar + (void)__builtin_smulll_overflow(a, b, &c); +} + +void uninitvar__builtin_umul_overflow(void) +{ + unsigned int a; + unsigned int b; + unsigned int c; + + // cppcheck-suppress uninitvar + (void)__builtin_umul_overflow(a, b, &c); +} + +void uninitvar__builtin_umull_overflow(void) +{ + unsigned long int a; + unsigned long int b; + unsigned long int c; + + // cppcheck-suppress uninitvar + (void)__builtin_umull_overflow(a, b, &c); +} + +void uninitvar__builtin_umulll_overflow(void) +{ + unsigned long long int a; + unsigned long long int b; + unsigned long long int c; + + // cppcheck-suppress uninitvar + (void)__builtin_umulll_overflow(a, b, &c); +} + void bufferAccessOutOfBounds__builtin_memset(void) { uint8_t buf[42]; @@ -536,3 +905,10 @@ int nullPointer_epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) return epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL); } #endif + +void mmap_fixed(void *addr) // No constParameterPointer: mmap() may replace the mapping at addr +{ + // cppcheck-suppress valueFlowBailoutIncompleteVar + // cppcheck-suppress leakReturnValNotUsed + (void)mmap(addr, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); +} diff --git a/test/cfg/gtk.c b/test/cfg/gtk.c index 3e4e5b1cb06..2612b572638 100644 --- a/test/cfg/gtk.c +++ b/test/cfg/gtk.c @@ -59,6 +59,14 @@ void validCode(int argInt, GHashTableIter * hash_table_iter, GHashTable * hash_t g_string_free(pGStr1, TRUE); gchar * pGchar1 = g_strconcat("a", "b", NULL); + + // g_str_has_prefix and g_str_has_suffix can have side effects because they use + // g_return_val_if_fail, which logs to the console upon failure + // cppcheck-suppress assertWithSideEffect + g_assert_true(g_str_has_prefix(pGchar1, "a")); + // cppcheck-suppress assertWithSideEffect + g_assert_true(g_str_has_suffix(pGchar1, "b")); + printf("%s", pGchar1); g_free(pGchar1); @@ -82,6 +90,10 @@ void validCode(int argInt, GHashTableIter * hash_table_iter, GHashTable * hash_t // cppcheck-suppress valueFlowBailout // TODO: caused by ? printf("%s", str); g_free(str); + + // transfer none functions: return value should not be unreffed + const GApplication *app = g_application_get_default(); + printf("%p\n", app); } void g_malloc_test() @@ -402,6 +414,7 @@ void g_error_new_test() g_error_new(1, -2, "a %d", 1); const GError * pNew2 = g_error_new(1, -2, "a %d", 1); + g_assert_true(g_error_matches(pNew2, 1, -2)); printf("%p", pNew2); // cppcheck-suppress memleak } @@ -526,6 +539,16 @@ void g_variant_test() { // cppcheck-suppress memleak } +void g_list_test() { + GList *list1 = NULL; + gchar *c = "c"; + + list1 = g_list_append(list1, c); + g_assert_true(g_list_find(list1, c) != NULL); + + g_list_free(list1); +} + void g_queue_test() { // cppcheck-suppress leakReturnValNotUsed g_queue_new(); @@ -581,3 +604,129 @@ void g_tree_test() { printf("%p\n", tree2); // cppcheck-suppress memleak } + +void gtk_widget_destroy_test() { + GtkWidget *widget = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_widget_show(widget); + // cppcheck-suppress memleak + + widget = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_widget_show(widget); + // cppcheck-suppress mismatchAllocDealloc + g_object_unref(widget); +} + +void g_return_if_fail_test(const char *s) { + // cppcheck-suppress valueFlowBailout + g_return_if_fail(s); + + if (*s) + // cppcheck-suppress valueFlowBailout + g_return_if_fail(*s == 'a'); + else + g_info("Test the else branch can be parsed"); +} + +int g_return_val_if_fail_test(const char *s) { + // cppcheck-suppress valueFlowBailout + g_return_val_if_fail(s, 1); + + if (*s) + // cppcheck-suppress valueFlowBailout + g_return_val_if_fail(*s == 'a', 1); + else + g_info("Test the else branch can be parsed"); + + return 0; +} + +void g_return_if_reached_test(const char *s) { + if (s) + g_return_if_reached(); + else + g_info("Test the else branch can be parsed"); +} + +int g_return_val_if_reached_test(const char *s) { + if (s) + g_return_val_if_reached(1); + else + g_info("Test the else branch can be parsed"); + + return 0; +} + +void g_ascii_formatd_test(double d) +{ + // g_ascii_formatd() writes the buffer and formats exactly one double + gchar buf[39]; + g_ascii_formatd(buf, sizeof(buf), "%.2f", d); + g_print("%s", buf); + + // cppcheck-suppress invalidPrintfArgType_sint + g_ascii_formatd(buf, sizeof(buf), "%d", d); + g_print("%s", buf); + + gchar small[4]; + // cppcheck-suppress bufferAccessOutOfBounds + g_ascii_formatd(small, sizeof(buf), "%.2f", d); + g_print("%s", small); +} + +void g_ascii_formatd_format_test(const gchar *format, int i) +{ + gchar buf[39]; + // cppcheck-suppress invalidFunctionArgBool + g_ascii_formatd(buf, sizeof(buf), format, i == 1); + g_print("%s", buf); + + struct { + double x; + } s; + // cppcheck-suppress [uninitvar, uninitStructMember] + g_ascii_formatd(buf, sizeof(buf), format, s.x); + g_print("%s", buf); +} + +#define MY_APP_TYPE_WINDOW my_app_window_get_type() +G_DECLARE_FINAL_TYPE(MyAppWindow, my_app_window, MY_APP, WINDOW, GtkWindow) +struct _MyAppWindow { + GtkWindow parent; +}; +G_DEFINE_TYPE(MyAppWindow, my_app_window, GTK_TYPE_WINDOW) +// cppcheck-suppress constParameterPointer +static void my_app_window_class_init(MyAppWindowClass *class) { +} +// cppcheck-suppress constParameterPointer +static void my_app_window_init(MyAppWindow *window) { +} + +void g_declare_and_define_type_test(void) { + // cppcheck-suppress checkLibraryFunction + MyAppWindow *window = g_object_new(MY_APP_TYPE_WINDOW, NULL); + gtk_widget_show(GTK_WIDGET(window)); + g_object_unref(window); +} + +#if GLIB_CHECK_VERSION(2, 74, 0) +typedef enum { + MY_ENUM_A, + MY_ENUM_B, +} MyEnum; +G_DEFINE_ENUM_TYPE (MyEnum, my_enum, + G_DEFINE_ENUM_VALUE (MY_ENUM_A, "a"), + G_DEFINE_ENUM_VALUE (MY_ENUM_B, "b")) +typedef enum { + MY_FLAGS_A = 1 << 0, + MY_FLAGS_B = 1 << 1, +} MyFlags; +G_DEFINE_FLAGS_TYPE (MyFlags, my_flags, + G_DEFINE_ENUM_VALUE (MY_FLAGS_A, "a"), + G_DEFINE_ENUM_VALUE (MY_FLAGS_B, "b")) + +int g_define_enum_and_flags_type_test(void) { + MyEnum e = MY_ENUM_A; + MyFlags f = MY_FLAGS_A; + return e + f; +} +#endif diff --git a/test/cfg/libcurl.c b/test/cfg/libcurl.c index 07fd26b031f..f5faadbd0d1 100644 --- a/test/cfg/libcurl.c +++ b/test/cfg/libcurl.c @@ -12,6 +12,16 @@ #include #include +struct S_WriteHeader { + int x; +}; + +size_t cb_WriteHeader(void * /*ptr*/, size_t size, size_t nmemb, void *p) { + // cppcheck-suppress constVariablePointer + struct S_WriteHeader *s = (struct S_WriteHeader *)p; + return s->x ? size * nmemb : 0; +} + void validCode() { CURL *curl = curl_easy_init(); @@ -19,6 +29,9 @@ void validCode() CURLcode res; // cppcheck-suppress valueFlowBailoutIncompleteVar curl_easy_setopt(curl, CURLOPT_URL, "http://example.com"); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, cb_WriteHeader); + struct S_WriteHeader s; + curl_easy_setopt(curl, CURLOPT_WRITEHEADER, &s); // #14692 res = curl_easy_perform(curl); if (res != CURLE_OK) { printf("error"); diff --git a/test/cfg/microsoft_gsl.cpp b/test/cfg/microsoft_gsl.cpp new file mode 100644 index 00000000000..4241cce045d --- /dev/null +++ b/test/cfg/microsoft_gsl.cpp @@ -0,0 +1,72 @@ +// Test library configuration for microsoft_gsl.cfg +// +// Usage: +// $ cppcheck --check-library --library=microsoft_gsl --enable=style,information --inconclusive --error-exitcode=1 --inline-suppr --suppress=autoNoType test/cfg/microsoft_gsl.cpp +// => +// No warnings about bad library configuration, unmatched suppressions, etc. exitcode=0 +// + +// C++ Standard Library +#include + +// Guideline Support Library +#include + +struct owner_type +{ + owner_type() = default; + owner_type(const owner_type &) = delete; + owner_type(owner_type &&) = default; + + ~owner_type() { delete ptr_; } + + auto operator=(const owner_type &) -> owner_type & = delete; + auto operator=(owner_type &&) -> owner_type & = default; + +private: + gsl::owner ptr_{new int(42)}; +}; + +auto pre_and_post_condition_test(int i) -> int +{ + Expects(i > 0); + + const auto result{i * 2}; + + Ensures(result > 0); + return result; +} + +auto suppress_macro_test(std::span s) -> int +{ + GSL_SUPPRESS("bounds.1") + return s[0]; +} + +auto iterate_over_container_test(const std::vector &v) -> int +{ + int sum{0}; + + for (gsl::index i{0}; i < v.size(); ++i) + { + sum += v[i]; + } + return sum; +} + +void not_null_test(gsl::not_null p) +{ + Expects(p != nullptr); + *p = 42; +} + +void strict_not_null_test(gsl::strict_not_null p) +{ + Expects(p != nullptr); + *p = 42; +} + +auto byte_test(gsl::byte b) -> gsl::byte +{ + return b | 0x81; +} diff --git a/test/cfg/posix.c b/test/cfg/posix.c index 52607fe3a98..874ae168e34 100644 --- a/test/cfg/posix.c +++ b/test/cfg/posix.c @@ -37,6 +37,7 @@ #include #if defined(__APPLE__) #include +#include #endif #if !defined(__APPLE__) #include @@ -193,6 +194,12 @@ void invalidFunctionArgStr_mbsnrtowcs(void) if (mbsnrtowcs (wenough, &cp, 1, 10, &s) != 1 || wcscmp (wenough, L"AEF") != 0) {} } +int invalidFunctionArgStr_strncasecmp(const char* s) +{ + const char buf[3] = {'a', 'b', 'c'}; + return strncasecmp(buf, s, 3); +} + struct tm * ignoredReturnValue_localtime(const time_t *tp) { // cppcheck-suppress [ignoredReturnValue,localtimeCalled] @@ -718,6 +725,13 @@ size_t bufferAccessOutOfBounds_strnlen(const char *s, size_t maxlen) size_t len = strnlen(buf,2); // cppcheck-suppress bufferAccessOutOfBounds len+=strnlen(buf,3); + len+=strnlen("ab",5); + return len; +} + +size_t bufferAccessOutOfBounds_strncasecmp(const char *s) +{ + size_t len = strncasecmp(s, "abc", 5); return len; } @@ -1508,3 +1522,116 @@ void invalidFunctionArg_nice(int inc) // cppcheck-suppress invalidFunctionArg nice(+20); } + +void pthread_cond_wait_test(pthread_cond_t *cond, pthread_mutex_t *mutex) +{ + (void)pthread_cond_wait(cond, mutex); +} + +void pthread_cond_timedwait_test(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime) +{ + // cppcheck-suppress nullPointer + (void)pthread_cond_timedwait(cond, mutex, NULL); + (void)pthread_cond_timedwait(cond, mutex, abstime); +} + +void pthread_cond_init_test(pthread_cond_t *cond, const pthread_condattr_t *attr) +{ + (void)pthread_cond_init(cond, NULL); + (void)pthread_cond_init(cond, attr); + const pthread_condattr_t *uattr; + // cppcheck-suppress uninitvar + (void)pthread_cond_init(cond, uattr); +} + +void pthread_cond_destroy_test(const pthread_cond_t *cond) +{ + (void)pthread_cond_destroy(cond); +} + +void pthread_cleanup_push_test(void (*routine)(void *), void *arg) +{ + pthread_cleanup_push(routine, arg); + pthread_cleanup_pop(0); +} + +void pthread_cleanup_push_test_NULL(void (*routine)(void *)) +{ + pthread_cleanup_push(routine, NULL); + pthread_cleanup_pop(0); +} + +void pthread_mutexattr_init_test(pthread_mutexattr_t *attr) +{ + (void)pthread_mutexattr_init(attr); +} + +void pthread_mutexattr_destroy_test(pthread_mutexattr_t *attr) +{ + (void)pthread_mutexattr_destroy(attr); +} + +void pthread_mutexattr_settype_test(const pthread_mutexattr_t *attr, int type) +{ + (void)pthread_mutexattr_settype(attr, type); +} + +void pthread_mutexattr_gettype_test(const pthread_mutexattr_t *attr, int *type) +{ + (void)pthread_mutexattr_gettype(attr, type); +} + +void pthread_setcancelstate_test(int state, int *oldstate) +{ + (void)pthread_setcancelstate(state, oldstate); +} + +void pthread_setcanceltype_test(int type, int *oldtype) +{ + (void)pthread_setcanceltype(type, oldtype); +} + +void pthread_cancel_test(pthread_t thread) +{ + (void)pthread_cancel(thread); +} + +void pthread_define_test() +{ + PTHREAD_STACK_MIN; + PTHREAD_CANCEL_ASYNCHRONOUS; + PTHREAD_CANCEL_ENABLE; + PTHREAD_CANCEL_DEFERRED; + PTHREAD_CANCEL_DISABLE; + PTHREAD_CANCELED; + pthread_cond_t cond = PTHREAD_COND_INITIALIZER; + (void)cond; + PTHREAD_CREATE_DETACHED; + PTHREAD_CREATE_JOINABLE; + PTHREAD_EXPLICIT_SCHED; + PTHREAD_INHERIT_SCHED; + PTHREAD_MUTEX_DEFAULT; + PTHREAD_MUTEX_ERRORCHECK; + PTHREAD_MUTEX_NORMAL; + pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + (void)mutex; + PTHREAD_MUTEX_RECURSIVE; + pthread_once_t once_block = PTHREAD_ONCE_INIT; + (void)once_block; + PTHREAD_PRIO_INHERIT; + PTHREAD_PRIO_NONE; + PTHREAD_PRIO_PROTECT; + PTHREAD_PROCESS_SHARED; + PTHREAD_PROCESS_PRIVATE; + pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; + (void)rwlock; + PTHREAD_SCOPE_PROCESS; + PTHREAD_SCOPE_SYSTEM; +} + +void mmap_fixed(void *addr) // No constParameterPointer: mmap() may replace the mapping at addr +{ + // cppcheck-suppress valueFlowBailoutIncompleteVar + // cppcheck-suppress leakReturnValNotUsed + (void)mmap(addr, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED, 0, 0); +} diff --git a/test/cfg/qt.cpp b/test/cfg/qt.cpp index 70e6ac0310a..fe2f48e3175 100644 --- a/test/cfg/qt.cpp +++ b/test/cfg/qt.cpp @@ -33,6 +33,7 @@ #include #include #include +#include // TODO: this is actually available via Core5Compat but I could not get it to work with pkg-config #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) @@ -76,6 +77,17 @@ void ignoredReturnValue_QDir(const QString& dirname) dir.entryList(); } +void assertWithSideEffect_QDir_exists(const char *path) { // #14720 + QDir dir(path); + assert(dir.exists()); + (void)dir; +} + +void assertWithSideEffect_QDir_mkdir(const char *path) { // #12468 + QDir dir; + // cppcheck-suppress [assertWithSideEffect, checkLibraryNoReturn] // TODO: #8329 + Q_ASSERT(dir.mkdir(path)); +} void unusedVariable_QTransform() { @@ -200,7 +212,6 @@ void QString1(QString s) bool QString2() { QString s; - // cppcheck-suppress knownConditionTrueFalse return s.size(); } @@ -723,6 +734,7 @@ namespace { private: int m_value; }; + // cppcheck-suppress shadowFunction void Counter1::setValue(int value) { if (value != m_value) { m_value = value; @@ -746,6 +758,7 @@ namespace { private: int m_value; }; + // cppcheck-suppress shadowFunction void Counter2::setValue(int value) { if (value != m_value) { m_value = value; @@ -885,3 +898,20 @@ int qdateIsValid() Q_ASSERT(qd.isValid()); // Should not warn here with assertWithSideEffect return qd.month(); } + +void qAssertX() +{ + const char *where = "test"; + const char *what = "assertion failed"; + Q_ASSERT_X(false, where, what); +} + +struct S_QTimer_connect : QObject { // #13846 + S_QTimer_connect() { + // cppcheck-suppress checkLibraryFunction - timeout() is a signal from QTimer + QObject::connect(&timer, SIGNAL(timeout()), this, SLOT(update())); + } + QTimer timer; +private slots: + bool update(); +}; diff --git a/test/cfg/runtests.sh b/test/cfg/runtests.sh index c8c1515e66c..4cf17b3a9df 100755 --- a/test/cfg/runtests.sh +++ b/test/cfg/runtests.sh @@ -549,7 +549,7 @@ function check_file { kde.cpp) # TODO: "kde-4config" is no longer commonly available in recent distros #kde_fn - cppcheck_run --library="$lib" --library=qt "${DIR}""$f" + cppcheck_run --library="$lib" --library=qt "${DIR}""$f" ;; libcurl.c) libcurl_fn @@ -563,6 +563,9 @@ function check_file { lua_fn cppcheck_run --library="$lib" "${DIR}""$f" ;; + microsoft_gsl.cpp) + cppcheck_run --suppress=autoNoType --library="$lib" "${DIR}""$f" + ;; mfc.cpp) mfc_fn cppcheck_run --platform=win64 --library="$lib" "${DIR}""$f" diff --git a/test/cfg/std.c b/test/cfg/std.c index af0b30e03b2..a8914d608a9 100644 --- a/test/cfg/std.c +++ b/test/cfg/std.c @@ -45,7 +45,7 @@ void test_int32_min() { } void test_int64_min() { - // cppcheck-suppress compareValueOutOfTypeRangeError ; tests that INT64_MIN is a signed expression + // TODO cppcheck-suppress knownConditionTrueFalse // #13814 if (INT64_MIN < 0) {} } @@ -78,6 +78,9 @@ void invalidFunctionArgStr_strncat(void) // No warning is expected for strncat (str1, &src, 1); puts (str1); + char d[] = {'a','b','c'}; + // cppcheck-suppress invalidFunctionArgStr + strncat(d, "def", 1); } char * invalidFunctionArgStr_strpbrk( const char *p ) @@ -287,6 +290,13 @@ char* nullPointer_fgets(char *buffer, int n, FILE *stream) return fgets(buffer, n, stream); } +void nullPointer_fseek(void) { // #10948 + FILE *p = fopen("foo", "r"); + // cppcheck-suppress nullPointerOutOfResources + fseek(p, 1, SEEK_SET); + fclose(p); +} + void memleak_aligned_alloc(void) { // cppcheck-suppress [unusedAllocatedMemory, unreadVariable, constVariablePointer] @@ -609,6 +619,20 @@ void bufferAccessOutOfBounds_wcsncpy() wcsncpy(s, L"abc", 17); } +void bufferAccessOutOfBounds_wmemcpy() +{ + wchar_t a[4]; + // cppcheck-suppress bufferAccessOutOfBounds + wmemcpy(a, L"abcd", 5); +} + +int bufferAccessOutOfBounds_wmemcmp(const wchar_t* b) +{ + const wchar_t a[4] = { L'a', L'b', L'c', L'd' }; + // cppcheck-suppress bufferAccessOutOfBounds + return wmemcmp(a, b, 5); +} + int nullPointer_wcsncmp(const wchar_t* s1, const wchar_t* s2, size_t n) { // cppcheck-suppress nullPointer @@ -2930,6 +2954,27 @@ void *bufferAccessOutOfBounds_memchr(const void *s, int c, size_t n) return memchr(s,c,n); } +bool bufferAccessOutOfBounds_wmemchr(wchar_t c) +{ + const wchar_t buf[] = {'a', 'b', 'c'}; + // cppcheck-suppress bufferAccessOutOfBounds + return wmemchr(buf, c, 5) != NULL; +} + +void bufferAccessOutOfBounds_wmemset(wchar_t c) +{ + wchar_t buf[3]; + // cppcheck-suppress bufferAccessOutOfBounds + wmemset(buf, c, 5); +} + +void bufferAccessOutOfBounds_wmemmove(const wchar_t* s) +{ + wchar_t d[3]; + // cppcheck-suppress bufferAccessOutOfBounds + wmemmove(d, s, 5); +} + void uninitvar_wmemchr(void) { const wchar_t *cs; @@ -3512,6 +3557,13 @@ void invalidFunctionArg_strchr(const char *cs, int c) (void)strchr(cs, 256); } +void constParameterPointer_strchr(char *str) // #14453 +{ + char *sep = strchr(str, ':'); + if (sep) + *sep = '\0'; +} + void invalidFunctionArg_log10(float f, double d, const long double ld) { // cppcheck-suppress invalidFunctionArg @@ -4899,6 +4951,10 @@ void invalidFunctionArgString(char c) char d='d'; /* cppcheck-suppress invalidFunctionArgStr */ (void)strcat(str,&d); + + char a[] = { 'a', 'b', 'c' }; + /* cppcheck-suppress [bufferAccessOutOfBounds,invalidFunctionArgStr] */ + (void)strcat(a, "def"); } void ignoredReturnValue_abs(int i) diff --git a/test/cfg/std.cpp b/test/cfg/std.cpp index ea9ee17fa4f..cf4f74b6dbf 100644 --- a/test/cfg/std.cpp +++ b/test/cfg/std.cpp @@ -869,7 +869,7 @@ char * overlappingWriteFunction_strncat(const char *src, char *dest, const std:: // cppcheck-suppress overlappingWriteFunction (void)strncat(dest, dest+1, 2); char buffer[] = "strncat"; - // cppcheck-suppress overlappingWriteFunction + // cppcheck-suppress [overlappingWriteFunction,returnDanglingLifetime] return strncat(buffer, buffer + 1, 3); } @@ -882,7 +882,7 @@ wchar_t * overlappingWriteFunction_wcsncat(const wchar_t *src, wchar_t *dest, co // cppcheck-suppress overlappingWriteFunction (void)wcsncat(dest, dest+1, 2); wchar_t buffer[] = L"strncat"; - // cppcheck-suppress overlappingWriteFunction + // cppcheck-suppress [overlappingWriteFunction,returnDanglingLifetime] return wcsncat(buffer, buffer + 1, 3); } @@ -917,8 +917,8 @@ char * overlappingWriteFunction_strncpy(char *buf, const std::size_t count) void * overlappingWriteFunction_memmove(void) { - // No warning shall be shown: char str[] = "memmove handles overlapping data well"; + // cppcheck-suppress returnDanglingLifetime return memmove(str,str+3,4); } @@ -1058,7 +1058,7 @@ void returnValue_std_isgreater(void) { // cppcheck-suppress knownConditionTrueFalse if (std::isgreater(4,2) == 0) {} - // @todo support floats + // cppcheck-suppress knownConditionTrueFalse if (std::isgreater(4.0f,2.0f) == 0) {} } @@ -1066,7 +1066,7 @@ void returnValue_std_isgreaterequal(void) { // cppcheck-suppress knownConditionTrueFalse if (std::isgreaterequal(4,2) == 0) {} - // @todo support floats + // cppcheck-suppress knownConditionTrueFalse if (std::isgreaterequal(4.0f,2.0f) == 0) {} } @@ -1074,7 +1074,7 @@ void returnValue_std_isless(void) { // cppcheck-suppress knownConditionTrueFalse if (std::isless(4,2) == 0) {} - // @todo support floats + // cppcheck-suppress knownConditionTrueFalse if (std::isless(4.0f,2.0f) == 0) {} } @@ -1082,7 +1082,7 @@ void returnValue_std_islessequal(void) { // cppcheck-suppress knownConditionTrueFalse if (std::islessequal(4,2) == 0) {} - // @todo support floats + // cppcheck-suppress knownConditionTrueFalse if (std::islessequal(4.0f,2.0f) == 0) {} } @@ -1093,8 +1093,10 @@ void returnValue_std_islessgreater(void) // cppcheck-suppress knownConditionTrueFalse if (std::islessgreater(2,4) == 0) {} - if (std::islessgreater(4.0f,2.0f) == 0) {} // @todo support floats - if (std::islessgreater(2.0f,4.0f) == 0) {} // @todo support floats + // cppcheck-suppress knownConditionTrueFalse + if (std::islessgreater(4.0f,2.0f) == 0) {} + // cppcheck-suppress knownConditionTrueFalse + if (std::islessgreater(2.0f,4.0f) == 0) {} } void bufferAccessOutOfBounds(void) @@ -2836,6 +2838,13 @@ void uninitvar_logb(void) (void)std::logbl(ld); } +void bool_isless(float f1, float f2) +{ + // cppcheck-suppress compareBoolExpressionWithInt + // cppcheck-suppress compareValueOutOfTypeRangeError + if (std::isless(f1,f2)==123) {} +} + void uninitvar_isless(void) { float f1,f2; @@ -2851,6 +2860,13 @@ void uninitvar_isless(void) (void)std::isless(ld1,ld2); } +void bool_islessequal(float f1, float f2) +{ + // cppcheck-suppress compareBoolExpressionWithInt + // cppcheck-suppress compareValueOutOfTypeRangeError + if (std::islessequal(f1,f2)==123) {} +} + void uninitvar_islessequal(void) { float f1,f2; @@ -2866,6 +2882,13 @@ void uninitvar_islessequal(void) (void)std::islessequal(ld1,ld2); } +void bool_islessgreater(float f1, float f2) +{ + // cppcheck-suppress compareBoolExpressionWithInt + // cppcheck-suppress compareValueOutOfTypeRangeError + if (std::islessgreater(f1,f2)==123) {} +} + void uninitvar_islessgreater(void) { float f1,f2; @@ -2892,6 +2915,13 @@ void uninitvar_nan(void) (void)std::nanl(tagp3); } +void bool_isnan(float f) +{ + // cppcheck-suppress compareBoolExpressionWithInt + // cppcheck-suppress compareValueOutOfTypeRangeError + if (std::isnan(f)==123) {} +} + void uninitvar_isnan(void) { double d; @@ -2899,6 +2929,13 @@ void uninitvar_isnan(void) (void)std::isnan(d); } +void bool_isnormal(float f) +{ + // cppcheck-suppress compareBoolExpressionWithInt + // cppcheck-suppress compareValueOutOfTypeRangeError + if (std::isnormal(f)==123) {} +} + void uninitvar_isnormal(void) { double d; @@ -2906,6 +2943,13 @@ void uninitvar_isnormal(void) (void)std::isnormal(d); } +void bool_isunordered(float f1, float f2) +{ + // cppcheck-suppress compareBoolExpressionWithInt + // cppcheck-suppress compareValueOutOfTypeRangeError + if (std::isunordered(f1,f2)==123) {} +} + void uninitvar_isunordered(void) { double d1,d2; @@ -3441,6 +3485,13 @@ void uninivar_scalbln(void) (void)std::scalblnl(ld,i3); } +void bool_signbit(float f) +{ + // cppcheck-suppress compareBoolExpressionWithInt + // cppcheck-suppress compareValueOutOfTypeRangeError + if (std::signbit(f)==123) {} +} + void uninitvar_signbit(void) { double d; @@ -4980,6 +5031,18 @@ std::span returnDanglingLifetime_std_span1() { } #endif +void* returnDanglingLifetime_memcpy() { // #14833 + char a[4]; + // cppcheck-suppress returnDanglingLifetime + return memcpy(a, "abc", 4); +} + +wchar_t* returnDanglingLifetime_wcscat() { + wchar_t a[10]{L"abc"}; + // cppcheck-suppress returnDanglingLifetime + return wcscat(a, L"def"); +} + void beginEnd() { std::vector v; @@ -5032,15 +5095,22 @@ struct T_constParameter_std_begin { std::vector v; }; +struct U_constParameter_std_begin { + std::vector v[1][1]; +}; + void f(S_constParameter_std_begin& s) { std::for_each(std::begin(s.a), std::end(s.a), [](int& i) { ++i; }); } -// cppcheck-suppress constParameterReference - FP void f(T_constParameter_std_begin& t) { std::for_each(std::begin(t.v), std::end(t.v), [](int& i) { ++i; }); } +void f(U_constParameter_std_begin& u) { + std::for_each(std::begin(u.v[0][0]), std::end(u.v[0][0]), [](int& i) { ++i; }); +} + void g_constVariable_std_begin(int* p) { *p = 0; } int f_constVariable_std_begin() { @@ -5050,6 +5120,11 @@ int f_constVariable_std_begin() { return arr[0]; } +std::size_t uninitvar_std_size() { // #14976 + int a[3]; + return std::size(a); +} + void smartPtr_get() { std::unique_ptr p; @@ -5358,3 +5433,13 @@ int containerOutOfBounds_std_initializer_list() { // #14340 int i = *x.end(); return i + containerOutOfBounds_std_initializer_list_access(x); } + +int* missingReturn_std_throw_with_nested() { // #14374 + try { + int* p = new int(); + return p; + } + catch (...) { + std::throw_with_nested(std::runtime_error("xyz")); + } +} diff --git a/test/cfg/windows.cpp b/test/cfg/windows.cpp index c01e57f6f91..689fd425604 100644 --- a/test/cfg/windows.cpp +++ b/test/cfg/windows.cpp @@ -23,6 +23,7 @@ #include #include #include +#include bool UpdateTraceACalled(TRACEHANDLE traceHandle, LPCSTR loggerName, EVENT_TRACE_PROPERTIES* pProperties) { @@ -615,6 +616,27 @@ void memleak_malloca() // cppcheck-suppress memleak } +void memleak_create_locale() +{ + // cppcheck-suppress-begin valueFlowBailoutIncompleteVar + _locale_t locale = _create_locale(LC_ALL, "C"); + _locale_t wlocale = _wcreate_locale(LC_ALL, L"C"); + (void) locale; + (void) wlocale; + // cppcheck-suppress-end valueFlowBailoutIncompleteVar + // cppcheck-suppress memleak +} + +void no_memleak_create_locale() +{ + // cppcheck-suppress-begin valueFlowBailoutIncompleteVar + _locale_t locale = _create_locale(LC_ALL, "C"); + _locale_t wlocale = _wcreate_locale(LC_ALL, L"C"); + _free_locale(locale); + _free_locale(wlocale); + // cppcheck-suppress-end valueFlowBailoutIncompleteVar +} + void memleak_AllocateAndInitializeSid() { PSID pEveryoneSID = NULL; @@ -862,7 +884,7 @@ void uninitvar() // cppcheck-suppress uninitvar lstrcat(buf, _T("test")); buf[0] = _T('\0'); - // TODO cppcheck-suppress constVariable + // cppcheck-suppress constVariable TCHAR buf2[2]; // cppcheck-suppress lstrcatCalled // cppcheck-suppress uninitvar diff --git a/test/cli/clang-import_test.py b/test/cli/clang-import_test.py index e4ec19a5ada..da5bb2f3af7 100644 --- a/test/cli/clang-import_test.py +++ b/test/cli/clang-import_test.py @@ -128,6 +128,21 @@ def test_log(tmpdir): assert_cppcheck(args, ec_exp=0, err_exp=[], out_exp=out_lines) +def test_include(tmp_path): + test_file = tmp_path / 'test.c' + with open(test_file, 'wt') as f: + f.write('#include "test.h"') + + with open(tmp_path / 'test.h', 'wt') as f: + f.write('int x;\n') + + exitcode, stdout, stderr = cppcheck(['-q', '--clang', '--debug', str(test_file)]) + assert exitcode == 0, stderr if not stdout else stdout + # the number of empty lines after "##file" depends on the builtin declarations in the clang AST dump + assert '##file {}\n'.format(tmp_path / 'test.h') in stdout, stdout + assert '\n1: int x@var1 ;\n' in stdout, stdout + + def test_warning(tmpdir): # #12424 test_file = os.path.join(tmpdir, 'test_2') with open(test_file, 'wt') as f: diff --git a/test/cli/dumpfile_test.py b/test/cli/dumpfile_test.py index 932c4363a30..5dbb29f9e38 100644 --- a/test/cli/dumpfile_test.py +++ b/test/cli/dumpfile_test.py @@ -132,3 +132,17 @@ def test_container_methods(tmpdir): with open(dumpfile, 'rt') as f: dump = f.read() assert '' in dump + +def test_line_macro(tmpdir): + test_file = str(tmpdir / 'test.cpp') + with open(test_file, 'wt') as f: + f.write('#define MACRO() __LINE__\n') + f.write('int x = MACRO();\n') + + exitcode, _, stderr = cppcheck(['--dump', test_file]) + assert exitcode == 0, stderr + + dumpfile = test_file + '.dump' + with open(dumpfile, 'rt') as f: + dump = f.read() + assert 'type="number" isInt="true" macroName="__LINE__"' in dump diff --git a/test/cli/exclude/DebugX64.cpp b/test/cli/exclude/DebugX64.cpp new file mode 100644 index 00000000000..cfb1fce687a --- /dev/null +++ b/test/cli/exclude/DebugX64.cpp @@ -0,0 +1,8 @@ +#include + +int foo() +{ + std::cout << "DebugX64\n"; + int x = 3 / 0; (void)x; // ERROR + return 0; +} diff --git a/test/cli/exclude/ReleaseX64.cpp b/test/cli/exclude/ReleaseX64.cpp new file mode 100644 index 00000000000..8fa6e6d0f82 --- /dev/null +++ b/test/cli/exclude/ReleaseX64.cpp @@ -0,0 +1,8 @@ +#include + +int foo() +{ + std::cout << "ReleaseX64\n"; + int x = 3 / 0; (void)x; // ERROR + return 0; +} diff --git a/test/cli/exclude/exclude.cppcheck b/test/cli/exclude/exclude.cppcheck new file mode 100644 index 00000000000..50d7da84486 --- /dev/null +++ b/test/cli/exclude/exclude.cppcheck @@ -0,0 +1,16 @@ + + + exclude-cppcheck-build-dir + exclude.slnx + false + true + true + true + 2 + 100 + + Debug + + + exclude + diff --git a/test/cli/exclude/exclude.slnx b/test/cli/exclude/exclude.slnx new file mode 100644 index 00000000000..837662784ac --- /dev/null +++ b/test/cli/exclude/exclude.slnx @@ -0,0 +1,6 @@ + + + + + + diff --git a/test/cli/exclude/exclude.vcxproj b/test/cli/exclude/exclude.vcxproj new file mode 100644 index 00000000000..cc326aaec2f --- /dev/null +++ b/test/cli/exclude/exclude.vcxproj @@ -0,0 +1,94 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 18.0 + Win32Proj + {c9d1dca1-d8ff-4c05-9159-f00816645319} + exclude + 10.0 + + + + StaticLibrary + true + v145 + Unicode + + + Application + false + v145 + true + Unicode + + + + + + + + + + + + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Console + true + + + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Console + true + + + true + + + + + true + + + true + + + + + + + + + \ No newline at end of file diff --git a/test/cli/exclude/foo.h b/test/cli/exclude/foo.h new file mode 100644 index 00000000000..5d5f8f0c9e7 --- /dev/null +++ b/test/cli/exclude/foo.h @@ -0,0 +1 @@ +int foo(); diff --git a/test/cli/exclude_test.py b/test/cli/exclude_test.py new file mode 100644 index 00000000000..4f674aac34e --- /dev/null +++ b/test/cli/exclude_test.py @@ -0,0 +1,20 @@ + +# python -m pytest exclude_test.py + +import os + +from testutils import cppcheck + +__script_dir = os.path.dirname(os.path.abspath(__file__)) +__proj_dir = os.path.join(__script_dir, 'exclude') + +def test_exclude(): + args = [ + '--template=cppcheck1', + '--project=exclude/exclude.cppcheck', + '--no-cppcheck-build-dir' + ] + ret, stdout, stderr = cppcheck(args, cwd=__script_dir) + filename = os.path.join('exclude', 'DebugX64.cpp') + assert ret == 0, stdout + assert stderr == '[%s:6]: (error) Division by zero.\n' % filename diff --git a/test/cli/fuzz-crash/crash-17be2b85446aeb0c7722bedfad4b0e4af27fae3d b/test/cli/fuzz-crash/crash-17be2b85446aeb0c7722bedfad4b0e4af27fae3d new file mode 100644 index 00000000000..0f06807b7ba --- /dev/null +++ b/test/cli/fuzz-crash/crash-17be2b85446aeb0c7722bedfad4b0e4af27fae3d @@ -0,0 +1 @@ +{for([]{});} diff --git a/test/cli/fuzz-crash/crash-41fefbc326c326d02c9e1fe315280ddd326e8a5b b/test/cli/fuzz-crash/crash-41fefbc326c326d02c9e1fe315280ddd326e8a5b new file mode 100644 index 00000000000..b090d2bf345 --- /dev/null +++ b/test/cli/fuzz-crash/crash-41fefbc326c326d02c9e1fe315280ddd326e8a5b @@ -0,0 +1 @@ +#if< \ No newline at end of file diff --git a/test/cli/fuzz-crash/crash-42dad53b40278386e4ebd224813e26541cc725f1 b/test/cli/fuzz-crash/crash-42dad53b40278386e4ebd224813e26541cc725f1 new file mode 100644 index 00000000000..3a9abbf229f --- /dev/null +++ b/test/cli/fuzz-crash/crash-42dad53b40278386e4ebd224813e26541cc725f1 @@ -0,0 +1 @@ +using C=C*;C{{}} diff --git a/test/cli/fuzz-crash/crash-60d6b7bc43d21f6a8ad8eb8cca551e45c31c6a32 b/test/cli/fuzz-crash/crash-60d6b7bc43d21f6a8ad8eb8cca551e45c31c6a32 new file mode 100644 index 00000000000..4a60eb05b88 --- /dev/null +++ b/test/cli/fuzz-crash/crash-60d6b7bc43d21f6a8ad8eb8cca551e45c31c6a32 @@ -0,0 +1 @@ +ti(){using S}; diff --git a/test/cli/fuzz-crash/crash-6d019f821f348d758d8d88bd7de692af6f9b07f4 b/test/cli/fuzz-crash/crash-6d019f821f348d758d8d88bd7de692af6f9b07f4 new file mode 100644 index 00000000000..cd0cc97f0fb --- /dev/null +++ b/test/cli/fuzz-crash/crash-6d019f821f348d758d8d88bd7de692af6f9b07f4 @@ -0,0 +1 @@ +#error\ \ No newline at end of file diff --git a/test/cli/fuzz-crash/crash-793c09dbb007b0e1a295f592813f40f8d6d449d1 b/test/cli/fuzz-crash/crash-793c09dbb007b0e1a295f592813f40f8d6d449d1 new file mode 100644 index 00000000000..706fe52b407 --- /dev/null +++ b/test/cli/fuzz-crash/crash-793c09dbb007b0e1a295f592813f40f8d6d449d1 @@ -0,0 +1 @@ +enum{A=s(u)0&=s}; diff --git a/test/cli/fuzz-crash/crash-b4bd9ce1af8a423d15d034f42c7b99dc0f8a0a7d b/test/cli/fuzz-crash/crash-b4bd9ce1af8a423d15d034f42c7b99dc0f8a0a7d new file mode 100644 index 00000000000..e7b11b223da --- /dev/null +++ b/test/cli/fuzz-crash/crash-b4bd9ce1af8a423d15d034f42c7b99dc0f8a0a7d @@ -0,0 +1 @@ +d o(n c){do if(c){}while(0)n} diff --git a/test/cli/fuzz-crash/crash-def181ea7bf444e7a9d0f85b3ad264c31a0b90e4 b/test/cli/fuzz-crash/crash-def181ea7bf444e7a9d0f85b3ad264c31a0b90e4 new file mode 100644 index 00000000000..3b609c62362 --- /dev/null +++ b/test/cli/fuzz-crash/crash-def181ea7bf444e7a9d0f85b3ad264c31a0b90e4 @@ -0,0 +1 @@ +enumf:{}; diff --git a/test/cli/fuzz-crash_c/14742 b/test/cli/fuzz-crash_c/14742 new file mode 100644 index 00000000000..a645277c405 --- /dev/null +++ b/test/cli/fuzz-crash_c/14742 @@ -0,0 +1 @@ +i(){b?8:{}!$} diff --git a/test/cli/fuzz-crash_c/crash-3be27d29a1cc703716c646e3cfdf21cc896654fa b/test/cli/fuzz-crash_c/crash-3be27d29a1cc703716c646e3cfdf21cc896654fa new file mode 100644 index 00000000000..e999e558f64 --- /dev/null +++ b/test/cli/fuzz-crash_c/crash-3be27d29a1cc703716c646e3cfdf21cc896654fa @@ -0,0 +1 @@ +i(){!:!=!!?:1} diff --git a/test/cli/fuzz-crash_c/crash-401f925d3c6fd78f864ffd7bf0531bd6a76bb832 b/test/cli/fuzz-crash_c/crash-401f925d3c6fd78f864ffd7bf0531bd6a76bb832 new file mode 100644 index 00000000000..a778e6e2823 --- /dev/null +++ b/test/cli/fuzz-crash_c/crash-401f925d3c6fd78f864ffd7bf0531bd6a76bb832 @@ -0,0 +1 @@ +i(){!!:!=!&&d} diff --git a/test/cli/fuzz-crash_c/crash-85cab41781812453dfc8e74e1f0b915225f7a0a1 b/test/cli/fuzz-crash_c/crash-85cab41781812453dfc8e74e1f0b915225f7a0a1 new file mode 100644 index 00000000000..a2e58c69ea7 --- /dev/null +++ b/test/cli/fuzz-crash_c/crash-85cab41781812453dfc8e74e1f0b915225f7a0a1 @@ -0,0 +1 @@ +n(f=n){n&&,n} diff --git a/test/cli/fuzz-crash_c/crash-8a4f047728c598ad00d835c16e7fbef5e43e8489 b/test/cli/fuzz-crash_c/crash-8a4f047728c598ad00d835c16e7fbef5e43e8489 new file mode 100644 index 00000000000..3b81bf6f322 --- /dev/null +++ b/test/cli/fuzz-crash_c/crash-8a4f047728c598ad00d835c16e7fbef5e43e8489 @@ -0,0 +1 @@ +n(S=n){n*,+&&n} \ No newline at end of file diff --git a/test/cli/fuzz-crash_c/crash-9feb09f580c0cf101456c6c650ab4193b1009d07 b/test/cli/fuzz-crash_c/crash-9feb09f580c0cf101456c6c650ab4193b1009d07 new file mode 100644 index 00000000000..8577d6ccb7f --- /dev/null +++ b/test/cli/fuzz-crash_c/crash-9feb09f580c0cf101456c6c650ab4193b1009d07 @@ -0,0 +1 @@ +d(){e?&d=d++++F:a} \ No newline at end of file diff --git a/test/cli/fuzz-timeout/oom-6fd8356f0baeb3bb43463298803b94ac8ea93cac b/test/cli/fuzz-timeout/oom-6fd8356f0baeb3bb43463298803b94ac8ea93cac new file mode 100644 index 00000000000..29e36cf8c55 --- /dev/null +++ b/test/cli/fuzz-timeout/oom-6fd8356f0baeb3bb43463298803b94ac8ea93cac @@ -0,0 +1 @@ +typedef struct D{itoftor;}tor tor; \ No newline at end of file diff --git a/test/cli/fuzz-timeout/timeout-242e02017d15d072fd7a230de22faeef0816693d b/test/cli/fuzz-timeout/timeout-242e02017d15d072fd7a230de22faeef0816693d new file mode 100644 index 00000000000..c269baf1efe --- /dev/null +++ b/test/cli/fuzz-timeout/timeout-242e02017d15d072fd7a230de22faeef0816693d @@ -0,0 +1 @@ +typedef const v*v,*; \ No newline at end of file diff --git a/test/cli/fuzz-timeout/timeout-5225c0e6e895cdd05a15c388547d13a78a2574e2 b/test/cli/fuzz-timeout/timeout-5225c0e6e895cdd05a15c388547d13a78a2574e2 new file mode 100644 index 00000000000..71e751bb201 --- /dev/null +++ b/test/cli/fuzz-timeout/timeout-5225c0e6e895cdd05a15c388547d13a78a2574e2 @@ -0,0 +1 @@ +templatestruct e<; diff --git a/test/cli/fuzz-timeout/timeout-5e798df7f1bc86caef9901d6662319a4d636f269 b/test/cli/fuzz-timeout/timeout-5e798df7f1bc86caef9901d6662319a4d636f269 new file mode 100644 index 00000000000..35e56badcc8 --- /dev/null +++ b/test/cli/fuzz-timeout/timeout-5e798df7f1bc86caef9901d6662319a4d636f269 @@ -0,0 +1 @@ +typedef consted e,{} diff --git a/test/cli/inline-suppress_test.py b/test/cli/inline-suppress_test.py index ac3dc643485..065a59f5067 100644 --- a/test/cli/inline-suppress_test.py +++ b/test/cli/inline-suppress_test.py @@ -248,6 +248,30 @@ def test_build_dir(tmpdir): assert stdout == '' assert ret == 0, stdout + +def test_build_dir_include(tmpdir): + args = [ + '-q', + '--template=simple', + '--cppcheck-build-dir={}'.format(tmpdir), + '--enable=all', + '--inline-suppr', + '{}5.cpp'.format(__proj_inline_suppres_path) + ] + + ret, stdout, stderr = cppcheck(args, cwd=__script_dir) + lines = stderr.splitlines() + assert lines == [] + assert stdout == '' + assert ret == 0, stdout + + ret, stdout, stderr = cppcheck(args, cwd=__script_dir) + lines = stderr.splitlines() + assert lines == [] + assert stdout == '' + assert ret == 0, stdout + + def test_build_dir_jobs_suppressions(tmpdir): #14064 args = [ '-q', diff --git a/test/cli/more-projects_test.py b/test/cli/more-projects_test.py index 73d738d9cfb..98304bbe8aa 100644 --- a/test/cli/more-projects_test.py +++ b/test/cli/more-projects_test.py @@ -173,7 +173,6 @@ def test_project_empty_fields(tmpdir): - @@ -258,7 +257,7 @@ def test_project_std(tmpdir): -@pytest.mark.skip() # clang-tidy is not available in all cases +@pytest.mark.skip() # clang-tidy is not available in all cases - TODO: enable conditionally def test_clang_tidy(tmpdir): test_file = os.path.join(tmpdir, 'test.cpp') with open(test_file, 'wt') as f: diff --git a/test/cli/other_test.py b/test/cli/other_test.py index 5c06bd07a94..0123a97128e 100644 --- a/test/cli/other_test.py +++ b/test/cli/other_test.py @@ -1,4 +1,3 @@ - # python -m pytest test-other.py import os @@ -9,10 +8,18 @@ import subprocess import shutil -from testutils import cppcheck, assert_cppcheck, cppcheck_ex, __lookup_cppcheck_exe +from testutils import cppcheck, assert_cppcheck, cppcheck_ex, __lookup_cppcheck_exe, create_compile_commands from xml.etree import ElementTree +try: + # TODO: handle exitcode? + subprocess.call(['clang-tidy', '--version']) + __has_clang_tidy = True +except OSError: + __has_clang_tidy = False + + def __remove_verbose_log(l : list): l.remove('Defines:') l.remove('Undefines:') @@ -183,8 +190,6 @@ def test_progress(tmpdir): "progress: ValueFlow::valueFlowCondition(SymbolicConditionHandler{}, tokenlist, symboldatabase, errorLogger, settings, skippedFunctions) 1 100%\n" "progress: ValueFlow::valueFlowSymbolicInfer(symboldatabase, settings) 1 0%\n" "progress: ValueFlow::valueFlowSymbolicInfer(symboldatabase, settings) 1 100%\n" - "progress: ValueFlow::valueFlowArrayBool(tokenlist, settings) 1 0%\n" - "progress: ValueFlow::valueFlowArrayBool(tokenlist, settings) 1 100%\n" "progress: ValueFlow::valueFlowArrayElement(tokenlist, settings) 1 0%\n" "progress: ValueFlow::valueFlowArrayElement(tokenlist, settings) 1 100%\n" "progress: ValueFlow::valueFlowRightShift(tokenlist, settings) 1 0%\n" @@ -203,6 +208,8 @@ def test_progress(tmpdir): "progress: ValueFlow::valueFlowSwitchVariable(tokenlist, symboldatabase, errorLogger, settings) 1 100%\n" "progress: ValueFlow::valueFlowForLoop(tokenlist, symboldatabase, errorLogger, settings) 1 0%\n" "progress: ValueFlow::valueFlowForLoop(tokenlist, symboldatabase, errorLogger, settings) 1 100%\n" + "progress: ValueFlow::valueFlowDynamicBufferSize(tokenlist, symboldatabase, errorLogger, settings) 1 0%\n" + "progress: ValueFlow::valueFlowDynamicBufferSize(tokenlist, symboldatabase, errorLogger, settings) 1 100%\n" "progress: ValueFlow::valueFlowSubFunction(tokenlist, symboldatabase, errorLogger, settings) 1 0%\n" "progress: ValueFlow::valueFlowSubFunction(tokenlist, symboldatabase, errorLogger, settings) 1 100%\n" "progress: ValueFlow::valueFlowFunctionReturn(tokenlist, errorLogger, settings) 1 0%\n" @@ -420,7 +427,30 @@ def test_addon_misra(tmpdir): assert lines == [ 'Checking {} ...'.format(test_file) ] - assert stderr == '{}:2:1: style: misra violation (use --rule-texts= to get proper output) [misra-c2012-2.3]\ntypedef int MISRA_5_6_VIOLATION;\n^\n'.format(test_file) + assert stderr == '{}:2:13: style: misra violation (use --rule-texts= to get proper output) [misra-c2012-2.3]\ntypedef int MISRA_5_6_VIOLATION;\n ^\n'.format(test_file) + + +def test_report_type_misra_c_2025_20_6(tmpdir): # #15017 + """ using a preprocessor directive as a macro parameter is a MISRA C 20.6 violation """ + test_file = os.path.join(tmpdir, 'test.c') + with open(test_file, 'wt') as f: + f.write(""" +#define A(X) X + +A( +#ifdef __GNUC__ + 1 +#else + 0 +#endif +) +""") + + args = ['--template=simple', '--report-type=misra-c-2025', test_file] + + exitcode, stdout, stderr = cppcheck(args) + assert exitcode == 0, stdout if stdout else stderr + assert stderr == "{}:5:1: Required: failed to expand 'A', it is invalid to use a preprocessor directive as macro parameter [20.6]\n".format(test_file) def test_addon_y2038(tmpdir): @@ -450,9 +480,9 @@ def test_addon_threadsafety(tmpdir): test_file = os.path.join(tmpdir, 'test.cpp') with open(test_file, 'wt') as f: f.write(""" -extern const char* f() +extern void f() { - return strerror(1); + elf_fill(1); } """) @@ -464,7 +494,7 @@ def test_addon_threadsafety(tmpdir): assert lines == [ 'Checking {} ...'.format(test_file) ] - assert stderr == '{}:4:12: warning: strerror is MT-unsafe [threadsafety-unsafe-call]\n'.format(test_file) + assert stderr == '{}:4:5: warning: elf_fill is MT-unsafe [threadsafety-unsafe-call]\n'.format(test_file) def test_addon_naming(tmpdir): @@ -954,12 +984,9 @@ def test_unused_function_include(tmpdir): __test_unused_function_include(tmpdir, []) -# TODO: test with clang-tidy -# TODO: test with --addon -# TODO: test with FileSettings # TODO: test with multiple files -def __test_showtime(tmp_path, showtime, exp_res, exp_last, extra_args=None): - test_file = tmp_path / 'test.cpp' +def __test_showtime(tmp_path, showtime, exp_res, exp_last, use_compdb=False, use_addons=False, use_clang_tidy=False, extra_args=None): + test_file = tmp_path / 'test.cpp' # the use of C++ is intentional with open(test_file, 'wt') as f: f.write( """ @@ -972,10 +999,28 @@ def __test_showtime(tmp_path, showtime, exp_res, exp_last, extra_args=None): args = [ f'--showtime={showtime}', '--quiet', - '--inline-suppr', - str(test_file) + '--inline-suppr' ] + if use_addons: + args += ['--addon=misra', '--addon=misc'] + + if use_clang_tidy: + args += ['--clang-tidy'] + args += ['--suppress=clang-tidy-misc-use-internal-linkage'] + args += ['--suppress=clang-tidy-google-readability-casting'] + args += ['--suppress=clang-tidy-modernize-avoid-c-style-cast'] + args += ['--suppress=clang-tidy-hicpp-use-nullptr'] + args += ['--suppress=clang-tidy-modernize-use-nullptr'] + + if use_compdb: + compdb_file = tmp_path / 'compile_commands.json' + create_compile_commands(compdb_file, [test_file]) + + args.append(f'--project={compdb_file}') + else: + args.append(str(test_file)) + if extra_args: args += extra_args @@ -985,6 +1030,9 @@ def __test_showtime(tmp_path, showtime, exp_res, exp_last, extra_args=None): exp_len = exp_res if 'cppcheck internal API usage' in stdout: exp_len += 1 + if use_addons: + exp_len += 1 # TODO: should have individual entries for each addon and whole program analysis + # TODO: add entry for clang-tidy analysis exp_len += 1 # last line assert len(lines) == exp_len for i in range(1, exp_res): @@ -994,50 +1042,108 @@ def __test_showtime(tmp_path, showtime, exp_res, exp_last, extra_args=None): assert stderr == '' +def __test_showtime_top5_file(tmp_path, use_compdb=False): + __test_showtime(tmp_path, 'top5_file', 5, 'Check time: ', use_compdb=use_compdb) + + def test_showtime_top5_file(tmp_path): - __test_showtime(tmp_path, 'top5_file', 5, 'Check time: ') + __test_showtime_top5_file(tmp_path) + + +def test_showtime_top5_file_compdb(tmp_path): + __test_showtime_top5_file(tmp_path, use_compdb=True) + + +def __test_showtime_top5_summary(tmp_path, use_compdb=False): + __test_showtime(tmp_path, 'top5_summary', 5, 'Overall time: ', use_compdb=use_compdb) -# TODO: remove extra args when --executor=process works works def test_showtime_top5_summary(tmp_path): - __test_showtime(tmp_path, 'top5_summary', 5, 'Overall time: ', ['-j1']) + __test_showtime_top5_summary(tmp_path) -# TODO: remove when --executor=process works works -def test_showtime_top5_summary_j_thread(tmp_path): - __test_showtime(tmp_path, 'top5_summary', 5, 'Overall time: ', ['-j2', '--executor=thread']) +def test_showtime_top5_summary_compdb(tmp_path): + __test_showtime_top5_summary(tmp_path, use_compdb=True) -# TODO: remove override when fixed -@pytest.mark.skipif(sys.platform == 'win32', reason="requires ProcessExecutor") -@pytest.mark.xfail(strict=True) # TODO: need to transfer the timer results to parent process - see #4452 -def test_showtime_top5_summary_j_process(tmp_path): - __test_showtime(tmp_path, 'top5_summary', 5, 'Overall time: ', ['-j2', '--executor=process']) +def __test_showtime_file(tmp_path, use_compdb=False, use_addons=False, use_clang_tidy=False): + exp_res = 78 + # project analysis does not call Preprocessor::getConfig() + if use_compdb: + exp_res -= 1 + __test_showtime(tmp_path, 'file', exp_res, 'Check time: ', use_compdb=use_compdb, use_addons=use_addons, use_clang_tidy=use_clang_tidy) def test_showtime_file(tmp_path): - __test_showtime(tmp_path, 'file', 79, 'Check time: ') + __test_showtime_file(tmp_path) + + +def test_showtime_file_compdb(tmp_path): + __test_showtime_file(tmp_path, use_compdb=True) + + +def test_showtime_file_addon(tmp_path): + __test_showtime_file(tmp_path, use_addons=True) + + +def test_showtime_file_addon_compdb(tmp_path): + __test_showtime_file(tmp_path, use_addons=True, use_compdb=True) + + +@pytest.mark.skipif(not __has_clang_tidy, reason='clang-tidy is not available') +def test_showtime_file_clang_tidy(tmp_path): + __test_showtime_file(tmp_path, use_clang_tidy=True) + + +@pytest.mark.skipif(not __has_clang_tidy, reason='clang-tidy is not available') +def test_showtime_file_clang_tidy_compdb(tmp_path): + __test_showtime_file(tmp_path, use_clang_tidy=True, use_compdb=True) + + +def __test_showtime_summary(tmp_path, use_compdb=False, use_addons=False, use_clang_tidy=False): + exp_res = 78 + # project analysis does not call Preprocessor::getConfig() + if use_compdb: + exp_res -= 1 + __test_showtime(tmp_path, 'summary', exp_res, 'Overall time: ', use_compdb=use_compdb, use_addons=use_addons, use_clang_tidy=use_clang_tidy) -# TODO: remove extra args when --executor=process works works def test_showtime_summary(tmp_path): - __test_showtime(tmp_path, 'summary', 79, 'Overall time: ', ['-j1']) + __test_showtime_summary(tmp_path) -# TODO: remove when --executor=process works works -def test_showtime_summary_j_thread(tmp_path): - __test_showtime(tmp_path, 'summary', 79, 'Overall time: ', ['-j2', '--executor=thread']) +def test_showtime_summary_compdb(tmp_path): + __test_showtime_summary(tmp_path, use_compdb=True) -# TODO: remove override when fixed -@pytest.mark.skipif(sys.platform == 'win32', reason="requires ProcessExecutor") -@pytest.mark.xfail(strict=True) # TODO: need to transfer the timer results to parent process - see #4452 -def test_showtime_summary_j_process(tmp_path): - __test_showtime(tmp_path, 'summary', 79, 'Overall time: ', ['-j2', '--executor=process']) +def test_showtime_summary_addon(tmp_path): + __test_showtime_summary(tmp_path, use_addons=True) + + +def test_showtime_summary_addon_compdb(tmp_path): + __test_showtime_summary(tmp_path, use_addons=True, use_compdb=True) + + +@pytest.mark.skipif(not __has_clang_tidy, reason='clang-tidy is not available') +def test_showtime_summary_clang_tidy(tmp_path): + __test_showtime_summary(tmp_path, use_clang_tidy=True) + + +@pytest.mark.skipif(not __has_clang_tidy, reason='clang-tidy is not available') +def test_showtime_summary_clang_tidy_compdb(tmp_path): + __test_showtime_summary(tmp_path, use_clang_tidy=True, use_compdb=True) + + +def __test_showtime_file_total(tmp_path, use_compdb=False): + __test_showtime(tmp_path, 'file-total', 0, 'Check time: ', use_compdb=use_compdb) def test_showtime_file_total(tmp_path): - __test_showtime(tmp_path, 'file-total', 0, 'Check time: ') + __test_showtime_file_total(tmp_path) + + +def test_showtime_file_total_compdb(tmp_path): + __test_showtime_file_total(tmp_path, True) def test_showtime_unique(tmp_path): @@ -2235,7 +2341,7 @@ def test_builddir_hash_check_level(tmp_path): # #13376 cache_file = (build_dir / 'test.a1') root = ElementTree.fromstring(cache_file.read_text()) - hash_1 = root.get('hash') + hash_1 = root.findtext('hash') args += ['--check-level=exhaustive'] @@ -2244,7 +2350,7 @@ def test_builddir_hash_check_level(tmp_path): # #13376 assert stderr == '' root = ElementTree.fromstring(cache_file.read_text()) - hash_2 = root.get('hash') + hash_2 = root.findtext('hash') assert hash_1 != hash_2 @@ -2583,7 +2689,7 @@ def test_xml_output(tmp_path): # #13391 / #13485 - + p @@ -2674,7 +2780,7 @@ def __test_addon_suppr(tmp_path, extra_args): assert exitcode == 0, stdout assert stdout == '' assert stderr.splitlines() == [ - '{}:4:1: style: misra violation (use --rule-texts= to get proper output) [misra-c2012-2.3]'.format(test_file), + '{}:4:13: style: misra violation (use --rule-texts= to get proper output) [misra-c2012-2.3]'.format(test_file), ] @@ -3345,13 +3451,6 @@ def test_check_unused_templates_func(tmp_path): # #13714 assert stdout.splitlines() == [] assert stderr.splitlines() == [] # no error since the unused templates are not being checked -try: - # TODO: handle exitcode? - subprocess.call(['clang-tidy', '--version']) - has_clang_tidy = True -except OSError: - has_clang_tidy = False - def __test_clang_tidy(tmpdir, use_compdb): test_file = os.path.join(tmpdir, 'test.cpp') with open(test_file, 'wt') as f: @@ -3382,18 +3481,18 @@ def __test_clang_tidy(tmpdir, use_compdb): ] -@pytest.mark.skipif(not has_clang_tidy, reason='clang-tidy is not available') +@pytest.mark.skipif(not __has_clang_tidy, reason='clang-tidy is not available') @pytest.mark.xfail(strict=True) # TODO: clang-tidy is only invoked with FileSettings - see #12053 def test_clang_tidy(tmpdir): # #12053 __test_clang_tidy(tmpdir, False) -@pytest.mark.skipif(not has_clang_tidy, reason='clang-tidy is not available') +@pytest.mark.skipif(not __has_clang_tidy, reason='clang-tidy is not available') def test_clang_tidy_project(tmpdir): __test_clang_tidy(tmpdir, True) -@pytest.mark.skipif(not has_clang_tidy, reason='clang-tidy is not available') +@pytest.mark.skipif(not __has_clang_tidy, reason='clang-tidy is not available') def test_clang_tidy_error_exit(tmp_path): # #13828 / #13829 test_file = tmp_path / 'test.cpp' with open(test_file, 'wt') as f: @@ -4163,6 +4262,117 @@ def test_no_valid_configuration(tmp_path): ] +# The implementation for "A::a" is missing - so don't check if "A::b" is used or not +def test_unused_private_function_incomplete_impl(tmpdir): + test_inc = os.path.join(tmpdir, 'test.h') + with open(test_inc, 'wt') as f: + f.write( +""" +class A +{ +public: + A(); + void a(); +private: + void b(); +}; +""") + + test_file = os.path.join(tmpdir, 'test.cpp') + with open(test_file, 'wt') as f: + f.write( +""" +#include "test.h" + +A::A() { } +void A::b() { } +""") + + args = [ + '-q', + '--template=simple', + '--enable=style', + '--suppress=functionStatic', # we do not care about this - this was converted from TestUnusedPrivateFunction + test_file + ] + + ret, stdout, stderr = cppcheck(args) + assert stdout == '' + assert stderr.splitlines() == [] + assert ret == 0, stdout + + +def test_unused_private_function_multi_file(tmpdir): # ticket #2567 + test_inc = os.path.join(tmpdir, 'test.h') + with open(test_inc, 'wt') as f: + f.write( +""" +struct Fred +{ + Fred() + { + Init(); + } +private: + void Init(); +}; +""") + + test_file = os.path.join(tmpdir, 'test.cpp') + with open(test_file, 'wt') as f: + f.write( +""" +#include "test.h" + +void Fred::Init() +{ +} +""") + + args = [ + '-q', + '--template=simple', + '--enable=style', + '--suppress=functionStatic', # we do not care about this - this was converted from TestUnusedPrivateFunction + test_file + ] + + ret, stdout, stderr = cppcheck(args) + assert stdout == '' + assert stderr.splitlines() == [] + assert ret == 0, stdout + + +def test_missing_doublequote_include(tmpdir): + test_inc = os.path.join(tmpdir, 'abc.h') + with open(test_inc, 'wt') as f: + f.write( +''' +#define a +" +''') + + test_file = os.path.join(tmpdir, 'test.cpp') + with open(test_file, 'wt') as f: + f.write( +""" +#include "abc.h" +""") + + args = [ + '-q', + '--template=simple', + test_file + ] + + ret, stdout, stderr = cppcheck(args) + assert stdout == '' + assert stderr.splitlines() == [ + f"{test_inc}:3:1: error: No pair for character (\"). Can't process file. File is either invalid or unicode, which is currently not supported. [syntaxError]" + ] + assert ret == 0, stdout + + def test_no_valid_configuration_check_config(tmp_path): test_file = tmp_path / 'test.c' with open(test_file, "w") as f: @@ -4241,25 +4451,25 @@ def __test_active_checkers(tmp_path, active_cnt, total_cnt, use_misra=False, use def test_active_unusedfunction_only(tmp_path): - __test_active_checkers(tmp_path, 1, 186, use_unusedfunction_only=True) + __test_active_checkers(tmp_path, 1, 189, use_unusedfunction_only=True) def test_active_unusedfunction_only_builddir(tmp_path): checkers_exp = [ 'CheckUnusedFunctions::check' ] - __test_active_checkers(tmp_path, 1, 186, use_unusedfunction_only=True, checkers_exp=checkers_exp) + __test_active_checkers(tmp_path, 1, 189, use_unusedfunction_only=True, checkers_exp=checkers_exp) def test_active_unusedfunction_only_misra(tmp_path): - __test_active_checkers(tmp_path, 1, 386, use_unusedfunction_only=True, use_misra=True) + __test_active_checkers(tmp_path, 1, 389, use_unusedfunction_only=True, use_misra=True) def test_active_unusedfunction_only_misra_builddir(tmp_path): checkers_exp = [ 'CheckUnusedFunctions::check' ] - __test_active_checkers(tmp_path, 1, 386, use_unusedfunction_only=True, use_misra=True, checkers_exp=checkers_exp) + __test_active_checkers(tmp_path, 1, 389, use_unusedfunction_only=True, use_misra=True, checkers_exp=checkers_exp) def test_analyzerinfo(tmp_path): @@ -4342,17 +4552,17 @@ def run_and_assert_cppcheck(stdout_exp): "discarding cached result from '{}' for '{}' - unexpected root node".format(test_a1_file_s, test_file_s) ]) - # missing 'hash' attribute + # missing 'hash' node with open(test_a1_file, 'w') as f: f.write('') run_and_assert_cppcheck([ - "discarding cached result from '{}' for '{}' - no 'hash' attribute found".format(test_a1_file_s, test_file_s) + "discarding cached result from '{}' for '{}' - no 'hash' node found".format(test_a1_file_s, test_file_s) ]) - # invalid 'hash' attribute + # invalid 'hash' node with open(test_a1_file, 'w') as f: - f.write('') + f.write('hash') run_and_assert_cppcheck([ "discarding cached result from '{}' for '{}' - hash mismatch".format(test_a1_file_s, test_file_s) @@ -4363,6 +4573,47 @@ def run_and_assert_cppcheck(stdout_exp): # - internalError +def test_analyzerinfo_include_order(tmp_path): # #15132 + with open(tmp_path / 'z.h', 'wt') as f: + f.write('int z;\n') + with open(tmp_path / 'a.h', 'wt') as f: + f.write('int a;\n') + test_file = tmp_path / 'test.c' + with open(test_file, 'wt') as f: + f.write( +"""#include "z.h" +#include "a.h" +int f(void) { return a + z; } +""") + + build_dir = tmp_path / 'b1' + os.makedirs(build_dir) + + args = [ + '-q', + '--debug-analyzerinfo', + '--cppcheck-build-dir={}'.format(build_dir), + str(test_file) + ] + + test_file_s = str(test_file).replace('\\', '/') + test_a1_file_s = str(build_dir / 'test.a1').replace('\\', '/') + + exitcode, stdout, stderr = cppcheck(args) + assert exitcode == 0, stdout + assert stdout.splitlines() == [ + "no cached result '{}' for '{}' found".format(test_a1_file_s, test_file_s) + ] + assert stderr.splitlines() == [] + + exitcode, stdout, stderr = cppcheck(args) + assert exitcode == 0, stdout + assert stdout.splitlines() == [ + "skipping analysis - loaded 0 cached finding(s) from '{}' for '{}'".format(test_a1_file_s, test_file_s) + ] + assert stderr.splitlines() == [] + + def test_ctu_function_call_path_slash(tmp_path): # #14591 test_file = tmp_path / 'test.cpp' with open(test_file, "w") as f: @@ -4427,3 +4678,325 @@ def test_inline_block_suppr_builddir_twice(tmp_path): assert exitcode == 0 assert stdout == '' assert stderr == '' + + +def test_dui_include(tmp_path): + test_file = tmp_path / 'test.c' + with open(test_file, "w") as f: + f.write('void f() {}') + + inc_file = tmp_path / 'inc.h' + with open(inc_file, "w") as f: + f.write( +""" +void f_i() +{ + (void)(*((int*)0)); +} +""") + + args = [ + '-q', + '--template=simple', + f'--include={inc_file}', + str(test_file) + ] + + exitcode, stdout, stderr = cppcheck(args) + assert exitcode == 0 + assert stdout == '' + assert stderr.splitlines() == [ + f'{inc_file}:4:14: error: Null pointer dereference: (int*)0 [nullPointer]' + ] + + +def test_dui_include_missing(tmp_path): # #14675 + test_file = tmp_path / 'test.c' + with open(test_file, "w") as f: + f.write('void f() {}') + + args = [ + '-q', + '--template=simple', + '--include=missing.h', + str(test_file) + ] + + exitcode, stdout, stderr = cppcheck(args) + assert exitcode == 0 + assert stdout == '' + assert stderr.splitlines() == [ + f"{test_file}:0:0: error: Can not open include file 'missing.h' that is explicitly included. [missingIncludeExplicit]" + ] + + +def test_dui_include_relative(tmp_path): + test_file = tmp_path / 'test.c' + with open(test_file, "w") as f: + f.write('void f() {}') + + inc_file = tmp_path / 'inc.h' + with open(inc_file, "w") as f: + f.write( +""" +void f_i() +{ + (void)(*((int*)0)); +} +""") + + args = [ + '-q', + '--template=simple', + '--include=inc.h', + str(test_file) + ] + + exitcode, stdout, stderr = cppcheck(args, cwd=tmp_path) + assert exitcode == 0 + assert stdout == '' + assert stderr.splitlines() == [ + 'inc.h:4:14: error: Null pointer dereference: (int*)0 [nullPointer]' + ] + + +def test_dui_include_relative_missing(tmp_path): + test_file = tmp_path / 'test.c' + with open(test_file, "w") as f: + f.write('void f() {}') + + inc_file = tmp_path / 'inc.h' + with open(inc_file, "w") as f: + f.write( +""" +void f_i() +{ + (void)(*((int*)0)); +} +""") + + args = [ + '-q', + '--template=simple', + '--include=inc.h', + str(test_file) + ] + + exitcode, stdout, stderr = cppcheck(args,) + assert exitcode == 0 + assert stdout == '' + assert stderr.splitlines() == [ + f"{test_file}:0:0: error: Can not open include file 'inc.h' that is explicitly included. [missingIncludeExplicit]" + ] + + +def test_dui_include_absolute_missing(tmp_path): # #14675 + test_file = tmp_path / 'test.c' + with open(test_file, "w") as f: + f.write('void f() {}') + + args = [ + '-q', + '--template=simple', + '--include=/share/include/missing.h', + str(test_file) + ] + + exitcode, stdout, stderr = cppcheck(args) + assert exitcode == 0 + assert stdout == '' + assert stderr.splitlines() == [ + f"{test_file}:0:0: error: Can not open include file '/share/include/missing.h' that is explicitly included. [missingIncludeExplicit]" + ] + + +@pytest.mark.skipif(sys.platform == 'win32', reason="requires ProcessExecutor") +def test_ipc(tmp_path): + test_file = tmp_path / 'test.c' + with open(test_file, "w") as f: + f.write('void f() {}') + + args = [ + '-q', + '--debug-ipc', + '-j2', + '--executor=process', + '--no-cppcheck-build-dir', + str(test_file) + ] + + exitcode, stdout, stderr = cppcheck(args) + assert exitcode == 0 + assert stdout.splitlines() == [ + 'writeToPipe - 5 - 0', + 'handleRead - 5 - 0' + ] + assert stderr.splitlines() == [] + + +@pytest.mark.skipif(sys.platform == 'win32', reason="requires ProcessExecutor") +def test_ipc_suppressions(tmp_path): + test1_file = tmp_path / 'test1.c' + with open(test1_file, "w") as f: + f.write('void f() {}') + + test2_file = tmp_path / 'test2.c' + with open(test2_file, "w") as f: + f.write('void f() {}') + + test3_file = tmp_path / 'test3.c' + with open(test3_file, "w") as f: + f.write('void f() {}') + + args = [ + '-q', + '--debug-ipc', + '-j2', + '--executor=process', + '--no-cppcheck-build-dir', + '--suppress=id0:test1.c', + str(tmp_path) + ] + + exitcode, stdout, stderr = cppcheck(args) + assert exitcode == 0 + # sort the lines since the order is not fixed + stdout_exp = [ + 'writeToPipe - 4 - id0:test1.c;0;1;0;', + 'handleRead - 4 - id0:test1.c;0;1;0;', + 'writeToPipe - 5 - 0', + 'handleRead - 5 - 0', + 'writeToPipe - 5 - 0', + 'handleRead - 5 - 0', + 'writeToPipe - 5 - 0', + 'handleRead - 5 - 0' + ] + stdout_exp.sort() + stdout_lines = stdout.splitlines() + stdout_lines.sort() + assert stdout_lines == stdout_exp + assert stderr.splitlines() == [] + + +@pytest.mark.skipif(sys.platform == 'win32', reason="requires ProcessExecutor") +def test_ipc_inline_suppressions(tmp_path): + test1_file = tmp_path / 'test1.c' + with open(test1_file, "w") as f: + f.write('void f() {} // cppcheck-suppress id1') + + test2_file = tmp_path / 'test2.c' + with open(test2_file, "w") as f: + f.write('void f() {} // cppcheck-suppress id2') + + test3_file = tmp_path / 'test3.c' + with open(test3_file, "w") as f: + f.write('void f() {} // cppcheck-suppress id3') + + args = [ + '-q', + '--debug-ipc', + '-j2', + '--executor=process', + '--no-cppcheck-build-dir', + '--inline-suppr', + str(tmp_path) + ] + + exitcode, stdout, stderr = cppcheck(args) + assert exitcode == 0 + # sort the lines since the order is not fixed + stdout_exp = [ + f'writeToPipe - 3 - id1:{test1_file}:1;13;1;0;', + f'handleRead - 3 - id1:{test1_file}:1;13;1;0;', + 'writeToPipe - 5 - 0', + 'handleRead - 5 - 0', + f'writeToPipe - 3 - id2:{test2_file}:1;13;1;0;', + f'handleRead - 3 - id2:{test2_file}:1;13;1;0;', + 'writeToPipe - 5 - 0', + 'handleRead - 5 - 0', + f'writeToPipe - 3 - id3:{test3_file}:1;13;1;0;', + f'handleRead - 3 - id3:{test3_file}:1;13;1;0;', + 'writeToPipe - 5 - 0', + 'handleRead - 5 - 0' + ] + stdout_exp.sort() + stdout_lines = stdout.splitlines() + stdout_lines.sort() + assert stdout_lines == stdout_exp + assert stderr.splitlines() == [] + + +@pytest.mark.skipif(sys.platform == 'win32', reason="requires ProcessExecutor") +def test_ipc_hash_suppressions(tmp_path): + test_file = tmp_path / 'test.c' + with open(test_file, "w") as f: + f.write('void f() { int x = 1; }') + + _, _, stderr = cppcheck(['--enable=style', '--xml-version=3', str(test_file)]) + error_hash = ElementTree.fromstring(stderr).find('.//error').get('hash') + + suppr_file = tmp_path / 'suppr.xml' + with open(suppr_file, "w") as f: + f.write(f'unreadVariable{test_file}{error_hash}') + + args = [ + '-q', + '--debug-ipc', + '-j2', + '--executor=process', + '--no-cppcheck-build-dir', + '--enable=style', + f'--suppress-xml={suppr_file}', + str(test_file) + ] + + exitcode, stdout, stderr = cppcheck(args) + assert exitcode == 0 + # the hash suppression is not sent to the parent + assert stdout.splitlines() == [ + 'writeToPipe - 5 - 0', + 'handleRead - 5 - 0' + ] + assert stderr.splitlines() == [] + +test_redundant_file_reads_params = [ + ([], 3), + (['--suppress=zerodiv'], 1), + (['--template=cppcheck1'], 1), + (['--xml'], 1), +] + +@pytest.mark.skipif(sys.platform != 'linux' or 'ASAN_OPTIONS' in os.environ, reason="uses strace") +@pytest.mark.parametrize('flags,expected', test_redundant_file_reads_params) +def test_redundant_file_reads(tmpdir, flags, expected): + source_pathname = os.path.join(tmpdir, 'test.c') + content = """ +void f(int x) { + int y = x / 0; + int z = x / 0; +} +""" + cppcheck_path = __lookup_cppcheck_exe() + + with open(source_pathname, 'wt') as f: + f.write(content) + + args = [ + 'strace', + '--summary-only', + '--summary-columns=count', + '--trace=openat', + '--follow-forks', + f'--trace-path={source_pathname}', + cppcheck_path, + '-q', + source_pathname, + ] + + args += flags + proc = subprocess.Popen(args, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE) + + _, stderr = proc.communicate() + + assert proc.returncode == 0 + assert stderr.splitlines()[-1].strip() == f'{expected} total'.encode('utf-8') diff --git a/test/cli/performance_test.py b/test/cli/performance_test.py index 55da3b3b04e..ea1ad8bd5ff 100644 --- a/test/cli/performance_test.py +++ b/test/cli/performance_test.py @@ -3,6 +3,7 @@ import os import sys +import time import pytest @@ -267,7 +268,7 @@ def test_crash_array_in_namespace(tmpdir): @pytest.mark.skipif(sys.platform == 'darwin', reason='GitHub macOS runners are too slow') -@pytest.mark.timeout(20) +@pytest.mark.timeout(30) def test_crash_array_in_array(tmpdir): # 12861 filename = os.path.join(tmpdir, 'hang.cpp') @@ -413,3 +414,67 @@ class C { } }""") cppcheck([filename]) # should not take more than ~1 second + + +@pytest.mark.timeout(60) +def test_slow_many_headers(tmpdir): + # 14113 + c_file = os.path.join(tmpdir, 'source.c') + h_file = os.path.join(tmpdir, 'header.h') + n_hdr = 128 + with open(c_file, 'wt') as f: + f.write('#include "header.h"\n') + with open(h_file, 'wt') as f: + for i in range(n_hdr): + f.write(f'#ifdef CONFIG{i}\n#include "header{i}.h"\n#endif\n') + for i in range(n_hdr): + h_file_i = os.path.join(tmpdir, f"header{i}.h") + with open(h_file_i, 'wt') as f: + for j in range(2048): + f.write(f'#define MACRO{j}{(" "+str(j))*128}\n') + f.write(f'MACRO{i}\n') + # creating the files used for testing can be slow, so we use perf counter here instead + start = time.perf_counter_ns() + cppcheck(['-DCONFIG0', c_file]) + end = time.perf_counter_ns() + assert end - start < 2 * 10**9 # max 2 sec + + +@pytest.mark.skipif(sys.platform == 'darwin', reason='GitHub macOS runners are too slow') +@pytest.mark.timeout(5) +def test_large_number_of_violations_and_suppressions(tmpdir): + filename_main = os.path.join(tmpdir, 'main.c') + # This name causes the PathMatch::match() to iterate ~70 times, which is not unrealistic for a header file placed in subdirs. + # The number of iterations also depends on how the suppressions are written. + header_name = 'long_filename_to_simulate_a_more_realistic_header_placed_in_subdirs.h' + header_file = os.path.join(tmpdir, header_name) + suppressions_file = os.path.join(tmpdir, 'suppressions.txt') + + # Create a main file that includes a header and returns 0. + with open(filename_main, "w") as f: + f.write(f'#include "{header_name}"\n\n') + f.write('int main() \n') + f.write('{\n') + f.write(' return 0;\n') + f.write('}\n') + + # Create a header file with macro definitions that violates misra because they start with an underscore. + with open(header_file, "w") as f: + f.write(f'#ifndef {header_name.upper().replace(".", "_")}\n') + f.write(f'#define {header_name.upper().replace(".", "_")}\n') + f.write('\n') + for i in range(5000): + f.write(f'#define _FOO{i} {i}\n') + f.write('\n') + f.write(f'#endif // {header_name.upper().replace(".", "_")}\n') + + # Create a suppressions file that suppresses the misra violation for the macros in the header file + with open(suppressions_file, "w") as f: + f.write(f'misra-c2012-2.5:{header_name}\n') + f.write(f'misra-c2012-21.1:{header_name}\n') + # Create other suppressions that don't match the file name to test that iterating the suppressions are faster with cache + for i in range(300): + f.write(f'misra-c2012-2.5:**/{i}/{header_name}\n') + f.write(f'misra-c2012-21.1:**/{i}/{header_name}\n') + + cppcheck([filename_main] + ['--addon=misra.py', '--check-level=exhaustive', '--enable=all', f'--suppressions-list={suppressions_file}']) diff --git a/test/cli/premium_test.py b/test/cli/premium_test.py index ddf7b1e2fbe..820f7b680e9 100644 --- a/test/cli/premium_test.py +++ b/test/cli/premium_test.py @@ -88,11 +88,11 @@ def test_build_dir_hash_cppcheck_product(tmpdir): assert exitcode == 0 def _get_hash(s:str): - i = s.find(' hash="') + i = s.find('') if i <= -1: return '' i += 7 - return s[i:s.find('"', i)] + return s[i:s.find('', i)] with open(build_dir.join('test.a1'), 'rt') as f: f1 = f.read() diff --git a/test/cli/proj-inline-suppress/5.cpp b/test/cli/proj-inline-suppress/5.cpp new file mode 100644 index 00000000000..c2c717ba8b3 --- /dev/null +++ b/test/cli/proj-inline-suppress/5.cpp @@ -0,0 +1 @@ +#include "5.h" diff --git a/test/cli/proj-inline-suppress/5.h b/test/cli/proj-inline-suppress/5.h new file mode 100644 index 00000000000..63967f45e28 --- /dev/null +++ b/test/cli/proj-inline-suppress/5.h @@ -0,0 +1,5 @@ +struct expected +{ + // cppcheck-suppress noExplicitConstructor + expected(int){} +}; diff --git a/test/cli/proj-inline-suppress/cfg.c b/test/cli/proj-inline-suppress/cfg.c index b597217fa32..42709095dcf 100644 --- a/test/cli/proj-inline-suppress/cfg.c +++ b/test/cli/proj-inline-suppress/cfg.c @@ -2,9 +2,9 @@ void f() { #if DEF_1 // cppcheck-suppress id - (void)0; + ; #endif // cppcheck-suppress id - (void)0; + ; } diff --git a/test/cli/proj2_test.py b/test/cli/proj2_test.py index c9516d9ddbf..aa8cf6d3355 100644 --- a/test/cli/proj2_test.py +++ b/test/cli/proj2_test.py @@ -18,6 +18,11 @@ 'x = 3 / 0;\n' + ' ^\n') % os.path.join('b', 'b.c') +def __get_lines(s): + # file order is not guaranteed when multiple jobs are used (TEST_CPPCHECK_INJECT_J) so + # compare output order-independently + return sorted(s.split('\n')) + def __create_compile_commands(proj_dir): proj_dir = str(proj_dir) j = [{'directory': os.path.join(proj_dir, 'a'), 'command': 'gcc -c a.c', 'file': 'a.c'}, @@ -152,7 +157,7 @@ def test_gui_project_loads_relative_vs_solution_2(tmp_path): create_gui_project_file(os.path.join(tmp_path, 'test.cppcheck'), root_path='proj2', import_project='proj2/proj2.sln') ret, stdout, stderr = cppcheck(['--project=test.cppcheck'], cwd=tmp_path) assert ret == 0, stdout - assert stderr == __ERR_A + __ERR_B + assert __get_lines(stderr) == __get_lines(__ERR_A + __ERR_B) def test_gui_project_loads_relative_vs_solution_with_exclude(tmp_path): proj_dir = tmp_path / 'proj2' @@ -170,4 +175,4 @@ def test_gui_project_loads_absolute_vs_solution_2(tmp_path): import_project=os.path.join(proj_dir, 'proj2.sln')) ret, stdout, stderr = cppcheck(['--project=test.cppcheck'], cwd=tmp_path) assert ret == 0, stdout - assert stderr == __ERR_A + __ERR_B + assert __get_lines(stderr) == __get_lines(__ERR_A + __ERR_B) diff --git a/test/cli/project-suppressions.py b/test/cli/project-suppressions.py new file mode 100644 index 00000000000..e2b6ba5e7d6 --- /dev/null +++ b/test/cli/project-suppressions.py @@ -0,0 +1,33 @@ + +# python -m pytest project-suppressions.py + +from testutils import create_gui_project_file, assert_cppcheck + +def test_cli_and_project_suppressions(tmp_path): + # Uninitvar suppressed in project file + suppressions = [{ 'id': 'uninitvar' }] + project_path = tmp_path / 'project.cppcheck' + create_gui_project_file(project_path, root_path=str(tmp_path), suppressions=suppressions) + + # Uninitvar suppressed on command line before import + args = ['--suppress=uninitvar', f'--project={project_path}'] + out_exp = [ + "cppcheck: error: suppression 'uninitvar' already exists", + f"cppcheck: error: failed to load project '{project_path}'. An error occurred." + ] + assert_cppcheck(args, ec_exp=1, out_exp=out_exp) + +def test_multiple_cli_and_project_suppressions(tmp_path): + # Uninitvar and unreadVariable suppressed in project file + suppressions = [{ 'id': 'uninitvar' }, { 'id': 'unreadVariable' },] + project_path = tmp_path / 'project.cppcheck' + create_gui_project_file(project_path, root_path=str(tmp_path), suppressions=suppressions) + + # Uninitvar and unreadVariable suppressed on command line before import + args = ['--suppress=uninitvar', '--suppress=unreadVariable', f'--project={project_path}'] + out_exp = [ + "cppcheck: error: suppression 'uninitvar' already exists", + "cppcheck: error: suppression 'unreadVariable' already exists", + f"cppcheck: error: failed to load project '{project_path}'. An error occurred." + ] + assert_cppcheck(args, ec_exp=1, out_exp=out_exp) diff --git a/test/cli/project_test.py b/test/cli/project_test.py index 7421ca406a6..86d528f074f 100644 --- a/test/cli/project_test.py +++ b/test/cli/project_test.py @@ -20,9 +20,9 @@ def test_missing_project(project_ext): def __test_project_error(tmpdir, ext, content, expected): project_file = os.path.join(tmpdir, "file.{}".format(ext)) - with open(project_file, 'w') as f: + with open(project_file, 'wb') as f: if content is not None: - f.write(content) + f.write(content.encode('utf-8')) ret, stdout, stderr = cppcheck(['--project=' + str(project_file)]) assert 1 == ret @@ -102,7 +102,7 @@ def test_sln_invalid_file(tmpdir): def test_sln_no_header(tmpdir): - content = "\xEF\xBB\xBF\r\n" \ + content = "\r\n" \ "some header" expected = "Visual Studio solution file header not found" @@ -111,7 +111,7 @@ def test_sln_no_header(tmpdir): def test_sln_no_projects(tmpdir): - content = "\xEF\xBB\xBF\r\n" \ + content = "\r\n" \ "Microsoft Visual Studio Solution File, Format Version 12.00\r\n" expected = "no projects found in Visual Studio solution file" @@ -120,7 +120,7 @@ def test_sln_no_projects(tmpdir): def test_sln_project_file_not_found(tmpdir): - content = "\xEF\xBB\xBF\r\n" \ + content = "\r\n" \ "Microsoft Visual Studio Solution File, Format Version 12.00\r\n" \ "# Visual Studio Version 16\r\n" \ "VisualStudioVersion = 16.0.29020.237\r\n" \ @@ -147,6 +147,16 @@ def test_slnx_no_xml_root(tmpdir): __test_project_error(tmpdir, "slnx", content, expected) +def test_slnx_invalid_xml_root(tmpdir): + content = '\r\n' \ + "\r\n" \ + "\r\n" + + expected = "Invalid Visual Studio solution file format" + + __test_project_error(tmpdir, "slnx", content, expected) + + def test_slnx_no_projects(tmpdir): content = '\r\n' \ "\r\n" \ @@ -161,6 +171,22 @@ def test_slnx_no_projects(tmpdir): __test_project_error(tmpdir, "slnx", content, expected) +def test_slnx_no_projects_in_folder(tmpdir): + content = '\r\n' \ + "\r\n" \ + " \r\n" \ + ' \r\n' \ + ' \r\n' \ + " \r\n" \ + ' \r\n' \ + ' \r\n' \ + "\r\n" + + expected = "no projects found in Visual Studio solution file" + + __test_project_error(tmpdir, "slnx", content, expected) + + def test_slnx_project_file_not_found(tmpdir): content = '\r\n' \ "\r\n" \ @@ -179,6 +205,26 @@ def test_slnx_project_file_not_found(tmpdir): __test_project_error(tmpdir, "slnx", content, expected) +def test_slnx_project_file_in_folder_not_found(tmpdir): + content = '\r\n' \ + "\r\n" \ + " \r\n" \ + ' \r\n' \ + ' \r\n' \ + " \r\n" \ + ' \r\n' \ + ' \r\n' \ + ' \r\n' \ + "\r\n" + + expected = "Visual Studio project file is not a valid XML - XML_ERROR_FILE_NOT_FOUND\n" \ + "cppcheck: error: failed to load '{}' from Visual Studio solution".format(os.path.join(tmpdir, "common/test.vcxproj")) + if sys.platform == "win32": + expected = expected.replace('\\', '/') + + __test_project_error(tmpdir, "slnx", content, expected) + + def test_vcxproj_no_xml_root(tmpdir): content = '' diff --git a/test/cli/props-dirs/Cpp.Build.props b/test/cli/props-dirs/Cpp.Build.props new file mode 100644 index 00000000000..c7ec9783a4b --- /dev/null +++ b/test/cli/props-dirs/Cpp.Build.props @@ -0,0 +1,12 @@ + + + + + + + Debug + x64 + + + + diff --git a/test/cli/props-dirs/Cpp.Build.targets b/test/cli/props-dirs/Cpp.Build.targets new file mode 100644 index 00000000000..341027f3c7a --- /dev/null +++ b/test/cli/props-dirs/Cpp.Build.targets @@ -0,0 +1,3 @@ + + + diff --git a/test/cli/props-dirs/Directory.Build.props b/test/cli/props-dirs/Directory.Build.props new file mode 100644 index 00000000000..0e0ec4010eb --- /dev/null +++ b/test/cli/props-dirs/Directory.Build.props @@ -0,0 +1,6 @@ + + + $(MSBuildThisFileDirectory) + + + diff --git a/test/cli/props-dirs/Directory.Build.targets b/test/cli/props-dirs/Directory.Build.targets new file mode 100644 index 00000000000..8c119d5413b --- /dev/null +++ b/test/cli/props-dirs/Directory.Build.targets @@ -0,0 +1,2 @@ + + diff --git a/test/cli/props-dirs/ProjA/ProjA.vcxproj b/test/cli/props-dirs/ProjA/ProjA.vcxproj new file mode 100644 index 00000000000..9ce10068a8b --- /dev/null +++ b/test/cli/props-dirs/ProjA/ProjA.vcxproj @@ -0,0 +1,27 @@ + + + + {a1a1a1a1-a1a1-a1a1-a1a1-a1a1a1a1a1a1} + ProjA + + + + Application + v143 + + + + + + + + + + PROJA_DEFINE;%(PreprocessorDefinitions) + + + + + + + diff --git a/test/cli/props-dirs/ProjA/a.cpp b/test/cli/props-dirs/ProjA/a.cpp new file mode 100644 index 00000000000..aab1237748c --- /dev/null +++ b/test/cli/props-dirs/ProjA/a.cpp @@ -0,0 +1,15 @@ +#include "common.h" +#include "common2.h" + +#ifndef COMMON_H_INCLUDED_MARKER +#error "common.h was not found - AdditionalIncludeDirectories from common.props did not resolve" +#endif +#ifndef COMMON2_H_INCLUDED_MARKER +#error "common2.h was not found - AdditionalIncludeDirectories from common.props did not resolve" +#endif + +int main() +{ + int x = 1; + return x / 0; +} diff --git a/test/cli/props-dirs/ProjB/ProjB.vcxproj b/test/cli/props-dirs/ProjB/ProjB.vcxproj new file mode 100644 index 00000000000..3daf8907924 --- /dev/null +++ b/test/cli/props-dirs/ProjB/ProjB.vcxproj @@ -0,0 +1,24 @@ + + + + {b2b2b2b2-b2b2-b2b2-b2b2-b2b2b2b2b2b2} + ProjB + + + + Application + v143 + + + + + + + + + + + + diff --git a/test/cli/props-dirs/ProjB/b.cpp b/test/cli/props-dirs/ProjB/b.cpp new file mode 100644 index 00000000000..8c3e4016529 --- /dev/null +++ b/test/cli/props-dirs/ProjB/b.cpp @@ -0,0 +1,15 @@ +#include "common.h" +#include "common2.h" + +#ifndef COMMON_H_INCLUDED_MARKER +#error "common.h was not found - AdditionalIncludeDirectories from common.props did not resolve" +#endif +#ifndef COMMON2_H_INCLUDED_MARKER +#error "common2.h was not found - AdditionalIncludeDirectories from common.props did not resolve" +#endif + +int main() +{ + int y = 2; + return y / 0; +} diff --git a/test/cli/props-dirs/common/common.h b/test/cli/props-dirs/common/common.h new file mode 100644 index 00000000000..72674fc6c62 --- /dev/null +++ b/test/cli/props-dirs/common/common.h @@ -0,0 +1,3 @@ +#ifndef COMMON_H_INCLUDED_MARKER +#define COMMON_H_INCLUDED_MARKER +#endif diff --git a/test/cli/props-dirs/common/common.props b/test/cli/props-dirs/common/common.props new file mode 100644 index 00000000000..f969a6e897d --- /dev/null +++ b/test/cli/props-dirs/common/common.props @@ -0,0 +1,13 @@ + + + + + + COMMON_DEFINE;%(PreprocessorDefinitions) + $(MSBuildThisFileDirectory);%(AdditionalIncludeDirectories) + stdcpp17 + + + diff --git a/test/cli/props-dirs/common/common2.h b/test/cli/props-dirs/common/common2.h new file mode 100644 index 00000000000..320e68a96fa --- /dev/null +++ b/test/cli/props-dirs/common/common2.h @@ -0,0 +1,3 @@ +#ifndef COMMON2_H_INCLUDED_MARKER +#define COMMON2_H_INCLUDED_MARKER +#endif diff --git a/test/cli/props-dirs/common/common2.props b/test/cli/props-dirs/common/common2.props new file mode 100644 index 00000000000..cf945d49ffa --- /dev/null +++ b/test/cli/props-dirs/common/common2.props @@ -0,0 +1,13 @@ + + + + + + COMMON2_DEFINE;%(PreprocessorDefinitions) + $(MSBuildThisFileDirectory);%(AdditionalIncludeDirectories) + stdcpp17 + + + diff --git a/test/cli/props-dirs/props-dirs.slnx b/test/cli/props-dirs/props-dirs.slnx new file mode 100644 index 00000000000..0aae89ada2a --- /dev/null +++ b/test/cli/props-dirs/props-dirs.slnx @@ -0,0 +1,7 @@ + + + + + + + diff --git a/test/cli/props-dirs/shared/shared.props b/test/cli/props-dirs/shared/shared.props new file mode 100644 index 00000000000..623e1094e9f --- /dev/null +++ b/test/cli/props-dirs/shared/shared.props @@ -0,0 +1,14 @@ + + + + + + + + + SHARED_DEFINE;%(PreprocessorDefinitions) + + + diff --git a/test/cli/props-dirs/shared/shared2.props b/test/cli/props-dirs/shared/shared2.props new file mode 100644 index 00000000000..745a5d45a21 --- /dev/null +++ b/test/cli/props-dirs/shared/shared2.props @@ -0,0 +1,14 @@ + + + + + + + + + SHARED2_DEFINE;%(PreprocessorDefinitions) + + + diff --git a/test/cli/props_dirs_test.py b/test/cli/props_dirs_test.py new file mode 100644 index 00000000000..bccfe3cf2bc --- /dev/null +++ b/test/cli/props_dirs_test.py @@ -0,0 +1,93 @@ + +# python -m pytest props_dirs_test.py +# +# Regression coverage for MSBuild property-sheet (.props) loading across multiple +# directories: +# - $(MSBuildThisFileDirectory) must resolve to each .props file's own directory, +# not the importing project's directory, even through a chain of nested imports +# (ProjA/ -> shared/shared.props -> common/common.props). +# - AdditionalIncludeDirectories set via that chain must actually make a header in a +# different directory (common/common.h) resolvable from the project's source file. +# - A project that imports common/common.props directly (ProjB) must pick up exactly +# what that file sets and nothing that a *different* project in the same solution +# (ProjA) added on top - no cross-project variable leakage. + +import os + +from testutils import cppcheck + +__script_dir = os.path.dirname(os.path.abspath(__file__)) + +__ERR_A = ('%s:14:14: error: Division by zero. [zerodiv]\n' + ' return x / 0;\n' + ' ^\n') % os.path.join('props-dirs', 'ProjA', 'a.cpp') +__ERR_B = ('%s:14:14: error: Division by zero. [zerodiv]\n' + ' return y / 0;\n' + ' ^\n') % os.path.join('props-dirs', 'ProjB', 'b.cpp') + + +def __get_lines(s): + # file order is not guaranteed when multiple jobs are used (TEST_CPPCHECK_INJECT_J) so + # compare output order-independently + return sorted(s.split('\n')) + + +def test_props_dirs_solution(): + args = [ + '--project=props-dirs/props-dirs.slnx', + '--no-cppcheck-build-dir' + ] + ret, stdout, stderr = cppcheck(args, cwd=__script_dir) + assert ret == 0, stdout + + # both files were actually analyzed (division by zero fires) which also proves + # "common.h" was found via AdditionalIncludeDirectories - if it hadn't resolved, the + # #error guard in each .cpp would have fired instead and there would be no zerodiv + assert __get_lines(stderr) == __get_lines(__ERR_A + __ERR_B) + + +def test_props_dirs_defines_and_standard(): + args = [ + '--project=props-dirs/props-dirs.slnx', + '--no-cppcheck-build-dir', + '--dump' + ] + ret, stdout, _ = cppcheck(args, cwd=__script_dir) + assert ret == 0, stdout + + dump_a = os.path.join(__script_dir, 'props-dirs', 'ProjA', 'a.cpp.dump') + dump_b = os.path.join(__script_dir, 'props-dirs', 'ProjB', 'b.cpp.dump') + assert os.path.exists(dump_a), f"Dump file not found at {dump_a}" + assert os.path.exists(dump_b), f"Dump file not found at {dump_b}" + + with open(dump_a, 'rt') as f: + dump_a_content = f.read() + with open(dump_b, 'rt') as f: + dump_b_content = f.read() + + # ProjA imports shared/shared.props (-> common/common.props) and shared/shared2.props + # (-> common/common2.props), and sets its own PROJA_DEFINE. + # v143 toolset -> _MSC_VER=1930/_MSC_FULL_VER=193000000; common.props sets stdcpp17 + # -> _MSVC_LANG=201703L. + assert '_WIN32=1' in dump_a_content + assert '_WIN64=1' in dump_a_content + assert '_M_X64=100' in dump_a_content + assert '_MSC_VER=1930' in dump_a_content + assert '_MSC_FULL_VER=193000000' in dump_a_content + assert '_MSVC_LANG=201703L' in dump_a_content + assert 'PROJA_DEFINE=1' in dump_a_content + assert 'SHARED_DEFINE=1' in dump_a_content + assert 'COMMON_DEFINE=1' in dump_a_content + assert '' in dump_a_content + + # ProjB imports common/common.props and common/common2.props directly - it must see + # COMMON2_DEFINE and COMMON_DEFINE, but none of ProjA's or shared's defines. + assert '_MSC_VER=1930' in dump_b_content + assert '_MSC_FULL_VER=193000000' in dump_b_content + assert '_MSVC_LANG=201703L' in dump_b_content + assert 'COMMON2_DEFINE=1' in dump_b_content + assert 'COMMON_DEFINE=1' in dump_b_content + assert '' in dump_b_content + assert 'PROJA_DEFINE' not in dump_b_content + assert 'SHARED_DEFINE' not in dump_b_content + assert 'SHARED2_DEFINE' not in dump_b_content diff --git a/test/cli/slnx-folders/app/app.cpp b/test/cli/slnx-folders/app/app.cpp new file mode 100644 index 00000000000..3454c2de254 --- /dev/null +++ b/test/cli/slnx-folders/app/app.cpp @@ -0,0 +1,7 @@ +#include "../lib/lib.h" + +int main(int argc, char *argv[]) +{ + int x = 3 / 0; (void)x; // ERROR + return foo(); +} diff --git a/test/cli/slnx-folders/app/app.vcxproj b/test/cli/slnx-folders/app/app.vcxproj new file mode 100644 index 00000000000..b72ae55c565 --- /dev/null +++ b/test/cli/slnx-folders/app/app.vcxproj @@ -0,0 +1,102 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 18.0 + Win32Proj + {0de77c38-881a-4f9f-bdfa-2c429968985e} + unused + 10.0 + + + + Application + true + v145 + Unicode + + + Application + false + v145 + true + Unicode + + + + + + + + + + + + + + + ..\x64\Debug\ + x64\Debug\ + + + ..\x64\Release\ + x64\Release\ + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + + + + + Console + true + ../lib + + + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Console + true + + + true + + + + + + + + {93BE1430-BE74-35DF-1882-AF70E49C9898} + + + + + + diff --git a/test/cli/slnx-folders/lib/lib.cpp b/test/cli/slnx-folders/lib/lib.cpp new file mode 100644 index 00000000000..b4a89cee1c7 --- /dev/null +++ b/test/cli/slnx-folders/lib/lib.cpp @@ -0,0 +1,9 @@ +#include +#include "lib.h" + +int foo() +{ + std::cout << "hello world\n"; + int x = 3 / 0; (void)x; // ERROR + return 0; +} diff --git a/test/cli/slnx-folders/lib/lib.h b/test/cli/slnx-folders/lib/lib.h new file mode 100644 index 00000000000..cf790ac3eab --- /dev/null +++ b/test/cli/slnx-folders/lib/lib.h @@ -0,0 +1 @@ +extern int foo(); diff --git a/test/cli/slnx-folders/lib/lib.vcxproj b/test/cli/slnx-folders/lib/lib.vcxproj new file mode 100644 index 00000000000..232c2478e2f --- /dev/null +++ b/test/cli/slnx-folders/lib/lib.vcxproj @@ -0,0 +1,97 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 18.0 + Win32Proj + {93BE1430-BE74-35DF-1882-AF70E49C9898} + unused + 10.0 + + + + StaticLibrary + true + v145 + Unicode + + + StaticLibrary + false + v145 + true + Unicode + + + + + + + + + + + + + + + ..\x64\Debug\ + x64\Debug\ + + + ..\x64\Release\ + x64\Release\ + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + + + + + Console + true + ../lib + + + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Console + true + + + true + + + + + + + + + diff --git a/test/cli/slnx-folders/slnx-folders.cppcheck b/test/cli/slnx-folders/slnx-folders.cppcheck new file mode 100644 index 00000000000..4fa0c4e7623 --- /dev/null +++ b/test/cli/slnx-folders/slnx-folders.cppcheck @@ -0,0 +1,17 @@ + + + slnx-folders-cppcheck-build-dir + slnx-folders.slnx + false + true + true + true + 2 + 100 + + Debug + Release + + + slnx-folders + diff --git a/test/cli/slnx-folders/slnx-folders.slnx b/test/cli/slnx-folders/slnx-folders.slnx new file mode 100644 index 00000000000..fd50c3a054c --- /dev/null +++ b/test/cli/slnx-folders/slnx-folders.slnx @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/test/cli/slnx-folders_test.py b/test/cli/slnx-folders_test.py new file mode 100644 index 00000000000..b9dd5f09c92 --- /dev/null +++ b/test/cli/slnx-folders_test.py @@ -0,0 +1,73 @@ + +# python -m pytest slnx-folders_test.py + +import os +import re + +from testutils import cppcheck + +__script_dir = os.path.dirname(os.path.abspath(__file__)) +__proj_dir = os.path.join(__script_dir, 'slnx-folders') + +def get_lines(s): + return sorted(s.split('\n')) + +# Get Visual Studio configurations checking a file +# Checking {file} {config}... +def __getVsConfigs(stdout, filename): + ret = [] + for line in stdout.split('\n'): + if not line.startswith('Checking %s ' % filename): + continue + if not line.endswith('...'): + continue + res = re.match(r'.* ([A-Za-z0-9|]+)...', line) + if res: + ret.append(res.group(1)) + ret.sort() + return ' '.join(ret) + +def test_relative_path(): + args = [ + '--template=cppcheck1', + 'slnx-folders' + ] + ret, stdout, stderr = cppcheck(args, cwd=__script_dir) + filename1 = os.path.join('slnx-folders', 'app', 'app.cpp') + filename2 = os.path.join('slnx-folders', 'lib', 'lib.cpp') + assert ret == 0, stdout + expected = ( + '[%s:5]: (error) Division by zero.\n' + '[%s:7]: (error) Division by zero.\n' % (filename1, filename2) + ) + assert get_lines(stderr) == get_lines(expected) + +def test_local_path(): + args = [ + '--template=cppcheck1', + '.' + ] + ret, stdout, stderr = cppcheck(args, cwd=__proj_dir) + filename1 = os.path.join('app', 'app.cpp') + filename2 = os.path.join('lib', 'lib.cpp') + assert ret == 0, stdout + expected = ( + '[%s:5]: (error) Division by zero.\n' + '[%s:7]: (error) Division by zero.\n' % (filename1, filename2) + ) + assert get_lines(stderr) == get_lines(expected) + +def test_absolute_path(): + args = [ + '--template=cppcheck1', + __proj_dir + ] + ret, stdout, stderr = cppcheck(args) + filename1 = os.path.join(__proj_dir, 'app', 'app.cpp') + filename2 = os.path.join(__proj_dir, 'lib', 'lib.cpp') + assert ret == 0, stdout + expected = ( + '[%s:5]: (error) Division by zero.\n' + '[%s:7]: (error) Division by zero.\n' % (filename1, filename2) + ) + assert get_lines(stderr) == get_lines(expected) diff --git a/test/cli/testutils.py b/test/cli/testutils.py index f352af49f15..2140c42205b 100644 --- a/test/cli/testutils.py +++ b/test/cli/testutils.py @@ -5,6 +5,7 @@ import subprocess import time import tempfile +import json # Create Cppcheck project file import sys @@ -45,6 +46,22 @@ def create_gui_project_file(project_file, root_path=None, import_project=None, p f.write(cppcheck_xml) +def create_compile_commands(compdb_file, files): + compdb = [] + # TODO: bail out on empty list + for f in files: + compdb.append( + { + "file": str(f), + "directory": os.path.dirname(f), + "command": "gcc -c {}".format(os.path.basename(f)) + } + ) + compdb_j = json.dumps(compdb) + with open(compdb_file, 'wt') as f: + f.write(compdb_j) + + def __lookup_cppcheck_exe(): # path the script is located in script_path = os.path.dirname(os.path.realpath(__file__)) diff --git a/test/cli/vcxproj-unicode/main.cpp b/test/cli/vcxproj-unicode/main.cpp new file mode 100644 index 00000000000..1a0e6f02e86 --- /dev/null +++ b/test/cli/vcxproj-unicode/main.cpp @@ -0,0 +1,7 @@ +#include + +int main() { + std::cout << "Hello world!" << std::endl; + return 0; +} + diff --git a/test/cli/vcxproj-unicode/vcxproj_unicode.vcxproj b/test/cli/vcxproj-unicode/vcxproj_unicode.vcxproj new file mode 100644 index 00000000000..cc7549a0e06 --- /dev/null +++ b/test/cli/vcxproj-unicode/vcxproj_unicode.vcxproj @@ -0,0 +1,43 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + MultiByte + Win32 + + + + + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + NotSet + Static + + + Application + false + v143 + MultiByte + + + + + diff --git a/test/cli/vcxproj_choose/main.cpp b/test/cli/vcxproj_choose/main.cpp new file mode 100644 index 00000000000..76e8197013a --- /dev/null +++ b/test/cli/vcxproj_choose/main.cpp @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/test/cli/vcxproj_choose/vcxproj_choose.vcxproj b/test/cli/vcxproj_choose/vcxproj_choose.vcxproj new file mode 100644 index 00000000000..f83db51b5fb --- /dev/null +++ b/test/cli/vcxproj_choose/vcxproj_choose.vcxproj @@ -0,0 +1,61 @@ + + + + + Debug + x64 + + + + {33333333-3333-3333-3333-333333333333} + choosetest + 10.0 + + + + Application + v143 + + + + + + + + + CHOSEN_BRANCH=When;%(PreprocessorDefinitions) + + + + true + + + + + + CHOSEN_BRANCH=Otherwise;%(PreprocessorDefinitions) + + + + true + + + + + + + + + + + true + + + + diff --git a/test/cli/vcxproj_choose_test.py b/test/cli/vcxproj_choose_test.py new file mode 100644 index 00000000000..a261dfada7b --- /dev/null +++ b/test/cli/vcxproj_choose_test.py @@ -0,0 +1,44 @@ + +# python -m pytest vcxproj_choose_test.py +# +# Regression coverage for MSBuild/Visual Studio // +# support: Visual Studio's project evaluator (the same Microsoft.Build engine +# msbuild.exe uses) selects a Choose's branch once, using properties as +# accumulated at that point in the document, and reuses that same branch for +# item-definition and item evaluation -- it does not re-evaluate the +# Condition a second time against final properties. This fixture's +# depends on a property that is only assigned by a PropertyGroup positioned +# AFTER the Choose; if cppcheck dropped Choose/When/Otherwise entirely (as it +# used to), the ItemDefinitionGroup inside it would never be evaluated at all, +# and if it instead re-evaluated the When Condition against final properties +# during item-definition/item evaluation, it would pick the wrong branch +# ("When" instead of the "Otherwise" branch Properties actually selected). + +import os + +from testutils import cppcheck + +__script_dir = os.path.dirname(os.path.abspath(__file__)) + + +def test_vcxproj_choose(): + args = [ + '--project=vcxproj_choose/vcxproj_choose.vcxproj', + '--no-cppcheck-build-dir', + '--dump' + ] + ret, stdout, _ = cppcheck(args, cwd=__script_dir) + assert ret == 0, stdout + + dump_file = os.path.join(__script_dir, 'vcxproj_choose', 'main.cpp.dump') + assert os.path.exists(dump_file), "Dump file not found at %s" % dump_file + + with open(dump_file, 'rt') as f: + dump_content = f.read() + + # UseSpecialDefine is unset at the point Properties reaches the Choose (it is + # only assigned afterwards), so the Properties pass selects -- + # and ItemDefs/Items must replay that same selection rather than + # re-evaluating the When Condition against the now-final property value. + assert 'CHOSEN_BRANCH=Otherwise' in dump_content, dump_content + assert 'CHOSEN_BRANCH=When' not in dump_content, dump_content diff --git a/test/cli/vcxproj_directory_build_disabled/Directory.Build.props b/test/cli/vcxproj_directory_build_disabled/Directory.Build.props new file mode 100644 index 00000000000..747f1613e36 --- /dev/null +++ b/test/cli/vcxproj_directory_build_disabled/Directory.Build.props @@ -0,0 +1,8 @@ + + + + + PROPS_DEFINE=1;%(PreprocessorDefinitions) + + + diff --git a/test/cli/vcxproj_directory_build_disabled/Directory.Build.targets b/test/cli/vcxproj_directory_build_disabled/Directory.Build.targets new file mode 100644 index 00000000000..fac7a9f273f --- /dev/null +++ b/test/cli/vcxproj_directory_build_disabled/Directory.Build.targets @@ -0,0 +1,8 @@ + + + + + TARGETS_DEFINE=1;%(PreprocessorDefinitions) + + + diff --git a/test/cli/vcxproj_directory_build_disabled/main.cpp b/test/cli/vcxproj_directory_build_disabled/main.cpp new file mode 100644 index 00000000000..61931dfffa0 --- /dev/null +++ b/test/cli/vcxproj_directory_build_disabled/main.cpp @@ -0,0 +1,15 @@ +// Each check below fails with #error if Directory.Build.props/.targets was +// imported despite ImportDirectoryBuildProps/ImportDirectoryBuildTargets being +// set to false; see vcxproj_directory_build_disabled_test.py. +#if defined(PROPS_DEFINE) +#error Directory.Build.props was imported despite ImportDirectoryBuildProps=false +#endif +#if defined(TARGETS_DEFINE) +#error Directory.Build.targets was imported despite ImportDirectoryBuildTargets=false +#endif + +int main() +{ + int x = 3 / 0; // ERROR + return x; +} diff --git a/test/cli/vcxproj_directory_build_disabled/vcxproj_directory_build_disabled.vcxproj b/test/cli/vcxproj_directory_build_disabled/vcxproj_directory_build_disabled.vcxproj new file mode 100644 index 00000000000..bddcfb9bc8a --- /dev/null +++ b/test/cli/vcxproj_directory_build_disabled/vcxproj_directory_build_disabled.vcxproj @@ -0,0 +1,31 @@ + + + + + Debug + x64 + + + + + {88888888-8888-8888-8888-888888888888} + dirbuilddisabledtest + 10.0 + false + false + + + + Application + v143 + + + + + + + diff --git a/test/cli/vcxproj_directory_build_disabled_test.py b/test/cli/vcxproj_directory_build_disabled_test.py new file mode 100644 index 00000000000..cf07274016f --- /dev/null +++ b/test/cli/vcxproj_directory_build_disabled_test.py @@ -0,0 +1,33 @@ + +# python -m pytest vcxproj_directory_build_disabled_test.py +# +# Regression coverage for $(ImportDirectoryBuildProps) / $(ImportDirectoryBuildTargets): +# attemptSyntheticImport()'s emulation of Microsoft.Cpp.Default.props (which normally +# imports Directory.Build.props) and Microsoft.Cpp.targets (which normally imports +# Directory.Build.targets) must honor these controls, which Microsoft documents in +# "Customize your build by folder or solution". Both default to true, but this +# fixture's Globals PropertyGroup -- processed before either import point is +# reached -- sets both to false, so neither Directory.Build.props nor +# Directory.Build.targets (both sitting right next to the project, exactly where +# the automatic upward search would otherwise find them) may be imported. +# +# main.cpp turns either file's ItemDefinitionGroup contribution into a #error, so +# the only expected diagnostic is the deliberate division by zero. + +import os + +from testutils import cppcheck + +__script_dir = os.path.dirname(os.path.abspath(__file__)) + + +def test_vcxproj_directory_build_disabled(): + args = [ + '--template=cppcheck1', + '--project=vcxproj_directory_build_disabled/vcxproj_directory_build_disabled.vcxproj', + '--no-cppcheck-build-dir' + ] + ret, stdout, stderr = cppcheck(args, cwd=__script_dir) + assert ret == 0, stdout + filename = os.path.join('vcxproj_directory_build_disabled', 'main.cpp') + assert stderr == '[%s:13]: (error) Division by zero.\n' % filename diff --git a/test/cli/vcxproj_directory_build_path/CustomBuild.props b/test/cli/vcxproj_directory_build_path/CustomBuild.props new file mode 100644 index 00000000000..c4591915f91 --- /dev/null +++ b/test/cli/vcxproj_directory_build_path/CustomBuild.props @@ -0,0 +1,8 @@ + + + + + RIGHT_PROPS_DEFINE=1;%(PreprocessorDefinitions) + + + diff --git a/test/cli/vcxproj_directory_build_path/CustomBuild.targets b/test/cli/vcxproj_directory_build_path/CustomBuild.targets new file mode 100644 index 00000000000..3a11e53f679 --- /dev/null +++ b/test/cli/vcxproj_directory_build_path/CustomBuild.targets @@ -0,0 +1,8 @@ + + + + + RIGHT_TARGETS_DEFINE=1;%(PreprocessorDefinitions) + + + diff --git a/test/cli/vcxproj_directory_build_path/Directory.Build.props b/test/cli/vcxproj_directory_build_path/Directory.Build.props new file mode 100644 index 00000000000..b15b0db8926 --- /dev/null +++ b/test/cli/vcxproj_directory_build_path/Directory.Build.props @@ -0,0 +1,8 @@ + + + + + WRONG_PROPS_DEFINE=1;%(PreprocessorDefinitions) + + + diff --git a/test/cli/vcxproj_directory_build_path/Directory.Build.targets b/test/cli/vcxproj_directory_build_path/Directory.Build.targets new file mode 100644 index 00000000000..9a18ed7fe4e --- /dev/null +++ b/test/cli/vcxproj_directory_build_path/Directory.Build.targets @@ -0,0 +1,8 @@ + + + + + WRONG_TARGETS_DEFINE=1;%(PreprocessorDefinitions) + + + diff --git a/test/cli/vcxproj_directory_build_path/main.cpp b/test/cli/vcxproj_directory_build_path/main.cpp new file mode 100644 index 00000000000..7dc10ed6c2b --- /dev/null +++ b/test/cli/vcxproj_directory_build_path/main.cpp @@ -0,0 +1,21 @@ +// Each check below fails with #error if DirectoryBuildPropsPath/ +// DirectoryBuildTargetsPath did not override the upward directory search with +// the explicit file they name; see vcxproj_directory_build_path_test.py. +#if !defined(RIGHT_PROPS_DEFINE) +#error DirectoryBuildPropsPath override (CustomBuild.props) was not imported +#endif +#if defined(WRONG_PROPS_DEFINE) +#error Directory.Build.props was imported even though DirectoryBuildPropsPath overrides the search +#endif +#if !defined(RIGHT_TARGETS_DEFINE) +#error DirectoryBuildTargetsPath override (CustomBuild.targets) was not imported +#endif +#if defined(WRONG_TARGETS_DEFINE) +#error Directory.Build.targets was imported even though DirectoryBuildTargetsPath overrides the search +#endif + +int main() +{ + int x = 3 / 0; // ERROR + return x; +} diff --git a/test/cli/vcxproj_directory_build_path/vcxproj_directory_build_path.vcxproj b/test/cli/vcxproj_directory_build_path/vcxproj_directory_build_path.vcxproj new file mode 100644 index 00000000000..bd596b95cde --- /dev/null +++ b/test/cli/vcxproj_directory_build_path/vcxproj_directory_build_path.vcxproj @@ -0,0 +1,31 @@ + + + + + Debug + x64 + + + + + {99999999-9999-9999-9999-999999999999} + dirbuildpathtest + 10.0 + CustomBuild.props + CustomBuild.targets + + + + Application + v143 + + + + + + + diff --git a/test/cli/vcxproj_directory_build_path_test.py b/test/cli/vcxproj_directory_build_path_test.py new file mode 100644 index 00000000000..721e6ba9b8d --- /dev/null +++ b/test/cli/vcxproj_directory_build_path_test.py @@ -0,0 +1,32 @@ + +# python -m pytest vcxproj_directory_build_path_test.py +# +# Regression coverage for $(DirectoryBuildPropsPath) / $(DirectoryBuildTargetsPath): +# when set, Microsoft documents that these override attemptSyntheticImport()'s +# normal upward-directory search (see "Customize your build by folder or +# solution") with an explicit file. This fixture's Globals PropertyGroup sets +# both to "CustomBuild.props"/"CustomBuild.targets", while Directory.Build.props +# and Directory.Build.targets -- which the upward search would otherwise find +# first, since they sit right next to the project -- are also present, each +# defining a macro the *other* file does not. main.cpp turns any mismatch (the +# override not imported, or the plain Directory.Build.* file imported anyway) +# into a #error, so the only expected diagnostic is the deliberate division by +# zero. + +import os + +from testutils import cppcheck + +__script_dir = os.path.dirname(os.path.abspath(__file__)) + + +def test_vcxproj_directory_build_path(): + args = [ + '--template=cppcheck1', + '--project=vcxproj_directory_build_path/vcxproj_directory_build_path.vcxproj', + '--no-cppcheck-build-dir' + ] + ret, stdout, stderr = cppcheck(args, cwd=__script_dir) + assert ret == 0, stdout + filename = os.path.join('vcxproj_directory_build_path', 'main.cpp') + assert stderr == '[%s:19]: (error) Division by zero.\n' % filename diff --git a/test/cli/vcxproj_discover_choose/Configs.props b/test/cli/vcxproj_discover_choose/Configs.props new file mode 100644 index 00000000000..aeebadde55d --- /dev/null +++ b/test/cli/vcxproj_discover_choose/Configs.props @@ -0,0 +1,9 @@ + + + + + Debug + x64 + + + diff --git a/test/cli/vcxproj_discover_choose/main.cpp b/test/cli/vcxproj_discover_choose/main.cpp new file mode 100644 index 00000000000..76e8197013a --- /dev/null +++ b/test/cli/vcxproj_discover_choose/main.cpp @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/test/cli/vcxproj_discover_choose/vcxproj_discover_choose.vcxproj b/test/cli/vcxproj_discover_choose/vcxproj_discover_choose.vcxproj new file mode 100644 index 00000000000..982b085258c --- /dev/null +++ b/test/cli/vcxproj_discover_choose/vcxproj_discover_choose.vcxproj @@ -0,0 +1,34 @@ + + + + {dddddddd-dddd-dddd-dddd-dddddddddddd} + discoverchoosetest + 10.0 + + + + + + + + + + + + Application + v143 + + + + + + + diff --git a/test/cli/vcxproj_discover_choose_test.py b/test/cli/vcxproj_discover_choose_test.py new file mode 100644 index 00000000000..c56d058c8f2 --- /dev/null +++ b/test/cli/vcxproj_discover_choose_test.py @@ -0,0 +1,43 @@ + +# python -m pytest vcxproj_discover_choose_test.py +# +# Regression coverage for the vcxproj configuration-discovery pass (used only +# when a project has no inline and +# must find its configurations by walking imports): it must also look inside +# a top-level , not just top-level PropertyGroup/ImportGroup/Import. +# +# This fixture's only configuration (Debug|x64) is defined in Configs.props, +# which is reachable only through an nested inside a top-level +# . A discovery pass that walks +# PropertyGroup/ImportGroup/Import at the top level but never inspects +# at all never finds Configs.props, so no configuration is discovered and +# cppcheck fails outright ("no C or C++ source files found") even though the +# project is well-formed -- this is a different bug than the discovery pass +# stopping early (covered by vcxproj_split_configs_test.py): here the node type +# is never even recognized, so it doesn't matter whether it's found first, +# last, or is the only import in the project. + +import os + +from testutils import cppcheck + +__script_dir = os.path.dirname(os.path.abspath(__file__)) + + +def test_vcxproj_discover_choose(): + args = [ + '--project=vcxproj_discover_choose/vcxproj_discover_choose.vcxproj', + '--no-cppcheck-build-dir', + '--dump' + ] + ret, stdout, _ = cppcheck(args, cwd=__script_dir) + assert ret == 0, stdout + + # Windows prints native '\' path separators ("Checking foo\main.cpp ..."); + # normalize before matching so this passes on every platform (same idiom + # used by test_log() in clang-import_test.py). + normalized_stdout = stdout.replace('\\', '/') + + # The configuration reachable only through the top-level Choose must be + # discovered and checked, not silently dropped. + assert 'Checking vcxproj_discover_choose/main.cpp Debug|x64' in normalized_stdout, stdout diff --git a/test/cli/vcxproj_discover_property_path/Debug/Configs.props b/test/cli/vcxproj_discover_property_path/Debug/Configs.props new file mode 100644 index 00000000000..aeebadde55d --- /dev/null +++ b/test/cli/vcxproj_discover_property_path/Debug/Configs.props @@ -0,0 +1,9 @@ + + + + + Debug + x64 + + + diff --git a/test/cli/vcxproj_discover_property_path/main.cpp b/test/cli/vcxproj_discover_property_path/main.cpp new file mode 100644 index 00000000000..76e8197013a --- /dev/null +++ b/test/cli/vcxproj_discover_property_path/main.cpp @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/test/cli/vcxproj_discover_property_path/vcxproj_discover_property_path.vcxproj b/test/cli/vcxproj_discover_property_path/vcxproj_discover_property_path.vcxproj new file mode 100644 index 00000000000..71677194e2b --- /dev/null +++ b/test/cli/vcxproj_discover_property_path/vcxproj_discover_property_path.vcxproj @@ -0,0 +1,28 @@ + + + + {11111111-2222-3333-4444-555555555555} + discoverpropertypathtest + 10.0 + + + + + + Application + v143 + + + + + + + diff --git a/test/cli/vcxproj_discover_property_path_test.py b/test/cli/vcxproj_discover_property_path_test.py new file mode 100644 index 00000000000..8007b969471 --- /dev/null +++ b/test/cli/vcxproj_discover_property_path_test.py @@ -0,0 +1,44 @@ + +# python -m pytest vcxproj_discover_property_path_test.py +# +# Regression coverage for the vcxproj configuration-discovery pass (used only +# when a project has no inline and +# must find its configurations by walking imports): it must still seed a +# guessed Configuration/Platform, even though it now ignores every Condition +# (see vcxproj_discover_choose_test.py / mDiscovering). +# +# This fixture's only has no Condition and is not behind a -- +# there is nothing for "ignore every Condition" to help with here. Instead, the +# import's PATH ITSELF embeds $(Configuration): . Resolving that path at all requires a +# concrete value for $(Configuration); without a seeded guess it is left as a +# literal, unexpanded "$(Configuration)" segment, which +# simplifyPathWithVariables() treats as unresolvable, so the import -- and the +# only configuration it defines -- is silently skipped and cppcheck fails +# outright ("no C or C++ source files found") despite the project being +# well-formed. + +import os + +from testutils import cppcheck + +__script_dir = os.path.dirname(os.path.abspath(__file__)) + + +def test_vcxproj_discover_property_path(): + args = [ + '--project=vcxproj_discover_property_path/vcxproj_discover_property_path.vcxproj', + '--no-cppcheck-build-dir', + '--dump' + ] + ret, stdout, _ = cppcheck(args, cwd=__script_dir) + assert ret == 0, stdout + + # Windows prints native '\' path separators ("Checking foo\main.cpp ..."); + # normalize before matching so this passes on every platform (same idiom + # used by test_log() in clang-import_test.py). + normalized_stdout = stdout.replace('\\', '/') + + # The configuration reachable only through the unconditional, path-embedded + # $(Configuration) import must be discovered and checked. + assert 'Checking vcxproj_discover_property_path/main.cpp Debug|x64' in normalized_stdout, stdout diff --git a/test/cli/vcxproj_duplicate_import/Common.props b/test/cli/vcxproj_duplicate_import/Common.props new file mode 100644 index 00000000000..b3058925ff6 --- /dev/null +++ b/test/cli/vcxproj_duplicate_import/Common.props @@ -0,0 +1,6 @@ + + + + $(Counter)X + + diff --git a/test/cli/vcxproj_duplicate_import/main.cpp b/test/cli/vcxproj_duplicate_import/main.cpp new file mode 100644 index 00000000000..76e8197013a --- /dev/null +++ b/test/cli/vcxproj_duplicate_import/main.cpp @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/test/cli/vcxproj_duplicate_import/vcxproj_duplicate_import.vcxproj b/test/cli/vcxproj_duplicate_import/vcxproj_duplicate_import.vcxproj new file mode 100644 index 00000000000..6fe09159cd9 --- /dev/null +++ b/test/cli/vcxproj_duplicate_import/vcxproj_duplicate_import.vcxproj @@ -0,0 +1,41 @@ + + + + + Debug + x64 + + + + {66666666-6666-6666-6666-666666666666} + duptest + 10.0 + + + + Application + v143 + + + + + + + + + + COUNTER_VALUE=$(Counter);%(PreprocessorDefinitions) + + + + + + + + + diff --git a/test/cli/vcxproj_duplicate_import_test.py b/test/cli/vcxproj_duplicate_import_test.py new file mode 100644 index 00000000000..d9fb3583d4d --- /dev/null +++ b/test/cli/vcxproj_duplicate_import_test.py @@ -0,0 +1,40 @@ + +# python -m pytest vcxproj_duplicate_import_test.py +# +# Regression coverage for MSBuild/Visual Studio duplicate-import handling +# (MSB4011): once a project file has been imported, a later of the +# same file (by resolved, case-insensitive path) is a no-op -- its content is +# not evaluated a second time. This fixture imports Common.props via two +# separate elements; Common.props appends to a property +# (Counter = $(Counter)X) non-idempotently, so double-processing is directly +# observable: processed once, Counter ends up "X"; processed twice, "XX". +# Checked in all three evaluation passes (Properties/ItemDefs/Items), not just +# Properties, since each pass has its own 'imported' dedup state that must +# independently reproduce the same "process once" result. + +import os + +from testutils import cppcheck + +__script_dir = os.path.dirname(os.path.abspath(__file__)) + + +def test_vcxproj_duplicate_import(): + args = [ + '--project=vcxproj_duplicate_import/vcxproj_duplicate_import.vcxproj', + '--no-cppcheck-build-dir', + '--dump' + ] + ret, stdout, _ = cppcheck(args, cwd=__script_dir) + assert ret == 0, stdout + + dump_file = os.path.join(__script_dir, 'vcxproj_duplicate_import', 'main.cpp.dump') + assert os.path.exists(dump_file), "Dump file not found at %s" % dump_file + + with open(dump_file, 'rt') as f: + dump_content = f.read() + + # Common.props must be evaluated exactly once despite being referenced by + # two separate elements -- Counter must be "X", not "XX". + assert 'COUNTER_VALUE=X;' in dump_content, dump_content + assert 'COUNTER_VALUE=XX' not in dump_content, dump_content diff --git a/test/cli/vcxproj_forced_includes/AllX64.h b/test/cli/vcxproj_forced_includes/AllX64.h new file mode 100644 index 00000000000..0c3063b59f4 --- /dev/null +++ b/test/cli/vcxproj_forced_includes/AllX64.h @@ -0,0 +1,6 @@ +class all +{ + all() { + int x = 3 / 0; (void)x; // ERROR + } +}; diff --git a/test/cli/vcxproj_forced_includes/DebugX64.cpp b/test/cli/vcxproj_forced_includes/DebugX64.cpp new file mode 100644 index 00000000000..cfb1fce687a --- /dev/null +++ b/test/cli/vcxproj_forced_includes/DebugX64.cpp @@ -0,0 +1,8 @@ +#include + +int foo() +{ + std::cout << "DebugX64\n"; + int x = 3 / 0; (void)x; // ERROR + return 0; +} diff --git a/test/cli/vcxproj_forced_includes/DebugX64.h b/test/cli/vcxproj_forced_includes/DebugX64.h new file mode 100644 index 00000000000..ab3bfb495da --- /dev/null +++ b/test/cli/vcxproj_forced_includes/DebugX64.h @@ -0,0 +1,6 @@ +class debug +{ + debug() { + int x = 3 / 0; (void)x; // ERROR + } +}; \ No newline at end of file diff --git a/test/cli/vcxproj_forced_includes/GlobalDebugX64.h b/test/cli/vcxproj_forced_includes/GlobalDebugX64.h new file mode 100644 index 00000000000..48038886307 --- /dev/null +++ b/test/cli/vcxproj_forced_includes/GlobalDebugX64.h @@ -0,0 +1,6 @@ +class global +{ + global() { + int x = 3 / 0; (void)x; // ERROR + } +}; diff --git a/test/cli/vcxproj_forced_includes/GlobalReleaseX64.h b/test/cli/vcxproj_forced_includes/GlobalReleaseX64.h new file mode 100644 index 00000000000..48038886307 --- /dev/null +++ b/test/cli/vcxproj_forced_includes/GlobalReleaseX64.h @@ -0,0 +1,6 @@ +class global +{ + global() { + int x = 3 / 0; (void)x; // ERROR + } +}; diff --git a/test/cli/vcxproj_forced_includes/PropsDebugX64.h b/test/cli/vcxproj_forced_includes/PropsDebugX64.h new file mode 100644 index 00000000000..49643c7ea86 --- /dev/null +++ b/test/cli/vcxproj_forced_includes/PropsDebugX64.h @@ -0,0 +1,6 @@ +class props +{ + props() { + int x = 3 / 0; (void)x; // ERROR + } +}; diff --git a/test/cli/vcxproj_forced_includes/PropsReleaseX64.h b/test/cli/vcxproj_forced_includes/PropsReleaseX64.h new file mode 100644 index 00000000000..49643c7ea86 --- /dev/null +++ b/test/cli/vcxproj_forced_includes/PropsReleaseX64.h @@ -0,0 +1,6 @@ +class props +{ + props() { + int x = 3 / 0; (void)x; // ERROR + } +}; diff --git a/test/cli/vcxproj_forced_includes/ReleaseX64.cpp b/test/cli/vcxproj_forced_includes/ReleaseX64.cpp new file mode 100644 index 00000000000..8fa6e6d0f82 --- /dev/null +++ b/test/cli/vcxproj_forced_includes/ReleaseX64.cpp @@ -0,0 +1,8 @@ +#include + +int foo() +{ + std::cout << "ReleaseX64\n"; + int x = 3 / 0; (void)x; // ERROR + return 0; +} diff --git a/test/cli/vcxproj_forced_includes/ReleaseX64.h b/test/cli/vcxproj_forced_includes/ReleaseX64.h new file mode 100644 index 00000000000..49f9766f927 --- /dev/null +++ b/test/cli/vcxproj_forced_includes/ReleaseX64.h @@ -0,0 +1,6 @@ +class release +{ + release() { + int x = 3 / 0; (void)x; // ERROR + } +}; \ No newline at end of file diff --git a/test/cli/vcxproj_forced_includes/foo.h b/test/cli/vcxproj_forced_includes/foo.h new file mode 100644 index 00000000000..5d5f8f0c9e7 --- /dev/null +++ b/test/cli/vcxproj_forced_includes/foo.h @@ -0,0 +1 @@ +int foo(); diff --git a/test/cli/vcxproj_forced_includes/vcxproj_forced_includes.props b/test/cli/vcxproj_forced_includes/vcxproj_forced_includes.props new file mode 100644 index 00000000000..22e858590c1 --- /dev/null +++ b/test/cli/vcxproj_forced_includes/vcxproj_forced_includes.props @@ -0,0 +1,8 @@ + + + + PropsDebugX64.h;%(ForcedIncludeFiles) + PropsReleaseX64.h;%(ForcedIncludeFiles) + + + diff --git a/test/cli/vcxproj_forced_includes/vcxproj_forced_includes.slnx b/test/cli/vcxproj_forced_includes/vcxproj_forced_includes.slnx new file mode 100644 index 00000000000..f586cfa3a29 --- /dev/null +++ b/test/cli/vcxproj_forced_includes/vcxproj_forced_includes.slnx @@ -0,0 +1,6 @@ + + + + + + diff --git a/test/cli/vcxproj_forced_includes/vcxproj_forced_includes.vcxproj b/test/cli/vcxproj_forced_includes/vcxproj_forced_includes.vcxproj new file mode 100644 index 00000000000..bd1676af947 --- /dev/null +++ b/test/cli/vcxproj_forced_includes/vcxproj_forced_includes.vcxproj @@ -0,0 +1,103 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 18.0 + Win32Proj + {c9d1dca1-d8ff-4c05-9159-f00816645319} + exclude + 10.0 + + + + StaticLibrary + true + v145 + Unicode + + + Application + false + v145 + true + Unicode + + + + + + + + + + + + + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + $(MSBuildThisFileDirectory)GlobalDebugX64.h;%(ForcedIncludeFiles) + + + Console + true + + + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + $(MSBuildThisFileDirectory)GlobalReleaseX64.h;%(ForcedIncludeFiles) + + + Console + true + + + true + + + + + $(MSBuildThisFileDirectory)AllX64.h;%(ForcedIncludeFiles) + $(MSBuildThisFileDirectory)DebugX64.h;%(ForcedIncludeFiles) + $(MSBuildThisFileDirectory)ReleaseX64.h;%(ForcedIncludeFiles) + true + + + $(MSBuildThisFileDirectory)AllX64.h;%(ForcedIncludeFiles) + $(MSBuildThisFileDirectory)DebugX64.h;%(ForcedIncludeFiles) + $(MSBuildThisFileDirectory)ReleaseX64.h;%(ForcedIncludeFiles) + true + + + + + + + + + \ No newline at end of file diff --git a/test/cli/vcxproj_forced_includes_test.py b/test/cli/vcxproj_forced_includes_test.py new file mode 100644 index 00000000000..e86b254f7da --- /dev/null +++ b/test/cli/vcxproj_forced_includes_test.py @@ -0,0 +1,59 @@ + +# python -m pytest vcxproj_forced_includes_test.py + +import os + +from testutils import cppcheck + +__script_dir = os.path.dirname(os.path.abspath(__file__)) +__proj_dir = os.path.join(__script_dir, 'vcxproj_forced_includes') + +def get_lines(s): + return sorted(s.split('\n')) + +def test_vcxproj_forced_includes_debug(): + args = [ + '--template=cppcheck1', + '--project=vcxproj_forced_includes/vcxproj_forced_includes.slnx', + '--project-configuration=Debug|x64', + '--no-cppcheck-build-dir' + ] + ret, stdout, stderr = cppcheck(args, cwd=__script_dir) + filename1 = os.path.join('vcxproj_forced_includes', 'DebugX64.cpp') + filename2 = os.path.join('vcxproj_forced_includes', 'DebugX64.h') + filename3 = os.path.join('vcxproj_forced_includes', 'AllX64.h') + filename4 = os.path.join('vcxproj_forced_includes', 'GlobalDebugX64.h') + filename5 = os.path.join('vcxproj_forced_includes', 'PropsDebugX64.h') + assert ret == 0, stdout + expected = ( + '[%s:6]: (error) Division by zero.\n' + '[%s:4]: (error) Division by zero.\n' + '[%s:4]: (error) Division by zero.\n' + '[%s:4]: (error) Division by zero.\n' + '[%s:4]: (error) Division by zero.\n' % (filename1, filename2, filename3, filename4, filename5) + ) + assert get_lines(stderr) == get_lines(expected) + + +def test_vcxproj_forced_includes_release(): + args = [ + '--template=cppcheck1', + '--project=vcxproj_forced_includes/vcxproj_forced_includes.slnx', + '--project-configuration=Release|x64', + '--no-cppcheck-build-dir' + ] + ret, stdout, stderr = cppcheck(args, cwd=__script_dir) + filename1 = os.path.join('vcxproj_forced_includes', 'ReleaseX64.cpp') + filename2 = os.path.join('vcxproj_forced_includes', 'ReleaseX64.h') + filename3 = os.path.join('vcxproj_forced_includes', 'AllX64.h') + filename4 = os.path.join('vcxproj_forced_includes', 'GlobalReleaseX64.h') + filename5 = os.path.join('vcxproj_forced_includes', 'PropsReleaseX64.h') + assert ret == 0, stdout + expected = ( + '[%s:6]: (error) Division by zero.\n' + '[%s:4]: (error) Division by zero.\n' + '[%s:4]: (error) Division by zero.\n' + '[%s:4]: (error) Division by zero.\n' + '[%s:4]: (error) Division by zero.\n' % (filename1, filename2, filename3, filename4, filename5) + ) + assert get_lines(stderr) == get_lines(expected) diff --git a/test/cli/vcxproj_import_condition/conditional.props b/test/cli/vcxproj_import_condition/conditional.props new file mode 100644 index 00000000000..8f93474463e --- /dev/null +++ b/test/cli/vcxproj_import_condition/conditional.props @@ -0,0 +1,8 @@ + + + + + SHOULD_NOT_APPEAR=1;%(PreprocessorDefinitions) + + + diff --git a/test/cli/vcxproj_import_condition/main.cpp b/test/cli/vcxproj_import_condition/main.cpp new file mode 100644 index 00000000000..76e8197013a --- /dev/null +++ b/test/cli/vcxproj_import_condition/main.cpp @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/test/cli/vcxproj_import_condition/vcxproj_import_condition.vcxproj b/test/cli/vcxproj_import_condition/vcxproj_import_condition.vcxproj new file mode 100644 index 00000000000..b4352581e87 --- /dev/null +++ b/test/cli/vcxproj_import_condition/vcxproj_import_condition.vcxproj @@ -0,0 +1,37 @@ + + + + + Debug + x64 + + + + {55555555-5555-5555-5555-555555555555} + condtest + 10.0 + + + + Application + v143 + + + + + + + + + + + + true + + + + diff --git a/test/cli/vcxproj_import_condition_test.py b/test/cli/vcxproj_import_condition_test.py new file mode 100644 index 00000000000..bd1cf4e6df7 --- /dev/null +++ b/test/cli/vcxproj_import_condition_test.py @@ -0,0 +1,43 @@ + +# python -m pytest vcxproj_import_condition_test.py +# +# Regression coverage for MSBuild/Visual Studio's three-pass evaluation order: +# an 's Condition is decided exactly once, during the Properties pass, +# using properties as accumulated at the point Properties reaches it -- not +# re-evaluated during the ItemDefs/Items passes against end-of-Properties +# (final) property state. This fixture's +# has a Condition that depends on a property (LateFlag) which is only assigned +# by a PropertyGroup positioned AFTER the Import -- so the Properties pass +# sees it unset and does not take the import. If ItemDefs/Items instead +# re-evaluated the same Condition against the now-final properties (where +# LateFlag=true), they would incorrectly take the import there and pick up +# conditional.props's ItemDefinitionGroup, leaking SHOULD_NOT_APPEAR into the +# dump even though the import was never actually part of the resolved graph +# Properties built. + +import os + +from testutils import cppcheck + +__script_dir = os.path.dirname(os.path.abspath(__file__)) + + +def test_vcxproj_import_condition(): + args = [ + '--project=vcxproj_import_condition/vcxproj_import_condition.vcxproj', + '--no-cppcheck-build-dir', + '--dump' + ] + ret, stdout, _ = cppcheck(args, cwd=__script_dir) + assert ret == 0, stdout + + dump_file = os.path.join(__script_dir, 'vcxproj_import_condition', 'main.cpp.dump') + assert os.path.exists(dump_file), "Dump file not found at %s" % dump_file + + with open(dump_file, 'rt') as f: + dump_content = f.read() + + # conditional.props must stay un-imported in every phase -- its Condition + # was false when Properties decided it, and that decision must be replayed + # (not re-evaluated) by ItemDefs/Items. + assert 'SHOULD_NOT_APPEAR' not in dump_content, dump_content diff --git a/test/cli/vcxproj_import_graph/Directory.Build.props b/test/cli/vcxproj_import_graph/Directory.Build.props new file mode 100644 index 00000000000..de26fe1ecac --- /dev/null +++ b/test/cli/vcxproj_import_graph/Directory.Build.props @@ -0,0 +1,8 @@ + + + + + DIRBUILD_DEFINE=1;%(PreprocessorDefinitions) + + + diff --git a/test/cli/vcxproj_import_graph/dup.props b/test/cli/vcxproj_import_graph/dup.props new file mode 100644 index 00000000000..ab74367157d --- /dev/null +++ b/test/cli/vcxproj_import_graph/dup.props @@ -0,0 +1,12 @@ + + + + + $(DupImportCount)1 + + + + DUP_IMPORT_COUNT=$(DupImportCount);%(PreprocessorDefinitions) + + + diff --git a/test/cli/vcxproj_import_graph/guarded.props b/test/cli/vcxproj_import_graph/guarded.props new file mode 100644 index 00000000000..a0c33fbbf98 --- /dev/null +++ b/test/cli/vcxproj_import_graph/guarded.props @@ -0,0 +1,11 @@ + + + + true + + + + GUARDED_DEFINE=1;%(PreprocessorDefinitions) + + + diff --git a/test/cli/vcxproj_import_graph/main.cpp b/test/cli/vcxproj_import_graph/main.cpp new file mode 100644 index 00000000000..bd828b0e725 --- /dev/null +++ b/test/cli/vcxproj_import_graph/main.cpp @@ -0,0 +1,20 @@ +// Each check below fails with #error when the import graph is not walked the way +// MSBuild / Visual Studio walk it; see vcxproj_import_graph_test.py. +#if !defined(GUARDED_DEFINE) +#error ItemDefinitionGroup of guarded.props (import guard) was not applied +#endif +#if DUP_IMPORT_COUNT != 1 +#error dup.props was imported more than once +#endif +#if !defined(DIRBUILD_DEFINE) +#error ItemDefinitionGroup of Directory.Build.props was not applied +#endif +#if !defined(PROJ_DEFINE) +#error ItemDefinitionGroup of the project was not applied +#endif + +int main() +{ + int x = 3 / 0; // ERROR + return x; +} diff --git a/test/cli/vcxproj_import_graph/vcxproj_import_graph.slnx b/test/cli/vcxproj_import_graph/vcxproj_import_graph.slnx new file mode 100644 index 00000000000..c740f7a1631 --- /dev/null +++ b/test/cli/vcxproj_import_graph/vcxproj_import_graph.slnx @@ -0,0 +1,6 @@ + + + + + + diff --git a/test/cli/vcxproj_import_graph/vcxproj_import_graph.vcxproj b/test/cli/vcxproj_import_graph/vcxproj_import_graph.vcxproj new file mode 100644 index 00000000000..1a8d4720cb9 --- /dev/null +++ b/test/cli/vcxproj_import_graph/vcxproj_import_graph.vcxproj @@ -0,0 +1,55 @@ + + + + + Debug + x64 + + + + 17.0 + Win32Proj + {4f0c2b5e-1a7d-4c3e-9b8a-2d6e7f1c0a91} + vcxprojimportgraph + 10.0 + + + + Application + true + v143 + Unicode + + + + + + + + + + + + + + + + + + Level3 + _DEBUG;_CONSOLE;PROJ_DEFINE=1;%(PreprocessorDefinitions) + stdcpp17 + + + Console + + + + + + + + + diff --git a/test/cli/vcxproj_import_graph_test.py b/test/cli/vcxproj_import_graph_test.py new file mode 100644 index 00000000000..f72b8d7bd99 --- /dev/null +++ b/test/cli/vcxproj_import_graph_test.py @@ -0,0 +1,32 @@ +# python -m pytest vcxproj_import_graph_test.py +# +# MSBuild (and therefore Visual Studio) resolves the import graph exactly once, while +# evaluating properties, and then walks that same graph for item definitions and items. +# This fixture checks that cppcheck's three-phase evaluation does the same: +# - an guarded by a property that the imported file itself sets +# () +# still contributes its ItemDefinitionGroup +# - a file that is imported twice is processed only once (MSB4011) +# - Directory.Build.props, imported through the Microsoft.Cpp.Default.props emulation, +# contributes its ItemDefinitionGroup as well +# main.cpp turns every one of these into an #error, so the only expected diagnostic is +# the deliberate division by zero. + +import os + +from testutils import cppcheck + +__script_dir = os.path.dirname(os.path.abspath(__file__)) + + +def test_vcxproj_import_graph(): + args = [ + '--template=cppcheck1', + '--project=vcxproj_import_graph/vcxproj_import_graph.slnx', + '--project-configuration=Debug|x64', + '--no-cppcheck-build-dir' + ] + ret, stdout, stderr = cppcheck(args, cwd=__script_dir) + assert ret == 0, stdout + filename = os.path.join('vcxproj_import_graph', 'main.cpp') + assert stderr == '[%s:18]: (error) Division by zero.\n' % filename diff --git a/test/cli/vcxproj_import_wildcard/ImportBefore/a.props b/test/cli/vcxproj_import_wildcard/ImportBefore/a.props new file mode 100644 index 00000000000..33aea328de2 --- /dev/null +++ b/test/cli/vcxproj_import_wildcard/ImportBefore/a.props @@ -0,0 +1,8 @@ + + + + + WILDCARD_A_DEFINE=1;%(PreprocessorDefinitions) + + + diff --git a/test/cli/vcxproj_import_wildcard/ImportBefore/b.props b/test/cli/vcxproj_import_wildcard/ImportBefore/b.props new file mode 100644 index 00000000000..193b35a1fc7 --- /dev/null +++ b/test/cli/vcxproj_import_wildcard/ImportBefore/b.props @@ -0,0 +1,8 @@ + + + + + WILDCARD_B_DEFINE=1;%(PreprocessorDefinitions) + + + diff --git a/test/cli/vcxproj_import_wildcard/ImportBefore/decoy.txt b/test/cli/vcxproj_import_wildcard/ImportBefore/decoy.txt new file mode 100644 index 00000000000..a0ba20a47fb --- /dev/null +++ b/test/cli/vcxproj_import_wildcard/ImportBefore/decoy.txt @@ -0,0 +1,2 @@ +Not a .props file -- the "*.props" wildcard pattern must not attempt to load +this as XML. If it did, cppcheck would fail since this isn't valid XML. diff --git a/test/cli/vcxproj_import_wildcard/main.cpp b/test/cli/vcxproj_import_wildcard/main.cpp new file mode 100644 index 00000000000..79c249d89d3 --- /dev/null +++ b/test/cli/vcxproj_import_wildcard/main.cpp @@ -0,0 +1,18 @@ +// Each check below fails with #error unless the wildcard correctly expanded to import BOTH a.props and +// b.props (sorted, per MSBuild's own documented behavior), while skipping +// decoy.txt (extension doesn't match "*.props") and the nonexistent +// ImportAfter\*.props wildcard resolved to nothing without error. See +// vcxproj_import_wildcard_test.py. +#if !defined(WILDCARD_A_DEFINE) +#error ImportBefore/a.props (matched by wildcard Import) was not imported +#endif +#if !defined(WILDCARD_B_DEFINE) +#error ImportBefore/b.props (matched by wildcard Import) was not imported +#endif + +int main() +{ + int x = 3 / 0; // ERROR + return x; +} diff --git a/test/cli/vcxproj_import_wildcard/vcxproj_import_wildcard.vcxproj b/test/cli/vcxproj_import_wildcard/vcxproj_import_wildcard.vcxproj new file mode 100644 index 00000000000..938fe0931d2 --- /dev/null +++ b/test/cli/vcxproj_import_wildcard/vcxproj_import_wildcard.vcxproj @@ -0,0 +1,36 @@ + + + + + Debug + x64 + + + + {aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee} + importwildcardtest + 10.0 + + + + Application + v143 + + + + + + + + + + + + + diff --git a/test/cli/vcxproj_import_wildcard_test.py b/test/cli/vcxproj_import_wildcard_test.py new file mode 100644 index 00000000000..c9d06096607 --- /dev/null +++ b/test/cli/vcxproj_import_wildcard_test.py @@ -0,0 +1,43 @@ + +# python -m pytest vcxproj_import_wildcard_test.py +# +# Regression coverage for wildcard support: MSBuild allows wildcards in +# an 's Project attribute -- a *different*, general mechanism from the +# Visual Studio C++ project-system restriction against wildcards in project +# items (see vcxproj_split_configs_test.py and expandItemSpec()'s rejection of +# '*'/'?' there). This is the mechanism behind the ImportBefore/ImportAfter +# extensibility folders Visual Studio's own C++ toolchain files use, e.g. +# $(VCTargetsPath)\ImportBefore\Default\*.props -- but it applies equally to +# any ordinary user-authored wildcard , which is what this fixture +# exercises directly (rather than depending on the synthetic, hand-emulated +# Microsoft.Cpp.*.props/targets handlers, which do not read the real files and +# so never reach a wildcard import inside them). +# +# This fixture's must expand to +# BOTH a.props and b.props (sorted, "as if the order had been explicitly +# set" per Microsoft's own docs), while skipping decoy.txt (its extension +# doesn't match "*.props", so it must never even be attempted as XML -- it +# isn't valid XML, so a match would make cppcheck fail outright). A second +# pointing at a directory that does +# not exist at all must be a silent no-op, not an error. +# +# main.cpp turns any of these going wrong into a #error, so the only expected +# diagnostic is the deliberate division by zero. + +import os + +from testutils import cppcheck + +__script_dir = os.path.dirname(os.path.abspath(__file__)) + + +def test_vcxproj_import_wildcard(): + args = [ + '--template=cppcheck1', + '--project=vcxproj_import_wildcard/vcxproj_import_wildcard.vcxproj', + '--no-cppcheck-build-dir' + ] + ret, stdout, stderr = cppcheck(args, cwd=__script_dir) + assert ret == 0, stdout + filename = os.path.join('vcxproj_import_wildcard', 'main.cpp') + assert stderr == '[%s:16]: (error) Division by zero.\n' % filename diff --git a/test/cli/vcxproj_include_dir_macro/RealInc/myheader.h b/test/cli/vcxproj_include_dir_macro/RealInc/myheader.h new file mode 100644 index 00000000000..133cdac53de --- /dev/null +++ b/test/cli/vcxproj_include_dir_macro/RealInc/myheader.h @@ -0,0 +1 @@ +#define FROM_REAL_HEADER 1 diff --git a/test/cli/vcxproj_include_dir_macro/main.cpp b/test/cli/vcxproj_include_dir_macro/main.cpp new file mode 100644 index 00000000000..401e2b3df79 --- /dev/null +++ b/test/cli/vcxproj_include_dir_macro/main.cpp @@ -0,0 +1,5 @@ +#include "myheader.h" +#ifndef FROM_REAL_HEADER +#error myheader.h (under RealInc) was not found +#endif +int main() { return 0; } diff --git a/test/cli/vcxproj_include_dir_macro/vcxproj_include_dir_macro.vcxproj b/test/cli/vcxproj_include_dir_macro/vcxproj_include_dir_macro.vcxproj new file mode 100644 index 00000000000..7f339ac81f4 --- /dev/null +++ b/test/cli/vcxproj_include_dir_macro/vcxproj_include_dir_macro.vcxproj @@ -0,0 +1,40 @@ + + + + + Debug + x64 + + + + {dddddddd-dddd-dddd-dddd-dddddddddddd} + incdirmacrotest + 10.0 + + + + Application + v143 + + + + + + + $(CppcheckTestIncDirMacro)\RealInc;%(AdditionalIncludeDirectories) + + + + + + + + + diff --git a/test/cli/vcxproj_include_dir_macro/vcxproj_include_dir_macro_relative.vcxproj b/test/cli/vcxproj_include_dir_macro/vcxproj_include_dir_macro_relative.vcxproj new file mode 100644 index 00000000000..cd5ced0239d --- /dev/null +++ b/test/cli/vcxproj_include_dir_macro/vcxproj_include_dir_macro_relative.vcxproj @@ -0,0 +1,38 @@ + + + + + Debug + x64 + + + + {eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee} + incdirmacrorelativetest + 10.0 + + + + Application + v143 + + + + + + + $(CppcheckTestIncDirMacro)RealInc;%(AdditionalIncludeDirectories) + + + + + + + + + diff --git a/test/cli/vcxproj_include_dir_macro_test.py b/test/cli/vcxproj_include_dir_macro_test.py new file mode 100644 index 00000000000..c56d6b5a1c6 --- /dev/null +++ b/test/cli/vcxproj_include_dir_macro_test.py @@ -0,0 +1,54 @@ +# python -m pytest vcxproj_include_dir_macro_test.py +# +# Regression coverage for an unresolvable macro in AdditionalIncludeDirectories +# (fsSetIncludePaths(), lib/importproject.cpp). +# +# MSBuild expands an undefined property to the empty string, and the result is +# handed to cl.exe like any other include directory. cppcheck must do the same +# rather than keep the literal "$(...)" text, which can never name a real +# directory. Whether the expanded entry finds anything depends on what is left: +# +# $(Undefined)\RealInc -> "\RealInc" root of the project directory's drive +# $(Undefined)RealInc -> "RealInc" relative to the project directory +# +# Both fixture projects compile main.cpp, which #includes "myheader.h" -- +# present only under RealInc/ next to the projects. CppcheckTestIncDirMacro is +# not defined anywhere (no PropertyGroup, and not expected to be a real +# environment variable). main.cpp's #error fires if and only if the header was +# not found, which independently shows how the entry was resolved. + +import os + +from testutils import cppcheck + +__script_dir = os.path.dirname(os.path.abspath(__file__)) + +__not_found = ('vcxproj_include_dir_macro/main.cpp:3:2: error: #error myheader.h (under RealInc) ' + 'was not found [preprocessorErrorDirective]') + + +def __run(project): + args = [ + '--project=vcxproj_include_dir_macro/%s' % project, + '--no-cppcheck-build-dir', + ] + ret, stdout, stderr = cppcheck(args, cwd=__script_dir) + assert ret == 0, stdout + # The unresolved macro is expanded to an empty string, as Visual Studio + # does -- it is not reported as a project import error. + assert 'cppcheck: error:' not in stdout, stdout + return stderr.replace('\\', '/') + + +def test_vcxproj_include_dir_macro_root_relative(): + # "\RealInc" is the root of the drive, not the RealInc folder next to the + # project, so myheader.h must NOT be found. + stderr = __run('vcxproj_include_dir_macro.vcxproj') + assert __not_found in stderr, stderr + + +def test_vcxproj_include_dir_macro_relative(): + # "RealInc" is relative to the project directory, so myheader.h IS found. + stderr = __run('vcxproj_include_dir_macro_relative.vcxproj') + assert __not_found not in stderr, stderr + assert 'preprocessorErrorDirective' not in stderr, stderr diff --git a/test/cli/vcxproj_item_absolute_drive_path/vcxproj_item_absolute_drive_path.vcxproj b/test/cli/vcxproj_item_absolute_drive_path/vcxproj_item_absolute_drive_path.vcxproj new file mode 100644 index 00000000000..26a6097d11f --- /dev/null +++ b/test/cli/vcxproj_item_absolute_drive_path/vcxproj_item_absolute_drive_path.vcxproj @@ -0,0 +1,36 @@ + + + + + Debug + x64 + + + + {dddddddd-eeee-ffff-0000-111111111111} + itemabsolutedrivepathtest + 10.0 + + + + Application + v143 + + + + + + + + + + diff --git a/test/cli/vcxproj_item_absolute_drive_path_test.py b/test/cli/vcxproj_item_absolute_drive_path_test.py new file mode 100644 index 00000000000..de22d09de1e --- /dev/null +++ b/test/cli/vcxproj_item_absolute_drive_path_test.py @@ -0,0 +1,49 @@ + +# python -m pytest vcxproj_item_absolute_drive_path_test.py +# +# Regression coverage for ImportProject::toAbsoluteExpanded() using the +# host-dependent Path::isAbsolute() to decide whether a ClCompile item path +# is already fully rooted (lib/importproject.cpp). Path::isAbsolute() only +# recognizes a leading '/' on a non-Windows host, so a Windows +# drive-absolute path straight out of a .vcxproj -- e.g. "C:/nonexistent/foo.cpp", +# with no macro involved at all -- would be misclassified as relative and +# joined onto the project directory instead of used as-is, producing +# something like "/C:/nonexistent/foo.cpp" -- not what real +# Visual Studio does (and not what toAbsoluteExpanded() itself would do +# running natively on Windows, where Path::isAbsolute() DOES recognize +# "C:\..." as absolute). The fix uses classifyPath() -- the same +# host-independent Windows/Unix path classification already used throughout +# this file (see pathCombineAppend(), toAbsolute(), etc.) -- instead. +# +# This fixture's only item is Include="C:\nonexistent\foo.cpp", +# a plain drive-absolute path with nothing to macro-expand, so +# expandItemSpec() passes it straight through to toAbsoluteExpanded(). + +import os + +from testutils import cppcheck + +__script_dir = os.path.dirname(os.path.abspath(__file__)) + + +def test_vcxproj_item_absolute_drive_path(): + args = [ + '--project=vcxproj_item_absolute_drive_path/vcxproj_item_absolute_drive_path.vcxproj', + '--no-cppcheck-build-dir', + ] + ret, stdout, stderr = cppcheck(args, cwd=__script_dir) + assert ret == 0, stdout + + normalized_stdout = stdout.replace('\\', '/') + normalized_stderr = stderr.replace('\\', '/') + + # The drive-absolute path must be used exactly as-is -- not joined onto + # the project directory. + assert 'Checking C:/nonexistent/foo.cpp Debug|x64...' in normalized_stdout, stdout + assert normalized_stderr.count('C:/nonexistent/foo.cpp:0:0: error: File is missing: C:/nonexistent/foo.cpp [missingFile]') == 1, stderr + + # It must NOT have been nested under the project directory -- that would + # mean toAbsoluteExpanded() wrongly treated the already-absolute path as + # relative and joined it onto projectDir. + assert 'vcxproj_item_absolute_drive_path/C:' not in normalized_stdout, stdout + assert 'vcxproj_item_absolute_drive_path/C:' not in normalized_stderr, stderr diff --git a/test/cli/vcxproj_item_condition/foo.cpp b/test/cli/vcxproj_item_condition/foo.cpp new file mode 100644 index 00000000000..76e8197013a --- /dev/null +++ b/test/cli/vcxproj_item_condition/foo.cpp @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/test/cli/vcxproj_item_condition/vcxproj_item_condition.vcxproj b/test/cli/vcxproj_item_condition/vcxproj_item_condition.vcxproj new file mode 100644 index 00000000000..2c293865e2b --- /dev/null +++ b/test/cli/vcxproj_item_condition/vcxproj_item_condition.vcxproj @@ -0,0 +1,39 @@ + + + + + Debug + x64 + + + Release + x64 + + + + {66666666-6666-6666-6666-666666666666} + itemcondtest + 10.0 + + + + Application + v143 + + + Application + v143 + + + + + + + + + + diff --git a/test/cli/vcxproj_item_condition_test.py b/test/cli/vcxproj_item_condition_test.py new file mode 100644 index 00000000000..5788df861fd --- /dev/null +++ b/test/cli/vcxproj_item_condition_test.py @@ -0,0 +1,47 @@ + +# python -m pytest vcxproj_item_condition_test.py +# +# Regression coverage for Condition on a ClCompile *project item* itself +# (as opposed to Condition on the item's metadata children, which is a +# different, fully-supported feature -- see applyClCompileChild()). +# +# Microsoft documents that the Visual Studio C++ project system does not +# support Condition on project items: "Conditions aren't supported for +# Project items (that is, item types that are treated as project items by +# rules definitions)." -- see +# https://learn.microsoft.com/en-us/cpp/build/reference/vcxproj-file-structure +# +# This fixture's only item is Include="foo.cpp" +# Condition="'$(Configuration)'=='Debug'". A discovery/evaluation pass that +# honors that Condition (i.e. treats the item like any other conditioned +# element) drops foo.cpp entirely when evaluating Release|x64 -- but real +# Visual Studio ignores the Condition on the item itself and always includes +# foo.cpp, in both Debug and Release. So foo.cpp must be checked under both +# configurations. + +import os + +from testutils import cppcheck + +__script_dir = os.path.dirname(os.path.abspath(__file__)) + + +def test_vcxproj_item_condition(): + args = [ + '--project=vcxproj_item_condition/vcxproj_item_condition.vcxproj', + '--no-cppcheck-build-dir', + '--dump' + ] + ret, stdout, _ = cppcheck(args, cwd=__script_dir) + assert ret == 0, stdout + + # Windows prints native '\' path separators ("Checking foo\main.cpp ..."); + # normalize before matching so this passes on every platform (same idiom + # used by test_log() in clang-import_test.py). + normalized_stdout = stdout.replace('\\', '/') + + # foo.cpp's Condition is gated on Configuration=='Debug', but Visual + # Studio does not evaluate Condition on project items -- so foo.cpp must + # be checked under BOTH configurations, not just Debug. + assert 'Checking vcxproj_item_condition/foo.cpp Debug|x64' in normalized_stdout, stdout + assert 'Checking vcxproj_item_condition/foo.cpp Release|x64' in normalized_stdout, stdout diff --git a/test/cli/vcxproj_item_macro_path/SomeDir/foo.cpp b/test/cli/vcxproj_item_macro_path/SomeDir/foo.cpp new file mode 100644 index 00000000000..76e8197013a --- /dev/null +++ b/test/cli/vcxproj_item_macro_path/SomeDir/foo.cpp @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/test/cli/vcxproj_item_macro_path/vcxproj_item_macro_path.vcxproj b/test/cli/vcxproj_item_macro_path/vcxproj_item_macro_path.vcxproj new file mode 100644 index 00000000000..639f8d759e2 --- /dev/null +++ b/test/cli/vcxproj_item_macro_path/vcxproj_item_macro_path.vcxproj @@ -0,0 +1,52 @@ + + + + + Debug + x64 + + + Release + x64 + + + + {77777777-7777-7777-7777-777777777777} + itemmacropathtest + 10.0 + + + + Application + v143 + + + Application + v143 + + + + + + SomeDir + + + SomeOtherDir + + + + + + + diff --git a/test/cli/vcxproj_item_macro_path_case_builtin/foo.cpp b/test/cli/vcxproj_item_macro_path_case_builtin/foo.cpp new file mode 100644 index 00000000000..c5e4ff6a07d --- /dev/null +++ b/test/cli/vcxproj_item_macro_path_case_builtin/foo.cpp @@ -0,0 +1,8 @@ +// Reached only if $(msbuildprojectdirectory)\foo.cpp was correctly +// recognized as the (case-insensitive) built-in MSBuildProjectDirectory +// property and expanded -- see vcxproj_item_macro_path_case_test.py. +int main() +{ + int x = 3 / 0; // ERROR + return x; +} diff --git a/test/cli/vcxproj_item_macro_path_case_builtin/vcxproj_item_macro_path_case_builtin.vcxproj b/test/cli/vcxproj_item_macro_path_case_builtin/vcxproj_item_macro_path_case_builtin.vcxproj new file mode 100644 index 00000000000..b8babcfcd02 --- /dev/null +++ b/test/cli/vcxproj_item_macro_path_case_builtin/vcxproj_item_macro_path_case_builtin.vcxproj @@ -0,0 +1,35 @@ + + + + + Debug + x64 + + + + {bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb} + itemmacropathcasebuiltintest + 10.0 + + + + Application + v143 + + + + + + + + + + diff --git a/test/cli/vcxproj_item_macro_path_case_invariant/SameCaseDir/mathplot.cpp b/test/cli/vcxproj_item_macro_path_case_invariant/SameCaseDir/mathplot.cpp new file mode 100644 index 00000000000..f35ae700ca5 --- /dev/null +++ b/test/cli/vcxproj_item_macro_path_case_invariant/SameCaseDir/mathplot.cpp @@ -0,0 +1,8 @@ +// Reached only if $(MYROOT)\mathplot.cpp was correctly recognized as the +// same config-invariant property as MyRoot/myroot and expanded -- see +// vcxproj_item_macro_path_case_test.py. +int main() +{ + int x = 3 / 0; // ERROR + return x; +} diff --git a/test/cli/vcxproj_item_macro_path_case_invariant/vcxproj_item_macro_path_case_invariant.vcxproj b/test/cli/vcxproj_item_macro_path_case_invariant/vcxproj_item_macro_path_case_invariant.vcxproj new file mode 100644 index 00000000000..29810cf054e --- /dev/null +++ b/test/cli/vcxproj_item_macro_path_case_invariant/vcxproj_item_macro_path_case_invariant.vcxproj @@ -0,0 +1,49 @@ + + + + + Debug + x64 + + + Release + x64 + + + + {aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa} + itemmacropathcaseinvarianttest + 10.0 + + + + Application + v143 + + + Application + v143 + + + + + + SameCaseDir + + + SameCaseDir + + + + + + + diff --git a/test/cli/vcxproj_item_macro_path_case_test.py b/test/cli/vcxproj_item_macro_path_case_test.py new file mode 100644 index 00000000000..d4e80820ed5 --- /dev/null +++ b/test/cli/vcxproj_item_macro_path_case_test.py @@ -0,0 +1,107 @@ + +# python -m pytest vcxproj_item_macro_path_case_test.py +# +# Regression coverage for MSBuild property-name case-insensitivity in the +# ClCompile item-path macro analysis (expandItemSpec() / +# hasOnlyInvariantItemPathVariables(), lib/importproject.cpp). Real MSBuild +# property names are case-insensitive -- PropertiesMap itself already uses +# cppcheck::stricmp for exactly this reason -- but the config-invariance +# analysis this feature added (mConfigInvariantProperties, the priming-pass +# valuesByProperty map, and the fixed invariantItemPathProperties() set) used +# plain, case-sensitive std::set/std::map. That mismatch has two distinct, +# independently regressed failure modes: +# +# 1. A property genuinely set to DIFFERENT values in different +# configurations, but spelled with different casing in each +# PropertyGroup (e.g. for Debug, for Release), gets +# tracked as two unrelated single-valued names instead of one +# multi-valued one -- each looks (wrongly) invariant on its own, so +# cppcheck would expand and check two different real files instead of +# correctly leaving the item unexpanded (matching real Visual Studio's +# documented restriction -- see vcxproj_item_macro_path_test.py). +# test_vcxproj_item_macro_path_case_variant covers this. +# +# 2. A property that genuinely IS config-invariant (same value everywhere), +# or one of the fixed MSBuildThisFile.../MSBuildProject... properties, +# fails to be recognized as such if it's spelled with different casing +# across the PropertyGroups that set it and/or the item path that +# references it -- real Visual Studio resolves it regardless, but +# cppcheck would wrongly reject it and report a real, checkable file as +# missing. test_vcxproj_item_macro_path_case_invariant (a user-defined +# property spelled three different ways) and +# test_vcxproj_item_macro_path_case_builtin (a fixed MSBuild property +# referenced in lowercase) cover this. + +import os + +from testutils import cppcheck + +__script_dir = os.path.dirname(os.path.abspath(__file__)) + + +def test_vcxproj_item_macro_path_case_variant(): + args = [ + '--project=vcxproj_item_macro_path_case_variant/vcxproj_item_macro_path_case_variant.vcxproj', + '--no-cppcheck-build-dir', + ] + ret, stdout, stderr = cppcheck(args, cwd=__script_dir) + assert ret == 0, stdout + + normalized_stdout = stdout.replace('\\', '/') + normalized_stderr = stderr.replace('\\', '/') + literal_path = 'vcxproj_item_macro_path_case_variant/$(CaseRoot)/foo.cpp' + + # $(CaseRoot) genuinely varies by configuration -- "CaseRoot" (Debug) and + # "caseroot" (Release) are the same MSBuild property, just spelled + # differently -- so it must NOT be expanded in either configuration, + # exactly like vcxproj_item_macro_path_test.py's same-casing case. + assert ('Checking %s Debug|x64...' % literal_path) in normalized_stdout, stdout + assert ('Checking %s Release|x64...' % literal_path) in normalized_stdout, stdout + assert normalized_stderr.count('%s:0:0: error: File is missing: %s [missingFile]' % (literal_path, literal_path)) == 1, stderr + + # The real files under DebugCaseDir/ and ReleaseCaseDir/ must never be + # reached -- if they were, their #error would surface here instead, and + # that would mean the case-sensitivity bug let a genuinely-varying + # property slip through as "invariant". + assert 'DebugCaseDir/foo.cpp' not in normalized_stdout, stdout + assert 'ReleaseCaseDir/foo.cpp' not in normalized_stdout, stdout + + +def test_vcxproj_item_macro_path_case_invariant(): + args = [ + '--project=vcxproj_item_macro_path_case_invariant/vcxproj_item_macro_path_case_invariant.vcxproj', + '--no-cppcheck-build-dir', + ] + ret, stdout, stderr = cppcheck(args, cwd=__script_dir) + assert ret == 0, stdout + + normalized_stdout = stdout.replace('\\', '/') + filename = 'vcxproj_item_macro_path_case_invariant/SameCaseDir/mathplot.cpp' + + # $(MYROOT) must resolve in both configurations to the one property real + # Visual Studio sees, however it's spelled at each definition/reference + # site (MyRoot / myroot / MYROOT) -- not be rejected as unsupported. + assert ('Checking %s Debug|x64...' % filename) in normalized_stdout, stdout + assert ('Checking %s Release|x64...' % filename) in normalized_stdout, stdout + + normalized_stderr = stderr.replace('\\', '/') + assert normalized_stderr.count('%s:6:15: error: Division by zero.' % filename) == 1, stderr + + +def test_vcxproj_item_macro_path_case_builtin(): + args = [ + '--project=vcxproj_item_macro_path_case_builtin/vcxproj_item_macro_path_case_builtin.vcxproj', + '--no-cppcheck-build-dir', + ] + ret, stdout, stderr = cppcheck(args, cwd=__script_dir) + assert ret == 0, stdout + + normalized_stdout = stdout.replace('\\', '/') + filename = 'vcxproj_item_macro_path_case_builtin/foo.cpp' + + # $(msbuildprojectdirectory), all lowercase, must still be recognized as + # the fixed MSBuildProjectDirectory property and expanded. + assert ('Checking %s Debug|x64...' % filename) in normalized_stdout, stdout + + normalized_stderr = stderr.replace('\\', '/') + assert normalized_stderr.count('%s:6:15: error: Division by zero.' % filename) == 1, stderr diff --git a/test/cli/vcxproj_item_macro_path_case_variant/DebugCaseDir/foo.cpp b/test/cli/vcxproj_item_macro_path_case_variant/DebugCaseDir/foo.cpp new file mode 100644 index 00000000000..9a9e81d27c6 --- /dev/null +++ b/test/cli/vcxproj_item_macro_path_case_variant/DebugCaseDir/foo.cpp @@ -0,0 +1,2 @@ +#error DebugCaseDir/foo.cpp must never be checked -- $(CaseRoot) genuinely varies by configuration (case-insensitively) and must not be expanded +int main() { return 0; } diff --git a/test/cli/vcxproj_item_macro_path_case_variant/ReleaseCaseDir/foo.cpp b/test/cli/vcxproj_item_macro_path_case_variant/ReleaseCaseDir/foo.cpp new file mode 100644 index 00000000000..5d4ef0c337c --- /dev/null +++ b/test/cli/vcxproj_item_macro_path_case_variant/ReleaseCaseDir/foo.cpp @@ -0,0 +1,2 @@ +#error ReleaseCaseDir/foo.cpp must never be checked -- $(CaseRoot) genuinely varies by configuration (case-insensitively) and must not be expanded +int main() { return 0; } diff --git a/test/cli/vcxproj_item_macro_path_case_variant/vcxproj_item_macro_path_case_variant.vcxproj b/test/cli/vcxproj_item_macro_path_case_variant/vcxproj_item_macro_path_case_variant.vcxproj new file mode 100644 index 00000000000..cd79684f61f --- /dev/null +++ b/test/cli/vcxproj_item_macro_path_case_variant/vcxproj_item_macro_path_case_variant.vcxproj @@ -0,0 +1,53 @@ + + + + + Debug + x64 + + + Release + x64 + + + + {99999999-9999-9999-9999-999999999999} + itemmacropathcasevarianttest + 10.0 + + + + Application + v143 + + + Application + v143 + + + + + + DebugCaseDir + + + ReleaseCaseDir + + + + + + + diff --git a/test/cli/vcxproj_item_macro_path_env/EnvLibRoot/mathplot.cpp b/test/cli/vcxproj_item_macro_path_env/EnvLibRoot/mathplot.cpp new file mode 100644 index 00000000000..6300635aa46 --- /dev/null +++ b/test/cli/vcxproj_item_macro_path_env/EnvLibRoot/mathplot.cpp @@ -0,0 +1,8 @@ +// Reached only if $(CppcheckTestEnvLibRoot)\mathplot.cpp was correctly +// resolved via the CppcheckTestEnvLibRoot environment variable -- see +// vcxproj_item_macro_path_env_test.py. +int main() +{ + int x = 3 / 0; // ERROR + return x; +} diff --git a/test/cli/vcxproj_item_macro_path_env/vcxproj_item_macro_path_env.vcxproj b/test/cli/vcxproj_item_macro_path_env/vcxproj_item_macro_path_env.vcxproj new file mode 100644 index 00000000000..304c471589d --- /dev/null +++ b/test/cli/vcxproj_item_macro_path_env/vcxproj_item_macro_path_env.vcxproj @@ -0,0 +1,45 @@ + + + + + Debug + x64 + + + Release + x64 + + + + {99999999-9999-9999-9999-999999999999} + itemmacropathenvtest + 10.0 + + + + Application + v143 + + + Application + v143 + + + + + + + + + + diff --git a/test/cli/vcxproj_item_macro_path_env_test.py b/test/cli/vcxproj_item_macro_path_env_test.py new file mode 100644 index 00000000000..ff0e7a99b66 --- /dev/null +++ b/test/cli/vcxproj_item_macro_path_env_test.py @@ -0,0 +1,74 @@ + +# python -m pytest vcxproj_item_macro_path_env_test.py +# +# Regression coverage for the environment-variable fallback in +# expandItemSpec()'s ClCompile item-path macro check (see +# vcxproj_item_macro_path_test.py / vcxproj_item_macro_path_invariant_test.py +# for the general rule this is a special case of): a $(...) reference in an +# Include/Update/Remove path to a property no PropertyGroup or property +# sheet in the project ever sets, but that resolves via a real OS +# environment variable of the same name, is config-invariant exactly like a +# property-sheet constant is -- an environment variable has exactly one +# value for the whole cppcheck invocation, it cannot differ between +# Debug|x64 and Release|x64 -- and real MSBuild property evaluation itself +# falls back to the environment for anything nothing else defines. So real +# Visual Studio resolves it, and cppcheck must too. +# +# This fixture's only item is +# Include="$(CppcheckTestEnvLibRoot)\mathplot.cpp", where +# CppcheckTestEnvLibRoot is not defined anywhere in the project itself. + +import os + +from testutils import cppcheck + +__script_dir = os.path.dirname(os.path.abspath(__file__)) + + +def test_vcxproj_item_macro_path_env_var_set(): + env = os.environ.copy() + env['CppcheckTestEnvLibRoot'] = 'EnvLibRoot' + + args = [ + '--project=vcxproj_item_macro_path_env/vcxproj_item_macro_path_env.vcxproj', + '--no-cppcheck-build-dir', + ] + ret, stdout, stderr = cppcheck(args, cwd=__script_dir, env=env) + assert ret == 0, stdout + + normalized_stdout = stdout.replace('\\', '/') + filename = 'vcxproj_item_macro_path_env/EnvLibRoot/mathplot.cpp' + + # Both configurations must resolve $(CppcheckTestEnvLibRoot)\mathplot.cpp + # via the environment variable to the same real file and actually check + # it -- not treat the macro as unsupported just because no PropertyGroup + # in the project itself ever set it. + assert ('Checking %s Debug|x64...' % filename) in normalized_stdout, stdout + assert ('Checking %s Release|x64...' % filename) in normalized_stdout, stdout + + normalized_stderr = stderr.replace('\\', '/') + assert normalized_stderr.count('%s:6:15: error: Division by zero.' % filename) == 1, stderr + + +def test_vcxproj_item_macro_path_env_var_unset(): + env = os.environ.copy() + env.pop('CppcheckTestEnvLibRoot', None) + + args = [ + '--project=vcxproj_item_macro_path_env/vcxproj_item_macro_path_env.vcxproj', + '--no-cppcheck-build-dir', + ] + ret, stdout, stderr = cppcheck(args, cwd=__script_dir, env=env) + assert ret == 0, stdout + + normalized_stdout = stdout.replace('\\', '/') + normalized_stderr = stderr.replace('\\', '/') + literal_path = 'vcxproj_item_macro_path_env/$(CppcheckTestEnvLibRoot)/mathplot.cpp' + + # With no environment variable and no project property defining it + # either, $(CppcheckTestEnvLibRoot) is genuinely unresolvable -- the item + # must still be checked (and reported missing) under its literal path in + # both configurations, not silently dropped. + assert ('Checking %s Debug|x64...' % literal_path) in normalized_stdout, stdout + assert ('Checking %s Release|x64...' % literal_path) in normalized_stdout, stdout + assert normalized_stderr.count('%s:0:0: error: File is missing: %s [missingFile]' % (literal_path, literal_path)) == 1, stderr diff --git a/test/cli/vcxproj_item_macro_path_hyphen/HyphenDir/foo.cpp b/test/cli/vcxproj_item_macro_path_hyphen/HyphenDir/foo.cpp new file mode 100644 index 00000000000..7bb863299e2 --- /dev/null +++ b/test/cli/vcxproj_item_macro_path_hyphen/HyphenDir/foo.cpp @@ -0,0 +1,10 @@ +// Reached only if $(My-Root)\foo.cpp was correctly recognized as a +// config-invariant property reference and expanded -- which requires the +// hyphen in "My-Root" to be accepted as part of the property name by +// hasOnlyInvariantItemPathVariables()'s inline scanner. See +// vcxproj_item_macro_path_hyphen_test.py. +int main() +{ + int x = 3 / 0; // ERROR + return x; +} diff --git a/test/cli/vcxproj_item_macro_path_hyphen/vcxproj_item_macro_path_hyphen.vcxproj b/test/cli/vcxproj_item_macro_path_hyphen/vcxproj_item_macro_path_hyphen.vcxproj new file mode 100644 index 00000000000..bab49b16519 --- /dev/null +++ b/test/cli/vcxproj_item_macro_path_hyphen/vcxproj_item_macro_path_hyphen.vcxproj @@ -0,0 +1,45 @@ + + + + + Debug + x64 + + + + {bbbbbbbb-cccc-dddd-eeee-ffffffffffff} + itemmacropathhyphentest + 10.0 + + + + Application + v143 + + + + + + HyphenDir + + + + + + + diff --git a/test/cli/vcxproj_item_macro_path_hyphen_test.py b/test/cli/vcxproj_item_macro_path_hyphen_test.py new file mode 100644 index 00000000000..3218e751f1f --- /dev/null +++ b/test/cli/vcxproj_item_macro_path_hyphen_test.py @@ -0,0 +1,49 @@ + +# python -m pytest vcxproj_item_macro_path_hyphen_test.py +# +# Regression coverage for hasOnlyInvariantItemPathVariables()'s inline +# property-name scanner rejecting '-' (lib/importproject.cpp). Real MSBuild +# property names are [A-Za-z_][A-Za-z0-9_-]* -- '-' IS valid after the +# first character, e.g. HyphenDir referenced as +# $(My-Root) -- but the scanner used to decide whether a ClCompile item +# path's macro references are all safely config-invariant only accepted +# [A-Za-z0-9_], so it stopped at the first '-' and captured a truncated +# name. Since the character right after that truncated name was '-' (not +# the required closing ')'), the whole reference was rejected as "not +# provably invariant" and left unexpanded, even though this property is +# only ever set once, to one value -- genuinely invariant, exactly like +# vcxproj_item_macro_path_invariant_test.py's ExternalLibRoot. See +# vcxproj_item_macro_path_hyphen/vcxproj_item_macro_path_hyphen.vcxproj. + +import os + +from testutils import cppcheck + +__script_dir = os.path.dirname(os.path.abspath(__file__)) + + +def test_vcxproj_item_macro_path_hyphen(): + args = [ + '--project=vcxproj_item_macro_path_hyphen/vcxproj_item_macro_path_hyphen.vcxproj', + '--no-cppcheck-build-dir', + ] + ret, stdout, stderr = cppcheck(args, cwd=__script_dir) + assert ret == 0, stdout + + normalized_stdout = stdout.replace('\\', '/') + filename = 'vcxproj_item_macro_path_hyphen/HyphenDir/foo.cpp' + + # $(My-Root) must resolve to "HyphenDir" and be expanded -- reaching and + # checking the real file, not being left as the literal, un-expandable + # "$(My-Root)\foo.cpp" text. + assert ('Checking %s Debug|x64...' % filename) in normalized_stdout, stdout + + normalized_stderr = stderr.replace('\\', '/') + assert normalized_stderr.count('%s:8:15: error: Division by zero.' % filename) == 1, stderr + + # It must never be reported as a missing file under the literal, + # unexpanded macro text -- that would mean the hyphen truncated the + # scanned property name and the reference was wrongly rejected as + # "not provably invariant". + assert '$(My-Root)' not in normalized_stdout, stdout + assert '$(My-Root)' not in normalized_stderr, stderr diff --git a/test/cli/vcxproj_item_macro_path_invariant/ExternalLibRoot/mathplot.cpp b/test/cli/vcxproj_item_macro_path_invariant/ExternalLibRoot/mathplot.cpp new file mode 100644 index 00000000000..7c43e7f9d47 --- /dev/null +++ b/test/cli/vcxproj_item_macro_path_invariant/ExternalLibRoot/mathplot.cpp @@ -0,0 +1,7 @@ +// Reached only if $(ExternalLibRoot)\mathplot.cpp was correctly expanded and +// checked -- see vcxproj_item_macro_path_invariant_test.py. +int main() +{ + int x = 3 / 0; // ERROR + return x; +} diff --git a/test/cli/vcxproj_item_macro_path_invariant/vcxproj_item_macro_path_invariant.vcxproj b/test/cli/vcxproj_item_macro_path_invariant/vcxproj_item_macro_path_invariant.vcxproj new file mode 100644 index 00000000000..cc812ac6072 --- /dev/null +++ b/test/cli/vcxproj_item_macro_path_invariant/vcxproj_item_macro_path_invariant.vcxproj @@ -0,0 +1,51 @@ + + + + + Debug + x64 + + + Release + x64 + + + + {88888888-8888-8888-8888-888888888888} + itemmacropathinvarianttest + 10.0 + + + + Application + v143 + + + Application + v143 + + + + + + ExternalLibRoot + + + + + + + diff --git a/test/cli/vcxproj_item_macro_path_invariant_test.py b/test/cli/vcxproj_item_macro_path_invariant_test.py new file mode 100644 index 00000000000..a7928ad5c46 --- /dev/null +++ b/test/cli/vcxproj_item_macro_path_invariant_test.py @@ -0,0 +1,52 @@ + +# python -m pytest vcxproj_item_macro_path_invariant_test.py +# +# Regression coverage for the flip side of vcxproj_item_macro_path_test.py: +# a macro in a ClCompile project item's Include/Update/Remove path whose +# value is the SAME in every one of the project's configurations. Real Visual +# Studio has no trouble resolving this -- it's the well-known pattern of a +# property sheet (or, as here, an unconditioned PropertyGroup) defining a +# fixed third-party library location, e.g. $(wxMathPlot) or $(BoostRoot), and +# referencing it directly in an Include path. Microsoft's documented +# restriction is specifically about macros whose value COULD differ by +# configuration -- see vcxproj_item_macro_path_test.py and +# https://learn.microsoft.com/en-us/cpp/build/reference/vcxproj-file-structure +# -- so a config-invariant macro like this one is not the case that +# restriction describes, and cppcheck must expand it just like real Visual +# Studio does, not treat it the same as a genuinely varying one. +# +# This fixture has two configurations (Debug|x64, Release|x64) and one +# , where +# ExternalLibRoot is set once, unconditionally, to the same value regardless +# of configuration. main.cpp's deliberate division by zero proves the file +# was actually found and checked (not silently skipped as unresolved) in +# every configuration. + +import os + +from testutils import cppcheck + +__script_dir = os.path.dirname(os.path.abspath(__file__)) + + +def test_vcxproj_item_macro_path_invariant(): + args = [ + '--project=vcxproj_item_macro_path_invariant/vcxproj_item_macro_path_invariant.vcxproj', + '--no-cppcheck-build-dir', + ] + ret, stdout, stderr = cppcheck(args, cwd=__script_dir) + assert ret == 0, stdout + + normalized_stdout = stdout.replace('\\', '/') + filename = 'vcxproj_item_macro_path_invariant/ExternalLibRoot/mathplot.cpp' + + # Both configurations must resolve $(ExternalLibRoot)\mathplot.cpp to the + # same real file and actually check it -- not treat the macro as + # unsupported and end up with no valid source files. + assert ('Checking %s Debug|x64...' % filename) in normalized_stdout, stdout + assert ('Checking %s Release|x64...' % filename) in normalized_stdout, stdout + + # Same underlying file checked under both configurations -- cppcheck + # dedups the identical diagnostic, so it's reported exactly once. + normalized_stderr = stderr.replace('\\', '/') + assert normalized_stderr.count('%s:5:15: error: Division by zero.' % filename) == 1, stderr diff --git a/test/cli/vcxproj_item_macro_path_missing_config/DebugDir/foo.cpp b/test/cli/vcxproj_item_macro_path_missing_config/DebugDir/foo.cpp new file mode 100644 index 00000000000..0e5ed632320 --- /dev/null +++ b/test/cli/vcxproj_item_macro_path_missing_config/DebugDir/foo.cpp @@ -0,0 +1,2 @@ +#error DebugDir/foo.cpp must never be checked -- $(MyRoot) is unset in Release|x64, so it genuinely varies by configuration and must not be expanded +int main() { return 0; } diff --git a/test/cli/vcxproj_item_macro_path_missing_config/vcxproj_item_macro_path_missing_config.vcxproj b/test/cli/vcxproj_item_macro_path_missing_config/vcxproj_item_macro_path_missing_config.vcxproj new file mode 100644 index 00000000000..628207b23b5 --- /dev/null +++ b/test/cli/vcxproj_item_macro_path_missing_config/vcxproj_item_macro_path_missing_config.vcxproj @@ -0,0 +1,50 @@ + + + + + Debug + x64 + + + Release + x64 + + + + {cccccccc-cccc-cccc-cccc-cccccccccccc} + itemmacropathmissingconfigtest + 10.0 + + + + Application + v143 + + + Application + v143 + + + + + + DebugDir + + + + + + + diff --git a/test/cli/vcxproj_item_macro_path_missing_config_test.py b/test/cli/vcxproj_item_macro_path_missing_config_test.py new file mode 100644 index 00000000000..2100e0dc02f --- /dev/null +++ b/test/cli/vcxproj_item_macro_path_missing_config_test.py @@ -0,0 +1,55 @@ + +# python -m pytest vcxproj_item_macro_path_missing_config_test.py +# +# Regression coverage for mConfigInvariantProperties's priming pass only +# recording a property's value in configurations where it is actually SET +# (lib/importproject.cpp). A property a PropertyGroup sets in only some of a +# project's configurations -- with no PropertyGroup for it at all in the +# others -- still genuinely varies by configuration: real MSBuild resolves +# $(MyRoot) to the PropertyGroup's value under the configuration that sets +# it, and to "" under every configuration that doesn't (no PropertyGroup, no +# matching OS environment variable -- see +# vcxproj_item_macro_path_env_test.py for the case where one does exist). +# Naively collecting only the values each configuration's own property map +# happens to contain misses this: a property set in exactly one +# configuration has exactly one recorded value there and looks spuriously +# invariant, when the real, config-varying picture is "one real value in one +# configuration, empty in the rest" -- two distinct values, not one. +# +# This fixture's only item is Include="$(MyRoot)\foo.cpp", where +# MyRoot is set only by Debug|x64's PropertyGroup; Release|x64 has none. +# DebugDir/foo.cpp's #error independently proves the item was never expanded +# and checked under Debug -- only that it consistently stays literal (and +# reported missing) in both configurations, exactly like +# vcxproj_item_macro_path_test.py's same-two-explicit-values case. + +import os + +from testutils import cppcheck + +__script_dir = os.path.dirname(os.path.abspath(__file__)) + + +def test_vcxproj_item_macro_path_missing_config(): + args = [ + '--project=vcxproj_item_macro_path_missing_config/vcxproj_item_macro_path_missing_config.vcxproj', + '--no-cppcheck-build-dir', + ] + ret, stdout, stderr = cppcheck(args, cwd=__script_dir) + assert ret == 0, stdout + + normalized_stdout = stdout.replace('\\', '/') + normalized_stderr = stderr.replace('\\', '/') + literal_path = 'vcxproj_item_macro_path_missing_config/$(MyRoot)/foo.cpp' + + # $(MyRoot) is unset in Release|x64 -- it genuinely varies by + # configuration -- so it must NOT be expanded in EITHER configuration, + # not just the one where it happens to be unset. + assert ('Checking %s Debug|x64...' % literal_path) in normalized_stdout, stdout + assert ('Checking %s Release|x64...' % literal_path) in normalized_stdout, stdout + assert normalized_stderr.count('%s:0:0: error: File is missing: %s [missingFile]' % (literal_path, literal_path)) == 1, stderr + + # DebugDir/foo.cpp must never be reached -- if it were, its #error would + # surface here instead, meaning MyRoot's being unset in Release wrongly + # let Debug's value be treated as config-invariant. + assert 'DebugDir/foo.cpp' not in normalized_stdout, stdout diff --git a/test/cli/vcxproj_item_macro_path_test.py b/test/cli/vcxproj_item_macro_path_test.py new file mode 100644 index 00000000000..1cb35164492 --- /dev/null +++ b/test/cli/vcxproj_item_macro_path_test.py @@ -0,0 +1,69 @@ + +# python -m pytest vcxproj_item_macro_path_test.py +# +# Regression coverage for macro ($(...)) expansion in a ClCompile project +# item's Include/Update/Remove path (as opposed to expansion elsewhere, e.g. +# in metadata values or Condition attributes, which is unaffected). +# +# Microsoft documents that the Visual Studio C++ project system does not +# reliably resolve a macro in a project item path if that macro's value could +# differ by configuration -- "The IDE doesn't expect project item paths to be +# different for different project configurations" -- see +# https://learn.microsoft.com/en-us/cpp/build/reference/vcxproj-file-structure +# That is specifically about macros whose value VARIES by configuration: the +# fixed set of MSBuild "this file"/"this project" location properties +# (MSBuildThisFileDirectory and friends) are always exempt, since they can't +# vary by construction -- see test/cli/shared-items-project, covered +# separately by test_shared_items_project() in more-projects_test.py -- and so +# is any other property THIS project happens to resolve to the same value in +# every one of its configurations, e.g. one a property sheet sets once, +# unconditionally (see vcxproj_item_macro_path_invariant_test.py). +# +# This fixture's only item is Include="$(SomeDir)\foo.cpp", where +# SomeDir is an ordinary user-defined property that genuinely differs between +# the project's two configurations (SomeDir for Debug|x64, SomeOtherDir for +# Release|x64) -- exactly the case Visual Studio can't reliably resolve, since +# a single Solution Explorer file list can't show two different files +# depending on which configuration happens to be active. Expanding $(SomeDir) +# here would resolve to a real file that genuinely exists on disk in either +# configuration -- but real Visual Studio does not expand it, so cppcheck +# must not either. +# +# Critically, "must not expand it" does not mean the item silently vanishes: +# a user whose real project has a file like this would have no way to know +# it went unchecked. So the item is still kept, under its literal, +# unexpanded path ("vcxproj_item_macro_path/$(SomeDir)/foo.cpp") -- which +# can never exist on disk -- and cppcheck reports it exactly like any other +# missing source file, a normal, visible "File is missing: ..." error that +# also names the exact unresolved macro path, not just an addDebug() trace +# nobody but a --debug run would ever see. + +import os + +from testutils import cppcheck + +__script_dir = os.path.dirname(os.path.abspath(__file__)) + + +def test_vcxproj_item_macro_path(): + args = [ + '--project=vcxproj_item_macro_path/vcxproj_item_macro_path.vcxproj', + '--no-cppcheck-build-dir', + ] + ret, stdout, stderr = cppcheck(args, cwd=__script_dir) + assert ret == 0, stdout + + normalized_stdout = stdout.replace('\\', '/') + normalized_stderr = stderr.replace('\\', '/') + literal_path = 'vcxproj_item_macro_path/$(SomeDir)/foo.cpp' + + # $(SomeDir) must NOT be expanded -- Visual Studio can't reliably resolve + # it either, since it genuinely differs between configurations -- but the + # item must still be checked (and fail to be found) under its literal, + # unexpanded path, in both configurations, rather than disappearing. + assert ('Checking %s Debug|x64...' % literal_path) in normalized_stdout, stdout + assert ('Checking %s Release|x64...' % literal_path) in normalized_stdout, stdout + + # Same literal (nonexistent) path in both configurations -- cppcheck + # dedups the identical diagnostic, so it's reported exactly once. + assert normalized_stderr.count('%s:0:0: error: File is missing: %s [missingFile]' % (literal_path, literal_path)) == 1, stderr diff --git a/test/cli/vcxproj_metadata_self_reference_case/foo.cpp b/test/cli/vcxproj_metadata_self_reference_case/foo.cpp new file mode 100644 index 00000000000..449a9a7862f --- /dev/null +++ b/test/cli/vcxproj_metadata_self_reference_case/foo.cpp @@ -0,0 +1,21 @@ +// Reached (division-by-zero branch) only if BOTH BASE_DEFINE and +// EXTRA_DEFINE ended up defined -- which requires addMetadata()'s +// case-mismatched "$(preprocessordefinitions)" self-reference (see the +// second ItemDefinitionGroup in the .vcxproj) to have correctly resolved to +// "BASE_DEFINE", accumulated from the first ItemDefinitionGroup. Before the +// fix, that self-reference was left as literal, unresolved macro text +// instead -- so BASE_DEFINE was never actually defined, and this file would +// take the "not defined" branch below instead, hiding the division by zero. +// See vcxproj_metadata_self_reference_case_test.py. +#if defined(BASE_DEFINE) && defined(EXTRA_DEFINE) +int main() +{ + int x = 3 / 0; // ERROR + return x; +} +#else +int main() +{ + return 0; +} +#endif diff --git a/test/cli/vcxproj_metadata_self_reference_case/vcxproj_metadata_self_reference_case.vcxproj b/test/cli/vcxproj_metadata_self_reference_case/vcxproj_metadata_self_reference_case.vcxproj new file mode 100644 index 00000000000..4dc9d515f78 --- /dev/null +++ b/test/cli/vcxproj_metadata_self_reference_case/vcxproj_metadata_self_reference_case.vcxproj @@ -0,0 +1,48 @@ + + + + + Debug + x64 + + + + {cccccccc-dddd-eeee-ffff-000011112222} + metadataselfreferencecasetest + 10.0 + + + + Application + v143 + + + + + + + BASE_DEFINE + + + + + $(preprocessordefinitions);EXTRA_DEFINE + + + + + + + + diff --git a/test/cli/vcxproj_metadata_self_reference_case_test.py b/test/cli/vcxproj_metadata_self_reference_case_test.py new file mode 100644 index 00000000000..5881bb052b6 --- /dev/null +++ b/test/cli/vcxproj_metadata_self_reference_case_test.py @@ -0,0 +1,42 @@ + +# python -m pytest vcxproj_metadata_self_reference_case_test.py +# +# Regression coverage for addMetadata()'s "$(eName) self-reference" +# accumulation idiom (lib/importproject.cpp) using plain, case-sensitive +# findAndReplace() instead of findAndReplaceCaseInsensitive() -- the same +# function already used everywhere else in this file for property/metadata +# name comparisons, and MetadataMap itself is already case-insensitive +# (cppcheck::stricmp). Real MSBuild item metadata names are case-insensitive, +# so a self-reference like $(preprocessordefinitions) inside a +# element must resolve regardless of casing -- see +# vcxproj_metadata_self_reference_case/vcxproj_metadata_self_reference_case.vcxproj, +# which accumulates PreprocessorDefinitions across two ItemDefinitionGroup +# blocks, the second referencing the first back via a differently-cased +# $(preprocessordefinitions). + +import os + +from testutils import cppcheck + +__script_dir = os.path.dirname(os.path.abspath(__file__)) + + +def test_vcxproj_metadata_self_reference_case(): + args = [ + '--project=vcxproj_metadata_self_reference_case/vcxproj_metadata_self_reference_case.vcxproj', + '--no-cppcheck-build-dir', + ] + ret, stdout, stderr = cppcheck(args, cwd=__script_dir) + assert ret == 0, stdout + + normalized_stdout = stdout.replace('\\', '/') + filename = 'vcxproj_metadata_self_reference_case/foo.cpp' + + assert ('Checking %s Debug|x64...' % filename) in normalized_stdout, stdout + + normalized_stderr = stderr.replace('\\', '/') + + # Reached only if BOTH BASE_DEFINE (accumulated from the first + # ItemDefinitionGroup, via the case-mismatched self-reference) and + # EXTRA_DEFINE ended up defined. + assert normalized_stderr.count('%s:13:15: error: Division by zero.' % filename) == 1, stderr diff --git a/test/cli/vcxproj_property_order/main.cpp b/test/cli/vcxproj_property_order/main.cpp new file mode 100644 index 00000000000..905869dfa38 --- /dev/null +++ b/test/cli/vcxproj_property_order/main.cpp @@ -0,0 +1,4 @@ +int main() +{ + return 0; +} diff --git a/test/cli/vcxproj_property_order/vcxproj_property_order.vcxproj b/test/cli/vcxproj_property_order/vcxproj_property_order.vcxproj new file mode 100644 index 00000000000..54d921985c9 --- /dev/null +++ b/test/cli/vcxproj_property_order/vcxproj_property_order.vcxproj @@ -0,0 +1,53 @@ + + + + + Debug + x64 + + + + 17.0 + Win32Proj + {7a1f4e2c-9b3d-4a6e-8c1f-3e5d7a9b2c4f} + vcxprojpropertyorder + 10.0 + + + + Application + true + v143 + Unicode + + + + + + + + + + + + + + Level3 + LATE_DEFINE=$(LateDefinedProp);_CONSOLE;%(PreprocessorDefinitions) + stdcpp17 + + + + + + + + 42 + + + + + diff --git a/test/cli/vcxproj_property_order_test.py b/test/cli/vcxproj_property_order_test.py new file mode 100644 index 00000000000..818ce4b6a86 --- /dev/null +++ b/test/cli/vcxproj_property_order_test.py @@ -0,0 +1,39 @@ + +# python -m pytest vcxproj_property_order_test.py +# +# Regression coverage for MSBuild/Visual Studio property-before-items evaluation +# order: a project fully resolves every property across the whole file before +# evaluating any ItemDefinitionGroup or ItemGroup, regardless of where in the +# document that property happens to be set. This fixture's ItemDefinitionGroup +# references a property that is only assigned by a PropertyGroup positioned AFTER +# it (and after the ItemGroup) -- if cppcheck evaluated the project as one +# document-order pass instead of a real Properties-then-ItemDefs-then-Items +# sequence, that property would still be unset (and therefore unexpanded) at the +# point the ItemDefinitionGroup is evaluated. + +import os + +from testutils import cppcheck + +__script_dir = os.path.dirname(os.path.abspath(__file__)) + + +def test_vcxproj_property_order(): + args = [ + '--project=vcxproj_property_order/vcxproj_property_order.vcxproj', + '--no-cppcheck-build-dir', + '--dump' + ] + ret, stdout, _ = cppcheck(args, cwd=__script_dir) + assert ret == 0, stdout + + dump_file = os.path.join(__script_dir, 'vcxproj_property_order', 'main.cpp.dump') + assert os.path.exists(dump_file), "Dump file not found at %s" % dump_file + + with open(dump_file, 'rt') as f: + dump_content = f.read() + + # LateDefinedProp is set by a PropertyGroup below the ItemDefinitionGroup that + # uses it; the ItemDefinitionGroup must still see its final value (42), not an + # unexpanded '$(LateDefinedProp)' or an empty expansion. + assert 'LATE_DEFINE=42' in dump_content, dump_content diff --git a/test/cli/vcxproj_split_configs/ConfigsDebug.props b/test/cli/vcxproj_split_configs/ConfigsDebug.props new file mode 100644 index 00000000000..aeebadde55d --- /dev/null +++ b/test/cli/vcxproj_split_configs/ConfigsDebug.props @@ -0,0 +1,9 @@ + + + + + Debug + x64 + + + diff --git a/test/cli/vcxproj_split_configs/ConfigsRelease.props b/test/cli/vcxproj_split_configs/ConfigsRelease.props new file mode 100644 index 00000000000..94921237e83 --- /dev/null +++ b/test/cli/vcxproj_split_configs/ConfigsRelease.props @@ -0,0 +1,9 @@ + + + + + Release + x64 + + + diff --git a/test/cli/vcxproj_split_configs/main.cpp b/test/cli/vcxproj_split_configs/main.cpp new file mode 100644 index 00000000000..76e8197013a --- /dev/null +++ b/test/cli/vcxproj_split_configs/main.cpp @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/test/cli/vcxproj_split_configs/vcxproj_split_configs.vcxproj b/test/cli/vcxproj_split_configs/vcxproj_split_configs.vcxproj new file mode 100644 index 00000000000..cf87ec77dc8 --- /dev/null +++ b/test/cli/vcxproj_split_configs/vcxproj_split_configs.vcxproj @@ -0,0 +1,36 @@ + + + + {aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa} + splitconfigtest3 + 10.0 + + + + + + + + + Application + v143 + + + Application + v143 + + + + + + + diff --git a/test/cli/vcxproj_split_configs_test.py b/test/cli/vcxproj_split_configs_test.py new file mode 100644 index 00000000000..8196add7edf --- /dev/null +++ b/test/cli/vcxproj_split_configs_test.py @@ -0,0 +1,49 @@ + +# python -m pytest vcxproj_split_configs_test.py +# +# Regression coverage for the vcxproj configuration-discovery pass (used only +# when a project has no inline and +# must find its configurations by walking imports): it must not stop scanning +# as soon as it finds the first configuration. Real MSBuild/Visual Studio has +# to know the complete configuration set before evaluation with a specific +# Configuration/Platform pair is even possible, so its own preliminary parse +# does not stop early either -- configurations can legitimately be split +# across multiple imports. +# +# This fixture is deliberately ordered so a naive "stop at the first +# configuration found" discovery pass fails it: ConfigsRelease.props is +# unconditional and comes first, so it is found regardless; ConfigsDebug.props +# is reachable only through an ImportGroup gated on Configuration=='Debug', +# positioned AFTER it. A discovery pass that stops once Release|x64 is found +# never reaches that second import at all -- and simply re-running the real +# per-configuration evaluation pass for Release|x64 does not rescue it either, +# since that pass evaluates with Configuration=Release, so the Debug-gated +# ImportGroup's Condition is false during it too. Only a discovery pass that +# keeps scanning (using its own guessed Configuration, seeded before the scan +# starts) reaches and evaluates the second import and finds Debug|x64 as well. + +import os + +from testutils import cppcheck + +__script_dir = os.path.dirname(os.path.abspath(__file__)) + + +def test_vcxproj_split_configs(): + args = [ + '--project=vcxproj_split_configs/vcxproj_split_configs.vcxproj', + '--no-cppcheck-build-dir', + '--dump' + ] + ret, stdout, _ = cppcheck(args, cwd=__script_dir) + assert ret == 0, stdout + + # Windows prints native '\' path separators ("Checking foo\main.cpp ..."); + # normalize before matching so this passes on every platform (same idiom + # used by test_log() in clang-import_test.py). + normalized_stdout = stdout.replace('\\', '/') + + # Both configurations must be discovered and checked, not just whichever + # one the (possibly early-stopping) discovery pass happens to find first. + assert 'Checking vcxproj_split_configs/main.cpp Release|x64' in normalized_stdout, stdout + assert 'Checking vcxproj_split_configs/main.cpp Debug|x64' in normalized_stdout, stdout diff --git a/test/cli/vcxproj_unicode_test.py b/test/cli/vcxproj_unicode_test.py new file mode 100644 index 00000000000..b59a4c4ad4e --- /dev/null +++ b/test/cli/vcxproj_unicode_test.py @@ -0,0 +1,63 @@ + +# python -m pytest vcxproj_unicode_test.py + +from testutils import cppcheck + +import os +import shutil + +__script_dir = os.path.dirname(os.path.abspath(__file__)) +__proj_dir = os.path.join(__script_dir, 'vcxproj-unicode') + +def _get_dump_for_configuration(tmp_path, configuration): + proj_dir = tmp_path / 'vcxproj-unicode' + shutil.copytree(__proj_dir, proj_dir) + + args = [ + '--template=cppcheck1', + '--project=vcxproj-unicode/vcxproj_unicode.vcxproj', + f'--project-configuration={configuration}', + '--no-cppcheck-build-dir', + '--dump' + ] + ret, stdout, stderr = cppcheck(args, cwd=str(tmp_path)) + assert ret == 0, stdout + assert stderr == '', stderr + + dump_path = proj_dir / 'main.cpp.dump' + assert dump_path.exists(), f"Dump file not found at {dump_path}" + + with open(dump_path, 'rt') as f: + return f.read() + +def test_vcxproj_unicode_debug(tmp_path): + dump_content = _get_dump_for_configuration(tmp_path, 'Debug|Win32') + + # v143 toolset -> _MSC_VER=1930, _MSC_FULL_VER=193000000 + assert '_WIN32=1' in dump_content + assert '_M_IX86=600' in dump_content + assert '_MSC_VER=1930' in dump_content + assert '_MSC_FULL_VER=193000000' in dump_content + assert '_MSVC_LANG=201402L' in dump_content # default C++14, no LanguageStandard set + assert 'UNICODE=1' in dump_content + assert '_UNICODE=1' in dump_content + assert '_MBCS' not in dump_content + +def test_vcxproj_unicode_release(tmp_path): + dump_content = _get_dump_for_configuration(tmp_path, 'Release|Win32') + + # v143 toolset, CharacterSet=NotSet, UseOfMfc=Static + assert '_WIN32=1' in dump_content + assert '_M_IX86=600' in dump_content + assert '_MSC_VER=1930' in dump_content + assert '_MSC_FULL_VER=193000000' in dump_content + assert '_MSVC_LANG=201402L' in dump_content + assert 'UNICODE' not in dump_content + assert '_MBCS' not in dump_content + +def test_vcxproj_multibyte(tmp_path): + dump_content = _get_dump_for_configuration(tmp_path, 'MultiByte|Win32') + + # MultiByte projects must define _MBCS and must NOT define UNICODE or _UNICODE (M-1) + assert '_MBCS=1' in dump_content + assert 'UNICODE' not in dump_content diff --git a/test/fixture.cpp b/test/fixture.cpp index cdc826d083a..a561b4fc28f 100644 --- a/test/fixture.cpp +++ b/test/fixture.cpp @@ -116,7 +116,7 @@ bool TestFixture::prepareTest(const char testname[]) std::cout << fullTestName << std::endl; } if (timer_results) - mTimer.reset(new Timer(fullTestName, ShowTime::TOP5_SUMMARY, timer_results)); + mTimer.reset(new Timer(fullTestName, timer_results)); return !dry_run; } @@ -423,11 +423,7 @@ std::size_t TestFixture::runTests(const options& args) const auto f = [&](){ fixture = test->create(); }; - // TODO: Timer::run() needs proper handling if no results should be collected - if (args.timer_results()) - Timer::run(test->classname + " - create", ShowTime::TOP5_SUMMARY, args.timer_results(), f); - else - f(); + Timer::run(test->classname + " - create", args.timer_results(), f); fixture->processOptions(args); fixture->run(tests); } @@ -513,7 +509,7 @@ TestFixture::SettingsBuilder& TestFixture::SettingsBuilder::library(const char l if (REDUNDANT_CHECK && std::find(settings.libraries.cbegin(), settings.libraries.cend(), lib) != settings.libraries.cend()) throw std::runtime_error("redundant setting: libraries (" + std::string(lib) + ")"); // TODO: exename is not yet set - const Library::ErrorCode lib_error = settings.library.load(fixture.exename.c_str(), lib).errorcode; + const Library::ErrorCode lib_error = settings.library.load(fixture().exename.c_str(), lib).errorcode; if (lib_error != Library::ErrorCode::OK) throw std::runtime_error("loading library '" + std::string(lib) + "' failed - " + std::to_string(static_cast(lib_error))); // strip extension @@ -535,7 +531,7 @@ TestFixture::SettingsBuilder& TestFixture::SettingsBuilder::platform(Platform::T std::string errstr; // TODO: exename is not yet set - if (!settings.platform.set(platformStr, errstr, {fixture.exename})) + if (!settings.platform.set(platformStr, errstr, {fixture().exename})) throw std::runtime_error("platform '" + platformStr + "' not found"); return *this; } diff --git a/test/fixture.h b/test/fixture.h index a564a54e399..3d1e96562ba 100644 --- a/test/fixture.h +++ b/test/fixture.h @@ -25,6 +25,7 @@ #include "config.h" #include "errorlogger.h" #include "errortypes.h" +#include "refthunk.h" #include "platform.h" #include "settings.h" #include "standards.h" @@ -32,7 +33,6 @@ #include #include #include -#include #include #include #include @@ -135,21 +135,13 @@ class TestFixture : public ErrorLogger { void processOptions(const options& args); - template - static T& getCheck() + static Check& getCheck(Check& check) { - for (Check *check : Check::instances()) { - //cppcheck-suppress useStlAlgorithm - if (T* c = dynamic_cast(check)) - return *c; - } - throw std::runtime_error("instance not found"); + return check; } - template - static void runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) + static void runChecks(Check& check, const Tokenizer &tokenizer, ErrorLogger& errorLogger) { - Check& check = getCheck(); check.runChecks(tokenizer, errorLogger); } @@ -248,7 +240,7 @@ class TestFixture : public ErrorLogger { private: SettingsBuilder& libraryxml(const char xmldata[], std::size_t len); - const TestFixture &fixture; + RefThunk fixture; Settings settings; static constexpr bool REDUNDANT_CHECK = false; @@ -291,7 +283,7 @@ class TestFixture : public ErrorLogger { std::unique_ptr mTimer; - void reportOut(const std::string &outmsg, Color c = Color::Reset) override; + void reportOut(const std::string &outmsg, Color /*c*/ = Color::Reset) override; void reportErr(const ErrorMessage &msg) override; void reportMetric(const std::string &metric) override { diff --git a/test/helpers.cpp b/test/helpers.cpp index 252658cc531..06ad0c4c298 100644 --- a/test/helpers.cpp +++ b/test/helpers.cpp @@ -113,18 +113,23 @@ ScopedFile::~ScopedFile() { void SimpleTokenizer2::preprocess(const char* code, std::size_t size, std::vector &files, const std::string& file0, Tokenizer& tokenizer, ErrorLogger& errorlogger) { + const Standards &standards = tokenizer.getSettings().standards; + const Standards::Language language = tokenizer.isCPP() ? Standards::CPP : Standards::C; simplecpp::OutputList outputList; - simplecpp::TokenList tokens1({code, size}, files, file0, &outputList); + simplecpp::DUI dui; + dui.std = standards.getStdForLanguage(language); + simplecpp::TokenList tokens1({code, size}, files, file0, dui, &outputList); Preprocessor preprocessor(tokens1, tokenizer.getSettings(), errorlogger, Path::identify(tokens1.getFiles()[0], false)); - (void)preprocessor.loadFiles(files); // TODO: check result + (void)preprocessor.loadAllIncludes(files); // TODO: check result simplecpp::TokenList tokens2 = preprocessor.preprocess("", files, outputList); (void)preprocessor.reportOutput(outputList, true); // Tokenizer.. tokenizer.list.createTokens(std::move(tokens2)); - std::list directives = preprocessor.createDirectives(); + std::list directives; + preprocessor.createDirectives(directives); tokenizer.setDirectives(std::move(directives)); } diff --git a/test/helpers.h b/test/helpers.h index 5a0bd106812..cd4cde7af1c 100644 --- a/test/helpers.h +++ b/test/helpers.h @@ -1,6 +1,6 @@ /* -*- C++ -*- * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -27,6 +27,7 @@ #include "tokenlist.h" #include +#include #include #include #include @@ -135,7 +136,7 @@ class SimpleTokenList } private: - const Settings settings; + Settings settings; TokenList list; }; diff --git a/test/options.cpp b/test/options.cpp index bdb4f553ac8..757b6cdff86 100644 --- a/test/options.cpp +++ b/test/options.cpp @@ -1,5 +1,5 @@ // Cppcheck - A tool for static C/C++ code analysis -// Copyright (C) 2007-2025 Cppcheck team. +// Copyright (C) 2007-2026 Cppcheck team. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -58,11 +58,8 @@ options::options(int argc, const char* const argv[]) options::~options() { - // TODO: allow more than 5 results to be shown - // TODO: provide higher resolution in output - // TODO: disable the metrics if (mTimerResults) - mTimerResults->showResults(ShowTime::TOP5_FILE); + mTimerResults->showResults(10, false); } bool options::quiet() const diff --git a/test/options.h b/test/options.h index b327721351c..0bd83cef78b 100644 --- a/test/options.h +++ b/test/options.h @@ -1,5 +1,5 @@ // Cppcheck - A tool for static C/C++ code analysis -// Copyright (C) 2007-2024 Cppcheck team. +// Copyright (C) 2007-2026 Cppcheck team. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by diff --git a/test/scripts/extracttests.py b/test/scripts/extracttests.py index a45c8261d64..776211152d4 100755 --- a/test/scripts/extracttests.py +++ b/test/scripts/extracttests.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # # Cppcheck - A tool for static C/C++ code analysis -# Copyright (C) 2007-2021 Cppcheck team. +# Copyright (C) 2007-2025 Cppcheck team. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/test/seh/test-sehwrapper.cpp b/test/seh/test-sehwrapper.cpp index a92753b8423..2c17d62334a 100644 --- a/test/seh/test-sehwrapper.cpp +++ b/test/seh/test-sehwrapper.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2024 Cppcheck team. + * Copyright (C) 2007-2025 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/test/signal/test-signalhandler.cpp b/test/signal/test-signalhandler.cpp index 153ec95f245..faf815b6577 100644 --- a/test/signal/test-signalhandler.cpp +++ b/test/signal/test-signalhandler.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2024 Cppcheck team. + * Copyright (C) 2007-2025 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/test/test64bit.cpp b/test/test64bit.cpp index 6f9a5fbda2f..1507f15ec72 100644 --- a/test/test64bit.cpp +++ b/test/test64bit.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -49,8 +49,7 @@ class Test64BitPortability : public TestFixture { SimpleTokenizer tokenizer(settings, *this, cpp); ASSERT_LOC(tokenizer.tokenize(code), file, line); - // Check char variable usage.. - Check64BitPortability check64BitPortability(&tokenizer, &settings, this); + Check64BitPortabilityImpl check64BitPortability(&tokenizer, settings, *this); check64BitPortability.pointerassignment(); } @@ -59,7 +58,7 @@ class Test64BitPortability : public TestFixture { check("using CharArray = char[16];\n" "void f() {\n" " CharArray foo = \"\";\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct T { std::vector*a[2][2]; };\n" // #11560 @@ -80,7 +79,7 @@ class Test64BitPortability : public TestFixture { check("void foo()\n" "{\n" " a = p;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -89,65 +88,65 @@ class Test64BitPortability : public TestFixture { "{\n" " int a = p;\n" " return a + 4;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:11]: (portability) Assigning a pointer to an integer is not portable. [AssignmentAddressToInteger]\n", errout_str()); check("int foo(int p[])\n" "{\n" " int a = p;\n" " return a + 4;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:11]: (portability) Assigning a pointer to an integer is not portable. [AssignmentAddressToInteger]\n", errout_str()); check("int foo(int p[])\n" "{\n" " int *a = p;\n" " return a;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:5]: (portability) Returning an address value in a function with integer return type is not portable. [CastAddressToIntegerAtReturn]\n", errout_str()); check("void foo(int x)\n" "{\n" " int *p = x;\n" " *p = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:12]: (portability) Assigning an integer to a pointer is not portable. [AssignmentIntegerToAddress]\n", errout_str()); check("int f(const char *p) {\n" // #4659 " return 6 + p[2] * 256;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int foo(int *p) {\n" // #6096 " bool a = p;\n" " return a;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("std::array f();\n" "void g() {\n" " std::array a = f();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("std::array f(int x);\n" "void g(int i) {\n" " std::array a = f(i);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("typedef std::array Array;\n" "Array f(int x);\n" "void g(int i) {\n" " Array a = f(i);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("typedef std::array Array;\n" "Array f();\n" "void g(int i) {\n" " Array a = f();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct S {\n" // #9951 @@ -175,7 +174,7 @@ class Test64BitPortability : public TestFixture { check("struct Foo { int *p; };\n" "void f(struct Foo *foo) {\n" " int i = foo->p;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:11]: (portability) Assigning a pointer to an integer is not portable. [AssignmentAddressToInteger]\n", errout_str()); check("struct S {\n" // #10145 @@ -193,7 +192,7 @@ class Test64BitPortability : public TestFixture { // Ticket #2892 check("void foo(int *p) {\n" " int a = (p != NULL);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -202,74 +201,74 @@ class Test64BitPortability : public TestFixture { check("void foo(int *p) {\n" " int x = 10;\n" " int *a = p + x;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int *p) {\n" " int x = 10;\n" " int *a = x + p;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int *p) {\n" " int x = 10;\n" " int *a = x * x;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:12]: (portability) Assigning an integer to a pointer is not portable. [AssignmentIntegerToAddress]\n", errout_str()); check("void foo(int *start, int *end) {\n" " int len;\n" " int len = end + 10 - start;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } void returnIssues() { check("void* foo(int i) {\n" " return i;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:5]: (portability) Returning an integer in a function with pointer return type is not portable. [CastIntegerToAddressAtReturn]\n", errout_str()); check("void* foo(int* i) {\n" " return i;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void* foo() {\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int foo(int i) {\n" " return i;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct Foo {};\n" "\n" "int* dostuff(Foo foo) {\n" " return foo;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int foo(char* c) {\n" " return c;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:5]: (portability) Returning an address value in a function with integer return type is not portable. [CastAddressToIntegerAtReturn]\n", errout_str()); check("int foo(char* c) {\n" " return 1+c;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:5]: (portability) Returning an address value in a function with integer return type is not portable. [CastAddressToIntegerAtReturn]\n", errout_str()); check("std::string foo(char* c) {\n" " return c;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int foo(char *a, char *b) {\n" // #4486 " return a + 1 - b;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct s {\n" // 4642 @@ -277,13 +276,13 @@ class Test64BitPortability : public TestFixture { "};\n" "int func(struct s *p) {\n" " return 1 + p->i;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("static void __iomem *f(unsigned int port_no) {\n" " void __iomem *mmio = hpriv->mmio;\n" " return mmio + (port_no * 0x80);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #7247: don't check return statements in nested functions.. @@ -291,7 +290,7 @@ class Test64BitPortability : public TestFixture { " struct {\n" " const char * name() { return \"abc\"; }\n" " } table;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #7451: Lambdas @@ -299,7 +298,7 @@ class Test64BitPortability : public TestFixture { " auto it = std::find_if(outputs.begin(), outputs.end(),\n" " [&](int ele) { return \"test\" == text; });\n" " return nullptr;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct S {\n" // #12159 diff --git a/test/testanalyzerinformation.cpp b/test/testanalyzerinformation.cpp index 0936dbbf3f2..0356630686c 100644 --- a/test/testanalyzerinformation.cpp +++ b/test/testanalyzerinformation.cpp @@ -53,6 +53,41 @@ class TestAnalyzerInformation : public TestFixture { ASSERT_EQUALS("file1.a4", AnalyzerInformationTest::getAnalyzerInfoFileFromFilesTxt(f, "file1.c", "", 0)); f.str(filesTxt); ASSERT_EQUALS("file1.a4", AnalyzerInformationTest::getAnalyzerInfoFileFromFilesTxt(f, "./file1.c", "", 0)); + + // only an exact match is used - "executor.cpp" is a suffix of "processexecutor.cpp" + // so both used to resolve to the same analyzer info file + { + constexpr char suffixTxt[] = "executor.a1:::executor.cpp\n" + "processexecutor.a2:::processexecutor.cpp\n"; + std::istringstream f1(suffixTxt); + ASSERT_EQUALS("executor.a1", AnalyzerInformationTest::getAnalyzerInfoFileFromFilesTxt(f1, "executor.cpp", "", 0)); + std::istringstream f2(suffixTxt); + ASSERT_EQUALS("processexecutor.a2", AnalyzerInformationTest::getAnalyzerInfoFileFromFilesTxt(f2, "processexecutor.cpp", "", 0)); + } + + // the same, with the exact match on the first line + { + constexpr char suffixTxt[] = "processexecutor.a1:::processexecutor.cpp\n" + "executor.a2:::executor.cpp\n"; + std::istringstream f1(suffixTxt); + ASSERT_EQUALS("processexecutor.a1", AnalyzerInformationTest::getAnalyzerInfoFileFromFilesTxt(f1, "processexecutor.cpp", "", 0)); + std::istringstream f2(suffixTxt); + ASSERT_EQUALS("executor.a2", AnalyzerInformationTest::getAnalyzerInfoFileFromFilesTxt(f2, "executor.cpp", "", 0)); + } + + // a path that only ends with the path in files.txt is not a match + { + constexpr char relTxt[] = "a.a1:::src/a.c\n"; + std::istringstream f1(relTxt); + ASSERT_EQUALS("", AnalyzerInformationTest::getAnalyzerInfoFileFromFilesTxt(f1, "/home/proj/src/a.c", "", 0)); + } + + // no match at all + { + constexpr char noneTxt[] = "a.a1:::a.c\n"; + std::istringstream f1(noneTxt); + ASSERT_EQUALS("", AnalyzerInformationTest::getAnalyzerInfoFileFromFilesTxt(f1, "b.c", "", 0)); + } } void getAnalyzerInfoFile() const { @@ -118,7 +153,8 @@ class TestAnalyzerInformation : public TestFixture { const tinyxml2::XMLError xmlError = doc.Parse( "" - "" + "" + "100" "" "" "" @@ -137,7 +173,8 @@ class TestAnalyzerInformation : public TestFixture { const tinyxml2::XMLError xmlError = doc.Parse( "" - "" + "" + "100" "" "" "" @@ -156,7 +193,8 @@ class TestAnalyzerInformation : public TestFixture { const tinyxml2::XMLError xmlError = doc.Parse( "" - "" + "" + "100" "" "" "" @@ -175,7 +213,8 @@ class TestAnalyzerInformation : public TestFixture { const tinyxml2::XMLError xmlError = doc.Parse( "" - "" + "" + "100" "" "" "" @@ -196,7 +235,8 @@ class TestAnalyzerInformation : public TestFixture { const tinyxml2::XMLError xmlError = doc.Parse( "" - "" + "" + "100" "" ); ASSERT_EQUALS(tinyxml2::XML_SUCCESS, xmlError); @@ -212,7 +252,8 @@ class TestAnalyzerInformation : public TestFixture { const tinyxml2::XMLError xmlError = doc.Parse( "" - "" + "" + "100" "" "" "" @@ -253,7 +294,7 @@ class TestAnalyzerInformation : public TestFixture { ASSERT_EQUALS(0, errorList.size()); } - // No 'hash' attribute found + // No 'hash' node found { std::list errorList; tinyxml2::XMLDocument doc; @@ -264,7 +305,7 @@ class TestAnalyzerInformation : public TestFixture { ); ASSERT_EQUALS(tinyxml2::XML_SUCCESS, xmlError); - ASSERT_EQUALS("no 'hash' attribute found", AnalyzerInformationTest::skipAnalysis(doc, 99, errorList)); + ASSERT_EQUALS("no 'hash' node found", AnalyzerInformationTest::skipAnalysis(doc, 99, errorList)); ASSERT_EQUALS(0, errorList.size()); } } diff --git a/test/testassert.cpp b/test/testassert.cpp index 412a7cb750b..c3c271a1bf2 100644 --- a/test/testassert.cpp +++ b/test/testassert.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -39,8 +39,8 @@ class TestAssert : public TestFixture { SimpleTokenizer tokenizer(settings, *this); ASSERT_LOC(tokenizer.tokenize(code), file, line); - // Check.. - runChecks(tokenizer, this); + CheckAssert check; + runChecks(check, tokenizer, *this); } void run() override { @@ -61,7 +61,7 @@ class TestAssert : public TestFixture { " if (b) { a = 1+2 };\n" " return a;\n" "}\n" - "assert(foo() == 3);"); + "assert(foo() == 3);\n"); ASSERT_EQUALS("", errout_str()); check( @@ -69,7 +69,7 @@ class TestAssert : public TestFixture { " int b=a+1;\n" " return b;\n" "}\n" - "assert(foo(1) == 2);"); + "assert(foo(1) == 2);\n"); ASSERT_EQUALS("", errout_str()); } @@ -80,7 +80,7 @@ class TestAssert : public TestFixture { " a = 1+2;\n" " return a;\n" "}\n" - "assert(foo() == 3);"); + "assert(foo() == 3);\n"); ASSERT_EQUALS("[test.cpp:6:8]: (warning) Assert statement calls a function which may have desired side effects: 'foo'. [assertWithSideEffect]\n", errout_str()); // Ticket #4937 "false positive: Assert calls a function which may have desired side effects" @@ -92,7 +92,7 @@ class TestAssert : public TestFixture { "};\n" "void foo() {\n" " assert( !SquarePack::isRank1Or8(push2) );\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct SquarePack {\n" @@ -103,7 +103,7 @@ class TestAssert : public TestFixture { "};\n" "void foo() {\n" " assert( !SquarePack::isRank1Or8(push2) );\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:25]: (warning) Assert statement calls a function which may have desired side effects: 'isRank1Or8'. [assertWithSideEffect]\n", errout_str()); check("struct SquarePack {\n" @@ -114,7 +114,7 @@ class TestAssert : public TestFixture { "};\n" "void foo() {\n" " assert( !SquarePack::isRank1Or8(push2) );\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:25]: (warning) Assert statement calls a function which may have desired side effects: 'isRank1Or8'. [assertWithSideEffect]\n", errout_str()); check("struct SquarePack {\n" @@ -125,7 +125,7 @@ class TestAssert : public TestFixture { "};\n" "void foo() {\n" " assert( !SquarePack::isRank1Or8(push2) );\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct Geometry {\n" @@ -136,7 +136,7 @@ class TestAssert : public TestFixture { "\n" "void Geometry::ReadGeometry() {\n" " assert(empty());\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct S {\n" // #4811 @@ -147,6 +147,17 @@ class TestAssert : public TestFixture { " assert(g());\n" "}\n"); ASSERT_EQUALS("", errout_str()); + + check("int i;\n" // #14973 + "bool f() {\n" + " i = 0;\n" + " return true;\n" + "}\n" + "void g() {\n" + " bool (*fp)() = f;\n" + " assert(fp == f);\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); } void memberFunctionCallInAssert() { @@ -155,15 +166,15 @@ class TestAssert : public TestFixture { "};\n" "void foo(SquarePack s) {\n" " assert( s.Foo() );\n" - "}"); - ASSERT_EQUALS("[test.cpp:5:14]: (warning) Assert statement calls a function which may have desired side effects: 'Foo'. [assertWithSideEffect]\n", errout_str()); + "}\n"); + ASSERT_EQUALS("[test.cpp:5:14]: (warning) Assert statement calls a function which may have desired side effects: 'Foo'. If there are no side effects, consider declaring the method const. [assertWithSideEffect]\n", errout_str()); check("struct SquarePack {\n" " int Foo() const;\n" "};\n" "void foo(SquarePack* s) {\n" " assert( s->Foo() );\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct SquarePack {\n" @@ -171,14 +182,14 @@ class TestAssert : public TestFixture { "};\n" "void foo(SquarePack* s) {\n" " assert( s->Foo() );\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct SquarePack {\n" "};\n" "void foo(SquarePack* s) {\n" " assert( s->Foo() );\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -187,54 +198,54 @@ class TestAssert : public TestFixture { " int a; a = 0;\n" " assert(a = 2);\n" " return a;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:14]: (warning) Assert statement modifies 'a'. [assignmentInAssert]\n", errout_str()); check("void f(int a) {\n" " assert(a == 2);\n" " return a;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int a, int b) {\n" " assert(a == 2 && (b = 1));\n" " return a;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:25]: (warning) Assert statement modifies 'b'. [assignmentInAssert]\n", errout_str()); check("void f() {\n" " int a; a = 0;\n" " assert(a += 2);\n" " return a;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:14]: (warning) Assert statement modifies 'a'. [assignmentInAssert]\n", errout_str()); check("void f() {\n" " int a; a = 0;\n" " assert(a *= 2);\n" " return a;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:14]: (warning) Assert statement modifies 'a'. [assignmentInAssert]\n", errout_str()); check("void f() {\n" " int a; a = 0;\n" " assert(a -= 2);\n" " return a;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:14]: (warning) Assert statement modifies 'a'. [assignmentInAssert]\n", errout_str()); check("void f() {\n" " int a = 0;\n" " assert(a--);\n" " return a;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:13]: (warning) Assert statement modifies 'a'. [assignmentInAssert]\n", errout_str()); check("void f() {\n" " int a = 0;\n" " assert(--a);\n" " return a;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:12]: (warning) Assert statement modifies 'a'. [assignmentInAssert]\n", errout_str()); check("void f() {\n" @@ -243,19 +254,19 @@ class TestAssert : public TestFixture { " auto const expected = someOtherValue;\n" " return tmp == expected;\n" " }));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } void crash() { check("void foo() {\n" " assert(sizeof(struct { int a[x++]; })==sizeof(int));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" // #9790 " assert(kad_bucket_hash(&(kad_guid) { .bytes = { 0 } }, & (kad_guid){.bytes = { 0 }}) == -1);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } }; diff --git a/test/testastutils.cpp b/test/testastutils.cpp index eb9559ed39d..46dde522338 100644 --- a/test/testastutils.cpp +++ b/test/testastutils.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -62,29 +62,29 @@ class TestAstUtils : public TestFixture { void findLambdaEndTokenTest() { const Token* nullTok = nullptr; ASSERT(nullptr == (::findLambdaEndToken)(nullTok)); - ASSERT_EQUALS(false, findLambdaEndToken("void f() { }")); - ASSERT_EQUALS(true, findLambdaEndToken("[]{ }")); - ASSERT_EQUALS(true, findLambdaEndToken("[]{ return 0; }")); - ASSERT_EQUALS(true, findLambdaEndToken("[](){ }")); - ASSERT_EQUALS(true, findLambdaEndToken("[&](){ }")); - ASSERT_EQUALS(true, findLambdaEndToken("[&, i](){ }")); - ASSERT_EQUALS(true, findLambdaEndToken("[](void) { return -1; }")); - ASSERT_EQUALS(true, findLambdaEndToken("[](int a, int b) { return a + b; }")); - ASSERT_EQUALS(true, findLambdaEndToken("[](int a, int b) mutable { return a + b; }")); - ASSERT_EQUALS(true, findLambdaEndToken("[](int a, int b) constexpr { return a + b; }")); - ASSERT_EQUALS(true, findLambdaEndToken("[](void) -> int { return -1; }")); - ASSERT_EQUALS(true, findLambdaEndToken("[](void) mutable -> int { return -1; }")); - ASSERT_EQUALS(false, findLambdaEndToken("[](void) foo -> int { return -1; }")); - ASSERT_EQUALS(true, findLambdaEndToken("[](void) constexpr -> int { return -1; }")); - ASSERT_EQUALS(true, findLambdaEndToken("[](void) constexpr -> int* { return x; }")); - ASSERT_EQUALS(true, findLambdaEndToken("[](void) constexpr -> const * int { return x; }")); - ASSERT_EQUALS(true, findLambdaEndToken("[](void) mutable -> const * int { return x; }")); - ASSERT_EQUALS(true, findLambdaEndToken("[](void) constexpr -> const ** int { return x; }")); - ASSERT_EQUALS(true, findLambdaEndToken("[](void) constexpr -> const * const* int { return x; }")); - ASSERT_EQUALS(false, findLambdaEndToken("int** a[] { new int*[2] { new int, new int} };", "[ ]")); - ASSERT_EQUALS(false, findLambdaEndToken("int** a[] { new int*[2] { new int, new int} };", "[ 2")); - ASSERT_EQUALS(false, findLambdaEndToken("shared_ptr sp{ new Type *[2] {new Type, new Type}, Deleter{ 2 } };", "[ 2")); - ASSERT_EQUALS(true, findLambdaEndToken("int i = 5 * []{ return 7; }();", "[", /*checkNext*/ false)); + ASSERT_EQUALS(false, findLambdaEndToken("void f() { }\n")); + ASSERT_EQUALS(true, findLambdaEndToken("[]{ }\n")); + ASSERT_EQUALS(true, findLambdaEndToken("[]{ return 0; }\n")); + ASSERT_EQUALS(true, findLambdaEndToken("[](){ }\n")); + ASSERT_EQUALS(true, findLambdaEndToken("[&](){ }\n")); + ASSERT_EQUALS(true, findLambdaEndToken("[&, i](){ }\n")); + ASSERT_EQUALS(true, findLambdaEndToken("[](void) { return -1; }\n")); + ASSERT_EQUALS(true, findLambdaEndToken("[](int a, int b) { return a + b; }\n")); + ASSERT_EQUALS(true, findLambdaEndToken("[](int a, int b) mutable { return a + b; }\n")); + ASSERT_EQUALS(true, findLambdaEndToken("[](int a, int b) constexpr { return a + b; }\n")); + ASSERT_EQUALS(true, findLambdaEndToken("[](void) -> int { return -1; }\n")); + ASSERT_EQUALS(true, findLambdaEndToken("[](void) mutable -> int { return -1; }\n")); + ASSERT_EQUALS(false, findLambdaEndToken("[](void) foo -> int { return -1; }\n")); + ASSERT_EQUALS(true, findLambdaEndToken("[](void) constexpr -> int { return -1; }\n")); + ASSERT_EQUALS(true, findLambdaEndToken("[](void) constexpr -> int* { return x; }\n")); + ASSERT_EQUALS(true, findLambdaEndToken("[](void) constexpr -> const * int { return x; }\n")); + ASSERT_EQUALS(true, findLambdaEndToken("[](void) mutable -> const * int { return x; }\n")); + ASSERT_EQUALS(true, findLambdaEndToken("[](void) constexpr -> const ** int { return x; }\n")); + ASSERT_EQUALS(true, findLambdaEndToken("[](void) constexpr -> const * const* int { return x; }\n")); + ASSERT_EQUALS(false, findLambdaEndToken("int** a[] { new int*[2] { new int, new int} };\n", "[ ]")); + ASSERT_EQUALS(false, findLambdaEndToken("int** a[] { new int*[2] { new int, new int} };\n", "[ 2")); + ASSERT_EQUALS(false, findLambdaEndToken("shared_ptr sp{ new Type *[2] {new Type, new Type}, Deleter{ 2 } };\n", "[ 2")); + ASSERT_EQUALS(true, findLambdaEndToken("int i = 5 * []{ return 7; }();\n", "[", /*checkNext*/ false)); } #define findLambdaStartToken(...) findLambdaStartToken_(__FILE__, __LINE__, __VA_ARGS__) @@ -98,25 +98,25 @@ class TestAstUtils : public TestFixture { void findLambdaStartTokenTest() { ASSERT(nullptr == (::findLambdaStartToken)(nullptr)); - ASSERT_EQUALS(false, findLambdaStartToken("void f() { }")); - ASSERT_EQUALS(true, findLambdaStartToken("[]{ }")); - ASSERT_EQUALS(true, findLambdaStartToken("[]{ return 0; }")); - ASSERT_EQUALS(true, findLambdaStartToken("[](){ }")); - ASSERT_EQUALS(true, findLambdaStartToken("[&](){ }")); - ASSERT_EQUALS(true, findLambdaStartToken("[&, i](){ }")); - ASSERT_EQUALS(true, findLambdaStartToken("[](void) { return -1; }")); - ASSERT_EQUALS(true, findLambdaStartToken("[](int a, int b) { return a + b; }")); - ASSERT_EQUALS(true, findLambdaStartToken("[](int a, int b) mutable { return a + b; }")); - ASSERT_EQUALS(true, findLambdaStartToken("[](int a, int b) constexpr { return a + b; }")); - ASSERT_EQUALS(true, findLambdaStartToken("[](void) -> int { return -1; }")); - ASSERT_EQUALS(true, findLambdaStartToken("[](void) mutable -> int { return -1; }")); - ASSERT_EQUALS(false, findLambdaStartToken("[](void) foo -> int { return -1; }")); - ASSERT_EQUALS(true, findLambdaStartToken("[](void) constexpr -> int { return -1; }")); - ASSERT_EQUALS(true, findLambdaStartToken("[](void) constexpr -> int* { return x; }")); - ASSERT_EQUALS(true, findLambdaStartToken("[](void) constexpr -> const * int { return x; }")); - ASSERT_EQUALS(true, findLambdaStartToken("[](void) mutable -> const * int { return x; }")); - ASSERT_EQUALS(true, findLambdaStartToken("[](void) constexpr -> const ** int { return x; }")); - ASSERT_EQUALS(true, findLambdaStartToken("[](void) constexpr -> const * const* int { return x; }")); + ASSERT_EQUALS(false, findLambdaStartToken("void f() { }\n")); + ASSERT_EQUALS(true, findLambdaStartToken("[]{ }\n")); + ASSERT_EQUALS(true, findLambdaStartToken("[]{ return 0; }\n")); + ASSERT_EQUALS(true, findLambdaStartToken("[](){ }\n")); + ASSERT_EQUALS(true, findLambdaStartToken("[&](){ }\n")); + ASSERT_EQUALS(true, findLambdaStartToken("[&, i](){ }\n")); + ASSERT_EQUALS(true, findLambdaStartToken("[](void) { return -1; }\n")); + ASSERT_EQUALS(true, findLambdaStartToken("[](int a, int b) { return a + b; }\n")); + ASSERT_EQUALS(true, findLambdaStartToken("[](int a, int b) mutable { return a + b; }\n")); + ASSERT_EQUALS(true, findLambdaStartToken("[](int a, int b) constexpr { return a + b; }\n")); + ASSERT_EQUALS(true, findLambdaStartToken("[](void) -> int { return -1; }\n")); + ASSERT_EQUALS(true, findLambdaStartToken("[](void) mutable -> int { return -1; }\n")); + ASSERT_EQUALS(false, findLambdaStartToken("[](void) foo -> int { return -1; }\n")); + ASSERT_EQUALS(true, findLambdaStartToken("[](void) constexpr -> int { return -1; }\n")); + ASSERT_EQUALS(true, findLambdaStartToken("[](void) constexpr -> int* { return x; }\n")); + ASSERT_EQUALS(true, findLambdaStartToken("[](void) constexpr -> const * int { return x; }\n")); + ASSERT_EQUALS(true, findLambdaStartToken("[](void) mutable -> const * int { return x; }\n")); + ASSERT_EQUALS(true, findLambdaStartToken("[](void) constexpr -> const ** int { return x; }\n")); + ASSERT_EQUALS(true, findLambdaStartToken("[](void) constexpr -> const * const* int { return x; }\n")); } #define isNullOperand(...) isNullOperand_(__FILE__, __LINE__, __VA_ARGS__) @@ -128,16 +128,16 @@ class TestAstUtils : public TestFixture { } void isNullOperandTest() { - ASSERT_EQUALS(true, isNullOperand("(void*)0;")); - ASSERT_EQUALS(true, isNullOperand("(void*)0U;")); - ASSERT_EQUALS(true, isNullOperand("(void*)0x0LL;")); - ASSERT_EQUALS(true, isNullOperand("NULL;")); - ASSERT_EQUALS(true, isNullOperand("nullptr;")); - ASSERT_EQUALS(true, isNullOperand("(void*)NULL;")); - ASSERT_EQUALS(true, isNullOperand("static_cast(0);")); - ASSERT_EQUALS(false, isNullOperand("0;")); - ASSERT_EQUALS(false, isNullOperand("(void*)0.0;")); - ASSERT_EQUALS(false, isNullOperand("(void*)1;")); + ASSERT_EQUALS(true, isNullOperand("(void*)0;\n")); + ASSERT_EQUALS(true, isNullOperand("(void*)0U;\n")); + ASSERT_EQUALS(true, isNullOperand("(void*)0x0LL;\n")); + ASSERT_EQUALS(true, isNullOperand("NULL;\n")); + ASSERT_EQUALS(true, isNullOperand("nullptr;\n")); + ASSERT_EQUALS(true, isNullOperand("(void*)NULL;\n")); + ASSERT_EQUALS(true, isNullOperand("static_cast(0);\n")); + ASSERT_EQUALS(false, isNullOperand("0;\n")); + ASSERT_EQUALS(false, isNullOperand("(void*)0.0;\n")); + ASSERT_EQUALS(false, isNullOperand("(void*)1;\n")); } #define isReturnScope(...) isReturnScope_(__FILE__, __LINE__, __VA_ARGS__) @@ -152,22 +152,22 @@ class TestAstUtils : public TestFixture { } void isReturnScopeTest() { - ASSERT_EQUALS(true, isReturnScope("void f() { if (a) { return; } }", -2)); - ASSERT_EQUALS(true, isReturnScope("int f() { if (a) { return {}; } }", -2)); // #8891 - ASSERT_EQUALS(true, isReturnScope("std::string f() { if (a) { return std::string{}; } }", -2)); // #8891 - ASSERT_EQUALS(true, isReturnScope("std::string f() { if (a) { return std::string{\"\"}; } }", -2)); // #8891 - ASSERT_EQUALS(true, isReturnScope("void f() { if (a) { return (ab){0}; } }", -2)); // #7103 - ASSERT_EQUALS(false, isReturnScope("void f() { if (a) { return (ab){0}; } }", -4)); // #7103 - ASSERT_EQUALS(true, isReturnScope("void f() { if (a) { {throw new string(x);}; } }", -4)); // #7144 - ASSERT_EQUALS(true, isReturnScope("void f() { if (a) { {throw new string(x);}; } }", -2)); // #7144 - ASSERT_EQUALS(false, isReturnScope("void f() { [=]() { return data; }; }", -1)); - ASSERT_EQUALS(true, isReturnScope("auto f() { return [=]() { return data; }; }", -1)); - ASSERT_EQUALS(true, isReturnScope("auto f() { return [=]() { return data; }(); }", -1)); - ASSERT_EQUALS(false, isReturnScope("auto f() { [=]() { return data; }(); }", -1)); - - ASSERT_EQUALS(true, isReturnScope("void negativeTokenOffset() { return; }", -1)); - ASSERT_EQUALS(false, isReturnScope("void zeroTokenOffset() { return; }", 0)); - ASSERT_EQUALS(true, isReturnScope("void positiveTokenOffset() { return; }", 7)); + ASSERT_EQUALS(true, isReturnScope("void f() { if (a) { return; } }\n", -2)); + ASSERT_EQUALS(true, isReturnScope("int f() { if (a) { return {}; } }\n", -2)); // #8891 + ASSERT_EQUALS(true, isReturnScope("std::string f() { if (a) { return std::string{}; } }\n", -2)); // #8891 + ASSERT_EQUALS(true, isReturnScope("std::string f() { if (a) { return std::string{\"\"}; } }\n", -2)); // #8891 + ASSERT_EQUALS(true, isReturnScope("void f() { if (a) { return (ab){0}; } }\n", -2)); // #7103 + ASSERT_EQUALS(false, isReturnScope("void f() { if (a) { return (ab){0}; } }\n", -4)); // #7103 + ASSERT_EQUALS(true, isReturnScope("void f() { if (a) { {throw new string(x);}; } }\n", -4)); // #7144 + ASSERT_EQUALS(true, isReturnScope("void f() { if (a) { {throw new string(x);}; } }\n", -2)); // #7144 + ASSERT_EQUALS(false, isReturnScope("void f() { [=]() { return data; }; }\n", -1)); + ASSERT_EQUALS(true, isReturnScope("auto f() { return [=]() { return data; }; }\n", -1)); + ASSERT_EQUALS(true, isReturnScope("auto f() { return [=]() { return data; }(); }\n", -1)); + ASSERT_EQUALS(false, isReturnScope("auto f() { [=]() { return data; }(); }\n", -1)); + + ASSERT_EQUALS(true, isReturnScope("void negativeTokenOffset() { return; }\n", -1)); + ASSERT_EQUALS(false, isReturnScope("void zeroTokenOffset() { return; }\n", 0)); + ASSERT_EQUALS(true, isReturnScope("void positiveTokenOffset() { return; }\n", 7)); } #define isSameExpression(...) isSameExpression_(__FILE__, __LINE__, __VA_ARGS__) @@ -181,33 +181,33 @@ class TestAstUtils : public TestFixture { } void isSameExpressionTestInternal(bool cpp) { - ASSERT_EQUALS(true, isSameExpression("x = 1 + 1;", "1", "1", cpp)); - ASSERT_EQUALS(false, isSameExpression("x = 1 + 1u;", "1", "1u", cpp)); - ASSERT_EQUALS(true, isSameExpression("x = 1.0 + 1.0;", "1.0", "1.0", cpp)); - ASSERT_EQUALS(false, isSameExpression("x = 1.0f + 1.0;", "1.0f", "1.0", cpp)); - ASSERT_EQUALS(false, isSameExpression("x = 1L + 1;", "1L", "1", cpp)); - ASSERT_EQUALS(true, isSameExpression("x = 0.0f + 0x0p+0f;", "0.0f", "0x0p+0f", cpp)); - ASSERT_EQUALS(true, isSameExpression("x < x;", "x", "x", cpp)); - ASSERT_EQUALS(false, isSameExpression("x < y;", "x", "y", cpp)); - ASSERT_EQUALS(true, isSameExpression("(x + 1) < (x + 1);", "+", "+", cpp)); - ASSERT_EQUALS(false, isSameExpression("(x + 1) < (x + 1L);", "+", "+", cpp)); - ASSERT_EQUALS(!cpp, isSameExpression("(1 + x) < (x + 1);", "+", "+", cpp)); - ASSERT_EQUALS(false, isSameExpression("(1.0l + x) < (1.0 + x);", "+", "+", cpp)); - ASSERT_EQUALS(!cpp, isSameExpression("(0.0 + x) < (x + 0x0p+0);", "+", "+", cpp)); - ASSERT_EQUALS(true, isSameExpression("void f() {double y = 1e1; (x + y) < (x + 10.0); } ", "+", "+", cpp)); - ASSERT_EQUALS(!cpp, isSameExpression("void f() {double y = 1e1; (x + 10.0) < (y + x); } ", "+", "+", cpp)); - ASSERT_EQUALS(true, isSameExpression("void f() {double y = 1e1; double z = 10.0; (x + y) < (x + z); } ", "+", "+", cpp)); - ASSERT_EQUALS(true, isSameExpression("A + A", "A", "A", cpp)); + ASSERT_EQUALS(true, isSameExpression("x = 1 + 1;\n", "1", "1", cpp)); + ASSERT_EQUALS(false, isSameExpression("x = 1 + 1u;\n", "1", "1u", cpp)); + ASSERT_EQUALS(true, isSameExpression("x = 1.0 + 1.0;\n", "1.0", "1.0", cpp)); + ASSERT_EQUALS(false, isSameExpression("x = 1.0f + 1.0;\n", "1.0f", "1.0", cpp)); + ASSERT_EQUALS(false, isSameExpression("x = 1L + 1;\n", "1L", "1", cpp)); + ASSERT_EQUALS(true, isSameExpression("x = 0.0f + 0x0p+0f;\n", "0.0f", "0x0p+0f", cpp)); + ASSERT_EQUALS(true, isSameExpression("x < x;\n", "x", "x", cpp)); + ASSERT_EQUALS(false, isSameExpression("x < y;\n", "x", "y", cpp)); + ASSERT_EQUALS(true, isSameExpression("(x + 1) < (x + 1);\n", "+", "+", cpp)); + ASSERT_EQUALS(false, isSameExpression("(x + 1) < (x + 1L);\n", "+", "+", cpp)); + ASSERT_EQUALS(!cpp, isSameExpression("(1 + x) < (x + 1);\n", "+", "+", cpp)); + ASSERT_EQUALS(false, isSameExpression("(1.0l + x) < (1.0 + x);\n", "+", "+", cpp)); + ASSERT_EQUALS(!cpp, isSameExpression("(0.0 + x) < (x + 0x0p+0);\n", "+", "+", cpp)); + ASSERT_EQUALS(true, isSameExpression("void f() {double y = 1e1; (x + y) < (x + 10.0); } \n", "+", "+", cpp)); + ASSERT_EQUALS(!cpp, isSameExpression("void f() {double y = 1e1; (x + 10.0) < (y + x); } \n", "+", "+", cpp)); + ASSERT_EQUALS(true, isSameExpression("void f() {double y = 1e1; double z = 10.0; (x + y) < (x + z); } \n", "+", "+", cpp)); + ASSERT_EQUALS(true, isSameExpression("A + A\n", "A", "A", cpp)); // the remaining test cases are not valid C code if (!cpp) return; //https://trac.cppcheck.net/ticket/9700 - ASSERT_EQUALS(true, isSameExpression("A::B + A::B;", "::", "::", cpp)); - ASSERT_EQUALS(false, isSameExpression("A::B + A::C;", "::", "::", cpp)); - ASSERT_EQUALS(true, isSameExpression("A::B* get() { if(x) return new A::B(true); else return new A::B(true); }", "new", "new", cpp)); - ASSERT_EQUALS(false, isSameExpression("A::B* get() { if(x) return new A::B(true); else return new A::C(true); }", "new", "new", cpp)); + ASSERT_EQUALS(true, isSameExpression("A::B + A::B;\n", "::", "::", cpp)); + ASSERT_EQUALS(false, isSameExpression("A::B + A::C;\n", "::", "::", cpp)); + ASSERT_EQUALS(true, isSameExpression("A::B* get() { if(x) return new A::B(true); else return new A::B(true); }\n", "new", "new", cpp)); + ASSERT_EQUALS(false, isSameExpression("A::B* get() { if(x) return new A::B(true); else return new A::C(true); }\n", "new", "new", cpp)); } void isSameExpressionCpp() { @@ -233,7 +233,7 @@ class TestAstUtils : public TestFixture { (void)isVariableChanged("void f() {\n" " int b;\n" " if (b) { (int)((INTOF(8))result >> b); }\n" - "}", "if", "}"); + "}\n", "if", "}"); // #9235 ASSERT_EQUALS(false, isVariableChanged("void f() {\n" @@ -242,13 +242,13 @@ class TestAstUtils : public TestFixture { "= a", "}")); - ASSERT_EQUALS(false, isVariableChanged("void f(const A& a) { a.f(); }", "{", "}")); + ASSERT_EQUALS(false, isVariableChanged("void f(const A& a) { a.f(); }\n", "{", "}")); ASSERT_EQUALS(true, isVariableChanged("void g(int*);\n" "void f(int x) { g(&x); }\n", "{", "}")); - ASSERT_EQUALS(false, isVariableChanged("const int A[] = { 1, 2, 3 };", "[", "]")); + ASSERT_EQUALS(false, isVariableChanged("const int A[] = { 1, 2, 3 };\n", "[", "]")); } #define isVariableChangedByFunctionCall(...) isVariableChangedByFunctionCall_( __FILE__, __LINE__, __VA_ARGS__) @@ -259,7 +259,7 @@ class TestAstUtils : public TestFixture { const Token * const argtok = Token::findmatch(tokenizer.tokens(), pattern); ASSERT_LOC(argtok, file, line); int indirect = (argtok->variable() && argtok->variable()->isArray()); - return (isVariableChangedByFunctionCall)(argtok, indirect, settingsDefault, inconclusive); + return (isVariableChangedByFunctionCall)(argtok, indirect, settingsDefault.library, inconclusive); } void isVariableChangedByFunctionCallTest() { @@ -267,7 +267,7 @@ class TestAstUtils : public TestFixture { { // #8271 - template method const char code[] = "void f(int x) {\n" " a(x);\n" - "}"; + "}\n"; bool inconclusive = false; ASSERT_EQUALS(false, isVariableChangedByFunctionCall(code, "x ) ;", &inconclusive)); ASSERT_EQUALS(true, inconclusive); @@ -431,8 +431,8 @@ class TestAstUtils : public TestFixture { void isExpressionChangedTest() { - ASSERT_EQUALS(true, isExpressionChanged("void f(std::map& m) { m[0]; }", "m [", "{", "}")); - ASSERT_EQUALS(false, isExpressionChanged("void f(const A& a) { a.f(); }", "a .", "{", "}")); + ASSERT_EQUALS(true, isExpressionChanged("void f(std::map& m) { m[0]; }\n", "m [", "{", "}")); + ASSERT_EQUALS(false, isExpressionChanged("void f(const A& a) { a.f(); }\n", "a .", "{", "}")); ASSERT_EQUALS(true, isExpressionChanged("void g(int*);\n" @@ -459,17 +459,17 @@ class TestAstUtils : public TestFixture { } void nextAfterAstRightmostLeafTest() { - ASSERT_EQUALS(true, nextAfterAstRightmostLeaf("void f(int a, int b) { int x = a + b; }", "=", "; }")); - ASSERT_EQUALS(true, nextAfterAstRightmostLeaf("int * g(int); void f(int a, int b) { int x = g(a); }", "=", "; }")); - ASSERT_EQUALS(true, nextAfterAstRightmostLeaf("int * g(int); void f(int a, int b) { int x = g(a)[b]; }", "=", "; }")); - ASSERT_EQUALS(true, nextAfterAstRightmostLeaf("int * g(int); void f(int a, int b) { int x = g(g(a)[b]); }", "=", "; }")); - ASSERT_EQUALS(true, nextAfterAstRightmostLeaf("int * g(int); void f(int a, int b) { int x = g(g(a)[b] + a); }", "=", "; }")); - ASSERT_EQUALS(true, nextAfterAstRightmostLeaf("int * g(int); void f(int a, int b) { int x = g(a)[b + 1]; }", "=", "; }")); - ASSERT_EQUALS(true, nextAfterAstRightmostLeaf("void f() { int a; int b; int x = [](int a){}; }", "=", "; }")); - - ASSERT_EQUALS(true, nextAfterAstRightmostLeaf("int * g(int); void f(int a, int b) { int x = a + b; }", "+", "; }")); - ASSERT_EQUALS(true, nextAfterAstRightmostLeaf("int * g(int); void f(int a, int b) { int x = g(a)[b + 1]; }", "+", "] ; }")); - ASSERT_EQUALS(true, nextAfterAstRightmostLeaf("int * g(int); void f(int a, int b) { int x = g(a + 1)[b]; }", "+", ") [")); + ASSERT_EQUALS(true, nextAfterAstRightmostLeaf("void f(int a, int b) { int x = a + b; }\n", "=", "; }")); + ASSERT_EQUALS(true, nextAfterAstRightmostLeaf("int * g(int); void f(int a, int b) { int x = g(a); }\n", "=", "; }")); + ASSERT_EQUALS(true, nextAfterAstRightmostLeaf("int * g(int); void f(int a, int b) { int x = g(a)[b]; }\n", "=", "; }")); + ASSERT_EQUALS(true, nextAfterAstRightmostLeaf("int * g(int); void f(int a, int b) { int x = g(g(a)[b]); }\n", "=", "; }")); + ASSERT_EQUALS(true, nextAfterAstRightmostLeaf("int * g(int); void f(int a, int b) { int x = g(g(a)[b] + a); }\n", "=", "; }")); + ASSERT_EQUALS(true, nextAfterAstRightmostLeaf("int * g(int); void f(int a, int b) { int x = g(a)[b + 1]; }\n", "=", "; }")); + ASSERT_EQUALS(true, nextAfterAstRightmostLeaf("void f() { int a; int b; int x = [](int a){}; }\n", "=", "; }")); + + ASSERT_EQUALS(true, nextAfterAstRightmostLeaf("int * g(int); void f(int a, int b) { int x = a + b; }\n", "+", "; }")); + ASSERT_EQUALS(true, nextAfterAstRightmostLeaf("int * g(int); void f(int a, int b) { int x = g(a)[b + 1]; }\n", "+", "] ; }")); + ASSERT_EQUALS(true, nextAfterAstRightmostLeaf("int * g(int); void f(int a, int b) { int x = g(a + 1)[b]; }\n", "+", ") [")); } enum class Result : std::uint8_t {False, True, Fail}; @@ -486,37 +486,37 @@ class TestAstUtils : public TestFixture { } void isUsedAsBool() { - ASSERT(Result::True == isUsedAsBool("void f() { bool b = true; }", "b")); - ASSERT(Result::False ==isUsedAsBool("void f() { int i = true; }", "i")); - ASSERT(Result::True == isUsedAsBool("void f() { int i; if (i) {} }", "i )")); - ASSERT(Result::True == isUsedAsBool("void f() { int i; while (i) {} }", "i )")); - ASSERT(Result::True == isUsedAsBool("void f() { int i; for (;i;) {} }", "i ; )")); - ASSERT(Result::False == isUsedAsBool("void f() { int i; for (;;i) {} }", "i )")); - ASSERT(Result::False == isUsedAsBool("void f() { int i; for (i;;) {} }", "i ; ; )")); - ASSERT(Result::True == isUsedAsBool("void f() { int i; for (int j=0; i; ++j) {} }", "i ; ++")); - ASSERT(Result::False == isUsedAsBool("void f() { int i; if (i == 2) {} }", "i ==")); - ASSERT(Result::False == isUsedAsBool("void f() { int i; if (i == true) {} }", "i ==")); - ASSERT(Result::False == isUsedAsBool("void f() { int i,j; if (i == (j&&f())) {} }", "i ==")); - ASSERT(Result::True == isUsedAsBool("void f() { int i; if (!i == 0) {} }", "i ==")); - ASSERT(Result::True == isUsedAsBool("void f() { int i; if (!i) {} }", "i )")); - ASSERT(Result::True == isUsedAsBool("void f() { int i; if (!!i) {} }", "i )")); - ASSERT(Result::True == isUsedAsBool("void f() { int i; if (i && f()) {} }", "i &&")); - ASSERT(Result::True == isUsedAsBool("void f() { int i; int j = i && f(); }", "i &&")); - ASSERT(Result::False == isUsedAsBool("void f() { int i; if (i & f()) {} }", "i &")); - ASSERT(Result::True == isUsedAsBool("void f() { int i; if (static_cast(i)) {} }", "i )")); - ASSERT(Result::True == isUsedAsBool("void f() { int i; if ((bool)i) {} }", "i )")); - ASSERT(Result::True == isUsedAsBool("void f() { int i; if (1+static_cast(i)) {} }", "i )")); - ASSERT(Result::True == isUsedAsBool("void f() { int i; if (1+(bool)i) {} }", "i )")); - ASSERT(Result::False == isUsedAsBool("void f() { int i; if (1+static_cast(i)) {} }", "i )")); - ASSERT(Result::True == isUsedAsBool("void f() { int i; if (1+!static_cast(i)) {} }", "i )")); - ASSERT(Result::False == isUsedAsBool("void f() { int i; if (1+(int)i) {} }", "i )")); - ASSERT(Result::False == isUsedAsBool("void f() { int i; if (i + 2) {} }", "i +")); - ASSERT(Result::True == isUsedAsBool("void f() { int i; bool b = i; }", "i ; }")); - ASSERT(Result::True == isUsedAsBool("void f(bool b); void f() { int i; f(i); }","i )")); - ASSERT(Result::False == isUsedAsBool("void f() { int *i; if (*i) {} }", "i )")); - ASSERT(Result::True == isUsedAsBool("void f() { int *i; if (*i) {} }", "* i )")); - ASSERT(Result::True == isUsedAsBool("int g(); void h(bool); void f() { h(g()); }", "( ) )")); - ASSERT(Result::True == isUsedAsBool("int g(int); void h(bool); void f() { h(g(0)); }", "( 0 ) )")); + ASSERT(Result::True == isUsedAsBool("void f() { bool b = true; }\n", "b")); + ASSERT(Result::False ==isUsedAsBool("void f() { int i = true; }\n", "i")); + ASSERT(Result::True == isUsedAsBool("void f() { int i; if (i) {} }\n", "i )")); + ASSERT(Result::True == isUsedAsBool("void f() { int i; while (i) {} }\n", "i )")); + ASSERT(Result::True == isUsedAsBool("void f() { int i; for (;i;) {} }\n", "i ; )")); + ASSERT(Result::False == isUsedAsBool("void f() { int i; for (;;i) {} }\n", "i )")); + ASSERT(Result::False == isUsedAsBool("void f() { int i; for (i;;) {} }\n", "i ; ; )")); + ASSERT(Result::True == isUsedAsBool("void f() { int i; for (int j=0; i; ++j) {} }\n", "i ; ++")); + ASSERT(Result::False == isUsedAsBool("void f() { int i; if (i == 2) {} }\n", "i ==")); + ASSERT(Result::False == isUsedAsBool("void f() { int i; if (i == true) {} }\n", "i ==")); + ASSERT(Result::False == isUsedAsBool("void f() { int i,j; if (i == (j&&f())) {} }\n", "i ==")); + ASSERT(Result::True == isUsedAsBool("void f() { int i; if (!i == 0) {} }\n", "i ==")); + ASSERT(Result::True == isUsedAsBool("void f() { int i; if (!i) {} }\n", "i )")); + ASSERT(Result::True == isUsedAsBool("void f() { int i; if (!!i) {} }\n", "i )")); + ASSERT(Result::True == isUsedAsBool("void f() { int i; if (i && f()) {} }\n", "i &&")); + ASSERT(Result::True == isUsedAsBool("void f() { int i; int j = i && f(); }\n", "i &&")); + ASSERT(Result::False == isUsedAsBool("void f() { int i; if (i & f()) {} }\n", "i &")); + ASSERT(Result::True == isUsedAsBool("void f() { int i; if (static_cast(i)) {} }\n", "i )")); + ASSERT(Result::True == isUsedAsBool("void f() { int i; if ((bool)i) {} }\n", "i )")); + ASSERT(Result::True == isUsedAsBool("void f() { int i; if (1+static_cast(i)) {} }\n", "i )")); + ASSERT(Result::True == isUsedAsBool("void f() { int i; if (1+(bool)i) {} }\n", "i )")); + ASSERT(Result::False == isUsedAsBool("void f() { int i; if (1+static_cast(i)) {} }\n", "i )")); + ASSERT(Result::True == isUsedAsBool("void f() { int i; if (1+!static_cast(i)) {} }\n", "i )")); + ASSERT(Result::False == isUsedAsBool("void f() { int i; if (1+(int)i) {} }\n", "i )")); + ASSERT(Result::False == isUsedAsBool("void f() { int i; if (i + 2) {} }\n", "i +")); + ASSERT(Result::True == isUsedAsBool("void f() { int i; bool b = i; }\n", "i ; }")); + ASSERT(Result::True == isUsedAsBool("void f(bool b); void f() { int i; f(i); }\n","i )")); + ASSERT(Result::False == isUsedAsBool("void f() { int *i; if (*i) {} }\n", "i )")); + ASSERT(Result::True == isUsedAsBool("void f() { int *i; if (*i) {} }\n", "* i )")); + ASSERT(Result::True == isUsedAsBool("int g(); void h(bool); void f() { h(g()); }\n", "( ) )")); + ASSERT(Result::True == isUsedAsBool("int g(int); void h(bool); void f() { h(g(0)); }\n", "( 0 ) )")); } }; diff --git a/test/testautovariables.cpp b/test/testautovariables.cpp index 008dc645419..d5ab5855c32 100644 --- a/test/testautovariables.cpp +++ b/test/testautovariables.cpp @@ -47,7 +47,8 @@ class TestAutoVariables : public TestFixture { SimpleTokenizer tokenizer(settings1, *this, options.cpp); ASSERT_LOC(tokenizer.tokenize(code), file, line); - runChecks(tokenizer, this); + CheckAutoVariables check; + runChecks(check, tokenizer, *this); } void run() override { @@ -132,6 +133,7 @@ class TestAutoVariables : public TestFixture { TEST_CASE(returnReference27); TEST_CASE(returnReference28); TEST_CASE(returnReference29); + TEST_CASE(returnReference30); // #15094 TEST_CASE(returnReferenceFunction); TEST_CASE(returnReferenceContainer); TEST_CASE(returnReferenceLiteral); @@ -179,21 +181,21 @@ class TestAutoVariables : public TestFixture { "{\n" " int num = 2;\n" " *res = #\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:5]: (error) Address of local auto-variable assigned to a function parameter. [autoVariables]\n", errout_str()); check("void func1(int **res)\n" "{\n" " int num = 2;\n" " res = #\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:5]: (warning) Assignment of function parameter has no effect outside the function. Did you forget dereferencing it? [uselessAssignmentPtrArg]\n", errout_str()); check("void func1(int **res)\n" "{\n" " int num = 2;\n" " foo.res = #\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -205,7 +207,7 @@ class TestAutoVariables : public TestFixture { "{\n" " int num = 2;\n" " *res = #\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:5]: (error) Address of local auto-variable assigned to a function parameter. [autoVariables]\n", errout_str()); check("class Fred {\n" @@ -215,7 +217,7 @@ class TestAutoVariables : public TestFixture { "{\n" " int num = 2;\n" " res = #\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:5]: (warning) Assignment of function parameter has no effect outside the function. Did you forget dereferencing it? [uselessAssignmentPtrArg]\n", errout_str()); check("class Fred {\n" @@ -225,7 +227,7 @@ class TestAutoVariables : public TestFixture { "{\n" " int num = 2;\n" " foo.res = #\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -234,7 +236,7 @@ class TestAutoVariables : public TestFixture { "{\n" " int x[100];\n" " *p = x;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:5]: (error) Address of local auto-variable assigned to a function parameter. [autoVariables]\n", errout_str()); } @@ -243,7 +245,7 @@ class TestAutoVariables : public TestFixture { "{\n" " static int x[100];\n" " *p = x;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -252,7 +254,7 @@ class TestAutoVariables : public TestFixture { "{\n" " char a;\n" " ab->a = &a;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:5]: (error, inconclusive) Address of local auto-variable assigned to a function parameter. [autoVariables]\n", errout_str()); } @@ -261,14 +263,14 @@ class TestAutoVariables : public TestFixture { "{\n" " char a[10];\n" " x->str = a;\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); check("void foo(struct X *x)\n" "{\n" " char a[10];\n" " x->str = a;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:5]: (error, inconclusive) Address of local auto-variable assigned to a function parameter. [autoVariables]\n", errout_str()); } @@ -278,7 +280,7 @@ class TestAutoVariables : public TestFixture { " struct txt_scrollpane_s * scrollpane;\n" " target->parent = &scrollpane->widget;\n" " return scrollpane;\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); } @@ -286,12 +288,12 @@ class TestAutoVariables : public TestFixture { check("void foo(int*& p) {\n" " int i = 0;\n" " p = &i;\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("[test.cpp:3:5]: (error) Address of local auto-variable assigned to a function parameter. [autoVariables]\n", errout_str()); check("void foo(std::string& s) {\n" " s = foo;\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); } @@ -302,7 +304,7 @@ class TestAutoVariables : public TestFixture { " FN fn;\n" " FP fp;\n" " p = &fn.i;\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("[test.cpp:6:5]: (error) Address of local auto-variable assigned to a function parameter. [autoVariables]\n", errout_str()); check("struct FN {int i;};\n" @@ -311,7 +313,7 @@ class TestAutoVariables : public TestFixture { " FN fn;\n" " FP fp;\n" " p = &p_fp->i;\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); check("struct FN {int i;};\n" @@ -320,100 +322,100 @@ class TestAutoVariables : public TestFixture { " FN fn;\n" " FP fp;\n" " p = &fp.f->i;\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); } void testautovar10() { // #2930 - assignment of function parameter check("void foo(char* p) {\n" " p = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:5]: (warning) Assignment of function parameter has no effect outside the function. Did you forget dereferencing it? [uselessAssignmentPtrArg]\n", errout_str()); check("void foo(int b) {\n" " b = foo(b);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:5]: (style) Assignment of function parameter has no effect outside the function. [uselessAssignmentArg]\n", errout_str()); check("void foo(int b) {\n" " b += 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:5]: (style) Assignment of function parameter has no effect outside the function. [uselessAssignmentArg]\n", errout_str()); check("void foo(std::string s) {\n" " s = foo(b);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:5]: (style) Assignment of function parameter has no effect outside the function. [uselessAssignmentArg]\n", errout_str()); check("void foo(char* p) {\n" // don't warn for self assignment, there is another warning for this " p = p;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(char* p) {\n" " if (!p) p = buf;\n" " *p = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(char* p) {\n" " if (!p) p = buf;\n" " do_something(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(char* p) {\n" " while (!p) p = buf;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(char* p) {\n" " p = 0;\n" " asm(\"somecmd\");\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(Foo* p) {\n" " p = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:5]: (warning) Assignment of function parameter has no effect outside the function. Did you forget dereferencing it? [uselessAssignmentPtrArg]\n", errout_str()); check("class Foo {};\n" "void foo(Foo p) {\n" " p = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (style) Assignment of function parameter has no effect outside the function. [uselessAssignmentArg]\n", errout_str()); check("void foo(Foo p) {\n" " p = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int& p) {\n" " p = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("double foo(double d) {\n" // #5005 " int i = d;\n" " d = i;\n" - " return d;" - "}",dinit(CheckOptions, $.inconclusive = false)); + " return d;\n" + "}\n",dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); check("void foo(int* ptr) {\n" // #4793 " ptr++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:5]: (warning) Assignment of function parameter has no effect outside the function. Did you forget dereferencing it? [uselessAssignmentPtrArg]\n", errout_str()); check("void foo(int* ptr) {\n" // #3177 " --ptr;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:7]: (warning) Assignment of function parameter has no effect outside the function. Did you forget dereferencing it? [uselessAssignmentPtrArg]\n", errout_str()); check("void foo(struct S* const x) {\n" // #7839 " ++x->n;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -424,7 +426,7 @@ class TestAutoVariables : public TestFixture { "void foo(char** p) {\n" " struct A a = bar();\n" " *p = &(*a.data)[0];\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct A {\n" @@ -433,36 +435,36 @@ class TestAutoVariables : public TestFixture { "void foo(char** p) {\n" " struct A a = bar();\n" " *p = &a.data[0];\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:5]: (error) Address of local auto-variable assigned to a function parameter. [autoVariables]\n", errout_str()); check("void f(char **out) {\n" " struct S *p = glob;\n" " *out = &p->data;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #4998 check("void f(s8**out) {\n" " s8 *p;\n" // <- p is pointer => no error " *out = &p[1];\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(s8**out) {\n" " s8 p[10];\n" // <- p is array => error " *out = &p[1];\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:3]: (error) Address of local auto-variable assigned to a function parameter. [autoVariables]\n", errout_str()); } void testautovar12() { // Ticket #5024, #5050 - Crash on invalid input - ASSERT_THROW_INTERNAL(check("void f(int* a) { a = }"), SYNTAX); + ASSERT_THROW_INTERNAL(check("void f(int* a) { a = }\n"), SYNTAX); check("struct custom_type { custom_type(int) {} };\n" "void func(int) {}\n" "int var;\n" "void init() { func(var); }\n" - "UNKNOWN_MACRO_EXPANDING_TO_SIGNATURE { custom_type a(var); }"); + "UNKNOWN_MACRO_EXPANDING_TO_SIGNATURE { custom_type a(var); }\n"); } void testautovar13() { // Ticket #5537 @@ -472,7 +474,7 @@ class TestAutoVariables : public TestFixture { " ~FileManager() {\n" " delete &UniqueRealDirs;\n" " }\n" - "};"); + "};\n"); } void testautovar14() { // Ticket #4776 @@ -482,7 +484,7 @@ class TestAutoVariables : public TestFixture { " x = x >> 1;\n" " goto label;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -495,7 +497,7 @@ class TestAutoVariables : public TestFixture { " if (lumdiff > 5.0f)\n" " return &darkOutline;\n" " return 0;\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); } @@ -503,7 +505,7 @@ class TestAutoVariables : public TestFixture { check("void f(const void* ptr, bool* result) {\n" " int dummy;\n" " *result = (&dummy < ptr);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -532,7 +534,7 @@ class TestAutoVariables : public TestFixture { "{\n" " int num=2;" " arr[0]=#\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:19]: (error) Address of local auto-variable assigned to a function parameter. [autoVariables]\n", errout_str()); } @@ -544,7 +546,7 @@ class TestAutoVariables : public TestFixture { "{\n" " int num=2;" " arr[0]=#\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:19]: (error) Address of local auto-variable assigned to a function parameter. [autoVariables]\n", errout_str()); } @@ -595,7 +597,7 @@ class TestAutoVariables : public TestFixture { "{\n" " XPoint DropPoint;\n" " ds->location_data = (XtPointer *)&DropPoint;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:5]: (error, inconclusive) Address of local auto-variable assigned to a function parameter. [autoVariables]\n", errout_str()); } @@ -603,7 +605,7 @@ class TestAutoVariables : public TestFixture { check("void remove_duplicate_matches (char **matches) {\n" " char dead_slot;\n" " matches[0] = (char *)&dead_slot;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:3]: (error) Address of local auto-variable assigned to a function parameter. [autoVariables]\n", errout_str()); } @@ -612,7 +614,7 @@ class TestAutoVariables : public TestFixture { "{\n" " int num=2;" " return #" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:26] -> [test.cpp:3:9] -> [test.cpp:3:26]: (error) Returning pointer to local variable 'num' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); } @@ -624,7 +626,7 @@ class TestAutoVariables : public TestFixture { "{\n" " int num=2;" " return #" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:26] -> [test.cpp:6:9] -> [test.cpp:6:26]: (error) Returning pointer to local variable 'num' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); } @@ -634,7 +636,7 @@ class TestAutoVariables : public TestFixture { "{\n" " void *&value = tls[id];" " return &value;" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -643,7 +645,7 @@ class TestAutoVariables : public TestFixture { check("void foo() {\n" " int cond2;\n" " dostuff([&cond2]() { return &cond2; });\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -680,7 +682,7 @@ class TestAutoVariables : public TestFixture { "{\n" " extern struct foo f;\n" " return &f;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -690,7 +692,7 @@ class TestAutoVariables : public TestFixture { " pcb->root0 = &root0;\n" " dostuff(pcb);\n" " pcb->root0 = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(cb* pcb) {\n" @@ -699,7 +701,7 @@ class TestAutoVariables : public TestFixture { " dostuff(pcb);\n" " if (condition) return;\n" // <- not reassigned => error " pcb->root0 = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:3]: (error, inconclusive) Address of local auto-variable assigned to a function parameter. [autoVariables]\n", errout_str()); check("void foo(cb* pcb) {\n" @@ -708,7 +710,7 @@ class TestAutoVariables : public TestFixture { " dostuff(pcb);\n" " if (condition)\n" " pcb->root0 = 0;\n" // <- conditional reassign => error - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:3]: (error, inconclusive) Address of local auto-variable assigned to a function parameter. [autoVariables]\n", errout_str()); check("struct S { int *p; };\n" @@ -720,7 +722,7 @@ class TestAutoVariables : public TestFixture { "void f() {\n" " struct S s;\n" " g(&s);\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:4:5]: (error) Address of local auto-variable assigned to a function parameter. [autoVariables]\n", errout_str()); @@ -738,7 +740,7 @@ class TestAutoVariables : public TestFixture { " delete[] (tmp4);\n" " char tmp5[256];\n" " delete[] tmp5;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:10]: (error) Deallocation of an auto-variable results in undefined behaviour. [autovarInvalidDeallocation]\n" "[test.cpp:5:12]: (error) Deallocation of an auto-variable results in undefined behaviour. [autovarInvalidDeallocation]\n" "[test.cpp:7:12]: (error) Deallocation of an auto-variable results in undefined behaviour. [autovarInvalidDeallocation]\n" @@ -747,28 +749,28 @@ class TestAutoVariables : public TestFixture { check("void func1(char * ptr) {\n" " free(ptr);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void func1() {\n" " char* tmp1[256];\n" " init(tmp1);\n" " delete tmp1[34];\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void func1() {\n" " static char tmp1[256];\n" " char *p = tmp1;\n" " free(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:10]: (error) Deallocation of a static variable (tmp1) results in undefined behaviour. [autovarInvalidDeallocation]\n", errout_str()); check("char tmp1[256];\n" "void func1() {\n" " char *p; if (x) p = tmp1;\n" " free(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:10]: (error) Deallocation of a global variable (tmp1) results in undefined behaviour. [autovarInvalidDeallocation]\n", errout_str()); check("void f()\n" @@ -779,7 +781,7 @@ class TestAutoVariables : public TestFixture { " abc(0, psz_title);\n" " free(psz_title);\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #2298 new check: passing stack-address to free() @@ -787,44 +789,44 @@ class TestAutoVariables : public TestFixture { " int *p = malloc(4);\n" " free(&p);\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:10]: (error) Deallocation of an auto-variable results in undefined behaviour. [autovarInvalidDeallocation]\n", errout_str()); check("int main() {\n" " int i;\n" " free(&i);\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:10]: (error) Deallocation of an auto-variable results in undefined behaviour. [autovarInvalidDeallocation]\n", errout_str()); // #5732 check("int main() {\n" " long (*pKoeff)[256] = new long[9][256];\n" " delete[] pKoeff;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int main() {\n" " long *pKoeff[256];\n" " delete[] pKoeff;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:13]: (error) Deallocation of an auto-variable results in undefined behaviour. [autovarInvalidDeallocation]\n", errout_str()); check("int main() {\n" " long *pKoeff[256];\n" " free (pKoeff);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:10]: (error) Deallocation of an auto-variable results in undefined behaviour. [autovarInvalidDeallocation]\n", errout_str()); check("void foo() {\n" " const intPtr& intref = Getter();\n" " delete intref;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void test() {\n" " MyObj& obj = *new MyObj;\n" " delete &obj;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #6506 @@ -836,7 +838,7 @@ class TestAutoVariables : public TestFixture { " F().free(c1);\n" " char *c2 = 0;\n" " F().free(&c2);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("class foo {\n" @@ -845,14 +847,14 @@ class TestAutoVariables : public TestFixture { " char **dst_copy = NULL;\n" " free(&dst_copy);\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // #6551 check("bool foo( ) {\n" " SwTxtFld * pTxtFld = GetFldTxtAttrAt();\n" " delete static_cast(&pTxtFld->GetAttr());\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #8910 @@ -862,14 +864,14 @@ class TestAutoVariables : public TestFixture { " if (data_size > sizeof (stack)) data = malloc (data_size);\n" " else data = (RGNDATA *)stack;\n" " if ((char *)data != stack) free (data);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #8923 check("void f(char **args1, char *args2[]) {\n" " free((char **)args1);\n" " free((char **)args2);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #10097 @@ -984,7 +986,7 @@ class TestAutoVariables : public TestFixture { check("void f(EventPtr *eventP, ActionPtr **actionsP) {\n" " EventPtr event = *eventP;\n" " *actionsP = &event->actions;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -992,7 +994,7 @@ class TestAutoVariables : public TestFixture { check("static void function(unsigned long **datap) {\n" " struct my_s *mr = global_structure_pointer;\n" " *datap = &mr->value;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1001,7 +1003,7 @@ class TestAutoVariables : public TestFixture { "void f() {\n" " int x[10];\n" " p = x;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:7] -> [test.cpp:3:7] -> [test.cpp:4:3]: (error) Non-local variable 'p' will use pointer to local variable 'x'. [danglingLifetime]\n", errout_str()); check("int *p;\n" @@ -1009,7 +1011,7 @@ class TestAutoVariables : public TestFixture { " int x[10];\n" " p = x;\n" " p = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1018,7 +1020,7 @@ class TestAutoVariables : public TestFixture { "void f() {\n" " int x;\n" " p = &x;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:7] -> [test.cpp:3:7] -> [test.cpp:4:3]: (error) Non-local variable 'p' will use pointer to local variable 'x'. [danglingLifetime]\n", errout_str()); check("int *p;\n" @@ -1026,7 +1028,7 @@ class TestAutoVariables : public TestFixture { " int x;\n" " p = &x;\n" " p = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1037,7 +1039,7 @@ class TestAutoVariables : public TestFixture { " ptr = &x;\n" " }\n" " int *ptr;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:11] -> [test.cpp:3:9] -> [test.cpp:4:5]: (error) Non-local variable 'ptr' will use pointer to local variable 'x'. [danglingLifetime]\n", errout_str()); check("struct A {\n" @@ -1047,7 +1049,7 @@ class TestAutoVariables : public TestFixture { " ptr = 0;\n" " }\n" " int *ptr;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct R {\n" // #11393 @@ -1071,7 +1073,7 @@ class TestAutoVariables : public TestFixture { "{\n" " char str[100] = {0};\n" " return str;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:4:12] -> [test.cpp:3:10] -> [test.cpp:4:12]: (error) Returning pointer to local variable 'str' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); @@ -1081,7 +1083,7 @@ class TestAutoVariables : public TestFixture { " char str[100] = {0};\n" " char *p = str;\n" " return p;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:4:15] -> [test.cpp:3:10] -> [test.cpp:5:12]: (error) Returning pointer to local variable 'str' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); @@ -1093,7 +1095,7 @@ class TestAutoVariables : public TestFixture { "{\n" " char str[100] = {0};\n" " return str;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:7:12] -> [test.cpp:6:10] -> [test.cpp:7:12]: (error) Returning pointer to local variable 'str' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); @@ -1105,7 +1107,7 @@ class TestAutoVariables : public TestFixture { " char temp[42];\n" " char *tp = temp;\n" " tp = format_reg(tp);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1114,7 +1116,7 @@ class TestAutoVariables : public TestFixture { "{\n" " char str[100] = {0};\n" " return str;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("class Fred {\n" @@ -1124,7 +1126,7 @@ class TestAutoVariables : public TestFixture { "{\n" " char str[100] = {0};\n" " return str;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1134,14 +1136,14 @@ class TestAutoVariables : public TestFixture { check("char *foo() {\n" " char q[] = \"AAAAAAAAAAAA\";\n" " return &q[1];\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:12] -> [test.cpp:2:10] -> [test.cpp:3:12]: (error) Returning pointer to local variable 'q' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); check("char *foo()\n" "{\n" " static char q[] = \"AAAAAAAAAAAA\";\n" " return &q[1];\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("char *foo()\n" @@ -1150,7 +1152,7 @@ class TestAutoVariables : public TestFixture { "char *p;\n" "p = &q[1];\n" "return p;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:5] -> [test.cpp:3:6] -> [test.cpp:6:8]: (error) Returning pointer to local variable 'q' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); } @@ -1158,7 +1160,7 @@ class TestAutoVariables : public TestFixture { check("char *foo() {\n" " char x[10] = {0};\n" " return x+5;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:3:12] -> [test.cpp:2:10] -> [test.cpp:3:13]: (error) Returning pointer to local variable 'x' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); @@ -1166,7 +1168,7 @@ class TestAutoVariables : public TestFixture { check("char *foo(int y) {\n" " char x[10] = {0};\n" " return (x+8)-y;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:3:13] -> [test.cpp:2:10] -> [test.cpp:3:17]: (error) Returning pointer to local variable 'x' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); @@ -1176,7 +1178,7 @@ class TestAutoVariables : public TestFixture { check("char *foo() {\n" " int x[10] = {0};\n" " return (char *)x;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:3:20] -> [test.cpp:2:9] -> [test.cpp:3:12]: (error) Returning pointer to local variable 'x' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); @@ -1187,7 +1189,7 @@ class TestAutoVariables : public TestFixture { " int x = 123;\n" " int p = &x;\n" " return p;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:13] -> [test.cpp:2:9] -> [test.cpp:4:12]: (error) Returning object that points to local variable 'x' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); } @@ -1197,35 +1199,35 @@ class TestAutoVariables : public TestFixture { " int s = 0;\n" " int& x = s;\n" " return x;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:12] -> [test.cpp:5:12]: (error) Reference to local variable returned. [returnReference]\n", errout_str()); check("std::string &foo()\n" "{\n" " std::string s;\n" " return s;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:12]: (error) Reference to local variable returned. [returnReference]\n", errout_str()); check("std::vector &foo()\n" "{\n" " std::vector v;\n" " return v;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:12]: (error) Reference to local variable returned. [returnReference]\n", errout_str()); check("std::vector &foo()\n" "{\n" " static std::vector v;\n" " return v;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("std::vector &foo()\n" "{\n" " thread_local std::vector v;\n" " return v;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("std::string hello()\n" @@ -1236,7 +1238,7 @@ class TestAutoVariables : public TestFixture { "std::string &f()\n" "{\n" " return hello();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:17]: (error) Reference to temporary returned. [returnTempReference]\n", errout_str()); // make sure scope is used in function lookup @@ -1247,7 +1249,7 @@ class TestAutoVariables : public TestFixture { "};\n" "std::string &f() {\n" " return hello();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("std::string hello() {\n" @@ -1256,7 +1258,7 @@ class TestAutoVariables : public TestFixture { "\n" "std::string &f() {\n" " return hello();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:17]: (error) Reference to temporary returned. [returnTempReference]\n", errout_str()); check("std::string hello() {\n" @@ -1265,7 +1267,7 @@ class TestAutoVariables : public TestFixture { "\n" "std::string &f() {\n" " return hello().substr(1);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:26]: (error) Reference to temporary returned. [returnTempReference]\n", errout_str()); check("class Foo;\n" @@ -1275,7 +1277,7 @@ class TestAutoVariables : public TestFixture { "\n" "Foo& f() {\n" " return hello();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:17]: (error) Reference to temporary returned. [returnTempReference]\n", errout_str()); // make sure function overloads are handled properly @@ -1290,7 +1292,7 @@ class TestAutoVariables : public TestFixture { "\n" "Foo& f() {\n" " return hello(true);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("Foo hello() {\n" @@ -1299,7 +1301,7 @@ class TestAutoVariables : public TestFixture { "\n" "Foo& f() {\n" // Unknown type - might be a reference " return hello();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1311,7 +1313,7 @@ class TestAutoVariables : public TestFixture { "{\n" " std::string s;\n" " return s;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:12]: (error) Reference to local variable returned. [returnReference]\n", errout_str()); check("class Fred {\n" @@ -1321,7 +1323,7 @@ class TestAutoVariables : public TestFixture { "{\n" " std::vector v;\n" " return v;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:12]: (error) Reference to local variable returned. [returnReference]\n", errout_str()); check("class Fred {\n" @@ -1331,7 +1333,7 @@ class TestAutoVariables : public TestFixture { "{\n" " static std::vector v;\n" " return v;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("class Fred {\n" @@ -1344,7 +1346,7 @@ class TestAutoVariables : public TestFixture { "std::string &Fred::f()\n" "{\n" " return hello();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:10:17]: (error) Reference to temporary returned. [returnTempReference]\n", errout_str()); check("class Fred {\n" @@ -1358,7 +1360,7 @@ class TestAutoVariables : public TestFixture { "std::string &Fred::f()\n" "{\n" " return hello();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:11:17]: (error) Reference to temporary returned. [returnTempReference]\n", errout_str()); check("class Bar;\n" @@ -1367,7 +1369,7 @@ class TestAutoVariables : public TestFixture { "}\n" "Bar& bar() {\n" " return foo();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:15]: (error) Reference to temporary returned. [returnTempReference]\n", errout_str()); check("std::map foo() {\n" @@ -1375,7 +1377,7 @@ class TestAutoVariables : public TestFixture { "}\n" "std::map& bar() {\n" " return foo();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:15]: (error) Reference to temporary returned. [returnTempReference]\n", errout_str()); check("Bar foo() {\n" @@ -1383,13 +1385,13 @@ class TestAutoVariables : public TestFixture { "}\n" "Bar& bar() {\n" // Unknown type - might be a typedef to a reference type " return foo();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Don't crash with function in unknown scope (#4076) - check("X& a::Bar() {}" + check("X& a::Bar() {}\n" "X& foo() {" " return Bar();" - "}"); + "}\n"); } void returnReference3() { @@ -1397,7 +1399,7 @@ class TestAutoVariables : public TestFixture { " double ret = getValue();\n" " rd = ret;\n" " return rd;\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); } @@ -1406,7 +1408,7 @@ class TestAutoVariables : public TestFixture { check("double a;\n" "double & f() {\n" " return a;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1426,7 +1428,7 @@ class TestAutoVariables : public TestFixture { " const A &ra = pb->a;\n" " return ra;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -1434,7 +1436,7 @@ class TestAutoVariables : public TestFixture { check("Fred & create() {\n" " Fred &fred(*new Fred);\n" " return fred;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1443,14 +1445,14 @@ class TestAutoVariables : public TestFixture { "std::string &a(int);\n" "std::string &b() {\n" " return a(12);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("std::string &a(int);\n" "std::string a();\n" "std::string &b() {\n" " return a(12);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1459,14 +1461,14 @@ class TestAutoVariables : public TestFixture { " std::vector::iterator it = v.begin();\n" " int& value = *it;\n" " return value;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } void returnReference9() { check("int& f(bool b, int& x, int& y) {\n" " return b ? x : y;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1475,14 +1477,14 @@ class TestAutoVariables : public TestFixture { "int& g() {\n" " A a;\n" " return a.f();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:15]: (error) Reference to temporary returned. [returnTempReference]\n", errout_str()); check("class A { int& f() const; };\n" "int& g() {\n" " A a;\n" " return a.f();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1490,19 +1492,19 @@ class TestAutoVariables : public TestFixture { check("class A { static int f(); };\n" "int& g() {\n" " return A::f();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:16]: (error) Reference to temporary returned. [returnTempReference]\n", errout_str()); check("class A { static int& f(); };\n" "int& g() {\n" " return A::f();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("namespace A { int& f(); }\n" "int& g() {\n" " return A::f();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1510,14 +1512,14 @@ class TestAutoVariables : public TestFixture { check("class A { static int& f(); };\n" "auto g() {\n" " return &A::f;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("class A { static int& f(); };\n" "auto g() {\n" " auto x = &A::f;\n" " return x;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1526,14 +1528,14 @@ class TestAutoVariables : public TestFixture { "void* vp = &v;\n" "int& foo(size_t i) {\n" " return ((std::vector*)vp)->at(i);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("std::vector v;\n" "void* vp = &v;\n" "int& foo(size_t i) {\n" " return static_cast*>(vp)->at(i);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1546,7 +1548,7 @@ class TestAutoVariables : public TestFixture { "}\n" "void* &A::f() {\n" " return g()->m;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1559,7 +1561,7 @@ class TestAutoVariables : public TestFixture { "template \n" "const int& f(const T&) {\n" " return f();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("template \n" @@ -1567,19 +1569,19 @@ class TestAutoVariables : public TestFixture { "template \n" "const int& f(const T&) {\n" " return g();\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("error", "", errout_str()); } void returnReference16() { check("int& f(std::tuple& x) {\n" " return std::get<0>(x);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int& f(int x) {\n" " return std::get<0>(std::make_tuple(x));\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("error", "", errout_str()); } @@ -1587,7 +1589,7 @@ class TestAutoVariables : public TestFixture { check("auto g() -> int&;\n" "int& f() {\n" " return g();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1596,7 +1598,7 @@ class TestAutoVariables : public TestFixture { "auto f(T& x) -> decltype(x);\n" "int& g(int* x) {\n" " return f(*x);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1604,7 +1606,7 @@ class TestAutoVariables : public TestFixture { void returnReference19() { check("struct C : B {\n" " const B &f() const { return (const B &)*this; }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1615,7 +1617,7 @@ class TestAutoVariables : public TestFixture { "};\n" "int& b() {\n" " return a()();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("auto a() {\n" @@ -1626,13 +1628,13 @@ class TestAutoVariables : public TestFixture { "}\n" "const int& c() {\n" " return a()();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("std::function a();\n" "int& b() {\n" " return a()();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #9889 @@ -1771,6 +1773,23 @@ class TestAutoVariables : public TestFixture { errout_str()); } + void returnReference30() // #15094 + { + check("int& f1(int& (*fp)()) { return fp(); }\n" + "void f2(int& (*fp)()) { int& r = fp(); r = 2; }\n" + "struct D {\n" + " int& call() { return f(); }\n" + " int& (*f)();\n" + "};\n" + "const int& f3(const int& (*fp)(int)) { return fp(1); }\n" + "int& (*g)();\n" + "int& f4() { return g(); }\n" + "typedef int& (*FP)();\n" + "int& f5(FP fp) { return fp(); }\n" + "int* f6(int& (*fp)()) { return &fp(); }\n"); + ASSERT_EQUALS("", errout_str()); + } + void returnReferenceFunction() { check("int& f(int& a) {\n" " return a;\n" @@ -1778,7 +1797,7 @@ class TestAutoVariables : public TestFixture { "int& hello() {\n" " int x = 0;\n" " return f(x);\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:1:14] -> [test.cpp:2:12] -> [test.cpp:6:12] -> [test.cpp:6:13]: (error) Reference to local variable returned. [returnReference]\n", errout_str()); @@ -1789,7 +1808,7 @@ class TestAutoVariables : public TestFixture { "int* hello() {\n" " int x = 0;\n" " return &f(x);\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:1:14] -> [test.cpp:2:12] -> [test.cpp:6:13] -> [test.cpp:6:12] -> [test.cpp:5:9] -> [test.cpp:6:12]: (error) Returning pointer to local variable 'x' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); @@ -1799,7 +1818,7 @@ class TestAutoVariables : public TestFixture { "}\n" "int * g(int x) {\n" " return f(&x);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:14] -> [test.cpp:5:14] -> [test.cpp:4:13] -> [test.cpp:5:13]: (error) Returning pointer to local variable 'x' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); check("int* f(int * x) {\n" @@ -1808,7 +1827,7 @@ class TestAutoVariables : public TestFixture { "}\n" "int * g(int x) {\n" " return f(&x);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int f(int& a) {\n" @@ -1817,7 +1836,7 @@ class TestAutoVariables : public TestFixture { "int& hello() {\n" " int x = 0;\n" " return f(x);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:13]: (error) Reference to temporary returned. [returnTempReference]\n", errout_str()); check("int& f(int a) {\n" @@ -1826,7 +1845,7 @@ class TestAutoVariables : public TestFixture { "int& hello() {\n" " int x = 0;\n" " return f(x);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:12]: (error) Reference to local variable returned. [returnReference]\n", errout_str()); check("int f(int a) {\n" @@ -1835,14 +1854,14 @@ class TestAutoVariables : public TestFixture { "int& hello() {\n" " int x = 0;\n" " return f(x);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:13]: (error) Reference to temporary returned. [returnTempReference]\n", errout_str()); check("template\n" "int& f(int& x, T y) {\n" " x += y;\n" " return x;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1850,31 +1869,31 @@ class TestAutoVariables : public TestFixture { check("auto& f() {\n" " std::vector x;\n" " return x[0];\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:13]: (error) Reference to local variable returned. [returnReference]\n", errout_str()); check("auto& f() {\n" " std::vector x;\n" " return x.front();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:14] -> [test.cpp:3:19]: (error) Reference to local variable returned. [returnReference]\n", errout_str()); check("std::vector g();\n" "auto& f() {\n" " return g().front();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:16] -> [test.cpp:3:21]: (error) Reference to temporary returned. [returnTempReference]\n", errout_str()); check("auto& f() {\n" " return std::vector{1}.front();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:32] -> [test.cpp:2:37]: (error) Reference to temporary returned. [returnTempReference]\n", errout_str()); check("struct A { int foo; };\n" "int& f(std::vector v) {\n" " auto it = v.begin();\n" " return it->foo;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:22] -> [test.cpp:4:14]: (error) Reference to local variable returned. [returnReference]\n", errout_str()); check("template \n" @@ -1908,7 +1927,7 @@ class TestAutoVariables : public TestFixture { "int& f(std::vector& v) {\n" " auto it = v.begin();\n" " return it->foo;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("static std::vector A[2];\n" @@ -1922,18 +1941,18 @@ class TestAutoVariables : public TestFixture { void returnReferenceLiteral() { check("const std::string &a() {\n" " return \"foo\";\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:12]: (error) Reference to temporary returned. [returnTempReference]\n", errout_str()); check("const std::string a() {\n" " return \"foo\";\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("const std::string& f(const std::string& x) { return x; }\n" "const std::string &a() {\n" " return f(\"foo\");\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:1:42] -> [test.cpp:1:53] -> [test.cpp:3:12] -> [test.cpp:3:13]: (error) Reference to temporary returned. [returnTempReference]\n", errout_str()); @@ -1941,59 +1960,59 @@ class TestAutoVariables : public TestFixture { check("const char * f(const char * x) { return x; }\n" "const std::string &a() {\n" " return f(\"foo\");\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:13]: (error) Reference to temporary returned. [returnTempReference]\n", errout_str()); } void returnReferenceCalculation() { check("const std::string &a(const std::string& str) {\n" " return \"foo\" + str;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:18]: (error) Reference to temporary returned. [returnTempReference]\n", errout_str()); check("int& operator<<(int out, int path) {\n" " return out << path;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:16]: (error) Reference to temporary returned. [returnTempReference]\n", errout_str()); check("std::ostream& operator<<(std::ostream& out, const std::string& path) {\n" " return out << path;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("std::ostream& operator<<(std::ostream* out, const std::string& path) {\n" " return *out << path;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("Unknown1& operator<<(Unknown1 out, Unknown2 path) {\n" " return out << path;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int& a(int b) {\n" " return 2*(b+1);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:13]: (error) Reference to temporary returned. [returnTempReference]\n", errout_str()); check("const std::string &a(const std::string& str) {\n" " return str;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("const std::string &a(int bar) {\n" " return foo(bar + 1);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("const std::string a(const std::string& str) {\n" " return \"foo\" + str;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int& incValue(int& value) {\n" " return ++value;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2004,7 +2023,7 @@ class TestAutoVariables : public TestFixture { " [](const Item& lhs, const Item& rhs) {\n" " return false;\n" " });\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #5844 @@ -2014,13 +2033,13 @@ class TestAutoVariables : public TestFixture { " return var;\n" " }();\n" "return s_var;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #7583 check("Command& foo() {\n" " return f([]() -> int { return 1; });\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2033,16 +2052,16 @@ class TestAutoVariables : public TestFixture { " }\n" " };\n" " return _make(_Wrapper::call, pmf);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } void returnReferenceRecursive() { - check("int& f() { return f(); }"); + check("int& f() { return f(); }\n"); ASSERT_EQUALS("", errout_str()); check("int& g(int& i) { return i; }\n" - "int& f() { return g(f()); }"); + "int& f() { return g(f()); }\n"); ASSERT_EQUALS("", errout_str()); } @@ -2052,7 +2071,7 @@ class TestAutoVariables : public TestFixture { "auto f() {\n" " const int& x = h();\n" " return [&] { return x; };\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:18] -> [test.cpp:5:25] -> [test.cpp:4:16] -> [test.cpp:5:12]: (error) Returning lambda that captures local variable 'x' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); check("void g(int*);\n" @@ -2060,7 +2079,7 @@ class TestAutoVariables : public TestFixture { "int* f() {\n" " const int& x = h();\n" " return &x;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:18] -> [test.cpp:5:12] -> [test.cpp:4:16] -> [test.cpp:5:12]: (error) Returning pointer to local variable 'x' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); check("void g(int*);\n" @@ -2068,7 +2087,7 @@ class TestAutoVariables : public TestFixture { "void f() {\n" " int& x = h();\n" " g(&x);\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:4:12] -> [test.cpp:5:7] -> [test.cpp:4:15] -> [test.cpp:5:7]: (error) Using pointer that is a temporary. [danglingTemporaryLifetime]\n" "[test.cpp:4:12] -> [test.cpp:5:8]: (error) Using reference to dangling temporary. [danglingTempReference]\n", @@ -2079,7 +2098,7 @@ class TestAutoVariables : public TestFixture { "void f() {\n" " const int& x = h();\n" " g(&x);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct Data {\n" @@ -2088,7 +2107,7 @@ class TestAutoVariables : public TestFixture { "const char* foo() {\n" " const Data& data = getData();\n" " return data.path.c_str();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2097,7 +2116,7 @@ class TestAutoVariables : public TestFixture { "{\n" " static int &r = k;\n" " return r;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:19] -> [test.cpp:3:17]: (error) Non-local reference variable 'r' to local variable 'k' [danglingReference]\n", errout_str()); @@ -2105,7 +2124,7 @@ class TestAutoVariables : public TestFixture { "{\n" " static int &r = k;\n" " return r;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2273,7 +2292,7 @@ class TestAutoVariables : public TestFixture { " {\n" " pNum = apNum;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2282,28 +2301,28 @@ class TestAutoVariables : public TestFixture { check("int* foo(int y)\n" "{\n" " return &y;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:10] -> [test.cpp:1:14] -> [test.cpp:3:10]: (error) Returning pointer to local variable 'y' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); check("int ** foo(int * y)\n" "{\n" " return &y;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:10] -> [test.cpp:1:18] -> [test.cpp:3:10]: (error) Returning pointer to local variable 'y' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); check("const int * foo(const int & y)\n" "{\n" " return &y;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int * foo(int * y)\n" "{\n" " return y;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); @@ -2313,7 +2332,7 @@ class TestAutoVariables : public TestFixture { "{\n" " struct s *r = f();\n" " *q = &r->p;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2324,7 +2343,7 @@ class TestAutoVariables : public TestFixture { " const_tree_iterator& parent() {\n" " return const_tree_iterator(foo);\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:31]: (error) Reference to temporary returned. [returnTempReference]\n", errout_str()); } @@ -2335,7 +2354,7 @@ class TestAutoVariables : public TestFixture { "}\n" "void opened_cb () {\n" " g_signal_connect (G_CALLBACK (removed_cb));\n" - "}"); + "}\n"); } void danglingLifetimeLambda() { @@ -2343,30 +2362,30 @@ class TestAutoVariables : public TestFixture { " int a = 1;\n" " auto l = [&](){ return a; };\n" " return l;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:28] -> [test.cpp:2:9] -> [test.cpp:4:12]: (error) Returning lambda that captures local variable 'a' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); check("auto f() {\n" " int a = 1;\n" " return [&](){ return a; };\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:26] -> [test.cpp:2:9] -> [test.cpp:3:12]: (error) Returning lambda that captures local variable 'a' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); check("auto f(int a) {\n" " return [&](){ return a; };\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:26] -> [test.cpp:1:12] -> [test.cpp:2:12]: (error) Returning lambda that captures local variable 'a' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); check("auto f(int a) {\n" " auto p = &a;\n" " return [=](){ return p; };\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:14] -> [test.cpp:3:26] -> [test.cpp:1:12] -> [test.cpp:3:12]: (error) Returning lambda that captures local variable 'a' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); check("auto g(int& a) {\n" " int p = a;\n" " return [&](){ return p; };\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:26] -> [test.cpp:2:9] -> [test.cpp:3:12]: (error) Returning lambda that captures local variable 'p' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); check("auto f() {\n" @@ -2374,7 +2393,7 @@ class TestAutoVariables : public TestFixture { " int a = 1;\n" " return [&](){ return a; };\n" " };\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:30] -> [test.cpp:3:13] -> [test.cpp:4:16]: (error) Returning lambda that captures local variable 'a' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); check("auto f(int b) {\n" @@ -2382,30 +2401,30 @@ class TestAutoVariables : public TestFixture { " a += b;\n" " return [&](){ return a; };\n" " };\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:30] -> [test.cpp:2:20] -> [test.cpp:4:16]: (error) Returning lambda that captures local variable 'a' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); check("auto g(int& a) {\n" " return [&](){ return a; };\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("auto g(int a) {\n" " auto p = a;\n" " return [=](){ return p; };\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("auto g(int& a) {\n" " auto p = a;\n" " return [=](){ return p; };\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("auto g(int& a) {\n" " int& p = a;\n" " return [&](){ return p; };\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("template\n" @@ -2413,7 +2432,7 @@ class TestAutoVariables : public TestFixture { "auto f() {\n" " int x;\n" " return g([&]() { return x; });\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("auto f() {\n" @@ -2486,28 +2505,28 @@ class TestAutoVariables : public TestFixture { check("auto f(const std::vector& x) {\n" " auto it = x.begin();\n" " return it;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("std::vector::iterator f() {\n" " std::vector x;\n" " auto it = x.begin();\n" " return it;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:22] -> [test.cpp:2:22] -> [test.cpp:4:12]: (error) Returning iterator to local container 'x' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); check("auto f() {\n" " std::vector x;\n" " auto it = std::begin(x);\n" " return it;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:25] -> [test.cpp:2:22] -> [test.cpp:4:12]: (error) Returning iterator to local container 'x' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); check("int* f() {\n" " std::vector x;\n" " auto p = x.data();\n" " return p;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:3:20] -> [test.cpp:2:22] -> [test.cpp:4:12]: (error) Returning pointer to local variable 'x' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); @@ -2516,7 +2535,7 @@ class TestAutoVariables : public TestFixture { " std::vector x;\n" " auto p = &x[0];\n" " return p;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:3:14] -> [test.cpp:2:22] -> [test.cpp:4:12]: (error) Returning pointer to local variable 'x' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); @@ -2525,7 +2544,7 @@ class TestAutoVariables : public TestFixture { "int* f(std::vector v) {\n" " auto it = v.begin();\n" " return &it->foo;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:3:22] -> [test.cpp:4:12] -> [test.cpp:2:23] -> [test.cpp:4:12]: (error) Returning pointer to local variable 'v' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); @@ -2533,14 +2552,14 @@ class TestAutoVariables : public TestFixture { check("std::vector::iterator f(std::vector x) {\n" " auto it = x.begin();\n" " return it;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:22] -> [test.cpp:1:47] -> [test.cpp:3:12]: (error) Returning iterator to local container 'x' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); check("auto f() {\n" " std::vector x;\n" " auto it = x.begin();\n" " return std::next(it);\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:3:22] -> [test.cpp:2:22] -> [test.cpp:4:21]: (error) Returning iterator to local container 'x' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); @@ -2549,7 +2568,7 @@ class TestAutoVariables : public TestFixture { " std::vector x;\n" " auto it = x.begin();\n" " return it + 1;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:3:22] -> [test.cpp:2:22] -> [test.cpp:4:15]: (error) Returning iterator to local container 'x' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); @@ -2558,7 +2577,7 @@ class TestAutoVariables : public TestFixture { " std::vector x;\n" " auto it = x.begin();\n" " return std::next(it + 1);\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:3:22] -> [test.cpp:4:25] -> [test.cpp:2:22] -> [test.cpp:4:21]: (error) Returning object that points to local variable 'x' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); @@ -2568,7 +2587,7 @@ class TestAutoVariables : public TestFixture { " std::vector v;\n" " v.push_back(&i);\n" " return v;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:4:17] -> [test.cpp:4:17] -> [test.cpp:2:9] -> [test.cpp:5:12]: (error) Returning object that points to local variable 'i' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); @@ -2580,7 +2599,7 @@ class TestAutoVariables : public TestFixture { " v.push_back(&i);\n" " r.assign(v.begin(), v.end());\n" " return r;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:5:17] -> [test.cpp:5:17] -> [test.cpp:5:5] -> [test.cpp:3:9] -> [test.cpp:7:12]: (error) Returning object that points to local variable 'i' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); @@ -2591,7 +2610,7 @@ class TestAutoVariables : public TestFixture { " int i;\n" " v.push_back(&i);\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS( "[test.cpp:5:21] -> [test.cpp:5:21] -> [test.cpp:4:13] -> [test.cpp:5:9]: (error) Non-local variable 'v' will use object that points to local variable 'i'. [danglingLifetime]\n", errout_str()); @@ -2603,7 +2622,7 @@ class TestAutoVariables : public TestFixture { " int * p = &i;\n" " v.push_back(p);\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS( "[test.cpp:5:19] -> [test.cpp:6:21] -> [test.cpp:4:13] -> [test.cpp:6:9]: (error) Non-local variable 'v' will use object that points to local variable 'i'. [danglingLifetime]\n", errout_str()); @@ -2616,7 +2635,7 @@ class TestAutoVariables : public TestFixture { " v.push_back(&x);\n" " m.insert(m.end(), v.begin(), v.end());\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS( "[test.cpp:6:21] -> [test.cpp:6:21] -> [test.cpp:6:9] -> [test.cpp:4:13] -> [test.cpp:7:9]: (error) Non-local variable 'm' will use object that points to local variable 'x'. [danglingLifetime]\n" "[test.cpp:6:21] -> [test.cpp:6:21] -> [test.cpp:6:9] -> [test.cpp:4:13] -> [test.cpp:7:18]: (error) Non-local variable 'm' will use object that points to local variable 'x'. [danglingLifetime]\n", // duplicate @@ -2627,7 +2646,7 @@ class TestAutoVariables : public TestFixture { " return it;\n" " }\n" " return {};\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:2:26] -> [test.cpp:1:47] -> [test.cpp:3:16]: (error) Returning iterator to local container 'v' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); @@ -2635,7 +2654,7 @@ class TestAutoVariables : public TestFixture { check("const char * f() {\n" " std::string ba(\"hello\");\n" " return ba.c_str();\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:3:19] -> [test.cpp:2:16] -> [test.cpp:3:19]: (error) Returning pointer to local variable 'ba' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); @@ -2657,7 +2676,7 @@ class TestAutoVariables : public TestFixture { check("std::vector g();\n" "auto f() {\n" " return g().begin();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:21] -> [test.cpp:3:21]: (error) Returning iterator that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); @@ -2665,14 +2684,14 @@ class TestAutoVariables : public TestFixture { "std::vector::iterator f() {\n" " auto it = g().begin();\n" " return it;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:24] -> [test.cpp:3:16] -> [test.cpp:4:5]: (error) Using iterator that is a temporary. [danglingTemporaryLifetime]\n", errout_str()); check("std::vector g();\n" "int& f() {\n" " return *g().begin();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:22] -> [test.cpp:3:12]: (error) Reference to temporary returned. [returnTempReference]\n", errout_str()); check("struct A {\n" @@ -2681,7 +2700,7 @@ class TestAutoVariables : public TestFixture { " char s[3];\n" " v.push_back(s);\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("std::vector f() {\n" @@ -2689,46 +2708,46 @@ class TestAutoVariables : public TestFixture { " std::vector v;\n" " v.push_back(s);\n" " return v;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("auto f() {\n" " static std::vector x;\n" " return x.begin();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("std::string g() {\n" " std::vector v;\n" " return v.data();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("std::vector::iterator f(std::vector* v) {\n" " return v->begin();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("std::vector::iterator f(std::vector* v) {\n" " std::vector* v = new std::vector();\n" " return v->begin();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int f(std::vector v) {\n" " return *v.begin();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int f(std::vector v) {\n" " return v.end() - v.begin();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("auto g() {\n" " std::vector v;\n" " return {v, [v]() { return v.data(); }};\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("template\n" @@ -2736,7 +2755,7 @@ class TestAutoVariables : public TestFixture { "auto f() {\n" " std::vector v;\n" " return g([&]() { return v.data(); });\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("std::vector g();\n" @@ -2746,7 +2765,7 @@ class TestAutoVariables : public TestFixture { " std::vector v = g();\n" " m.insert(m.end(), v.begin(), v.end());\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("void f(bool b) {\n" @@ -2756,7 +2775,7 @@ class TestAutoVariables : public TestFixture { " v.insert(a, a+1);\n" " }\n" " return v.back() == 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("class A {\n" @@ -2767,7 +2786,7 @@ class TestAutoVariables : public TestFixture { " struct B {};\n" " std::map< S, B > m1;\n" " std::map< S, B > m2;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct A {\n" @@ -2776,33 +2795,33 @@ class TestAutoVariables : public TestFixture { " void f() {\n" " v.push_back(&x);\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("size_t f(const std::string& x) {\n" " std::string y = \"x\";\n" " return y.find(x);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("std::string* f();\n" "const char* g() {\n" " std::string* var = f();\n" " return var->c_str();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("std::string f() {\n" " std::vector data{};\n" " data.push_back('a');\n" " return std::string{ data.data(), data.size() };\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("std::vector f() {\n" " char a = 0;\n" " return std::vector{&a};\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:31] -> [test.cpp:3:31] -> [test.cpp:2:10] -> [test.cpp:3:30]: (error) Returning object that points to local variable 'a' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); check("std::vector* g();\n" @@ -2961,6 +2980,15 @@ class TestAutoVariables : public TestFixture { " int m;\n" "}\n"); ASSERT_EQUALS("", errout_str()); + + check("std::string to_string(const char* p) {\n" // #14818 + " return p;\n" + "}\n" + "std::string get() {\n" + " std::string s;\n" + " return to_string(s.c_str());\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); } void danglingLifetimeContainerView() @@ -3230,26 +3258,35 @@ class TestAutoVariables : public TestFixture { " return s[0].p.get();\n" "}\n"); ASSERT_EQUALS("", errout_str()); + + check("std::unique_ptr g_p;\n" // #14958 + "int* f() {\n" + " std::unique_ptr tmp = std::make_unique();\n" + " int* p = tmp.get();\n" + " g_p = std::move(tmp);\n" + " return p;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); } void danglingLifetime() { check("auto f() {\n" " std::vector a;\n" " auto it = a.begin();\n" " return [=](){ return it; };\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:22] -> [test.cpp:4:26] -> [test.cpp:2:22] -> [test.cpp:4:12]: (error) Returning lambda that captures local variable 'a' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); check("auto f(std::vector a) {\n" " auto it = a.begin();\n" " return [=](){ return it; };\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:22] -> [test.cpp:3:26] -> [test.cpp:1:25] -> [test.cpp:3:12]: (error) Returning lambda that captures local variable 'a' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); check("struct e {};\n" "e * j() {\n" " e c[20];\n" " return c;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:4:12] -> [test.cpp:3:7] -> [test.cpp:4:12]: (error) Returning pointer to local variable 'c' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); @@ -3257,12 +3294,12 @@ class TestAutoVariables : public TestFixture { check("auto f(std::vector& a) {\n" " auto it = a.begin();\n" " return [=](){ return it; };\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int * f(int a[]) {\n" " return a;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -3270,14 +3307,14 @@ class TestAutoVariables : public TestFixture { " uint32_t f[6];\n" " } d;\n" " uint32_t *a = d.f;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Don't decay std::array check("std::array f() {\n" " std::array x;\n" " return x;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Make sure we don't hang @@ -3285,7 +3322,7 @@ class TestAutoVariables : public TestFixture { "void f() {\n" " using T = A[3];\n" " A &&a = T{1, 2, 3}[1];\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Make sure we don't hang @@ -3293,7 +3330,7 @@ class TestAutoVariables : public TestFixture { "void f() {\n" " using T = A[3];\n" " A &&a = T{1, 2, 3}[1]();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Make sure we don't hang @@ -3301,7 +3338,7 @@ class TestAutoVariables : public TestFixture { "void f() {\n" " using T = A[3];\n" " A &&a = T{1, 2, 3}[1];\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Make sure we don't hang @@ -3309,7 +3346,7 @@ class TestAutoVariables : public TestFixture { "void f() {\n" " using T = A[3];\n" " A &&a = T{1, 2, 3}[1]();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Crash #8872 @@ -3317,14 +3354,14 @@ class TestAutoVariables : public TestFixture { " void operator()(b c) override {\n" " d(c, [&] { c->e });\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct a {\n" " void operator()(b c) override {\n" " d(c, [=] { c->e });\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct a {\n" @@ -3333,7 +3370,7 @@ class TestAutoVariables : public TestFixture { "a f() {\n" " char c[20];\n" " return c;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct a {\n" @@ -3343,7 +3380,7 @@ class TestAutoVariables : public TestFixture { " char c[20];\n" " a d = c;\n" " return d;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -3351,14 +3388,14 @@ class TestAutoVariables : public TestFixture { " std::vector v;\n" " auto g() { return v.end(); }\n" " };\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int * f(std::vector& v) {\n" " for(int & x : v)\n" " return &x;\n" " return nullptr;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #9275 @@ -3370,7 +3407,7 @@ class TestAutoVariables : public TestFixture { " char buf[1024];\n" " const char* msg = buf;\n" " m = msg;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #9201 @@ -3378,14 +3415,14 @@ class TestAutoVariables : public TestFixture { " struct a { int m; };\n" " static a b{0};\n" " return &b.m;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #9453 check("int *ptr;\n" "void foo(int arr[]) {\n" " ptr = &arr[2];\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #9639 @@ -3396,7 +3433,7 @@ class TestAutoVariables : public TestFixture { "const char * f() {\n" " const Fred &fred = getFred();\n" " return fred.s.c_str();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #9534 @@ -3406,14 +3443,14 @@ class TestAutoVariables : public TestFixture { "int* f(int i, std::vector& v) {\n" " A& y = v[i];\n" " return &y.x[i];\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #9712 check("std::string f(const char *str) {\n" " char value[256];\n" " return value;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #9770 @@ -3572,7 +3609,7 @@ class TestAutoVariables : public TestFixture { " }\n" " *p = 0;\n" "}\n"); - ASSERT_EQUALS("[test.cpp:5:13] -> [test.cpp:2:9] -> [test.cpp:7:6]: (error) Static variable 'p' will use pointer to local variable 'a'. [danglingLifetime]\n", errout_str()); + ASSERT_EQUALS("[test.cpp:5:13] -> [test.cpp:4:9] -> [test.cpp:2:9] -> [test.cpp:7:6]: (error) Static variable 'p' will use pointer to local variable 'a'. [danglingLifetime]\n", errout_str()); // #10902 check("void f() {\n" @@ -3587,7 +3624,7 @@ class TestAutoVariables : public TestFixture { check("auto f() {\n" " int a;\n" " return std::ref(a);\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:3:21] -> [test.cpp:2:9] -> [test.cpp:3:20]: (error) Returning object that points to local variable 'a' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); @@ -3595,7 +3632,7 @@ class TestAutoVariables : public TestFixture { check("auto f() {\n" " int a;\n" " return std::make_tuple(std::ref(a));\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:3:37] -> [test.cpp:3:36] -> [test.cpp:2:9] -> [test.cpp:3:27]: (error) Returning object that points to local variable 'a' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); @@ -3607,7 +3644,7 @@ class TestAutoVariables : public TestFixture { "auto g() {\n" " std::vector v;\n" " return by_value(v.begin());\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:7:28] -> [test.cpp:7:28] -> [test.cpp:3:12] -> [test.cpp:3:25] -> [test.cpp:6:22] -> [test.cpp:7:20]: (error) Returning object that points to local variable 'v' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); @@ -3630,7 +3667,7 @@ class TestAutoVariables : public TestFixture { "auto f() {\n" " int i = 0;\n" " return by_ref(i);\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:2:12] -> [test.cpp:1:19] -> [test.cpp:2:25] -> [test.cpp:6:19] -> [test.cpp:5:9] -> [test.cpp:6:18]: (error) Returning object that points to local variable 'i' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); @@ -3648,7 +3685,7 @@ class TestAutoVariables : public TestFixture { " int a;\n" " std::tie(a) = x;\n" " return a;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("std::pair\n" @@ -3897,7 +3934,7 @@ class TestAutoVariables : public TestFixture { "A f() {\n" " int i = 0;\n" " return A{i, i};\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:7:14] -> [test.cpp:6:9] -> [test.cpp:7:13]: (error) Returning object that points to local variable 'i' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); @@ -3909,7 +3946,7 @@ class TestAutoVariables : public TestFixture { "A f() {\n" " int i = 0;\n" " return {i, i};\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:7:13] -> [test.cpp:6:9] -> [test.cpp:7:12]: (error) Returning object that points to local variable 'i' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); @@ -3922,7 +3959,7 @@ class TestAutoVariables : public TestFixture { " int i = 0;\n" " A r{i, i};\n" " return r;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:7:9] -> [test.cpp:6:9] -> [test.cpp:8:12]: (error) Returning object that points to local variable 'i' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); @@ -3935,7 +3972,7 @@ class TestAutoVariables : public TestFixture { " int i = 0;\n" " A r = {i, i};\n" " return r;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:7:12] -> [test.cpp:6:9] -> [test.cpp:8:12]: (error) Returning object that points to local variable 'i' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); @@ -3947,7 +3984,7 @@ class TestAutoVariables : public TestFixture { "A f(int& x) {\n" " int i = 0;\n" " return A{i, x};\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:7:14] -> [test.cpp:6:9] -> [test.cpp:7:13]: (error) Returning object that points to local variable 'i' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); @@ -3959,7 +3996,7 @@ class TestAutoVariables : public TestFixture { "A f(int& x) {\n" " int i = 0;\n" " return A{x, i};\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct A {\n" @@ -3968,14 +4005,14 @@ class TestAutoVariables : public TestFixture { "};\n" "A f(int& x) {\n" " return A{x, x};\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct A { int i; const int& j; };\n" "A f(int& x) {\n" " int y = 0;\n" " return A{y, x};\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct a {\n" @@ -4061,6 +4098,14 @@ class TestAutoVariables : public TestFixture { " struct S s = { .i = 0, true };\n" "}\n"); // don't crash ASSERT_EQUALS("", errout_str()); + + check("struct A { int& r; };\n" // #14772 + "A* f() {\n" + " int x = 0;\n" + " return new A{ x };\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:4:19] -> [test.cpp:3:9] -> [test.cpp:4:17]: (error) Returning object that points to local variable 'x' that will be invalid when returning. [returnDanglingLifetime]\n", + errout_str()); } void danglingLifetimeInitList() { @@ -4068,7 +4113,7 @@ class TestAutoVariables : public TestFixture { " int i = 0;\n" " std::vector v = {&i, &i};\n" " return v;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:3:28] -> [test.cpp:3:28] -> [test.cpp:2:9] -> [test.cpp:4:12]: (error) Returning object that points to local variable 'i' that will be invalid when returning. [returnDanglingLifetime]\n" "[test.cpp:3:32] -> [test.cpp:3:32] -> [test.cpp:2:9] -> [test.cpp:4:12]: (error) Returning object that points to local variable 'i' that will be invalid when returning. [returnDanglingLifetime]\n", @@ -4078,7 +4123,7 @@ class TestAutoVariables : public TestFixture { " int i = 0;\n" " std::vector v{&i, &i};\n" " return v;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:3:25] -> [test.cpp:3:25] -> [test.cpp:2:9] -> [test.cpp:4:12]: (error) Returning object that points to local variable 'i' that will be invalid when returning. [returnDanglingLifetime]\n" "[test.cpp:3:29] -> [test.cpp:3:29] -> [test.cpp:2:9] -> [test.cpp:4:12]: (error) Returning object that points to local variable 'i' that will be invalid when returning. [returnDanglingLifetime]\n", @@ -4087,7 +4132,7 @@ class TestAutoVariables : public TestFixture { check("std::vector f() {\n" " int i = 0;\n" " return {&i, &i};\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:3:13] -> [test.cpp:3:13] -> [test.cpp:2:9] -> [test.cpp:3:12]: (error) Returning object that points to local variable 'i' that will be invalid when returning. [returnDanglingLifetime]\n" "[test.cpp:3:17] -> [test.cpp:3:17] -> [test.cpp:2:9] -> [test.cpp:3:12]: (error) Returning object that points to local variable 'i' that will be invalid when returning. [returnDanglingLifetime]\n", @@ -4095,7 +4140,7 @@ class TestAutoVariables : public TestFixture { check("std::vector f(int& x) {\n" " return {&x, &x};\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("std::vector f() {\n" @@ -4103,7 +4148,7 @@ class TestAutoVariables : public TestFixture { " x.insert(\"1\");\n" " x.insert(\"2\");\n" " return { x.begin(), x.end() };\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -4112,7 +4157,7 @@ class TestAutoVariables : public TestFixture { "A f() {\n" " std::string ba(\"hello\");\n" " return ba.c_str();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct A { A(const char *a); };\n" @@ -4120,7 +4165,7 @@ class TestAutoVariables : public TestFixture { " std::string ba(\"hello\");\n" " A bp = ba.c_str();\n" " return bp;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct A { A(const char *a); };\n" @@ -4129,7 +4174,7 @@ class TestAutoVariables : public TestFixture { " std::vector v;\n" " v.push_back(ba.c_str());\n" " return v;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("std::string f(const std::string& x) {\n" @@ -4137,7 +4182,7 @@ class TestAutoVariables : public TestFixture { " if (!x.empty())\n" " return x + c;\n" " return \"\";\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("std::string f(const std::string& x) {\n" @@ -4145,7 +4190,7 @@ class TestAutoVariables : public TestFixture { " if (!x.empty())\n" " return c + 1;\n" " return \"\";\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -4168,7 +4213,7 @@ class TestAutoVariables : public TestFixture { "void f(int& i) {\n" " int* x = &g(0);\n" " i += *x;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:1:26] -> [test.cpp:2:12] -> [test.cpp:5:15] -> [test.cpp:5:14] -> [test.cpp:5:17] -> [test.cpp:6:11]: (error) Using pointer that is a temporary. [danglingTemporaryLifetime]\n", errout_str()); @@ -4178,7 +4223,7 @@ class TestAutoVariables : public TestFixture { " const char* b = a.toStdString().c_str();\n" " QString c = b;\n" " return c;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:42] -> [test.cpp:3:34] -> [test.cpp:4:15]: (error) Using pointer that is a temporary. [danglingTemporaryLifetime]\n", errout_str()); @@ -4186,7 +4231,7 @@ class TestAutoVariables : public TestFixture { " const char *x = s.substr(1,2).c_str();\n" " auto i = s.substr(4,5).begin();\n" " return *i;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:33] -> [test.cpp:3:22] -> [test.cpp:4:13]: (error) Using iterator that is a temporary. [danglingTemporaryLifetime]\n", errout_str()); @@ -4194,7 +4239,7 @@ class TestAutoVariables : public TestFixture { " std::stringstream tmp;\n" " const std::string &str = tmp.str();\n" " return std::string(str.c_str(), 1);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int get_value();\n" @@ -4303,7 +4348,7 @@ class TestAutoVariables : public TestFixture { " std::vector cargs = f({ \"0\", \"0\" });\n" " (void)cargs;\n" "};\n"); - ASSERT_EQUALS("[test.cpp:6:12] -> [test.cpp:4:47] -> [test.cpp:3:22] -> [test.cpp:1:58] -> [test.cpp:4:40] -> [test.cpp:9:34] -> [test.cpp:9:34] -> [test.cpp:10:11]: (error) Using object that is a temporary. [danglingTemporaryLifetime]\n", errout_str()); + ASSERT_EQUALS("[test.cpp:6:12] -> [test.cpp:4:47] -> [test.cpp:3:22] -> [test.cpp:1:58] -> [test.cpp:4:40] -> [test.cpp:3:24] -> [test.cpp:9:34] -> [test.cpp:9:34] -> [test.cpp:10:11]: (error) Using object that is a temporary. [danglingTemporaryLifetime]\n", errout_str()); check("struct C {\n" // #9194 " const int& m;\n" @@ -4443,6 +4488,116 @@ class TestAutoVariables : public TestFixture { "}\n"); ASSERT_EQUALS("[test.cpp:5:14] -> [test.cpp:5:18] -> [test.cpp:6:7]: (error) Using pointer that is a temporary. [danglingTemporaryLifetime]\n", errout_str()); + + check("struct A { const int* data[2]; };\n" + "A g() {\n" + " int x = 0;\n" + " A a;\n" + " a.data[0] = &x;\n" + " return a;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:17] -> [test.cpp:3:9] -> [test.cpp:6:12]: (error) Returning object that points to local variable 'x' that will be invalid when returning. [returnDanglingLifetime]\n", + errout_str()); + + check("struct A { const int* data[2]; };\n" + "A g() {\n" + " int x = 0;\n" + " A a[2];\n" + " a[0].data[0] = &x;\n" + " return a[0];\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:20] -> [test.cpp:3:9] -> [test.cpp:6:13]: (error) Returning object that points to local variable 'x' that will be invalid when returning. [returnDanglingLifetime]\n", + errout_str()); + + check("struct A { const int* data[2]; };\n" + "A* g() {\n" + " int x = 0;\n" + " static A arr[2];\n" + " arr[0].data[0] = &x;\n" + " return arr;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:22] -> [test.cpp:3:9] -> [test.cpp:6:12]: (error) Returning pointer to local variable 'x' that will be invalid when returning. [returnDanglingLifetime]\n", + errout_str()); + + check("struct A { const int* p; };\n" + "A g(int i) {\n" + " int x = 0;\n" + " A arr[2];\n" + " arr[i].p = &x;\n" + " return arr[i];\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:16] -> [test.cpp:3:9] -> [test.cpp:6:15]: (error) Returning object that points to local variable 'x' that will be invalid when returning. [returnDanglingLifetime]\n", + errout_str()); + + check("struct A { const int* p; };\n" + "A* g(int i) {\n" + " int x = 0;\n" + " static A arr[2];\n" + " arr[i].p = &x;\n" + " return arr;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:16] -> [test.cpp:3:9] -> [test.cpp:6:12]: (error) Returning pointer to local variable 'x' that will be invalid when returning. [returnDanglingLifetime]\n", + errout_str()); + + check("struct A { const int* p; };\n" + "struct B { A arr[2]; };\n" + "B g(int i) {\n" + " int x = 0;\n" + " B b;\n" + " b.arr[i].p = &x;\n" + " return b;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:6:18] -> [test.cpp:4:9] -> [test.cpp:7:12]: (error) Returning object that points to local variable 'x' that will be invalid when returning. [returnDanglingLifetime]\n", + errout_str()); + + check("struct A { const int* p; };\n" + "A* g() {\n" + " int x = 0;\n" + " static A a;\n" + " A* ap = &a;\n" + " ap->p = &x;\n" + " (*ap).p = &x;\n" + " return ap;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("struct A { int* a; int* b; };\n" + "static A g;\n" + "int* f() {\n" + " int x = 0;\n" + " g.a = &x;\n" + " return g.b;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:11] -> [test.cpp:4:9] -> [test.cpp:5:6]: (error) Non-local variable 'g.a' will use pointer to local variable 'x'. [danglingLifetime]\n", + errout_str()); + + check("struct A { int* a; int* b; };\n" + "static A g;\n" + "int* f() {\n" + " int x = 0;\n" + " g.a = &x;\n" + " return g.a;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:11] -> [test.cpp:4:9] -> [test.cpp:5:6]: (error) Non-local variable 'g.a' will use pointer to local variable 'x'. [danglingLifetime]\n" + "[test.cpp:5:11] -> [test.cpp:4:9] -> [test.cpp:6:13]: (error) Returning pointer to local variable 'x' that will be invalid when returning. [returnDanglingLifetime]\n", + errout_str()); + + check("int* f() {\n" // #11168 + " auto x = get();\n" + " return &*x.a;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("struct S { int a[1]; };\n" + "int& f(S s) {\n" + " return *s.a;\n" + "}\n" + "int* g(S s) {\n" + " return &*s.a;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:3:12]: (error) Reference to local variable returned. [returnReference]\n" + "[test.cpp:6:12] -> [test.cpp:5:10] -> [test.cpp:6:12]: (error) Returning pointer to local variable 's' that will be invalid when returning. [returnDanglingLifetime]\n", + errout_str()); } void danglingLifetimeClassMemberFunctions() @@ -4571,8 +4726,8 @@ class TestAutoVariables : public TestFixture { " f = [&]{ return b; };\n" " }\n" " f();\n" - "}"); - ASSERT_EQUALS("[test.cpp:5:25] -> [test.cpp:4:13] -> [test.cpp:7:5]: (error) Using lambda that captures local variable 'b' that is out of scope. [invalidLifetime]\n", errout_str()); + "}\n"); + ASSERT_EQUALS("[test.cpp:5:25] -> [test.cpp:3:11] -> [test.cpp:4:13] -> [test.cpp:7:5]: (error) Using lambda that captures local variable 'b' that is out of scope. [invalidLifetime]\n", errout_str()); check("void f(bool b) {\n" " int* x;\n" @@ -4581,9 +4736,9 @@ class TestAutoVariables : public TestFixture { " x = y;\n" " }\n" " x[3];\n" - "}"); + "}\n"); ASSERT_EQUALS( - "[test.cpp:5:9] -> [test.cpp:4:9] -> [test.cpp:7:3]: (error) Using pointer to local variable 'y' that is out of scope. [invalidLifetime]\n", + "[test.cpp:5:9] -> [test.cpp:3:6] -> [test.cpp:4:9] -> [test.cpp:7:3]: (error) Using pointer to local variable 'y' that is out of scope. [invalidLifetime]\n", errout_str()); check("void foo(int a) {\n" @@ -4593,7 +4748,7 @@ class TestAutoVariables : public TestFixture { " f = [&]{ return b; };\n" " f();\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct a {\n" @@ -4602,7 +4757,7 @@ class TestAutoVariables : public TestFixture { "};\n" "void a::b() {\n" " c.end()\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void b(char f[], char c[]) {\n" @@ -4610,7 +4765,7 @@ class TestAutoVariables : public TestFixture { " std::string e;\n" " b(f, e.c_str())\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(bool b) {\n" @@ -4620,11 +4775,11 @@ class TestAutoVariables : public TestFixture { " s = buf;\n" " }\n" " std::cout << s;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int &a[];\n" - "void b(){int *c = a};"); + "void b(){int *c = a};\n"); ASSERT_EQUALS("", errout_str()); check("struct A {\n" @@ -4638,7 +4793,7 @@ class TestAutoVariables : public TestFixture { " return y.x;\n" " };\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("namespace test {\n" @@ -4772,8 +4927,8 @@ class TestAutoVariables : public TestFixture { " p = &x;\n" " }\n" " *p = 0;\n" - "}"); - ASSERT_EQUALS("[test.cpp:5:9] -> [test.cpp:4:9] -> [test.cpp:7:4]: (error) Using pointer to local variable 'x' that is out of scope. [invalidLifetime]\n", errout_str()); + "}\n"); + ASSERT_EQUALS("[test.cpp:5:9] -> [test.cpp:3:7] -> [test.cpp:4:9] -> [test.cpp:7:4]: (error) Using pointer to local variable 'x' that is out of scope. [invalidLifetime]\n", errout_str()); // FP: don't warn in subfunction check("void f(struct KEY *key) {\n" @@ -4789,7 +4944,7 @@ class TestAutoVariables : public TestFixture { " } else {\n" " }\n" " f(tmp);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Don't warn about references (#6399) @@ -4801,7 +4956,7 @@ class TestAutoVariables : public TestFixture { " }\n" " if (former_hover != pitem)\n" " dosth();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -4812,9 +4967,9 @@ class TestAutoVariables : public TestFixture { " }\n" " if (former_hover != pitem)\n" " dosth();\n" - "}"); + "}\n"); ASSERT_EQUALS( - "[test.cpp:5:24] -> [test.cpp:3:47] -> [test.cpp:4:26] -> [test.cpp:7:9]: (error) Using pointer to local variable 'item' that is out of scope. [invalidLifetime]\n", + "[test.cpp:5:24] -> [test.cpp:3:47] -> [test.cpp:3:47] -> [test.cpp:4:26] -> [test.cpp:7:9]: (error) Using pointer to local variable 'item' that is out of scope. [invalidLifetime]\n", errout_str()); // #6575 @@ -4825,7 +4980,7 @@ class TestAutoVariables : public TestFixture { " };\n" " EventReport * rep = &repData;\n" " rep->setEventType(NDB_LE_Connected);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #8785 @@ -4838,8 +4993,10 @@ class TestAutoVariables : public TestFixture { " if(b && a)\n" " return *iPtr;\n" " return 0;\n" - "}"); - ASSERT_EQUALS("[test.cpp:5:16] -> [test.cpp:4:13] -> [test.cpp:8:17]: (error) Using pointer to local variable 'x' that is out of scope. [invalidLifetime]\n", errout_str()); + "}\n"); + ASSERT_EQUALS( + "[test.cpp:5:16] -> [test.cpp:3:8] -> [test.cpp:7:10] -> [test.cpp:4:13] -> [test.cpp:8:17]: (error) Using pointer to local variable 'x' that is out of scope. [invalidLifetime]\n", + errout_str()); // #11753 check("int main(int argc, const char *argv[]) {\n" @@ -4850,7 +5007,7 @@ class TestAutoVariables : public TestFixture { " }\n" " std::cout << s;\n" "}\n"); - ASSERT_EQUALS("[test.cpp:5:26] -> [test.cpp:4:14] -> [test.cpp:7:18]: (error) Using pointer to local variable 'buff' that is out of scope. [invalidLifetime]\n", errout_str()); + ASSERT_EQUALS("[test.cpp:5:26] -> [test.cpp:3:14] -> [test.cpp:4:14] -> [test.cpp:7:18]: (error) Using pointer to local variable 'buff' that is out of scope. [invalidLifetime]\n", errout_str()); check("char* f(char* dst) {\n" " const char* src = \"abc\";\n" diff --git a/test/testbool.cpp b/test/testbool.cpp index 61bc1b36d9e..46365c11af3 100644 --- a/test/testbool.cpp +++ b/test/testbool.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -88,36 +88,36 @@ class TestBool : public TestFixture { SimpleTokenizer tokenizer(settings, *this, options.cpp); ASSERT_LOC(tokenizer.tokenize(code), file, line); - // Check... - runChecks(tokenizer, this); + CheckBool check; + runChecks(check, tokenizer, *this); } void assignBoolToPointer() { check("void foo(bool *p) {\n" " p = false;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:7]: (error) Boolean value assigned to pointer. [assignBoolToPointer]\n", errout_str()); check("void foo(bool *p) {\n" " p = (x[rSize];\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // ticket #6588 (c mode) @@ -152,7 +152,7 @@ class TestBool : public TestFixture { " const int *rmat = n < 4 ? " /* OK */ " ctx->q_intra_matrix :" " ctx->q_chroma_intra_matrix;\n" - "}", dinit(CheckOptions, $.cpp = false)); + "}\n", dinit(CheckOptions, $.cpp = false)); ASSERT_EQUALS("[test.c:3:19]: (error) Boolean value assigned to pointer. [assignBoolToPointer]\n", errout_str()); // ticket #6588 (c++ mode) @@ -162,7 +162,7 @@ class TestBool : public TestFixture { " const int *rmat = n < 4 ? " /* OK */ " ctx->q_intra_matrix :" " ctx->q_chroma_intra_matrix;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:19]: (error) Boolean value assigned to pointer. [assignBoolToPointer]\n", errout_str()); // ticket #6665 @@ -171,37 +171,37 @@ class TestBool : public TestFixture { " char* m1 = compare(a, b) < 0\n" " ? (compare(b, c) < 0 ? b : (compare(a, c) < 0 ? c : a))\n" " : (compare(a, c) < 0 ? a : (compare(b, c) < 0 ? c : b));\n" - "}", dinit(CheckOptions, $.cpp = false)); + "}\n", dinit(CheckOptions, $.cpp = false)); ASSERT_EQUALS("", errout_str()); // #7381 check("void foo(bool *p, bool b) {\n" " p = b;\n" " p = &b;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:7]: (error) Boolean value assigned to pointer. [assignBoolToPointer]\n", errout_str()); } void assignBoolToFloat() { check("void foo1() {\n" " double d = false;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:14]: (style) Boolean value assigned to floating point variable. [assignBoolToFloat]\n", errout_str()); check("void foo2() {\n" " float d = true;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:13]: (style) Boolean value assigned to floating point variable. [assignBoolToFloat]\n", errout_str()); check("void foo3() {\n" " long double d = (2>1);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:19]: (style) Boolean value assigned to floating point variable. [assignBoolToFloat]\n", errout_str()); // stability - don't crash: check("void foo4() {\n" " unknown = false;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct S {\n" @@ -210,7 +210,7 @@ class TestBool : public TestFixture { "void f() {\n" " S s = {0};\n" " s.p = true;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:9]: (style) Boolean value assigned to floating point variable. [assignBoolToFloat]\n", errout_str()); check("struct S {\n" @@ -219,7 +219,7 @@ class TestBool : public TestFixture { "void f() {\n" " S s = {0};\n" " *s.p[0] = true;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:13]: (style) Boolean value assigned to floating point variable. [assignBoolToFloat]\n", errout_str()); } @@ -227,50 +227,50 @@ class TestBool : public TestFixture { check("void f(int x) {\n" " if ((x && 0x0f)==6)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:20]: (warning) Comparison of a boolean expression with an integer other than 0 or 1. [compareBoolExpressionWithInt]\n", errout_str()); check("void f(int x) {\n" " if ((x && 0x0f)==0)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" " if ((x || 0x0f)==6)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:20]: (warning) Comparison of a boolean expression with an integer other than 0 or 1. [compareBoolExpressionWithInt]\n", errout_str()); check("void f(int x) {\n" " if ((x || 0x0f)==0)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" " if ((x & 0x0f)==6)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" " if ((x | 0x0f)==6)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" " if ((5 && x)==3)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:17]: (warning) Comparison of a boolean expression with an integer other than 0 or 1. [compareBoolExpressionWithInt]\n", errout_str()); check("void f(int x) {\n" " if ((5 && x)==3 || (8 && x)==9)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:2:17]: (warning) Comparison of a boolean expression with an integer other than 0 or 1. [compareBoolExpressionWithInt]\n" "[test.cpp:2:32]: (warning) Comparison of a boolean expression with an integer other than 0 or 1. [compareBoolExpressionWithInt]\n", @@ -279,39 +279,39 @@ class TestBool : public TestFixture { check("void f(int x) {\n" " if ((5 && x)!=3)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:17]: (warning) Comparison of a boolean expression with an integer other than 0 or 1. [compareBoolExpressionWithInt]\n", errout_str()); check("void f(int x) {\n" " if ((5 && x) > 3)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:18]: (warning) Comparison of a boolean expression with an integer other than 0 or 1. [compareBoolExpressionWithInt]\n", errout_str()); check("void f(int x) {\n" " if ((5 && x) > 0)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" " if ((5 && x) < 0)\n" " a++;\n" - "}" + "}\n" ); ASSERT_EQUALS("[test.cpp:2:18]: (warning) Comparison of a boolean expression with an integer. [compareBoolExpressionWithInt]\n", errout_str()); check("void f(int x) {\n" " if ((5 && x) < 1)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" " if ((5 && x) > 1)\n" " a++;\n" - "}" + "}\n" ); ASSERT_EQUALS("[test.cpp:2:18]: (warning) Comparison of a boolean expression with an integer. [compareBoolExpressionWithInt]\n", errout_str()); @@ -319,91 +319,91 @@ class TestBool : public TestFixture { check("void f(int x) {\n" " if (0 < (5 && x))\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" " if (0 > (5 && x))\n" " a++;\n" - "}" + "}\n" ); ASSERT_EQUALS("[test.cpp:2:11]: (warning) Comparison of a boolean expression with an integer. [compareBoolExpressionWithInt]\n", errout_str()); check("void f(int x) {\n" " if (1 > (5 && x))\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" " if (1 < (5 && x))\n" " a++;\n" - "}" + "}\n" ); ASSERT_EQUALS("[test.cpp:2:11]: (warning) Comparison of a boolean expression with an integer. [compareBoolExpressionWithInt]\n", errout_str()); check("void f(bool x ) {\n" " if ( x > false )\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:12]: (warning) Comparison of a boolean value using relational operator (<, >, <= or >=). [comparisonOfBoolWithInvalidComparator]\n", errout_str()); check("void f(bool x ) {\n" " if ( false < x )\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:16]: (warning) Comparison of a boolean value using relational operator (<, >, <= or >=). [comparisonOfBoolWithInvalidComparator]\n", errout_str()); check("void f(bool x ) {\n" " if ( x < false )\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:12]: (warning) Comparison of a boolean value using relational operator (<, >, <= or >=). [comparisonOfBoolWithInvalidComparator]\n", errout_str()); check("void f(bool x ) {\n" " if ( false > x )\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:16]: (warning) Comparison of a boolean value using relational operator (<, >, <= or >=). [comparisonOfBoolWithInvalidComparator]\n", errout_str()); check("void f(bool x ) {\n" " if ( x >= false )\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:13]: (warning) Comparison of a boolean value using relational operator (<, >, <= or >=). [comparisonOfBoolWithInvalidComparator]\n", errout_str()); check("void f(bool x ) {\n" " if ( false >= x )\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:17]: (warning) Comparison of a boolean value using relational operator (<, >, <= or >=). [comparisonOfBoolWithInvalidComparator]\n", errout_str()); check("void f(bool x ) {\n" " if ( x <= false )\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:13]: (warning) Comparison of a boolean value using relational operator (<, >, <= or >=). [comparisonOfBoolWithInvalidComparator]\n", errout_str()); check("void f(bool x ) {\n" " if ( false <= x )\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:17]: (warning) Comparison of a boolean value using relational operator (<, >, <= or >=). [comparisonOfBoolWithInvalidComparator]\n", errout_str()); check("typedef int (*func)(bool invert);\n" "void x(int, func f);\n" "void foo(int error) {\n" " if (error == ABC) { }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); - check("int f() { return !a+b1){} }"); + check("void f() { if (!!a+!!b+!!c>1){} }\n"); ASSERT_EQUALS("",errout_str()); - check("void f(int a, int b, int c) { if (a != !b || c) {} }"); + check("void f(int a, int b, int c) { if (a != !b || c) {} }\n"); ASSERT_EQUALS("",errout_str()); - check("void f(int a, int b, int c) { if (1 < !!a + !!b + !!c) {} }"); + check("void f(int a, int b, int c) { if (1 < !!a + !!b + !!c) {} }\n"); ASSERT_EQUALS("",errout_str()); - check("void f(int a, int b, int c) { if (1 < !(a+b)) {} }"); + check("void f(int a, int b, int c) { if (1 < !(a+b)) {} }\n"); ASSERT_EQUALS("[test.cpp:1:37]: (warning) Comparison of a boolean expression with an integer. [compareBoolExpressionWithInt]\n",errout_str()); } void comparisonOfBoolExpressionWithInt3() { check("int f(int x) {\n" " return t<0>() && x;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -498,48 +498,48 @@ class TestBool : public TestFixture { // #5016 check("void f() {\n" " for(int i = 4; i > -1 < 5 ; --i) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:25]: (warning) Comparison of a boolean expression with an integer other than 0 or 1. [compareBoolExpressionWithInt]\n", errout_str()); check("void f(int a, int b, int c) {\n" " return (a > b) < c;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int a, int b, int c) {\n" " return x(a > b) < c;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int a, int b, int c) {\n" " return a > b == c;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // templates check("struct Tokenizer { TokenList list; };\n" "void Tokenizer::f() {\n" " std::list locationList;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #5063 - or check("void f() {\n" " return a > b or c < d;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int f() {\n" " return (a < b) != 0U;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int f() {\n" " return (a < b) != 0x0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int f() {\n" " return (a < b) != 42U;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:18]: (warning) Comparison of a boolean expression with an integer other than 0 or 1. [compareBoolExpressionWithInt]\n", errout_str()); } @@ -563,7 +563,7 @@ class TestBool : public TestFixture { " }\n" " else\n" " return true;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:17]: (style) Comparison of two functions returning boolean value using relational (<, >, <= or >=) operator. [comparisonOfTwoFuncsReturningBoolError]\n", errout_str()); } @@ -588,7 +588,7 @@ class TestBool : public TestFixture { " }\n" " else\n" " return false;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:12]: (style) Comparison of a function returning boolean value using relational (<, >, <= or >=) operator. [comparisonOfFuncReturningBoolError]\n" "[test.cpp:11:7]: (style) Comparison of a function returning boolean value using relational (<, >, <= or >=) operator. [comparisonOfFuncReturningBoolError]\n", errout_str()); } @@ -600,7 +600,7 @@ class TestBool : public TestFixture { " printf(\"foo\");\n" " }\n" "}\n" - "bool compare(int temp);"); + "bool compare(int temp);\n"); ASSERT_EQUALS("[test.cpp:3:19]: (warning) Comparison of a boolean expression with an integer other than 0 or 1. [compareBoolExpressionWithInt]\n" "[test.cpp:3:12]: (style) Comparison of a function returning boolean value using relational (<, >, <= or >=) operator. [comparisonOfFuncReturningBoolError]\n", errout_str()); } @@ -626,7 +626,7 @@ class TestBool : public TestFixture { " }\n" " else\n" " return false;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:13]: (style) Comparison of a function returning boolean value using relational (<, >, <= or >=) operator. [comparisonOfFuncReturningBoolError]\n", errout_str()); } @@ -650,7 +650,7 @@ class TestBool : public TestFixture { " }\n" " else\n" " return true;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:17]: (style) Comparison of two functions returning boolean value using relational (<, >, <= or >=) operator. [comparisonOfTwoFuncsReturningBoolError]\n", errout_str()); } @@ -664,7 +664,7 @@ class TestBool : public TestFixture { " if(compare1(temp) > compare2(temp)){\n" " printf(\"foo\");\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("namespace Foo {\n" @@ -676,7 +676,7 @@ class TestBool : public TestFixture { " if(compare1(temp) > compare2(temp)){\n" " printf(\"foo\");\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int compare1(int temp);\n" @@ -688,7 +688,7 @@ class TestBool : public TestFixture { " printf(\"foo\");\n" " }\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:20]: (style) Comparison of a function returning boolean value using relational (<, >, <= or >=) operator. [comparisonOfFuncReturningBoolError]\n", errout_str()); check("int compare1(int temp);\n" @@ -700,7 +700,7 @@ class TestBool : public TestFixture { " printf(\"foo\");\n" " }\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("bool compare1(int temp);\n" @@ -709,7 +709,7 @@ class TestBool : public TestFixture { " if(foo.compare1(temp) > compare2(temp)){\n" " printf(\"foo\");\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -739,7 +739,7 @@ class TestBool : public TestFixture { " if(b > a){ \n" // here warning should be displayed " ;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:10]: (style) Comparison of a variable having boolean value using relational (<, >, <= or >=) operator. [comparisonOfBoolWithBoolError]\n", errout_str()); // op: < check("int main(void){\n" @@ -748,7 +748,7 @@ class TestBool : public TestFixture { " if(b < a){ \n" // here warning should be displayed " ;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:10]: (style) Comparison of a variable having boolean value using relational (<, >, <= or >=) operator. [comparisonOfBoolWithBoolError]\n", errout_str()); // op: >= check("int main(void){\n" @@ -757,7 +757,7 @@ class TestBool : public TestFixture { " if(b >= a){ \n" // here warning should be displayed " ;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:10]: (style) Comparison of a variable having boolean value using relational (<, >, <= or >=) operator. [comparisonOfBoolWithBoolError]\n", errout_str()); // op: <= check("int main(void){\n" @@ -766,7 +766,7 @@ class TestBool : public TestFixture { " if(b <= a){ \n" // here warning should be displayed " ;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:10]: (style) Comparison of a variable having boolean value using relational (<, >, <= or >=) operator. [comparisonOfBoolWithBoolError]\n", errout_str()); } @@ -778,7 +778,7 @@ class TestBool : public TestFixture { " if ( eval(argv[1]) > eval(argv[2]) )\n" " return 1;\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -812,96 +812,96 @@ class TestBool : public TestFixture { void bitwiseOnBoolean() { // 3062 check("void f(_Bool a, _Bool b) {\n" " if(a & b) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:10]: (style, inconclusive) Boolean expression 'a' is used in bitwise operation. Did you mean '&&'? [bitwiseOnBoolean]\n", errout_str()); check("void f(_Bool a, _Bool b) {\n" " if(a | b) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:10]: (style, inconclusive) Boolean expression 'a' is used in bitwise operation. Did you mean '||'? [bitwiseOnBoolean]\n", errout_str()); check("void f(bool a, bool b) {\n" " if(a & !b) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:10]: (style, inconclusive) Boolean expression 'a' is used in bitwise operation. Did you mean '&&'? [bitwiseOnBoolean]\n", errout_str()); check("void f(bool a, bool b) {\n" " if(a | !b) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:10]: (style, inconclusive) Boolean expression 'a' is used in bitwise operation. Did you mean '||'? [bitwiseOnBoolean]\n", errout_str()); check("bool a, b;\n" "void f() {\n" " if(a & b) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style, inconclusive) Boolean expression 'a' is used in bitwise operation. Did you mean '&&'? [bitwiseOnBoolean]\n", errout_str()); check("bool a, b;\n" "void f() {\n" " if(a & !b) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style, inconclusive) Boolean expression 'a' is used in bitwise operation. Did you mean '&&'? [bitwiseOnBoolean]\n", errout_str()); check("bool a, b;\n" "void f() {\n" " if(a | b) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style, inconclusive) Boolean expression 'a' is used in bitwise operation. Did you mean '||'? [bitwiseOnBoolean]\n", errout_str()); check("bool a, b;\n" "void f() {\n" " if(a | !b) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style, inconclusive) Boolean expression 'a' is used in bitwise operation. Did you mean '||'? [bitwiseOnBoolean]\n", errout_str()); check("void f(bool a, int b) {\n" " if(a & b) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:10]: (style, inconclusive) Boolean expression 'a' is used in bitwise operation. Did you mean '&&'? [bitwiseOnBoolean]\n", errout_str()); check("void f(int a, bool b) {\n" " if(a & b) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:10]: (style, inconclusive) Boolean expression 'b' is used in bitwise operation. Did you mean '&&'? [bitwiseOnBoolean]\n", errout_str()); check("void f(int a, int b) {\n" " if((a > 0) & (b < 0)) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:16]: (style, inconclusive) Boolean expression 'a>0' is used in bitwise operation. Did you mean '&&'? [bitwiseOnBoolean]\n", errout_str()); check("void f(bool a, int b) {\n" " if(a | b) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:10]: (style, inconclusive) Boolean expression 'a' is used in bitwise operation. Did you mean '||'? [bitwiseOnBoolean]\n", errout_str()); check("void f(int a, bool b) {\n" " if(a | b) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:10]: (style, inconclusive) Boolean expression 'b' is used in bitwise operation. Did you mean '||'? [bitwiseOnBoolean]\n", errout_str()); check("int f(bool a, int b) {\n" " return a | b;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("bool f(bool a, int b) {\n" " return a | b;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:14]: (style, inconclusive) Boolean expression 'a' is used in bitwise operation. Did you mean '||'? [bitwiseOnBoolean]\n", errout_str()); check("void f(int a, int b) {\n" " if(a & b) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(bool b) {\n" " foo(bar, &b);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(bool b) {\n" // #9405 " class C { void foo(bool &b) {} };\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("bool f();\n" @@ -970,27 +970,27 @@ class TestBool : public TestFixture { void incrementBoolean() { check("bool bValue = true;\n" - "void f() { bValue++; }"); + "void f() { bValue++; }\n"); ASSERT_EQUALS("[test.cpp:2:12]: (style) Incrementing a variable of type 'bool' with postfix operator++ is deprecated by the C++ Standard. You should assign it the value 'true' instead. [incrementboolean]\n", errout_str()); check("void f(bool test){\n" " test++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:5]: (style) Incrementing a variable of type 'bool' with postfix operator++ is deprecated by the C++ Standard. You should assign it the value 'true' instead. [incrementboolean]\n", errout_str()); check("void f(bool* test){\n" " (*test)++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:6]: (style) Incrementing a variable of type 'bool' with postfix operator++ is deprecated by the C++ Standard. You should assign it the value 'true' instead. [incrementboolean]\n", errout_str()); check("void f(bool* test){\n" " test[0]++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9]: (style) Incrementing a variable of type 'bool' with postfix operator++ is deprecated by the C++ Standard. You should assign it the value 'true' instead. [incrementboolean]\n", errout_str()); check("void f(int test){\n" " test++;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -999,51 +999,51 @@ class TestBool : public TestFixture { " if (x < 10) {\n" " printf(\"foo\");\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11]: (warning) Comparison of a boolean expression with an integer other than 0 or 1. [compareBoolExpressionWithInt]\n", errout_str()); check("void f(bool x) {\n" " if (10 >= x) {\n" " printf(\"foo\");\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:12]: (warning) Comparison of a boolean expression with an integer other than 0 or 1. [compareBoolExpressionWithInt]\n", errout_str()); check("void f(bool x) {\n" " if (x != 0) {\n" " printf(\"foo\");\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(bool x) {\n" // #3356 " if (x == 1) {\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(bool x) {\n" " if (x != 10) {\n" " printf(\"foo\");\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11]: (warning) Comparison of a boolean expression with an integer other than 0 or 1. [compareBoolExpressionWithInt]\n", errout_str()); check("void f(bool x) {\n" " if (x == 10) {\n" " printf(\"foo\");\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11]: (warning) Comparison of a boolean expression with an integer other than 0 or 1. [compareBoolExpressionWithInt]\n", errout_str()); check("void f(bool x) {\n" " if (x == 0) {\n" " printf(\"foo\");\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); - check("DensePropertyMap visited;"); // #4075 + check("DensePropertyMap visited;\n"); // #4075 ASSERT_EQUALS("", errout_str()); } @@ -1052,28 +1052,28 @@ class TestBool : public TestFixture { " if (x == y) {\n" " printf(\"foo\");\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x, bool y) {\n" " if (x == y) {\n" " printf(\"foo\");\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(bool x, bool y) {\n" " if (x == y) {\n" " printf(\"foo\");\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(bool x, fooClass y) {\n" " if (x == y) {\n" " printf(\"foo\");\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1082,35 +1082,35 @@ class TestBool : public TestFixture { " if (y > false) {\n" " printf(\"foo\");\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:13]: (warning) Comparison of a boolean value using relational operator (<, >, <= or >=). [comparisonOfBoolWithInvalidComparator]\n", errout_str()); check("void f(int y) {\n" " if (true == y) {\n" " printf(\"foo\");\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(bool y) {\n" " if (y == true) {\n" " printf(\"foo\");\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(bool y) {\n" " if (false < 5) {\n" " printf(\"foo\");\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:15]: (warning) Comparison of a boolean expression with an integer other than 0 or 1. [compareBoolExpressionWithInt]\n", errout_str()); } void comparisonOfBoolWithInt4() { check("void f(int x) {\n" " if (!x == 1) { }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1118,21 +1118,21 @@ class TestBool : public TestFixture { check("void SetVisible(int index, bool visible) {\n" " bool (SciTEBase::*ischarforsel)(char ch);\n" " if (visible != GetVisible(index)) { }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } void comparisonOfBoolWithInt6() { // #4224 - integer is casted to bool check("void SetVisible(bool b, int i) {\n" " if (b == (bool)i) { }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } void comparisonOfBoolWithInt7() { // #4846 - (!x==true) check("void f(int x) {\n" " if (!x == true) { }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1142,7 +1142,7 @@ class TestBool : public TestFixture { " auto res = Fun();\n" " if (expectedResult == res)\n" " throw 2;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int Fun();\n" @@ -1150,7 +1150,7 @@ class TestBool : public TestFixture { " auto res = Fun();\n" " if (expectedResult == res)\n" " throw 2;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("bool Fun();\n" @@ -1158,7 +1158,7 @@ class TestBool : public TestFixture { " auto res = Fun();\n" " if (5 + expectedResult == res)\n" " throw 2;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int Fun();\n" @@ -1166,7 +1166,7 @@ class TestBool : public TestFixture { " auto res = Fun();\n" " if (5 + expectedResult == res)\n" " throw 2;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int Fun();\n" @@ -1174,7 +1174,7 @@ class TestBool : public TestFixture { " auto res = Fun();\n" " if (expectedResult == res + 5)\n" " throw 2;\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("error", "", errout_str()); } @@ -1185,7 +1185,7 @@ class TestBool : public TestFixture { " b = !b;\n" " }\n" " return b;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1210,76 +1210,76 @@ class TestBool : public TestFixture { void pointerArithBool1() { // #5126 check("void f(char *p) {\n" " if (p+1){}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:10]: (error) Converting pointer arithmetic result to bool. The bool is always true unless there is undefined behaviour. [pointerArithBool]\n", errout_str()); check("void f(char *p) {\n" " do {} while (p+1);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:19]: (error) Converting pointer arithmetic result to bool. The bool is always true unless there is undefined behaviour. [pointerArithBool]\n", errout_str()); check("void f(char *p) {\n" " while (p-1) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:13]: (error) Converting pointer arithmetic result to bool. The bool is always true unless there is undefined behaviour. [pointerArithBool]\n", errout_str()); check("void f(char *p) {\n" " for (int i = 0; p+1; i++) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:22]: (error) Converting pointer arithmetic result to bool. The bool is always true unless there is undefined behaviour. [pointerArithBool]\n", errout_str()); check("void f(char *p) {\n" " if (p && p+1){}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:15]: (error) Converting pointer arithmetic result to bool. The bool is always true unless there is undefined behaviour. [pointerArithBool]\n", errout_str()); check("void f(char *p) {\n" " if (p+2 || p) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:10]: (error) Converting pointer arithmetic result to bool. The bool is always true unless there is undefined behaviour. [pointerArithBool]\n", errout_str()); } void returnNonBool() { check("bool f(void) {\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("bool f(void) {\n" " return 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("bool f(void) {\n" " return 2;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:5]: (style) Non-boolean value returned from function returning bool [returnNonBoolInBooleanFunction]\n", errout_str()); check("bool f(void) {\n" " return -1;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:5]: (style) Non-boolean value returned from function returning bool [returnNonBoolInBooleanFunction]\n", errout_str()); check("bool f(void) {\n" " return 1 + 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:5]: (style) Non-boolean value returned from function returning bool [returnNonBoolInBooleanFunction]\n", errout_str()); check("bool f(void) {\n" " int x = 0;\n" " return x;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("bool f(void) {\n" " int x = 10;\n" " return x;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (style) Non-boolean value returned from function returning bool [returnNonBoolInBooleanFunction]\n", errout_str()); check("bool f(void) {\n" " return 2 < 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("bool f(void) {\n" @@ -1287,7 +1287,7 @@ class TestBool : public TestFixture { " if (a)\n" " ret = 1;\n" " return ret;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("bool f(void) {\n" @@ -1295,20 +1295,20 @@ class TestBool : public TestFixture { " if (a)\n" " ret = 3;\n" " return ret;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:5]: (style) Non-boolean value returned from function returning bool [returnNonBoolInBooleanFunction]\n", errout_str()); check("bool f(void) {\n" " if (a)\n" " return 3;\n" " return 4;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:9]: (style) Non-boolean value returned from function returning bool [returnNonBoolInBooleanFunction]\n" "[test.cpp:4:5]: (style) Non-boolean value returned from function returning bool [returnNonBoolInBooleanFunction]\n", errout_str()); check("bool f(void) {\n" " return;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1316,42 +1316,42 @@ class TestBool : public TestFixture { check("bool f(void) {\n" " auto x = [](void) { return -1; };\n" " return false;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("bool f(void) {\n" " auto x = [](void) { return -1; };\n" " return 2;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (style) Non-boolean value returned from function returning bool [returnNonBoolInBooleanFunction]\n", errout_str()); check("bool f(void) {\n" " auto x = [](void) -> int { return -1; };\n" " return false;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("bool f(void) {\n" " auto x = [](void) -> int { return -1; };\n" " return 2;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (style) Non-boolean value returned from function returning bool [returnNonBoolInBooleanFunction]\n", errout_str()); } void returnNonBoolLogicalOp() { check("bool f(int x) {\n" " return x & 0x4;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("bool f(int x, int y) {\n" " return x | y;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("bool f(int x) {\n" " return (x & 0x2);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1359,7 +1359,7 @@ class TestBool : public TestFixture { check("class X {\n" " public:\n" " bool f() { return -1;}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:20]: (style) Non-boolean value returned from function returning bool [returnNonBoolInBooleanFunction]\n", errout_str()); check("bool f() {\n" @@ -1368,7 +1368,7 @@ class TestBool : public TestFixture { " int f() { return -1;}\n" " };\n" " return false;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("bool f() {\n" @@ -1377,7 +1377,7 @@ class TestBool : public TestFixture { " int f() { return -1;}\n" " };\n" " return false;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("bool f() {\n" @@ -1386,7 +1386,7 @@ class TestBool : public TestFixture { " bool f() { return -1;}\n" " };\n" " return -1;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:5]: (style) Non-boolean value returned from function returning bool [returnNonBoolInBooleanFunction]\n" "[test.cpp:4:24]: (style) Non-boolean value returned from function returning bool [returnNonBoolInBooleanFunction]\n", errout_str()); } diff --git a/test/testbufferoverrun.cpp b/test/testbufferoverrun.cpp index 7118a7ae906..7771d294d2c 100644 --- a/test/testbufferoverrun.cpp +++ b/test/testbufferoverrun.cpp @@ -56,8 +56,8 @@ class TestBufferOverrun : public TestFixture { SimpleTokenizer tokenizer(settings, *this, cpp); ASSERT_LOC(tokenizer.tokenize(code), file, line); - // Check for buffer overruns.. - runChecks(tokenizer, this); + CheckBufferOverrun check; + runChecks(check, tokenizer, *this); } // TODO: get rid of this @@ -66,8 +66,8 @@ class TestBufferOverrun : public TestFixture { SimpleTokenizer tokenizer(settings0_i, *this); ASSERT_LOC(tokenizer.tokenize(code), file, line); - // Check for buffer overruns.. - runChecks(tokenizer, this); + CheckBufferOverrun check; + runChecks(check, tokenizer, *this); } #define checkP(...) checkP_(__FILE__, __LINE__, __VA_ARGS__) @@ -79,8 +79,8 @@ class TestBufferOverrun : public TestFixture { // Tokenizer.. ASSERT_LOC(tokenizer.simplifyTokens1(""), file, line); - // Check for buffer overruns.. - runChecks(tokenizer, this); + CheckBufferOverrun check; + runChecks(check, tokenizer, *this); } void run() override { @@ -164,6 +164,7 @@ class TestBufferOverrun : public TestFixture { TEST_CASE(array_index_74); // #11088 TEST_CASE(array_index_75); TEST_CASE(array_index_76); + TEST_CASE(array_index_77); TEST_CASE(array_index_multidim); TEST_CASE(array_index_switch_in_for); TEST_CASE(array_index_for_in_for); // FP: #2634 @@ -197,6 +198,7 @@ class TestBufferOverrun : public TestFixture { TEST_CASE(array_index_function_parameter); TEST_CASE(array_index_enum_array); // #8439 TEST_CASE(array_index_container); // #9386 + TEST_CASE(array_index_container_data); // pointer from data()/c_str() carries the container size TEST_CASE(array_index_two_for_loops); TEST_CASE(array_index_new); // #7690 @@ -226,6 +228,9 @@ class TestBufferOverrun : public TestFixture { TEST_CASE(buffer_overrun_34); //#11035 TEST_CASE(buffer_overrun_35); //#2304 TEST_CASE(buffer_overrun_36); + TEST_CASE(buffer_overrun_37); + TEST_CASE(buffer_overrun_38); + TEST_CASE(buffer_overrun_39); TEST_CASE(buffer_overrun_errorpath); TEST_CASE(buffer_overrun_bailoutIfSwitch); // ticket #2378 : bailoutIfSwitch TEST_CASE(buffer_overrun_function_array_argument); @@ -264,6 +269,7 @@ class TestBufferOverrun : public TestFixture { TEST_CASE(minsize_sizeof); TEST_CASE(minsize_strlen); TEST_CASE(minsize_mul); + TEST_CASE(minsize_overflow); TEST_CASE(unknownType); TEST_CASE(terminateStrncpy1); @@ -324,7 +330,7 @@ class TestBufferOverrun : public TestFixture { " {\n" " char str[50];\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -346,7 +352,7 @@ class TestBufferOverrun : public TestFixture { "void f4(const char str[])\n" "{\n" " strcpy(buf, str);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -357,7 +363,7 @@ class TestBufferOverrun : public TestFixture { "{\n" " char data[1];\n" " return abc.data[1];\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -367,7 +373,7 @@ class TestBufferOverrun : public TestFixture { check("static void f() {\n" " char data[100];\n" " const char *p = data + 100;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -379,7 +385,7 @@ class TestBufferOverrun : public TestFixture { " snprintf(group, 32, \"%u\", 0);\n" " struct group *gr;\n" " snprintf(group, 32, \"%u\", gr->gr_gid);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -389,14 +395,14 @@ class TestBufferOverrun : public TestFixture { " char str[0x10] = {0};\n" " str[15] = 0;\n" " str[16] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:8]: (error) Array 'str[16]' accessed at index 16, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("char f()\n" "{\n" " char str[16] = {0};\n" " return str[16];\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:15]: (error) Array 'str[16]' accessed at index 16, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); // test stack array @@ -406,7 +412,7 @@ class TestBufferOverrun : public TestFixture { " int y;\n" " y = x[ 4 ];\n" " return y;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:9]: (error) Array 'x[3]' accessed at index 4, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("int f()\n" @@ -415,15 +421,15 @@ class TestBufferOverrun : public TestFixture { " int y;\n" " y = x[ 2 ];\n" " return y;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int x[5] = {0};\n" - "int a = x[10];"); + "int a = x[10];\n"); ASSERT_EQUALS("[test.cpp:2:10]: (error) Array 'x[5]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("int x[5] = {0};\n" - "int a = (x)[10];"); + "int a = (x)[10];\n"); ASSERT_EQUALS("[test.cpp:2:12]: (error) Array 'x[5]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } @@ -434,21 +440,21 @@ class TestBufferOverrun : public TestFixture { " char *str = new char[0x10];\n" " str[i] = 0;\n" "}\n" - "void b() { a(16); }"); + "void b() { a(16); }\n"); ASSERT_EQUALS("[test.cpp:4:8]: (error) Array 'str[16]' accessed at index 16, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } void array_index_4() { - check("char c = \"abc\"[4];"); + check("char c = \"abc\"[4];\n"); ASSERT_EQUALS("[test.cpp:1:15]: (error) Array '\"abc\"[4]' accessed at index 4, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); - check("p = &\"abc\"[4];"); + check("p = &\"abc\"[4];\n"); ASSERT_EQUALS("", errout_str()); - check("char c = \"\\0abc\"[2];"); + check("char c = \"\\0abc\"[2];\n"); ASSERT_EQUALS("", errout_str()); - check("char c = L\"abc\"[4];"); + check("char c = L\"abc\"[4];\n"); ASSERT_EQUALS("[test.cpp:1:16]: (error) Array 'L\"abc\"[4]' accessed at index 4, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } @@ -459,7 +465,7 @@ class TestBufferOverrun : public TestFixture { " int i, sum=0;\n" " for (i = 0; i < 100; i++)\n" " sum += val[i];\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:19]: (error) Array 'val[50]' accessed at index 99, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void f()\n" @@ -468,7 +474,7 @@ class TestBufferOverrun : public TestFixture { " int i, sum=0;\n" " for (i = 1; i < 100; i++)\n" " sum += val[i];\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:19]: (error) Array 'val[50]' accessed at index 99, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void f(int a)\n" @@ -477,7 +483,7 @@ class TestBufferOverrun : public TestFixture { " int i, sum=0;\n" " for (i = a; i < 100; i++)\n" " sum += val[i];\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:19]: (error) Array 'val[50]' accessed at index 99, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("typedef struct g g2[3];\n" @@ -487,21 +493,21 @@ class TestBufferOverrun : public TestFixture { " {\n" " a[i]=0;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int argc)\n" "{\n" " char a[2];\n" " for (int i = 4; i < argc; i++){}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int a[10]) {\n" " for (int i=0;i<50;++i) {\n" " a[i] = 0;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:10]: (error) Array 'a[10]' accessed at index 49, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } @@ -515,7 +521,7 @@ class TestBufferOverrun : public TestFixture { "{\n" " struct ABC abc;\n" " abc.str[10] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:9:12]: (error) Array 'abc.str[10]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("struct ABC\n" @@ -527,7 +533,7 @@ class TestBufferOverrun : public TestFixture { "{\n" " struct ABC abc;\n" " return abc.str[10];\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:9:19]: (error) Array 'abc.str[10]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); // This is not out of bounds because it is a variable length array @@ -540,7 +546,7 @@ class TestBufferOverrun : public TestFixture { "{\n" " struct ABC* x = malloc(sizeof(struct ABC) + 10);\n" " x->str[1] = 0;" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // This is not out of bounds because it is not a variable length array @@ -554,7 +560,7 @@ class TestBufferOverrun : public TestFixture { "{\n" " struct ABC* x = malloc(sizeof(struct ABC) + 10);\n" " x->str[1] = 0;" - "}"); + "}\n"); TODO_ASSERT_EQUALS("error", "", errout_str()); // This is not out of bounds because it is a variable length array @@ -568,8 +574,8 @@ class TestBufferOverrun : public TestFixture { "static void f()\n" "{\n" " struct ABC* x = malloc(sizeof(struct ABC) + 10);\n" - " x->str[10] = 0;" - "}"); + " x->str[10] = 0;\n" + "}\n"); ASSERT_EQUALS("", errout_str()); // This is out of bounds because it is outside the memory allocated. @@ -582,8 +588,8 @@ class TestBufferOverrun : public TestFixture { "static void f()\n" "{\n" " struct ABC* x = malloc(sizeof(struct ABC) + 10);\n" - " x->str[11] = 0;" - "}"); + " x->str[11] = 0;\n" + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:9]: (error) Array 'str[1]' accessed at index 11, which is out of bounds.\n", "", errout_str()); // This is out of bounds if 'sizeof(ABC)' is 1 (No padding) @@ -595,8 +601,8 @@ class TestBufferOverrun : public TestFixture { "static void f()\n" "{\n" " struct ABC* x = malloc(sizeof(ABC) + 10);\n" - " x->str[11] = 0;" - "}"); + " x->str[11] = 0;\n" + "}\n"); TODO_ASSERT_EQUALS("error", "", errout_str()); // This is out of bounds because it is outside the memory allocated @@ -609,8 +615,8 @@ class TestBufferOverrun : public TestFixture { "static void f()\n" "{\n" " struct ABC* x = malloc(sizeof(struct ABC));\n" - " x->str[1] = 0;" - "}"); + " x->str[1] = 0;\n" + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:9]: (error) Array 'str[1]' accessed at index 1, which is out of bounds.\n", "", errout_str()); // This is out of bounds because it is outside the memory allocated @@ -623,8 +629,8 @@ class TestBufferOverrun : public TestFixture { "static void f()\n" "{\n" " struct ABC* x = malloc(sizeof(ABC));\n" - " x->str[1] = 0;" - "}"); + " x->str[1] = 0;\n" + "}\n"); TODO_ASSERT_EQUALS("error", "", errout_str()); // This is out of bounds because it is not a variable array @@ -636,8 +642,8 @@ class TestBufferOverrun : public TestFixture { "static void f()\n" "{\n" " struct ABC x;\n" - " x.str[1] = 0;" - "}"); + " x.str[1] = 0;\n" + "}\n"); ASSERT_EQUALS("[test.cpp:9:10]: (error) Array 'x.str[1]' accessed at index 1, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("struct foo\n" @@ -650,19 +656,19 @@ class TestBufferOverrun : public TestFixture { " foo f;\n" " for ( unsigned int i = 0; i < 64; ++i )\n" " f.str[i] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:10:14]: (error) Array 'f.str[10]' accessed at index 63, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("struct AB { char a[NUM]; char b[NUM]; }\n" "void f(struct AB *ab) {\n" " ab->a[0] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("union { char a[1]; int b; } ab;\n" "void f() {\n" " ab.a[2] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:9]: (error) Array 'ab.a[1]' accessed at index 2, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } @@ -676,7 +682,7 @@ class TestBufferOverrun : public TestFixture { "static void f(struct ABC *abc)\n" "{\n" " abc->str[10] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:13]: (error) Array 'abc->str[10]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } @@ -695,7 +701,7 @@ class TestBufferOverrun : public TestFixture { " {\n" " abc->str[10] = 0;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:13:17]: (error) Array 'abc->str[10]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } @@ -710,7 +716,7 @@ class TestBufferOverrun : public TestFixture { "Fred::Fred()\n" "{\n" " str[10] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:10:8]: (error) Array 'str[10]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("class Fred\n" @@ -723,7 +729,7 @@ class TestBufferOverrun : public TestFixture { "char Fred::c()\n" "{\n" " return str[10];\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:10:15]: (error) Array 'str[10]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } @@ -736,7 +742,7 @@ class TestBufferOverrun : public TestFixture { " if (i < 10)\n" " int x = buf[i];\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -746,7 +752,7 @@ class TestBufferOverrun : public TestFixture { " int a[10];\n" " for (int i = 0; i < 10; i++)\n" " a[i+10] = i;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:10]: (error) Array 'a[10]' accessed at index 19, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } @@ -756,7 +762,7 @@ class TestBufferOverrun : public TestFixture { " int a[10];\n" " for (int i = 0; i < 10; i++)\n" " a[10+i] = i;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:10]: (error) Array 'a[10]' accessed at index 19, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } @@ -766,7 +772,7 @@ class TestBufferOverrun : public TestFixture { " int a[10];\n" " for (int i = 0; i < 10; i++)\n" " a[i+1] = i;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:10]: (error) Array 'a[10]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } @@ -776,7 +782,7 @@ class TestBufferOverrun : public TestFixture { " int a[10];\n" " for (int i = 0; i < 10; i++)\n" " a[i*2] = i;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:10]: (error) Array 'a[10]' accessed at index 18, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void f()\n" @@ -784,7 +790,7 @@ class TestBufferOverrun : public TestFixture { " int a[12];\n" " for (int i = 0; i < 12; i+=6)\n" " a[i+5] = i;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f()\n" @@ -792,21 +798,21 @@ class TestBufferOverrun : public TestFixture { " int a[12];\n" " for (int i = 0; i < 12; i+=6)\n" " a[i+6] = i;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:10]: (error) Array 'a[12]' accessed at index 12, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void f() {\n" // #4398 " int a[2];\n" " for (int i = 0; i < 4; i+=2)\n" " a[i] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:10]: (error) Array 'a[2]' accessed at index 2, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void f() {\n" // #4398 " int a[2];\n" " for (int i = 0; i < 4; i+=2)\n" " do_stuff(a+i);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -819,7 +825,7 @@ class TestBufferOverrun : public TestFixture { " a[i] = i;\n" " i+=1;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f()\n" @@ -830,7 +836,7 @@ class TestBufferOverrun : public TestFixture { " a[i] = i;\n" " i++;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f()\n" @@ -841,7 +847,7 @@ class TestBufferOverrun : public TestFixture { " a[i] = i;\n" " ++i;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f()\n" @@ -852,7 +858,7 @@ class TestBufferOverrun : public TestFixture { " a[i] = i;\n" " i=4;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:10]: (error) Array 'a[5]' accessed at index 5, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void f()\n" @@ -863,7 +869,7 @@ class TestBufferOverrun : public TestFixture { " a[i] = i;\n" " i+=1;\n" " }\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:6]: (error) Buffer overrun\n", "", errout_str()); } @@ -873,7 +879,7 @@ class TestBufferOverrun : public TestFixture { "{\n" " char a[2];\n" " char *end = &(a[2]);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Getting more than one past the end is not legal @@ -881,7 +887,7 @@ class TestBufferOverrun : public TestFixture { "{\n" " char a[2];\n" " char *end = &(a[3]);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:18]: (error) Array 'a[2]' accessed at index 3, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } @@ -892,7 +898,7 @@ class TestBufferOverrun : public TestFixture { " int b[10];\n" " for ( int i = 0; i < 9; i++ )\n" " b[i] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -906,7 +912,7 @@ class TestBufferOverrun : public TestFixture { " for(int j=0; j<3; ++j) {\n" " int b = indices[j];\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -914,7 +920,7 @@ class TestBufferOverrun : public TestFixture { check("int main() {\n" " size_t indices[2];\n" " int b = indices[2];\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:18]: (error) Array 'indices[2]' accessed at index 2, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } @@ -923,7 +929,7 @@ class TestBufferOverrun : public TestFixture { "{\n" " char c[10];\n" " c[1<<23]='a';\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:6]: (error) Array 'c[10]' accessed at index 8388608, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } @@ -942,7 +948,7 @@ class TestBufferOverrun : public TestFixture { " int a[n];\n" // n <= SCHAR_MAX " a[-1] = 0;\n" // negative index " a[128] = 0;\n" // 128 > SCHAR_MAX - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:6]: (error) Array 'a[128]' accessed at index -1, which is out of bounds. [negativeIndex]\n" "[test.cpp:4:6]: (error) Array 'a[128]' accessed at index 128, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); @@ -950,7 +956,7 @@ class TestBufferOverrun : public TestFixture { " int a[n];\n" // n <= UCHAR_MAX " a[-1] = 0;\n" // negative index " a[256] = 0;\n" // 256 > UCHAR_MAX - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:6]: (error) Array 'a[256]' accessed at index -1, which is out of bounds. [negativeIndex]\n" "[test.cpp:4:6]: (error) Array 'a[256]' accessed at index 256, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); @@ -958,7 +964,7 @@ class TestBufferOverrun : public TestFixture { " int a[n];\n" // n <= SHRT_MAX " a[-1] = 0;\n" // negative index " a[32768] = 0;\n" // 32768 > SHRT_MAX - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:6]: (error) Array 'a[32768]' accessed at index -1, which is out of bounds. [negativeIndex]\n" "[test.cpp:4:6]: (error) Array 'a[32768]' accessed at index 32768, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); @@ -966,7 +972,7 @@ class TestBufferOverrun : public TestFixture { " int a[n];\n" // n <= USHRT_MAX " a[-1] = 0;\n" // negative index " a[65536] = 0;\n" // 65536 > USHRT_MAX - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:6]: (error) Array 'a[65536]' accessed at index -1, which is out of bounds. [negativeIndex]\n" "[test.cpp:4:6]: (error) Array 'a[65536]' accessed at index 65536, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); @@ -974,26 +980,26 @@ class TestBufferOverrun : public TestFixture { " int a[n];\n" // n <= SHRT_MAX " a[-1] = 0;\n" // negative index " a[32768] = 0;\n" // 32768 > SHRT_MAX - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:6]: (error) Array 'a[32768]' accessed at index -1, which is out of bounds. [negativeIndex]\n" "[test.cpp:4:6]: (error) Array 'a[32768]' accessed at index 32768, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void f(int n) {\n" " int a[n];\n" // n <= INT_MAX " a[-1] = 0;\n" // negative index - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:6]: (error) Array 'a[2147483648]' accessed at index -1, which is out of bounds. [negativeIndex]\n", errout_str()); check("void f(unsigned int n) {\n" " int a[n];\n" // n <= UINT_MAX " a[-1] = 0;\n" // negative index - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:6]: (error) Array 'a[4294967296]' accessed at index -1, which is out of bounds. [negativeIndex]\n", errout_str()); check("void f(signed int n) {\n" " int a[n];\n" // n <= INT_MAX " a[-1] = 0;\n" // negative index - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:6]: (error) Array 'a[2147483648]' accessed at index -1, which is out of bounds. [negativeIndex]\n", errout_str()); } @@ -1001,7 +1007,7 @@ class TestBufferOverrun : public TestFixture { check("void foo()\n" "{\n" " long l[SOME_SIZE];\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1011,7 +1017,7 @@ class TestBufferOverrun : public TestFixture { " int a[3];\n" " for (int i = 3; 0 <= i; i--)\n" " a[i] = i;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:10]: (error) Array 'a[3]' accessed at index 3, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void f()\n" @@ -1019,7 +1025,7 @@ class TestBufferOverrun : public TestFixture { " int a[4];\n" " for (int i = 3; 0 <= i; i--)\n" " a[i] = i;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1029,7 +1035,7 @@ class TestBufferOverrun : public TestFixture { " int a[10];\n" " for (int i = 0; i < 10; i++)\n" " a[i-1] = a[i];\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:10]: (error) Array 'a[10]' accessed at index -1, which is out of bounds. [negativeIndex]\n", errout_str()); } @@ -1040,7 +1046,7 @@ class TestBufferOverrun : public TestFixture { " int i[2];\n" " int *ip = i + 1;\n" " ip[-10] = 1;\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:5]: (error) Array ip[-10] out of bounds.\n", "", errout_str()); } @@ -1052,7 +1058,7 @@ class TestBufferOverrun : public TestFixture { " int *i = iBuf + 9;" " int *ii = i + -5;" " ii[10] = 0;" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:6]: (error) Array ii[10] out of bounds.\n", "", errout_str()); } @@ -1062,7 +1068,7 @@ class TestBufferOverrun : public TestFixture { check("void f() {\n" " UINT8 x[2];\n" " x[5] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:6]: (error) Array 'x[2]' accessed at index 5, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } @@ -1079,7 +1085,7 @@ class TestBufferOverrun : public TestFixture { "void y() {\n" " struct s1 obj;\n" " x(obj.delay, 123);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct s1 {\n" @@ -1093,7 +1099,7 @@ class TestBufferOverrun : public TestFixture { "void y() {\n" " struct s1 obj;\n" " x(obj.delay, 123);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:11] -> [test.cpp:6]: (error) Array 'obj.delay[3]' accessed at index 4, which is out of bounds.\n", "", errout_str()); @@ -1104,7 +1110,7 @@ class TestBufferOverrun : public TestFixture { "\n" "void f() {\n" " struct s1 *obj;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1118,14 +1124,14 @@ class TestBufferOverrun : public TestFixture { " m_x[1] = 0;\n" " }\n" " int m_x[1];\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:7:11]: (error) Array 'm_x[1]' accessed at index 1, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } void array_index_33() { check("void foo(char bar[][4]) {\n" " baz(bar[5]);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1134,7 +1140,7 @@ class TestBufferOverrun : public TestFixture { " int y[2][2][2];\n" " y[0][2][0] = 0;\n" " y[0][0][2] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:6]: (error) Array 'y[2][2][2]' accessed at index y[0][2][0], which is out of bounds. [arrayIndexOutOfBounds]\n" "[test.cpp:4:6]: (error) Array 'y[2][2][2]' accessed at index y[0][0][2], which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); @@ -1154,7 +1160,7 @@ class TestBufferOverrun : public TestFixture { " ptest->a[10] = 3;\n" " ptest->b[10][2] = 4;\n" " ptest->b[0][19] = 4;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:9:11]: (error) Array 'test.a[10]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]\n" "[test.cpp:10:11]: (error) Array 'test.b[10][5]' accessed at index test.b[10][2], which is out of bounds. [arrayIndexOutOfBounds]\n" "[test.cpp:11:11]: (error) Array 'test.b[10][5]' accessed at index test.b[0][19], which is out of bounds. [arrayIndexOutOfBounds]\n" @@ -1175,7 +1181,7 @@ class TestBufferOverrun : public TestFixture { " ptest = &test;\n" " ptest->a[9][5] = 4;\n" " ptest->a[0][50] = 4;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:11]: (error) Array 'test.a[10][5]' accessed at index test.a[9][5], which is out of bounds. [arrayIndexOutOfBounds]\n" "[test.cpp:9:11]: (error) Array 'test.a[10][5]' accessed at index test.a[0][50], which is out of bounds. [arrayIndexOutOfBounds]\n" "[test.cpp:12:13]: (error) Array 'ptest->a[10][5]' accessed at index ptest->a[9][5], which is out of bounds. [arrayIndexOutOfBounds]\n" @@ -1186,21 +1192,21 @@ class TestBufferOverrun : public TestFixture { check("void f() {\n" " struct Struct { unsigned m_Var[1]; } s;\n" " s.m_Var[1] = 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:12]: (error) Array 's.m_Var[1]' accessed at index 1, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("struct Struct { unsigned m_Var[1]; };\n" "void f() {\n" " struct Struct s;\n" " s.m_Var[1] = 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:12]: (error) Array 's.m_Var[1]' accessed at index 1, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("struct Struct { unsigned m_Var[1]; };\n" "void f() {\n" " struct Struct * s = calloc(40);\n" " s->m_Var[1] = 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1212,7 +1218,7 @@ class TestBufferOverrun : public TestFixture { "};\n" "Fred::Fred(const Fred & rhs) {\n" " m_b[2] = rhs.m_b[2];\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:8]: (error) Array 'm_b[2]' accessed at index 2, which is out of bounds. [arrayIndexOutOfBounds]\n" "[test.cpp:7:21]: (error) Array 'rhs.m_b[2]' accessed at index 2, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } @@ -1224,7 +1230,7 @@ class TestBufferOverrun : public TestFixture { " for (unsigned int i = 0; i < 15; i++)\n" " i;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1243,7 +1249,7 @@ class TestBufferOverrun : public TestFixture { " }\n" " }\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1252,7 +1258,7 @@ class TestBufferOverrun : public TestFixture { "{\n" " char a[10];\n" " a[10] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:6]: (error) Array 'a[10]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } @@ -1261,7 +1267,7 @@ class TestBufferOverrun : public TestFixture { " char a[10];\n" " for (int i = 0; i < 10; ++i)\n" " f2(&a[i + 1]);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1274,7 +1280,7 @@ class TestBufferOverrun : public TestFixture { "\n" "void b() {\n" " struct Fred { char data[3]; } fred;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void a() {\n" @@ -1285,7 +1291,7 @@ class TestBufferOverrun : public TestFixture { "void b() {\n" " struct Fred { char data[3]; } fred;\n" " fred.data[4] = 0;\n" // <- error - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:14]: (error) Array 'fred.data[3]' accessed at index 4, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } @@ -1296,7 +1302,7 @@ class TestBufferOverrun : public TestFixture { " char *p; p = (char *)malloc(10);\n" " p[10] = 7;\n" " free(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:4]: (error) Array 'p[10]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void f()\n" @@ -1304,7 +1310,7 @@ class TestBufferOverrun : public TestFixture { " float *p; p = (float *)malloc(10 * sizeof(float));\n" " p[10] = 7;\n" " free(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:4]: (error) Array 'p[10]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void f()\n" @@ -1313,7 +1319,7 @@ class TestBufferOverrun : public TestFixture { " p[0] = 0;\n" " p[9] = 9;\n" " free(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f()\n" @@ -1322,7 +1328,7 @@ class TestBufferOverrun : public TestFixture { " p[0] = 0;\n" " p[9] = 9;\n" " delete [] p;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f()\n" @@ -1331,7 +1337,7 @@ class TestBufferOverrun : public TestFixture { " p[0] = 0;\n" " p[9] = 9;\n" " delete [] p;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f()\n" @@ -1342,11 +1348,11 @@ class TestBufferOverrun : public TestFixture { " }\n" " catch(...){\n" " return;\n" - " }" + " }\n" " p[0] = 0;\n" " p[9] = 9;\n" " delete [] p;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1363,7 +1369,7 @@ class TestBufferOverrun : public TestFixture { " var[ 0 ].arr[ 2 ] = 2;\n" " y = var[ 0 ].arr[ 3 ];\n" // <-- array access out of bounds " return y;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:10:20]: (error) Array 'var[0].arr[3]' accessed at index 3, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("int f( )\n" @@ -1377,7 +1383,7 @@ class TestBufferOverrun : public TestFixture { " var[ 0 ].arr[ 2 ] = 2;\n" " y = var[ 0 ].arr[ 2 ];\n" " return y;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); @@ -1393,7 +1399,7 @@ class TestBufferOverrun : public TestFixture { "var.arr[ 3 ] = 3;\n" // <-- array access out of bounds "y=var.arr[ 3 ];\n" // <-- array access out of bounds "return y;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:10:8]: (error) Array 'var.arr[3]' accessed at index 3, which is out of bounds. [arrayIndexOutOfBounds]\n" "[test.cpp:11:10]: (error) Array 'var.arr[3]' accessed at index 3, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); @@ -1407,7 +1413,7 @@ class TestBufferOverrun : public TestFixture { "var[0].var[ 1 ] = 1;\n" "var[0].var[ 2 ] = 2;\n" "var[0].var[ 4 ] = 4;\n" // <-- array access out of bounds - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:9:11]: (error) Array 'var[0].var[3]' accessed at index 4, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void f( ) {\n" @@ -1418,7 +1424,7 @@ class TestBufferOverrun : public TestFixture { "var[0].var[ 0 ] = 0;\n" "var[0].var[ 1 ] = 1;\n" "var[0].var[ 2 ] = 2;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // avoid FPs (modified examples taken from #3838) @@ -1427,7 +1433,7 @@ class TestBufferOverrun : public TestFixture { " struct AB ab;\n" " int * p = &ab.a[10];\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct AB { int a[10]; int b[10]; };\n" @@ -1435,7 +1441,7 @@ class TestBufferOverrun : public TestFixture { " struct AB ab[1];\n" " int * p = &ab[0].a[10];\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct AB { int a[10]; int b[10]; };\n" @@ -1443,7 +1449,7 @@ class TestBufferOverrun : public TestFixture { " struct AB ab[1];\n" " int * p = &ab[10].a[0];\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:18]: (error) Array 'ab[1]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } @@ -1457,7 +1463,7 @@ class TestBufferOverrun : public TestFixture { " {\n" " buf[i] = 1;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f()\n" @@ -1467,7 +1473,7 @@ class TestBufferOverrun : public TestFixture { " {\n" " buf[i] = 2.;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1480,7 +1486,7 @@ class TestBufferOverrun : public TestFixture { "void test() {\n" " CHAR buffer[1024];\n" " f(\"%s\", buffer);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Unnamed argument @@ -1490,7 +1496,7 @@ class TestBufferOverrun : public TestFixture { "void test() {\n" " char buffer[1024];\n" " f(buffer);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1502,7 +1508,7 @@ class TestBufferOverrun : public TestFixture { " for(int i=3; i--;) {\n" " printf(\"files(%i): %s\", 3-i, buffer[3-i]);\n" " }\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:4]: (error) Array 'buffer[3]' accessed at index 3, which is out of bounds.\n", "", errout_str()); check("void f() {\n" @@ -1511,7 +1517,7 @@ class TestBufferOverrun : public TestFixture { " for(i=10; i--;) {\n" " buffer[i] = i;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:15]: (error) Array 'buffer[9]' accessed at index 9, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); // Correct access limits -> i from 9 to 0 @@ -1520,7 +1526,7 @@ class TestBufferOverrun : public TestFixture { " for(unsigned long int i=10; i--;) {\n" " buffer[i] = i;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1530,7 +1536,7 @@ class TestBufferOverrun : public TestFixture { "void f() {\n" " for (int i = 2; i < 0; i++)\n" " s[i] = 5;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1543,7 +1549,7 @@ class TestBufferOverrun : public TestFixture { " printf(\" %i\", i);\n" " array[i] = 0;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:14]: (error) Array 'array[4]' accessed at index 4, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void test(void)\n" @@ -1553,7 +1559,7 @@ class TestBufferOverrun : public TestFixture { " scanf(\"%i\", &i);\n" " array[i] = 0;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1567,7 +1573,7 @@ class TestBufferOverrun : public TestFixture { " i++;\n" " }\n" " arr[k];\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1579,7 +1585,7 @@ class TestBufferOverrun : public TestFixture { "void g() {\n" " f(\"12345678\");\n" " f(\"12345\");\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1587,7 +1593,7 @@ class TestBufferOverrun : public TestFixture { check("void f(void){\n" " int k=0, dd, d[1U] = {1};\n" " for (dd=d[k]; k<10; dd=d[++k]){;}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:29]: (error) Array 'd[1]' accessed at index 1, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } @@ -1598,7 +1604,7 @@ class TestBufferOverrun : public TestFixture { " for(int i = 0, j= 11; i < j; ++i)\n" " buf[i] = 0;\n" " return buf[0];\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:11]: (error) Array 'buf[10]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } @@ -1610,7 +1616,7 @@ class TestBufferOverrun : public TestFixture { " for (int i=0; i < 3; i++)\n" " for (int j = 0; j < 3; j++)\n" " M[i][j]=0.0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:15]: (error) Array 'M[3][1]' accessed at index M[*][2], which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } @@ -2009,92 +2015,107 @@ class TestBufferOverrun : public TestFixture { errout_str()); } + void array_index_77() + { + check("int f(bool b) {\n" // #6092 + " const int a[2] = {};\n" + " int i = 0, s = 0;\n" + " s += a[i++];\n" + " if (b)\n" + " s += a[i++];\n" + " s += a[i++];\n" + " return s + i;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:7:11]: (error) Array 'a[2]' accessed at index 2, which is out of bounds. [arrayIndexOutOfBounds]\n", + errout_str()); + } + void array_index_multidim() { check("void f()\n" "{\n" " char a[2][2];\n" " a[1][1] = 'a';\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f()\n" "{\n" " char a[2][2][2];\n" " a[1][1][1] = 'a';\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f()\n" "{\n" " char a[2][2];\n" " a[2][1] = 'a';\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:4]: (error) Array 'a[2][2]' accessed at index a[2][1], which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void f()\n" "{\n" " char a[2][2];\n" " a[1][2] = 'a';\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:4]: (error) Array 'a[2][2]' accessed at index a[1][2], which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void f()\n" "{\n" " char a[2][2][2];\n" " a[2][1][1] = 'a';\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:4]: (error) Array 'a[2][2][2]' accessed at index a[2][1][1], which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void f()\n" "{\n" " char a[2][2][2];\n" " a[1][2][1] = 'a';\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:4]: (error) Array 'a[2][2][2]' accessed at index a[1][2][1], which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void f()\n" "{\n" " char a[2][2][2][2];\n" " a[1][2][1][1] = 'a';\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:4]: (error) Array 'a[2][2][2][2]' accessed at index a[1][2][1][1], which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void f()\n" "{\n" " char a[2][2][2];\n" " a[1][1][2] = 'a';\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:4]: (error) Array 'a[2][2][2]' accessed at index a[1][1][2], which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void f()\n" "{\n" " char a[10][10][10];\n" " a[2*3][4*3][2] = 'a';\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:4]: (error) Array 'a[10][10][10]' accessed at index a[6][12][2], which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void f() {\n" " char a[10][10][10];\n" " a[6][40][10] = 'a';\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:4]: (error) Array 'a[10][10][10]' accessed at index a[6][40][10], which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void f() {\n" " char a[1][1][1];\n" " a[2][2][2] = 'a';\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:4]: (error) Array 'a[1][1][1]' accessed at index a[2][2][2], which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void f() {\n" " char a[6][6][6];\n" " a[6][6][2] = 'a';\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:4]: (error) Array 'a[6][6][6]' accessed at index a[6][6][2], which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void f() {\n" " int a[2][2];\n" " p = &a[2][0];\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // unknown dim.. @@ -2102,7 +2123,7 @@ class TestBufferOverrun : public TestFixture { "{\n" " int a[2][countof(x)] = {{1,2},{3,4}};\n" " a[0][0] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void draw_quad(float z) {\n" @@ -2124,7 +2145,7 @@ class TestBufferOverrun : public TestFixture { " vertices[i][1][2] = 4.0;\n" " vertices[i][1][3] = 5.0;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); { @@ -2133,7 +2154,7 @@ class TestBufferOverrun : public TestFixture { " const size_t B = 2;\n" " extern int stuff[A][B];\n" " return stuff[0][1];\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // TODO: better handling of VLAs in symboldatabase. should be @@ -2143,7 +2164,7 @@ class TestBufferOverrun : public TestFixture { " const size_t B = 2;\n" " extern int stuff[A][B];\n" " return stuff[0][1];\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("error", "", errout_str()); } } @@ -2164,7 +2185,7 @@ class TestBufferOverrun : public TestFixture { " break;\n" " };\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f()\n" @@ -2182,7 +2203,7 @@ class TestBufferOverrun : public TestFixture { " break;\n" " };\n" " }\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:12]: (error) Array index out of bounds.\n", "", errout_str()); } @@ -2194,7 +2215,7 @@ class TestBufferOverrun : public TestFixture { " a[i] = 0;\n" " }\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2204,7 +2225,7 @@ class TestBufferOverrun : public TestFixture { "void f(int i) {\n" " if (i >= 0 && i < 10) {}\n" " a[i] = 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:19] -> [test.cpp:4:4]: (warning) Either the condition 'i<10' is redundant or the array 'a[10]' is accessed at index 10, which is out of bounds. [arrayIndexOutOfBoundsCond]\n" "[test.cpp:3:9] -> [test.cpp:4:4]: (warning) Either the condition 'i>=0' is redundant or the array 'a[10]' is accessed at index -1, which is out of bounds. [negativeIndex]\n", errout_str()); @@ -2218,7 +2239,7 @@ class TestBufferOverrun : public TestFixture { " for (int i = 19; i < 36; ++i) {\n" " data[i/2] = 0;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:13]: (error) Array 'data[8]' accessed at index 17, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); // #2199 - false negative: array out of bounds in loop when there is calculation @@ -2228,7 +2249,7 @@ class TestBufferOverrun : public TestFixture { " for (int i = 0; i < 5; ++i) {\n" " arr[i + 7] = 0;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:12]: (error) Array 'arr[5]' accessed at index 11, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } @@ -2238,21 +2259,21 @@ class TestBufferOverrun : public TestFixture { "{\n" " char data[8];\n" " data[-1] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:9]: (error) Array 'data[8]' accessed at index -1, which is out of bounds. [negativeIndex]\n", errout_str()); check("void f()\n" "{\n" " char data[8][4];\n" " data[5][-1] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:9]: (error) Array 'data[8][4]' accessed at index data[*][-1], which is out of bounds. [negativeIndex]\n", errout_str()); // #1614 - negative index is ok for pointers check("void foo(char *p)\n" "{\n" " p[-1] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo()\n" @@ -2260,14 +2281,14 @@ class TestBufferOverrun : public TestFixture { " char s[] = \"abc\";\n" " char *p = s + strlen(s);\n" " if (p[-1]);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // ticket #1850 check("int f(const std::map > &m)\n" "{\n" " return m[0][-1];\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2276,7 +2297,7 @@ class TestBufferOverrun : public TestFixture { "void foo() {\n" " TEST test;\n" " test.a[-1] = 3;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:11]: (error) Array 'test.a[10]' accessed at index -1, which is out of bounds. [negativeIndex]\n", errout_str()); } @@ -2292,7 +2313,7 @@ class TestBufferOverrun : public TestFixture { "void g(int i) {\n" " if( i == 0 )\n" " return f(i);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2399,7 +2420,7 @@ class TestBufferOverrun : public TestFixture { " for (int i = 10; i > 0; --i) {\n" " data[i] = 0;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:13]: (error) Array 'data[8]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void f()\n" @@ -2408,7 +2429,7 @@ class TestBufferOverrun : public TestFixture { " for (unsigned int i = 3; i < 5; --i) {\n" " val[i+1] = val[i];\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f()\n" @@ -2417,7 +2438,7 @@ class TestBufferOverrun : public TestFixture { " for (int i = 3; i < 5; --i) {\n" " val[i+1] = val[i];\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:12]: (error) Array 'val[5]' accessed at index -9994, which is out of bounds. [negativeIndex]\n" "[test.cpp:5:23]: (error) Array 'val[5]' accessed at index -9995, which is out of bounds. [negativeIndex]\n", errout_str()); } @@ -2435,7 +2456,7 @@ class TestBufferOverrun : public TestFixture { " A a;\n" " a.data[3] = 0;\n" " a.b.data[2] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #1586 @@ -2450,7 +2471,7 @@ class TestBufferOverrun : public TestFixture { " A a;\n" " a.data[4] = 0;\n" " a.b.data[3] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:10:11]: (error) Array 'a.data[4]' accessed at index 4, which is out of bounds. [arrayIndexOutOfBounds]\n" "[test.cpp:11:13]: (error) Array 'a.b.data[3]' accessed at index 3, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } @@ -2464,7 +2485,7 @@ class TestBufferOverrun : public TestFixture { " for (x = 0; x < 10 && y; x++) {\n" " data[x] = 0;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:13]: (error) Array 'data[2]' accessed at index 9, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void f() {\n" @@ -2473,7 +2494,7 @@ class TestBufferOverrun : public TestFixture { " for (x = 0; x < 10 || y; x++) {\n" " data[x] = 0;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:13]: (error) Array 'data[2]' accessed at index 9, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void f() {\n" @@ -2482,7 +2503,7 @@ class TestBufferOverrun : public TestFixture { " for (x = 0; x <= 10 && y; x++) {\n" " data[x] = 0;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:13]: (error) Array 'data[2]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void f() {\n" @@ -2491,7 +2512,7 @@ class TestBufferOverrun : public TestFixture { " for (x = 0; y && x <= 10; x++) {\n" " data[x] = 0;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:13]: (error) Array 'data[2]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("int f() {\n" // #9126 @@ -2503,7 +2524,7 @@ class TestBufferOverrun : public TestFixture { " c++;\n" " }\n" " return c;\n" - "}", dinit(CheckOptions, $.cpp = false)); + "}\n", dinit(CheckOptions, $.cpp = false)); ASSERT_EQUALS("", errout_str()); } @@ -2517,7 +2538,7 @@ class TestBufferOverrun : public TestFixture { " }\n" " a[i - 1] = 0;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // extracttests.start: int maybe(); @@ -2529,7 +2550,7 @@ class TestBufferOverrun : public TestFixture { " }\n" " a[i - 1] = 0;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:10]: (error) Array 'a[2]' accessed at index -1, which is out of bounds. [negativeIndex]\n", errout_str()); } @@ -2542,7 +2563,7 @@ class TestBufferOverrun : public TestFixture { " }\n" " a[i] = 0;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:10]: (error) Array 'a[10]' accessed at index 19, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); // Ticket #2385 - No false positive @@ -2554,7 +2575,7 @@ class TestBufferOverrun : public TestFixture { " a[i-10] = 0;\n" " }\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Ticket #3893 - start value out of bounds @@ -2564,7 +2585,7 @@ class TestBufferOverrun : public TestFixture { " for (int i = 10; maybe(); dostuff()) {\n" " a[i] = 0;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:10]: (error) Array 'a[10]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); // #7686 @@ -2598,7 +2619,7 @@ class TestBufferOverrun : public TestFixture { " for (int i = 0; i != 10; ++i) {\n" " a[i] = 0;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:10]: (error) Array 'a[5]' accessed at index 9, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } @@ -2610,7 +2631,7 @@ class TestBufferOverrun : public TestFixture { " for (int i = 0; i != 10; ++i) {\n" " i == 0 ? 0 : a[i-1];\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -2618,7 +2639,7 @@ class TestBufferOverrun : public TestFixture { " for (int i = 0; i != 10; ++i) {\n" " some_condition ? 0 : a[i-1];\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:31]: (error) Array 'a[10]' accessed at index -1, which is out of bounds. [negativeIndex]\n", errout_str()); @@ -2628,7 +2649,7 @@ class TestBufferOverrun : public TestFixture { " i==0 ? 0 : a[i-1];\n" " a[i-1] = 0;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:10]: (error) Array 'a[10]' accessed at index -1, which is out of bounds. [negativeIndex]\n", errout_str()); } @@ -2636,7 +2657,7 @@ class TestBufferOverrun : public TestFixture { check("void f() {\n" " char a[10];\n" " for (i=0; i<10; i++);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2647,14 +2668,14 @@ class TestBufferOverrun : public TestFixture { " for (int i=0; i<7; ++i) {\n" " a[0] = 0;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } void array_index_extern() { // Ticket #1684. FP when using 'extern'. check("extern char arr[15];\n" - "char arr[15] = \"abc\";"); + "char arr[15] = \"abc\";\n"); ASSERT_EQUALS("", errout_str()); } @@ -2668,7 +2689,7 @@ class TestBufferOverrun : public TestFixture { "void f2() {\n" " int x[2];\n" " f1(x);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Same type => error @@ -2678,7 +2699,7 @@ class TestBufferOverrun : public TestFixture { "void f2() {\n" " char x[2];\n" " f1(x);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:6] -> [test.cpp:2]: (error) Array 'x[2]' accessed at index 4, which is out of bounds.\n", "", errout_str()); @@ -2688,41 +2709,41 @@ class TestBufferOverrun : public TestFixture { check("void f() {\n" " const char *str = \"abc\";\n" " bar(str[10]);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:12]: (error) Array 'str[4]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void f()\n" "{\n" " const char *str = \"abc\";\n" " bar(str[4]);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:12]: (error) Array 'str[4]' accessed at index 4, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void f()\n" "{\n" " const char *str = \"abc\";\n" " bar(str[3]);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f()\n" "{\n" " const char *str = \"a\tc\";\n" " bar(str[4]);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:12]: (error) Array 'str[4]' accessed at index 4, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void f() {\n" // #6973 " const char *name = \"\";\n" " if ( name[0] == 'U' ? name[1] : 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int main(int argc, char **argv) {\n" " char str[6] = \"\\0\";\n" " unsigned short port = 65535;\n" " snprintf(str, sizeof(str), \"%hu\", port);\n" - "}", dinit(CheckOptions, $.cpp = false)); + "}\n", dinit(CheckOptions, $.cpp = false)); ASSERT_EQUALS("", errout_str()); check("int f(int x) {\n" // #11020 @@ -2744,7 +2765,7 @@ class TestBufferOverrun : public TestFixture { "{\n" " struct tt *tt=x;\n" " tt->name;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // detect error @@ -2755,7 +2776,7 @@ class TestBufferOverrun : public TestFixture { "{\n" " struct tt *tt=x;\n" " tt->name[22] = 123;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:13]: (error) Array 'tt->name[21]' accessed at index 22, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } @@ -2764,7 +2785,7 @@ class TestBufferOverrun : public TestFixture { " char str[3];\n" " str[i] = 0;\n" " if (i==10) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:10] -> [test.cpp:3:8]: (warning) Either the condition 'i==10' is redundant or the array 'str[3]' is accessed at index 10, which is out of bounds. [arrayIndexOutOfBoundsCond]\n", errout_str()); check("void f(int i) {\n" @@ -2773,31 +2794,31 @@ class TestBufferOverrun : public TestFixture { " switch (i) {\n" " case 10: break;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:5] -> [test.cpp:3:8]: (warning) Either the switch case 'case 10' is redundant or the array 'str[3]' is accessed at index 10, which is out of bounds. [arrayIndexOutOfBoundsCond]\n", errout_str()); check("void f() {\n" " char str[3];\n" " str[((unsigned char)3) - 1] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" // #5416 FP " char *str[3];\n" " do_something(&str[0][5]);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("class X { static const int x[100]; };\n" // #6070 - "const int X::x[100] = {0};"); + "const int X::x[100] = {0};\n"); ASSERT_EQUALS("", errout_str()); check("namespace { class X { static const int x[100]; };\n" // #6232 - "const int X::x[100] = {0}; }"); + "const int X::x[100] = {0}; }\n"); ASSERT_EQUALS("", errout_str()); check("class ActorSprite { static ImageSet * targetCursorImages[2][10]; };\n" - "ImageSet *ActorSprite::targetCursorImages[2][10];"); + "ImageSet *ActorSprite::targetCursorImages[2][10];\n"); ASSERT_EQUALS("", errout_str()); check("int f(const std::size_t s) {\n" // #10130 @@ -2829,7 +2850,7 @@ class TestBufferOverrun : public TestFixture { " int a[10];\n" " int *p = a;\n" " p[20] = 0;\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:3] -> [test.cpp:4]: (error) Array 'a[10]' accessed at index 20, which is out of bounds.\n", "", errout_str()); { @@ -2838,14 +2859,14 @@ class TestBufferOverrun : public TestFixture { " int a[10];\n" " int *p = a;\n" " p[10] = 0;\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:3] -> [test.cpp:4:6]: (error) Array 'a[10]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]\n", "", errout_str()); check("void f() {\n" " int a[10];\n" " int *p = a;\n" " dostuff(&p[10]);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2853,33 +2874,33 @@ class TestBufferOverrun : public TestFixture { " int a[X];\n" // unknown size " int *p = a;\n" " p[20] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " int a[2];\n" " char *p = (char *)a;\n" // cast " p[4] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } void array_index_function_parameter() { check("void f(char a[10]) {\n" " a[20] = 0;\n" // <- cppcheck warn here even though it's not a definite access out of bounds - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:4]: (error) Array 'a[10]' accessed at index 20, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void f(char a[10]) {\n" // #6353 - reassign 'a' " a += 4;\n" " a[-1] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(char a[10]) {\n" " a[0] = 0;\n" " a[-1] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:4]: (error) Array 'a[10]' accessed at index -1, which is out of bounds. [negativeIndex]\n", errout_str()); } @@ -2888,7 +2909,7 @@ class TestBufferOverrun : public TestFixture { "void f() {\n" " E arrE[] = { e1, e2 };\n" " arrE[sizeof(arrE)] = e1;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:9]: (error) Array 'arrE[2]' accessed at index 8, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } @@ -2896,8 +2917,71 @@ class TestBufferOverrun : public TestFixture { check("constexpr int blockLen = 10;\n" "void foo(std::array& a) {\n" " a[2] = 2;\n" - "}"); + "}\n"); + ASSERT_EQUALS("", errout_str()); + } + + void array_index_container_data() + { + check("void f() {\n" + " std::vector v(3);\n" + " int* p = v.data();\n" + " p[2] = 1;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("void f() {\n" + " std::vector v(3);\n" + " int* p = v.data();\n" + " p[5] = 1;\n" + "}\n"); + ASSERT_EQUALS( + "[test.cpp:4:6]: (error) Array 'p[3]' accessed at index 5, which is out of bounds. [arrayIndexOutOfBounds]\n", + errout_str()); + + check("void f() {\n" + " std::vector v(3);\n" + " memset(v.data(), 0, 12);\n" + " memset(v.data(), 0, 100);\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:4:18]: (error) Buffer is accessed out of bounds: v.data() [bufferAccessOutOfBounds]\n", + errout_str()); + + check("void f() {\n" + " std::vector v(3);\n" + " int* p = v.data();\n" + " memset(p, 0, 100);\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:4:12]: (error) Buffer is accessed out of bounds: p [bufferAccessOutOfBounds]\n", + errout_str()); + + // the size is not tracked past changes of the container size + check("void f() {\n" + " std::vector v(3);\n" + " v.reserve(100);\n" + " int* p = v.data();\n" + " v.resize(10);\n" + " memset(p, 0, 40);\n" + "}\n"); ASSERT_EQUALS("", errout_str()); + + // ..or when the pointer is reassigned + check("void f(int* q) {\n" + " std::vector v(3);\n" + " int* p = v.data();\n" + " p = q;\n" + " memset(p, 0, 100);\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + // the buffer of c_str() includes the null terminator + check("void f(char* dst) {\n" + " std::string s = \"abc\";\n" + " memcpy(dst, s.c_str(), 4);\n" + " memcpy(dst, s.c_str(), 5);\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:4:24]: (error) Buffer is accessed out of bounds: s.c_str() [bufferAccessOutOfBounds]\n", + errout_str()); } void array_index_two_for_loops() { @@ -2910,7 +2994,7 @@ class TestBufferOverrun : public TestFixture { " sum += val[i];\n" " if (i < 50)\n" " sum -= val[i];\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("bool b();\n" @@ -2924,7 +3008,7 @@ class TestBufferOverrun : public TestFixture { " sum -= val[i];\n" " break;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("bool b();\n" @@ -2936,7 +3020,7 @@ class TestBufferOverrun : public TestFixture { " sum += val[i];\n" " for (; i < 50; i++)\n" " sum -= val[i];\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -3023,7 +3107,7 @@ class TestBufferOverrun : public TestFixture { "static void f(struct ABC *abc)\n" "{\n" " strcpy( abc->str, \"abcdef\" );\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:16]: (error) Buffer is accessed out of bounds: abc->str [bufferAccessOutOfBounds]\n", errout_str()); check("struct ABC\n" @@ -3035,7 +3119,7 @@ class TestBufferOverrun : public TestFixture { "{\n" " struct ABC abc;\n" " strcpy( abc.str, \"abcdef\" );\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:9:16]: (error) Buffer is accessed out of bounds: abc.str [bufferAccessOutOfBounds]\n", errout_str()); check("struct ABC\n" @@ -3046,7 +3130,7 @@ class TestBufferOverrun : public TestFixture { "static void f(struct ABC &abc)\n" "{\n" " strcpy( abc.str, \"abcdef\" );\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:16]: (error) Buffer is accessed out of bounds: abc.str [bufferAccessOutOfBounds]\n", errout_str()); check("static void f()\n" @@ -3056,7 +3140,7 @@ class TestBufferOverrun : public TestFixture { " char str[5];\n" " } abc;\n" " strcpy( abc.str, \"abcdef\" );\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:16]: (error) Buffer is accessed out of bounds: abc.str [bufferAccessOutOfBounds]\n", errout_str()); check("static void f()\n" @@ -3068,7 +3152,7 @@ class TestBufferOverrun : public TestFixture { " struct ABC *abc = malloc(sizeof(struct ABC));\n" " strcpy( abc->str, \"abcdef\" );\n" " free(abc);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:16]: (error) Buffer is accessed out of bounds: abc->str [bufferAccessOutOfBounds]\n", errout_str()); } @@ -3081,7 +3165,7 @@ class TestBufferOverrun : public TestFixture { " int i;\n" " for (i = 0; i <= 10; ++i)\n" " a[i] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:10]: (error) Array 'a[10]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("struct S { int b; } static e[1];\n" // #11052 @@ -3096,7 +3180,7 @@ class TestBufferOverrun : public TestFixture { " const char *p[2];\n" " for (int i = 0; i < 8; ++i)\n" " p[i] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:10]: (error) Array 'p[2]' accessed at index 7, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); // No false positive @@ -3105,7 +3189,7 @@ class TestBufferOverrun : public TestFixture { " const char *p[2];\n" " const char *s = y + p[1];\n" " p[1] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // There is no error here @@ -3117,7 +3201,7 @@ class TestBufferOverrun : public TestFixture { "{\n" " char s[3];\n" " f1(s,20);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:8] -> [test.cpp:3]: (error) Buffer is accessed out of bounds.\n", "", errout_str()); check("void f1(char *s,int size)\n" @@ -3138,7 +3222,7 @@ class TestBufferOverrun : public TestFixture { " char n[5];\n" " sprintf(n, \"d\");\n" " printf(\"hello!\");\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -3148,21 +3232,21 @@ class TestBufferOverrun : public TestFixture { " char n[5];\n" " strcat(n, \"abc\");\n" " strcat(n, \"def\");\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:5]: (error) Buffer is accessed out of bounds: n\n", "", errout_str()); check("void f()\n" // #12489 "{\n" " char d[3] = {};\n" " strcat(d, \"12345678\");\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:11]: (error) Buffer is accessed out of bounds: d [bufferAccessOutOfBounds]\n", errout_str()); check("void f()\n" "{\n" " char d[3] = \"ab\"; \n" " strcat(d, \"c\");\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:4:11]: (error) Buffer is accessed out of bounds: d [bufferAccessOutOfBounds]\n", "", errout_str()); } @@ -3172,7 +3256,7 @@ class TestBufferOverrun : public TestFixture { "{\n" " char a[2];\n" " strcpy(a, \"a\\0\");\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -3185,7 +3269,7 @@ class TestBufferOverrun : public TestFixture { " {\n" " a[i] = 0;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f()\n" @@ -3195,7 +3279,7 @@ class TestBufferOverrun : public TestFixture { " {\n" " a[i] = 0;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -3209,7 +3293,7 @@ class TestBufferOverrun : public TestFixture { " a[i] = 0;\n" " i += 100;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -3222,7 +3306,7 @@ class TestBufferOverrun : public TestFixture { " {\n" " char b = a[i];\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -3233,7 +3317,7 @@ class TestBufferOverrun : public TestFixture { " for (float i=0; i<10.0;i=i+0.1)\n" " {\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f()\n" @@ -3242,7 +3326,7 @@ class TestBufferOverrun : public TestFixture { " for (float i=0; i<10.0;i=0.1+i)\n" " {\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -3254,7 +3338,7 @@ class TestBufferOverrun : public TestFixture { "void A::f(int i, int ii)\n" "{\n" " strcpy(val, \"ab\") ;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:12]: (error) Buffer is accessed out of bounds: val [bufferAccessOutOfBounds]\n", errout_str()); } @@ -3263,20 +3347,20 @@ class TestBufferOverrun : public TestFixture { check("void f() {\n" " struct Foo foo[5];\n" " memset(foo, 0, sizeof(foo));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" // ticket #2093 " gchar x[3];\n" " strcpy(x, \"12\");\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("extern char a[10];\n" "void f() {\n" " char b[25] = {0};\n" " std::memcpy(b, a, 10);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -3290,7 +3374,7 @@ class TestBufferOverrun : public TestFixture { " for (int i=0; i<6; i++) {\n" " b[i] = b[i+1];\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("class A {\n" @@ -3302,7 +3386,7 @@ class TestBufferOverrun : public TestFixture { " for (int i=0; i<7; i++) {\n" " b[i] = b[i+1];\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:17]: (error) Array 'b[7]' accessed at index 7, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } @@ -3315,7 +3399,7 @@ class TestBufferOverrun : public TestFixture { "\n" "A::A() {\n" " memset(buf, 0, 10);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -3326,7 +3410,7 @@ class TestBufferOverrun : public TestFixture { " const char *src = \"AAAAAAAAAAAAAAAAAAAAA\";\n" " for (size_t i = 0; i <= 4; i++)\n" " dst[i] = src[i];\n" - "} } }"); + "} } }\n"); ASSERT_EQUALS("[test.cpp:6:12]: (error) Array 'dst[4]' accessed at index 4, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } @@ -3338,7 +3422,7 @@ class TestBufferOverrun : public TestFixture { " for( int i = 0; i<6; ) {\n" " x += array[i];\n" " i++; }\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("error", "", errout_str()); // ticket #4096 @@ -3348,7 +3432,7 @@ class TestBufferOverrun : public TestFixture { " for( int i = 0; i<6; ) {\n" " x += array[i++];\n" " }\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("error", "", errout_str()); } @@ -3358,7 +3442,7 @@ class TestBufferOverrun : public TestFixture { " char inbuf[1000];\n" " char *f[10];\n" " split(inbuf, f, 10, \"\t\t\");\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -3368,7 +3452,7 @@ class TestBufferOverrun : public TestFixture { "void main() {\n" "struct foobar x[5];\n" "abc(x);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -3379,7 +3463,7 @@ class TestBufferOverrun : public TestFixture { "int main(){\n" " testChar tc1 = \"\";\n" " tc1[5]='a';\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -3389,14 +3473,14 @@ class TestBufferOverrun : public TestFixture { check("struct S { int m[9]; };\n" "int f(S * s) {\n" " return s->m[sizeof(s->m)];\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:16]: (error) Array 's->m[9]' accessed at index 36, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } void buffer_overrun_31() { check("void f(WhereInfo *pWInfo, int *aiCur) {\n" " memcpy(aiCur, pWInfo->aiCurOnePass, sizeof(int)*2);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -3407,7 +3491,7 @@ class TestBufferOverrun : public TestFixture { " char dest[1] = \"a\";\n" " (void)strxfrm(dest,src,1);\n" " (void)strxfrm(dest,src,2);\n"// << - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:19]: (error) Buffer is accessed out of bounds: dest [bufferAccessOutOfBounds]\n", errout_str()); // destination size is too small check("void f(void) {\n" @@ -3416,7 +3500,7 @@ class TestBufferOverrun : public TestFixture { " (void)strxfrm(dest,src,1);\n" " (void)strxfrm(dest,src,2);\n" " (void)strxfrm(dest,src,3);\n" // << - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:19]: (error) Buffer is accessed out of bounds: dest [bufferAccessOutOfBounds]\n", errout_str()); // source size is too small check("void f(void) {\n" @@ -3425,16 +3509,8 @@ class TestBufferOverrun : public TestFixture { " (void)strxfrm(dest,src,1);\n" " (void)strxfrm(dest,src,2);\n" " (void)strxfrm(dest,src,3);\n" // << - "}"); - ASSERT_EQUALS("[test.cpp:6:24]: (error) Buffer is accessed out of bounds: src [bufferAccessOutOfBounds]\n", errout_str()); - // source size is too small - check("void f(void) {\n" - " const char src[1] = \"a\";\n" - " char dest[3] = \"abc\";\n" - " (void)strxfrm(dest,src,1);\n" - " (void)strxfrm(dest,src,2);\n" // << - "}"); - ASSERT_EQUALS("[test.cpp:5:24]: (error) Buffer is accessed out of bounds: src [bufferAccessOutOfBounds]\n", errout_str()); + "}\n"); + ASSERT_EQUALS("", errout_str()); } void buffer_overrun_33() { // #2019 @@ -3444,7 +3520,7 @@ class TestBufferOverrun : public TestFixture { " for (int j=0; j<20; j++)\n" " z[i] = 0;\n" " return z[0];\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:12]: (error) Array 'z[16]' accessed at index 19, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } @@ -3497,18 +3573,191 @@ class TestBufferOverrun : public TestFixture { ASSERT_EQUALS("", errout_str()); } + void buffer_overrun_37() { // #14703 + check("void f() {\n" + " const char *dst[256];\n" + " static const char * const src[] = {\"a\", \"b\", \"c\"};\n" + " memcpy(dst, src, sizeof(src));\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + } + + void buffer_overrun_38() { // #9173 + check("void f() {\n" + " int a[10];\n" + " memset(&a[0], 0, 20 * sizeof(int));\n" + "}\n" + "void g() {\n" + " int a[10];\n" + " memset(&a[0], 0, 10 * sizeof(int));\n" + "}\n" + "void h() {\n" + " int a[10];\n" + " memset(&a[5], 0, 5 * sizeof(int));\n" + "}\n" + "void i() {\n" + " int a[10][10];\n" + " memset(&a[0][0], 0, 100 * sizeof(int));\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:3:12]: (error) Buffer is accessed out of bounds: &a[0] [bufferAccessOutOfBounds]\n", errout_str()); + + check("void f() {\n" + " int a[10];\n" + " memset(&a[5], 0, 10 * sizeof(int));\n" + "}\n" + "void g() {\n" + " int a[1][1];\n" + " memset(&a[0][0], 0, 10 * sizeof(int));\n" + "}\n"); + TODO_ASSERT_EQUALS("[test.cpp:3:12]: (error) Buffer is accessed out of bounds: &a[5] [bufferAccessOutOfBounds]\n" + "[test.cpp:7:12]: (error) Buffer is accessed out of bounds: &a[0][0] [bufferAccessOutOfBounds]\n", + "[test.cpp:3:12]: (error) Buffer is accessed out of bounds: &a[5] [bufferAccessOutOfBounds]\n", + errout_str()); + + check("void f() {\n" // #14866 + " std::vector buf(25);\n" + " std::memset(&buf[0], 0, 25);\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("void f() {\n" // #14859 + " std::vector buf(25);\n" + " std::memset(&buf[0], 0, 26);\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:3:17]: (error) Buffer is accessed out of bounds: &buf[0] [bufferAccessOutOfBounds]\n", errout_str()); + + check("void f(FILE *fp) {\n" // #14929 + " std::string s;\n" + " fwrite(&s, 1, 1, fp);\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); // don't crash + + check("void f() {\n" // #14935 + " int a[5];\n" + " for (int i = 0; i < 5; ++i)\n" + " memset(&a[i], 0, sizeof(a));\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:4:16]: (error) Buffer is accessed out of bounds: &a[i] [bufferAccessOutOfBounds]\n", errout_str()); + + check("void f(const std::vector& s) {\n" // #14948 + " if (s.size() < 4)\n" + " return;\n" + " uint32_t u = 0;\n" + " std::memcpy(&u, &s[0], sizeof(u));\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + } + + void buffer_overrun_39() { // #11765 + check("void f() {\n" + " char buf[10];\n" + " memcpy(buf, \"abc\", 4);\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("void f() {\n" + " char buf[128];\n" + " memcpy(buf, \"abc\", 5);\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:3:17]: (error) Buffer is accessed out of bounds: \"abc\" [bufferAccessOutOfBounds]\n", + errout_str()); + + check("void f() {\n" + " char buf[128];\n" + " memcpy(buf, L\"abc\", 4 * sizeof(wchar_t));\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("void f() {\n" + " char buf[128];\n" + " memcpy(buf, L\"abc\", 4 * sizeof(wchar_t) + 1);\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:3:17]: (error) Buffer is accessed out of bounds: L\"abc\" [bufferAccessOutOfBounds]\n", + errout_str()); + + check("int f(char* s) {\n" + " return memcmp(s, \"\\0\\0\\0\", 3);\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("int f(const char16_t* buf) {\n" + " return memcmp(buf, u\"abc\", 4 * sizeof(char16_t));\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("int f(const char* buf) {\n" + " return memcmp(buf, &\"abc\"[2], 4 * sizeof(char));\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:2:24]: (error) Buffer is accessed out of bounds: &\"abc\"[2] [bufferAccessOutOfBounds]\n", + errout_str()); + } + void buffer_overrun_errorpath() { setMultiline(); Settings s = settings0; - s.templateLocation = "{file}:{line}:note:{info}"; + s.templateLocation = "{file}:{line}:note:{info}\n"; check("void f() {\n" " char *p = malloc(10);\n" " memset(p, 0, 20);\n" - "}", s); + "}\n", s); ASSERT_EQUALS("[test.cpp:3:12]: error: Buffer is accessed out of bounds: p [bufferAccessOutOfBounds]\n" "[test.cpp:2:13]: note: Assign p, buffer with size 10\n" "[test.cpp:3:12]: note: Buffer overrun\n", errout_str()); + + check("void f(const std::vector& s) {\n" + " if (s.size() == 2) {}\n" + " uint32_t u = 0;\n" + " std::memcpy(&u, &s[0], sizeof(u));\n" + "}\n", s); + ASSERT_EQUALS("[test.cpp:4:21]: warning: Buffer is accessed out of bounds: &s[0] [bufferAccessOutOfBounds]\n" + "[test.cpp:2:18]: note: Assuming that condition 's.size()==2' is not redundant\n" + "[test.cpp:4:21]: note: Buffer overrun\n", errout_str()); + + check("void f(int i) {\n" // #14960 + " int a[1];\n" + " if (i != 2) return;\n" + " memset(a, 0, i * sizeof(int));\n" + "}" + "void g(int i) {\n" + " int a[1];\n" + " if (i != 2) {}\n" + " memset(a, 0, i * sizeof(int));\n" + "}", s); + ASSERT_EQUALS("[test.cpp:4:12]: warning: Buffer is accessed out of bounds: a [bufferAccessOutOfBounds]\n" + "[test.cpp:3:11]: note: Assuming that condition 'i!=2' is not redundant\n" + "[test.cpp:4:12]: note: Buffer overrun\n" + "[test.cpp:8:12]: warning: Buffer is accessed out of bounds: a [bufferAccessOutOfBounds]\n" + "[test.cpp:7:11]: note: Assuming that condition 'i!=2' is not redundant\n" + "[test.cpp:8:12]: note: Buffer overrun\n", errout_str()); + + check("int a[3];\n" + "int f1(int i, bool b) {\n" + " int j = b ? i : -1;\n" + " return a[j];\n" + "}" + "int f2(int i, bool b) {\n" + " int j = b ? -1 : i;\n" + " return a[j];\n" + "}" + "int f3(int i, bool b) {\n" + " int j = -1;\n" + " if (b)\n" + " j = i;\n" + " return a[j];\n" + "}", s); + ASSERT_EQUALS("[test.cpp:4:13]: warning: Array 'a[3]' accessed at index -1, which is out of bounds. [negativeIndex]\n" + "[test.cpp:3:13]: note: Assuming condition 'b' is false\n" + "[test.cpp:3:15]: note: Assignment 'j=b?i:-1', assigned value is -1\n" + "[test.cpp:4:13]: note: Negative array index\n" + "[test.cpp:7:13]: warning: Array 'a[3]' accessed at index -1, which is out of bounds. [negativeIndex]\n" + "[test.cpp:6:13]: note: Assuming condition 'b' is true\n" + "[test.cpp:6:15]: note: Assignment 'j=b?-1:i', assigned value is -1\n" + "[test.cpp:7:13]: note: Negative array index\n" + "[test.cpp:12:13]: warning: Array 'a[3]' accessed at index -1, which is out of bounds. [negativeIndex]\n" + "[test.cpp:9:14]: note: Assignment 'j=-1', assigned value is -1\n" + "[test.cpp:10:9]: note: Assuming condition is false\n" + "[test.cpp:12:13]: note: Negative array index\n", errout_str()); } void buffer_overrun_bailoutIfSwitch() { @@ -3520,7 +3769,7 @@ class TestBufferOverrun : public TestFixture { "void f2() {\n" " char a[10];\n" " f1(a);" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // No false positive @@ -3532,7 +3781,7 @@ class TestBufferOverrun : public TestFixture { "void f2() {\n" " char a[10];\n" " f1(a);" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // No false negative @@ -3544,7 +3793,7 @@ class TestBufferOverrun : public TestFixture { "void f2() {\n" " char a[10];\n" " f1(a);" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:8] -> [test.cpp:3]: (error) Array 'a[10]' accessed at index 100, which is out of bounds.\n", "", errout_str()); } @@ -3555,7 +3804,7 @@ class TestBufferOverrun : public TestFixture { "void g() {\n" " char a[2];\n" " f(a);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:5]: warning: Buffer 'a' is too small, the function 'f' expects a bigger buffer in 1st argument [argumentSize]\n" "[test.cpp:4:5]: note: Function 'f' is called\n" "[test.cpp:1:13]: note: Declaration of 1st function argument.\n" @@ -3566,7 +3815,7 @@ class TestBufferOverrun : public TestFixture { "void g() {\n" " float a[2][3];\n" " f(a);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:5]: warning: Buffer 'a' is too small, the function 'f' expects a bigger buffer in 1st argument [argumentSize]\n" "[test.cpp:4:5]: note: Function 'f' is called\n" "[test.cpp:1:14]: note: Declaration of 1st function argument.\n" @@ -3577,7 +3826,7 @@ class TestBufferOverrun : public TestFixture { "void g() {\n" " int a[2];\n" " f(a);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:5]: warning: Buffer 'a' is too small, the function 'f' expects a bigger buffer in 1st argument [argumentSize]\n" "[test.cpp:4:5]: note: Function 'f' is called\n" "[test.cpp:1:12]: note: Declaration of 1st function argument.\n" @@ -3599,7 +3848,7 @@ class TestBufferOverrun : public TestFixture { "void foo() {\n" " unsigned char(* tree)[2048][4] = new unsigned char[256][2048][4];\n" " CreateLeafTex(tree);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int a[10]) {\n" // #10069 @@ -3627,7 +3876,7 @@ class TestBufferOverrun : public TestFixture { " memset(src, 'C', 99);\n" " src[99] = '\\0';\n" " strcat(data, src);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:6]: (warning) Possible buffer overflow if strlen(src) is larger than sizeof(data)-strlen(data).\n", "", errout_str()); check("void foo() {\n" @@ -3636,19 +3885,19 @@ class TestBufferOverrun : public TestFixture { " memset(src, 'C', 99);\n" " src[99] = '\\0';\n" " strcat(data, src);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(char src[100]) {\n" " char * data = (char *)alloca(50);\n" " strcat(data, src);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:3]: (warning) Possible buffer overflow if strlen(src) is larger than sizeof(data)-strlen(data).\n", "", errout_str()); check("void foo(char src[100]) {\n" " char * data = (char *)alloca(100);\n" " strcat(data, src);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" @@ -3657,7 +3906,7 @@ class TestBufferOverrun : public TestFixture { " memset(src, 'C', 99);\n" " src[99] = '\\0';\n" " strcpy(data, src);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:6]: (warning) Possible buffer overflow if strlen(src) is larger than or equal to sizeof(data).\n", "", errout_str()); check("void foo() {\n" @@ -3666,19 +3915,19 @@ class TestBufferOverrun : public TestFixture { " memset(src, 'C', 99);\n" " src[99] = '\\0';\n" " strcpy(data, src);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(char src[100]) {\n" " char * data = (char *)alloca(50);\n" " strcpy(data, src);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:3]: (warning) Possible buffer overflow if strlen(src) is larger than or equal to sizeof(data).\n", "", errout_str()); check("void foo(char src[100]) {\n" " char * data = (char *)alloca(100);\n" " strcpy(data, src);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -3700,26 +3949,26 @@ class TestBufferOverrun : public TestFixture { check("void f() {\n" " u8 str[256];\n" " mystrcpy(str, \"abcd\");\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " u8 str[2];\n" " mystrcpy(str, \"abcd\");\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("[test.cpp:3:12]: (error) Buffer is accessed out of bounds: str [bufferAccessOutOfBounds]\n", errout_str()); // The same for structs, where the message comes from a different check check("void f() {\n" " struct { u8 str[256]; } ms;\n" " mystrcpy(ms.str, \"abcd\");\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " struct { u8 str[2]; } ms;\n" " mystrcpy(ms.str, \"abcd\");\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("[test.cpp:3:16]: (error) Buffer is accessed out of bounds: ms.str [bufferAccessOutOfBounds]\n", errout_str()); } @@ -3727,32 +3976,32 @@ class TestBufferOverrun : public TestFixture { check("void f() {\n" " int i;\n" " memset(i, 0, 1000);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " int i;\n" " memset(&i, 0, 1000);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:10]: (error) Buffer is accessed out of bounds: &i [bufferAccessOutOfBounds]\n", errout_str()); check("void f() {\n" " int i[2];\n" " memset(&i, 0, 1000);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:10]: (error) Buffer is accessed out of bounds: i [bufferAccessOutOfBounds]\n", errout_str()); check("void f() {\n" " int i;\n" " memset(&i, 0, sizeof(i));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " int i[10];\n" " memset(&i[1], 0, 1000);\n" - "}"); - TODO_ASSERT_EQUALS("[test.cpp:3:10]: (error) Buffer is accessed out of bounds: &i[1] [bufferAccessOutOfBounds]\n", "", errout_str()); + "}\n"); + ASSERT_EQUALS("[test.cpp:3:10]: (error) Buffer is accessed out of bounds: &i[1] [bufferAccessOutOfBounds]\n", errout_str()); check("struct S { int x; };\n" // #8616 "void f() {\n" @@ -3780,7 +4029,7 @@ class TestBufferOverrun : public TestFixture { " const char *x = s;\n" " if (cond) x = \"abcde\";\n" " return x[20];\n" // <- array index out of bounds when x is "abcde" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:11]: (error) Array 'x[6]' accessed at index 20, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } @@ -3790,40 +4039,40 @@ class TestBufferOverrun : public TestFixture { check("void f() {\n" " char a[10];\n" " char *p = a + 100;\n" - "}", settings0_p); + "}\n", settings0_p); ASSERT_EQUALS("[test.cpp:3:17]: (portability) Undefined behaviour, pointer arithmetic 'a+100' is out of bounds. [pointerOutOfBounds]\n", errout_str()); check("char *f() {\n" " char a[10];\n" " return a + 100;\n" - "}", settings0_p); + "}\n", settings0_p); ASSERT_EQUALS("[test.cpp:3:14]: (portability) Undefined behaviour, pointer arithmetic 'a+100' is out of bounds. [pointerOutOfBounds]\n", errout_str()); check("void f(int i) {\n" " char x[10];\n" " if (i == 123) {}\n" " dostuff(x+i);\n" - "}", settings0_p); + "}\n", settings0_p); ASSERT_EQUALS("[test.cpp:3:11] -> [test.cpp:4:14]: (portability) Undefined behaviour, when 'i' is 123 the pointer arithmetic 'x+i' is out of bounds. [pointerOutOfBoundsCond]\n", errout_str()); check("void f(int i) {\n" " char x[10];\n" " if (i == -1) {}\n" " dostuff(x+i);\n" - "}", settings0_p); + "}\n", settings0_p); ASSERT_EQUALS("[test.cpp:3:11] -> [test.cpp:4:14]: (portability) Undefined behaviour, when 'i' is -1 the pointer arithmetic 'x+i' is out of bounds. [pointerOutOfBoundsCond]\n", errout_str()); check("void f() {\n" // #6350 - fp when there is cast of buffer " wchar_t buf[64];\n" " p = (unsigned char *) buf + sizeof (buf);\n" - "}", settings0_p, false); + "}\n", settings0_p, false); ASSERT_EQUALS("", errout_str()); check("int f() {\n" " const char d[] = \"0123456789\";\n" " char *cp = d + 3;\n" " return cp - d;\n" - "}", settings0_p); + "}\n", settings0_p); ASSERT_EQUALS("", errout_str()); } @@ -3832,7 +4081,7 @@ class TestBufferOverrun : public TestFixture { " char *p = malloc(10);\n" " p += 100;\n" " free(p);" - "}", settings0_p); + "}\n", settings0_p); TODO_ASSERT_EQUALS("[test.cpp:3]: (portability) Undefined behaviour, pointer arithmetic 'p+100' is out of bounds.\n", "", errout_str()); check("void f() {\n" @@ -3840,7 +4089,7 @@ class TestBufferOverrun : public TestFixture { " p += 10;\n" " *p = 0;\n" " free(p);" - "}", settings0_p); + "}\n", settings0_p); TODO_ASSERT_EQUALS("[test.cpp:4]: (error) p is out of bounds.\n", "", errout_str()); check("void f() {\n" @@ -3849,7 +4098,7 @@ class TestBufferOverrun : public TestFixture { " p -= 10;\n" " *p = 0;\n" " free(p);" - "}", settings0_p); + "}\n", settings0_p); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -3858,7 +4107,7 @@ class TestBufferOverrun : public TestFixture { " p = p - 1;\n" " *p = 0;\n" " free(p);" - "}", settings0_p); + "}\n", settings0_p); ASSERT_EQUALS("", errout_str()); } @@ -3866,7 +4115,7 @@ class TestBufferOverrun : public TestFixture { check("struct S { int a[10]; };\n" "void f(struct S *s) {\n" " int *p = s->a + 100;\n" - "}", settings0_p); + "}\n", settings0_p); ASSERT_EQUALS("[test.cpp:3:19]: (portability) Undefined behaviour, pointer arithmetic 's->a+100' is out of bounds. [pointerOutOfBounds]\n", errout_str()); check("template class Vector\n" @@ -3889,22 +4138,22 @@ class TestBufferOverrun : public TestFixture { void pointer_out_of_bounds_4() { check("const char* f() {\n" " g(\"Hello\" + 6);\n" - "}", settings0_p); + "}\n", settings0_p); ASSERT_EQUALS("", errout_str()); check("const char* f() {\n" " g(\"Hello\" + 7);\n" - "}", settings0_p); + "}\n", settings0_p); ASSERT_EQUALS("[test.cpp:2:15]: (portability) Undefined behaviour, pointer arithmetic '\"Hello\"+7' is out of bounds. [pointerOutOfBounds]\n", errout_str()); check("const char16_t* f() {\n" " g(u\"Hello\" + 6);\n" - "}", settings0_p); + "}\n", settings0_p); ASSERT_EQUALS("", errout_str()); check("const char16_t* f() {\n" " g(u\"Hello\" + 7);\n" - "}", settings0_p); + "}\n", settings0_p); ASSERT_EQUALS("[test.cpp:2:16]: (portability) Undefined behaviour, pointer arithmetic 'u\"Hello\"+7' is out of bounds. [pointerOutOfBounds]\n", errout_str()); check("void f() {\n" // #4647 @@ -3951,26 +4200,26 @@ class TestBufferOverrun : public TestFixture { check("char *f() {\n" " char x[10];\n" " return x-1;\n" - "}", settings0_p); + "}\n", settings0_p); ASSERT_EQUALS("[test.cpp:3:13]: (portability) Undefined behaviour, pointer arithmetic 'x-1' is out of bounds. [pointerOutOfBounds]\n", errout_str()); check("void f(int i) {\n" " char x[10];\n" " if (i == 123) {}\n" " dostuff(x-i);\n" - "}", settings0_p); + "}\n", settings0_p); ASSERT_EQUALS("[test.cpp:3:11] -> [test.cpp:4:14]: (portability) Undefined behaviour, when 'i' is 123 the pointer arithmetic 'x-i' is out of bounds. [pointerOutOfBoundsCond]\n", errout_str()); check("void f(int i) {\n" " char x[10];\n" " if (i == -20) {}\n" " dostuff(x-i);\n" - "}", settings0_p); + "}\n", settings0_p); TODO_ASSERT_EQUALS("[test.cpp:4]: (portability) Undefined behaviour, when 'i' is -20 the pointer arithmetic 'x-i' is out of bounds.\n", "", errout_str()); check("void f(const char *x[10]) {\n" " return x-4;\n" - "}", settings0_p); + "}\n", settings0_p); ASSERT_EQUALS("", errout_str()); } @@ -3980,7 +4229,7 @@ class TestBufferOverrun : public TestFixture { " struct Foo x = {0};\n" " strcat(x.a, \"aa\");\n" " strcat(x.a, \"aa\");\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:5]: (error) Buffer is accessed out of bounds.\n", "", errout_str()); } @@ -3993,7 +4242,7 @@ class TestBufferOverrun : public TestFixture { " char str[50];\n" " str[30] = 0;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -4001,7 +4250,7 @@ class TestBufferOverrun : public TestFixture { check("struct foo {\n" " void bar() { return; }\n" " type<> member[1];\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -4011,7 +4260,7 @@ class TestBufferOverrun : public TestFixture { "void foo()\n" "{\n" " str[3] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:8]: (error) Array 'str[3]' accessed at index 3, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } @@ -4020,7 +4269,7 @@ class TestBufferOverrun : public TestFixture { "{\n" " char *s; s = new char[10];\n" " s[10] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:6]: (error) Array 's[10]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); // ticket #1670 - false negative when using return @@ -4028,7 +4277,7 @@ class TestBufferOverrun : public TestFixture { "{\n" " int *s; s = new int[10];\n" " return s[10];\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:13]: (error) Array 's[10]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("struct Fred { char c[10]; };\n" @@ -4036,12 +4285,12 @@ class TestBufferOverrun : public TestFixture { "{\n" " Fred *f; f = new Fred;\n" " return f->c[10];\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:16]: (error) Array 'f->c[10]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("static const size_t MAX_SIZE = UNAVAILABLE_TO_CPPCHECK;\n" "struct Thing { char data[MAX_SIZE]; };\n" - "char f4(const Thing& t) { return !t.data[0]; }"); + "char f4(const Thing& t) { return !t.data[0]; }\n"); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" @@ -4051,7 +4300,7 @@ class TestBufferOverrun : public TestFixture { " buf = new char[9];\n" " buf[8] = 0;\n" " delete [] buf;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" @@ -4061,7 +4310,7 @@ class TestBufferOverrun : public TestFixture { " buf = new char[9];\n" " buf[9] = 0;\n" " delete [] buf;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:6]: (error) Array 'buf[9]' accessed at index 9, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void foo()\n" @@ -4069,7 +4318,7 @@ class TestBufferOverrun : public TestFixture { " enum E { Size = 10 };\n" " char *s; s = new char[Size];\n" " s[Size] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:6]: (error) Array 's[10]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void foo()\n" @@ -4077,7 +4326,7 @@ class TestBufferOverrun : public TestFixture { " enum E { ZERO };\n" " E *e; e = new E[10];\n" " e[10] = ZERO;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:6]: (error) Array 'e[10]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } @@ -4087,14 +4336,14 @@ class TestBufferOverrun : public TestFixture { "{\n" " char *s; s = (char *)malloc(10);\n" " s[10] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:6]: (error) Array 's[10]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); // ticket #842 check("void f() {\n" " int *tab4 = (int *)malloc(20 * sizeof(int));\n" " tab4[20] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:9]: (error) Array 'tab4[20]' accessed at index 20, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); // ticket #1478 @@ -4103,7 +4352,7 @@ class TestBufferOverrun : public TestFixture { " free(p);\n" " p = (char *)malloc(10);\n" " p[10] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:6]: (error) Array 'p[10]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); // ticket #1134 @@ -4111,7 +4360,7 @@ class TestBufferOverrun : public TestFixture { " int *x, i;\n" " x = (int *)malloc(10 * sizeof(int));\n" " x[10] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:6]: (error) Array 'x[10]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void f() {\n" @@ -4121,7 +4370,7 @@ class TestBufferOverrun : public TestFixture { " tab4 = malloc(21 * sizeof(int));\n" " tab4[20] = 0;\n" " free(tab4);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -4130,49 +4379,49 @@ class TestBufferOverrun : public TestFixture { " tab4 = realloc(tab4,21 * sizeof(int));\n" " tab4[20] = 0;\n" " free(tab4);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " enum E { Size = 20 };\n" " E *tab4 = (E *)malloc(Size * 4);\n" " tab4[Size] = Size;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:9]: (error) Array 'tab4[20]' accessed at index 20, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void f() {\n" " enum E { Size = 20 };\n" " E *tab4 = (E *)malloc(4 * Size);\n" " tab4[Size] = Size;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:9]: (error) Array 'tab4[20]' accessed at index 20, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void f() {\n" " enum E { ZERO };\n" " E *tab4 = (E *)malloc(20 * sizeof(E));\n" " tab4[20] = ZERO;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:9]: (error) Array 'tab4[20]' accessed at index 20, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void f() {\n" // #8721 " unsigned char **cache = malloc(32);\n" " cache[i] = malloc(65536);\n" " cache[i][0xFFFF] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " int **a = malloc(2 * sizeof(int*));\n" " for (int i = 0; i < 3; i++)\n" " a[i] = NULL;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:6]: (error) Array 'a[2]' accessed at index 2, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void f() {\n" " int **a = new int*[2];\n" " for (int i = 0; i < 3; i++)\n" " a[i] = NULL;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:6]: (error) Array 'a[2]' accessed at index 2, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void f(const uint8_t* a) {\n" // 10421 @@ -4191,21 +4440,21 @@ class TestBufferOverrun : public TestFixture { "{\n" " const char *s = \"123\";\n" " s[10] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:6]: (error) Array 's[4]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void foo()\n" "{\n" " char *s; s = \"\";\n" " s[10] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:6]: (error) Array 's[1]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); check("void foo() {\n" " const char *s = \"\";\n" " s = y();\n" " s[10] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo()\n" // #7718 @@ -4213,7 +4462,7 @@ class TestBufferOverrun : public TestFixture { " std::string s = \"123\";\n" " s.resize(100);\n" " s[10] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -4223,7 +4472,7 @@ class TestBufferOverrun : public TestFixture { "{\n" " char *s = (char *)alloca(10);\n" " s[10] = 0;\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:4:6]: (error) Array 's[10]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]\n", "", errout_str()); } /* @@ -4320,13 +4569,13 @@ class TestBufferOverrun : public TestFixture { check("void f() {\n" " char c[10];\n" " mymemset(c, 0, 10);\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " char c[10];\n" " mymemset(c, 0, 11);\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("[test.cpp:3:14]: (error) Buffer is accessed out of bounds: c [bufferAccessOutOfBounds]\n", errout_str()); check("struct S {\n" @@ -4335,13 +4584,13 @@ class TestBufferOverrun : public TestFixture { "void f() {\n" " S s;\n" " mymemset(s.a, 0, 10);\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("[test.cpp:6:15]: (error) Buffer is accessed out of bounds: s.a [bufferAccessOutOfBounds]\n", errout_str()); check("void foo() {\n" " char s[10];\n" " mymemset(s, 0, '*');\n" - "}", settings); + "}\n", settings); TODO_ASSERT_EQUALS("[test.cpp:3]: (warning) The size argument is given as a char constant.\n" "[test.cpp:3:14]: (error) Buffer is accessed out of bounds: s [bufferAccessOutOfBounds]\n", "[test.cpp:3:14]: (error) Buffer is accessed out of bounds: s [bufferAccessOutOfBounds]\n", errout_str()); @@ -4349,65 +4598,65 @@ class TestBufferOverrun : public TestFixture { check("void f(void) {\n" " char a[10];\n" " mymemset(a+5, 0, 10);\n" - "}", settings); + "}\n", settings); TODO_ASSERT_EQUALS("[test.cpp:3:13]: (error) Buffer is accessed out of bounds: a [bufferAccessOutOfBounds]\n", "", errout_str()); // Ticket #909 check("void f(void) {\n" " char str[] = \"abcd\";\n" " mymemset(str, 0, 6);\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("[test.cpp:3:14]: (error) Buffer is accessed out of bounds: str [bufferAccessOutOfBounds]\n", errout_str()); check("void f(void) {\n" " char str[] = \"abcd\";\n" " mymemset(str, 0, 5);\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("", errout_str()); check("void f(void) {\n" " wchar_t str[] = L\"abcd\";\n" " mymemset(str, 0, 21);\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("[test.cpp:3:14]: (error) Buffer is accessed out of bounds: str [bufferAccessOutOfBounds]\n", errout_str()); check("void f(void) {\n" " wchar_t str[] = L\"abcd\";\n" " mymemset(str, 0, 20);\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("", errout_str()); // ticket #1659 - overflowing variable when using memcpy check("void f(void) {\n" " char c;\n" " mymemset(&c, 0, 4);\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("[test.cpp:3:12]: (error) Buffer is accessed out of bounds: &c [bufferAccessOutOfBounds]\n", errout_str()); // ticket #2121 - buffer access out of bounds when using uint32_t check("void f(void) {\n" " unknown_type_t buf[4];\n" " mymemset(buf, 0, 100);\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("", errout_str()); // #3124 - multidimensional array check("int main() {\n" " char b[5][6];\n" " mymemset(b, 0, 5 * 6);\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("", errout_str()); check("int main() {\n" " char b[5][6];\n" " mymemset(b, 0, 6 * 6);\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("[test.cpp:3:14]: (error) Buffer is accessed out of bounds: b [bufferAccessOutOfBounds]\n", errout_str()); check("int main() {\n" " char b[5][6];\n" " mymemset(b, 0, 31);\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("[test.cpp:3:14]: (error) Buffer is accessed out of bounds: b [bufferAccessOutOfBounds]\n", errout_str()); // #4968 - not standard function @@ -4416,26 +4665,25 @@ class TestBufferOverrun : public TestFixture { " foo.mymemset(str, 0, 100);\n" " foo::mymemset(str, 0, 100);\n" " std::mymemset(str, 0, 100);\n" - "}", settings); + "}\n", settings); TODO_ASSERT_EQUALS("[test.cpp:5:15]: (error) Buffer is accessed out of bounds: str [bufferAccessOutOfBounds]\n", "", errout_str()); // #5257 - check strings check("void f() {\n" " mymemset(\"abc\", 0, 20);\n" - "}", settings); - TODO_ASSERT_EQUALS("[test.cpp:2]: (error) Buffer is accessed out of bounds.\n", - "", - errout_str()); + "}\n", settings); + ASSERT_EQUALS("[test.cpp:2:12]: (error) Buffer is accessed out of bounds: \"abc\" [bufferAccessOutOfBounds]\n", + errout_str()); check("void f() {\n" " mymemset(temp, \"abc\", 4);\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("", errout_str()); check("void f() {\n" // #6816 - fp when array has known string value " char c[10] = \"c\";\n" " mymemset(c, 0, 10);\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("", errout_str()); } @@ -4457,43 +4705,43 @@ class TestBufferOverrun : public TestFixture { check("void f() {\n" " char c[7];\n" " mystrncpy(c, \"hello\", 7);\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " char c[6];\n" " mystrncpy(c,\"hello\",6);\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " char c[5];\n" " mystrncpy(c,\"hello\",6);\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("[test.cpp:3:12]: (error) Buffer is accessed out of bounds: c [bufferAccessOutOfBounds]\n", errout_str()); check("void f() {\n" " char c[6];\n" " mystrncpy(c,\"hello!\",7);\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("[test.cpp:3:15]: (error) Buffer is accessed out of bounds: c [bufferAccessOutOfBounds]\n", errout_str()); check("void f(unsigned int addr) {\n" " memset((void *)addr, 0, 1000);\n" - "}", settings0); // TODO: use settings? + "}\n", settings0); // TODO: use settings? ASSERT_EQUALS("", errout_str()); check("struct AB { char a[10]; };\n" "void foo(AB *ab) {\n" " mystrncpy(x, ab->a, 100);\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("", errout_str()); check("void a(char *p) { mystrncpy(p,\"hello world!\",10); }\n" // #3168 "void b() {\n" " char buf[5];\n" " a(buf);" - "}", settings); + "}\n", settings); TODO_ASSERT_EQUALS("[test.cpp:4] -> [test.cpp:1]: (error) Buffer is accessed out of bounds: buf\n", "", errout_str()); @@ -4519,13 +4767,13 @@ class TestBufferOverrun : public TestFixture { check("void f() {\n" " char str[3];\n" " mysprintf(str, \"test\");\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("[test.cpp:3:15]: (error) Buffer is accessed out of bounds: str [bufferAccessOutOfBounds]\n", errout_str()); check("void f() {\n" " char str[5];\n" " mysprintf(str, \"%s\", \"abcde\");\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("[test.cpp:3:15]: (error) Buffer is accessed out of bounds: str [bufferAccessOutOfBounds]\n", errout_str()); check("int getnumber();\n" @@ -4533,51 +4781,51 @@ class TestBufferOverrun : public TestFixture { "{\n" " char str[5];\n" " mysprintf(str, \"%d: %s\", getnumber(), \"abcde\");\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("[test.cpp:5:15]: (error) Buffer is accessed out of bounds: str [bufferAccessOutOfBounds]\n", errout_str()); check("void f() {\n" " char str[5];\n" " mysprintf(str, \"test%s\", \"\");\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " char *str = new char[5];\n" " mysprintf(str, \"abcde\");\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("[test.cpp:3:15]: (error) Buffer is accessed out of bounds: str [bufferAccessOutOfBounds]\n", errout_str()); check("void f(int condition) {\n" " char str[5];\n" " mysprintf(str, \"test%s\", condition ? \"12\" : \"34\");\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("", errout_str()); check("void f(int condition) {\n" " char str[5];\n" " mysprintf(str, \"test%s\", condition ? \"12\" : \"345\");\n" - "}", settings); + "}\n", settings); TODO_ASSERT_EQUALS("error", "", errout_str()); check("struct Foo { char a[1]; };\n" "void f() {\n" " struct Foo x;\n" " mysprintf(x.a, \"aa\");\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("[test.cpp:4:14]: (error) Buffer is accessed out of bounds: x.a [bufferAccessOutOfBounds]\n", errout_str()); // ticket #900 check("void f() {\n" " char *a = new char(30);\n" " mysprintf(a, \"a\");\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("[test.cpp:3:13]: (error) Buffer is accessed out of bounds: a [bufferAccessOutOfBounds]\n", errout_str()); check("void f(char value) {\n" " char *a = new char(value);\n" " mysprintf(a, \"a\");\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("[test.cpp:3:13]: (error) Buffer is accessed out of bounds: a [bufferAccessOutOfBounds]\n", errout_str()); // This is out of bounds if 'sizeof(ABC)' is 1 (No padding) @@ -4585,21 +4833,21 @@ class TestBufferOverrun : public TestFixture { "void f() {\n" " struct Foo *x = malloc(sizeof(Foo));\n" " mysprintf(x->a, \"aa\");\n" - "}", settings); + "}\n", settings); TODO_ASSERT_EQUALS("[test.cpp:4]: (error, inconclusive) Buffer is accessed out of bounds: x.a\n", "", errout_str()); check("struct Foo { char a[1]; };\n" "void f() {\n" " struct Foo *x = malloc(sizeof(Foo) + 10);\n" " mysprintf(x->a, \"aa\");\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("", errout_str()); check("struct Foo { char a[1]; };\n" "void f() {\n" " struct Foo x;\n" " mysprintf(x.a, \"aa\");\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("[test.cpp:4:14]: (error) Buffer is accessed out of bounds: x.a [bufferAccessOutOfBounds]\n", errout_str()); check("struct Foo {\n" // #6668 - unknown size @@ -4608,8 +4856,43 @@ class TestBufferOverrun : public TestFixture { "};" "void Foo::f() {\n" " mysprintf(a, \"abcd\");\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("", errout_str()); + + check("void f() {\n" // #901 + " const char b[] = \"b\";\n" + " char a[1];\n" + " sprintf(a, \"%s\", b);\n" + "}\n" + "void g() {\n" + " const char* b = \"b\";\n" + " char a[1];\n" + " sprintf(a, \"%s\", b);\n" + "}\n" + "void h() {\n" + " const std::string b = \"b\";\n" + " char a[1];\n" + " sprintf(a, \"%s\", b.c_str());\n" + "}\n" + "void i() {\n" + " const char b[] = \"b\";\n" + " char a[2];\n" + " sprintf(a, \"%s\", b);\n" + "}\n" + "void j() {\n" + " const char* b = \"b\";\n" + " char a[2];\n" + " sprintf(a, \"%s\", b);\n" + "}\n" + "void k() {\n" + " const std::string b = \"b\";\n" + " char a[2];\n" + " sprintf(a, \"%s\", b.c_str());\n" + "}\n", settings0); + ASSERT_EQUALS("[test.cpp:4:13]: (error) Buffer is accessed out of bounds: a [bufferAccessOutOfBounds]\n" + "[test.cpp:9:13]: (error) Buffer is accessed out of bounds: a [bufferAccessOutOfBounds]\n" + "[test.cpp:14:13]: (error) Buffer is accessed out of bounds: a [bufferAccessOutOfBounds]\n", + errout_str()); } void minsize_mul() { @@ -4629,23 +4912,50 @@ class TestBufferOverrun : public TestFixture { check("void f() {\n" " char c[5];\n" " myfread(c, 1, 5, stdin);\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " char c[5];\n" " myfread(c, 1, 6, stdin);\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("[test.cpp:3:13]: (error) Buffer is accessed out of bounds: c [bufferAccessOutOfBounds]\n", errout_str()); } + void minsize_overflow() { + // the computed minimum size must not wrap around + check("void f() {\n" + " char buf[10];\n" + " sprintf(buf, \"%2000000000d%2000000000d\", 1, 2);\n" + "}\n", settings0); + ASSERT_EQUALS("[test.cpp:3:13]: (error) Buffer is accessed out of bounds: buf [bufferAccessOutOfBounds]\n", errout_str()); + + check("void f() {\n" + " char buf[10];\n" + " sprintf(buf, \"%4294967296d\", 1);\n" + "}\n", settings0); + ASSERT_EQUALS("[test.cpp:3:13]: (error) Buffer is accessed out of bounds: buf [bufferAccessOutOfBounds]\n", errout_str()); + + check("void f(FILE *fp) {\n" + " char buf[10];\n" + " fread(buf, 0x100000000LL, 0x100000000LL, fp);\n" + "}\n", settings0); + ASSERT_EQUALS("[test.cpp:3:11]: (error) Buffer is accessed out of bounds: buf [bufferAccessOutOfBounds]\n", errout_str()); + + check("void f() {\n" + " wchar_t buf[10];\n" + " (void)wmemchr(buf, L'a', 0x4000000000000000LL);\n" + "}\n", settings0); + ASSERT_EQUALS("[test.cpp:3:19]: (error) Buffer is accessed out of bounds: buf [bufferAccessOutOfBounds]\n", errout_str()); + } + // extracttests.enable void unknownType() { check("void f()\n" "{\n" " UnknownType *a = malloc(4);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } // extracttests.disable @@ -4657,26 +4967,26 @@ class TestBufferOverrun : public TestFixture { " baz[99] = 0;\n" " strncpy(baz, bar, 100);\n" " baz[99] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo ( char *bar ) {\n" " char baz[100];\n" " strncpy(baz, bar, 100);\n" " baz[99] = '\\0';\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo ( char *bar ) {\n" " char baz[100];\n" " strncpy(baz, bar, 100);\n" " baz[x+1] = '\\0';\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Test with invalid code that there is no segfault check("char baz[100];\n" - "strncpy(baz, \"var\", 100)"); + "strncpy(baz, \"var\", 100)\n"); ASSERT_EQUALS("", errout_str()); // Test that there are no duplicate error messages @@ -4685,7 +4995,7 @@ class TestBufferOverrun : public TestFixture { " strncpy(baz, bar, 100);\n" " foo(baz);\n" " foo(baz);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (warning, inconclusive) The buffer 'baz' may not be null-terminated after the call to strncpy(). [terminateStrncpy]\n", errout_str()); } @@ -4695,7 +5005,7 @@ class TestBufferOverrun : public TestFixture { " strncpy(baz, bar, 100);\n" " bar[99] = 0;\n" " return strdup(baz);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (warning, inconclusive) The buffer 'baz' may not be null-terminated after the call to strncpy(). [terminateStrncpy]\n", errout_str()); } @@ -4718,13 +5028,13 @@ class TestBufferOverrun : public TestFixture { check("void bar() {\n" " char buf[4];\n" " strncpy(buf, \"ab\", 4);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void bar() {\n" " char buf[4];\n" " strncpy(buf, \"abcde\", 4);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (warning, inconclusive) The buffer 'buf' may not be null-terminated after the call to strncpy(). [terminateStrncpy]\n", errout_str()); } @@ -4750,13 +5060,13 @@ class TestBufferOverrun : public TestFixture { check("void foo() {\n" " char c[6];\n" " strncpy(&c, \"hello!\", 6);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:3]: (warning, inconclusive) The buffer 'c' may not be null-terminated after the call to strncpy(). [terminateStrncpy]\n", errout_str()); check("void foo() {\n" " char c[6];\n" " strncpy(&c, \"hello\\0\", 6);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -4784,7 +5094,7 @@ class TestBufferOverrun : public TestFixture { "{\n" " char a[10];\n" " f2(a);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -4800,7 +5110,7 @@ class TestBufferOverrun : public TestFixture { "{\n" " struct A *str;\n" " str = malloc(4);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -4812,12 +5122,12 @@ class TestBufferOverrun : public TestFixture { "void b() {\n" " char arr[64];\n" " a(arr);\n" - "}"); + "}\n"); } void crash3() { check("struct b { unknown v[0]; };\n" - "void d() { struct b *f; f = malloc(108); }"); + "void d() { struct b *f; f = malloc(108); }\n"); } void crash4() { // #8679 @@ -4825,13 +5135,13 @@ class TestBufferOverrun : public TestFixture { "int main() { " " thread_local_var = malloc(1337); " " return 0; " - "}"); + "}\n"); check("thread_local void *thread_local_var; " "int main() { " " thread_local_var = malloc(1337); " " return 0; " - "}"); + "}\n"); } void crash5() { // 8644 - token has varId() but variable() is null @@ -4839,19 +5149,19 @@ class TestBufferOverrun : public TestFixture { " void b(char **dst) {\n" " *dst = malloc(50);\n" " }\n" - "}"); + "}\n"); } void crash6() { check("void start(char* name) {\n" "char snapname[64] = { 0 };\n" "strncpy(snapname, \"snapshot\", arrayLength(snapname));\n" - "}"); + "}\n"); } void crash7() { // 9073 - [ has no astParent check("char x[10];\n" - "void f() { x[10]; }"); + "void f() { x[10]; }\n"); } void insecureCmdLineArgs() { @@ -4865,7 +5175,7 @@ class TestBufferOverrun : public TestFixture { " free(p);\n" " }\n" " return 0;\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:7]: (error) Buffer overrun possible for long command line arguments.\n", "", errout_str()); check("int main(int argc, char *argv[])\n" @@ -4878,112 +5188,112 @@ class TestBufferOverrun : public TestFixture { " free(p);\n" " }\n" " return 0;\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:7]: (error) Buffer overrun possible for long command line arguments.\n", "", errout_str()); check("int main(const int argc, char* argv[])\n" "{\n" " char prog[10];\n" " strcpy(prog, argv[0]);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:4]: (error) Buffer overrun possible for long command line arguments.\n", "", errout_str()); check("int main(int argc, const char* argv[])\n" "{\n" " char prog[10];\n" " strcpy(prog, argv[0]);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:4]: (error) Buffer overrun possible for long command line arguments.\n", "", errout_str()); check("int main(const int argc, const char* argv[])\n" "{\n" " char prog[10];\n" " strcpy(prog, argv[0]);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:4]: (error) Buffer overrun possible for long command line arguments.\n", "", errout_str()); check("int main(int argc, char* argv[])\n" "{\n" " char prog[10] = {'\\0'};\n" " strcat(prog, argv[0]);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:4]: (error) Buffer overrun possible for long command line arguments.\n", "", errout_str()); check("int main(int argc, char **argv, char **envp)\n" "{\n" " char prog[10];\n" " strcpy(prog, argv[0]);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:4]: (error) Buffer overrun possible for long command line arguments.\n", "", errout_str()); check("int main(int argc, const char *const *const argv, char **envp)\n" "{\n" " char prog[10];\n" " strcpy(prog, argv[0]);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:4]: (error) Buffer overrun possible for long command line arguments.\n", "", errout_str()); check("int main(const int argc, const char *const *const argv, const char *const *const envp)\n" "{\n" " char prog[10];\n" " strcpy(prog, argv[0]);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:4]: (error) Buffer overrun possible for long command line arguments.\n", "", errout_str()); check("int main(int argc, char **argv, char **envp)\n" "{\n" " char prog[10] = {'\\0'};\n" " strcat(prog, argv[0]);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:4]: (error) Buffer overrun possible for long command line arguments.\n", "", errout_str()); check("int main(const int argc, const char **argv, char **envp)\n" "{\n" " char prog[10] = {'\\0'};\n" " strcat(prog, argv[0]);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:4]: (error) Buffer overrun possible for long command line arguments.\n", "", errout_str()); check("int main(int argc, const char **argv, char **envp)\n" "{\n" " char prog[10] = {'\\0'};\n" " strcat(prog, argv[0]);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:4]: (error) Buffer overrun possible for long command line arguments.\n", "", errout_str()); check("int main(const int argc, char **argv, char **envp)\n" "{\n" " char prog[10] = {'\\0'};\n" " strcat(prog, argv[0]);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:4]: (error) Buffer overrun possible for long command line arguments.\n", "", errout_str()); check("int main(int argc, char **options)\n" "{\n" " char prog[10];\n" " strcpy(prog, options[0]);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:4]: (error) Buffer overrun possible for long command line arguments.\n", "", errout_str()); check("int main(int argc, char **options)\n" "{\n" " char prog[10] = {'\\0'};\n" " strcat(prog, options[0]);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:4]: (error) Buffer overrun possible for long command line arguments.\n", "", errout_str()); check("int main(int argc, char **options)\n" "{\n" " char prog[10];\n" " strcpy(prog, *options);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:4]: (error) Buffer overrun possible for long command line arguments.\n", "", errout_str()); check("int main(int argc, char **options)\n" "{\n" " char prog[10];\n" " strcpy(prog+3, *options);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:4]: (error) Buffer overrun possible for long command line arguments.\n", "", errout_str()); check("int main(int argc, char **argv, char **envp)\n" @@ -4991,7 +5301,7 @@ class TestBufferOverrun : public TestFixture { " char prog[10];\n" " if (strlen(argv[0]) < 10)\n" " strcpy(prog, argv[0]);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int main(int argc, char **argv, char **envp)\n" @@ -4999,7 +5309,7 @@ class TestBufferOverrun : public TestFixture { " char prog[10] = {'\\0'};\n" " if (10 > strlen(argv[0]))\n" " strcat(prog, argv[0]);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int main(int argc, char **argv, char **envp)\n" @@ -5007,7 +5317,7 @@ class TestBufferOverrun : public TestFixture { " char prog[10];\n" " argv[0][0] = '\\0';\n" " strcpy(prog, argv[0]);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #5835 @@ -5015,18 +5325,18 @@ class TestBufferOverrun : public TestFixture { " char prog[10];\n" " strcpy(prog, argv[0]);\n" " strcpy(prog, argv[0]);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:3]: (error) Buffer overrun possible for long command line arguments.\n" "[test.cpp:4]: (error) Buffer overrun possible for long command line arguments.\n", "", errout_str()); // #7964 check("int main(int argc, char *argv[]) {\n" " char *strcpy();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int main(int argc, char *argv[]) {\n" " char *strcat();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -5034,33 +5344,33 @@ class TestBufferOverrun : public TestFixture { check("void f(char *a) {\n" " char *b = new char[strlen(a)];\n" " strcpy(b, a);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:3]: (error) Buffer is accessed out of bounds.\n", "", errout_str()); check("void f(char *a) {\n" " char *b = new char[strlen(a) + 1];\n" " strcpy(b, a);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(char *a) {\n" " char *b = new char[strlen(a)];\n" " a[0] = '\\0';\n" " strcpy(b, a);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(char *a) {\n" " char *b = (char *)malloc(strlen(a));\n" " b = realloc(b, 10000);\n" " strcpy(b, a);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(char *a) {\n" " char *b = (char *)malloc(strlen(a));\n" " strcpy(b, a);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:3]: (error) Buffer is accessed out of bounds.\n", "", errout_str()); check("void f(char *a) {\n" @@ -5068,31 +5378,31 @@ class TestBufferOverrun : public TestFixture { " {\n" " strcpy(b, a);\n" " }\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:4]: (error) Buffer is accessed out of bounds.\n", "", errout_str()); check("void f(char *a) {\n" " char *b = (char *)malloc(strlen(a) + 1);\n" " strcpy(b, a);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(char *a, char *c) {\n" " char *b = (char *)realloc(c, strlen(a));\n" " strcpy(b, a);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:3]: (error) Buffer is accessed out of bounds.\n", "", errout_str()); check("void f(char *a, char *c) {\n" " char *b = (char *)realloc(c, strlen(a) + 1);\n" " strcpy(b, a);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(char *a) {\n" " char *b = (char *)malloc(strlen(a));\n" " strcpy(b, a);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:3]: (error) Buffer is accessed out of bounds.\n", "", errout_str()); } @@ -5106,7 +5416,7 @@ class TestBufferOverrun : public TestFixture { "{\n" " X x;\n" " x.buf[10] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("class A {\n" @@ -5118,14 +5428,15 @@ class TestBufferOverrun : public TestFixture { "{\n" " A::X x;\n" " x.buf[10] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:9:10]: (error) Array 'x.buf[10]' accessed at index 10, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); } void getErrorMessages() { // Ticket #2292: segmentation fault when using --errorlist - const Check& c = getCheck(); - c.getErrorMessages(this, nullptr); + CheckBufferOverrun check; + const Check& c = getCheck(check); + c.getErrorMessages(*this, settingsDefault); // we are not interested in the output - just consume it ignore_errout(); } @@ -5136,68 +5447,68 @@ class TestBufferOverrun : public TestFixture { check("void f(const char s[]) {\n" " if (s[i] == 'x' && i < y) {\n" " }" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // No message because i is unknown and thus gets no varid. Avoid an internalError here. check("void f(const char s[], int i) {\n" " if (s[i] == 'x' && i < y) {\n" " }" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:10]: (style) Array index 'i' is used before limits check. [arrayIndexThenCheck]\n", errout_str()); check("void f(const char s[]) {\n" " for (int i = 0; s[i] == 'x' && i < y; ++i) {\n" " }" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:22]: (style) Array index 'i' is used before limits check. [arrayIndexThenCheck]\n", errout_str()); check("void f(const int a[], unsigned i) {\n" " if((a[i] < 2) && (i <= 42)) {\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:10]: (style) Array index 'i' is used before limits check. [arrayIndexThenCheck]\n", errout_str()); check("void f(const int a[], unsigned i) {\n" " if((a[i] < 2) && (42 >= i)) {\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:10]: (style) Array index 'i' is used before limits check. [arrayIndexThenCheck]\n", errout_str()); // extracttests.start: int elen; check("void f(char* e, int y) {\n" " if (e[y] == '/' && elen > y + 1 && e[y + 1] == '?') {\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // extracttests.start: int foo(int); int func(int); check("void f(const int a[], unsigned i) {\n" " if(a[i] < func(i) && i <= 42) {\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9]: (style) Array index 'i' is used before limits check. [arrayIndexThenCheck]\n", errout_str()); check("void f(const int a[], unsigned i) {\n" " if (i <= 42 && a[i] < func(i)) {\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(const int a[], unsigned i) {\n" " if (foo(a[i] + 3) < func(i) && i <= 42) {\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:14]: (style) Array index 'i' is used before limits check. [arrayIndexThenCheck]\n", errout_str()); check("void f(int i) {\n" // sizeof " sizeof(a)/sizeof(a[i]) && i < 10;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // extracttests.start: extern int buf[]; check("void f(int i) {\n" // ?: " if ((i < 10 ? buf[i] : 1) && (i < 5 ? buf[i] : 5)){}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -5220,19 +5531,19 @@ class TestBufferOverrun : public TestFixture { check("void f() {\n" " char c[6];\n" " strncpy(c,\"hello!\",6);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (warning, inconclusive) The buffer 'c' may not be null-terminated after the call to strncpy(). [terminateStrncpy]\n", errout_str()); check("void f() {\n" " char c[6];\n" " memcpy(c,\"hello!\",6);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:3]: (warning, inconclusive) The buffer 'c' may not be null-terminated after the call to memcpy().\n", "", errout_str()); check("void f() {\n" " char c[6];\n" " memmove(c,\"hello!\",6);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:3]: (warning, inconclusive) The buffer 'c' may not be null-terminated after the call to memmove().\n", "", errout_str()); } @@ -5242,7 +5553,7 @@ class TestBufferOverrun : public TestFixture { " int *a;\n" " a = new int[-1];\n" " delete [] a;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:8]: (error) Memory allocation size is negative. [negativeMemoryAllocationSize]\n", errout_str()); check("void f()\n" @@ -5250,7 +5561,7 @@ class TestBufferOverrun : public TestFixture { " int *a;\n" " a = (int *)malloc( -10 );\n" " free(a);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:4:8]: (error) Memory allocation size is negative. [negativeMemoryAllocationSize]\n", "", errout_str()); check("void f()\n" @@ -5258,14 +5569,14 @@ class TestBufferOverrun : public TestFixture { " int *a;\n" " a = (int *)malloc( -10);\n" " free(a);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:4:8]: (error) Memory allocation size is negative. [negativeMemoryAllocationSize]\n", "", errout_str()); check("void f()\n" "{\n" " int *a;\n" " a = (int *)alloca( -10 );\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:4:8]: (error) Memory allocation size is negative. [negativeMemoryAllocationSize]\n", "", errout_str()); check("int* f(int n) {\n" // #11145 @@ -5282,14 +5593,14 @@ class TestBufferOverrun : public TestFixture { check("void f(int sz) {\n" // #1760 - VLA " int a[sz];\n" "}\n" - "void x() { f(-100); }"); + "void x() { f(-100); }\n"); ASSERT_EQUALS("[test.cpp:2:8]: (error) Declaration of array 'a' with negative size is undefined behaviour [negativeArraySize]\n", errout_str()); // don't warn for constant sizes -> this is a compiler error so this is used for static assertions for instance check("int x, y;\n" "int a[-1];\n" "int b[x?1:-1];\n" - "int c[x?y:-1];"); + "int c[x?y:-1];\n"); ASSERT_EQUALS("", errout_str()); } @@ -5297,7 +5608,7 @@ class TestBufferOverrun : public TestFixture { check("void f() {\n" " char arr[10];\n" " char *p = arr + 20;\n" - "}", settings0_p); + "}\n", settings0_p); ASSERT_EQUALS("[test.cpp:3:19]: (portability) Undefined behaviour, pointer arithmetic 'arr+20' is out of bounds. [pointerOutOfBounds]\n", errout_str()); check("char(*g())[1];\n" // #7950 @@ -5315,11 +5626,11 @@ class TestBufferOverrun : public TestFixture { SimpleTokenizer tokenizer(settings0, *this); ASSERT_LOC(tokenizer.tokenize(code), file, line); - CTU::FileInfo *ctu = CTU::getFileInfo(tokenizer); + const CTU::FileInfo *ctu = CTU::getFileInfo(tokenizer); - // Check code.. - std::list fileInfo; - Check& c = getCheck(); + std::list fileInfo; + CheckBufferOverrun check; + Check& c = getCheck(check); fileInfo.push_back(c.getFileInfo(tokenizer, settings0, "")); c.analyseWholeProgram(*ctu, fileInfo, settings0, *this); // TODO: check result while (!fileInfo.empty()) { @@ -5337,7 +5648,7 @@ class TestBufferOverrun : public TestFixture { "int main() {\n" " char *s = malloc(4);\n" " dostuff(s);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:11] -> [test.cpp:7:10] -> [test.cpp:2:3]: (error) Array index out of bounds; buffer 'p' is accessed at offset -3. [ctuArrayIndex]\n", errout_str()); ctu("void dostuff(char *p) {\n" @@ -5347,7 +5658,7 @@ class TestBufferOverrun : public TestFixture { "int main() {\n" " char *s = malloc(4);\n" " dostuff(s);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:11] -> [test.cpp:7:10] -> [test.cpp:2:3]: (error) Array index out of bounds; 'p' buffer size is 4 and it is accessed at offset 4. [ctuArrayIndex]\n", errout_str()); ctu("void f(int* p) {\n" // #10415 @@ -5379,7 +5690,7 @@ class TestBufferOverrun : public TestFixture { "int main() {\n" " char str[4];\n" " dostuff(str);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:10] -> [test.cpp:2:5]: (error) Array index out of bounds; 'p' buffer size is 4 and it is accessed at offset 10. [ctuArrayIndex]\n", errout_str()); ctu("static void memclr( char *data )\n" @@ -5391,7 +5702,7 @@ class TestBufferOverrun : public TestFixture { "{\n" " char str[5];\n" " memclr( str );\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:9:11] -> [test.cpp:3:5]: (error) Array index out of bounds; 'data' buffer size is 5 and it is accessed at offset 10. [ctuArrayIndex]\n", errout_str()); ctu("static void memclr( int i, char *data )\n" @@ -5403,7 +5714,7 @@ class TestBufferOverrun : public TestFixture { "{\n" " char str[5];\n" " memclr( 0, str );\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:9:11] -> [test.cpp:3:5]: (error) Array index out of bounds; 'data' buffer size is 5 and it is accessed at offset 10. [ctuArrayIndex]\n", errout_str()); ctu("static void memclr( int i, char *data )\n" @@ -5415,7 +5726,7 @@ class TestBufferOverrun : public TestFixture { "{\n" " char str[5];\n" " memclr( 10, str );\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:9] -> [test.cpp:3]: (possible error) Array index out of bounds.\n", "", errout_str()); @@ -5430,7 +5741,7 @@ class TestBufferOverrun : public TestFixture { "{\n" " char str[5];\n" " memclr( str, 5 );\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #2097 @@ -5444,7 +5755,7 @@ class TestBufferOverrun : public TestFixture { "{\n" " int p[3];\n" " foo(p+1);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #9112 @@ -5457,7 +5768,7 @@ class TestBufferOverrun : public TestFixture { "{\n" " u8 macstrbuf[17] = { 0 };\n" " get_mac_address(macstrbuf);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #9788 @@ -5514,7 +5825,7 @@ class TestBufferOverrun : public TestFixture { "int main() {\n" " int x = 4;\n" " dostuff(&x);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:10] -> [test.cpp:2:5]: (error) Array index out of bounds; 'p' buffer size is 4 and it is accessed at offset 40. [ctuArrayIndex]\n", errout_str()); } @@ -5523,7 +5834,7 @@ class TestBufferOverrun : public TestFixture { "int main() {\n" " int x[3];\n" " dostuff(x);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:10] -> [test.cpp:1:28]: (error) Pointer arithmetic overflow; 'p' buffer size is 12 [ctuPointerArith]\n", errout_str()); ctu("void f(const char *p) {\n" // #11361 @@ -5542,7 +5853,7 @@ class TestBufferOverrun : public TestFixture { "void f() {\n" " char a[10] = {};\n" " g(a);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:12]: error: Pointer arithmetic overflow; 'p' buffer size is 10 [ctuPointerArith]\n" "[test.cpp:6:6]: note: Calling function g, 1st argument is accessed out of bounds\n" "[test.cpp:2:12]: note: Using argument p\n", @@ -5553,7 +5864,7 @@ class TestBufferOverrun : public TestFixture { check("int f() {\n" " int i;\n" " return (&i)[1];\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:3:13] -> [test.cpp:3:16]: (error) The address of variable 'i' is accessed at non-zero index. [objectIndex]\n", errout_str()); @@ -5561,7 +5872,7 @@ class TestBufferOverrun : public TestFixture { check("int f(int j) {\n" " int i;\n" " return (&i)[j];\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:3:13] -> [test.cpp:3:16]: (warning) The address of variable 'i' might be accessed at non-zero index. [objectIndex]\n", errout_str()); @@ -5569,34 +5880,34 @@ class TestBufferOverrun : public TestFixture { check("int f() {\n" " int i;\n" " return (&i)[0];\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int f(int * i) {\n" " return i[1];\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int f(std::vector i) {\n" " return i[1];\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int f(std::vector i) {\n" " return i.data()[1];\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int* f(std::vector& i) {\n" " return &(i[1]);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct A { int i; int j; };\n" "int f() {\n" " A x;\n" " return (&x.i)[0];\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct A { int i; int j; };\n" @@ -5604,7 +5915,7 @@ class TestBufferOverrun : public TestFixture { " A x;\n" " int * i = &x.i;\n" " return i[0];\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -5612,7 +5923,7 @@ class TestBufferOverrun : public TestFixture { " std::map m;\n" " m[0] = &x;\n" " m[1] = &x;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int f() {\n" @@ -5620,7 +5931,7 @@ class TestBufferOverrun : public TestFixture { " std::map m;\n" " m[0] = &x;\n" " return m[0][1];\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:4:10] -> [test.cpp:5:14]: (error) The address of variable 'x' is accessed at non-zero index. [objectIndex]\n", errout_str()); @@ -5630,7 +5941,7 @@ class TestBufferOverrun : public TestFixture { " std::map m;\n" " m[0] = &x;\n" " return m[0][1];\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:4:10] -> [test.cpp:5:14]: (error) The address of variable 'x' is accessed at non-zero index. [objectIndex]\n", errout_str()); @@ -5641,7 +5952,7 @@ class TestBufferOverrun : public TestFixture { " m[0] = &x;\n" " m[1] = y;\n" " return m[1][1];\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void print(char** test);\n" @@ -5711,7 +6022,7 @@ class TestBufferOverrun : public TestFixture { "void h() {\n" " const uint8_t u = 4;\n" " f(&u, N);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("uint32_t f(uint32_t u) {\n" // #10154 @@ -5771,7 +6082,29 @@ class TestBufferOverrun : public TestFixture { " (void)y[1];\n" " (void)y[2];\n" "}\n"); - TODO_ASSERT_EQUALS("error", "", errout_str()); + ASSERT_EQUALS("[test.cpp:7:20] -> [test.cpp:9:12]: (error) The address of variable 's.a' is accessed at non-zero index. [objectIndex]\n" + "[test.cpp:7:20] -> [test.cpp:10:12]: (error) The address of variable 's.a' is accessed at non-zero index. [objectIndex]\n", + errout_str()); + + check("const int N = 12;\n" // #14887 + "struct S {\n" + " void f() const;\n" + " int a[N];\n" + "};\n" + "struct T {\n" + " void f() const;\n" + " S s;\n" + "};\n" + "int g(const int* p) { return p[5]; }\n" + "void S::f() const {\n" + " const int* q = a;\n" + " g(q);\n" + "}\n" + "void T::f() const {\n" + " const int* q = s.a;\n" + " g(q);\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); } void checkPipeParameterSize() { // #3521 @@ -5783,7 +6116,7 @@ class TestBufferOverrun : public TestFixture { "if (pipe(pipefd) == -1) {\n" " return;\n" " }\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("[test.cpp:3:10]: (error) Buffer is accessed out of bounds: pipefd [bufferAccessOutOfBounds]\n", errout_str()); check("void f(){\n" @@ -5791,7 +6124,7 @@ class TestBufferOverrun : public TestFixture { "if (pipe(pipefd) == -1) {\n" " return;\n" " }\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("", errout_str()); check("void f(){\n" @@ -5799,7 +6132,7 @@ class TestBufferOverrun : public TestFixture { "if (pipe((int*)pipefd) == -1) {\n" " return;\n" " }\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("[test.cpp:3:10]: (error) Buffer is accessed out of bounds: (int*)pipefd [bufferAccessOutOfBounds]\n", errout_str()); check("void f(){\n" @@ -5807,7 +6140,7 @@ class TestBufferOverrun : public TestFixture { "if (pipe((int*)pipefd) == -1) {\n" " return;\n" " }\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("", errout_str()); } }; diff --git a/test/testcharvar.cpp b/test/testcharvar.cpp index 624efcff539..0c9c8dd4b5b 100644 --- a/test/testcharvar.cpp +++ b/test/testcharvar.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -47,7 +47,7 @@ class TestCharVar : public TestFixture { ASSERT_LOC(tokenizer.tokenize(code), file, line); // Check char variable usage.. - CheckOther checkOther(&tokenizer, &settings, this); + CheckOtherImpl checkOther(&tokenizer, settings, *this); checkOther.checkCharVariable(); } @@ -57,7 +57,7 @@ class TestCharVar : public TestFixture { "{\n" " unsigned char ch = 0x80;\n" " buf[ch] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int buf[256];\n" @@ -65,7 +65,7 @@ class TestCharVar : public TestFixture { "{\n" " char ch = 0x80;\n" " buf[ch] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:5]: (portability) 'char' type used as array index. [unknownSignCharArrayIndex]\n", errout_str()); check("int buf[256];\n" @@ -73,7 +73,7 @@ class TestCharVar : public TestFixture { "{\n" " char ch = 0;\n" " buf[ch] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int buf[256];\n" @@ -81,7 +81,7 @@ class TestCharVar : public TestFixture { "{\n" " signed char ch = 0;\n" " buf[ch] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int buf[256];\n" @@ -89,67 +89,67 @@ class TestCharVar : public TestFixture { "{\n" " char ch = 0x80;\n" " buf[ch] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:5]: (portability) 'char' type used as array index. [unknownSignCharArrayIndex]\n", errout_str()); check("int buf[256];\n" "void foo(signed char ch)\n" "{\n" " buf[ch] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int buf[256];\n" "void foo(char ch)\n" "{\n" " buf[ch] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(char* buf)\n" "{\n" " char ch = 0x80;" " buf[ch] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:24]: (portability) 'char' type used as array index. [unknownSignCharArrayIndex]\n", errout_str()); check("void foo(char* buf)\n" "{\n" " char ch = 0;" " buf[ch] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(char* buf)\n" "{\n" " buf['A'] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(char* buf, char ch)\n" "{\n" " buf[ch] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int flags[256];\n" "void foo(const char* str)\n" "{\n" " flags[*str] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int flags[256];\n" "void foo(const char* str)\n" "{\n" " flags[(unsigned char)*str] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(const char str[])\n" "{\n" " map[str] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -159,7 +159,7 @@ class TestCharVar : public TestFixture { "void bar(int i) {\n" " const char *s = \"abcde\";\n" " foo(s[i]);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -167,32 +167,32 @@ class TestCharVar : public TestFixture { check("void foo(int *result) {\n" " signed char ch = -1;\n" " *result = a | ch;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:17]: (warning) When using 'char' variables in bit operations, sign extension can generate unexpected results. [charBitOp]\n", errout_str()); check("void foo(int *result) {\n" " unsigned char ch = -1;\n" " *result = a | ch;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(char *result) {\n" " signed char ch = -1;\n" " *result = a | ch;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // 0x03 & .. check("void foo(int *result) {\n" " signed char ch = -1;\n" " *result = 0x03 | ch;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:20]: (warning) When using 'char' variables in bit operations, sign extension can generate unexpected results. [charBitOp]\n", errout_str()); check("void foo(int *result) {\n" " signed char ch = -1;\n" " *result = 0x03 & ch;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } }; diff --git a/test/testcheck.cpp b/test/testcheck.cpp index 8ea4cef38d8..254519da2eb 100644 --- a/test/testcheck.cpp +++ b/test/testcheck.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,6 +17,7 @@ */ #include "check.h" +#include "checks.h" #include "fixture.h" #include @@ -28,23 +29,12 @@ class TestCheck : public TestFixture { private: void run() override { - TEST_CASE(instancesSorted); TEST_CASE(classInfoFormat); } - void instancesSorted() const { - for (auto i = Check::instances().cbegin(); i != Check::instances().cend(); ++i) { - auto j = i; - ++j; - if (j != Check::instances().cend()) { - ASSERT_EQUALS(true, (*i)->name() < (*j)->name()); - } - } - } - void classInfoFormat() const { - for (auto i = Check::instances().cbegin(); i != Check::instances().cend(); ++i) { - const std::string info = (*i)->classInfo(); + for (const Check * const c : CheckInstances::get()) { + const std::string info = c->classInfo(); if (!info.empty()) { ASSERT('\n' != info[0]); // No \n in the beginning ASSERT('\n' == info.back()); // \n at end diff --git a/test/testcheckersreport.cpp b/test/testcheckersreport.cpp index eafe7b8848f..39a6f2a9997 100644 --- a/test/testcheckersreport.cpp +++ b/test/testcheckersreport.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/test/testclangimport.cpp b/test/testclangimport.cpp index 6a39b563982..a22f901a261 100644 --- a/test/testclangimport.cpp +++ b/test/testclangimport.cpp @@ -116,6 +116,8 @@ class TestClangImport : public TestFixture { TEST_CASE(vardecl5); TEST_CASE(vardecl6); TEST_CASE(vardecl7); + TEST_CASE(vardeclInHeader); + TEST_CASE(vardeclBuiltinLocation); TEST_CASE(whileStmt1); TEST_CASE(whileStmt2); @@ -139,6 +141,10 @@ class TestClangImport : public TestFixture { TEST_CASE(valueType2); TEST_CASE(crash); + TEST_CASE(crash2); + TEST_CASE(crash3); + TEST_CASE(nullNodeInvalidLevel); + TEST_CASE(invalidExtTokens); } std::string parse(const char clang[]) { @@ -1039,6 +1045,45 @@ class TestClangImport : public TestFixture { ASSERT_EQUALS("void * * start@1 ;", parse(clang)); } + void vardeclInHeader() { + // a new file is seen after tokens have been created + const char clang[] = "|-VarDecl 0x32b8aa0 <1.c:1:1, col:5> col:5 a 'int'\n" + "`-VarDecl 0x32b8b78 <1.h:1:1, col:5> col:5 b 'int'"; + + ASSERT_EQUALS("int a@1 ; int b@2 ;", + parse(clang)); + } + + void vardeclBuiltinLocation() { + // #15100 - the location starts in a buffer that is generated by clang + const char clang[] = "|-VarDecl 0x32b8aa0 <1.c:1:1, col:5> col:5 a 'int'\n" + "|-VarDecl 0x32b8b78 <:115:23, 1.h:18:19> col:19 b 'unsigned int'\n" + "|-VarDecl 0x32b8c50 <:2:1, col:5> col:5 c 'int'\n" + "`-VarDecl 0x32b8d28 <:41:1> col:5 d 'int'"; + + ASSERT_EQUALS("int a@1 ; unsigned int b@2 ; int c@3 ; int d@4 ;", + parse(clang)); + + const Settings settings = settingsBuilder().clang().build(); + TokenList tokenlist{settings, Standards::Language::C}; + Tokenizer tokenizer(std::move(tokenlist), *this); + std::istringstream istr(clang); + clangimport::parseClangAstDump(tokenizer, istr); + + // the end of the range is used for "b", the location is unchanged for "c" and "d" + ASSERT_EQUALS(2, tokenizer.list.getFiles().size()); + ASSERT_EQUALS("1.c", tokenizer.list.getFiles()[0]); + ASSERT_EQUALS("1.h", tokenizer.list.getFiles()[1]); + const Token *tok = Token::findsimplematch(tokenizer.tokens(), "b"); + ASSERT(tok); + ASSERT_EQUALS(1, tok->fileIndex()); + ASSERT_EQUALS(18, tok->linenr()); + tok = Token::findsimplematch(tokenizer.tokens(), "d"); + ASSERT(tok); + ASSERT_EQUALS(1, tok->fileIndex()); + ASSERT_EQUALS(18, tok->linenr()); + } + void whileStmt1() { const char clang[] = "`-FunctionDecl 0x3d45b18 <1.c:1:1, line:3:1> line:1:6 foo 'void ()'\n" " `-CompoundStmt 0x3d45c48 \n" @@ -1372,6 +1417,55 @@ class TestClangImport : public TestFixture { " `-CompoundStmt 0x5603791b5700 \n"; (void)parse(clang); // don't crash } + + void crash2() { + // getSpelling() indexed mExtTokens[typeIndex - 1] without a lower-bound + // check, so a node whose line carries no ext tokens (typeIndex <= 0) read + // out of bounds. + ASSERT_THROW_INTERNAL_EQUALS(parse("`-RecordDecl "), AST, "invalid AST node: RecordDecl"); + } + + void nullNodeInvalidLevel() { + // a "<<>>" line whose indentation maps to level 0 must not index tree[-1] + const char* clang = "`-FunctionDecl 0x1 col:6 foo 'void ()'\n" + "`-<<>>\n"; + ASSERT_EQUALS("void foo ( ) ;", parse(clang)); + } + + void crash3() { + // GotoStmt, NamespaceDecl and CXXRecordDecl read mExtTokens[size() - 2] + // without checking the node has two ext tokens, so a crafted node with + // fewer tokens indexed out of bounds. + (void)parse("`-FunctionDecl 0x1 col:6 foo 'void ()'\n" + " `-CompoundStmt 0x2 \n" + " `-GotoStmt 0x3\n"); // don't crash + (void)parse("`-NamespaceDecl 0x1\n" + " `-CXXRecordDecl 0x2\n"); // don't crash + (void)parse("`-CXXRecordDecl 0x1"); // don't crash + } + + void invalidExtTokens() { + // nodes without the ext tokens the handlers read must not index out of bounds + ASSERT_THROW_INTERNAL_EQUALS(parse("`-FunctionDecl 0x1 col:5 f 'int ()'\n" + " `-CompoundStmt 0x2 \n" + " `-ReturnStmt 0x3 \n" + " `-IntegerLiteral \n"), AST, "invalid AST node: IntegerLiteral"); + ASSERT_THROW_INTERNAL_EQUALS(parse("`-EnumDecl "), AST, "invalid AST node: EnumDecl"); + ASSERT_THROW_INTERNAL_EQUALS(parse("`-VarDecl 0x1"), AST, "invalid AST node: VarDecl"); + ASSERT_THROW_INTERNAL_EQUALS(parse("`-VarDecl 0x1 'int'"), AST, "invalid AST node: VarDecl"); + ASSERT_THROW_INTERNAL_EQUALS(parse("`-VarDecl 0x1 "), AST, "invalid AST node: VarDecl"); + ASSERT_THROW_INTERNAL_EQUALS(parse("`-FieldDecl 0x1 'int'"), AST, "invalid AST node: FieldDecl"); + ASSERT_THROW_INTERNAL_EQUALS(parse("`-FunctionDecl 0x1 col:5 f 'int ()' prev"), AST, "invalid AST node: FunctionDecl"); + // address-only declarations: getSpelling() is empty, addtoken() adds nothing and back() is null + ASSERT_THROW_INTERNAL_EQUALS(parse("`-FunctionDecl 0x1"), AST, "invalid AST node: FunctionDecl"); + ASSERT_THROW_INTERNAL_EQUALS(parse("`-CXXConstructorDecl 0x1"), AST, "invalid AST node: CXXConstructorDecl"); + ASSERT_THROW_INTERNAL_EQUALS(parse("`-CXXMethodDecl 0x1"), AST, "invalid AST node: CXXMethodDecl"); + ASSERT_THROW_INTERNAL_EQUALS(parse("`-EnumConstantDecl 0x1"), AST, "invalid AST node: EnumConstantDecl"); + ASSERT_THROW_INTERNAL_EQUALS(parse("`-FunctionDecl 0x1 col:5 f 'int ()'\n" + " `-ParmVarDecl 0x2 \n"), AST, "invalid AST node: ParmVarDecl"); + ASSERT_THROW_INTERNAL_EQUALS(parse("`-FunctionDecl 0x1 col:5 f 'int ()'\n" + " `-ParmVarDecl 0x2 'int'\n"), AST, "invalid AST node: ParmVarDecl"); + } }; REGISTER_TEST(TestClangImport) diff --git a/test/testclass.cpp b/test/testclass.cpp index 62cf32cfba5..4449e368561 100644 --- a/test/testclass.cpp +++ b/test/testclass.cpp @@ -38,7 +38,7 @@ class TestClass : public TestFixture { const Settings settings0_i = settingsBuilder(settings0).certainty(Certainty::inconclusive).build(); const Settings settings1 = settingsBuilder().severity(Severity::warning).library("std.cfg").build(); const Settings settings2 = settingsBuilder().severity(Severity::style).library("std.cfg").certainty(Certainty::inconclusive).build(); - const Settings settings3 = settingsBuilder().severity(Severity::style).library("std.cfg").severity(Severity::warning).build(); + const Settings settings3 = settingsBuilder().severity(Severity::style).library("std.cfg").severity(Severity::warning).library("posix.cfg").build(); const Settings settings3_i = settingsBuilder(settings3).certainty(Certainty::inconclusive).build(); const Settings settings4 = settingsBuilder().severity(Severity::warning).severity(Severity::portability).library("std.cfg").library("posix.cfg").build(); @@ -62,6 +62,8 @@ class TestClass : public TestFixture { TEST_CASE(copyConstructor4); // base class with private constructor TEST_CASE(copyConstructor5); // multiple inheritance TEST_CASE(copyConstructor6); // array of pointers + TEST_CASE(copyConstructor7); // ticket #14954 + TEST_CASE(copyConstructor8); TEST_CASE(deletedMemberPointer); // deleted member pointer in destructor TEST_CASE(noOperatorEq); // class with memory management should have operator eq TEST_CASE(noDestructor); // class with memory management should have destructor @@ -82,6 +84,7 @@ class TestClass : public TestFixture { TEST_CASE(operatorEqToSelf7); TEST_CASE(operatorEqToSelf8); // ticket #2179 TEST_CASE(operatorEqToSelf9); // ticket #2592 + TEST_CASE(operatorEqToSelf10); // ticket #15156 TEST_CASE(memsetOnStruct); TEST_CASE(memsetVector); @@ -196,6 +199,9 @@ class TestClass : public TestFixture { TEST_CASE(const99); TEST_CASE(const100); TEST_CASE(const101); + TEST_CASE(const102); + TEST_CASE(const103); + TEST_CASE(const104); // #15104 TEST_CASE(const_handleDefaultParameters); TEST_CASE(const_passThisToMemberOfOtherClass); @@ -270,7 +276,7 @@ class TestClass : public TestFixture { ASSERT_LOC(tokenizer.tokenize(code), file, line); // Check.. - CheckClass checkClass(&tokenizer, &settings, this); + CheckClassImpl checkClass(&tokenizer, settings, *this); (checkClass.checkCopyCtorAndEqOperator)(); } @@ -279,25 +285,25 @@ class TestClass : public TestFixture { "{\n" " A(const A& other) { }\n" " A& operator=(const A& other) { return *this; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkCopyCtorAndEqOperator("class A\n" "{\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkCopyCtorAndEqOperator("class A\n" "{\n" " A(const A& other) { }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkCopyCtorAndEqOperator("class A\n" "{\n" " A& operator=(const A& other) { return *this; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); @@ -305,7 +311,7 @@ class TestClass : public TestFixture { "{\n" " A(const A& other) { }\n" " int x;\n" - "};"); + "};\n"); TODO_ASSERT_EQUALS("[test.cpp:1]: (warning) The class 'A' has 'copy constructor' but lack of 'operator='.\n", "", errout_str()); // TODO the error message should be clarified. It should say something like 'copy constructor is empty and will not assign i and therefore the behaviour is different to the default assignment operator' @@ -313,7 +319,7 @@ class TestClass : public TestFixture { "{\n" " A& operator=(const A& other) { return *this; }\n" " int x;\n" - "};"); + "};\n"); TODO_ASSERT_EQUALS("[test.cpp:1]: (warning) The class 'A' has 'operator=' but lack of 'copy constructor'.\n", "", errout_str()); // TODO the error message should be clarified. It should say something like 'assignment operator does not assign i and therefore the behaviour is different to the default copy constructor' @@ -321,7 +327,7 @@ class TestClass : public TestFixture { "{\n" " A& operator=(const int &x) { this->x = x; return *this; }\n" " int x;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkCopyCtorAndEqOperator("class A {\n" @@ -341,7 +347,7 @@ class TestClass : public TestFixture { " B(const B & b) :A(b) { }\n" "private:\n" " static int i;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // #7987 - Don't show warning when there is a move constructor @@ -352,14 +358,14 @@ class TestClass : public TestFixture { " test = std::move(s.test);\n" " return *this;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // #8337 - False positive in copy constructor detection checkCopyCtorAndEqOperator("struct StaticListNode {\n" " StaticListNode(StaticListNode*& prev) : m_next(0) {}\n" " StaticListNode* m_next;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -371,7 +377,7 @@ class TestClass : public TestFixture { ASSERT_LOC(tokenizer.tokenize(code), file, line); // Check.. - CheckClass checkClass(&tokenizer, &settings0, this); + CheckClassImpl checkClass(&tokenizer, settings0, *this); (checkClass.checkExplicitConstructors)(); } @@ -383,7 +389,7 @@ class TestClass : public TestFixture { " explicit Class(int i) { }\n" " explicit Class(const std::string&) { }\n" " Class(int a, int b) { }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkExplicitConstructors("class Class {\n" @@ -391,7 +397,7 @@ class TestClass : public TestFixture { " explicit Class(const Class& other) { }\n" " explicit Class(Class&& other) { }\n" " virtual int i() = 0;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkExplicitConstructors("class Class {\n" @@ -399,37 +405,37 @@ class TestClass : public TestFixture { " Class(const Class& other) = delete;\n" " Class(Class&& other) = delete;\n" " virtual int i() = 0;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkExplicitConstructors("class Class {\n" " Class(int i) { }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:2:5]: (style) Class 'Class' has a constructor with 1 argument that is not explicit. [noExplicitConstructor]\n", errout_str()); checkExplicitConstructors("class Class {\n" " Class(const Class& other) { }\n" " virtual int i() = 0;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkExplicitConstructors("class Class {\n" " Class(Class&& other) { }\n" " virtual int i() = 0;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // #6585 checkExplicitConstructors("class Class {\n" " private: Class(const Class&);\n" " virtual int i() = 0;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkExplicitConstructors("class Class {\n" " public: Class(const Class&);\n" " virtual int i() = 0;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // #7465: Error properly reported in templates @@ -440,7 +446,7 @@ class TestClass : public TestFixture { "int main() {\n" " Test test;\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:3]: (style) Struct 'Test < int >' has a constructor with 1 argument that is not explicit. [noExplicitConstructor]\n", errout_str()); // #7465: No error for copy or move constructors @@ -453,7 +459,7 @@ class TestClass : public TestFixture { "int main() {\n" " Test test;\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #8600 @@ -461,12 +467,12 @@ class TestClass : public TestFixture { "struct A::B {\n" " B() = default;\n" " B(const B&) {}\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkExplicitConstructors("struct A{" - " A(int, int y=2) {}" - "};"); + " A(int, int y=2) {}\n" + "};\n"); ASSERT_EQUALS("[test.cpp:1:14]: (style) Struct 'A' has a constructor with 1 argument that is not explicit. [noExplicitConstructor]\n", errout_str()); checkExplicitConstructors("struct Foo {\n" // #10515 @@ -528,7 +534,7 @@ class TestClass : public TestFixture { ASSERT_LOC(tokenizer.tokenize(code), file, line); // Check.. - CheckClass checkClass(&tokenizer, &settings1, this); + CheckClassImpl checkClass(&tokenizer, settings1, *this); (checkClass.checkDuplInheritedMembers)(); } @@ -538,7 +544,7 @@ class TestClass : public TestFixture { "};\n" "struct Derived : Base {\n" " int x;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkDuplInheritedMembers("class Base {\n" @@ -547,7 +553,7 @@ class TestClass : public TestFixture { "};\n" "struct Derived : Base {\n" " int x;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:8] -> [test.cpp:6:8]: (warning) The struct 'Derived' defines member variable with name 'x' also defined in its parent class 'Base'. [duplInheritedMember]\n", errout_str()); checkDuplInheritedMembers("class Base {\n" @@ -556,7 +562,7 @@ class TestClass : public TestFixture { "};\n" "struct Derived : public Base {\n" " int x;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:8] -> [test.cpp:6:8]: (warning) The struct 'Derived' defines member variable with name 'x' also defined in its parent class 'Base'. [duplInheritedMember]\n", errout_str()); checkDuplInheritedMembers("class Base0 {\n" @@ -567,7 +573,7 @@ class TestClass : public TestFixture { "};\n" "struct Derived : Base0, Base1 {\n" " int x;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkDuplInheritedMembers("class Base0 {\n" @@ -579,7 +585,7 @@ class TestClass : public TestFixture { "};\n" "struct Derived : Base0, Base1 {\n" " int x;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:8] -> [test.cpp:9:8]: (warning) The struct 'Derived' defines member variable with name 'x' also defined in its parent class 'Base0'. [duplInheritedMember]\n", errout_str()); checkDuplInheritedMembers("class Base0 {\n" @@ -592,7 +598,7 @@ class TestClass : public TestFixture { "};\n" "struct Derived : Base0, Base1 {\n" " int x;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:8] -> [test.cpp:10:8]: (warning) The struct 'Derived' defines member variable with name 'x' also defined in its parent class 'Base0'. [duplInheritedMember]\n" "[test.cpp:7:8] -> [test.cpp:10:8]: (warning) The struct 'Derived' defines member variable with name 'x' also defined in its parent class 'Base1'. [duplInheritedMember]\n", errout_str()); @@ -601,7 +607,7 @@ class TestClass : public TestFixture { "};\n" "struct Derived : Base {\n" " int y;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkDuplInheritedMembers("class A {\n" @@ -609,20 +615,20 @@ class TestClass : public TestFixture { "};\n" "struct B {\n" " int x;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // Unknown 'Base' class checkDuplInheritedMembers("class Derived : public UnknownBase {\n" " int x;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkDuplInheritedMembers("class Base {\n" " int x;\n" "};\n" "class Derived : public Base {\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // #6692 @@ -634,7 +640,7 @@ class TestClass : public TestFixture { " struct SWibble : public test1::wibble {\n" " int Value;\n" " };\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #9957 @@ -648,12 +654,12 @@ class TestClass : public TestFixture { "};\n" "class Derived2 : public Derived1 {\n" " int i;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:13] -> [test.cpp:10:9]: (warning) The class 'Derived2' defines member variable with name 'i' also defined in its parent class 'Base'. [duplInheritedMember]\n", errout_str()); // don't crash on recursive template checkDuplInheritedMembers("template\n" - "struct BitInt : public BitInt { };"); + "struct BitInt : public BitInt { };\n"); ASSERT_EQUALS("", errout_str()); // don't crash on recursive template @@ -668,7 +674,7 @@ class TestClass : public TestFixture { " template \n" " struct fn_traits\n" " : public fn_traits {};\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #10594 @@ -752,7 +758,7 @@ class TestClass : public TestFixture { ASSERT_LOC(tokenizer.tokenize(code), file, line); // Check.. - CheckClass checkClass(&tokenizer, &settings3, this); + CheckClassImpl checkClass(&tokenizer, settings3, *this); checkClass.copyconstructors(); } @@ -773,7 +779,7 @@ class TestClass : public TestFixture { " }\n" " F&operator=(const F&);\n" " ~F();\n" - "};"); + "};\n"); TODO_ASSERT_EQUALS("[test.cpp:5]: (warning) Value of pointer 'p', which points to allocated memory, is copied in copy constructor instead of allocating new memory.\n", "", errout_str()); checkCopyConstructor("class F {\n" @@ -786,7 +792,7 @@ class TestClass : public TestFixture { " }\n" " ~F();\n" " F& operator=(const F&f);\n" - "};"); + "};\n"); TODO_ASSERT_EQUALS("[test.cpp:4:7]: (warning) Value of pointer 'p', which points to allocated memory, is copied in copy constructor instead of allocating new memory. [copyCtorPointerCopying]\n" "[test.cpp:3] -> [test.cpp:7]: (warning) Copy constructor does not allocate memory for member 'p' although memory has been allocated in other constructors.\n", "[test.cpp:4:7]: (warning) Value of pointer 'p', which points to allocated memory, is copied in copy constructor instead of allocating new memory. [copyCtorPointerCopying]\n" @@ -806,7 +812,7 @@ class TestClass : public TestFixture { " }\n" " ~F();\n" " F& operator=(const F&f);\n" - "};"); + "};\n"); TODO_ASSERT_EQUALS("[test.cpp:5]: (warning) Value of pointer 'p', which points to allocated memory, is copied in copy constructor instead of allocating new memory.\n" "[test.cpp:5] -> [test.cpp:10]: (warning) Copy constructor does not allocate memory for member 'p' although memory has been allocated in other constructors.\n", "" @@ -836,7 +842,7 @@ class TestClass : public TestFixture { " }\n" " ~kalci();\n" " kalci& operator=(const kalci&kalci);\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkCopyConstructor("class F\n" @@ -861,7 +867,7 @@ class TestClass : public TestFixture { " }\n" " ~F();\n" " F& operator=(const F&f);\n" - "};"); + "};\n"); TODO_ASSERT_EQUALS("[test.cpp:14] -> [test.cpp:11]: (warning) Copy constructor does not allocate memory for member 'd' although memory has been allocated in other constructors.\n", "", errout_str()); checkCopyConstructor("class F {\n" @@ -875,7 +881,7 @@ class TestClass : public TestFixture { " }\n" " ~F();\n" " F& operator=(const F&f);\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkCopyConstructor("class F {\n" @@ -889,7 +895,7 @@ class TestClass : public TestFixture { " }\n" " ~F();\n" " F& operator=(const F&f);\n" - "};"); + "};\n"); TODO_ASSERT_EQUALS("[test.cpp:7] -> [test.cpp:4]: (warning) Copy constructor does not allocate memory for member 'd' although memory has been allocated in other constructors.\n", "", errout_str()); checkCopyConstructor("class F\n" @@ -904,7 +910,7 @@ class TestClass : public TestFixture { " }\n" " ~F();\n" " F& operator=(const F&f);\n" - "};"); + "};\n"); TODO_ASSERT_EQUALS("[test.cpp:8]: (warning) Class 'F' does not have a copy constructor which is recommended since it has dynamic memory/resource management.\n", "", errout_str()); checkCopyConstructor("class F\n" @@ -928,7 +934,7 @@ class TestClass : public TestFixture { " }\n" " ~F();\n" " F& operator=(const F&f);\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkCopyConstructor("class F : E\n" @@ -939,7 +945,7 @@ class TestClass : public TestFixture { " }\n" " ~F();\n" " F& operator=(const F&f);\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkCopyConstructor("class E { E(E&); };\n" // non-copyable @@ -951,7 +957,7 @@ class TestClass : public TestFixture { " }\n" " ~F();\n" " F& operator=(const F&f);\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkCopyConstructor("class E {};\n" @@ -962,7 +968,7 @@ class TestClass : public TestFixture { " }\n" " ~F();\n" " F& operator=(const F&f);\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:5:7]: (warning) Class 'F' does not have a copy constructor which is recommended since it has dynamic memory/resource management. [noCopyConstructor]\n", errout_str()); checkCopyConstructor("class F {\n" @@ -973,7 +979,7 @@ class TestClass : public TestFixture { " F(F& f);\n" " ~F();\n" " F& operator=(const F&f);\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkCopyConstructor("class F {\n" @@ -981,7 +987,7 @@ class TestClass : public TestFixture { " F() : p(malloc(100)) {}\n" " ~F();\n" " F& operator=(const F&f);\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (warning) Class 'F' does not have a copy constructor which is recommended since it has dynamic memory/resource management. [noCopyConstructor]\n", errout_str()); // #7198 @@ -990,7 +996,7 @@ class TestClass : public TestFixture { " F() {\n" " p = malloc(100);\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -1007,7 +1013,7 @@ class TestClass : public TestFixture { " ~Vector();\n" " Vector& operator=(const Vector&v);\n" " _Tp* _M_finish;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -1018,7 +1024,7 @@ class TestClass : public TestFixture { " F(const F &f) = delete;\n" " F&operator=(const F &f);\n" " ~F();\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkCopyConstructor("struct F {\n" @@ -1027,7 +1033,7 @@ class TestClass : public TestFixture { " F(const F &f) = default;\n" " F&operator=(const F &f);\n" " ~F();\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (warning) Struct 'F' has dynamic memory/resource allocation(s). The copy constructor is explicitly defaulted but the default copy constructor does not work well. It is recommended to define or delete the copy constructor. [noCopyConstructor]\n", errout_str()); } @@ -1050,7 +1056,7 @@ class TestClass : public TestFixture { " ~Foo() { delete m_ptr; }\n" "private:\n" " int* m_ptr;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -1063,7 +1069,7 @@ class TestClass : public TestFixture { " ~Foo() { delete m_ptr; }\n" "private:\n" " int* m_ptr;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkCopyConstructor("class Copyable {};\n" @@ -1074,7 +1080,7 @@ class TestClass : public TestFixture { " ~Foo() { delete m_ptr; }\n" "private:\n" " int* m_ptr;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -1093,6 +1099,25 @@ class TestClass : public TestFixture { errout_str()); } + void copyConstructor7() { // ticket #14954 + checkCopyConstructor("struct S {\n" + " explicit S(char *name) { m_fd = mkstemp(name); }\n" + " ~S() { /* close(m_fd); */ }\n" + " S &operator =(const S&);\n" + " int m_fd;\n" + "};\n"); + ASSERT_EQUALS("[test.cpp:2:30]: (warning) Struct 'S' does not have a copy constructor which is recommended since it has dynamic memory/resource management. [noCopyConstructor]\n", errout_str()); + } + + void copyConstructor8() { + checkCopyConstructor("struct S {\n" + " S() : m_ptr(new int) {}\n" + " ~S();\n" + " std::unique_ptr m_ptr;\n" + "};\n"); + ASSERT_EQUALS("", errout_str()); + } + void deletedMemberPointer() { // delete ... @@ -1126,7 +1151,7 @@ class TestClass : public TestFixture { " F() { c = malloc(100); }\n" " F(const F &f);\n" " ~F();\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (warning) Struct 'F' does not have a operator= which is recommended since it has dynamic memory/resource management. [noOperatorEq]\n", errout_str()); // defaulted operator= @@ -1136,7 +1161,7 @@ class TestClass : public TestFixture { " F(const F &f);\n" " F &operator=(const F &f) = default;\n" " ~F();\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (warning) Struct 'F' has dynamic memory/resource allocation(s). The operator= is explicitly defaulted but the default operator= does not work well. It is recommended to define or delete the operator=. [noOperatorEq]\n", errout_str()); // deleted operator= @@ -1146,7 +1171,7 @@ class TestClass : public TestFixture { " F(const F &f);\n" " F &operator=(const F &f) = delete;\n" " ~F();\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // base class deletes operator= @@ -1155,8 +1180,17 @@ class TestClass : public TestFixture { " F() { c = malloc(100); }\n" " F(const F &f);\n" " ~F();\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); + + checkCopyConstructor("struct S {\n" + " explicit S(char *name) { m_fd = mkstemp(name); }\n" + " S(const S&);\n" + " ~S() { /* close(m_fd); */ }\n" + " int m_fd;\n" + "};\n"); + ASSERT_EQUALS("[test.cpp:2:30]: (warning) Struct 'S' does not have a operator= which is recommended since it has dynamic memory/resource management. [noOperatorEq]\n", errout_str()); + } void noDestructor() { @@ -1165,7 +1199,7 @@ class TestClass : public TestFixture { " F() { c = malloc(100); }\n" " F(const F &f);\n" " F&operator=(const F&);" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (warning) Struct 'F' does not have a destructor which is recommended since it has dynamic memory/resource management. [noDestructor]\n", errout_str()); checkCopyConstructor("struct F {\n" @@ -1173,7 +1207,7 @@ class TestClass : public TestFixture { " F() { c = new C; }\n" " F(const F &f);\n" " F&operator=(const F&);" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkCopyConstructor("struct F {\n" @@ -1181,7 +1215,7 @@ class TestClass : public TestFixture { " F() { i = new int(); }\n" " F(const F &f);\n" " F& operator=(const F&);" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (warning) Struct 'F' does not have a destructor which is recommended since it has dynamic memory/resource management. [noDestructor]\n", errout_str()); checkCopyConstructor("struct Data { int x; int y; };\n" @@ -1190,7 +1224,7 @@ class TestClass : public TestFixture { " F() { c = new Data; }\n" " F(const F &f);\n" " F&operator=(const F&);" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:10]: (warning) Struct 'F' does not have a destructor which is recommended since it has dynamic memory/resource management. [noDestructor]\n", errout_str()); // defaulted destructor @@ -1200,7 +1234,7 @@ class TestClass : public TestFixture { " F(const F &f);\n" " F &operator=(const F &f);\n" " ~F() = default;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (warning) Struct 'F' has dynamic memory/resource allocation(s). The destructor is explicitly defaulted but the default destructor does not work well. It is recommended to define the destructor. [noDestructor]\n", errout_str()); // deleted destructor @@ -1210,7 +1244,7 @@ class TestClass : public TestFixture { " F(const F &f);\n" " F &operator=(const F &f);\n" " ~F() = delete;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -1223,7 +1257,7 @@ class TestClass : public TestFixture { ASSERT_LOC(tokenizer.tokenize(code), file, line); // Check.. - CheckClass checkClass(&tokenizer, &settings0, this); + CheckClassImpl checkClass(&tokenizer, settings0, *this); checkClass.operatorEqRetRefThis(); } @@ -1233,7 +1267,7 @@ class TestClass : public TestFixture { "{\n" "public:\n" " A & operator=(const A &a) { return *this; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkOpertorEqRetRefThis( @@ -1241,7 +1275,7 @@ class TestClass : public TestFixture { "{\n" "public:\n" " A & operator=(const A &a) { return a; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:9]: (style) 'operator=' should return reference to 'this' instance. [operatorEqRetRefThis]\n", errout_str()); checkOpertorEqRetRefThis( @@ -1250,7 +1284,7 @@ class TestClass : public TestFixture { "public:\n" " A & operator=(const A &);\n" "};\n" - "A & A::operator=(const A &a) { return *this; }"); + "A & A::operator=(const A &a) { return *this; }\n"); ASSERT_EQUALS("", errout_str()); checkOpertorEqRetRefThis( @@ -1259,7 +1293,7 @@ class TestClass : public TestFixture { "public:\n" " A & operator=(const A &a);\n" "};\n" - "A & A::operator=(const A &a) { return *this; }"); + "A & A::operator=(const A &a) { return *this; }\n"); ASSERT_EQUALS("", errout_str()); checkOpertorEqRetRefThis( @@ -1268,7 +1302,7 @@ class TestClass : public TestFixture { "public:\n" " A & operator=(const A &);\n" "};\n" - "A & A::operator=(const A &a) { return a; }"); + "A & A::operator=(const A &a) { return a; }\n"); ASSERT_EQUALS("[test.cpp:6:8]: (style) 'operator=' should return reference to 'this' instance. [operatorEqRetRefThis]\n", errout_str()); checkOpertorEqRetRefThis( @@ -1277,7 +1311,7 @@ class TestClass : public TestFixture { "public:\n" " A & operator=(const A &a);\n" "};\n" - "A & A::operator=(const A &a) { return a; }"); + "A & A::operator=(const A &a) { return a; }\n"); ASSERT_EQUALS("[test.cpp:6:8]: (style) 'operator=' should return reference to 'this' instance. [operatorEqRetRefThis]\n", errout_str()); checkOpertorEqRetRefThis( @@ -1289,7 +1323,7 @@ class TestClass : public TestFixture { " public:\n" " B & operator=(const B &b) { return *this; }\n" " };\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkOpertorEqRetRefThis( @@ -1301,7 +1335,7 @@ class TestClass : public TestFixture { " public:\n" " B & operator=(const B &b) { return b; }\n" " };\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:7:13]: (style) 'operator=' should return reference to 'this' instance. [operatorEqRetRefThis]\n", errout_str()); checkOpertorEqRetRefThis( @@ -1314,7 +1348,7 @@ class TestClass : public TestFixture { " B & operator=(const B &);\n" " };\n" "};\n" - "A::B & A::B::operator=(const A::B &b) { return *this; }"); + "A::B & A::B::operator=(const A::B &b) { return *this; }\n"); ASSERT_EQUALS("", errout_str()); checkOpertorEqRetRefThis( @@ -1327,7 +1361,7 @@ class TestClass : public TestFixture { " B & operator=(const B &);\n" " };\n" "};\n" - "A::B & A::B::operator=(const A::B &b) { return b; }"); + "A::B & A::B::operator=(const A::B &b) { return b; }\n"); ASSERT_EQUALS("[test.cpp:10:14]: (style) 'operator=' should return reference to 'this' instance. [operatorEqRetRefThis]\n", errout_str()); checkOpertorEqRetRefThis( @@ -1337,7 +1371,7 @@ class TestClass : public TestFixture { "class A::B\n" "{\n" " B & operator=(const B & b) { return b; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:6:7]: (style) 'operator=' should return reference to 'this' instance. [operatorEqRetRefThis]\n", errout_str()); checkOpertorEqRetRefThis( @@ -1348,7 +1382,7 @@ class TestClass : public TestFixture { "{\n" " B & operator=(const B &);\n" "};\n" - "A::B & A::B::operator=(const A::B & b) { return b; }"); + "A::B & A::B::operator=(const A::B & b) { return b; }\n"); ASSERT_EQUALS("[test.cpp:8:14]: (style) 'operator=' should return reference to 'this' instance. [operatorEqRetRefThis]\n", errout_str()); checkOpertorEqRetRefThis( @@ -1358,7 +1392,7 @@ class TestClass : public TestFixture { "class A::B\n" "{\n" " A::B & operator=(const A::B & b) { return b; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:6:10]: (style) 'operator=' should return reference to 'this' instance. [operatorEqRetRefThis]\n", errout_str()); checkOpertorEqRetRefThis( @@ -1369,7 +1403,7 @@ class TestClass : public TestFixture { "{\n" " A::B & operator=(const A::B &);\n" "};\n" - "A::B & A::B::operator=(const A::B & b) { return b; }"); + "A::B & A::B::operator=(const A::B & b) { return b; }\n"); ASSERT_EQUALS("[test.cpp:8:14]: (style) 'operator=' should return reference to 'this' instance. [operatorEqRetRefThis]\n", errout_str()); checkOpertorEqRetRefThis( @@ -1379,7 +1413,7 @@ class TestClass : public TestFixture { "class A::B\n" "{\n" " B & operator=(const B & b) { return b; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:6:7]: (style) 'operator=' should return reference to 'this' instance. [operatorEqRetRefThis]\n", errout_str()); checkOpertorEqRetRefThis( @@ -1390,7 +1424,7 @@ class TestClass : public TestFixture { "{\n" " B & operator=(const B &);\n" "};\n" - "A::B & A::B::operator=(const A::B & b) { return b; }"); + "A::B & A::B::operator=(const A::B & b) { return b; }\n"); ASSERT_EQUALS("[test.cpp:8:14]: (style) 'operator=' should return reference to 'this' instance. [operatorEqRetRefThis]\n", errout_str()); checkOpertorEqRetRefThis( @@ -1400,7 +1434,7 @@ class TestClass : public TestFixture { "class A::B\n" "{\n" " A::B & operator=(const A::B & b) { return b; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:6:10]: (style) 'operator=' should return reference to 'this' instance. [operatorEqRetRefThis]\n", errout_str()); checkOpertorEqRetRefThis( @@ -1411,7 +1445,7 @@ class TestClass : public TestFixture { "{\n" " A::B & operator=(const A::B &);\n" "};\n" - "A::B & A::B::operator=(const A::B & b) { return b; }"); + "A::B & A::B::operator=(const A::B & b) { return b; }\n"); ASSERT_EQUALS("[test.cpp:8:14]: (style) 'operator=' should return reference to 'this' instance. [operatorEqRetRefThis]\n", errout_str()); checkOpertorEqRetRefThis( // #11380 @@ -1431,7 +1465,7 @@ class TestClass : public TestFixture { "class szp\n" "{\n" " szp &operator =(int *other) {}\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:8]: (error) No 'return' statement in non-void function causes undefined behavior. [operatorEqMissingReturnStatement]\n", errout_str()); checkOpertorEqRetRefThis( @@ -1439,7 +1473,7 @@ class TestClass : public TestFixture { "{\n" " szp &operator =(int *other);\n" "};\n" - "szp &szp::operator =(int *other) {}"); + "szp &szp::operator =(int *other) {}\n"); ASSERT_EQUALS("[test.cpp:5:11]: (error) No 'return' statement in non-void function causes undefined behavior. [operatorEqMissingReturnStatement]\n", errout_str()); checkOpertorEqRetRefThis( @@ -1449,7 +1483,7 @@ class TestClass : public TestFixture { "class NS::szp\n" "{\n" " szp &operator =(int *other) {}\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:6:8]: (error) No 'return' statement in non-void function causes undefined behavior. [operatorEqMissingReturnStatement]\n", errout_str()); checkOpertorEqRetRefThis( @@ -1460,7 +1494,7 @@ class TestClass : public TestFixture { "{\n" " szp &operator =(int *other);\n" "};\n" - "NS::szp &NS::szp::operator =(int *other) {}"); + "NS::szp &NS::szp::operator =(int *other) {}\n"); ASSERT_EQUALS("[test.cpp:8:19]: (error) No 'return' statement in non-void function causes undefined behavior. [operatorEqMissingReturnStatement]\n", errout_str()); checkOpertorEqRetRefThis( @@ -1470,7 +1504,7 @@ class TestClass : public TestFixture { "class NS::szp\n" "{\n" " NS::szp &operator =(int *other) {}\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:6:12]: (error) No 'return' statement in non-void function causes undefined behavior. [operatorEqMissingReturnStatement]\n", errout_str()); checkOpertorEqRetRefThis( @@ -1481,7 +1515,7 @@ class TestClass : public TestFixture { "{\n" " NS::szp &operator =(int *other);\n" "};\n" - "NS::szp &NS::szp::operator =(int *other) {}"); + "NS::szp &NS::szp::operator =(int *other) {}\n"); ASSERT_EQUALS("[test.cpp:8:19]: (error) No 'return' statement in non-void function causes undefined behavior. [operatorEqMissingReturnStatement]\n", errout_str()); checkOpertorEqRetRefThis( @@ -1491,7 +1525,7 @@ class TestClass : public TestFixture { "class A::szp\n" "{\n" " szp &operator =(int *other) {}\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:6:8]: (error) No 'return' statement in non-void function causes undefined behavior. [operatorEqMissingReturnStatement]\n", errout_str()); checkOpertorEqRetRefThis( @@ -1502,7 +1536,7 @@ class TestClass : public TestFixture { "{\n" " szp &operator =(int *other);\n" "};\n" - "A::szp &A::szp::operator =(int *other) {}"); + "A::szp &A::szp::operator =(int *other) {}\n"); ASSERT_EQUALS("[test.cpp:8:17]: (error) No 'return' statement in non-void function causes undefined behavior. [operatorEqMissingReturnStatement]\n", errout_str()); checkOpertorEqRetRefThis( @@ -1512,7 +1546,7 @@ class TestClass : public TestFixture { "class A::szp\n" "{\n" " A::szp &operator =(int *other) {}\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:6:11]: (error) No 'return' statement in non-void function causes undefined behavior. [operatorEqMissingReturnStatement]\n", errout_str()); checkOpertorEqRetRefThis( @@ -1523,7 +1557,7 @@ class TestClass : public TestFixture { "{\n" " A::szp &operator =(int *other);\n" "};\n" - "A::szp &A::szp::operator =(int *other) {}"); + "A::szp &A::szp::operator =(int *other) {}\n"); ASSERT_EQUALS("[test.cpp:8:17]: (error) No 'return' statement in non-void function causes undefined behavior. [operatorEqMissingReturnStatement]\n", errout_str()); } @@ -1534,7 +1568,7 @@ class TestClass : public TestFixture { "public:\n" " inline A &operator =(int *other) { return (*this); };\n" " inline A &operator =(long *other) { return (*this = 0); };\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkOpertorEqRetRefThis( @@ -1544,7 +1578,7 @@ class TestClass : public TestFixture { " A &operator =(long *other);\n" "};\n" "A &A::operator =(int *other) { return (*this); };\n" - "A &A::operator =(long *other) { return (*this = 0); };"); + "A &A::operator =(long *other) { return (*this = 0); };\n"); ASSERT_EQUALS("", errout_str()); checkOpertorEqRetRefThis( @@ -1552,7 +1586,7 @@ class TestClass : public TestFixture { "public:\n" " inline A &operator =(int *other) { return (*this); };\n" " inline A &operator =(long *other) { return operator = (*(int *)other); };\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkOpertorEqRetRefThis( @@ -1562,7 +1596,7 @@ class TestClass : public TestFixture { " A &operator =(long *other);\n" "};\n" "A &A::operator =(int *other) { return (*this); };\n" - "A &A::operator =(long *other) { return operator = (*(int *)other); };"); + "A &A::operator =(long *other) { return operator = (*(int *)other); };\n"); ASSERT_EQUALS("", errout_str()); checkOpertorEqRetRefThis( @@ -1572,7 +1606,7 @@ class TestClass : public TestFixture { " A &operator =(long *other);\n" "};\n" "A &A::operator =(int *other) { return (*this); };\n" - "A &A::operator =(long *other) { return this->operator = (*(int *)other); };"); + "A &A::operator =(long *other) { return this->operator = (*(int *)other); };\n"); ASSERT_EQUALS("", errout_str()); checkOpertorEqRetRefThis( // #9045 @@ -1584,7 +1618,7 @@ class TestClass : public TestFixture { " }\n" " return *this;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -1594,7 +1628,7 @@ class TestClass : public TestFixture { "P& P::operator = (const P& pc)\n" "{\n" " return (P&)(*this += pc);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1604,21 +1638,21 @@ class TestClass : public TestFixture { "class A {\n" "public:\n" " A & operator=(const A &a) { }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:9]: (error) No 'return' statement in non-void function causes undefined behavior. [operatorEqMissingReturnStatement]\n", errout_str()); checkOpertorEqRetRefThis( "class A {\n" "protected:\n" " A & operator=(const A &a) {}\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:9]: (style) 'operator=' should return reference to 'this' instance. [operatorEqRetRefThis]\n", errout_str()); checkOpertorEqRetRefThis( "class A {\n" "private:\n" " A & operator=(const A &a) {}\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:9]: (style) 'operator=' should return reference to 'this' instance. [operatorEqRetRefThis]\n", errout_str()); checkOpertorEqRetRefThis( @@ -1628,7 +1662,7 @@ class TestClass : public TestFixture { " rand();\n" " throw std::exception();\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:9]: (style) 'operator=' should either return reference to 'this' instance or be declared private and left unimplemented. [operatorEqShouldBeLeftUnimplemented]\n", errout_str()); checkOpertorEqRetRefThis( @@ -1638,7 +1672,7 @@ class TestClass : public TestFixture { " rand();\n" " abort();\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:9]: (style) 'operator=' should either return reference to 'this' instance or be declared private and left unimplemented. [operatorEqShouldBeLeftUnimplemented]\n", errout_str()); checkOpertorEqRetRefThis( @@ -1646,7 +1680,7 @@ class TestClass : public TestFixture { "public:\n" " A & operator=(const A &a);\n" "};\n" - "A & A :: operator=(const A &a) { }"); + "A & A :: operator=(const A &a) { }\n"); ASSERT_EQUALS("[test.cpp:5:10]: (error) No 'return' statement in non-void function causes undefined behavior. [operatorEqMissingReturnStatement]\n", errout_str()); } @@ -1663,7 +1697,7 @@ class TestClass : public TestFixture { "}\n" "UString& UString::operator=( const UString& s ) {\n" " return assign( s );\n" - "}"); + "}\n"); } void operatorEqRetRefThis7() { // ticket #5782 Endless recursion in CheckClass::checkReturnPtrThis() @@ -1681,7 +1715,7 @@ class TestClass : public TestFixture { " basic_fbstring& replace() {\n" " return replaceImplDiscr();\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -1694,7 +1728,7 @@ class TestClass : public TestFixture { ASSERT_LOC(tokenizer.tokenize(code), file, line); // Check.. - CheckClass checkClass(&tokenizer, &settings1, this); + CheckClassImpl checkClass(&tokenizer, settings1, *this); checkClass.operatorEqToSelf(); } @@ -1705,7 +1739,7 @@ class TestClass : public TestFixture { "{\n" "public:\n" " A & operator=(const A &a) { if (&a != this) { } return *this; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // this test doesn't have an assignment test but it is not needed @@ -1714,7 +1748,7 @@ class TestClass : public TestFixture { "{\n" "public:\n" " A & operator=(const A &a) { return *this; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // this test needs an assignment test and has it @@ -1732,7 +1766,7 @@ class TestClass : public TestFixture { " }\n" " return *this;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // this class needs an assignment test but doesn't have it @@ -1747,7 +1781,7 @@ class TestClass : public TestFixture { " s = strdup(a.s);\n" " return *this;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:5:9]: (warning) 'operator=' should check for assignment to self to avoid problems with dynamic memory. [operatorEqToSelf]\n", errout_str()); // this test has an assignment test but doesn't need it @@ -1757,7 +1791,7 @@ class TestClass : public TestFixture { "public:\n" " A & operator=(const A &);\n" "};\n" - "A & A::operator=(const A &a) { if (&a != this) { } return *this; }"); + "A & A::operator=(const A &a) { if (&a != this) { } return *this; }\n"); ASSERT_EQUALS("", errout_str()); // this test doesn't have an assignment test but doesn't need it @@ -1767,7 +1801,7 @@ class TestClass : public TestFixture { "public:\n" " A & operator=(const A &);\n" "};\n" - "A & A::operator=(const A &a) { return *this; }"); + "A & A::operator=(const A &a) { return *this; }\n"); ASSERT_EQUALS("", errout_str()); // this test needs an assignment test and has it @@ -1786,7 +1820,7 @@ class TestClass : public TestFixture { " s = strdup(a.s);\n" " }\n" " return *this;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // this test needs an assignment test and has the inverse test @@ -1805,7 +1839,7 @@ class TestClass : public TestFixture { " s = strdup(a.s);\n" " }\n" " return *this;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:8]: (warning) 'operator=' should check for assignment to self to avoid problems with dynamic memory. [operatorEqToSelf]\n", errout_str()); // this test needs an assignment test and has the inverse test @@ -1824,7 +1858,7 @@ class TestClass : public TestFixture { " s = strdup(a.s);\n" " }\n" " return *this;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:8]: (warning) 'operator=' should check for assignment to self to avoid problems with dynamic memory. [operatorEqToSelf]\n", errout_str()); // this test needs an assignment test and has the inverse test @@ -1843,7 +1877,7 @@ class TestClass : public TestFixture { " s = strdup(a.s);\n" " }\n" " return *this;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:8]: (warning) 'operator=' should check for assignment to self to avoid problems with dynamic memory. [operatorEqToSelf]\n", errout_str()); // this test needs an assignment test and has the inverse test @@ -1862,7 +1896,7 @@ class TestClass : public TestFixture { " s = strdup(a.s);\n" " }\n" " return *this;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:8]: (warning) 'operator=' should check for assignment to self to avoid problems with dynamic memory. [operatorEqToSelf]\n", errout_str()); // this test needs an assignment test and has the inverse test @@ -1881,7 +1915,7 @@ class TestClass : public TestFixture { " s = strdup(a.s);\n" " }\n" " return *this;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:8]: (warning) 'operator=' should check for assignment to self to avoid problems with dynamic memory. [operatorEqToSelf]\n", errout_str()); // this test needs an assignment test and has the inverse test @@ -1903,7 +1937,7 @@ class TestClass : public TestFixture { " s = strdup(a.s);\n" " }\n" " return *this;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:8]: (warning) 'operator=' should check for assignment to self to avoid problems with dynamic memory. [operatorEqToSelf]\n", errout_str()); // this test needs an assignment test and has the inverse test @@ -1924,7 +1958,7 @@ class TestClass : public TestFixture { " s = strdup(a.s);\n" " }\n" " return *this;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:8]: (warning) 'operator=' should check for assignment to self to avoid problems with dynamic memory. [operatorEqToSelf]\n", errout_str()); @@ -1941,7 +1975,7 @@ class TestClass : public TestFixture { " free(s);\n" " s = strdup(a.s);\n" " return *this;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:8]: (warning) 'operator=' should check for assignment to self to avoid problems with dynamic memory. [operatorEqToSelf]\n", errout_str()); // ticket #1224 @@ -1959,7 +1993,7 @@ class TestClass : public TestFixture { " tree = new CodeTree(b);\n" " delete oldtree;\n" " return *this;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1975,7 +2009,7 @@ class TestClass : public TestFixture { " public:\n" " B & operator=(const B &b) { if (&b != this) { } return *this; }\n" " };\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // this test doesn't have an assignment test but doesn't need it @@ -1988,7 +2022,7 @@ class TestClass : public TestFixture { " public:\n" " B & operator=(const B &b) { return *this; }\n" " };\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // this test needs an assignment test but has it @@ -2008,7 +2042,7 @@ class TestClass : public TestFixture { " return *this;\n" " }\n" " };\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // this test needs an assignment test but doesn't have it @@ -2027,7 +2061,7 @@ class TestClass : public TestFixture { " return *this;\n" " }\n" " };\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:8:13]: (warning) 'operator=' should check for assignment to self to avoid problems with dynamic memory. [operatorEqToSelf]\n", errout_str()); // this test has an assignment test but doesn't need it @@ -2041,7 +2075,7 @@ class TestClass : public TestFixture { " B & operator=(const B &);\n" " };\n" "};\n" - "A::B & A::B::operator=(const A::B &b) { if (&b != this) { } return *this; }"); + "A::B & A::B::operator=(const A::B &b) { if (&b != this) { } return *this; }\n"); ASSERT_EQUALS("", errout_str()); // this test doesn't have an assignment test but doesn't need it @@ -2055,7 +2089,7 @@ class TestClass : public TestFixture { " B & operator=(const B &);\n" " };\n" "};\n" - "A::B & A::B::operator=(const A::B &b) { return *this; }"); + "A::B & A::B::operator=(const A::B &b) { return *this; }\n"); ASSERT_EQUALS("", errout_str()); // this test needs an assignment test and has it @@ -2078,7 +2112,7 @@ class TestClass : public TestFixture { " s = strdup(b.s);\n" " }\n" " return *this;\n" - " }"); + " }\n"); ASSERT_EQUALS("", errout_str()); // this test needs an assignment test but doesn't have it @@ -2098,7 +2132,7 @@ class TestClass : public TestFixture { " free(s);\n" " s = strdup(b.s);\n" " return *this;\n" - " }"); + " }\n"); ASSERT_EQUALS("[test.cpp:11:14]: (warning) 'operator=' should check for assignment to self to avoid problems with dynamic memory. [operatorEqToSelf]\n", errout_str()); } @@ -2109,7 +2143,7 @@ class TestClass : public TestFixture { "{\n" "public:\n" " A & operator=(const A &a) { return *this; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // this test has multiple inheritance and needs an assignment test but there is no trivial way to test for it @@ -2124,7 +2158,7 @@ class TestClass : public TestFixture { " s = strdup(a.s);\n" " return *this;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // this test has multiple inheritance so there is no trivial way to test for self assignment but doesn't need it @@ -2134,7 +2168,7 @@ class TestClass : public TestFixture { "public:\n" " A & operator=(const A &);\n" "};\n" - "A & A::operator=(const A &a) { return *this; }"); + "A & A::operator=(const A &a) { return *this; }\n"); ASSERT_EQUALS("", errout_str()); // this test has multiple inheritance and needs an assignment test but there is no trivial way to test for it @@ -2150,7 +2184,7 @@ class TestClass : public TestFixture { " free(s);\n" " s = strdup(a.s);\n" " return *this;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2165,7 +2199,7 @@ class TestClass : public TestFixture { " public:\n" " B & operator=(const B &b) { return *this; }\n" " };\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // this test has multiple inheritance and needs an assignment test but there is no trivial way to test for it @@ -2184,7 +2218,7 @@ class TestClass : public TestFixture { " return *this;\n" " }\n" " };\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // this test has multiple inheritance so there is no trivial way to test for self assignment but doesn't need it @@ -2198,7 +2232,7 @@ class TestClass : public TestFixture { " B & operator=(const B &);\n" " };\n" "};\n" - "A::B & A::B::operator=(const A::B &b) { return *this; }"); + "A::B & A::B::operator=(const A::B &b) { return *this; }\n"); ASSERT_EQUALS("", errout_str()); // this test has multiple inheritance and needs an assignment test but there is no trivial way to test for it @@ -2218,7 +2252,7 @@ class TestClass : public TestFixture { " free(s);\n" " s = strdup(b.s);\n" " return *this;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2238,7 +2272,7 @@ class TestClass : public TestFixture { " }\n" " return *this;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkOpertorEqToSelf( @@ -2255,7 +2289,7 @@ class TestClass : public TestFixture { " }\n" " return *this;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkOpertorEqToSelf( @@ -2272,7 +2306,7 @@ class TestClass : public TestFixture { " }\n" " return *this;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkOpertorEqToSelf( @@ -2289,7 +2323,7 @@ class TestClass : public TestFixture { " }\n" " return *this;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkOpertorEqToSelf( @@ -2306,7 +2340,7 @@ class TestClass : public TestFixture { " }\n" " return *this;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkOpertorEqToSelf( @@ -2323,7 +2357,7 @@ class TestClass : public TestFixture { " }\n" " return *this;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkOpertorEqToSelf( @@ -2340,7 +2374,7 @@ class TestClass : public TestFixture { " }\n" " return *this;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkOpertorEqToSelf( @@ -2357,7 +2391,7 @@ class TestClass : public TestFixture { " }\n" " return *this;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkOpertorEqToSelf( @@ -2375,7 +2409,7 @@ class TestClass : public TestFixture { " s = strdup(a.s);\n" " }\n" " return *this;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkOpertorEqToSelf( @@ -2393,7 +2427,7 @@ class TestClass : public TestFixture { " s = strdup(a.s);\n" " }\n" " return *this;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkOpertorEqToSelf( @@ -2411,7 +2445,7 @@ class TestClass : public TestFixture { " s = strdup(a.s);\n" " }\n" " return *this;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkOpertorEqToSelf( @@ -2429,7 +2463,7 @@ class TestClass : public TestFixture { " s = strdup(a.s);\n" " }\n" " return *this;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkOpertorEqToSelf( @@ -2447,7 +2481,7 @@ class TestClass : public TestFixture { " s = strdup(a.s);\n" " }\n" " return *this;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkOpertorEqToSelf( @@ -2465,7 +2499,7 @@ class TestClass : public TestFixture { " s = strdup(a.s);\n" " }\n" " return *this;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkOpertorEqToSelf( @@ -2483,7 +2517,7 @@ class TestClass : public TestFixture { " s = strdup(a.s);\n" " }\n" " return *this;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkOpertorEqToSelf( @@ -2501,7 +2535,7 @@ class TestClass : public TestFixture { " s = strdup(a.s);\n" " }\n" " return *this;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkOpertorEqToSelf( @@ -2513,7 +2547,7 @@ class TestClass : public TestFixture { " free(s);\n" " s = strdup(a.s);\n" " return *this;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -2532,7 +2566,7 @@ class TestClass : public TestFixture { " }\n" "private:\n" " char * data;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:9]: (warning) 'operator=' should check for assignment to self to avoid problems with dynamic memory. [operatorEqToSelf]\n", errout_str()); checkOpertorEqToSelf( @@ -2549,7 +2583,7 @@ class TestClass : public TestFixture { " data = new char[strlen(a.data) + 1];\n" " strcpy(data, a.data);\n" " return *this;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:8:8]: (warning) 'operator=' should check for assignment to self to avoid problems with dynamic memory. [operatorEqToSelf]\n", errout_str()); checkOpertorEqToSelf( @@ -2565,7 +2599,7 @@ class TestClass : public TestFixture { " }\n" "private:\n" " char * data;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:9]: (warning) 'operator=' should check for assignment to self to avoid problems with dynamic memory. [operatorEqToSelf]\n", errout_str()); checkOpertorEqToSelf( @@ -2582,7 +2616,7 @@ class TestClass : public TestFixture { " data = new char;\n" " *data = *a.data;\n" " return *this;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:8:8]: (warning) 'operator=' should check for assignment to self to avoid problems with dynamic memory. [operatorEqToSelf]\n", errout_str()); } @@ -2599,7 +2633,7 @@ class TestClass : public TestFixture { " {\n" " return assign(a);\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -2618,7 +2652,7 @@ class TestClass : public TestFixture { "FMat& FMat::operator=(const FMat& in)\n" "{\n" " return copy(in);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2637,10 +2671,27 @@ class TestClass : public TestFixture { "Foo& Foo::operator=(Foo& other)\n" "{\n" " return Foo::operator=(&other);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } + void operatorEqToSelf10() { + checkOpertorEqToSelf( + "class Foo\n" + "{\n" + " int32_t * p;\n" + "public:\n" + " Foo& operator=( Foo && o ) noexcept\n" + " {\n" + " delete p;\n" + " p = o.p;\n" + " o.p = nullptr;\n" + " return *this;\n" + " }\n" + "};\n"); + ASSERT_EQUALS("[test.cpp:5:10]: (warning) 'operator=' should check for assignment to self to avoid problems with dynamic memory. [operatorEqToSelf]\n", errout_str()); + } + struct CheckVirtualDestructorOptions { bool inconclusive = false; @@ -2657,7 +2708,7 @@ class TestClass : public TestFixture { ASSERT_LOC(tokenizer.tokenize(code), file, line); // Check.. - CheckClass checkClass(&tokenizer, &s, this); + CheckClassImpl checkClass(&tokenizer, s, *this); checkClass.virtualDestructor(); } @@ -2666,12 +2717,12 @@ class TestClass : public TestFixture { checkVirtualDestructor("class Derived : public Base { };\n" "Base *base = new Derived;\n" - "delete base;"); + "delete base;\n"); ASSERT_EQUALS("", errout_str()); checkVirtualDestructor("class Derived : Base { };\n" "Base *base = new Derived;\n" - "delete base;"); + "delete base;\n"); ASSERT_EQUALS("", errout_str()); } @@ -2679,27 +2730,27 @@ class TestClass : public TestFixture { // Base class doesn't have a destructor checkVirtualDestructor("class Base { };\n" - "class Derived : public Base { public: ~Derived() { (void)11; } };" + "class Derived : public Base { public: ~Derived() { (void)11; } };\n" "Base *base = new Derived;\n" - "delete base;"); + "delete base;\n"); ASSERT_EQUALS("[test.cpp:1:1]: (error) Class 'Base' which is inherited by class 'Derived' does not have a virtual destructor. [virtualDestructor]\n", errout_str()); checkVirtualDestructor("class Base { };\n" - "class Derived : protected Base { public: ~Derived() { (void)11; } };" + "class Derived : protected Base { public: ~Derived() { (void)11; } };\n" "Base *base = new Derived;\n" - "delete base;"); + "delete base;\n"); ASSERT_EQUALS("[test.cpp:1:1]: (error) Class 'Base' which is inherited by class 'Derived' does not have a virtual destructor. [virtualDestructor]\n", errout_str()); checkVirtualDestructor("class Base { };\n" - "class Derived : private Base { public: ~Derived() { (void)11; } };" + "class Derived : private Base { public: ~Derived() { (void)11; } };\n" "Base *base = new Derived;\n" - "delete base;"); + "delete base;\n"); ASSERT_EQUALS("", errout_str()); checkVirtualDestructor("class Base { };\n" - "class Derived : Base { public: ~Derived() { (void)11; } };" + "class Derived : Base { public: ~Derived() { (void)11; } };\n" "Base *base = new Derived;\n" - "delete base;"); + "delete base;\n"); ASSERT_EQUALS("", errout_str()); // #9104 @@ -2720,7 +2771,7 @@ class TestClass : public TestFixture { "{\n" " Base* p = new Derived();\n" " delete p;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:1]: (error) Class 'Base' which is inherited by class 'Derived' does not have a virtual destructor. [virtualDestructor]\n", errout_str()); checkVirtualDestructor("using namespace std;\n" @@ -2741,7 +2792,7 @@ class TestClass : public TestFixture { "{\n" " Base* p = new Derived();\n" " delete p;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:1]: (error) Class 'Base' which is inherited by class 'Derived' does not have a virtual destructor. [virtualDestructor]\n", errout_str()); } @@ -2749,21 +2800,21 @@ class TestClass : public TestFixture { // Base class has a destructor, but it's not virtual checkVirtualDestructor("class Base { public: ~Base(); };\n" - "class Derived : public Base { public: ~Derived() { (void)11; } };" + "class Derived : public Base { public: ~Derived() { (void)11; } };\n" "Base *base = new Derived;\n" - "delete base;"); + "delete base;\n"); ASSERT_EQUALS("[test.cpp:1:23]: (error) Class 'Base' which is inherited by class 'Derived' does not have a virtual destructor. [virtualDestructor]\n", errout_str()); checkVirtualDestructor("class Base { public: ~Base(); };\n" - "class Derived : protected Base { public: ~Derived() { (void)11; } };" + "class Derived : protected Base { public: ~Derived() { (void)11; } };\n" "Base *base = new Derived;\n" - "delete base;"); + "delete base;\n"); ASSERT_EQUALS("[test.cpp:1:23]: (error) Class 'Base' which is inherited by class 'Derived' does not have a virtual destructor. [virtualDestructor]\n", errout_str()); checkVirtualDestructor("class Base { public: ~Base(); };\n" - "class Derived : private Fred, public Base { public: ~Derived() { (void)11; } };" + "class Derived : private Fred, public Base { public: ~Derived() { (void)11; } };\n" "Base *base = new Derived;\n" - "delete base;"); + "delete base;\n"); ASSERT_EQUALS("[test.cpp:1:23]: (error) Class 'Base' which is inherited by class 'Derived' does not have a virtual destructor. [virtualDestructor]\n", errout_str()); } @@ -2771,15 +2822,15 @@ class TestClass : public TestFixture { // Derived class doesn't have a destructor => undefined behaviour according to paragraph 3 in [expr.delete] checkVirtualDestructor("class Base { public: ~Base(); };\n" - "class Derived : public Base { };" + "class Derived : public Base { };\n" "Base *base = new Derived;\n" - "delete base;"); + "delete base;\n"); ASSERT_EQUALS("[test.cpp:1:23]: (error) Class 'Base' which is inherited by class 'Derived' does not have a virtual destructor. [virtualDestructor]\n", errout_str()); checkVirtualDestructor("class Base { public: ~Base(); };\n" - "class Derived : private Fred, public Base { };" + "class Derived : private Fred, public Base { };\n" "Base *base = new Derived;\n" - "delete base;"); + "delete base;\n"); ASSERT_EQUALS("[test.cpp:1:23]: (error) Class 'Base' which is inherited by class 'Derived' does not have a virtual destructor. [virtualDestructor]\n", errout_str()); } @@ -2787,15 +2838,15 @@ class TestClass : public TestFixture { // Derived class has empty destructor => undefined behaviour according to paragraph 3 in [expr.delete] checkVirtualDestructor("class Base { public: ~Base(); };\n" - "class Derived : public Base { public: ~Derived() {} };" + "class Derived : public Base { public: ~Derived() {} };\n" "Base *base = new Derived;\n" - "delete base;"); + "delete base;\n"); ASSERT_EQUALS("[test.cpp:1:23]: (error) Class 'Base' which is inherited by class 'Derived' does not have a virtual destructor. [virtualDestructor]\n", errout_str()); checkVirtualDestructor("class Base { public: ~Base(); };\n" - "class Derived : public Base { public: ~Derived(); }; Derived::~Derived() {}" + "class Derived : public Base { public: ~Derived(); }; Derived::~Derived() {}\n" "Base *base = new Derived;\n" - "delete base;"); + "delete base;\n"); ASSERT_EQUALS("[test.cpp:1:23]: (error) Class 'Base' which is inherited by class 'Derived' does not have a virtual destructor. [virtualDestructor]\n", errout_str()); } @@ -2804,8 +2855,38 @@ class TestClass : public TestFixture { // points at derived class checkVirtualDestructor("class Base { public: ~Base(); };\n" - "class Derived : public Base { public: ~Derived() { (void)11; } };"); + "class Derived : public Base { public: ~Derived() { (void)11; } };\n"); ASSERT_EQUALS("", errout_str()); + + // the pointer is deleted before it points at the derived class, or another pointer is deleted + checkVirtualDestructor("class Base { public: ~Base(); };\n" + "class Derived : public Base { public: ~Derived() { (void)11; } };\n" + "void f(Base *p, Base *q) {\n" + " delete p;\n" + " p = new Derived;\n" + " delete q;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + // only the derived class that the deleted pointer points at + checkVirtualDestructor("class Base { public: ~Base(); };\n" + "class Derived1 : public Base { public: ~Derived1() { (void)11; } };\n" + "class Derived2 : public Base { public: ~Derived2() { (void)11; } };\n" + "void f(Base *p, Base *q) {\n" + " p = new Derived1;\n" + " q = new Derived2;\n" + " delete q;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:1:23]: (error) Class 'Base' which is inherited by class 'Derived2' does not have a virtual destructor. [virtualDestructor]\n", errout_str()); + + // the derived class is a template instantiation, its name contains spaces + checkVirtualDestructor("class Base { public: ~Base(); };\n" + "template class Derived : public Base { public: ~Derived() { (void)11; } };\n" + "void f(Base *p) {\n" + " p = new Derived;\n" + " delete p;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:1:23]: (error) Class 'Base' which is inherited by class 'Derived < unsigned int >' does not have a virtual destructor. [virtualDestructor]\n", errout_str()); } void virtualDestructorProtected() { @@ -2821,7 +2902,7 @@ class TestClass : public TestFixture { "{\n" "public:\n" " ~B() { int a; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -2842,7 +2923,7 @@ class TestClass : public TestFixture { "{\n" "public:\n" " ~B() { int a; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // class A inherits virtual destructor from struct Base -> no error @@ -2860,7 +2941,7 @@ class TestClass : public TestFixture { "{\n" "public:\n" " ~B() { int a; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // Unknown Base class -> it could have virtual destructor, so ignore @@ -2874,7 +2955,7 @@ class TestClass : public TestFixture { "{\n" "public:\n" " ~B() { int a; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // Virtual destructor is inherited -> no error @@ -2895,7 +2976,7 @@ class TestClass : public TestFixture { "{\n" "public:\n" " ~B() { int a; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // class A doesn't inherit virtual destructor from class Base -> error @@ -2914,7 +2995,7 @@ class TestClass : public TestFixture { "{\n" "public:\n" " ~B() { int a; }\n" - "};"); + "};\n"); TODO_ASSERT_EQUALS("[test.cpp:7]: (error) Class 'Base' which is inherited by class 'B' does not have a virtual destructor.\n", "", errout_str()); } @@ -2936,7 +3017,7 @@ class TestClass : public TestFixture { " ~B(){int a;}\n" "};\n" "\n" - "AA *p = new B; delete p;"); + "AA *p = new B; delete p;\n"); ASSERT_EQUALS("[test.cpp:9:3]: (error) Class 'AA < double >' which is inherited by class 'B' does not have a virtual destructor. [virtualDestructor]\n", errout_str()); } @@ -2994,7 +3075,7 @@ class TestClass : public TestFixture { ASSERT_LOC(tokenizer.tokenize(code), file, line); // Check.. - CheckClass checkClass(&tokenizer, &settings, this); + CheckClassImpl checkClass(&tokenizer, settings, *this); checkClass.checkMemset(); } @@ -3006,7 +3087,7 @@ class TestClass : public TestFixture { "{\n" " Fred fred;\n" " memset(&fred, 0, sizeof(Fred));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); checkNoMemset("class Fred\n" @@ -3017,7 +3098,7 @@ class TestClass : public TestFixture { "{\n" " Fred fred;\n" " memset(&fred, 0, sizeof(Fred));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); checkNoMemset("class Fred\n" @@ -3028,7 +3109,7 @@ class TestClass : public TestFixture { "{\n" " Fred fred;\n" " memset(&fred, 0, sizeof(Fred));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); checkNoMemset("class Fred\n" @@ -3039,7 +3120,7 @@ class TestClass : public TestFixture { "{\n" " Fred fred;\n" " memset(&fred, 0, sizeof(Fred));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:5]: (error) Using 'memset' on class that contains a 'std::string'. [memsetClass]\n", errout_str()); checkNoMemset("class Fred\n" @@ -3050,7 +3131,7 @@ class TestClass : public TestFixture { "{\n" " Fred fred;\n" " memset(&fred, 0, sizeof(Fred));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:5]: (error) Using 'memset' on class that contains a 'std::string'. [memsetClass]\n", errout_str()); checkNoMemset("class Fred {\n" @@ -3059,7 +3140,7 @@ class TestClass : public TestFixture { "};\n" "void Fred::f() {\n" " memset(this, 0, sizeof(*this));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:5]: (error) Using 'memset' on class that contains a 'std::string'. [memsetClass]\n", errout_str()); checkNoMemset("class Fred\n" @@ -3069,7 +3150,7 @@ class TestClass : public TestFixture { "{\n" " Fred fred;\n" " memset(&fred, 0, sizeof(fred));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); checkNoMemset("class Fred\n" @@ -3080,7 +3161,7 @@ class TestClass : public TestFixture { "{\n" " Fred fred;\n" " memset(&fred, 0, sizeof(fred));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:5]: (error) Using 'memset' on class that contains a 'std::string'. [memsetClass]\n", errout_str()); checkNoMemset("class Fred\n" @@ -3092,7 +3173,7 @@ class TestClass : public TestFixture { "{\n" " Pebbles pebbles;\n" " memset(&pebbles, 0, sizeof(pebbles));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:9:5]: (error) Using 'memset' on class that contains a 'std::string'. [memsetClass]\n", errout_str()); checkNoMemset("class Fred\n" @@ -3103,7 +3184,7 @@ class TestClass : public TestFixture { "{\n" " Fred fred;\n" " memset(&fred, 0, sizeof(fred));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:5]: (error) Using 'memset' on class that contains a virtual function. [memsetClass]\n", errout_str()); checkNoMemset("class Fred\n" @@ -3114,7 +3195,7 @@ class TestClass : public TestFixture { "{\n" " static Fred fred;\n" " memset(&fred, 0, sizeof(fred));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:5]: (error) Using 'memset' on class that contains a virtual function. [memsetClass]\n", errout_str()); checkNoMemset("class Fred\n" @@ -3129,7 +3210,7 @@ class TestClass : public TestFixture { "{\n" " Pebbles pebbles;\n" " memset(&pebbles, 0, sizeof(pebbles));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:12:5]: (error) Using 'memset' on class that contains a virtual function. [memsetClass]\n", errout_str()); // Fred not defined in scope @@ -3143,7 +3224,7 @@ class TestClass : public TestFixture { "{\n" " Fred fred;\n" " memset(&fred, 0, sizeof(Fred));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Fred with namespace qualifier @@ -3157,7 +3238,7 @@ class TestClass : public TestFixture { "{\n" " n1::Fred fred;\n" " memset(&fred, 0, sizeof(n1::Fred));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:10:5]: (error) Using 'memset' on class that contains a 'std::string'. [memsetClass]\n", errout_str()); // Fred with namespace qualifier @@ -3171,7 +3252,7 @@ class TestClass : public TestFixture { "{\n" " n1::Fred fred;\n" " memset(&fred, 0, sizeof(fred));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:10:5]: (error) Using 'memset' on class that contains a 'std::string'. [memsetClass]\n", errout_str()); checkNoMemset("class A {\n" @@ -3182,7 +3263,7 @@ class TestClass : public TestFixture { " const int N = 10;\n" " A** arr = new A*[N];\n" " memset(arr, 0, N * sizeof(A*));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); checkNoMemset("class A {\n" // #5116 - nested class data is mixed in the SymbolDatabase @@ -3191,7 +3272,7 @@ class TestClass : public TestFixture { "};\n" "void f(A::B *b) {\n" " memset(b,0,4);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #4461 Warn about memset/memcpy on class with references as members @@ -3201,7 +3282,7 @@ class TestClass : public TestFixture { "void f() {\n" " A a;\n" " memset(&a, 0, sizeof(a));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:3]: (error) Using 'memset' on class that contains a reference. [memsetClassReference]\n", errout_str()); checkNoMemset("class A {\n" " const B&b;\n" @@ -3209,7 +3290,7 @@ class TestClass : public TestFixture { "void f() {\n" " A a;\n" " memset(&a, 0, sizeof(a));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:3]: (error) Using 'memset' on class that contains a reference. [memsetClassReference]\n", errout_str()); // #7456 @@ -3223,7 +3304,7 @@ class TestClass : public TestFixture { "void func() {\n" " B b[4];\n" " memset(b, 0, sizeof(b));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #8619 @@ -3278,11 +3359,11 @@ class TestClass : public TestFixture { " } vid;\n" " };\n" " } hdr;\n" - "};" + "};\n" "void parseHeader() {\n" " ASFStreamHeader strhdr;\n" " memset(&strhdr, 0, sizeof(strhdr));\n" - "}"); + "}\n"); } void memsetOnStruct() { @@ -3293,7 +3374,7 @@ class TestClass : public TestFixture { "{\n" " A a;\n" " memset(&a, 0, sizeof(A));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); checkNoMemset("struct A\n" @@ -3303,7 +3384,7 @@ class TestClass : public TestFixture { "{\n" " struct A a;\n" " memset(&a, 0, sizeof(struct A));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); checkNoMemset("struct A\n" @@ -3313,14 +3394,14 @@ class TestClass : public TestFixture { "{\n" " struct A a;\n" " memset(&a, 0, sizeof(A));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); checkNoMemset("void f()\n" "{\n" " struct sockaddr_in6 fail;\n" " memset(&fail, 0, sizeof(struct sockaddr_in6));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); checkNoMemset("struct A\n" @@ -3333,7 +3414,7 @@ class TestClass : public TestFixture { "{\n" " struct A fail;\n" " memset(&fail, 0, sizeof(struct A));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:10:2]: (error) Using 'memset' on struct that contains a 'std::string'. [memsetClass]\n", errout_str()); checkNoMemset("struct Fred\n" @@ -3344,7 +3425,7 @@ class TestClass : public TestFixture { "{\n" " Fred fred;\n" " memset(&fred, 0, sizeof(fred));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:5]: (error) Using 'memset' on struct that contains a 'std::string'. [memsetClass]\n", errout_str()); checkNoMemset("struct Stringy {\n" @@ -3356,7 +3437,7 @@ class TestClass : public TestFixture { "int main() {\n" " Foo foo;\n" " memset(&foo, 0, sizeof(Foo));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:9:5]: (error) Using 'memset' on struct that contains a 'std::string'. [memsetClass]\n", errout_str()); } @@ -3369,7 +3450,7 @@ class TestClass : public TestFixture { "{\n" " A a;\n" " memset(&a, 0, sizeof(A));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:5]: (error) Using 'memset' on class that contains a 'std::vector'. [memsetClass]\n", errout_str()); checkNoMemset("struct A\n" @@ -3379,7 +3460,7 @@ class TestClass : public TestFixture { "{\n" " A a;\n" " memset(&a, 0, sizeof(A));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:5]: (error) Using 'memset' on struct that contains a 'std::vector'. [memsetClass]\n", errout_str()); checkNoMemset("struct A\n" @@ -3389,7 +3470,7 @@ class TestClass : public TestFixture { "{\n" " A a;\n" " memset(&a, 0, sizeof(struct A));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:5]: (error) Using 'memset' on struct that contains a 'std::vector'. [memsetClass]\n", errout_str()); checkNoMemset("struct A\n" @@ -3399,7 +3480,7 @@ class TestClass : public TestFixture { "{\n" " A a;\n" " memset(&a, 0, sizeof(a));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:5]: (error) Using 'memset' on struct that contains a 'std::vector'. [memsetClass]\n", errout_str()); checkNoMemset("class A\n" @@ -3409,7 +3490,7 @@ class TestClass : public TestFixture { "{\n" " A a;\n" " memset(&a, 0, sizeof(A));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:5]: (error) Using 'memset' on class that contains a 'std::vector'. [memsetClass]\n", errout_str()); checkNoMemset("struct A\n" @@ -3419,7 +3500,7 @@ class TestClass : public TestFixture { "{\n" " A a;\n" " memset(&a, 0, sizeof(A));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:5]: (error) Using 'memset' on struct that contains a 'std::vector'. [memsetClass]\n", errout_str()); checkNoMemset("struct A\n" @@ -3429,7 +3510,7 @@ class TestClass : public TestFixture { "{\n" " A a;\n" " memset(&a, 0, sizeof(a));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:5]: (error) Using 'memset' on struct that contains a 'std::vector'. [memsetClass]\n", errout_str()); checkNoMemset("struct A\n" @@ -3439,7 +3520,7 @@ class TestClass : public TestFixture { "{\n" " A a;\n" " memset(&a, 0, sizeof(A));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:5]: (error) Using 'memset' on struct that contains a 'std::vector'. [memsetClass]\n", errout_str()); checkNoMemset("struct A {\n" @@ -3449,7 +3530,7 @@ class TestClass : public TestFixture { "void f() {\n" " A a;\n" " memset(a, 0, 100);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #4460 checkNoMemset("struct C {\n" @@ -3467,7 +3548,7 @@ class TestClass : public TestFixture { " memset(*c4, 0, 10);\n" " memset(c2, 0, 10);\n" " memset(c3, 0, 10);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:9:5]: (error) Using 'memset' on struct that contains a 'std::string'. [memsetClass]\n" "[test.cpp:11:5]: (error) Using 'memset' on struct that contains a 'std::string'. [memsetClass]\n" "[test.cpp:12:5]: (error) Using 'memset' on struct that contains a 'std::string'. [memsetClass]\n" @@ -3483,7 +3564,7 @@ class TestClass : public TestFixture { " multilevel_data *d = (multilevel_data *) malloc(sizeof(multilevel_data));\n" " memset(d, 0, sizeof(multilevel_data));\n" " return (void*) d;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:3]: (portability) Using memset() on struct which contains a floating point number. [memsetClassFloat]\n", errout_str()); } @@ -3501,7 +3582,7 @@ class TestClass : public TestFixture { "void f() {\n" " A a;\n" " memset(&a, 0, sizeof(A));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // std::array is POD (#5481) checkNoMemset("struct st {\n" @@ -3512,7 +3593,7 @@ class TestClass : public TestFixture { "void f() {\n" " st s;\n" " std::memset(&s, 0, sizeof(st));\n" - "}", settings); + "}\n", settings); ASSERT_EQUALS("", errout_str()); } @@ -3523,7 +3604,7 @@ class TestClass : public TestFixture { "void f() {\n" " A a;\n" " memset(&a, 0, sizeof(A));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:5]: (portability) Using memset() on struct which contains a floating point number. [memsetClassFloat]\n", errout_str()); checkNoMemset("struct A {\n" @@ -3532,7 +3613,7 @@ class TestClass : public TestFixture { "void f() {\n" " A a;\n" " memset(&a, 0, sizeof(A));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:5]: (portability) Using memset() on struct which contains a floating point number. [memsetClassFloat]\n", errout_str()); checkNoMemset("struct A {\n" @@ -3541,7 +3622,7 @@ class TestClass : public TestFixture { "void f(const A& b) {\n" " A a;\n" " memcpy(&a, &b, sizeof(A));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); checkNoMemset("struct A {\n" @@ -3550,7 +3631,7 @@ class TestClass : public TestFixture { "void f() {\n" " A a;\n" " memset(&a, 0, sizeof(A));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -3558,7 +3639,7 @@ class TestClass : public TestFixture { checkNoMemset("void clang_tokenize(CXToken **Tokens) {\n" " *Tokens = (CXToken *)malloc(sizeof(CXToken) * CXTokens.size());\n" " memmove(*Tokens, CXTokens.data(), sizeof(CXToken) * CXTokens.size());\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -3566,49 +3647,49 @@ class TestClass : public TestFixture { checkNoMemset("class C { C() {} };\n" "void foo(C*& p) {\n" " p = malloc(sizeof(C));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5] -> [test.cpp:1:1]: (warning) Memory for class instance allocated with malloc(), but class provides constructors. [mallocOnClassWarning]\n", errout_str()); checkNoMemset("class C { C(int z, Foo bar) { bar(); } };\n" "void foo(C*& p) {\n" " p = malloc(sizeof(C));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5] -> [test.cpp:1:1]: (warning) Memory for class instance allocated with malloc(), but class provides constructors. [mallocOnClassWarning]\n", errout_str()); checkNoMemset("struct C { C() {} };\n" "void foo(C*& p) {\n" " p = realloc(p, sizeof(C));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5] -> [test.cpp:1:1]: (warning) Memory for class instance allocated with realloc(), but class provides constructors. [mallocOnClassWarning]\n", errout_str()); checkNoMemset("struct C { virtual void bar(); };\n" "void foo(C*& p) {\n" " p = malloc(sizeof(C));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:9] -> [test.cpp:1:1]: (error) Memory for class instance allocated with malloc(), but class contains a virtual function. [mallocOnClassError]\n", errout_str()); checkNoMemset("struct C { std::string s; };\n" "void foo(C*& p) {\n" " p = malloc(sizeof(C));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:9] -> [test.cpp:1:1]: (error) Memory for class instance allocated with malloc(), but class contains a 'std::string'. [mallocOnClassError]\n", errout_str()); checkNoMemset("class C { };\n" // C-Style class/struct "void foo(C*& p) {\n" " p = malloc(sizeof(C));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); checkNoMemset("struct C { C() {} };\n" "void foo(C*& p) {\n" " p = new C();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); checkNoMemset("class C { C() {} };\n" "void foo(D*& p) {\n" // Unknown type " p = malloc(sizeof(C));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); checkNoMemset("class AutoCloseFD {\n" @@ -3619,7 +3700,7 @@ class TestClass : public TestFixture { "};\n" "void f() {\n" " AutoCloseFD fd = open(\"abc\", O_RDONLY | O_CLOEXEC);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); checkNoMemset("struct C {\n" // #12313 @@ -3628,7 +3709,7 @@ class TestClass : public TestFixture { "};\n" "void f() {\n" " C c = strdup(\"abc\");\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -3640,24 +3721,24 @@ class TestClass : public TestFixture { ASSERT_LOC(tokenizer.tokenize(code), file, line); // Check.. - CheckClass checkClass(&tokenizer, &settings1, this); + CheckClassImpl checkClass(&tokenizer, settings1, *this); checkClass.thisSubtraction(); } void this_subtraction() { - checkThisSubtraction("; this-x ;"); + checkThisSubtraction("; this-x ;\n"); ASSERT_EQUALS("[test.cpp:1:3]: (warning) Suspicious pointer subtraction. Did you intend to write '->'? [thisSubtraction]\n", errout_str()); - checkThisSubtraction("; *this = *this-x ;"); + checkThisSubtraction("; *this = *this-x ;\n"); ASSERT_EQUALS("", errout_str()); checkThisSubtraction("; *this = *this-x ;\n" - "this-x ;"); + "this-x ;\n"); ASSERT_EQUALS("[test.cpp:2:1]: (warning) Suspicious pointer subtraction. Did you intend to write '->'? [thisSubtraction]\n", errout_str()); checkThisSubtraction("; *this = *this-x ;\n" "this-x ;\n" - "this-x ;"); + "this-x ;\n"); ASSERT_EQUALS("[test.cpp:2:1]: (warning) Suspicious pointer subtraction. Did you intend to write '->'? [thisSubtraction]\n" "[test.cpp:3:1]: (warning) Suspicious pointer subtraction. Did you intend to write '->'? [thisSubtraction]\n", errout_str()); } @@ -3680,7 +3761,7 @@ class TestClass : public TestFixture { SimpleTokenizer tokenizer(settings, *this); ASSERT_LOC(tokenizer.tokenize(code), file, line); - CheckClass checkClass(&tokenizer, &settings, this); + CheckClassImpl checkClass(&tokenizer, settings, *this); (checkClass.checkConst)(); } @@ -3688,18 +3769,18 @@ class TestClass : public TestFixture { checkConst("class Fred {\n" " int a;\n" " int getA() { return a; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:9]: (style, inconclusive) Technically the member function 'Fred::getA' can be const. [functionConst]\n", errout_str()); checkConst("class Fred {\n" " const std::string foo() { return \"\"; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:2:23]: (style) The member function 'Fred::foo' can be static. [functionStatic]\n", errout_str()); checkConst("class Fred {\n" " std::string s;\n" " const std::string & foo() { return \"\"; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:25]: (style) The member function 'Fred::foo' can be static. [functionStatic]\n", errout_str()); // constructors can't be const.. @@ -3707,14 +3788,14 @@ class TestClass : public TestFixture { " int a;\n" "public:\n" " Fred() { }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // assignment through |=.. checkConst("class Fred {\n" " int a;\n" " int setA() { a |= true; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // functions with a call to a member function can only be const, if that member function is const, too.. (#1305) @@ -3723,7 +3804,7 @@ class TestClass : public TestFixture { " int x;\n" " void a() { x = 1; }\n" " void b() { a(); }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class Fred {\n" @@ -3731,14 +3812,14 @@ class TestClass : public TestFixture { " int x;\n" " int a() const { return x; }\n" " void b() { a(); }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:5:10]: (style, inconclusive) Technically the member function 'Fred::b' can be const. [functionConst]\n", errout_str()); checkConst("class Fred {\n" "public:\n" " int x;\n" " void b() { a(); }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:10]: (style) The member function 'Fred::b' can be static. [functionStatic]\n", errout_str()); // static functions can't be const.. @@ -3747,12 +3828,12 @@ class TestClass : public TestFixture { "public:\n" " static unsigned get()\n" " { return 0; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class Fred {\n" " const std::string foo() const throw() { return \"\"; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:2:23]: (style) The member function 'Fred::foo' can be static. [functionStatic]\n", errout_str()); } @@ -3762,49 +3843,49 @@ class TestClass : public TestFixture { checkConst("class Fred {\n" " std::string s;\n" " void foo() { s = \"\"; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // assignment to function argument reference can be const checkConst("class Fred {\n" " std::string s;\n" " void foo(std::string & a) { a = s; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style, inconclusive) Technically the member function 'Fred::foo' can be const. [functionConst]\n", errout_str()); // assignment to variable can't be const checkConst("class Fred {\n" " std::string s;\n" " void foo(std::string & a) { s = a; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // assignment to function argument references can be const checkConst("class Fred {\n" " std::string s;\n" " void foo(std::string & a, std::string & b) { a = s; b = s; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style, inconclusive) Technically the member function 'Fred::foo' can be const. [functionConst]\n", errout_str()); // assignment to variable, can't be const checkConst("class Fred {\n" " std::string s;\n" " void foo(std::string & a, std::string & b) { s = a; s = b; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // assignment to variable, can't be const checkConst("class Fred {\n" " std::string s;\n" " void foo(std::string & a, std::string & b) { s = a; b = a; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // assignment to variable, can't be const checkConst("class Fred {\n" " std::string s;\n" " void foo(std::string & a, std::string & b) { a = s; s = b; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -3813,42 +3894,42 @@ class TestClass : public TestFixture { checkConst("class Fred {\n" " int s;\n" " void foo(int * a) { *a = s; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style, inconclusive) Technically the member function 'Fred::foo' can be const. [functionConst]\n", errout_str()); // assignment to variable, can't be const checkConst("class Fred {\n" " int s;\n" " void foo(int * a) { s = *a; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // assignment to function argument pointers can be const checkConst("class Fred {\n" " std::string s;\n" " void foo(std::string * a, std::string * b) { *a = s; *b = s; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style, inconclusive) Technically the member function 'Fred::foo' can be const. [functionConst]\n", errout_str()); // assignment to variable, can't be const checkConst("class Fred {\n" " std::string s;\n" " void foo(std::string * a, std::string * b) { s = *a; s = *b; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // assignment to variable, can't be const checkConst("class Fred {\n" " std::string s;\n" " void foo(std::string * a, std::string * b) { s = *a; *b = s; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // assignment to variable, can't be const checkConst("class Fred {\n" " std::string s;\n" " void foo(std::string * a, std::string * b) { *a = s; s = b; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -3857,14 +3938,14 @@ class TestClass : public TestFixture { " int a;\n" " int getA();\n" "};\n" - "int Fred::getA() { return a; }"); + "int Fred::getA() { return a; }\n"); ASSERT_EQUALS("[test.cpp:3:9] -> [test.cpp:5:11]: (style, inconclusive) Technically the member function 'Fred::getA' can be const. [functionConst]\n", errout_str()); checkConst("class Fred {\n" " std::string s;\n" " const std::string & foo();\n" "};\n" - "const std::string & Fred::foo() { return \"\"; }"); + "const std::string & Fred::foo() { return \"\"; }\n"); ASSERT_EQUALS("[test.cpp:3:25] -> [test.cpp:5:27]: (style) The member function 'Fred::foo' can be static. [functionStatic]\n", errout_str()); // functions with a function call to a non-const member can't be const.. (#1305) @@ -3875,7 +3956,7 @@ class TestClass : public TestFixture { " void a() { x = 1; }\n" " void b();\n" "};\n" - "void Fred::b() { a(); }"); + "void Fred::b() { a(); }\n"); ASSERT_EQUALS("", errout_str()); // static functions can't be const.. @@ -3884,7 +3965,7 @@ class TestClass : public TestFixture { "public:\n" " static unsigned get();\n" "};\n" - "static unsigned Fred::get() { return 0; }"); + "static unsigned Fred::get() { return 0; }\n"); ASSERT_EQUALS("", errout_str()); // assignment to variable can't be const @@ -3892,7 +3973,7 @@ class TestClass : public TestFixture { " std::string s;\n" " void foo();\n" "};\n" - "void Fred::foo() { s = \"\"; }"); + "void Fred::foo() { s = \"\"; }\n"); ASSERT_EQUALS("", errout_str()); // assignment to function argument reference can be const @@ -3900,7 +3981,7 @@ class TestClass : public TestFixture { " std::string s;\n" " void foo(std::string & a);\n" "};\n" - "void Fred::foo(std::string & a) { a = s; }"); + "void Fred::foo(std::string & a) { a = s; }\n"); ASSERT_EQUALS("[test.cpp:3:10] -> [test.cpp:5:12]: (style, inconclusive) Technically the member function 'Fred::foo' can be const. [functionConst]\n", errout_str()); // assignment to variable can't be const @@ -3908,7 +3989,7 @@ class TestClass : public TestFixture { " std::string s;\n" " void foo(std::string & a);\n" "};\n" - "void Fred::foo(std::string & a) { s = a; }"); + "void Fred::foo(std::string & a) { s = a; }\n"); ASSERT_EQUALS("", errout_str()); // assignment to function argument references can be const @@ -3916,7 +3997,7 @@ class TestClass : public TestFixture { " std::string s;\n" " void foo(std::string & a, std::string & b);\n" "};\n" - "void Fred::foo(std::string & a, std::string & b) { a = s; b = s; }"); + "void Fred::foo(std::string & a, std::string & b) { a = s; b = s; }\n"); ASSERT_EQUALS("[test.cpp:3:10] -> [test.cpp:5:12]: (style, inconclusive) Technically the member function 'Fred::foo' can be const. [functionConst]\n", errout_str()); // assignment to variable, can't be const @@ -3924,7 +4005,7 @@ class TestClass : public TestFixture { " std::string s;\n" " void foo(std::string & a, std::string & b);\n" "};\n" - "void Fred::foo(std::string & a, std::string & b) { s = a; s = b; }"); + "void Fred::foo(std::string & a, std::string & b) { s = a; s = b; }\n"); ASSERT_EQUALS("", errout_str()); // assignment to variable, can't be const @@ -3932,7 +4013,7 @@ class TestClass : public TestFixture { " std::string s;\n" " void foo(std::string & a, std::string & b);\n" "};\n" - "void Fred::foo(std::string & a, std::string & b) { s = a; b = a; }"); + "void Fred::foo(std::string & a, std::string & b) { s = a; b = a; }\n"); ASSERT_EQUALS("", errout_str()); // assignment to variable, can't be const @@ -3940,7 +4021,7 @@ class TestClass : public TestFixture { " std::string s;\n" " void foo(std::string & a, std::string & b);\n" "};\n" - "void Fred::foo(std::string & a, std::string & b) { a = s; s = b; }"); + "void Fred::foo(std::string & a, std::string & b) { a = s; s = b; }\n"); ASSERT_EQUALS("", errout_str()); // assignment to function argument pointer can be const @@ -3948,7 +4029,7 @@ class TestClass : public TestFixture { " int s;\n" " void foo(int * a);\n" "};\n" - "void Fred::foo(int * a) { *a = s; }"); + "void Fred::foo(int * a) { *a = s; }\n"); ASSERT_EQUALS("[test.cpp:3:10] -> [test.cpp:5:12]: (style, inconclusive) Technically the member function 'Fred::foo' can be const. [functionConst]\n", errout_str()); // assignment to variable, can't be const @@ -3956,7 +4037,7 @@ class TestClass : public TestFixture { " int s;\n" " void foo(int * a);\n" "};\n" - "void Fred::foo(int * a) { s = *a; }"); + "void Fred::foo(int * a) { s = *a; }\n"); ASSERT_EQUALS("", errout_str()); // assignment to function argument pointers can be const @@ -3964,7 +4045,7 @@ class TestClass : public TestFixture { " std::string s;\n" " void foo(std::string * a, std::string * b);\n" "};\n" - "void Fred::foo(std::string * a, std::string * b) { *a = s; *b = s; }"); + "void Fred::foo(std::string * a, std::string * b) { *a = s; *b = s; }\n"); ASSERT_EQUALS("[test.cpp:3:10] -> [test.cpp:5:12]: (style, inconclusive) Technically the member function 'Fred::foo' can be const. [functionConst]\n", errout_str()); // assignment to variable, can't be const @@ -3972,7 +4053,7 @@ class TestClass : public TestFixture { " std::string s;\n" " void foo(std::string * a, std::string * b);\n" "};\n" - "void Fred::foo(std::string * a, std::string * b) { s = *a; s = *b; }"); + "void Fred::foo(std::string * a, std::string * b) { s = *a; s = *b; }\n"); ASSERT_EQUALS("", errout_str()); // assignment to variable, can't be const @@ -3980,7 +4061,7 @@ class TestClass : public TestFixture { " std::string s;\n" " void foo(std::string * a, std::string * b);\n" "};\n" - "void Fred::foo(std::string * a, std::string * b) { s = *a; *b = s; }"); + "void Fred::foo(std::string * a, std::string * b) { s = *a; *b = s; }\n"); ASSERT_EQUALS("", errout_str()); // assignment to variable, can't be const @@ -3988,7 +4069,7 @@ class TestClass : public TestFixture { " std::string s;\n" " void foo(std::string * a, std::string * b);\n" "};\n" - "void Fred::foo(std::string * a, std::string * b) { *a = s; s = b; }"); + "void Fred::foo(std::string * a, std::string * b) { *a = s; s = b; }\n"); ASSERT_EQUALS("", errout_str()); // check functions with same name @@ -3998,11 +4079,11 @@ class TestClass : public TestFixture { " void foo(std::string & a);\n" " void foo(const std::string & a);\n" "};\n" - "void Fred::foo() { }" - "void Fred::foo(std::string & a) { a = s; }" - "void Fred::foo(const std::string & a) { s = a; }"); + "void Fred::foo() { }\n" + "void Fred::foo(std::string & a) { a = s; }\n" + "void Fred::foo(const std::string & a) { s = a; }\n"); ASSERT_EQUALS("[test.cpp:3:10] -> [test.cpp:7:12]: (style) The member function 'Fred::foo' can be static. [functionStatic]\n" - "[test.cpp:4:10] -> [test.cpp:7:32]: (style, inconclusive) Technically the member function 'Fred::foo' can be const. [functionConst]\n", errout_str()); + "[test.cpp:4:10] -> [test.cpp:8:12]: (style, inconclusive) Technically the member function 'Fred::foo' can be const. [functionConst]\n", errout_str()); // check functions with different or missing parameter names checkConst("class Fred {\n" @@ -4017,7 +4098,7 @@ class TestClass : public TestFixture { "void Fred::foo2(int c, int d) { }\n" "void Fred::foo3(int a, int b) { }\n" "void Fred::foo4(int a, int b) { }\n" - "void Fred::foo5(int, int) { }"); + "void Fred::foo5(int, int) { }\n"); ASSERT_EQUALS("[test.cpp:3:10] -> [test.cpp:9:12]: (style) The member function 'Fred::foo1' can be static. [functionStatic]\n" "[test.cpp:4:10] -> [test.cpp:10:12]: (style) The member function 'Fred::foo2' can be static. [functionStatic]\n" "[test.cpp:5:10] -> [test.cpp:11:12]: (style) The member function 'Fred::foo3' can be static. [functionStatic]\n" @@ -4030,7 +4111,7 @@ class TestClass : public TestFixture { " int a;\n" " int getA() { return a; }\n" " };\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:13]: (style, inconclusive) Technically the member function 'Fred::A::getA' can be const. [functionConst]\n", errout_str()); checkConst("class Fred {\n" @@ -4039,7 +4120,7 @@ class TestClass : public TestFixture { " int getA();\n" " };\n" " int A::getA() { return a; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:13] -> [test.cpp:6:12]: (style, inconclusive) Technically the member function 'Fred::A::getA' can be const. [functionConst]\n", errout_str()); checkConst("class Fred {\n" @@ -4048,7 +4129,7 @@ class TestClass : public TestFixture { " int getA();\n" " };\n" "};\n" - "int Fred::A::getA() { return a; }"); + "int Fred::A::getA() { return a; }\n"); ASSERT_EQUALS("[test.cpp:4:13] -> [test.cpp:7:14]: (style, inconclusive) Technically the member function 'Fred::A::getA' can be const. [functionConst]\n", errout_str()); // check deeply nested classes @@ -4061,7 +4142,7 @@ class TestClass : public TestFixture { " int getA() { return a; }\n" " };\n" " };\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:13]: (style, inconclusive) Technically the member function 'Fred::B::getB' can be const. [functionConst]\n" "[test.cpp:7:17]: (style, inconclusive) Technically the member function 'Fred::B::A::getA' can be const. [functionConst]\n" , errout_str()); @@ -4077,7 +4158,7 @@ class TestClass : public TestFixture { " int A::getA() { return a; }\n" " };\n" " int B::getB() { return b; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:13] -> [test.cpp:11:12]: (style, inconclusive) Technically the member function 'Fred::B::getB' can be const. [functionConst]\n" "[test.cpp:7:17] -> [test.cpp:9:16]: (style, inconclusive) Technically the member function 'Fred::B::A::getA' can be const. [functionConst]\n", errout_str()); @@ -4092,7 +4173,7 @@ class TestClass : public TestFixture { " };\n" " int B::A::getA() { return a; }\n" " int B::getB() { return b; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:13] -> [test.cpp:11:12]: (style, inconclusive) Technically the member function 'Fred::B::getB' can be const. [functionConst]\n" "[test.cpp:7:17] -> [test.cpp:10:15]: (style, inconclusive) Technically the member function 'Fred::B::A::getA' can be const. [functionConst]\n", errout_str()); @@ -4107,7 +4188,7 @@ class TestClass : public TestFixture { " };\n" "};\n" "int Fred::B::A::getA() { return a; }\n" - "int Fred::B::getB() { return b; }"); + "int Fred::B::getB() { return b; }\n"); ASSERT_EQUALS("[test.cpp:4:13] -> [test.cpp:12:14]: (style, inconclusive) Technically the member function 'Fred::B::getB' can be const. [functionConst]\n" "[test.cpp:7:17] -> [test.cpp:11:17]: (style, inconclusive) Technically the member function 'Fred::B::A::getA' can be const. [functionConst]\n", errout_str()); } @@ -4117,7 +4198,7 @@ class TestClass : public TestFixture { checkConst("struct Fred {\n" " int a;\n" " bool operator<(const Fred &f) { return a < f.a; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style, inconclusive) Technically the member function 'Fred::operator<' can be const. [functionConst]\n", errout_str()); } @@ -4132,7 +4213,7 @@ class TestClass : public TestFixture { " {\n" " foo << 123;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("struct Foo {\n" @@ -4144,7 +4225,7 @@ class TestClass : public TestFixture { " {\n" " std::cout << foo << 123;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:6:10]: (style, inconclusive) Technically the member function 'Fred::x' can be const. [functionConst]\n", errout_str()); } @@ -4153,13 +4234,13 @@ class TestClass : public TestFixture { " int array[10];\n" " int const & operator [] (unsigned int index) const { return array[index]; }\n" " int & operator [] (unsigned int index) { return array[index]; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("struct Fred {\n" " int array[10];\n" " int const & operator [] (unsigned int index) { return array[index]; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:17]: (style, inconclusive) Technically the member function 'Fred::operator[]' can be const. [functionConst]\n", errout_str()); } @@ -4169,14 +4250,14 @@ class TestClass : public TestFixture { " int c;\n" "public:\n" " operator int*() { return &c; };\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class A {\n" " int c;\n" "public:\n" " operator const int*() { return &c; };\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:5]: (style, inconclusive) Technically the member function 'A::operatorconstint*' can be const. [functionConst]\n", errout_str()); // #2375 @@ -4184,14 +4265,14 @@ class TestClass : public TestFixture { " int array[10];\n" " typedef int* (Fred::*UnspecifiedBoolType);\n" " operator UnspecifiedBoolType() { };\n" - "};"); + "};\n"); TODO_ASSERT_EQUALS("[test.cpp:4]: (style, inconclusive) Technically the member function 'Fred::operatorint**' can be const.\n", "", errout_str()); checkConst("struct Fred {\n" " int array[10];\n" " typedef int* (Fred::*UnspecifiedBoolType);\n" " operator UnspecifiedBoolType() { array[0] = 0; };\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -4200,21 +4281,21 @@ class TestClass : public TestFixture { " int c;\n" "public:\n" " operator int& () {return c}\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class A {\n" " int c;\n" "public:\n" " operator const int& () {return c}\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:5]: (style, inconclusive) Technically the member function 'A::operatorconstint&' can be const. [functionConst]\n", errout_str()); checkConst("class A {\n" " int c;\n" "public:\n" " operator int () {return c}\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:5]: (style, inconclusive) Technically the member function 'A::operatorint' can be const. [functionConst]\n", errout_str()); } @@ -4222,7 +4303,7 @@ class TestClass : public TestFixture { checkConst("class A {\n" " int c;\n" " void f() { os >> *this; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -4236,7 +4317,7 @@ class TestClass : public TestFixture { " same = (i == a);\n" " return same;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style, inconclusive) Technically the member function 'A::foo' can be const. [functionConst]\n", errout_str()); } @@ -4245,14 +4326,14 @@ class TestClass : public TestFixture { checkConst("class foo {\n" "public:\n" "};\n" - "void bar() {}"); + "void bar() {}\n"); ASSERT_EQUALS("", errout_str()); checkConst("class Fred\n" "{\n" "public:\n" " void foo() { }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:10]: (style) The member function 'Fred::foo' can be static. [functionStatic]\n", errout_str()); checkConst("struct fast_string\n" @@ -4266,7 +4347,7 @@ class TestClass : public TestFixture { "inline void fast_string::set_type(char t)\n" "{\n" " buff[10] = t;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -4277,7 +4358,7 @@ class TestClass : public TestFixture { " void set(int i) { a = i; }\n" " void set(const foo & f) { *this = f; }\n" "};\n" - "void bar() {}"); + "void bar() {}\n"); ASSERT_EQUALS("", errout_str()); } @@ -4289,7 +4370,7 @@ class TestClass : public TestFixture { " std::string strGetString() { return m_strValue; }\n" "private:\n" " std::string m_strValue;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:17]: (style, inconclusive) Technically the member function 'A::strGetString' can be const. [functionConst]\n", errout_str()); } @@ -4300,7 +4381,7 @@ class TestClass : public TestFixture { " void SetExitCode(wxThread::ExitCode exitcode) { m_exitcode = exitcode; }\n" "private:\n" " wxThread::ExitCode m_exitcode;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -4311,7 +4392,7 @@ class TestClass : public TestFixture { " int foo() { return x = 0; }\n" "private:\n" " int x;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class A {\n" @@ -4319,7 +4400,7 @@ class TestClass : public TestFixture { " int foo() { return x ? x : x = 0; }\n" "private:\n" " int x;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class A {\n" @@ -4327,7 +4408,7 @@ class TestClass : public TestFixture { " int foo() { return x ? x = 0 : x; }\n" "private:\n" " int x;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -4338,7 +4419,7 @@ class TestClass : public TestFixture { " void set(struct tm time) { m_time = time; }\n" "private:\n" " struct tm m_time;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -4349,7 +4430,7 @@ class TestClass : public TestFixture { " int foo() { x = 0; }\n" "private:\n" " mutable int x;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:9]: (style, inconclusive) Technically the member function 'A::foo' can be const. [functionConst]\n", errout_str()); } @@ -4363,7 +4444,7 @@ class TestClass : public TestFixture { "private:\n" " std::vector m_vec;\n" " std::pair m_pair;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:22]: (style, inconclusive) Technically the member function 'A::GetVec' can be const. [functionConst]\n" "[test.cpp:5:27]: (style, inconclusive) Technically the member function 'A::GetPair' can be const. [functionConst]\n", errout_str()); @@ -4375,7 +4456,7 @@ class TestClass : public TestFixture { "private:\n" " std::vector m_vec;\n" " std::pair m_pair;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:30]: (style, inconclusive) Technically the member function 'A::GetVec' can be const. [functionConst]\n" "[test.cpp:5:35]: (style, inconclusive) Technically the member function 'A::GetPair' can be const. [functionConst]\n", errout_str()); } @@ -4388,7 +4469,7 @@ class TestClass : public TestFixture { " std::pair,double> GetPair() {return m_pair;}\n" "private:\n" " std::pair,double> m_pair;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:40]: (style, inconclusive) Technically the member function 'A::GetPair' can be const. [functionConst]\n", errout_str()); checkConst("class A {\n" @@ -4397,7 +4478,7 @@ class TestClass : public TestFixture { " const std::pair,double>& GetPair() {return m_pair;}\n" "private:\n" " std::pair,double> m_pair;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:47]: (style, inconclusive) Technically the member function 'A::GetPair' can be const. [functionConst]\n", errout_str()); checkConst("class A {\n" @@ -4406,7 +4487,7 @@ class TestClass : public TestFixture { " std::pair,double>& GetPair() {return m_pair;}\n" "private:\n" " std::pair,double> m_pair;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); @@ -4417,7 +4498,7 @@ class TestClass : public TestFixture { " pair GetPair() {return m_pair;}\n" "private:\n" " pair m_pair;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:23]: (style, inconclusive) Technically the member function 'A::GetPair' can be const. [functionConst]\n", errout_str()); checkConst("using namespace std;" @@ -4427,7 +4508,7 @@ class TestClass : public TestFixture { " const pair & GetPair() {return m_pair;}\n" "private:\n" " pair m_pair;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:31]: (style, inconclusive) Technically the member function 'A::GetPair' can be const. [functionConst]\n", errout_str()); checkConst("using namespace std;" @@ -4437,7 +4518,7 @@ class TestClass : public TestFixture { " pair & GetPair() {return m_pair;}\n" "private:\n" " pair m_pair;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); @@ -4447,7 +4528,7 @@ class TestClass : public TestFixture { " std::pair< int,std::vector > GetPair() {return m_pair;}\n" "private:\n" " std::pair< int,std::vector > m_pair;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:40]: (style, inconclusive) Technically the member function 'A::GetPair' can be const. [functionConst]\n", errout_str()); checkConst("class A {\n" @@ -4456,7 +4537,7 @@ class TestClass : public TestFixture { " const std::pair< int,std::vector >& GetPair() {return m_pair;}\n" "private:\n" " std::pair< int,std::vector > m_pair;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:47]: (style, inconclusive) Technically the member function 'A::GetPair' can be const. [functionConst]\n", errout_str()); checkConst("class A {\n" @@ -4465,7 +4546,7 @@ class TestClass : public TestFixture { " std::pair< int,std::vector >& GetPair() {return m_pair;}\n" "private:\n" " std::pair< int,std::vector > m_pair;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); @@ -4476,7 +4557,7 @@ class TestClass : public TestFixture { " pair< vector, int > GetPair() {return m_pair;}\n" "private:\n" " pair< vector, int > m_pair;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:31]: (style, inconclusive) Technically the member function 'A::GetPair' can be const. [functionConst]\n", errout_str()); checkConst("using namespace std;" @@ -4486,7 +4567,7 @@ class TestClass : public TestFixture { " const pair< vector, int >& GetPair() {return m_pair;}\n" "private:\n" " pair< vector, int > m_pair;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:38]: (style, inconclusive) Technically the member function 'A::GetPair' can be const. [functionConst]\n", errout_str()); checkConst("using namespace std;" @@ -4496,7 +4577,7 @@ class TestClass : public TestFixture { " pair< vector, int >& GetPair() {return m_pair;}\n" "private:\n" " pair< vector, int > m_pair;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class A {\n" @@ -4505,7 +4586,7 @@ class TestClass : public TestFixture { " std::pair< std::vector,std::vector > GetPair() {return m_pair;}\n" "private:\n" " std::pair< std::vector,std::vector > m_pair;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:53]: (style, inconclusive) Technically the member function 'A::GetPair' can be const. [functionConst]\n", errout_str()); checkConst("class A {\n" @@ -4514,7 +4595,7 @@ class TestClass : public TestFixture { " const std::pair< std::vector,std::vector >& GetPair() {return m_pair;}\n" "private:\n" " std::pair< std::vector,std::vector > m_pair;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:60]: (style, inconclusive) Technically the member function 'A::GetPair' can be const. [functionConst]\n", errout_str()); checkConst("class A {\n" @@ -4523,7 +4604,7 @@ class TestClass : public TestFixture { " std::pair< std::vector,std::vector >& GetPair() {return m_pair;}\n" "private:\n" " std::pair< std::vector,std::vector > m_pair;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); @@ -4534,7 +4615,7 @@ class TestClass : public TestFixture { " std::pair< std::pair < int, char > , int > GetPair() {return m_pair;}\n" "private:\n" " std::pair< std::pair < int, char > , int > m_pair;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:49]: (style, inconclusive) Technically the member function 'A::GetPair' can be const. [functionConst]\n", errout_str()); checkConst("class A {\n" @@ -4543,7 +4624,7 @@ class TestClass : public TestFixture { " const std::pair< std::pair < int, char > , int > & GetPair() {return m_pair;}\n" "private:\n" " std::pair< std::pair < int, char > , int > m_pair;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:56]: (style, inconclusive) Technically the member function 'A::GetPair' can be const. [functionConst]\n", errout_str()); checkConst("class A {\n" @@ -4552,7 +4633,7 @@ class TestClass : public TestFixture { " std::pair< std::pair < int, char > , int > & GetPair() {return m_pair;}\n" "private:\n" " std::pair< std::pair < int, char > , int > m_pair;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); @@ -4562,7 +4643,7 @@ class TestClass : public TestFixture { " std::pair< int , std::pair < int, char > > GetPair() {return m_pair;}\n" "private:\n" " std::pair< int , std::pair < int, char > > m_pair;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:49]: (style, inconclusive) Technically the member function 'A::GetPair' can be const. [functionConst]\n", errout_str()); checkConst("class A {\n" @@ -4571,7 +4652,7 @@ class TestClass : public TestFixture { " const std::pair< int , std::pair < int, char > >& GetPair() {return m_pair;}\n" "private:\n" " std::pair< int , std::pair < int, char > > m_pair;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:56]: (style, inconclusive) Technically the member function 'A::GetPair' can be const. [functionConst]\n", errout_str()); checkConst("class A {\n" @@ -4580,7 +4661,7 @@ class TestClass : public TestFixture { " std::pair< int , std::pair < int, char > >& GetPair() {return m_pair;}\n" "private:\n" " std::pair< int , std::pair < int, char > > m_pair;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); @@ -4591,7 +4672,7 @@ class TestClass : public TestFixture { " vector GetVec() {return m_Vec;}\n" "private:\n" " vector m_Vec;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:18]: (style, inconclusive) Technically the member function 'A::GetVec' can be const. [functionConst]\n", errout_str()); checkConst("using namespace std;" @@ -4601,7 +4682,7 @@ class TestClass : public TestFixture { " const vector& GetVec() {return m_Vec;}\n" "private:\n" " vector m_Vec;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:25]: (style, inconclusive) Technically the member function 'A::GetVec' can be const. [functionConst]\n", errout_str()); checkConst("using namespace std;" @@ -4611,7 +4692,7 @@ class TestClass : public TestFixture { " vector& GetVec() {return m_Vec;}\n" "private:\n" " vector m_Vec;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); @@ -4620,7 +4701,7 @@ class TestClass : public TestFixture { " int * * foo() { return &x; }\n" "private:\n" " const int * x;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class A {\n" @@ -4628,7 +4709,7 @@ class TestClass : public TestFixture { " const int ** foo() { return &x; }\n" "private:\n" " const int * x;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:18]: (style, inconclusive) Technically the member function 'A::foo' can be const. [functionConst]\n", errout_str()); } @@ -4636,7 +4717,7 @@ class TestClass : public TestFixture { checkConst("class Fred {\n" " unsigned long long int a;\n" " unsigned long long int getA() { return a; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:28]: (style, inconclusive) Technically the member function 'Fred::getA' can be const. [functionConst]\n", errout_str()); // constructors can't be const.. @@ -4644,14 +4725,14 @@ class TestClass : public TestFixture { " unsigned long long int a;\n" "public:\n" " Fred() { }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // assignment through |=.. checkConst("class Fred {\n" " unsigned long long int a;\n" " unsigned long long int setA() { a |= true; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // static functions can't be const.. @@ -4660,7 +4741,7 @@ class TestClass : public TestFixture { "public:\n" " static unsigned long long int get()\n" " { return 0; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -4669,7 +4750,7 @@ class TestClass : public TestFixture { checkConst("class Fred {\n" " int a;\n" " void set(int i) { Fred::a = i; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -4680,7 +4761,7 @@ class TestClass : public TestFixture { " void set(int i, int j) { a[i].k = i; }\n" "private:\n" " struct { int k; } a[4];\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -4689,7 +4770,7 @@ class TestClass : public TestFixture { "static int x;\n" "public:\n" " void set(int i) { x = i; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:10]: (style) The member function 'Fred::set' can be static. [functionStatic]\n", errout_str()); } @@ -4701,7 +4782,7 @@ class TestClass : public TestFixture { " std::string s;\n" "public:\n" " void set(std::string ss) { s = ss; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -4711,35 +4792,35 @@ class TestClass : public TestFixture { " int x : 3;\n" "public:\n" " void set(int i) { x = i; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class Fred {\n" " list x;\n" "public:\n" " list get() { return x; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class Fred {\n" " list x;\n" "public:\n" " list get() { return x; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:23]: (style, inconclusive) Technically the member function 'Fred::get' can be const. [functionConst]\n", errout_str()); checkConst("class Fred {\n" " std::list x;\n" "public:\n" " std::list get() { return x; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class Fred {\n" " std::list x;\n" "public:\n" " std::list get() { return x; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:36]: (style, inconclusive) Technically the member function 'Fred::get' can be const. [functionConst]\n", errout_str()); } @@ -4755,7 +4836,7 @@ class TestClass : public TestFixture { " for (int i = 0 ; i < 10; l1[i] = NULL, i++);\n" " }\n" " void f1() { l1[0] = \"Hello\"; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -4766,7 +4847,7 @@ class TestClass : public TestFixture { " B::C * v1;\n" "public:\n" " void f1() { v1 = 0; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class A\n" @@ -4775,7 +4856,7 @@ class TestClass : public TestFixture { " B::C * v1[0];\n" "public:\n" " void f1() { v1[0] = 0; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -4787,7 +4868,7 @@ class TestClass : public TestFixture { " void set_member(Type2 m) { _m = m; }\n" "private:\n" " Type2 _m;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -4800,7 +4881,7 @@ class TestClass : public TestFixture { "}\n" "private:\n" " std::map *m_pSettings;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -4815,7 +4896,7 @@ class TestClass : public TestFixture { "{return m_strVal.c_str();}\n" "private:\n" "std::string m_strVal;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class A{\n" @@ -4825,7 +4906,7 @@ class TestClass : public TestFixture { "{return m_strVal.c_str();}\n" "private:\n" "std::string m_strVal;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:13]: (style, inconclusive) Technically the member function 'A::strGetString' can be const. [functionConst]\n", errout_str()); checkConst("class A{\n" @@ -4835,7 +4916,7 @@ class TestClass : public TestFixture { "{return m_strVal.c_str();}\n" "private:\n" "std::string m_strVal;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:19]: (style, inconclusive) Technically the member function 'A::strGetString1' can be const. [functionConst]\n", errout_str()); checkConst("class A{\n" @@ -4845,7 +4926,7 @@ class TestClass : public TestFixture { "{return m_strVec.size();}\n" "private:\n" "std::vector m_strVec;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:8]: (style, inconclusive) Technically the member function 'A::strGetSize' can be const. [functionConst]\n", errout_str()); checkConst("class A{\n" @@ -4855,7 +4936,7 @@ class TestClass : public TestFixture { "{return m_strVec.empty();}\n" "private:\n" "std::vector m_strVec;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:6]: (style, inconclusive) Technically the member function 'A::strGetEmpty' can be const. [functionConst]\n", errout_str()); } @@ -4866,7 +4947,7 @@ class TestClass : public TestFixture { " std::swap(delays_[index1], delays_[index2]);\n" "}\n" "float delays_[4];\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("struct DelayBase {\n" @@ -4874,7 +4955,7 @@ class TestClass : public TestFixture { " return delays_[index1];\n" " }\n" " float delays_[4];\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:2:11]: (style, inconclusive) Technically the member function 'DelayBase::swapSpecificDelays' can be const. [functionConst]\n", errout_str()); } @@ -4892,7 +4973,7 @@ class TestClass : public TestFixture { " if( m_d != 0 )\n" " return m_iRealVal / m_d;\n" " return dRet;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:12] -> [test.cpp:9:12]: (style, inconclusive) Technically the member function 'A::dGetValue' can be const. [functionConst]\n", errout_str()); } @@ -4909,7 +4990,7 @@ class TestClass : public TestFixture { " x=xPos;\n" " y=yPos;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class AA : public P {\n" @@ -4919,7 +5000,7 @@ class TestClass : public TestFixture { " {\n" " UnknownScope::x = x_;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class AA {\n" @@ -4929,7 +5010,7 @@ class TestClass : public TestFixture { " {\n" " UnknownScope::x = x_;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:17]: (style) The member function 'AA::vSetXPos' can be static. [functionStatic]\n", errout_str()); } @@ -4954,7 +5035,7 @@ class TestClass : public TestFixture { "char* test::get()\n" "{\n" " return value_;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -4969,7 +5050,7 @@ class TestClass : public TestFixture { " int get() {\n" " return a;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:7:9]: (style, inconclusive) Technically the member function 'Derived::get' can be const. [functionConst]\n", errout_str()); checkConst("class Base1 {\n" @@ -4988,7 +5069,7 @@ class TestClass : public TestFixture { " int getB() {\n" " return b;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:11:9]: (style, inconclusive) Technically the member function 'Derived::getA' can be const. [functionConst]\n" "[test.cpp:14:9]: (style, inconclusive) Technically the member function 'Derived::getB' can be const. [functionConst]\n", errout_str()); @@ -5002,7 +5083,7 @@ class TestClass : public TestFixture { " int get() {\n" " return a;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:8:9]: (style, inconclusive) Technically the member function 'Derived2::get' can be const. [functionConst]\n", errout_str()); checkConst("class Base {\n" @@ -5017,7 +5098,7 @@ class TestClass : public TestFixture { " int get() {\n" " return a;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:10:9]: (style, inconclusive) Technically the member function 'Derived4::get' can be const. [functionConst]\n", errout_str()); // check for false positives @@ -5030,7 +5111,7 @@ class TestClass : public TestFixture { " int get() const {\n" " return a;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class Base1 {\n" @@ -5049,7 +5130,7 @@ class TestClass : public TestFixture { " int getB() const {\n" " return b;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class Base {\n" @@ -5062,7 +5143,7 @@ class TestClass : public TestFixture { " int get() const {\n" " return a;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class Base {\n" @@ -5077,7 +5158,7 @@ class TestClass : public TestFixture { " int get() const {\n" " return a;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -5087,7 +5168,7 @@ class TestClass : public TestFixture { "public:\n" " int a;\n" " int get() { return a; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:5:9]: (style, inconclusive) Technically the member function 'Fred::get' can be const. [functionConst]\n", errout_str()); } @@ -5096,7 +5177,7 @@ class TestClass : public TestFixture { "public:\n" " std::string a[10];\n" " void seta() { a[0] = \"\"; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -5104,7 +5185,7 @@ class TestClass : public TestFixture { checkConst("class derived : public base {\n" "public:\n" " void f(){}\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style) Either there is a missing 'override', or the member function 'derived::f' can be static. [functionStatic]\n", errout_str()); } @@ -5113,7 +5194,7 @@ class TestClass : public TestFixture { " void init(Foo * foo) {\n" " foo.bar = this;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -5132,7 +5213,7 @@ class TestClass : public TestFixture { " int getResourceName() { return var; }\n" " int var;\n" " };\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:12:21]: (style, inconclusive) Technically the member function 'N::Derived::getResourceName' can be const. [functionConst]\n", errout_str()); checkConst("namespace N\n" @@ -5144,7 +5225,7 @@ class TestClass : public TestFixture { " int var;\n" " };\n" "}\n" - "int N::Base::getResourceName() { return var; }"); + "int N::Base::getResourceName() { return var; }\n"); ASSERT_EQUALS("[test.cpp:6:21] -> [test.cpp:10:14]: (style, inconclusive) Technically the member function 'N::Base::getResourceName' can be const. [functionConst]\n", errout_str()); checkConst("namespace N\n" @@ -5159,7 +5240,7 @@ class TestClass : public TestFixture { "namespace N\n" "{\n" " int Base::getResourceName() { return var; }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:21] -> [test.cpp:12:19]: (style, inconclusive) Technically the member function 'N::Base::getResourceName' can be const. [functionConst]\n", errout_str()); checkConst("namespace N\n" @@ -5172,7 +5253,7 @@ class TestClass : public TestFixture { " };\n" "}\n" "using namespace N;\n" - "int Base::getResourceName() { return var; }"); + "int Base::getResourceName() { return var; }\n"); ASSERT_EQUALS("[test.cpp:6:21] -> [test.cpp:11:11]: (style, inconclusive) Technically the member function 'N::Base::getResourceName' can be const. [functionConst]\n", errout_str()); } @@ -5184,7 +5265,7 @@ class TestClass : public TestFixture { " {\n" " m_MaxQueueSize = a_MaxQueueSize;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -5199,7 +5280,7 @@ class TestClass : public TestFixture { " }\n" "private:\n" " std::string m_str;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:5:17]: (style, inconclusive) Technically the member function 'A::operator+' can be const. [functionConst]\n", errout_str()); checkConst("class Fred\n" @@ -5213,7 +5294,7 @@ class TestClass : public TestFixture { " bool isValid() {\n" " return (x == 0x11224488);\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:9:10]: (style, inconclusive) Technically the member function 'Fred::isValid' can be const. [functionConst]\n", errout_str()); } @@ -5229,7 +5310,7 @@ class TestClass : public TestFixture { "void Foo::MyMethod()\n" "{\n" " (*oArq) << \"\";\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -5249,7 +5330,7 @@ class TestClass : public TestFixture { "int * Foo::f()\n" "{\n" " return p;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -5266,7 +5347,7 @@ class TestClass : public TestFixture { " {\n" " pView = aView;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -5280,7 +5361,7 @@ class TestClass : public TestFixture { " {\n" " m_name = name;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); @@ -5292,7 +5373,7 @@ class TestClass : public TestFixture { " {\n" " pNum = apNum;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); @@ -5307,7 +5388,7 @@ class TestClass : public TestFixture { " {\n" " pNum = apNum;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); @@ -5324,7 +5405,7 @@ class TestClass : public TestFixture { " {\n" " pNum = apNum;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); @@ -5342,7 +5423,7 @@ class TestClass : public TestFixture { " {\n" " pNum = apNum;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -5356,7 +5437,7 @@ class TestClass : public TestFixture { "};\n" "bool Fred::f(Fred::AB * ab)\n" "{\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:10] -> [test.cpp:7:12]: (style) The member function 'Fred::f' can be static. [functionStatic]\n", errout_str()); @@ -5370,7 +5451,7 @@ class TestClass : public TestFixture { "};\n" "bool Fred::f(Fred::AB::CD * cd)\n" "{\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:10] -> [test.cpp:9:12]: (style) The member function 'Fred::f' can be static. [functionStatic]\n", errout_str()); @@ -5386,7 +5467,7 @@ class TestClass : public TestFixture { " bool Fred::f(Fred::AB::CD * cd)\n" " {\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:14] -> [test.cpp:10:16]: (style) The member function 'NS::Fred::f' can be static. [functionStatic]\n", errout_str()); @@ -5402,7 +5483,7 @@ class TestClass : public TestFixture { "}\n" "bool NS::Fred::f(NS::Fred::AB::CD * cd)\n" "{\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:14] -> [test.cpp:11:16]: (style) The member function 'NS::Fred::f' can be static. [functionStatic]\n", errout_str()); @@ -5418,7 +5499,7 @@ class TestClass : public TestFixture { "};\n" "bool Foo::Fred::f(Foo::Fred::AB::CD * cd)\n" "{\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:14] -> [test.cpp:11:17]: (style) The member function 'Foo::Fred::f' can be static. [functionStatic]\n", errout_str()); } @@ -5433,7 +5514,7 @@ class TestClass : public TestFixture { "void A::foo( AA::BB::CC::DD b )\n" "{\n" " a = b;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); @@ -5457,7 +5538,7 @@ class TestClass : public TestFixture { " {\n" " a = b;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); @@ -5477,7 +5558,7 @@ class TestClass : public TestFixture { " {\n" " a = b;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -5490,7 +5571,7 @@ class TestClass : public TestFixture { " {\n" " return 0 != (bOn = bOn);\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -5507,7 +5588,7 @@ class TestClass : public TestFixture { " {\n" " }\n" " };\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:13]: (style) The member function 'tools::WorkspaceControl::toGrid' can be static. [functionStatic]\n", errout_str()); } @@ -5522,7 +5603,7 @@ class TestClass : public TestFixture { " int fun2() {\n" " b++;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:9]: (style) The member function 'Altren::fun1' can be static. [functionStatic]\n" "[test.cpp:7:9]: (style) The member function 'Altren::fun2' can be static. [functionStatic]\n", errout_str()); @@ -5534,7 +5615,7 @@ class TestClass : public TestFixture { " void foo() { delete this; }\n" " void foo(int i) const { }\n" " void bar() { foo(); }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:8]: (style) The member function 'Altren::foo' can be static. [functionStatic]\n", errout_str()); @@ -5543,7 +5624,7 @@ class TestClass : public TestFixture { " void foo() { delete this; }\n" " void foo(int i) const { }\n" " void bar() { foo(1); }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:8]: (style) The member function 'Altren::foo' can be static. [functionStatic]\n" "[test.cpp:5:8]: (style, inconclusive) Technically the member function 'Altren::bar' can be const. [functionConst]\n", errout_str()); @@ -5566,7 +5647,7 @@ class TestClass : public TestFixture { "};\n" "bool TextIterator::setTagColour() {\n" " mSave = mCurrent;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -5581,7 +5662,7 @@ class TestClass : public TestFixture { " {\n" " return _hash[key];\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -5596,7 +5677,7 @@ class TestClass : public TestFixture { "{\n" " if (mTileSize.height > 0) return;\n" " if (mEmptyView) return;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -5610,7 +5691,7 @@ class TestClass : public TestFixture { " void SetSection(uint num) { pesdata()[6] = num; }\n" "private:\n" " unsigned char *_pesdata;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class PESPacket {\n" @@ -5625,7 +5706,7 @@ class TestClass : public TestFixture { "{\n" "public:\n" " void SetSection(uint num) { pesdata()[6] = num; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -5635,7 +5716,7 @@ class TestClass : public TestFixture { " void DoSomethingElse() { DoSomething(bar); }\n" "private:\n" " int bar;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:2:10]: (style) The member function 'foo::DoSomething' can be static. [functionStatic]\n", errout_str()); } @@ -5651,7 +5732,7 @@ class TestClass : public TestFixture { " public:\n" " B() {};\n" " bool One(bool b = false) { return false; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -5659,7 +5740,7 @@ class TestClass : public TestFixture { checkConst("class Example {\n" " public:\n" " void Clear(void) { Example tmp; (*this) = tmp; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -5669,7 +5750,7 @@ class TestClass : public TestFixture { " MyObject() : tmp(0) {}\n" "public:\n" " void set(std::stringstream &in) { in >> tmp; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -5679,7 +5760,7 @@ class TestClass : public TestFixture { " void foo(int x) {\n" " switch (x) { }\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style) The member function 'MyObject::foo' can be static. [functionStatic]\n", errout_str()); checkConst("class A\n" @@ -5719,7 +5800,7 @@ class TestClass : public TestFixture { " }\n" "\n" " return RET_NOK;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:24] -> [test.cpp:9:19]: (style) The member function 'A::f' can be static. [functionStatic]\n", errout_str()); checkConst("class MyObject {\n" @@ -5727,7 +5808,7 @@ class TestClass : public TestFixture { " void foo(int x) {\n" " for (int i = 0; i < 5; i++) { }\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style) The member function 'MyObject::foo' can be static. [functionStatic]\n", errout_str()); } @@ -5752,7 +5833,7 @@ class TestClass : public TestFixture { " }\n" "private:\n" " MyGUI::IntCoord mCoordValue;\n" - "};"); + "};\n"); TODO_ASSERT_EQUALS("[test.cpp:7:13]: (style) The member function 'MyGUI::types::TCoord::size' can be static. [functionStatic]\n" "[test.cpp:15]: (style, inconclusive) Technically the member function 'SelectorControl::getSize' can be const.\n", "[test.cpp:7:13]: (style) The member function 'MyGUI::types::TCoord::size' can be static. [functionStatic]\n", errout_str()); @@ -5762,7 +5843,7 @@ class TestClass : public TestFixture { " void foo(Foo f) {\n" " b.run();\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("struct Bar {\n" @@ -5774,7 +5855,7 @@ class TestClass : public TestFixture { " void foo(Foo f) {\n" " b.run();\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("struct Bar {\n" @@ -5785,7 +5866,7 @@ class TestClass : public TestFixture { " void foo(Foo f) {\n" " b.run();\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:2:10]: (style) The member function 'Bar::run' can be static. [functionStatic]\n" "[test.cpp:6:10]: (style, inconclusive) Technically the member function 'Foo::foo' can be const. [functionConst]\n", errout_str()); } @@ -5795,14 +5876,14 @@ class TestClass : public TestFixture { " void foo(Foo f) {\n" " f.clear();\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:2:10]: (style) The member function 'MyObject::foo' can be static. [functionStatic]\n", errout_str()); checkConst("struct MyObject {\n" " int foo(Foo f) {\n" " return f.length();\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:2:9]: (style) The member function 'MyObject::foo' can be static. [functionStatic]\n", errout_str()); checkConst("struct MyObject {\n" @@ -5810,7 +5891,7 @@ class TestClass : public TestFixture { " int foo() {\n" " return f.length();\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("struct MyObject {\n" @@ -5818,7 +5899,7 @@ class TestClass : public TestFixture { " int foo() {\n" " return f.length();\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:9]: (style, inconclusive) Technically the member function 'MyObject::foo' can be const. [functionConst]\n", errout_str()); } @@ -5830,7 +5911,7 @@ class TestClass : public TestFixture { "protected:\n" " int re;\n" " int im;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -5845,7 +5926,7 @@ class TestClass : public TestFixture { " MyString append( const MyString& str )\n" " { return operator+=( str ); }\n" " char *m_ptr;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class MyString {\n" "public:\n" @@ -5854,7 +5935,7 @@ class TestClass : public TestFixture { " MyString append( const MyString& str )\n" " { return operator+=( str ); }\n" " char *m_ptr;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -5865,14 +5946,14 @@ class TestClass : public TestFixture { "};\n" "int MixerParticipant::GetAudioFrame() {\n" " return 0;\n" - "}"); + "}\n"); // this code is invalid so a false negative is OK checkConst("class MixerParticipant : public MixerParticipant {\n" " bool InitializeFileReader() {\n" " printf(\"music\");\n" " }\n" - "};"); + "};\n"); // Based on an example from SVN source code causing an endless recursion within CheckClass::isConstMemberFunc() // A more complete example including a template declaration like @@ -5905,7 +5986,7 @@ class TestClass : public TestFixture { " {\n" " return _hash[key];\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -5916,7 +5997,7 @@ class TestClass : public TestFixture { " std::string* p = &s;\n" " p->clear();\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("struct A {\n" @@ -5925,7 +6006,7 @@ class TestClass : public TestFixture { " std::string& r = s;\n" " r.clear();\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("struct A {\n" @@ -5934,7 +6015,7 @@ class TestClass : public TestFixture { " std::string& r = sth; r = s;\n" " r.clear();\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style, inconclusive) Technically the member function 'A::clear' can be const. [functionConst]\n", errout_str()); checkConst("struct A {\n" @@ -5943,7 +6024,7 @@ class TestClass : public TestFixture { " const std::string* p = &s;\n" " p->somefunction();\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style, inconclusive) Technically the member function 'A::clear' can be const. [functionConst]\n", errout_str()); checkConst("struct A {\n" @@ -5952,7 +6033,7 @@ class TestClass : public TestFixture { " const std::string& r = s;\n" " r.somefunction();\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style, inconclusive) Technically the member function 'A::clear' can be const. [functionConst]\n", errout_str()); } @@ -5967,7 +6048,7 @@ class TestClass : public TestFixture { " }\n" " ::B::D::DKIPtr membervariable;\n" " };\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -5986,7 +6067,7 @@ class TestClass : public TestFixture { " TemplateClass a;\n" " TemplateClass b;\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -5996,7 +6077,7 @@ class TestClass : public TestFixture { " void f(int v) { g((char *) &v); }\n" " void g(char *) { n++; }\n" " int n;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -6010,7 +6091,7 @@ class TestClass : public TestFixture { "public:\n" " const std::list>& get() { return m_test.m_list; }\n" " TestList> m_test;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:8:44]: (style, inconclusive) Technically the member function 'Test::get' can be const. [functionConst]\n", errout_str()); } @@ -6040,7 +6121,7 @@ class TestClass : public TestFixture { " call();\n" " call(1, 2, 3);\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -6053,7 +6134,7 @@ class TestClass : public TestFixture { " void test() {\n" " call(1, 2);\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -6188,7 +6269,7 @@ class TestClass : public TestFixture { " void bar(void) {\n" " for(std::vector::const_iterator it = m_str.begin(); it != m_str.end(); ++it) {;}\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:5:10]: (style, inconclusive) Technically the member function 'A::bar' can be const. [functionConst]\n", errout_str()); // Don't crash @@ -6974,13 +7055,80 @@ class TestClass : public TestFixture { } void const101() { - checkConst("struct error {\n" + checkConst("struct error {\n" // #14450 " error() = default;\n" "};\n" "struct S : U {\n" " int f() { return this->error(); }\n" "};\n"); ASSERT_EQUALS("", errout_str()); + + checkConst("struct S {\n" // #14702 + " int i;\n" + " void f() {\n" + " S& r = *this;\n" + " r.i = 0;\n" + " }\n" + "};\n" + "struct T : std::array {\n" + " void g() {\n" + " for (int& r : *this)\n" + " r = 0;\n" + " }\n" + "};\n"); + ASSERT_EQUALS("", errout_str()); + + checkConst("struct A {\n" // #9981 + " int a = 0;\n" + " auto operator[](int x) -> int& {\n" + " a = 1;\n" + " return a;\n" + " }\n" + " int f() {\n" + " return (*this)[0];\n" + " }\n" + "};\n"); + ASSERT_EQUALS("", errout_str()); + } + + void const102() { + checkConst("struct S {\n" // #14888 + " void f() {\n" + " int *p = a;\n" + " *p = 0;\n" + " }\n" + " void g() {\n" + " const int *p = a;\n" + " if (*p) {}\n" + " }\n" + " int a[3];\n" + "};\n"); + ASSERT_EQUALS("[test.cpp:6:10]: (style, inconclusive) Technically the member function 'S::g' can be const. [functionConst]\n", + errout_str()); + } + + void const103() { + checkConst("constexpr int BITS{4};\n" + "struct Struct {\n" + " unsigned int data() const { return m_data; }\n" + " unsigned int m_data : BITS;\n" + "};\n"); + ASSERT_EQUALS("", errout_str()); + } + + void const104() { // #15104 + checkConst("struct DS;\n" + "void select(int a, int b);\n" + "class Query {\n" + " int m = 0;\n" + "public:\n" + " template \n" + " int select(DS &ds, Callback cb) { cb(m); return 0; }\n" + " template \n" + " int select(DS *ds, Callback cb) { return select(*ds, cb); }\n" + " int select(DS &ds) { return select(ds, [](int) {}); }\n" + "};\n"); + ASSERT_EQUALS("", errout_str()); } void const_handleDefaultParameters() { @@ -7012,7 +7160,7 @@ class TestClass : public TestFixture { " int bar6() {\n" " return foo3();\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:11:9]: (style) The member function 'Foo::bar3' can be static. [functionStatic]\n" "[test.cpp:14:9]: (style) The member function 'Foo::bar4' can be static. [functionStatic]\n", errout_str()); } @@ -7023,7 +7171,7 @@ class TestClass : public TestFixture { " Bar b;\n" " b.takeFoo(this);\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("struct Foo {\n" @@ -7031,7 +7179,7 @@ class TestClass : public TestFixture { " Foo f;\n" " f.foo();\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:2:10]: (style) The member function 'Foo::foo' can be static. [functionStatic]\n", errout_str()); checkConst("struct A;\n" // #5839 - operator() @@ -7042,7 +7190,7 @@ class TestClass : public TestFixture { " void dostuff() {\n" " B()(this);\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -7054,7 +7202,7 @@ class TestClass : public TestFixture { " {\n" " v = 0;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -7066,7 +7214,7 @@ class TestClass : public TestFixture { " {\n" " v[0] = \"Happy new year!\";\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -7075,49 +7223,49 @@ class TestClass : public TestFixture { checkConst("class Fred {\n" " int a;\n" " void nextA() { return ++a; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class Fred {\n" " int a;\n" " void nextA() { return --a; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class Fred {\n" " int a;\n" " void nextA() { return a++; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class Fred {\n" " int a;\n" " void nextA() { return a--; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("int a;\n" "class Fred {\n" " void nextA() { return ++a; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style) The member function 'Fred::nextA' can be static. [functionStatic]\n", errout_str()); checkConst("int a;\n" "class Fred {\n" " void nextA() { return --a; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style) The member function 'Fred::nextA' can be static. [functionStatic]\n", errout_str()); checkConst("int a;\n" "class Fred {\n" " void nextA() { return a++; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style) The member function 'Fred::nextA' can be static. [functionStatic]\n", errout_str()); checkConst("int a;\n" "class Fred {\n" " void nextA() { return a--; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style) The member function 'Fred::nextA' can be static. [functionStatic]\n", errout_str()); checkConst("struct S {\n" // #10077 @@ -7133,61 +7281,61 @@ class TestClass : public TestFixture { checkConst("class Fred {\n" " int a;\n" " void nextA() { return a=1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class Fred {\n" " int a;\n" " void nextA() { return a-=1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class Fred {\n" " int a;\n" " void nextA() { return a+=1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class Fred {\n" " int a;\n" " void nextA() { return a*=-1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class Fred {\n" " int a;\n" " void nextA() { return a/=-2; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("int a;\n" "class Fred {\n" " void nextA() { return a=1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style) The member function 'Fred::nextA' can be static. [functionStatic]\n", errout_str()); checkConst("int a;\n" "class Fred {\n" " void nextA() { return a-=1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style) The member function 'Fred::nextA' can be static. [functionStatic]\n", errout_str()); checkConst("int a;\n" "class Fred {\n" " void nextA() { return a+=1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style) The member function 'Fred::nextA' can be static. [functionStatic]\n", errout_str()); checkConst("int a;\n" "class Fred {\n" " void nextA() { return a*=-1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style) The member function 'Fred::nextA' can be static. [functionStatic]\n", errout_str()); checkConst("int a;\n" "class Fred {\n" " void nextA() { return a/=-2; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style) The member function 'Fred::nextA' can be static. [functionStatic]\n", errout_str()); } @@ -7195,90 +7343,90 @@ class TestClass : public TestFixture { checkConst("class Fred {\n" " struct A { int a; } s;\n" " void nextA() { return s.a=1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class Fred {\n" " struct A { int a; } s;\n" " void nextA() { return s.a-=1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class Fred {\n" " struct A { int a; } s;\n" " void nextA() { return s.a+=1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class Fred {\n" " struct A { int a; } s;\n" " void nextA() { return s.a*=-1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("struct A { int a; } s;\n" "class Fred {\n" " void nextA() { return s.a=1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style) The member function 'Fred::nextA' can be static. [functionStatic]\n", errout_str()); checkConst("struct A { int a; } s;\n" "class Fred {\n" " void nextA() { return s.a-=1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style) The member function 'Fred::nextA' can be static. [functionStatic]\n", errout_str()); checkConst("struct A { int a; } s;\n" "class Fred {\n" " void nextA() { return s.a+=1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style) The member function 'Fred::nextA' can be static. [functionStatic]\n", errout_str()); checkConst("struct A { int a; } s;\n" "class Fred {\n" " void nextA() { return s.a*=-1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style) The member function 'Fred::nextA' can be static. [functionStatic]\n", errout_str()); checkConst("struct A { int a; } s;\n" "class Fred {\n" " void nextA() { return s.a/=-2; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style) The member function 'Fred::nextA' can be static. [functionStatic]\n", errout_str()); checkConst("struct A { int a; };\n" "class Fred {\n" " A s;\n" " void nextA() { return s.a=1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("struct A { int a; };\n" "class Fred {\n" " A s;\n" " void nextA() { return s.a-=1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("struct A { int a; };\n" "class Fred {\n" " A s;\n" " void nextA() { return s.a+=1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("struct A { int a; };\n" "class Fred {\n" " A s;\n" " void nextA() { return s.a*=-1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("struct A { int a; };\n" "class Fred {\n" " A s;\n" " void nextA() { return s.a/=-2; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -7287,49 +7435,49 @@ class TestClass : public TestFixture { checkConst("class Fred {\n" " int a[2];\n" " void nextA() { return ++a[0]; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class Fred {\n" " int a[2];\n" " void nextA() { return --a[0]; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class Fred {\n" " int a[2];\n" " void nextA() { return a[0]++; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class Fred {\n" " int a[2];\n" " void nextA() { return a[0]--; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("int a[2];\n" "class Fred {\n" " void nextA() { return ++a[0]; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style) The member function 'Fred::nextA' can be static. [functionStatic]\n", errout_str()); checkConst("int a[2];\n" "class Fred {\n" " void nextA() { return --a[0]; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style) The member function 'Fred::nextA' can be static. [functionStatic]\n", errout_str()); checkConst("int a[2];\n" "class Fred {\n" " void nextA() { return a[0]++; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style) The member function 'Fred::nextA' can be static. [functionStatic]\n", errout_str()); checkConst("int a[2];\n" "class Fred {\n" " void nextA() { return a[0]--; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style) The member function 'Fred::nextA' can be static. [functionStatic]\n", errout_str()); } @@ -7337,61 +7485,61 @@ class TestClass : public TestFixture { checkConst("class Fred {\n" " int a[2];\n" " void nextA() { return a[0]=1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class Fred {\n" " int a[2];\n" " void nextA() { return a[0]-=1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class Fred {\n" " int a[2];\n" " void nextA() { return a[0]+=1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class Fred {\n" " int a[2];\n" " void nextA() { return a[0]*=-1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class Fred {\n" " int a[2];\n" " void nextA() { return a[0]/=-2; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("int a[2];\n" "class Fred {\n" " void nextA() { return a[0]=1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style) The member function 'Fred::nextA' can be static. [functionStatic]\n", errout_str()); checkConst("int a[2];\n" "class Fred {\n" " void nextA() { return a[0]-=1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style) The member function 'Fred::nextA' can be static. [functionStatic]\n", errout_str()); checkConst("int a[2];\n" "class Fred {\n" " void nextA() { return a[0]+=1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style) The member function 'Fred::nextA' can be static. [functionStatic]\n", errout_str()); checkConst("int a[2];\n" "class Fred {\n" " void nextA() { return a[0]*=-1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style) The member function 'Fred::nextA' can be static. [functionStatic]\n", errout_str()); checkConst("int a[2];\n" "class Fred {\n" " void nextA() { return a[0]/=-2; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style) The member function 'Fred::nextA' can be static. [functionStatic]\n", errout_str()); } @@ -7400,8 +7548,8 @@ class TestClass : public TestFixture { checkConst("class Fred {\n" " int a;\n" " int &getR() { return a; }\n" - " int *getP() { return &a; }" - "};"); + " int *getP() { return &a; }\n" + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -7410,7 +7558,7 @@ class TestClass : public TestFixture { checkConst("class Fred {\n" " int *a;\n" " void clean() { delete a; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -7418,14 +7566,14 @@ class TestClass : public TestFixture { void constLPVOID() { checkConst("class Fred {\n" " UNKNOWN a() { return 0; };\n" - "};"); + "};\n"); TODO_ASSERT_EQUALS("[test.cpp:2]: (style) The member function 'Fred::a' can be static.\n", "", errout_str()); // #1579 - HDC checkConst("class Fred {\n" " foo bar;\n" " UNKNOWN a() { return b; };\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -7434,7 +7582,7 @@ class TestClass : public TestFixture { checkConst("class Fred {\n" " void f() const { };\n" " void a() { f(); };\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:2:10]: (style) The member function 'Fred::f' can be static. [functionStatic]\n" "[test.cpp:3:10]: (style, inconclusive) Technically the member function 'Fred::a' can be const. [functionConst]\n", errout_str()); @@ -7445,7 +7593,7 @@ class TestClass : public TestFixture { "public:\n" " A(){}\n" " unsigned int GetVecSize() {return m_v.size();}\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:6:17]: (style, inconclusive) Technically the member function 'A::GetVecSize' can be const. [functionConst]\n", errout_str()); checkConst("class A\n" @@ -7454,7 +7602,7 @@ class TestClass : public TestFixture { "public:\n" " A(){}\n" " bool GetVecEmpty() {return m_v.empty();}\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:6:9]: (style, inconclusive) Technically the member function 'A::GetVecEmpty' can be const. [functionConst]\n", errout_str()); } @@ -7466,7 +7614,7 @@ class TestClass : public TestFixture { "public:\n" " B() : b(0) { }\n" " int func() { return b; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:6:8]: (style, inconclusive) Technically the member function 'B::func' can be const. [functionConst]\n", errout_str()); checkConst("class A { };\n" @@ -7476,7 +7624,7 @@ class TestClass : public TestFixture { " B() : b(0) { }\n" " int func();\n" "};\n" - "int B::func() { return b; }"); + "int B::func() { return b; }\n"); ASSERT_EQUALS("[test.cpp:6:8] -> [test.cpp:8:8]: (style, inconclusive) Technically the member function 'B::func' can be const. [functionConst]\n", errout_str()); // base class has no virtual function @@ -7489,7 +7637,7 @@ class TestClass : public TestFixture { "public:\n" " B() : b(0) { }\n" " int func() { return b; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:9:9]: (style, inconclusive) Technically the member function 'B::func' can be const. [functionConst]\n", errout_str()); checkConst("class A {\n" @@ -7502,7 +7650,7 @@ class TestClass : public TestFixture { " B() : b(0) { }\n" " int func();\n" "};\n" - "int B::func() { return b; }"); + "int B::func() { return b; }\n"); ASSERT_EQUALS("[test.cpp:9:9] -> [test.cpp:11:8]: (style, inconclusive) Technically the member function 'B::func' can be const. [functionConst]\n", errout_str()); // base class has virtual function @@ -7515,7 +7663,7 @@ class TestClass : public TestFixture { "public:\n" " B() : b(0) { }\n" " int func() { return b; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class A {\n" @@ -7528,7 +7676,7 @@ class TestClass : public TestFixture { " B() : b(0) { }\n" " int func();\n" "};\n" - "int B::func() { return b; }"); + "int B::func() { return b; }\n"); ASSERT_EQUALS("", errout_str()); checkConst("class A {\n" @@ -7541,7 +7689,7 @@ class TestClass : public TestFixture { " B() : b(0) { }\n" " int func();\n" "};\n" - "int B::func() { return b; }"); + "int B::func() { return b; }\n"); ASSERT_EQUALS("", errout_str()); // base class has no virtual function @@ -7562,7 +7710,7 @@ class TestClass : public TestFixture { "public:\n" " C() : c(0) { }\n" " int func() { return c; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:5:9]: (style, inconclusive) Technically the member function 'A::func' can be const. [functionConst]\n" "[test.cpp:11:9]: (style, inconclusive) Technically the member function 'B::func' can be const. [functionConst]\n" "[test.cpp:17:9]: (style, inconclusive) Technically the member function 'C::func' can be const. [functionConst]\n", errout_str()); @@ -7587,7 +7735,7 @@ class TestClass : public TestFixture { " C() : c(0) { }\n" " int func();\n" "};\n" - "int C::func() { return c; }"); + "int C::func() { return c; }\n"); ASSERT_EQUALS("[test.cpp:5:9] -> [test.cpp:7:8]: (style, inconclusive) Technically the member function 'A::func' can be const. [functionConst]\n" "[test.cpp:12:9] -> [test.cpp:14:8]: (style, inconclusive) Technically the member function 'B::func' can be const. [functionConst]\n" "[test.cpp:19:9] -> [test.cpp:21:8]: (style, inconclusive) Technically the member function 'C::func' can be const. [functionConst]\n", errout_str()); @@ -7610,7 +7758,7 @@ class TestClass : public TestFixture { "public:\n" " C() : c(0) { }\n" " int func() { return c; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkConst("class A {\n" @@ -7633,7 +7781,7 @@ class TestClass : public TestFixture { " C() : c(0) { }\n" " int func();\n" "};\n" - "int C::func() { return c; }"); + "int C::func() { return c; }\n"); ASSERT_EQUALS("", errout_str()); // ticket #1311 @@ -7654,7 +7802,7 @@ class TestClass : public TestFixture { "public:\n" " Z(int x, int y, int z) : Y(x, y), z(z) { }\n" " int getZ() { return z; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:5:9]: (style, inconclusive) Technically the member function 'X::getX' can be const. [functionConst]\n" "[test.cpp:11:9]: (style, inconclusive) Technically the member function 'Y::getY' can be const. [functionConst]\n" "[test.cpp:17:9]: (style, inconclusive) Technically the member function 'Z::getZ' can be const. [functionConst]\n", errout_str()); @@ -7679,7 +7827,7 @@ class TestClass : public TestFixture { " Z(int x, int y, int z) : Y(x, y), z(z) { }\n" " int getZ();\n" "};\n" - "int Z::getZ() { return z; }"); + "int Z::getZ() { return z; }\n"); ASSERT_EQUALS("[test.cpp:5:9] -> [test.cpp:7:8]: (style, inconclusive) Technically the member function 'X::getX' can be const. [functionConst]\n" "[test.cpp:12:9] -> [test.cpp:14:8]: (style, inconclusive) Technically the member function 'Y::getY' can be const. [functionConst]\n" "[test.cpp:19:9] -> [test.cpp:21:8]: (style, inconclusive) Technically the member function 'Z::getZ' can be const. [functionConst]\n", errout_str()); @@ -7693,7 +7841,7 @@ class TestClass : public TestFixture { //" i = 4;\n" //"endif\n" " }\n" - "};"; + "};\n"; checkConst(code); ASSERT_EQUALS("[test.cpp:3:10]: (style) The member function 'foo::f' can be static. [functionStatic]\n", errout_str()); @@ -7705,7 +7853,7 @@ class TestClass : public TestFixture { void constFriend() { // ticket #1921 const char code[] = "class foo {\n" " friend void f() { }\n" - "};"; + "};\n"; checkConst(code); ASSERT_EQUALS("", errout_str()); } @@ -7720,7 +7868,7 @@ class TestClass : public TestFixture { " void setf(float x) {\n" " d.f = x;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -7737,7 +7885,7 @@ class TestClass : public TestFixture { " T c() {\n" " return y[1][6];\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:10:7]: (style, inconclusive) Technically the member function 'foo::c' can be const. [functionConst]\n", errout_str()); } @@ -7757,7 +7905,7 @@ class TestClass : public TestFixture { " for (decltype(auto) e : array)\n" " foo(e);\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:8:10]: (style, inconclusive) Technically the member function 'Fred::f2' can be const. [functionConst]\n", errout_str()); checkConst("struct T {\n" // #14390 @@ -7782,7 +7930,7 @@ class TestClass : public TestFixture { " std::shared_ptr data;\n" "};\n" "\n" - "std::shared_ptr Fred::getData() { return data; }"); + "std::shared_ptr Fred::getData() { return data; }\n"); ASSERT_EQUALS("", errout_str()); } @@ -7790,7 +7938,7 @@ class TestClass : public TestFixture { checkConst("class Fred {\n" "public:\n" " const char *const *data;\n" - " const char *const *getData() { return data; }\n}"); + " const char *const *getData() { return data; }\n}\n"); ASSERT_EQUALS("[test.cpp:4:24]: (style, inconclusive) Technically the member function 'Fred::getData' can be const. [functionConst]\n", errout_str()); } @@ -7798,7 +7946,7 @@ class TestClass : public TestFixture { checkConst("struct A {\n" " int x = 1;\n" " auto get() -> int & { return x; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -7848,7 +7996,7 @@ class TestClass : public TestFixture { SimpleTokenizer tokenizer(settings2, *this); ASSERT_LOC(tokenizer.tokenize(code), file, line); - CheckClass checkClass(&tokenizer, &settings2, this); + CheckClassImpl checkClass(&tokenizer, settings2, *this); checkClass.initializerListOrder(); } @@ -7857,7 +8005,7 @@ class TestClass : public TestFixture { " int a, b, c;\n" "public:\n" " Fred() : c(0), b(0), a(0) { }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:20] -> [test.cpp:2:12]: (style, inconclusive) Member variable 'Fred::b' is in the wrong place in the initializer list. [initializerList]\n" "[test.cpp:4:26] -> [test.cpp:2:9]: (style, inconclusive) Member variable 'Fred::a' is in the wrong place in the initializer list. [initializerList]\n", errout_str()); @@ -7865,14 +8013,14 @@ class TestClass : public TestFixture { " int a, b, c;\n" "public:\n" " Fred() : c{0}, b{0}, a{0} { }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:20] -> [test.cpp:2:12]: (style, inconclusive) Member variable 'Fred::b' is in the wrong place in the initializer list. [initializerList]\n" "[test.cpp:4:26] -> [test.cpp:2:9]: (style, inconclusive) Member variable 'Fred::a' is in the wrong place in the initializer list. [initializerList]\n", errout_str()); checkInitializerListOrder("struct S {\n" " S() : b(a = 1) {}\n" " int a, b;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkInitializerListOrder("struct S {\n" @@ -7895,7 +8043,7 @@ class TestClass : public TestFixture { " : B(\"abc\", s.nRows(), s.nCols())\n" " , m_i(_i)\n" " {}\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -7906,41 +8054,41 @@ class TestClass : public TestFixture { " C() : b(&a) {}\n" " B b;\n" " const A a;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:11] -> [test.cpp:5:7]: (style, inconclusive) Member variable 'C::b' uses an uninitialized argument 'a' due to the order of declarations. [initializerList]\n", errout_str()); checkInitializerListOrder("struct S {\n" " S(const std::string& f, std::string i, int b, int c) : a(0), b(b), c(c) {}\n" " int a, b, c;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkInitializerListOrder("struct S {\n" " S() : p(a) {}\n" " int* p;\n" " int a[1];\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkInitializerListOrder("struct S {\n" " S() : p(&i) {}\n" " int* p;\n" " int i;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkInitializerListOrder("struct S {\n" " S() : a(b = 1) {}\n" " int a, b;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkInitializerListOrder("struct S {\n" " S() : r(i) {}\n" " int& r;\n" " int i{};\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkInitializerListOrder("struct B {\n" @@ -7949,7 +8097,7 @@ class TestClass : public TestFixture { "struct D : B {\n" " D() : B(), j(b) {}\n" " int j;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkInitializerListOrder("struct S {\n" @@ -7957,13 +8105,13 @@ class TestClass : public TestFixture { " int a;\n" " static int i;\n" "};\n" - "int S::i = 0;"); + "int S::i = 0;\n"); ASSERT_EQUALS("", errout_str()); checkInitializerListOrder("struct S {\n" " S(int b) : a(b) {}\n" " int a, b{};\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkInitializerListOrder("class Foo {\n" // #3524 @@ -8002,7 +8150,7 @@ class TestClass : public TestFixture { SimpleTokenizer tokenizer(settings, *this); ASSERT_LOC(tokenizer.tokenize(code), file, line); - CheckClass checkClass(&tokenizer, &settings, this); + CheckClassImpl checkClass(&tokenizer, settings, *this); checkClass.initializationListUsage(); } @@ -8013,113 +8161,113 @@ class TestClass : public TestFixture { " int* b;\n" // No message for pointers: No performance gain " Enum c;\n" // No message for enums: No performance gain " Fred() { a = 0; b = 0; c = C; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkInitializationListUsage("class Fred {\n" " std::string s;\n" " Fred() { a = 0; s = \"foo\"; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:21]: (performance) Variable 's' is assigned in constructor body. Consider performing initialization in initialization list. [useInitializationList]\n", errout_str()); checkInitializationListUsage("class Fred {\n" " std::string& s;\n" // Message is invalid for references, since their initialization in initializer list is required anyway and behaves different from assignment (#5004) " Fred(const std::string& s_) : s(s_) { s = \"foo\"; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkInitializationListUsage("class Fred {\n" " std::vector v;\n" " Fred() { v = unknown; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:14]: (performance) Variable 'v' is assigned in constructor body. Consider performing initialization in initialization list. [useInitializationList]\n", errout_str()); checkInitializationListUsage("class C { std::string s; };\n" "class Fred {\n" " C c;\n" " Fred() { c = unknown; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:14]: (performance) Variable 'c' is assigned in constructor body. Consider performing initialization in initialization list. [useInitializationList]\n", errout_str()); checkInitializationListUsage("class C;\n" "class Fred {\n" " C c;\n" " Fred() { c = unknown; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:14]: (performance) Variable 'c' is assigned in constructor body. Consider performing initialization in initialization list. [useInitializationList]\n", errout_str()); checkInitializationListUsage("class C;\n" "class Fred {\n" " C c;\n" " Fred(Fred const & other) { c = other.c; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:32]: (performance) Variable 'c' is assigned in constructor body. Consider performing initialization in initialization list. [useInitializationList]\n", errout_str()); checkInitializationListUsage("class C;\n" "class Fred {\n" " C c;\n" " Fred(Fred && other) { c = other.c; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:27]: (performance) Variable 'c' is assigned in constructor body. Consider performing initialization in initialization list. [useInitializationList]\n", errout_str()); checkInitializationListUsage("class C;\n" "class Fred {\n" " C a;\n" " Fred() { initB(); a = b; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkInitializationListUsage("class C;\n" "class Fred {\n" " C a;\n" " Fred() : a(0) { if(b) a = 0; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkInitializationListUsage("class C;\n" "class Fred {\n" " C a[5];\n" " Fred() { for(int i = 0; i < 5; i++) a[i] = 0; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkInitializationListUsage("class C;\n" "class Fred {\n" " C a; int b;\n" " Fred() : b(5) { a = b; }\n" // Don't issue a message here: You actually could move it to the initialization list, but it would cause problems if you change the order of the variable declarations. - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkInitializationListUsage("class C;\n" "class Fred {\n" " C a;\n" " Fred() { try { a = new int; } catch(...) {} }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkInitializationListUsage("class Fred {\n" " std::string s;\n" " Fred() { s = toString((size_t)this); }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkInitializationListUsage("class Fred {\n" " std::string a;\n" " std::string foo();\n" " Fred() { a = foo(); }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkInitializationListUsage("class Fred {\n" " std::string a;\n" " Fred() { a = foo(); }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:14]: (performance) Variable 'a' is assigned in constructor body. Consider performing initialization in initialization list. [useInitializationList]\n", errout_str()); checkInitializationListUsage("class Fred {\n" // #4332 " static std::string s;\n" " Fred() { s = \"foo\"; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkInitializationListUsage("class Fred {\n" // #5640 @@ -8130,7 +8278,7 @@ class TestClass : public TestFixture { " str[1] = 0;\n" " s = str;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkInitializationListUsage("class B {\n" // #5640 @@ -8138,7 +8286,7 @@ class TestClass : public TestFixture { " B(const B& other) : _d(std::make_shared()) {\n" " *_d = *other._d;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkInitializationListUsage("class Bar {\n" // #8466 @@ -8147,7 +8295,7 @@ class TestClass : public TestFixture { " explicit Bar(const char s) : s{s} {}\n" "private:\n" " char s;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkInitializationListUsage("unsigned bar(std::string);\n" // #8291 @@ -8156,7 +8304,7 @@ class TestClass : public TestFixture { " int a_, b_;\n" " Foo(int a, int b) : a_(a), b_(b) {}\n" " Foo(int a, const std::string& b) : Foo(a, bar(b)) {}\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkInitializationListUsage("class Fred {\n" // #8111 @@ -8166,7 +8314,7 @@ class TestClass : public TestFixture { " ostr << x;\n" " a = ostr.str();\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // bailout: multi line lambda in rhs => do not warn @@ -8177,7 +8325,7 @@ class TestClass : public TestFixture { " return 1;\n" " };\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // don't warn if some other instance's members are assigned to @@ -8186,7 +8334,7 @@ class TestClass : public TestFixture { " C(C& c) : m_i(c.m_i) { c.m_i = (Foo)-1; }\n" "private:\n" " Foo m_i;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkInitializationListUsage("class A {\n" // #9821 - delegate constructor @@ -8199,7 +8347,7 @@ class TestClass : public TestFixture { "\n" "private:\n" " std::string st;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkInitializationListUsage("struct S {\n" // #14189 @@ -8214,7 +8362,7 @@ class TestClass : public TestFixture { " p = std::make_unique(s);\n" " }\n" " std::unique_ptr p;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -8226,7 +8374,7 @@ class TestClass : public TestFixture { SimpleTokenizer tokenizer(settings0, *this); ASSERT_LOC(tokenizer.tokenize(code), file, line); - CheckClass checkClass(&tokenizer, &settings0, this); + CheckClassImpl checkClass(&tokenizer, settings0, *this); (checkClass.checkSelfInitialization)(); } @@ -8235,14 +8383,14 @@ class TestClass : public TestFixture { " int i;\n" " Fred() : i(i) {\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:12]: (error) Member variable 'i' is initialized by itself. [selfInitialization]\n", errout_str()); checkSelfInitialization("class Fred {\n" " int i;\n" " Fred() : i{i} {\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:12]: (error) Member variable 'i' is initialized by itself. [selfInitialization]\n", errout_str()); checkSelfInitialization("class Fred {\n" @@ -8250,7 +8398,7 @@ class TestClass : public TestFixture { " Fred();\n" "};\n" "Fred::Fred() : i(i) {\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:14]: (error) Member variable 'i' is initialized by itself. [selfInitialization]\n", errout_str()); checkSelfInitialization("class A {\n" // #10427 @@ -8273,56 +8421,56 @@ class TestClass : public TestFixture { " std::string s;\n" " Fred() : s(s) {\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:12]: (error) Member variable 's' is initialized by itself. [selfInitialization]\n", errout_str()); checkSelfInitialization("class Fred {\n" " int x;\n" " Fred(int x);\n" "};\n" - "Fred::Fred(int x) : x(x) { }"); + "Fred::Fred(int x) : x(x) { }\n"); ASSERT_EQUALS("", errout_str()); checkSelfInitialization("class Fred {\n" " int x;\n" " Fred(int x);\n" "};\n" - "Fred::Fred(int x) : x{x} { }"); + "Fred::Fred(int x) : x{x} { }\n"); ASSERT_EQUALS("", errout_str()); checkSelfInitialization("class Fred {\n" " std::string s;\n" " Fred(const std::string& s) : s(s) {\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkSelfInitialization("class Fred {\n" " std::string s;\n" " Fred(const std::string& s) : s{s} {\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkSelfInitialization("struct Foo : Bar {\n" " int i;\n" " Foo(int i)\n" " : Bar(\"\"), i(i) {}\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkSelfInitialization("struct Foo : std::Bar {\n" // #6073 " int i;\n" " Foo(int i)\n" " : std::Bar(\"\"), i(i) {}\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkSelfInitialization("struct Foo : std::Bar {\n" // #6073 " int i;\n" " Foo(int i)\n" " : std::Bar(\"\"), i{i} {}\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -8336,7 +8484,7 @@ class TestClass : public TestFixture { SimpleTokenizer tokenizer(settings, *this); ASSERT_LOC(tokenizer.tokenize(code), file, line); - CheckClass checkClass(&tokenizer, &settings, this); + CheckClassImpl checkClass(&tokenizer, settings, *this); checkClass.checkVirtualFunctionCallInConstructor(); } @@ -8347,21 +8495,21 @@ class TestClass : public TestFixture { " A();\n" "};\n" "A::A()\n" - "{f();}"); + "{f();}\n"); ASSERT_EQUALS("[test.cpp:7:2] -> [test.cpp:3:17]: (style) Virtual function 'f' is called from constructor 'A()' at line 7. Dynamic binding is not used. [virtualCallInConstructor]\n", errout_str()); checkVirtualFunctionCall("class A {\n" " virtual int f();\n" " A() {f();}\n" "};\n" - "int A::f() { return 1; }"); + "int A::f() { return 1; }\n"); ASSERT_EQUALS("[test.cpp:3:10] -> [test.cpp:2:17]: (style) Virtual function 'f' is called from constructor 'A()' at line 3. Dynamic binding is not used. [virtualCallInConstructor]\n", errout_str()); checkVirtualFunctionCall("class A : B {\n" " int f() override;\n" " A() {f();}\n" "};\n" - "int A::f() { return 1; }"); + "int A::f() { return 1; }\n"); ASSERT_EQUALS("[test.cpp:3:10] -> [test.cpp:2:9]: (style) Virtual function 'f' is called from constructor 'A()' at line 3. Dynamic binding is not used. [virtualCallInConstructor]\n", errout_str()); checkVirtualFunctionCall("class B {\n" @@ -8371,14 +8519,14 @@ class TestClass : public TestFixture { " int f();\n" // <- not explicitly virtual " A() {f();}\n" "};\n" - "int A::f() { return 1; }"); + "int A::f() { return 1; }\n"); ASSERT_EQUALS("", errout_str()); checkVirtualFunctionCall("class A\n" "{\n" " A() { A::f(); }\n" " virtual void f() {}\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkVirtualFunctionCall("class A : B {\n" @@ -8464,7 +8612,7 @@ class TestClass : public TestFixture { " A();\n" "};\n" "A::A()\n" - "{pure();}"); + "{pure();}\n"); ASSERT_EQUALS("[test.cpp:7:2] -> [test.cpp:3:18]: (warning) Call of pure virtual function 'pure' in constructor. [pureVirtualCall]\n", errout_str()); checkVirtualFunctionCall("class A\n" @@ -8474,7 +8622,7 @@ class TestClass : public TestFixture { " int m;\n" "};\n" "A::A():m(A::pure())\n" - "{}"); + "{}\n"); ASSERT_EQUALS("[test.cpp:7:13] -> [test.cpp:3:17]: (warning) Call of pure virtual function 'pure' in constructor. [pureVirtualCall]\n", errout_str()); checkVirtualFunctionCall("namespace N {\n" @@ -8495,7 +8643,7 @@ class TestClass : public TestFixture { " int m;\n" "};\n" "A::~A()\n" - "{pure();}"); + "{pure();}\n"); ASSERT_EQUALS("[test.cpp:8:2] -> [test.cpp:3:18]: (warning) Call of pure virtual function 'pure' in destructor. [pureVirtualCall]\n", errout_str()); checkVirtualFunctionCall("class A\n" @@ -8506,7 +8654,7 @@ class TestClass : public TestFixture { " A();\n" "};\n" "A::A()\n" - "{nonpure();}"); + "{nonpure();}\n"); ASSERT_EQUALS("[test.cpp:9:2] -> [test.cpp:5:6] -> [test.cpp:3:18]: (warning) Call of pure virtual function 'pure' in constructor. [pureVirtualCall]\n", errout_str()); checkVirtualFunctionCall("class A\n" @@ -8518,7 +8666,7 @@ class TestClass : public TestFixture { " int m;\n" "};\n" "A::A():m(nonpure())\n" - "{}"); + "{}\n"); TODO_ASSERT_EQUALS("[test.cpp:9:2] -> [test.cpp:5:6] -> [test.cpp:3:18]: (warning) Call of pure virtual function 'pure' in constructor. [pureVirtualCall]\n", "", errout_str()); checkVirtualFunctionCall("class A\n" @@ -8530,7 +8678,7 @@ class TestClass : public TestFixture { " int m;\n" "};\n" "A::~A()\n" - "{nonpure();}"); + "{nonpure();}\n"); ASSERT_EQUALS("[test.cpp:10:2] -> [test.cpp:5:6] -> [test.cpp:3:18]: (warning) Call of pure virtual function 'pure' in destructor. [pureVirtualCall]\n", errout_str()); checkVirtualFunctionCall("class A\n" @@ -8539,7 +8687,7 @@ class TestClass : public TestFixture { " A(bool b);\n" "};\n" "A::A(bool b)\n" - "{if (b) pure();}"); + "{if (b) pure();}\n"); ASSERT_EQUALS("[test.cpp:7:9] -> [test.cpp:3:18]: (warning) Call of pure virtual function 'pure' in constructor. [pureVirtualCall]\n", errout_str()); checkVirtualFunctionCall("class A\n" @@ -8549,7 +8697,7 @@ class TestClass : public TestFixture { " int m;\n" "};\n" "A::~A()\n" - "{if (b) pure();}"); + "{if (b) pure();}\n"); ASSERT_EQUALS("[test.cpp:8:9] -> [test.cpp:3:18]: (warning) Call of pure virtual function 'pure' in destructor. [pureVirtualCall]\n", errout_str()); // #5831 @@ -8557,7 +8705,7 @@ class TestClass : public TestFixture { "public:\n" " virtual ~abc() throw() {}\n" " virtual void def(void* g) throw () = 0;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // #4992 @@ -8568,7 +8716,7 @@ class TestClass : public TestFixture { " m_callback = [this]() { return VirtualMethod(); };\n" " }\n" " virtual void VirtualMethod() = 0;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // #10559 @@ -8587,7 +8735,7 @@ class TestClass : public TestFixture { " A(const A & a);\n" "};\n" "A::A(const A & a)\n" - "{a.pure();}"); + "{a.pure();}\n"); ASSERT_EQUALS("", errout_str()); checkVirtualFunctionCall("class A\n" @@ -8600,7 +8748,7 @@ class TestClass : public TestFixture { " virtual void pure()=0;\n" "};\n" "A::A()\n" - "{B b; b.pure();}"); + "{B b; b.pure();}\n"); ASSERT_EQUALS("", errout_str()); } @@ -8613,7 +8761,7 @@ class TestClass : public TestFixture { "A::A()\n" "{pureWithBody();}\n" "void A::pureWithBody()\n" - "{}"); + "{}\n"); ASSERT_EQUALS("", errout_str()); checkVirtualFunctionCall("class A\n" @@ -8626,7 +8774,7 @@ class TestClass : public TestFixture { "A::A()\n" "{nonpure();}\n" "void A::pureWithBody()\n" - "{}"); + "{}\n"); ASSERT_EQUALS("", errout_str()); } @@ -8640,7 +8788,7 @@ class TestClass : public TestFixture { " A();\n" "};\n" "A::A()\n" - "{nonpure(false);}"); + "{nonpure(false);}\n"); ASSERT_EQUALS("", errout_str()); checkVirtualFunctionCall("class A\n" @@ -8651,7 +8799,7 @@ class TestClass : public TestFixture { " A();\n" "};\n" "A::A()\n" - "{nonpure(false);}"); + "{nonpure(false);}\n"); ASSERT_EQUALS("", errout_str()); checkVirtualFunctionCall("class A\n" @@ -8666,7 +8814,7 @@ class TestClass : public TestFixture { " A();\n" "};\n" "A::A()\n" - "{nonpure(false);}"); + "{nonpure(false);}\n"); ASSERT_EQUALS("", errout_str()); } @@ -8681,21 +8829,21 @@ class TestClass : public TestFixture { ASSERT_LOC(tokenizer.tokenize(code), file, line); // Check.. - CheckClass checkClass(&tokenizer, &settings, this); + CheckClassImpl checkClass(&tokenizer, settings, *this); (checkClass.checkOverride)(); } void override1() { checkOverride("class Base { virtual void f(); };\n" - "class Derived : Base { virtual void f(); };"); + "class Derived : Base { virtual void f(); };\n"); ASSERT_EQUALS("[test.cpp:1:27] -> [test.cpp:2:37]: (style) The function 'f' overrides a function in a base class but is not marked with a 'override' specifier. [missingOverride]\n", errout_str()); checkOverride("class Base { virtual void f(); };\n" - "class Derived : Base { virtual void f() override; };"); + "class Derived : Base { virtual void f() override; };\n"); ASSERT_EQUALS("", errout_str()); checkOverride("class Base { virtual void f(); };\n" - "class Derived : Base { virtual void f() final; };"); + "class Derived : Base { virtual void f() final; };\n"); ASSERT_EQUALS("", errout_str()); checkOverride("class Base {\n" @@ -8707,7 +8855,7 @@ class TestClass : public TestFixture { "public :\n" " auto foo( ) const -> size_t { return 0; }\n" " auto bar( ) const -> size_t override { return 0; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:18] -> [test.cpp:8:10]: (style) The function 'foo' overrides a function in a base class but is not marked with a 'override' specifier. [missingOverride]\n", errout_str()); checkOverride("namespace Test {\n" @@ -8719,7 +8867,7 @@ class TestClass : public TestFixture { "class C : Test::C {\n" "public:\n" " ~C();\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:18] -> [test.cpp:9:6]: (style) The destructor '~C' overrides a destructor in a base class but is not marked with a 'override' specifier. [missingOverride]\n", errout_str()); checkOverride("struct Base {\n" @@ -8729,7 +8877,7 @@ class TestClass : public TestFixture { "struct Derived: public Base {\n" " void foo() override;\n" " void foo(int);\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkOverride("struct B {\n" // #9092 @@ -8867,19 +9015,19 @@ class TestClass : public TestFixture { void overrideCVRefQualifiers() { checkOverride("class Base { virtual void f(); };\n" - "class Derived : Base { void f() const; }"); + "class Derived : Base { void f() const; }\n"); ASSERT_EQUALS("", errout_str()); checkOverride("class Base { virtual void f(); };\n" - "class Derived : Base { void f() volatile; }"); + "class Derived : Base { void f() volatile; }\n"); ASSERT_EQUALS("", errout_str()); checkOverride("class Base { virtual void f(); };\n" - "class Derived : Base { void f() &; }"); + "class Derived : Base { void f() &; }\n"); ASSERT_EQUALS("", errout_str()); checkOverride("class Base { virtual void f(); };\n" - "class Derived : Base { void f() &&; }"); + "class Derived : Base { void f() &&; }\n"); ASSERT_EQUALS("", errout_str()); } @@ -8893,7 +9041,7 @@ class TestClass : public TestFixture { ASSERT_LOC(tokenizer.simplifyTokens1(""), file, line); // Check.. - CheckClass checkClass(&tokenizer, &settings, this); + CheckClassImpl checkClass(&tokenizer, settings, *this); (checkClass.checkUselessOverride)(); } @@ -8901,51 +9049,51 @@ class TestClass : public TestFixture { checkUselessOverride("struct B { virtual int f() { return 5; } };\n" // #11757 "struct D : B {\n" " int f() override { return B::f(); }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:1:24] -> [test.cpp:3:9]: (style) The function 'f' overrides a function in a base class but just delegates back to the base class. [uselessOverride]\n", errout_str()); checkUselessOverride("struct B { virtual void f(); };\n" "struct D : B {\n" " void f() override { B::f(); }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:1:25] -> [test.cpp:3:10]: (style) The function 'f' overrides a function in a base class but just delegates back to the base class. [uselessOverride]\n", errout_str()); checkUselessOverride("struct B { virtual int f() = 0; };\n" "int B::f() { return 5; }\n" "struct D : B {\n" " int f() override { return B::f(); }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkUselessOverride("struct B { virtual int f(int i); };\n" "struct D : B {\n" " int f(int i) override { return B::f(i); }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:1:24] -> [test.cpp:3:9]: (style) The function 'f' overrides a function in a base class but just delegates back to the base class. [uselessOverride]\n", errout_str()); checkUselessOverride("struct B { virtual int f(int i); };\n" "struct D : B {\n" " int f(int i) override { return B::f(i + 1); }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkUselessOverride("struct B { virtual int f(int i, int j); };\n" "struct D : B {\n" " int f(int i, int j) override { return B::f(j, i); }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkUselessOverride("struct B { virtual int f(); };\n" "struct I { virtual int f() = 0; };\n" "struct D : B, I {\n" " int f() override { return B::f(); }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkUselessOverride("struct S { virtual void f(); };\n" "struct D : S {\n" " void f() final { S::f(); }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkUselessOverride("struct S {\n" @@ -8955,7 +9103,7 @@ class TestClass : public TestFixture { "struct D : S {\n" "public:\n" " void f() override { S::f(); }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkUselessOverride("struct B { virtual void f(int, int, int) const; };\n" // #11799 @@ -8965,7 +9113,7 @@ class TestClass : public TestFixture { "};\n" "void D::f(int a, int b, int c) const {\n" " B::f(a, b, m);\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkUselessOverride("struct B {\n" // #11803 @@ -8976,7 +9124,7 @@ class TestClass : public TestFixture { " void f() override { B::f(); }\n" " void f(int i) override;\n" " void g() { f(); }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkUselessOverride("struct B { virtual void f(); };\n" // #11808 @@ -8985,7 +9133,7 @@ class TestClass : public TestFixture { " void f() override {\n" " B::f();\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkUselessOverride("struct B {\n" @@ -8999,7 +9147,7 @@ class TestClass : public TestFixture { " int g() override { return 7; }\n" " int h(int j, int i) override { return i + j; }\n" " int j(int i, int j) override { return i + j; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:17] -> [test.cpp:9:9]: (style) The function 'g' overrides a function in a base class but is identical to the overridden function [uselessOverride]\n" "[test.cpp:5:17] -> [test.cpp:11:9]: (style) The function 'j' overrides a function in a base class but is identical to the overridden function [uselessOverride]\n", errout_str()); @@ -9083,12 +9231,12 @@ class TestClass : public TestFixture { ASSERT_LOC(tokenizer.tokenize(code), file, line); // Check.. - CheckClass checkClass(&tokenizer, &settings, this); + CheckClassImpl checkClass(&tokenizer, settings, *this); (checkClass.checkUnsafeClassRefMember)(); } void unsafeClassRefMember() { - checkUnsafeClassRefMember("class C { C(const std::string &s) : s(s) {} const std::string &s; };"); + checkUnsafeClassRefMember("class C { C(const std::string &s) : s(s) {} const std::string &s; };\n"); ASSERT_EQUALS("[test.cpp:1:37]: (warning) Unsafe class: The const reference member 'C::s' is initialized by a const reference constructor argument. You need to be careful about lifetime issues. [unsafeClassRefMember]\n", errout_str()); } @@ -9101,7 +9249,7 @@ class TestClass : public TestFixture { ASSERT_LOC(tokenizer.tokenize(code), file, line); // Check.. - CheckClass checkClass(&tokenizer, &settings1, this); + CheckClassImpl checkClass(&tokenizer, settings1, *this); (checkClass.checkThisUseAfterFree)(); } @@ -9115,7 +9263,7 @@ class TestClass : public TestFixture { "private:\n" " static C *mInstance;\n" " void hello() {}\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:38]: warning: Calling method 'hello()' when 'this' might be invalid [thisUseAfterFree]\n" "[test.cpp:5:13]: note: Assuming 'mInstance' is used as 'this'\n" "[test.cpp:3:20]: note: Delete 'mInstance', invalidating 'this'\n" @@ -9128,7 +9276,7 @@ class TestClass : public TestFixture { "private:\n" " static std::shared_ptr mInstance;\n" " void hello() {}\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:39]: warning: Calling method 'hello()' when 'this' might be invalid [thisUseAfterFree]\n" "[test.cpp:5:29]: note: Assuming 'mInstance' is used as 'this'\n" "[test.cpp:3:20]: note: Delete 'mInstance', invalidating 'this'\n" @@ -9142,7 +9290,7 @@ class TestClass : public TestFixture { " static std::shared_ptr mInstance;\n" " void hello();\n" " void reset() { mInstance.reset(); }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:29]: warning: Calling method 'hello()' when 'this' might be invalid [thisUseAfterFree]\n" "[test.cpp:5:29]: note: Assuming 'mInstance' is used as 'this'\n" "[test.cpp:7:18]: note: Delete 'mInstance', invalidating 'this'\n" @@ -9156,7 +9304,7 @@ class TestClass : public TestFixture { "private:\n" " static C *self;\n" " int x;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:33]: warning: Using member 'x' when 'this' might be invalid [thisUseAfterFree]\n" "[test.cpp:5:13]: note: Assuming 'self' is used as 'this'\n" "[test.cpp:3:20]: note: Delete 'self', invalidating 'this'\n" @@ -9169,7 +9317,7 @@ class TestClass : public TestFixture { "private:\n" " static C *self;\n" " std::map x;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:33]: warning: Using member 'x' when 'this' might be invalid [thisUseAfterFree]\n" "[test.cpp:5:13]: note: Assuming 'self' is used as 'this'\n" "[test.cpp:3:20]: note: Delete 'self', invalidating 'this'\n" @@ -9184,7 +9332,7 @@ class TestClass : public TestFixture { "private:\n" " std::shared_ptr mInstance;\n" " void hello() {}\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:39]: warning: Calling method 'hello()' when 'this' might be invalid [thisUseAfterFree]\n" "[test.cpp:6:22]: note: Assuming 'mInstance' is used as 'this'\n" "[test.cpp:4:20]: note: Delete 'mInstance', invalidating 'this'\n" @@ -9198,7 +9346,7 @@ class TestClass : public TestFixture { "private:\n" " C *self;\n" " int x;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkThisUseAfterFree("class C {\n" @@ -9208,7 +9356,7 @@ class TestClass : public TestFixture { "private:\n" " std::shared_ptr mInstance;\n" " void hello() {}\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkThisUseAfterFree("class C\n" @@ -9225,7 +9373,7 @@ class TestClass : public TestFixture { " static C* instanceSingleton;\n" "};\n" "\n" - "C* C::instanceSingleton;"); + "C* C::instanceSingleton;\n"); ASSERT_EQUALS("", errout_str()); // Avoid false positive when pointer is deleted in lambda @@ -9241,7 +9389,7 @@ class TestClass : public TestFixture { " auto done = [this] () { delete p; };\n" " dostuff();\n" " done();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); checkThisUseAfterFree("class C {\n" // #13311 @@ -9257,16 +9405,17 @@ class TestClass : public TestFixture { " if (self)\n" " delete self;\n" " self = new C();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } void ctu(const std::vector &code) { - Check &check = getCheck(); + CheckClass checkClass; + Check &check = getCheck(checkClass); // getFileInfo - std::list fileInfo; + std::list fileInfo; for (const std::string& c: code) { const std::string filename = std::to_string(fileInfo.size()) + ".cpp"; SimpleTokenizer tokenizer{settingsDefault, *this, filename}; @@ -9285,19 +9434,19 @@ class TestClass : public TestFixture { } void ctuOneDefinitionRule() { - ctu({"class C { C() { std::cout << 0; } };", "class C { C() { std::cout << 1; } };"}); + ctu({"class C { C() { std::cout << 0; } };\n", "class C { C() { std::cout << 1; } };\n"}); ASSERT_EQUALS("[1.cpp:1:1] -> [0.cpp:1:1]: (error) The one definition rule is violated, different classes/structs have the same name 'C' [ctuOneDefinitionRuleViolation]\n", errout_str()); - ctu({"class C { C(); }; C::C() { std::cout << 0; }", "class C { C(); }; C::C() { std::cout << 1; }"}); + ctu({"class C { C(); }; C::C() { std::cout << 0; }\n", "class C { C(); }; C::C() { std::cout << 1; }\n"}); ASSERT_EQUALS("[1.cpp:1:1] -> [0.cpp:1:1]: (error) The one definition rule is violated, different classes/structs have the same name 'C' [ctuOneDefinitionRuleViolation]\n", errout_str()); ctu({"class C { C() {} };\n", "class C { C() {} };\n"}); ASSERT_EQUALS("", errout_str()); - ctu({"class C { C(); }; C::C(){}", "class C { C(); }; C::C(){}"}); + ctu({"class C { C(); }; C::C(){}\n", "class C { C(); }; C::C(){}\n"}); ASSERT_EQUALS("", errout_str()); - ctu({"class A::C { C() { std::cout << 0; } };", "class B::C { C() { std::cout << 1; } };"}); + ctu({"class A::C { C() { std::cout << 0; } };\n", "class B::C { C() { std::cout << 1; } };\n"}); ASSERT_EQUALS("", errout_str()); // 11435 - template specialisations @@ -9315,16 +9464,16 @@ class TestClass : public TestFixture { SimpleTokenizer tokenizer(settings1, *this); ASSERT_LOC(tokenizer.tokenize(code), file, line); - // Check.. - const Check& c = getCheck(); - Check::FileInfo * fileInfo = (c.getFileInfo)(tokenizer, settings1, ""); + CheckClass check; + const Check& c = getCheck(check); + const Check::FileInfo * fileInfo = (c.getFileInfo)(tokenizer, settings1, ""); delete fileInfo; } void testGetFileInfo() { - getFileInfo("void foo() { union { struct { }; }; }"); // don't crash - getFileInfo("struct sometype { sometype(); }; sometype::sometype() = delete;"); // don't crash + getFileInfo("void foo() { union { struct { }; }; }\n"); // don't crash + getFileInfo("struct sometype { sometype(); }; sometype::sometype() = delete;\n"); // don't crash } #define checkReturnByReference(...) checkReturnByReference_(__FILE__, __LINE__, __VA_ARGS__) @@ -9337,7 +9486,7 @@ class TestClass : public TestFixture { ASSERT_LOC(tokenizer.simplifyTokens1(""), file, line); // Check.. - CheckClass checkClass(&tokenizer, &settings, this); + CheckClassImpl checkClass(&tokenizer, settings, *this); (checkClass.checkReturnByReference)(); } diff --git a/test/testcmdlineparser.cpp b/test/testcmdlineparser.cpp index 71dd068b39e..6f963e07212 100644 --- a/test/testcmdlineparser.cpp +++ b/test/testcmdlineparser.cpp @@ -31,9 +31,13 @@ #include "settings.h" #include "standards.h" #include "suppressions.h" -#include "timer.h" #include "utils.h" +#ifdef HAVE_RULES +#include "regex.h" +#include "rule.h" +#endif + #include #include #include @@ -258,6 +262,7 @@ class TestCmdlineParser : public TestFixture { TEST_CASE(premiumOptionsMetrics); TEST_CASE(premiumOptionsCertCIntPrecision); TEST_CASE(premiumOptionsLicenseFile); + TEST_CASE(premiumOptionsLicenseFilePathWithSpace); TEST_CASE(premiumOptionsInvalid1); TEST_CASE(premiumOptionsInvalid2); TEST_CASE(premiumSafety); @@ -499,6 +504,7 @@ class TestCmdlineParser : public TestFixture { TEST_CASE(noSafety); TEST_CASE(noSafetyOverride); TEST_CASE(debugAnalyzerinfo); + TEST_CASE(debugIpc); TEST_CASE(ignorepaths1); TEST_CASE(ignorepaths2); @@ -544,6 +550,8 @@ class TestCmdlineParser : public TestFixture { TEST_CASE(reportTypeMisraCpp2023); TEST_CASE(invalidReportType); TEST_CASE(defaultReportType); + TEST_CASE(keepComments); + TEST_CASE(keepCommentsNoDashE); } void nooptions() { @@ -1236,7 +1244,7 @@ class TestCmdlineParser : public TestFixture { const char * const argv[] = {"cppcheck", "--error-exitcode=", "file.cpp"}; // Fails since exit code not given ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); - ASSERT_EQUALS("cppcheck: error: argument to '--error-exitcode=' is not valid - not an integer.\n", logger->str()); + ASSERT_EQUALS("cppcheck: error: argument to '--error-exitcode=' is not valid - not an integer (invalid_argument).\n", logger->str()); } void errorExitcodeStr() { @@ -1244,7 +1252,7 @@ class TestCmdlineParser : public TestFixture { const char * const argv[] = {"cppcheck", "--error-exitcode=foo", "file.cpp"}; // Fails since invalid exit code ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); - ASSERT_EQUALS("cppcheck: error: argument to '--error-exitcode=' is not valid - not an integer.\n", logger->str()); + ASSERT_EQUALS("cppcheck: error: argument to '--error-exitcode=' is not valid - not an integer (invalid_argument).\n", logger->str()); } void exitcodeSuppressionsOld() { @@ -1395,7 +1403,7 @@ class TestCmdlineParser : public TestFixture { const char * const argv[] = {"cppcheck", "-j", "file.cpp"}; // Fails since -j is missing thread count ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); - ASSERT_EQUALS("cppcheck: error: argument to '-j' is not valid - not an integer.\n", logger->str()); + ASSERT_EQUALS("cppcheck: error: argument to '-j' is not valid - not an integer (invalid_argument).\n", logger->str()); } void jobsInvalid() { @@ -1403,7 +1411,7 @@ class TestCmdlineParser : public TestFixture { const char * const argv[] = {"cppcheck", "-j", "e", "file.cpp"}; // Fails since invalid count given for -j ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); - ASSERT_EQUALS("cppcheck: error: argument to '-j' is not valid - not an integer.\n", logger->str()); + ASSERT_EQUALS("cppcheck: error: argument to '-j' is not valid - not an integer (invalid_argument).\n", logger->str()); } void jobsNoJobs() { @@ -1433,7 +1441,7 @@ class TestCmdlineParser : public TestFixture { const char * const argv[] = {"cppcheck", "--max-configs=", "file.cpp"}; // Fails since --max-configs= is missing limit ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); - ASSERT_EQUALS("cppcheck: error: argument to '--max-configs=' is not valid - not an integer.\n", logger->str()); + ASSERT_EQUALS("cppcheck: error: argument to '--max-configs=' is not valid - not an integer (invalid_argument).\n", logger->str()); } void maxConfigsInvalid() { @@ -1441,7 +1449,7 @@ class TestCmdlineParser : public TestFixture { const char * const argv[] = {"cppcheck", "--max-configs=e", "file.cpp"}; // Fails since invalid count given for --max-configs= ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); - ASSERT_EQUALS("cppcheck: error: argument to '--max-configs=' is not valid - not an integer.\n", logger->str()); + ASSERT_EQUALS("cppcheck: error: argument to '--max-configs=' is not valid - not an integer (invalid_argument).\n", logger->str()); } void maxConfigsTooSmall() { @@ -1621,6 +1629,14 @@ class TestCmdlineParser : public TestFixture { ASSERT_EQUALS("--cert-c-int-precision=12", settings->premiumArgs); } + void premiumOptionsCertCIntPrecisionInvalid() { + REDIRECT; + asPremium(); + const char * const argv[] = {"cppcheck", "--premium-cert-c-int-precision=abc", "file.c"}; + ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); + ASSERT_EQUALS("cppcheck: error: argument to '--premium-cert-c-int-precision=' is not valid - not an integer (invalid_argument).\n", logger->str()); + } + void premiumOptionsLicenseFile() { REDIRECT; asPremium(); @@ -1629,6 +1645,14 @@ class TestCmdlineParser : public TestFixture { ASSERT_EQUALS("--license-file=file.lic", settings->premiumArgs); } + void premiumOptionsLicenseFilePathWithSpace() { + REDIRECT; + asPremium(); + const char * const argv[] = {"cppcheck", "--premium-license-file=license folder/file.lic", "file.c"}; + ASSERT_EQUALS_ENUM(CmdLineParser::Result::Success, parseFromArgs(argv)); + ASSERT_EQUALS("\"--license-file=license folder/file.lic\"", settings->premiumArgs); + } + void premiumOptionsInvalid1() { REDIRECT; asPremium(); @@ -1672,7 +1696,7 @@ class TestCmdlineParser : public TestFixture { REDIRECT; const char * const argv[] = {"cppcheck", "--report-progress=", "file.cpp"}; ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); - ASSERT_EQUALS("cppcheck: error: argument to '--report-progress=' is not valid - not an integer.\n", logger->str()); + ASSERT_EQUALS("cppcheck: error: argument to '--report-progress=' is not valid - not an integer (invalid_argument).\n", logger->str()); } void reportProgress3() { @@ -2172,7 +2196,7 @@ class TestCmdlineParser : public TestFixture { const char * const argv[] = {"cppcheck", "--xml", "--xml-version=a", "file.cpp"}; // FAils since unknown XML format version ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); - ASSERT_EQUALS("cppcheck: error: argument to '--xml-version=' is not valid - not an integer.\n", logger->str()); + ASSERT_EQUALS("cppcheck: error: argument to '--xml-version=' is not valid - not an integer (invalid_argument).\n", logger->str()); } void doc() { @@ -2193,21 +2217,21 @@ class TestCmdlineParser : public TestFixture { REDIRECT; const char * const argv[] = {"cppcheck", "--showtime=summary", "file.cpp"}; ASSERT_EQUALS_ENUM(CmdLineParser::Result::Success, parseFromArgs(argv)); - ASSERT(settings->showtime == ShowTime::SUMMARY); + ASSERT_EQUALS_ENUM(Settings::ShowTime::SUMMARY, settings->showtime); } void showtimeFile() { REDIRECT; const char * const argv[] = {"cppcheck", "--showtime=file", "file.cpp"}; ASSERT_EQUALS_ENUM(CmdLineParser::Result::Success, parseFromArgs(argv)); - ASSERT(settings->showtime == ShowTime::FILE); + ASSERT_EQUALS_ENUM(Settings::ShowTime::FILE, settings->showtime); } void showtimeFileTotal() { REDIRECT; const char * const argv[] = {"cppcheck", "--showtime=file-total", "file.cpp"}; ASSERT_EQUALS_ENUM(CmdLineParser::Result::Success, parseFromArgs(argv)); - ASSERT(settings->showtime == ShowTime::FILE_TOTAL); + ASSERT_EQUALS_ENUM(Settings::ShowTime::FILE_TOTAL, settings->showtime); } void showtimeTop5() { @@ -2221,21 +2245,21 @@ class TestCmdlineParser : public TestFixture { REDIRECT; const char * const argv[] = {"cppcheck", "--showtime=top5_file", "file.cpp"}; ASSERT_EQUALS_ENUM(CmdLineParser::Result::Success, parseFromArgs(argv)); - ASSERT(settings->showtime == ShowTime::TOP5_FILE); + ASSERT_EQUALS_ENUM(Settings::ShowTime::TOP5_FILE, settings->showtime); } void showtimeTop5Summary() { REDIRECT; const char * const argv[] = {"cppcheck", "--showtime=top5_summary", "file.cpp"}; ASSERT_EQUALS_ENUM(CmdLineParser::Result::Success, parseFromArgs(argv)); - ASSERT(settings->showtime == ShowTime::TOP5_SUMMARY); + ASSERT_EQUALS_ENUM(Settings::ShowTime::TOP5_SUMMARY, settings->showtime); } void showtimeNone() { REDIRECT; const char * const argv[] = {"cppcheck", "--showtime=none", "file.cpp"}; ASSERT_EQUALS_ENUM(CmdLineParser::Result::Success, parseFromArgs(argv)); - ASSERT(settings->showtime == ShowTime::NONE); + ASSERT_EQUALS_ENUM(Settings::ShowTime::NONE, settings->showtime); } void showtimeEmpty() { @@ -2395,7 +2419,7 @@ class TestCmdlineParser : public TestFixture { REDIRECT; const char * const argv[] = {"cppcheck", "--valueflow-max-iterations=seven"}; ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); - ASSERT_EQUALS("cppcheck: error: argument to '--valueflow-max-iterations=' is not valid - not an integer.\n", logger->str()); + ASSERT_EQUALS("cppcheck: error: argument to '--valueflow-max-iterations=' is not valid - not an integer (invalid_argument).\n", logger->str()); } void valueFlowMaxIterationsInvalid3() { @@ -2423,7 +2447,7 @@ class TestCmdlineParser : public TestFixture { REDIRECT; const char * const argv[] = {"cppcheck", "--checks-max-time=one", "file.cpp"}; ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); - ASSERT_EQUALS("cppcheck: error: argument to '--checks-max-time=' is not valid - not an integer.\n", logger->str()); + ASSERT_EQUALS("cppcheck: error: argument to '--checks-max-time=' is not valid - not an integer (invalid_argument).\n", logger->str()); } #ifdef HAS_THREADING_MODEL_FORK @@ -2445,7 +2469,7 @@ class TestCmdlineParser : public TestFixture { REDIRECT; const char * const argv[] = {"cppcheck", "-l", "one", "file.cpp"}; ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); - ASSERT_EQUALS("cppcheck: error: argument to '-l' is not valid - not an integer.\n", logger->str()); + ASSERT_EQUALS("cppcheck: error: argument to '-l' is not valid - not an integer (invalid_argument).\n", logger->str()); } #else void loadAverageNotSupported() { @@ -2482,7 +2506,7 @@ class TestCmdlineParser : public TestFixture { REDIRECT; const char * const argv[] = {"cppcheck", "--max-ctu-depth=one", "file.cpp"}; ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); - ASSERT_EQUALS("cppcheck: error: argument to '--max-ctu-depth=' is not valid - not an integer.\n", logger->str()); + ASSERT_EQUALS("cppcheck: error: argument to '--max-ctu-depth=' is not valid - not an integer (invalid_argument).\n", logger->str()); } void performanceValueflowMaxTime() { @@ -2496,7 +2520,7 @@ class TestCmdlineParser : public TestFixture { REDIRECT; const char * const argv[] = {"cppcheck", "--performance-valueflow-max-time=one", "file.cpp"}; ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); - ASSERT_EQUALS("cppcheck: error: argument to '--performance-valueflow-max-time=' is not valid - not an integer.\n", logger->str()); + ASSERT_EQUALS("cppcheck: error: argument to '--performance-valueflow-max-time=' is not valid - not an integer (invalid_argument).\n", logger->str()); } void performanceValueFlowMaxIfCount() { @@ -2510,7 +2534,7 @@ class TestCmdlineParser : public TestFixture { REDIRECT; const char * const argv[] = {"cppcheck", "--performance-valueflow-max-if-count=one", "file.cpp"}; ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); - ASSERT_EQUALS("cppcheck: error: argument to '--performance-valueflow-max-if-count=' is not valid - not an integer.\n", logger->str()); + ASSERT_EQUALS("cppcheck: error: argument to '--performance-valueflow-max-if-count=' is not valid - not an integer (invalid_argument).\n", logger->str()); } void templateMaxTime() { @@ -2524,7 +2548,7 @@ class TestCmdlineParser : public TestFixture { REDIRECT; const char * const argv[] = {"cppcheck", "--template-max-time=one", "file.cpp"}; ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); - ASSERT_EQUALS("cppcheck: error: argument to '--template-max-time=' is not valid - not an integer.\n", logger->str()); + ASSERT_EQUALS("cppcheck: error: argument to '--template-max-time=' is not valid - not an integer (invalid_argument).\n", logger->str()); } void templateMaxTimeInvalid2() { @@ -2545,7 +2569,7 @@ class TestCmdlineParser : public TestFixture { REDIRECT; const char * const argv[] = {"cppcheck", "--typedef-max-time=one", "file.cpp"}; ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); - ASSERT_EQUALS("cppcheck: error: argument to '--typedef-max-time=' is not valid - not an integer.\n", logger->str()); + ASSERT_EQUALS("cppcheck: error: argument to '--typedef-max-time=' is not valid - not an integer (invalid_argument).\n", logger->str()); } void typedefMaxTimeInvalid2() { @@ -2720,14 +2744,14 @@ class TestCmdlineParser : public TestFixture { ASSERT_EQUALS_ENUM(CmdLineParser::Result::Success, parseFromArgs(argv)); ASSERT_EQUALS(2, settings->rules.size()); auto it = settings->rules.cbegin(); - ASSERT_EQUALS_ENUM(Regex::Engine::Pcre, it->engine); + ASSERT_EQUALS_ENUM(Regex::Engine::Pcre, it->regex->engine()); ASSERT_EQUALS("raw", it->tokenlist); ASSERT_EQUALS(".+", it->pattern); ASSERT_EQUALS_ENUM(Severity::error, it->severity); ASSERT_EQUALS("ruleId1", it->id); ASSERT_EQUALS("ruleSummary1", it->summary); ++it; - ASSERT_EQUALS_ENUM(Regex::Engine::Pcre, it->engine); + ASSERT_EQUALS_ENUM(Regex::Engine::Pcre, it->regex->engine()); ASSERT_EQUALS("define", it->tokenlist); ASSERT_EQUALS(".*", it->pattern); ASSERT_EQUALS_ENUM(Severity::warning, it->severity); @@ -2751,7 +2775,7 @@ class TestCmdlineParser : public TestFixture { ASSERT_EQUALS_ENUM(CmdLineParser::Result::Success, parseFromArgs(argv)); ASSERT_EQUALS(1, settings->rules.size()); auto it = settings->rules.cbegin(); - ASSERT_EQUALS_ENUM(Regex::Engine::Pcre, it->engine); + ASSERT_EQUALS_ENUM(Regex::Engine::Pcre, it->regex->engine()); ASSERT_EQUALS("define", it->tokenlist); ASSERT_EQUALS(".+", it->pattern); ASSERT_EQUALS_ENUM(Severity::error, it->severity); @@ -3235,7 +3259,7 @@ class TestCmdlineParser : public TestFixture { REDIRECT; const char * const argv[] = {"cppcheck", "--max-template-recursion=", "file.cpp"}; ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); - ASSERT_EQUALS("cppcheck: error: argument to '--max-template-recursion=' is not valid - not an integer.\n", logger->str()); + ASSERT_EQUALS("cppcheck: error: argument to '--max-template-recursion=' is not valid - not an integer (invalid_argument).\n", logger->str()); } void emitDuplicates() { @@ -3480,6 +3504,13 @@ class TestCmdlineParser : public TestFixture { ASSERT_EQUALS(true, settings->debugainfo); } + void debugIpc() { + REDIRECT; + const char * const argv[] = {"cppcheck", "--debug-ipc", "file.cpp"}; + ASSERT_EQUALS_ENUM(CmdLineParser::Result::Success, parseFromArgs(argv)); + ASSERT_EQUALS(true, settings->debugipc); + } + void ignorepaths1() { REDIRECT; const char * const argv[] = {"cppcheck", "-isrc", "file.cpp"}; @@ -3802,6 +3833,20 @@ class TestCmdlineParser : public TestFixture { ASSERT_EQUALS_ENUM(CmdLineParser::Result::Success, parseFromArgs(argv)); ASSERT_EQUALS_ENUM(ReportType::normal, settings->reportType); } + + void keepComments() { + REDIRECT; + const char *const argv[] = { "cppcheck", "-E", "-CC", "file.cpp" }; + ASSERT_EQUALS_ENUM(CmdLineParser::Result::Success, parseFromArgs(argv)); + ASSERT(settings->keepComments); + } + + void keepCommentsNoDashE() { + REDIRECT; + const char *const argv[] = { "cppcheck", "-CC", "file.cpp" }; + ASSERT_EQUALS_ENUM(CmdLineParser::Result::Fail, parseFromArgs(argv)); + ASSERT_EQUALS("cppcheck: error: -CC may only be used on conjunciton with -E\n", logger->str()); + } }; REGISTER_TEST(TestCmdlineParser) diff --git a/test/testcondition.cpp b/test/testcondition.cpp index 421fa53e2bc..37efa4f8091 100644 --- a/test/testcondition.cpp +++ b/test/testcondition.cpp @@ -69,6 +69,7 @@ class TestCondition : public TestFixture { TEST_CASE(incorrectLogicOperator15); TEST_CASE(incorrectLogicOperator16); // #10070 TEST_CASE(incorrectLogicOperator17); + TEST_CASE(incorrectLogicOperator18); // #12282 TEST_CASE(secondAlwaysTrueFalseWhenFirstTrueError); TEST_CASE(incorrectLogicOp_condSwapping); TEST_CASE(testBug5895); @@ -94,6 +95,8 @@ class TestCondition : public TestFixture { TEST_CASE(identicalConditionAfterEarlyExit); TEST_CASE(innerConditionModified); + TEST_CASE(overlappingInnerCondition); + TEST_CASE(clarifyCondition1); // if (a = b() < 0) TEST_CASE(clarifyCondition2); // if (a & b == c) TEST_CASE(clarifyCondition3); // if (! a & b) @@ -147,8 +150,8 @@ class TestCondition : public TestFixture { // Tokenizer.. ASSERT_LOC(tokenizer.simplifyTokens1(""), file, line); - // Run checks.. - runChecks(tokenizer, this); + CheckCondition check; + runChecks(check, tokenizer, *this); } #define checkP(...) checkP_(__FILE__, __LINE__, __VA_ARGS__) @@ -160,8 +163,8 @@ class TestCondition : public TestFixture { // Tokenizer.. ASSERT_LOC(tokenizer.simplifyTokens1(""), file, line); - // Run checks.. - runChecks(tokenizer, this); + CheckCondition check; + runChecks(check, tokenizer, *this); } void assignAndCompare() { @@ -170,59 +173,59 @@ class TestCondition : public TestFixture { "{\n" " int y = x & 4;\n" " if (y == 3);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:11] -> [test.cpp:4:8]: (style) Mismatching assignment and comparison, comparison 'y==3' is always false. [assignIfError]\n", errout_str()); check("void foo(int x)\n" "{\n" " int y = x & 4;\n" " if (y != 3);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:11] -> [test.cpp:4:8]: (style) Mismatching assignment and comparison, comparison 'y!=3' is always true. [assignIfError]\n", errout_str()); // | check("void foo(int x) {\n" " int y = x | 0x14;\n" " if (y == 0x710);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11] -> [test.cpp:3:8]: (style) Mismatching assignment and comparison, comparison 'y==0x710' is always false. [assignIfError]\n", errout_str()); check("void foo(int x) {\n" " int y = x | 0x14;\n" " if (y == 0x71f);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // various simple assignments check("void foo(int x) {\n" " int y = (x+1) | 1;\n" " if (y == 2);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11] -> [test.cpp:3:8]: (style) Mismatching assignment and comparison, comparison 'y==2' is always false. [assignIfError]\n", errout_str()); check("void foo() {\n" " int y = 1 | x();\n" " if (y == 2);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11] -> [test.cpp:3:8]: (style) Mismatching assignment and comparison, comparison 'y==2' is always false. [assignIfError]\n", errout_str()); // multiple conditions check("void foo(int x) {\n" " int y = x & 4;\n" " if ((y == 3) && (z == 1));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11] -> [test.cpp:3:9]: (style) Mismatching assignment and comparison, comparison 'y==3' is always false. [assignIfError]\n", errout_str()); check("void foo(int x) {\n" " int y = x & 4;\n" " if ((x==123) || ((y == 3) && (z == 1)));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11] -> [test.cpp:3:22]: (style) Mismatching assignment and comparison, comparison 'y==3' is always false. [assignIfError]\n", errout_str()); check("void f(int x) {\n" " int y = x & 7;\n" " if (setvalue(&y) && y != 8);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // recursive checking into scopes @@ -230,20 +233,20 @@ class TestCondition : public TestFixture { " int y = x & 7;\n" " if (z) y=0;\n" " else { if (y==8); }\n" // always false - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11] -> [test.cpp:4:15]: (style) Mismatching assignment and comparison, comparison 'y==8' is always false. [assignIfError]\n", errout_str()); // while check("void f(int x) {\n" " int y = x & 7;\n" " while (y==8);\n" // local variable => always false - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11] -> [test.cpp:3:11]: (style) Mismatching assignment and comparison, comparison 'y==8' is always false. [assignIfError]\n", errout_str()); check("void f(int x) {\n" " extern int y; y = x & 7;\n" " while (y==8);\n" // non-local variable => no error - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" @@ -252,7 +255,7 @@ class TestCondition : public TestFixture { " int y = 16 | a;\n" " while (y != 0) y--;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void g(int x);\n" @@ -262,7 +265,7 @@ class TestCondition : public TestFixture { " int y = 16 | a;\n" " while (y != 0) g(y);\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:5:15] -> [test.cpp:6:15]: (style) Mismatching assignment and comparison, comparison 'y!=0' is always true. [assignIfError]\n", errout_str()); @@ -274,7 +277,7 @@ class TestCondition : public TestFixture { " int y = 16 | a;\n" " while (y != 0) g(y);\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // calling function @@ -282,14 +285,14 @@ class TestCondition : public TestFixture { " int y = x & 7;\n" " do_something();\n" " if (y==8);\n" // local variable => always false - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11] -> [test.cpp:4:8]: (style) Mismatching assignment and comparison, comparison 'y==8' is always false. [assignIfError]\n", errout_str()); check("void f(int x) {\n" " int y = x & 7;\n" " do_something(&y);\n" // passing variable => no error " if (y==8);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void do_something(int);\n" @@ -297,14 +300,14 @@ class TestCondition : public TestFixture { " int y = x & 7;\n" " do_something(y);\n" " if (y==8);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:11] -> [test.cpp:5:8]: (style) Mismatching assignment and comparison, comparison 'y==8' is always false. [assignIfError]\n", errout_str()); check("void f(int x) {\n" " extern int y; y = x & 7;\n" " do_something();\n" " if (y==8);\n" // non-local variable => no error - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #4434 : false positive: ?: @@ -312,28 +315,28 @@ class TestCondition : public TestFixture { " x = x & 1;\n" " x = x & 1 ? 1 : -1;\n" " if(x != -1) { }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #4735 check("void f() {\n" " int x = *(char*)&0x12345678;\n" " if (x==18) { }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // bailout: no variable info check("void foo(int x) {\n" " y = 2 | x;\n" // y not declared => no error " if(y == 1) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // bailout: negative number check("void foo(int x) {\n" " int y = -2 | x;\n" // negative number => no error " if (y==1) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // bailout: pass variable to function @@ -341,7 +344,7 @@ class TestCondition : public TestFixture { " int y = 2 | x;\n" " bar(&y);\n" // pass variable to function => no error " if (y==1) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // no crash on unary operator& (#5643) @@ -349,7 +352,7 @@ class TestCondition : public TestFixture { check("SdrObject* ApplyGraphicToObject() {\n" " if (&rHitObject) {}\n" " else if (rHitObject.IsClosedObj() && !&rHitObject) { }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9]: (style) Condition '&rHitObject' is always true [knownConditionTrueFalse]\n" "[test.cpp:3:42]: (style) Condition '!&rHitObject' is always false [knownConditionTrueFalse]\n", errout_str()); @@ -362,7 +365,7 @@ class TestCondition : public TestFixture { " if (c == 4)\n" " c = 0;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int a) {\n" // #6662 @@ -370,7 +373,7 @@ class TestCondition : public TestFixture { " while (x <= 4) {\n" " if (x != 5) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9] -> [test.cpp:4:8]: (style) Mismatching assignment and comparison, comparison 'x!=5' is always true. [assignIfError]\n", errout_str()); check("void f(int a) {\n" // #6662 @@ -378,7 +381,7 @@ class TestCondition : public TestFixture { " while ((x += 4) < 10) {\n" " if (x != 5) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -386,7 +389,7 @@ class TestCondition : public TestFixture { " while (x) {\n" " g(x);\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void g(int x);\n" @@ -395,7 +398,7 @@ class TestCondition : public TestFixture { " while (x) {\n" " g(x);\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:12]: (style) Condition 'x' is always true [knownConditionTrueFalse]\n", errout_str()); check("void g(int & x);\n" @@ -404,7 +407,7 @@ class TestCondition : public TestFixture { " while (x) {\n" " g(x);\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -413,13 +416,13 @@ class TestCondition : public TestFixture { check("void f(int a) {\n" " int b = a & 0xf0;\n" " b &= 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11] -> [test.cpp:3:5]: (style) Mismatching bitmasks. Result is always 0 (X = Y & 0xf0; Z = X & 0x1; => Z=0). [mismatchingBitAnd]\n", errout_str()); check("void f(int a) {\n" " int b = a & 0xf0;\n" " int c = b & 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11] -> [test.cpp:3:9]: (style) Mismatching bitmasks. Result is always 0 (X = Y & 0xf0; Z = X & 0x1; => Z=0). [mismatchingBitAnd]\n", errout_str()); check("void f(int a) {\n" @@ -428,67 +431,67 @@ class TestCondition : public TestFixture { " case 1: b &= 1; break;\n" " case 2: b &= 2; break;\n" " };\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } void comparison() { // CheckCondition::comparison test cases // '==' - check("void f(int a) {\n assert( (a & 0x07) == 8U );\n}"); + check("void f(int a) {\n assert( (a & 0x07) == 8U );\n}\n"); ASSERT_EQUALS("[test.cpp:2:13]: (style) Expression '(X & 0x7) == 0x8' is always false. [comparisonError]\n",errout_str()); - check("void f(int a) {\n assert( (a & b & 4 & c ) == 3 );\n}"); + check("void f(int a) {\n assert( (a & b & 4 & c ) == 3 );\n}\n"); ASSERT_EQUALS("[test.cpp:2:21]: (style) Expression '(X & 0x4) == 0x3' is always false. [comparisonError]\n", errout_str()); - check("void f(int a) {\n assert( (a | 0x07) == 8U );\n}"); + check("void f(int a) {\n assert( (a | 0x07) == 8U );\n}\n"); ASSERT_EQUALS("[test.cpp:2:13]: (style) Expression '(X | 0x7) == 0x8' is always false. [comparisonError]\n",errout_str()); - check("void f(int a) {\n assert( (a & 0x07) == 7U );\n}"); + check("void f(int a) {\n assert( (a & 0x07) == 7U );\n}\n"); ASSERT_EQUALS("", errout_str()); - check("void f(int a) {\n assert( (a | 0x01) == -15 );\n}"); + check("void f(int a) {\n assert( (a | 0x01) == -15 );\n}\n"); ASSERT_EQUALS("", errout_str()); // '!=' - check("void f(int a) {\n assert( (a & 0x07) != 8U );\n}"); + check("void f(int a) {\n assert( (a & 0x07) != 8U );\n}\n"); ASSERT_EQUALS("[test.cpp:2:13]: (style) Expression '(X & 0x7) != 0x8' is always true. [comparisonError]\n",errout_str()); - check("void f(int a) {\n assert( (a | 0x07) != 8U );\n}"); + check("void f(int a) {\n assert( (a | 0x07) != 8U );\n}\n"); ASSERT_EQUALS("[test.cpp:2:13]: (style) Expression '(X | 0x7) != 0x8' is always true. [comparisonError]\n",errout_str()); - check("void f(int a) {\n assert( (a & 0x07) != 7U );\n}"); + check("void f(int a) {\n assert( (a & 0x07) != 7U );\n}\n"); ASSERT_EQUALS("", errout_str()); - check("void f(int a) {\n assert( (a | 0x07) != 7U );\n}"); + check("void f(int a) {\n assert( (a | 0x07) != 7U );\n}\n"); ASSERT_EQUALS("", errout_str()); // '>=' - check("void f(int a) {\n assert( (a & 0x07) >= 8U );\n}"); + check("void f(int a) {\n assert( (a & 0x07) >= 8U );\n}\n"); ASSERT_EQUALS("[test.cpp:2:13]: (style) Expression '(X & 0x7) >= 0x8' is always false. [comparisonError]\n",errout_str()); - check("void f(unsigned int a) {\n assert( (a | 0x7) >= 7U );\n}"); + check("void f(unsigned int a) {\n assert( (a | 0x7) >= 7U );\n}\n"); ASSERT_EQUALS("[test.cpp:2:13]: (style) Expression '(X | 0x7) >= 0x7' is always true. [comparisonError]\n",errout_str()); - check("void f(int a) {\n assert( (a & 0x07) >= 7U );\n}"); + check("void f(int a) {\n assert( (a & 0x07) >= 7U );\n}\n"); ASSERT_EQUALS("",errout_str()); - check("void f(int a) {\n assert( (a | 0x07) >= 8U );\n}"); + check("void f(int a) {\n assert( (a | 0x07) >= 8U );\n}\n"); ASSERT_EQUALS("",errout_str()); //correct for negative 'a' // '>' - check("void f(int a) {\n assert( (a & 0x07) > 7U );\n}"); + check("void f(int a) {\n assert( (a & 0x07) > 7U );\n}\n"); ASSERT_EQUALS("[test.cpp:2:13]: (style) Expression '(X & 0x7) > 0x7' is always false. [comparisonError]\n",errout_str()); - check("void f(unsigned int a) {\n assert( (a | 0x7) > 6U );\n}"); + check("void f(unsigned int a) {\n assert( (a | 0x7) > 6U );\n}\n"); ASSERT_EQUALS("[test.cpp:2:13]: (style) Expression '(X | 0x7) > 0x6' is always true. [comparisonError]\n",errout_str()); - check("void f(int a) {\n assert( (a & 0x07) > 6U );\n}"); + check("void f(int a) {\n assert( (a & 0x07) > 6U );\n}\n"); ASSERT_EQUALS("",errout_str()); - check("void f(int a) {\n assert( (a | 0x07) > 7U );\n}"); + check("void f(int a) {\n assert( (a | 0x07) > 7U );\n}\n"); ASSERT_EQUALS("",errout_str()); //correct for negative 'a' // '<=' - check("void f(int a) {\n assert( (a & 0x07) <= 7U );\n}"); + check("void f(int a) {\n assert( (a & 0x07) <= 7U );\n}\n"); ASSERT_EQUALS("[test.cpp:2:13]: (style) Expression '(X & 0x7) <= 0x7' is always true. [comparisonError]\n",errout_str()); - check("void f(unsigned int a) {\n assert( (a | 0x08) <= 7U );\n}"); + check("void f(unsigned int a) {\n assert( (a | 0x08) <= 7U );\n}\n"); ASSERT_EQUALS("[test.cpp:2:13]: (style) Expression '(X | 0x8) <= 0x7' is always false. [comparisonError]\n",errout_str()); - check("void f(int a) {\n assert( (a & 0x07) <= 6U );\n}"); + check("void f(int a) {\n assert( (a & 0x07) <= 6U );\n}\n"); ASSERT_EQUALS("",errout_str()); - check("void f(int a) {\n assert( (a | 0x08) <= 7U );\n}"); + check("void f(int a) {\n assert( (a | 0x08) <= 7U );\n}\n"); ASSERT_EQUALS("",errout_str()); //correct for negative 'a' // '<' - check("void f(int a) {\n assert( (a & 0x07) < 8U );\n}"); + check("void f(int a) {\n assert( (a & 0x07) < 8U );\n}\n"); ASSERT_EQUALS("[test.cpp:2:13]: (style) Expression '(X & 0x7) < 0x8' is always true. [comparisonError]\n",errout_str()); - check("void f(unsigned int a) {\n assert( (a | 0x07) < 7U );\n}"); + check("void f(unsigned int a) {\n assert( (a | 0x07) < 7U );\n}\n"); ASSERT_EQUALS("[test.cpp:2:13]: (style) Expression '(X | 0x7) < 0x7' is always false. [comparisonError]\n",errout_str()); - check("void f(int a) {\n assert( (a & 0x07) < 3U );\n}"); + check("void f(int a) {\n assert( (a & 0x07) < 3U );\n}\n"); ASSERT_EQUALS("",errout_str()); - check("void f(int a) {\n assert( (a | 0x07) < 7U );\n}"); + check("void f(int a) {\n assert( (a | 0x07) < 7U );\n}\n"); ASSERT_EQUALS("",errout_str()); //correct for negative 'a' check("void f(int i) {\n" // #11998 @@ -525,7 +528,8 @@ class TestCondition : public TestFixture { SimpleTokenizer tokenizer(settings1, *this); ASSERT_LOC(tokenizer.tokenize(code), file, line); - runChecks(tokenizer, this); + CheckCondition check; + runChecks(check, tokenizer, *this); } void multicompare() { @@ -533,14 +537,14 @@ class TestCondition : public TestFixture { "{\n" " if (x & 7);\n" " else { if (x == 1); }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:18]: (style) Expression is always false because 'else if' condition matches previous condition at line 3. [multiCondition]\n", errout_str()); check("void foo(int x)\n" "{\n" " if (x & 7);\n" " else { if (x & 1); }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:18]: (style) Expression is always false because 'else if' condition matches previous condition at line 3. [multiCondition]\n", errout_str()); check("extern int bar() __attribute__((pure));\n" @@ -548,7 +552,7 @@ class TestCondition : public TestFixture { "{\n" " if ( bar() >1 && b) {}\n" " else if (bar() >1 && b) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:23]: (style) Expression is always false because 'else if' condition matches previous condition at line 4. [multiCondition]\n", errout_str()); checkPureFunction("extern int bar();\n" @@ -556,14 +560,14 @@ class TestCondition : public TestFixture { "{\n" " if ( bar() >1 && b) {}\n" " else if (bar() >1 && b) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:23]: (style) Expression is always false because 'else if' condition matches previous condition at line 4. [multiCondition]\n", errout_str()); // 7284 check("void foo() {\n" " if (a) {}\n" " else if (!!a) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:14]: (style) Expression is always false because 'else if' condition matches previous condition at line 2. [multiCondition]\n", errout_str()); // #11059 @@ -590,53 +594,53 @@ class TestCondition : public TestFixture { check("void f(int a, int &b) {\n" " if (a) { b = 1; }\n" " else { if (a) { b = 2; } }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:16]: (style) Expression is always false because 'else if' condition matches previous condition at line 2. [multiCondition]\n", errout_str()); check("void f(int a, int &b) {\n" " if (a) { b = 1; }\n" " else { if (a) { b = 2; } }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:16]: (style) Expression is always false because 'else if' condition matches previous condition at line 2. [multiCondition]\n", errout_str()); check("void f(int a, int &b) {\n" " if (a == 1) { b = 1; }\n" " else { if (a == 2) { b = 2; }\n" " else { if (a == 1) { b = 3; } } }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:18]: (style) Expression is always false because 'else if' condition matches previous condition at line 2. [multiCondition]\n", errout_str()); check("void f(int a, int &b) {\n" " if (a == 1) { b = 1; }\n" " else { if (a == 2) { b = 2; }\n" " else { if (a == 2) { b = 3; } } }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:18]: (style) Expression is always false because 'else if' condition matches previous condition at line 3. [multiCondition]\n", errout_str()); check("void f(int a, int &b) {\n" " if (a++) { b = 1; }\n" " else { if (a++) { b = 2; }\n" " else { if (a++) { b = 3; } } }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int a, int &b) {\n" " if (!strtok(NULL, \" \")) { b = 1; }\n" " else { if (!strtok(NULL, \" \")) { b = 2; } }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); { check("void f(Class &c) {\n" " if (c.dostuff() == 3) {}\n" " else { if (c.dostuff() == 3) {} }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(const Class &c) {\n" " if (c.dostuff() == 3) {}\n" " else { if (c.dostuff() == 3) {} }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:28]: (style) Expression is always false because 'else if' condition matches previous condition at line 2. [multiCondition]\n", errout_str()); } @@ -644,13 +648,13 @@ class TestCondition : public TestFixture { " x = x / 2;\n" " if (x < 100) { b = 1; }\n" " else { x = x / 2; if (x < 100) { b = 2; } }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int64_t i) {\n" " if(i == 0x02e2000000 || i == 0xa0c6000000)\n" " foo(i);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // ticket 3689 ( avoid false positive ) @@ -672,13 +676,13 @@ class TestCondition : public TestFixture { " return -1;\n" " }\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(WIDGET *widget) {\n" " if (dynamic_cast(widget)){}\n" " else if (dynamic_cast(widget)){}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("class B { virtual void v() {} };\n" // #11037 @@ -695,67 +699,67 @@ class TestCondition : public TestFixture { check("void f(int x) {\n" // #6482 " if (x & 1) {}\n" " else if (x == 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" " if (x & 15) {}\n" " else if (x == 40) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:14]: (style) Expression is always false because 'else if' condition matches previous condition at line 2. [multiCondition]\n", errout_str()); check("void f(int x) {\n" " if (x == sizeof(double)) {}\n" - " else { if (x == sizeof(long double)) {} }" - "}"); + " else { if (x == sizeof(long double)) {} }\n" + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" " if (x & 0x08) {}\n" " else if (x & 0xF8) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" " if (x & 0xF8) {}\n" " else if (x & 0x08) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:14]: (style) Expression is always false because 'else if' condition matches previous condition at line 2. [multiCondition]\n", errout_str()); check("void f(bool a, bool b) {\n" " if(a && b){}\n" " else if( !!b && !!a){}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:17]: (style) Expression is always false because 'else if' condition matches previous condition at line 2. [multiCondition]\n", errout_str()); check("void f(bool a, bool b) {\n" " if(a && b){}\n" " else if( !!b && a){}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:17]: (style) Expression is always false because 'else if' condition matches previous condition at line 2. [multiCondition]\n", errout_str()); check("void f(bool a, bool b) {\n" " if(a && b){}\n" " else if( b && !!a){}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:15]: (style) Expression is always false because 'else if' condition matches previous condition at line 2. [multiCondition]\n", errout_str()); check("void f(bool a, bool b) {\n" " if(a && b){}\n" " else if( b && !(!a)){}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:15]: (style) Expression is always false because 'else if' condition matches previous condition at line 2. [multiCondition]\n", errout_str()); check("void f(bool a, bool b) {\n" " if(a && b){}\n" " else if( !!b && !(!a)){}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:17]: (style) Expression is always false because 'else if' condition matches previous condition at line 2. [multiCondition]\n", errout_str()); check("void f(bool a, bool b) {\n" " if(a && b){}\n" " else if( !!(b) && !!(a+b)){}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #8168 @@ -767,7 +771,7 @@ class TestCondition : public TestFixture { "void TestFunction(int value) {\n" " if ( value & (int)Value1 ) {}\n" " else if ( value & (int)Value2 ) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(size_t x) {\n" @@ -789,7 +793,7 @@ class TestCondition : public TestFixture { check("void f(int x) {\n" " if (x) {}\n" " else if (!x) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:14]: style: Expression is always true because 'else if' condition is opposite to previous condition at line 2. [multiCondition]\n" "[test.cpp:2:9]: note: first condition\n" "[test.cpp:3:14]: note: else if condition is opposite to first condition\n", errout_str()); @@ -798,7 +802,7 @@ class TestCondition : public TestFixture { " int y = x;\n" " if (x) {}\n" " else if (!y) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:14]: style: Expression is always true because 'else if' condition is opposite to previous condition at line 3. [multiCondition]\n" "[test.cpp:2:13]: note: 'y' is assigned value 'x' here.\n" "[test.cpp:3:9]: note: first condition\n" @@ -809,31 +813,31 @@ class TestCondition : public TestFixture { check("bool f(int x) {\n" " bool b = x | 0x02;\n" " return b;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:16]: (warning) Result of operator '|' is always true if one operand is non-zero. Did you intend to use '&'? [badBitmaskCheck]\n", errout_str()); check("bool f(int x) {\n" " bool b = 0x02 | x;\n" " return b;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:19]: (warning) Result of operator '|' is always true if one operand is non-zero. Did you intend to use '&'? [badBitmaskCheck]\n", errout_str()); check("int f(int x) {\n" " int b = x | 0x02;\n" " return b;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("bool f(int x) {\n" " bool b = x & 0x02;\n" " return b;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("bool f(int x) {\n" " if(x | 0x02)\n" " return b;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:10]: (warning) Result of operator '|' is always true if one operand is non-zero. Did you intend to use '&'? [badBitmaskCheck]\n", errout_str()); check("bool f(int x) {\n" @@ -841,27 +845,27 @@ class TestCondition : public TestFixture { " if(b) y = 0;\n" " if(x | y)\n" " return b;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("bool f(int x) {\n" " foo(a && (x | 0x02));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:17]: (warning) Result of operator '|' is always true if one operand is non-zero. Did you intend to use '&'? [badBitmaskCheck]\n", errout_str()); check("int f(int x) {\n" " return (x | 0x02) ? 0 : 5;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:15]: (warning) Result of operator '|' is always true if one operand is non-zero. Did you intend to use '&'? [badBitmaskCheck]\n", errout_str()); check("int f(int x) {\n" " return x ? (x | 0x02) : 5;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("bool f(int x) {\n" " return x | 0x02;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:14]: (warning) Result of operator '|' is always true if one operand is non-zero. Did you intend to use '&'? [badBitmaskCheck]\n", errout_str()); check("bool f(int x) {\n" @@ -869,19 +873,19 @@ class TestCondition : public TestFixture { " return x | 0x02;\n" " }\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:14]: (warning) Result of operator '|' is always true if one operand is non-zero. Did you intend to use '&'? [badBitmaskCheck]\n", errout_str()); check("const bool f(int x) {\n" " return x | 0x02;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:14]: (warning) Result of operator '|' is always true if one operand is non-zero. Did you intend to use '&'? [badBitmaskCheck]\n", errout_str()); check("struct F {\n" " static const bool f(int x) {\n" " return x | 0x02;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:16]: (warning) Result of operator '|' is always true if one operand is non-zero. Did you intend to use '&'? [badBitmaskCheck]\n", errout_str()); check("struct F {\n" @@ -889,12 +893,12 @@ class TestCondition : public TestFixture { "};\n" "F::b_t f(int x) {\n" " return x | 0x02;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:12]: (warning) Result of operator '|' is always true if one operand is non-zero. Did you intend to use '&'? [badBitmaskCheck]\n", errout_str()); check("int f(int x) {\n" " return x | 0x02;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void create_rop_masks_4( rop_mask_bits *bits) {\n" @@ -902,7 +906,7 @@ class TestCondition : public TestFixture { "BYTE *and_bits = bits->and;\n" "rop_mask *rop_mask;\n" "and_bits[mask_offset] |= (rop_mask->and & 0x0f);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(unsigned a, unsigned b) {\n" @@ -910,7 +914,7 @@ class TestCondition : public TestFixture { " if (cmd1 | a) {\n" " if (b == 0x0C) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int i) {\n" // #11082 @@ -938,7 +942,7 @@ class TestCondition : public TestFixture { "#if BAR_ENABLED\n" " FEATURE_BAR |\n" "#endif\n" - " 0;"); + " 0;\n"); ASSERT_EQUALS("", errout_str()); check("enum precedence { PC0, UNARY };\n" @@ -957,84 +961,84 @@ class TestCondition : public TestFixture { check("void f(int x) {\n" " if ((x != 1) || (x != 3))\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:18]: (warning) Logical disjunction always evaluates to true: x != 1 || x != 3. [incorrectLogicOperator]\n", errout_str()); check("void f(int x) {\n" " if (1 != x || 3 != x)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:16]: (warning) Logical disjunction always evaluates to true: x != 1 || x != 3. [incorrectLogicOperator]\n", errout_str()); check("void f(int x) {\n" " if (x<0 && !x) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11]: (warning) Logical conjunction always evaluates to false: x < 0 && !x. [incorrectLogicOperator]\n", errout_str()); check("void f(int x) {\n" " if (x==0 && x) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:12]: (warning) Logical conjunction always evaluates to false: x == 0 && x. [incorrectLogicOperator]\n", errout_str()); check("void f(int x) {\n" // ast.. " if (y == 1 && x == 1 && x == 7) { }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:26]: (warning) Logical conjunction always evaluates to false: x == 1 && x == 7. [incorrectLogicOperator]\n", errout_str()); check("void f(int x, int y) {\n" " if (x != 1 || y != 1)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x, int y) {\n" " if ((y == 1) && (x != 1) || (x != 3))\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x, int y) {\n" " if ((x != 1) || (x != 3) && (y == 1))\n" " a++;\n" - "}" + "}\n" ); ASSERT_EQUALS("[test.cpp:2:12] -> [test.cpp:2:24]: (style) Condition 'x!=3' is always true [knownConditionTrueFalse]\n", errout_str()); check("void f(int x) {\n" " if ((x != 1) && (x != 3))\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" " if ((x == 1) || (x == 3))\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x, int y) {\n" " if ((x != 1) || (y != 3))\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x, int y) {\n" " if ((x != hotdog) || (y != hotdog))\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x, int y) {\n" " if ((x != 5) || (y != 5))\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" " if ((x != 5) || (x != 6))\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:18]: (warning) Logical disjunction always evaluates to true: x != 5 || x != 6. [incorrectLogicOperator]\n", errout_str()); check("void f(unsigned int a, unsigned int b, unsigned int c) {\n" @@ -1043,7 +1047,7 @@ class TestCondition : public TestFixture { " return true;\n" " }\n" " return false;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:23] -> [test.cpp:2:35]: (style) Condition 'c!=a' is always false [knownConditionTrueFalse]\n", errout_str()); } @@ -1051,224 +1055,224 @@ class TestCondition : public TestFixture { check("void f(float x) {\n" " if ((x == 1) && (x == 1.0))\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" " if ((x == 1) && (x == 0x00000001))\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:12] -> [test.cpp:2:24]: (style) Condition 'x==0x00000001' is always true [knownConditionTrueFalse]\n", errout_str()); check("void f(int x) {\n" " if (x == 1 && x == 3)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:16]: (warning) Logical conjunction always evaluates to false: x == 1 && x == 3. [incorrectLogicOperator]\n", errout_str()); check("void f(int x) {\n" " if (x == 1.0 && x == 3.0)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // float comparisons with == and != are not checked right now - such comparison is a bad idea check("void f(float x) {\n" " if (x == 1 && x == 1.0)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void bar(float f) {\n" // #5246 " if ((f > 0) && (f < 1)) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" " if (x < 1 && x > 1)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:15]: (warning) Logical conjunction always evaluates to false: x < 1 && x > 1. [incorrectLogicOperator]\n", errout_str()); check("void f(int x) {\n" " if (x < 1.0 && x > 1.0)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:17]: (warning) Logical conjunction always evaluates to false: x < 1.0 && x > 1.0. [incorrectLogicOperator]\n", errout_str()); check("void f(int x) {\n" " if (x < 1 && x > 1.0)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:15]: (warning) Logical conjunction always evaluates to false: x < 1 && x > 1.0. [incorrectLogicOperator]\n", errout_str()); check("void f(int x) {\n" " if (x >= 1.0 && x <= 1.001)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" " if (x < 1 && x > 3)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:15]: (warning) Logical conjunction always evaluates to false: x < 1 && x > 3. [incorrectLogicOperator]\n", errout_str()); check("void f(float x) {\n" " if (x < 1.0 && x > 3.0)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:17]: (warning) Logical conjunction always evaluates to false: x < 1.0 && x > 3.0. [incorrectLogicOperator]\n", errout_str()); check("void f(int x) {\n" " if (1 > x && 3 < x)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:15]: (warning) Logical conjunction always evaluates to false: x < 1 && x > 3. [incorrectLogicOperator]\n", errout_str()); check("void f(int x) {\n" " if (x < 3 && x > 1)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" " if (x > 3 || x < 10)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:15]: (warning) Logical disjunction always evaluates to true: x > 3 || x < 10. [incorrectLogicOperator]\n", errout_str()); check("void f(int x) {\n" " if (x >= 3 || x <= 10)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:16]: (warning) Logical disjunction always evaluates to true: x >= 3 || x <= 10. [incorrectLogicOperator]\n", errout_str()); check("void f(int x) {\n" " if (x >= 3 || x < 10)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:16]: (warning) Logical disjunction always evaluates to true: x >= 3 || x < 10. [incorrectLogicOperator]\n", errout_str()); check("void f(int x) {\n" " if (x > 3 || x <= 10)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:15]: (warning) Logical disjunction always evaluates to true: x > 3 || x <= 10. [incorrectLogicOperator]\n", errout_str()); check("void f(int x) {\n" " if (x > 3 || x < 3)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" " if (x >= 3 || x <= 3)\n" " a++;\n" - "}" + "}\n" ); ASSERT_EQUALS("[test.cpp:2:16]: (warning) Logical disjunction always evaluates to true: x >= 3 || x <= 3. [incorrectLogicOperator]\n", errout_str()); check("void f(int x) {\n" " if (x >= 3 || x < 3)\n" " a++;\n" - "}" + "}\n" ); ASSERT_EQUALS("[test.cpp:2:16]: (warning) Logical disjunction always evaluates to true: x >= 3 || x < 3. [incorrectLogicOperator]\n", errout_str()); check("void f(int x) {\n" " if (x > 3 || x <= 3)\n" " a++;\n" - "}" + "}\n" ); ASSERT_EQUALS("[test.cpp:2:15]: (warning) Logical disjunction always evaluates to true: x > 3 || x <= 3. [incorrectLogicOperator]\n", errout_str()); check("void f(int x) {\n" " if((x==3) && (x!=4))\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:14]: (style) Redundant condition: The condition 'x != 4' is redundant since 'x == 3' is sufficient. [redundantCondition]\n", errout_str()); check("void f(const std::string &s) {\n" // #8860 " const std::size_t p = s.find(\"42\");\n" " const std::size_t * const ptr = &p;\n" " if(p != std::string::npos && p == 0 && *ptr != 1){;}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:36] -> [test.cpp:4:49]: (style) Condition '*ptr!=1' is always true [knownConditionTrueFalse]\n", errout_str()); check("void f(int x) {\n" " if ((x!=4) && (x==3))\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:16]: (style) Redundant condition: The condition 'x != 4' is redundant since 'x == 3' is sufficient. [redundantCondition]\n", errout_str()); check("void f(int x) {\n" " if ((x==3) || (x!=4))\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:16]: (style) Redundant condition: The condition 'x == 3' is redundant since 'x != 4' is sufficient. [redundantCondition]\n", errout_str()); check("void f(int x) {\n" " if ((x!=4) || (x==3))\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:16]: (style) Redundant condition: The condition 'x == 3' is redundant since 'x != 4' is sufficient. [redundantCondition]\n", errout_str()); check("void f(int x) {\n" " if ((x==3) && (x!=3))\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:16]: (warning) Logical conjunction always evaluates to false: x == 3 && x != 3. [incorrectLogicOperator]\n", errout_str()); check("void f(int x) {\n" " if ((x==6) || (x!=6))\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:16]: (warning) Logical disjunction always evaluates to true: x == 6 || x != 6. [incorrectLogicOperator]\n", errout_str()); check("void f(int x) {\n" " if (x > 10 || x < 3)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" " if (x > 5 && x == 1)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:15]: (warning) Logical conjunction always evaluates to false: x > 5 && x == 1. [incorrectLogicOperator]\n", errout_str()); check("void f(int x) {\n" " if (x > 5 && x == 6)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:15]: (style) Redundant condition: The condition 'x > 5' is redundant since 'x == 6' is sufficient. [redundantCondition]\n", errout_str()); // #3419 check("void f() {\n" " if ( &q != &a && &q != &b ) { }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #3676 check("void f(int m_x2, int w, int x) {\n" " if (x + w - 1 > m_x2 || m_x2 < 0 )\n" " m_x2 = x + w - 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(float x) {\n" // x+1 => x " if (x <= 1.0e20 && x >= -1.0e20) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(float x) {\n" // x+1 => x " if (x >= 1.0e20 && x <= 1.0e21) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(float x) {\n" // x+1 => x " if (x <= -1.0e20 && x >= -1.0e21) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1278,7 +1282,7 @@ class TestCondition : public TestFixture { " c = x < 1 && x == 3;\n" " d = x >= 5 && x == 1;\n" " e = x <= 1 && x == 3;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:15]: (warning) Logical conjunction always evaluates to false: x > 5 && x == 1. [incorrectLogicOperator]\n" "[test.cpp:3:15]: (warning) Logical conjunction always evaluates to false: x < 1 && x == 3. [incorrectLogicOperator]\n" "[test.cpp:4:16]: (warning) Logical conjunction always evaluates to false: x >= 5 && x == 1. [incorrectLogicOperator]\n" @@ -1289,7 +1293,7 @@ class TestCondition : public TestFixture { check("#define ZERO 0\n" "void f(int x) {\n" " if (x && x != ZERO) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int N) {\n" // #9789 @@ -1304,7 +1308,7 @@ class TestCondition : public TestFixture { void incorrectLogicOperator5() { // complex expressions check("void f(int x) {\n" " if (x+3 > 2 || x+3 < 10) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:15]: (warning) Logical disjunction always evaluates to true: x+3 > 2 || x+3 < 10. [incorrectLogicOperator]\n", errout_str()); } @@ -1312,51 +1316,51 @@ class TestCondition : public TestFixture { const Settings s = settingsBuilder(settings0).certainty(Certainty::inconclusive).build(); check("void f(char x) {\n" " if (x == '1' || x == '2') {}\n" - "}", s); + "}\n", s); ASSERT_EQUALS("", errout_str()); check("void f(char x) {\n" " if (x == '1' && x == '2') {}\n" - "}", s); + "}\n", s); ASSERT_EQUALS("[test.cpp:2:16]: (warning) Logical conjunction always evaluates to false: x == '1' && x == '2'. [incorrectLogicOperator]\n", errout_str()); check("int f(char c) {\n" " return (c >= 'a' && c <= 'z');\n" - "}", s); + "}\n", s); ASSERT_EQUALS("", errout_str()); check("int f(char c) {\n" " return (c <= 'a' && c >= 'z');\n" - "}", s); + "}\n", s); ASSERT_EQUALS("[test.cpp:2:20]: (warning, inconclusive) Logical conjunction always evaluates to false: c <= 'a' && c >= 'z'. [incorrectLogicOperator]\n", errout_str()); check("int f(char c) {\n" " return (c <= 'a' && c >= 'z');\n" - "}"); // TODO: use s? - ASSERT_EQUALS("[test.cpp:2:13] -> [test.cpp:2:25]: (style) Return value 'c>='z'' is always false [knownConditionTrueFalse]\n", errout_str()); + "}\n"); // TODO: use s? + ASSERT_EQUALS("[test.cpp:2:13] -> [test.cpp:2:25]: (style) Condition 'c>='z'' is always false [knownConditionTrueFalse]\n", errout_str()); } void incorrectLogicOperator7() { // opposite expressions check("void f(int i) {\n" " if (i || !i) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9]: (warning) Logical disjunction always evaluates to true: i || !(i). [incorrectLogicOperator]\n", errout_str()); check("void f(int a, int b) {\n" " if (a>b || a<=b) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11]: (warning) Logical disjunction always evaluates to true: a > b || a <= b. [incorrectLogicOperator]\n", errout_str()); check("void f(int a, int b) {\n" " if (a>b || a T icdf( const T uniform ) {\n" " if ((0(obj));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1391,21 +1395,21 @@ class TestCondition : public TestFixture { "void f(Type_t t) {\n" " if ((t == A) && (t == B))\n" " {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:18]: (warning) Logical conjunction always evaluates to false: t == 0 && t == 1. [incorrectLogicOperator]\n", errout_str()); } void incorrectLogicOperator11() { - check("void foo(int i, const int n) { if ( i < n && i == n ) {} }"); + check("void foo(int i, const int n) { if ( i < n && i == n ) {} }\n"); ASSERT_EQUALS("[test.cpp:1:43]: (warning) Logical conjunction always evaluates to false: i < n && i == n. [incorrectLogicOperator]\n", errout_str()); - check("void foo(int i, const int n) { if ( i > n && i == n ) {} }"); + check("void foo(int i, const int n) { if ( i > n && i == n ) {} }\n"); ASSERT_EQUALS("[test.cpp:1:43]: (warning) Logical conjunction always evaluates to false: i > n && i == n. [incorrectLogicOperator]\n", errout_str()); - check("void foo(int i, const int n) { if ( i == n && i > n ) {} }"); + check("void foo(int i, const int n) { if ( i == n && i > n ) {} }\n"); ASSERT_EQUALS("[test.cpp:1:44]: (warning) Logical conjunction always evaluates to false: i == n && i > n. [incorrectLogicOperator]\n", errout_str()); - check("void foo(int i, const int n) { if ( i == n && i < n ) {} }"); + check("void foo(int i, const int n) { if ( i == n && i < n ) {} }\n"); ASSERT_EQUALS("[test.cpp:1:44]: (warning) Logical conjunction always evaluates to false: i == n && i < n. [incorrectLogicOperator]\n", errout_str()); } @@ -1419,7 +1423,7 @@ class TestCondition : public TestFixture { " y.f();\n" " if (a > x && a < y)\n" " return;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:5:9] -> [test.cpp:6:9] -> [test.cpp:8:13]: (warning) Logical conjunction always evaluates to false: a > x && a < y. [incorrectLogicOperator]\n", errout_str()); @@ -1433,7 +1437,7 @@ class TestCondition : public TestFixture { " y.f();\n" " if (a > x && a < y)\n" " return;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(A a, A b) {\n" @@ -1442,7 +1446,7 @@ class TestCondition : public TestFixture { " y.f();\n" " if (a > x && a < y)\n" " return;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(A a, A b) {\n" @@ -1451,7 +1455,7 @@ class TestCondition : public TestFixture { " y.f();\n" " if (a > x && a < y)\n" " return;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:2:15] -> [test.cpp:3:15] -> [test.cpp:5:13]: (warning) Logical conjunction always evaluates to false: a > x && a < y. [incorrectLogicOperator]\n", errout_str()); @@ -1465,7 +1469,7 @@ class TestCondition : public TestFixture { " y.f();\n" " if (a > x && a < y)\n" " return;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:9]: (style) Condition 'a>x' is always false [knownConditionTrueFalse]\n" "[test.cpp:8:18]: (style) Condition 'a x && a < y)\n" " return;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:9]: (style) Condition 'a>x' is always false [knownConditionTrueFalse]\n", errout_str()); check("void foo(A a) {\n" @@ -1488,7 +1492,7 @@ class TestCondition : public TestFixture { " y.f();\n" " if (a > x && a < y)\n" " return;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:9]: (style) Condition 'a>x' is always false [knownConditionTrueFalse]\n", errout_str()); check("void foo(A a) {\n" @@ -1497,7 +1501,7 @@ class TestCondition : public TestFixture { " y.f();\n" " if (a > x && a < y)\n" " return;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:9]: (style) Condition 'a>x' is always false [knownConditionTrueFalse]\n" "[test.cpp:5:18]: (style) Condition 'a [test.cpp:3:18]: (warning) Logical conjunction always evaluates to false: v == 1 && x == 2. [incorrectLogicOperator]\n", errout_str()); check("void f2(const int *v) {\n" " const int *x=v;\n" " if ((*v == 1) && (*x == 2)) {;}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:18] -> [test.cpp:3:19]: (warning) Logical conjunction always evaluates to false: *(v) == 1 && *(x) == 2. [incorrectLogicOperator]\n", errout_str()); } @@ -1721,7 +1725,7 @@ class TestCondition : public TestFixture { " if (f && g)\n" " ;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:200:11] -> [test.cpp:200:16]: (style) Condition 'g' is always true [knownConditionTrueFalse]\n", errout_str()); } @@ -1755,6 +1759,17 @@ class TestCondition : public TestFixture { ASSERT_EQUALS("", errout_str()); } + void incorrectLogicOperator18() { // #12282 + // long double literals out of the range of double are different values + check("int f(long double x) {\n" + " return x > 1e400L && x < 1e500L;\n" + "}\n" + "int g() {\n" + " return 1e400L < 1e500L;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + } + void incorrectLogicOperator17() { // #12471 check("struct R {\n" " void set() { i = 1; }\n" @@ -1777,31 +1792,31 @@ class TestCondition : public TestFixture { check("void f(void) {\n" // #8892 " const char c[1] = { \'x\' }; \n" " if(c[0] == \'x\'){;}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:13]: (style) Condition 'c[0]=='x'' is always true [knownConditionTrueFalse]\n", errout_str()); check("void f(int x) {\n" " if (x > 5 && x != 1)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:15]: (style) Redundant condition: The condition 'x != 1' is redundant since 'x > 5' is sufficient. [redundantCondition]\n", errout_str()); check("void f(int x) {\n" " if (x > 5 && x != 6)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" " if ((x > 5) && (x != 1))\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:17]: (style) Redundant condition: The condition 'x != 1' is redundant since 'x > 5' is sufficient. [redundantCondition]\n", errout_str()); check("void f(int x) {\n" " if ((x > 5) && (x != 6))\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x, bool& b) {\n" @@ -1809,7 +1824,7 @@ class TestCondition : public TestFixture { " c = x < 5 || x == 4;\n" " d = x >= 3 || x == 4;\n" " e = x <= 5 || x == 4;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:15]: (style) Redundant condition: The condition 'x == 4' is redundant since 'x > 3' is sufficient. [redundantCondition]\n" "[test.cpp:3:15]: (style) Redundant condition: The condition 'x == 4' is redundant since 'x < 5' is sufficient. [redundantCondition]\n" "[test.cpp:4:16]: (style) Redundant condition: The condition 'x == 4' is redundant since 'x >= 3' is sufficient. [redundantCondition]\n" @@ -1821,7 +1836,7 @@ class TestCondition : public TestFixture { " c = x < 1 || x != 3;\n" " d = x >= 5 || x != 1;\n" " e = x <= 1 || x != 3;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:15]: (style) Redundant condition: The condition 'x > 5' is redundant since 'x != 1' is sufficient. [redundantCondition]\n" "[test.cpp:3:15]: (style) Redundant condition: The condition 'x < 1' is redundant since 'x != 3' is sufficient. [redundantCondition]\n" "[test.cpp:4:16]: (style) Redundant condition: The condition 'x >= 5' is redundant since 'x != 1' is sufficient. [redundantCondition]\n" @@ -1833,7 +1848,7 @@ class TestCondition : public TestFixture { " c = x > 5 || x > 6;\n" " d = x < 6 && x < 5;\n" " e = x < 5 || x < 6;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:15]: (style) Redundant condition: The condition 'x > 5' is redundant since 'x > 6' is sufficient. [redundantCondition]\n" "[test.cpp:3:15]: (style) Redundant condition: The condition 'x > 6' is redundant since 'x > 5' is sufficient. [redundantCondition]\n" "[test.cpp:4:15]: (style) Redundant condition: The condition 'x < 6' is redundant since 'x < 5' is sufficient. [redundantCondition]\n" @@ -1845,7 +1860,7 @@ class TestCondition : public TestFixture { " c = x > 5.5 || x > 6.5;\n" " d = x < 6.5 && x < 5.5;\n" " e = x < 5.5 || x < 6.5;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:17]: (style) Redundant condition: The condition 'x > 5.5' is redundant since 'x > 6.5' is sufficient. [redundantCondition]\n" "[test.cpp:3:17]: (style) Redundant condition: The condition 'x > 6.5' is redundant since 'x > 5.5' is sufficient. [redundantCondition]\n" "[test.cpp:4:17]: (style) Redundant condition: The condition 'x < 6.5' is redundant since 'x < 5.5' is sufficient. [redundantCondition]\n" @@ -1863,49 +1878,49 @@ class TestCondition : public TestFixture { check("void f(int x) {\n" " if (x < 1 && x > 3)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:15]: (warning) Logical conjunction always evaluates to false: x < 1 && x > 3. [incorrectLogicOperator]\n", errout_str()); check("void f(int x) {\n" " if (1 > x && x > 3)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:15]: (warning) Logical conjunction always evaluates to false: x < 1 && x > 3. [incorrectLogicOperator]\n", errout_str()); check("void f(int x) {\n" " if (x < 1 && 3 < x)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:15]: (warning) Logical conjunction always evaluates to false: x < 1 && x > 3. [incorrectLogicOperator]\n", errout_str()); check("void f(int x) {\n" " if (1 > x && 3 < x)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:15]: (warning) Logical conjunction always evaluates to false: x < 1 && x > 3. [incorrectLogicOperator]\n", errout_str()); check("void f(int x) {\n" " if (x > 3 && x < 1)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:15]: (warning) Logical conjunction always evaluates to false: x > 3 && x < 1. [incorrectLogicOperator]\n", errout_str()); check("void f(int x) {\n" " if (3 < x && x < 1)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:15]: (warning) Logical conjunction always evaluates to false: x > 3 && x < 1. [incorrectLogicOperator]\n", errout_str()); check("void f(int x) {\n" " if (x > 3 && 1 > x)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:15]: (warning) Logical conjunction always evaluates to false: x > 3 && x < 1. [incorrectLogicOperator]\n", errout_str()); check("void f(int x) {\n" " if (3 < x && 1 > x)\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:15]: (warning) Logical conjunction always evaluates to false: x > 3 && x < 1. [incorrectLogicOperator]\n", errout_str()); } @@ -1915,7 +1930,7 @@ class TestCondition : public TestFixture { " b2 = a % c == 100000;\n" " b3 = a % 5 == c;\n" " return a % 5 == 5-p;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("bool f(bool& b1, bool& b2, bool& b3, bool& b4, bool& b5) {\n" @@ -1925,7 +1940,7 @@ class TestCondition : public TestFixture { " b4 = a % 5 != 5;\n" " b5 = a % 5 >= 5;\n" " return a % 5 > 5;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:2:16]: (warning) Comparison of modulo result is predetermined, because it is always less than 5. [moduloAlwaysTrueFalse]\n" "[test.cpp:3:16]: (warning) Comparison of modulo result is predetermined, because it is always less than 5. [moduloAlwaysTrueFalse]\n" @@ -1939,7 +1954,7 @@ class TestCondition : public TestFixture { " b1 = bar() % 5 < 889;\n" " if(x[593] % 5 <= 5)\n" " b2 = x.a % 5 == 5;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:2:20]: (warning) Comparison of modulo result is predetermined, because it is always less than 5. [moduloAlwaysTrueFalse]\n" "[test.cpp:3:19]: (warning) Comparison of modulo result is predetermined, because it is always less than 5. [moduloAlwaysTrueFalse]\n" @@ -1949,7 +1964,7 @@ class TestCondition : public TestFixture { check("void f() {\n" " if (a % 2 + b % 2 == 2)\n" " foo();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1958,21 +1973,21 @@ class TestCondition : public TestFixture { " if(a==b)\n" " if(a!=b)\n" " cout << a;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9] -> [test.cpp:3:13]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); check("bool foo(int a, int b) {\n" " if(a==b)\n" " return a!=b;\n" " return false;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9] -> [test.cpp:3:17]: (warning) Opposite inner 'return' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); check("void foo(int a, int b) {\n" " if(a==b)\n" " if(b!=a)\n" " cout << a;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9] -> [test.cpp:3:13]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); check("void foo(int a) {\n" @@ -1982,7 +1997,7 @@ class TestCondition : public TestFixture { " else\n" " cout << 100;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:10] -> [test.cpp:3:14]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); // #4186 @@ -1993,7 +2008,7 @@ class TestCondition : public TestFixture { " else\n" " cout << 100;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // 4170 @@ -2012,7 +2027,7 @@ class TestCondition : public TestFixture { " }\n" " void next();\n" " const char *tok;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int i)\n" @@ -2023,7 +2038,7 @@ class TestCondition : public TestFixture { " cout << a;\n" " }\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int& i) {\n" @@ -2035,7 +2050,7 @@ class TestCondition : public TestFixture { " if(i<5) {\n" " }\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int& i);\n" @@ -2046,7 +2061,7 @@ class TestCondition : public TestFixture { " if(i<5) {\n" " }\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int i);\n" @@ -2056,7 +2071,7 @@ class TestCondition : public TestFixture { " if(i<5) {\n" " }\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:9] -> [test.cpp:5:13]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); check("void foo(const int &i);\n" @@ -2066,7 +2081,7 @@ class TestCondition : public TestFixture { " if(i<5) {\n" " }\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:9] -> [test.cpp:5:13]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); check("void foo(int i);\n" @@ -2077,7 +2092,7 @@ class TestCondition : public TestFixture { " if(i<5) {\n" " }\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:9] -> [test.cpp:6:13]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); check("class C { void f(int &i) const; };\n" // #7028 - variable is changed by const method @@ -2086,7 +2101,7 @@ class TestCondition : public TestFixture { " c.f(i);\n" " if (i != 5) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // see linux revision 1f80c0cc @@ -2100,7 +2115,7 @@ class TestCondition : public TestFixture { " err = rc;\n" " }\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:11] -> [test.cpp:7:14]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); @@ -2111,7 +2126,7 @@ class TestCondition : public TestFixture { " array[0] += 5;\n" " if (array[0] > 2) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #6227 - FP caused by simplifications of casts and known variables @@ -2120,7 +2135,7 @@ class TestCondition : public TestFixture { " B *b = dynamic_cast(a);\n" " if(!b) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int a) {\n" @@ -2128,7 +2143,7 @@ class TestCondition : public TestFixture { " int b = a;\n" " if(!b) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:16] -> [test.cpp:2:7] -> [test.cpp:4:11]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); check("void foo(unsigned u) {\n" @@ -2138,7 +2153,7 @@ class TestCondition : public TestFixture { " u = x;\n" " }\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #8186 @@ -2146,7 +2161,7 @@ class TestCondition : public TestFixture { " for (int i=0;i<4;i++) {\n" " if (i==5) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:17] -> [test.cpp:3:10]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); // #8938 @@ -2155,7 +2170,7 @@ class TestCondition : public TestFixture { " GetActiveCell(&(upperleft.Col), &(upperleft.Row));\n" " if (upperleft.Row == -1) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #9702 @@ -2168,7 +2183,7 @@ class TestCondition : public TestFixture { " }\n" " bool IsSet() const { return m_value; }\n" " bool m_value = false;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // #12725 @@ -2178,7 +2193,7 @@ class TestCondition : public TestFixture { " b = g();\n" " return b;\n" "}\n"); - ASSERT_EQUALS("[test.cpp:2:9] -> [test.cpp:3:16]: (style) Return value '!b' is always false [knownConditionTrueFalse]\n", errout_str()); + ASSERT_EQUALS("", errout_str()); } void oppositeInnerConditionPointers() { @@ -2190,7 +2205,7 @@ class TestCondition : public TestFixture { " err = rc;\n" " }\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void Fred::f() {\n" // daca: ace @@ -2198,7 +2213,7 @@ class TestCondition : public TestFixture { " this->next_ = n;\n" " if (this->next_ != map_man_->table_) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void test(float *f) {\n" // #7405 @@ -2206,13 +2221,13 @@ class TestCondition : public TestFixture { " (*f) += 0.1f;\n" " if(*f<10) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int * f(int * x, int * y) {\n" " if(!x) return x;\n" " return y;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2224,7 +2239,7 @@ class TestCondition : public TestFixture { " fred.dostuff();\n" " if (!fred.isValid()) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:19] -> [test.cpp:5:9]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); check("class Fred { public: bool isValid() const; void dostuff() const; };\n" @@ -2234,7 +2249,7 @@ class TestCondition : public TestFixture { " fred.dostuff();\n" // <- dostuff() is const, warn " if (!fred.isValid()) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:19] -> [test.cpp:6:9]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); check("void f() {\n" @@ -2243,7 +2258,7 @@ class TestCondition : public TestFixture { " fred.dostuff();\n" " if (!fred.isValid()) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #6385 "crash in Variable::getFlag()" @@ -2254,14 +2269,14 @@ class TestCondition : public TestFixture { " qApp->removeTranslator(mTranslator);\n" " }\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // just don't crash... check("bool f(std::ofstream &CFileStream) {\n" // #8198 " if(!CFileStream.good()) { return; }\n" " CFileStream << \"abc\";\n" " if (!CFileStream.good()) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2272,7 +2287,7 @@ class TestCondition : public TestFixture { " x = do_something();\n" " if (x != -1) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #5750 - another fp when undeclared variable is used @@ -2281,7 +2296,7 @@ class TestCondition : public TestFixture { " r += 3;\n" " if (r > w) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #6574 - another fp when undeclared variable is used @@ -2290,7 +2305,7 @@ class TestCondition : public TestFixture { " i++;\n" " if(!i) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // undeclared array @@ -2299,7 +2314,7 @@ class TestCondition : public TestFixture { " a[x] -= dt;\n" " if (a[x] < 0) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #6313 - false positive: opposite conditions in nested if blocks when condition changed @@ -2309,7 +2324,7 @@ class TestCondition : public TestFixture { " if(!var){}\n" " else {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // daca hyphy @@ -2318,7 +2333,7 @@ class TestCondition : public TestFixture { " rec.Delete(i);\n" " if (rec.lLength!=0) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2332,7 +2347,7 @@ class TestCondition : public TestFixture { " doStuff(&s);\n" " if (hasFailed) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:7]: (style) Condition '!hasFailed' is always true [knownConditionTrueFalse]\n", errout_str()); } @@ -2343,7 +2358,7 @@ class TestCondition : public TestFixture { " if (x<4) {\n" " if (x==5) {}\n" // <- Warning " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:8] -> [test.cpp:4:10]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); check("void f(int x) {\n" @@ -2351,14 +2366,14 @@ class TestCondition : public TestFixture { " if (x<4) {\n" " if (x!=5) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:8] -> [test.cpp:4:10]: (style) Condition 'x!=5' is always true [knownConditionTrueFalse]\n", errout_str()); check("void f(int x) {\n" "\n" " if (x<4) {\n" " if (x>5) {}\n" // <- Warning " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:8] -> [test.cpp:4:10]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); check("void f(int x) {\n" @@ -2366,7 +2381,7 @@ class TestCondition : public TestFixture { " if (x<4) {\n" " if (x>=5) {}\n" // <- Warning " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:8] -> [test.cpp:4:10]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); check("void f(int x) {\n" @@ -2374,14 +2389,14 @@ class TestCondition : public TestFixture { " if (x<4) {\n" " if (x<5) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:8] -> [test.cpp:4:10]: (style) Condition 'x<5' is always true [knownConditionTrueFalse]\n", errout_str()); check("void f(int x) {\n" "\n" " if (x<4) {\n" " if (x<=5) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:8] -> [test.cpp:4:10]: (style) Condition 'x<=5' is always true [knownConditionTrueFalse]\n", errout_str()); check("void f(int x) {\n" @@ -2389,49 +2404,49 @@ class TestCondition : public TestFixture { " if (x<5) {\n" " if (x==4) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" "\n" " if (x<5) {\n" " if (x!=4) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" "\n" " if (x<5) {\n" " if (x!=6) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:8] -> [test.cpp:4:10]: (style) Condition 'x!=6' is always true [knownConditionTrueFalse]\n", errout_str()); check("void f(int x) {\n" "\n" " if (x<5) {\n" " if (x>4) {}\n" // <- Warning " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:8] -> [test.cpp:4:10]: (style) Condition 'x>4' is always false [knownConditionTrueFalse]\n", errout_str()); check("void f(int x) {\n" "\n" " if (x<5) {\n" " if (x>=4) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" "\n" " if (x<5) {\n" " if (x<4) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" "\n" " if (x<5) {\n" " if (x<=4) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:8] -> [test.cpp:4:10]: (style) Condition 'x<=4' is always true [knownConditionTrueFalse]\n", errout_str()); // first comparison: > @@ -2440,35 +2455,35 @@ class TestCondition : public TestFixture { " if (x>4) {\n" " if (x==5) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" "\n" " if (x>4) {\n" " if (x>5) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" "\n" " if (x>4) {\n" " if (x>=5) {}\n" // <- Warning " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:8] -> [test.cpp:4:10]: (style) Condition 'x>=5' is always true [knownConditionTrueFalse]\n", errout_str()); check("void f(int x) {\n" "\n" " if (x>4) {\n" " if (x<5) {}\n" // <- Warning " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:8] -> [test.cpp:4:10]: (style) Condition 'x<5' is always false [knownConditionTrueFalse]\n", errout_str()); check("void f(int x) {\n" "\n" " if (x>4) {\n" " if (x<=5) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" @@ -2476,7 +2491,7 @@ class TestCondition : public TestFixture { " if (x>5) {\n" " if (x==4) {}\n" // <- Warning " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:8] -> [test.cpp:4:10]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); check("void f(int x) {\n" @@ -2484,21 +2499,21 @@ class TestCondition : public TestFixture { " if (x>5) {\n" " if (x>4) {}\n" // <- Warning " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:8] -> [test.cpp:4:10]: (style) Condition 'x>4' is always true [knownConditionTrueFalse]\n", errout_str()); check("void f(int x) {\n" "\n" " if (x>5) {\n" " if (x>=4) {}\n" // <- Warning " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:8] -> [test.cpp:4:10]: (style) Condition 'x>=4' is always true [knownConditionTrueFalse]\n", errout_str()); check("void f(int x) {\n" "\n" " if (x>5) {\n" " if (x<4) {}\n" // <- Warning " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:8] -> [test.cpp:4:10]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); check("void f(int x) {\n" @@ -2506,7 +2521,7 @@ class TestCondition : public TestFixture { " if (x>5) {\n" " if (x<=4) {}\n" // <- Warning " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:8] -> [test.cpp:4:10]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); @@ -2514,62 +2529,62 @@ class TestCondition : public TestFixture { " if (x < 4) {\n" " if (10 < x) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9] -> [test.cpp:3:12]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); } void oppositeInnerCondition3() { - check("void f3(char c) { if(c=='x') if(c=='y') {}}"); + check("void f3(char c) { if(c=='x') if(c=='y') {}}\n"); ASSERT_EQUALS("[test.cpp:1:23] -> [test.cpp:1:34]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); - check("void f4(char *p) { if(*p=='x') if(*p=='y') {}}"); + check("void f4(char *p) { if(*p=='x') if(*p=='y') {}}\n"); ASSERT_EQUALS("[test.cpp:1:25] -> [test.cpp:1:37]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); - check("void f5(const char * const p) { if(*p=='x') if(*p=='y') {}}"); + check("void f5(const char * const p) { if(*p=='x') if(*p=='y') {}}\n"); ASSERT_EQUALS("[test.cpp:1:38] -> [test.cpp:1:50]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); - check("void f5(const char * const p) { if('x'==*p) if('y'==*p) {}}"); + check("void f5(const char * const p) { if('x'==*p) if('y'==*p) {}}\n"); ASSERT_EQUALS("[test.cpp:1:39] -> [test.cpp:1:51]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); - check("void f6(char * const p) { if(*p=='x') if(*p=='y') {}}"); + check("void f6(char * const p) { if(*p=='x') if(*p=='y') {}}\n"); ASSERT_EQUALS("[test.cpp:1:32] -> [test.cpp:1:44]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); - check("void f7(const char * p) { if(*p=='x') if(*p=='y') {}}"); + check("void f7(const char * p) { if(*p=='x') if(*p=='y') {}}\n"); ASSERT_EQUALS("[test.cpp:1:32] -> [test.cpp:1:44]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); - check("void f8(int i) { if(i==4) if(i==2) {}}"); + check("void f8(int i) { if(i==4) if(i==2) {}}\n"); ASSERT_EQUALS("[test.cpp:1:22] -> [test.cpp:1:31]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); - check("void f9(int *p) { if (*p==4) if(*p==2) {}}"); + check("void f9(int *p) { if (*p==4) if(*p==2) {}}\n"); ASSERT_EQUALS("[test.cpp:1:25] -> [test.cpp:1:35]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); - check("void f10(int * const p) { if (*p==4) if(*p==2) {}}"); + check("void f10(int * const p) { if (*p==4) if(*p==2) {}}\n"); ASSERT_EQUALS("[test.cpp:1:33] -> [test.cpp:1:43]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); - check("void f11(const int *p) { if (*p==4) if(*p==2) {}}"); + check("void f11(const int *p) { if (*p==4) if(*p==2) {}}\n"); ASSERT_EQUALS("[test.cpp:1:32] -> [test.cpp:1:42]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); - check("void f12(const int * const p) { if (*p==4) if(*p==2) {}}"); + check("void f12(const int * const p) { if (*p==4) if(*p==2) {}}\n"); ASSERT_EQUALS("[test.cpp:1:39] -> [test.cpp:1:49]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); check("struct foo {\n" " int a;\n" " int b;\n" "};\n" - "void f(foo x) { if(x.a==4) if(x.b==2) {}}"); + "void f(foo x) { if(x.a==4) if(x.b==2) {}}\n"); ASSERT_EQUALS("", errout_str()); check("struct foo {\n" " int a;\n" " int b;\n" "};\n" - "void f(foo x) { if(x.a==4) if(x.b==4) {}}"); + "void f(foo x) { if(x.a==4) if(x.b==4) {}}\n"); ASSERT_EQUALS("", errout_str()); - check("void f3(char a, char b) { if(a==b) if(a==0) {}}"); + check("void f3(char a, char b) { if(a==b) if(a==0) {}}\n"); ASSERT_EQUALS("", errout_str()); - check("void f(int x) { if (x == 1) if (x != 1) {} }"); + check("void f(int x) { if (x == 1) if (x != 1) {} }\n"); ASSERT_EQUALS("[test.cpp:1:23] -> [test.cpp:1:35]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); } @@ -2577,8 +2592,8 @@ class TestCondition : public TestFixture { check("void f(int x) {\n" " if (a>3 && x > 100) {\n" " if (x < 10) {}\n" - " }" - "}"); + " }\n" + "}\n"); ASSERT_EQUALS("[test.cpp:2:16] -> [test.cpp:3:11]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); check("void f(bool x, const int a, const int b) {\n" @@ -2629,63 +2644,69 @@ class TestCondition : public TestFixture { } void oppositeInnerConditionEmpty() { - check("void f1(const std::string &s) { if(s.size() > 42) if(s.empty()) {}}"); + check("void f1(const std::string &s) { if(s.size() > 42) if(s.empty()) {}}\n"); ASSERT_EQUALS("[test.cpp:1:45] -> [test.cpp:1:61]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); - check("void f1(const std::string &s) { if(s.size() > 0) if(s.empty()) {}}"); + check("void f1(const std::string &s) { if(s.size() > 0) if(s.empty()) {}}\n"); ASSERT_EQUALS("[test.cpp:1:45] -> [test.cpp:1:60]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); - check("void f1(const std::string &s) { if(s.size() < 0) if(s.empty()) {}} "); // <- CheckOther reports: checking if unsigned expression is less than zero + check("void f1(const std::string &s) { if(s.size() < 0) if(s.empty()) {}} \n"); // <- CheckOther reports: checking if unsigned expression is less than zero ASSERT_EQUALS("[test.cpp:1:45] -> [test.cpp:1:60]: (style) Condition 's.empty()' is always false [knownConditionTrueFalse]\n", errout_str()); - check("void f1(const std::string &s) { if(s.empty()) if(s.size() > 42) {}}"); + check("void f1(const std::string &s) { if(s.empty()) if(s.size() > 42) {}}\n"); ASSERT_EQUALS("[test.cpp:1:43] -> [test.cpp:1:59]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); - check("template void f1(const T &s) { if(s.size() > 42) if(s.empty()) {}}"); + check("template void f1(const T &s) { if(s.size() > 42) if(s.empty()) {}}\n"); ASSERT_EQUALS("", errout_str()); //We don't know the type of T so we don't know the relationship between size() and empty(). e.g. s might be a 50 tonne truck with nothing in it. - check("void f2(const std::wstring &s) { if(s.empty()) if(s.size() > 42) {}}"); + check("void f2(const std::wstring &s) { if(s.empty()) if(s.size() > 42) {}}\n"); ASSERT_EQUALS("[test.cpp:1:44] -> [test.cpp:1:60]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); - check("void f1(QString s) { if(s.isEmpty()) if(s.length() > 42) {}}"); + check("void f1(QString s) { if(s.isEmpty()) if(s.length() > 42) {}}\n"); ASSERT_EQUALS("[test.cpp:1:34] -> [test.cpp:1:52]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); - check("void f1(const std::string &s, bool b) { if(s.empty() || ((s.size() == 1) && b)) {}}"); + check("void f1(const std::string &s, bool b) { if(s.empty() || ((s.size() == 1) && b)) {}}\n"); ASSERT_EQUALS("", errout_str()); - check("void f1(const std::string &x, const std::string &y) { if(x.size() > 42) if(y.empty()) {}}"); + check("void f1(const std::string &x, const std::string &y) { if(x.size() > 42) if(y.empty()) {}}\n"); ASSERT_EQUALS("", errout_str()); - check("void f1(const std::string &x, const std::string &y) { if(y.empty()) if(x.size() > 42) {}}"); + check("void f1(const std::string &x, const std::string &y) { if(y.empty()) if(x.size() > 42) {}}\n"); ASSERT_EQUALS("", errout_str()); - check("void f1(const std::string v[10]) { if(v[0].size() > 42) if(v[1].empty()) {}}"); + check("void f1(const std::string v[10]) { if(v[0].size() > 42) if(v[1].empty()) {}}\n"); ASSERT_EQUALS("", errout_str()); - check("void f1(const std::string &s) { if(s.size() <= 1) if(s.empty()) {}}"); + check("void f1(const std::string &s) { if(s.size() <= 1) if(s.empty()) {}}\n"); ASSERT_EQUALS("", errout_str()); - check("void f1(const std::string &s) { if(s.size() <= 2) if(s.empty()) {}}"); + check("void f1(const std::string &s) { if(s.size() <= 2) if(s.empty()) {}}\n"); ASSERT_EQUALS("", errout_str()); - check("void f1(const std::string &s) { if(s.size() < 2) if(s.empty()) {}}"); + check("void f1(const std::string &s) { if(s.size() < 2) if(s.empty()) {}}\n"); ASSERT_EQUALS("", errout_str()); - check("void f1(const std::string &s) { if(s.size() >= 0) if(s.empty()) {}} "); // CheckOther says: Unsigned expression 's.size()' can't be negative so it is unnecessary to test it. [unsignedPositive] + check("void f1(const std::string &s) { if(s.size() >= 0) if(s.empty()) {}} \n"); // CheckOther says: Unsigned expression 's.size()' can't be negative so it is unnecessary to test it. [unsignedPositive] ASSERT_EQUALS("", errout_str()); - check("void f1(const std::string &s) { if(42 < s.size()) if(s.empty()) {}}"); + check("void f1(const std::string &s) { if(42 < s.size()) if(s.empty()) {}}\n"); ASSERT_EQUALS("[test.cpp:1:39] -> [test.cpp:1:61]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); - check("void f1(const std::string &s) { if(s.empty()) if(42 < s.size()) {}}"); + check("void f1(const std::string &s) { if(s.empty()) if(42 < s.size()) {}}\n"); ASSERT_EQUALS("[test.cpp:1:43] -> [test.cpp:1:53]: (warning) Opposite inner 'if' condition leads to a dead code block. [oppositeInnerCondition]\n", errout_str()); + check("void f(const std::string& s, int n) {\n" // #14716 + " if (s.size() < n)\n" + " if (s.empty()) {}\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + // TODO: These are identical condition since size cannot be negative - check("void f1(const std::string &s) { if(s.size() <= 0) if(s.empty()) {}}"); + check("void f1(const std::string &s) { if(s.size() <= 0) if(s.empty()) {}}\n"); ASSERT_EQUALS("", errout_str()); // TODO: These are identical condition since size cannot be negative - check("void f1(const std::string &s) { if(s.size() < 1) if(s.empty()) {}}"); + check("void f1(const std::string &s) { if(s.size() < 1) if(s.empty()) {}}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2700,7 +2721,7 @@ class TestCondition : public TestFixture { " }\n" " void bar();\n" " int get() const;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct CD {\n" @@ -2728,7 +2749,7 @@ class TestCondition : public TestFixture { " bool b = f();\n" " x += 1;\n" " if (!b && f()) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(double d) {\n" @@ -2757,10 +2778,10 @@ class TestCondition : public TestFixture { } void identicalInnerCondition() { - check("void f1(int a, int b) { if(a==b) if(a==b) {}}"); + check("void f1(int a, int b) { if(a==b) if(a==b) {}}\n"); ASSERT_EQUALS("[test.cpp:1:29] -> [test.cpp:1:38]: (warning) Identical inner 'if' condition is always true. [identicalInnerCondition]\n", errout_str()); - check("void f2(int a, int b) { if(a!=b) if(a!=b) {}}"); + check("void f2(int a, int b) { if(a!=b) if(a!=b) {}}\n"); ASSERT_EQUALS("[test.cpp:1:29] -> [test.cpp:1:38]: (warning) Identical inner 'if' condition is always true. [identicalInnerCondition]\n", errout_str()); // #6645 false negative: condition is always false @@ -2769,38 +2790,38 @@ class TestCondition : public TestFixture { " if(a) {}\n" " else {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:6] -> [test.cpp:3:9]: (warning) Identical inner 'if' condition is always true. [identicalInnerCondition]\n", errout_str()); check("bool f(int a, int b) {\n" " if(a == b) { return a == b; }\n" " return false;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:10] -> [test.cpp:2:27]: (warning) Identical inner 'return' condition is always true. [identicalInnerCondition]\n", errout_str()); check("bool f(bool a) {\n" " if(a) { return a; }\n" " return false;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int* f(int* a, int * b) {\n" " if(a) { return a; }\n" " return b;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int* f(std::shared_ptr a, std::shared_ptr b) {\n" " if(a.get()) { return a.get(); }\n" " return b.get();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct A { int * x; };\n" "int* f(A a, int * b) {\n" " if(a.x) { return a.x; }\n" " return b;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -2812,7 +2833,7 @@ class TestCondition : public TestFixture { " get_value (&value);\n" " if ((value >> 28) & 1) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); checkP("#define TYPE_1 \"a\"\n" // #13202 @@ -2824,7 +2845,7 @@ class TestCondition : public TestFixture { " if (s == TYPE_2) {}\n" " else if (s == TYPE_3) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(const int* p, const int* e) {\n" // #14595 @@ -2834,59 +2855,75 @@ class TestCondition : public TestFixture { " }\n" "}\n"); ASSERT_EQUALS("[test.cpp:2:12] -> [test.cpp:4:13]: (warning) Identical inner 'if' condition is always true. [identicalInnerCondition]\n", errout_str()); + + check("int f(const int *q, const int *s) {\n" // #14711 + " if (*q && *s)\n" + " return *q;\n" + " return 0;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("void f(int x, int y) {\n" + " int a[] = { x, y };\n" + " if (a[0] == 1) {\n" + " if (a[0] == 1) {}\n" + " }\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:3:14] -> [test.cpp:4:18]: (warning) Identical inner 'if' condition is always true. [identicalInnerCondition]\n", + errout_str()); } void identicalConditionAfterEarlyExit() { check("void f(int x) {\n" // #8137 " if (x > 100) { return; }\n" " if (x > 100) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9] -> [test.cpp:3:9]: (warning) Identical condition 'x>100', second condition is always false [identicalConditionAfterEarlyExit]\n", errout_str()); check("bool f(int x) {\n" " if (x > 100) { return false; }\n" " return x > 100;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9] -> [test.cpp:3:12]: (warning) Identical condition and return expression 'x>100', return value is always false [identicalConditionAfterEarlyExit]\n", errout_str()); check("void f(int x) {\n" " if (x > 100) { return; }\n" " if (x > 100 || y > 100) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9] -> [test.cpp:3:9]: (warning) Identical condition 'x>100', second condition is always false [identicalConditionAfterEarlyExit]\n", errout_str()); check("void f(int x) {\n" " if (x > 100) { return; }\n" " if (x > 100 && y > 100) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9] -> [test.cpp:3:9]: (warning) Identical condition 'x>100', second condition is always false [identicalConditionAfterEarlyExit]\n", errout_str()); check("void f(int x) {\n" " if (x > 100) { return; }\n" " if (abc) {}\n" " if (x > 100) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9] -> [test.cpp:4:9]: (warning) Identical condition 'x>100', second condition is always false [identicalConditionAfterEarlyExit]\n", errout_str()); check("void f(int x) {\n" " if (x > 100) { return; }\n" " while (abc) { y = x; }\n" " if (x > 100) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9] -> [test.cpp:4:9]: (warning) Identical condition 'x>100', second condition is always false [identicalConditionAfterEarlyExit]\n", errout_str()); ASSERT_THROW_INTERNAL(check("void f(int x) {\n" // #8217 - crash for incomplete code " if (x > 100) { return; }\n" " X(do);\n" " if (x > 100) {}\n" - "}"), + "}\n"), SYNTAX); check("void f(const int *i) {\n" " if (!i) return;\n" " if (!num1tok) { *num1 = *num2; }\n" " if (!i) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:7] -> [test.cpp:4:7]: (warning) Identical condition '!i', second condition is always false [identicalConditionAfterEarlyExit]\n", errout_str()); check("void C::f(Tree &coreTree) {\n" // daca @@ -2894,7 +2931,7 @@ class TestCondition : public TestFixture { " return;\n" " coreTree.dostuff();\n" " if(!coreTree.build()) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct C { void f(const Tree &coreTree); };\n" @@ -2903,7 +2940,7 @@ class TestCondition : public TestFixture { " return;\n" " coreTree.dostuff();\n" " if(!coreTree.build()) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:6] -> [test.cpp:6:6]: (warning) Identical condition '!coreTree.build()', second condition is always false [identicalConditionAfterEarlyExit]\n", errout_str()); check("void f(int x) {\n" // daca: labplot @@ -2916,7 +2953,7 @@ class TestCondition : public TestFixture { " else return 4;\n" " }\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("static int failed = 0;\n" @@ -2924,14 +2961,14 @@ class TestCondition : public TestFixture { " if (failed) return;\n" " checkBuffer();\n" " if (failed) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // daca icu check("void f(const uint32_t *section, int32_t start) {\n" " if(10<=section[start]) { return; }\n" " if(++start<100 && 10<=section[start]) { }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // daca iqtree @@ -2941,7 +2978,7 @@ class TestCondition : public TestFixture { " if (ch != '|') return;\n" " in >> ch;\n" " if (ch != '|') {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #8924 @@ -2952,7 +2989,7 @@ class TestCondition : public TestFixture { " if (this->FileIndex < 0) return;\n" " }\n" " int FileIndex;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:5:29]: (style) Condition 'this->FileIndex<0' is always false [knownConditionTrueFalse]\n", errout_str()); // #8858 - #if @@ -2964,7 +3001,7 @@ class TestCondition : public TestFixture { " ret = bar2();\n" "#endif\n" " return ret;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #10456 @@ -3002,7 +3039,7 @@ class TestCondition : public TestFixture { " x += y;\n" " if (x == 0) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" @@ -3010,7 +3047,7 @@ class TestCondition : public TestFixture { " x += y;\n" " if (x == 1) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int * x, int * y) {\n" @@ -3018,7 +3055,32 @@ class TestCondition : public TestFixture { " (*y)++;\n" " if (x[*y] == 0) {}\n" " }\n" - "}"); + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("void g(int[]);\n" // #14724 + "void f(int a[]) {\n" + " if (a[0] == 1) {\n" + " g(a);\n" + " if (a[0] == 1) {}\n" + " }\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + } + + void overlappingInnerCondition() { + check("void f(int x) {\n" + " if (x == 1) {\n" + " if (x & 7) {}\n" + " }\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:2:11] -> [test.cpp:3:15]: (warning) Overlapping inner 'if' condition is always true. [overlappingInnerCondition]\n", errout_str()); + + check("void f(int x) {\n" + " if (x & 7) {\n" + " if (x == 1) {}\n" + " }\n" + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -3026,102 +3088,102 @@ class TestCondition : public TestFixture { void clarifyCondition1() { check("void f() {\n" " if (x = b() < 0) {}\n" // don't simplify and verify this code - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:8]: (style) Suspicious condition (assignment + comparison); Clarify expression with parentheses. [clarifyCondition]\n", errout_str()); check("void f(int i) {\n" " for (i = 0; i < 10; i++) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " x = a(); if (x) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " if (x = b < 0 ? 1 : 2) {}\n" // don't simplify and verify this code - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " int y = rand(), z = rand();\n" " if (y || (!y && z));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:11]: (style) Redundant condition: !y. 'y || (!y && z)' is equivalent to 'y || z' [redundantCondition]\n", errout_str()); check("void f() {\n" " int y = rand(), z = rand();\n" " if (y || !y && z);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:11]: (style) Redundant condition: !y. 'y || (!y && z)' is equivalent to 'y || z' [redundantCondition]\n", errout_str()); check("void f() {\n" " if (!a || a && b) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:12]: (style) Redundant condition: a. '!a || (a && b)' is equivalent to '!a || b' [redundantCondition]\n", errout_str()); check("void f(const Token *tok) {\n" " if (!tok->next()->function() ||\n" " (tok->next()->function() && tok->next()->function()->isConstructor()));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:34]: (style) Redundant condition: tok->next()->function(). '!A || (A && B)' is equivalent to '!A || B' [redundantCondition]\n", errout_str()); check("void f() {\n" " if (!tok->next()->function() ||\n" " (!tok->next()->function() && tok->next()->function()->isConstructor()));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " if (!tok->next()->function() ||\n" " (!tok2->next()->function() && tok->next()->function()->isConstructor()));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(const Token *tok) {\n" " if (!tok->next(1)->function(1) ||\n" " (tok->next(1)->function(1) && tok->next(1)->function(1)->isConstructor()));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:36]: (style) Redundant condition: tok->next(1)->function(1). '!A || (A && B)' is equivalent to '!A || B' [redundantCondition]\n", errout_str()); check("void f() {\n" " if (!tok->next()->function(1) ||\n" " (tok->next()->function(2) && tok->next()->function()->isConstructor()));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " int y = rand(), z = rand();\n" " if (y==0 || y!=0 && z);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:13]: (style) Redundant condition: y!=0. 'y==0 || (y!=0 && z)' is equivalent to 'y==0 || z' [redundantCondition]\n", errout_str()); check("void f() {\n" " if (x>0 || (x<0 && y)) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Test Token::expressionString, TODO move this test check("void f() {\n" " if (!dead || (dead && (*it).ticks > 0)) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:13]: (style) Redundant condition: dead. '!dead || (dead && (*it).ticks>0)' is equivalent to '!dead || (*it).ticks>0' [redundantCondition]\n", errout_str()); check("void f() {\n" " if (!x || (x && (2>(y-1)))) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:10]: (style) Redundant condition: x. '!x || (x && 2>(y-1))' is equivalent to '!x || 2>(y-1)' [redundantCondition]\n", errout_str()); check("void f(bool a, bool b) {\n" " if (a || (a && b)) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11]: (style) Redundant condition: a. 'a || (a && b)' is equivalent to 'a' [redundantCondition]\n", errout_str()); check("void f(bool a, bool b) {\n" " if (a && (a || b)) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11]: (style) Redundant condition: a. 'a && (a || b)' is equivalent to 'a' [redundantCondition]\n", errout_str()); } @@ -3129,14 +3191,14 @@ class TestCondition : public TestFixture { void clarifyCondition2() { check("void f() {\n" " if (x & 3 == 2) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:8]: (style) Suspicious condition (bitwise operator + comparison); Clarify expression with parentheses. [clarifyCondition]\n" "[test.cpp:2:11]: (style) Boolean result is used in bitwise operation. Clarify expression with parentheses. [clarifyCondition]\n" "[test.cpp:2:11]: (style) Condition 'x&3==2' is always false [knownConditionTrueFalse]\n", errout_str()); check("void f() {\n" " if (a & fred1.x == fred2.y) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:8]: (style) Suspicious condition (bitwise operator + comparison); Clarify expression with parentheses. [clarifyCondition]\n" "[test.cpp:2:11]: (style) Boolean result is used in bitwise operation. Clarify expression with parentheses. [clarifyCondition]\n" , errout_str()); @@ -3146,50 +3208,50 @@ class TestCondition : public TestFixture { void clarifyCondition3() { check("void f(int w) {\n" " if(!w & 0x8000) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11]: (style) Boolean result is used in bitwise operation. Clarify expression with parentheses. [clarifyCondition]\n", errout_str()); check("void f(int w) {\n" " if((!w) & 0x8000) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " if (x == foo() & 2) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:20]: (style) Boolean result is used in bitwise operation. Clarify expression with parentheses. [clarifyCondition]\n", errout_str()); check("void f() {\n" " if (2 & x == foo()) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11]: (style) Boolean result is used in bitwise operation. Clarify expression with parentheses. [clarifyCondition]\n", errout_str()); check("void f() {\n" " if (2 & (x == foo())) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); - check("void f(std::list &ints) { }"); + check("void f(std::list &ints) { }\n"); ASSERT_EQUALS("", errout_str()); - check("void f() { A a; }"); + check("void f() { A a; }\n"); ASSERT_EQUALS("", errout_str()); - check("void f() { a(x there are never templates + check("void f() { a(x there are never templates ASSERT_EQUALS("[test.c:1:17]: (style) Boolean result is used in bitwise operation. Clarify expression with parentheses. [clarifyCondition]\n", errout_str()); - check("class A;"); + check("class A;\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " if (result != (char *)&inline_result) { }\n" // don't simplify and verify cast - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #8495 check("void f(bool a, bool b) {\n" " C & a & b;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -3204,13 +3266,13 @@ class TestCondition : public TestFixture { " {\n" " return left.first < right.first;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } void clarifyCondition5() { // ticket #3609 (using | in template instantiation) check("template struct CWinTraits;\n" - "CWinTraits::GetWndStyle(0);"); + "CWinTraits::GetWndStyle(0);\n"); ASSERT_EQUALS("", errout_str()); } @@ -3219,7 +3281,7 @@ class TestCondition : public TestFixture { "SharedPtr& operator=( SharedPtr const & r ) {\n" " px = r.px;\n" " return *this;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -3228,7 +3290,7 @@ class TestCondition : public TestFixture { check("void f(bool error) {\n" " bool & withoutSideEffects=found.first->second;\n" // Declaring a reference to a boolean; & is no operator at all " execute(secondExpression, &programMemory, &result, &error);\n" // Unary & - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -3238,24 +3300,24 @@ class TestCondition : public TestFixture { check("bool a();\n" "bool f(bool b) {\n" " return (a() & b);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("bool f(bool *a, bool b) {\n" " return (a[10] & b);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct A { bool a; };\n" "bool f(struct A a, bool b) {\n" " return (a.a & b);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct A { bool a; };\n" "bool f(struct A a, bool b) {\n" " return (A::a & b);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -3263,7 +3325,7 @@ class TestCondition : public TestFixture { check("void png_parse(uint64_t init, int buf_size) {\n" " if (init == 0x89504e470d0a1a0a || init == 0x8a4d4e470d0a1a0a)\n" " ;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -3272,7 +3334,7 @@ class TestCondition : public TestFixture { "void foo() {\n" " if( ( value >= 0x7ff0000000000001ULL )\n" " && ( value <= 0x7fffffffffffffffULL ) );\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -3282,7 +3344,7 @@ class TestCondition : public TestFixture { " int x1 = s->x;\n" " int x2 = s->x;\n" " if (x1 == 10 && x2 == 10) {}\n" // << - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:10] -> [test.cpp:4:22]: (style) Condition 'x2==10' is always true [knownConditionTrueFalse]\n", errout_str()); check("void f ()\n"// #8220 @@ -3298,30 +3360,35 @@ class TestCondition : public TestFixture { " a ++;\n" " }\n" " ret = b;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:27] -> [test.cpp:8:40]: (style) Condition '8 [test.cpp:2:30]: (style) Return value 'x==0' is always false [knownConditionTrueFalse]\n", errout_str()); + "}\n"); + ASSERT_EQUALS("", errout_str()); check("void f() {\n" // #6898 (Token::expressionString) " int x = 0;\n" " A(x++ == 1);\n" " A(x++ == 2);\n" - "}"); - ASSERT_EQUALS("[test.cpp:3:9]: (style) Condition 'x++==1' is always false [knownConditionTrueFalse]\n" - "[test.cpp:4:9]: (style) Condition 'x++==2' is always false [knownConditionTrueFalse]\n", - errout_str()); + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("void f() {\n" + " int x = 0;\n" + " if (x++ == 1) {}\n" // <- no warning as there is a side effect + " if (x++ == 2) {}\n" // <- no warning as there is a side effect + "}\n"); + ASSERT_EQUALS("", errout_str()); check("bool foo(int bar) {\n" " bool ret = false;\n" @@ -3330,77 +3397,83 @@ class TestCondition : public TestFixture { " if (bar == 2)\n" " ret = true;\n" " return ret;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); - check("void f1(const std::string &s) { if(s.empty()) if(s.size() == 0) {}}"); + check("void f1(const std::string &s) { if(s.empty()) if(s.size() == 0) {}}\n"); ASSERT_EQUALS("[test.cpp:1:43] -> [test.cpp:1:59]: (style) Condition 's.size()==0' is always true [knownConditionTrueFalse]\n", errout_str()); check("void f() {\n" " int buf[42];\n" " if( buf != 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:12]: (style) Condition 'buf!=0' is always true [knownConditionTrueFalse]\n", errout_str()); // #8924 check("void f() {\n" " int buf[42];\n" " if( !buf ) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:8]: (style) Condition '!buf' is always false [knownConditionTrueFalse]\n", errout_str()); check("void f() {\n" " int buf[42];\n" " bool b = buf;\n" " if( b ) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:8]: (style) Condition 'b' is always true [knownConditionTrueFalse]\n", errout_str()); check("void f() {\n" " int buf[42];\n" " bool b = buf;\n" " if( !b ) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:8]: (style) Condition '!b' is always false [knownConditionTrueFalse]\n", errout_str()); check("void f() {\n" " int buf[42];\n" " int * p = nullptr;\n" " if( buf == p ) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:12]: (style) Condition 'buf==p' is always false [knownConditionTrueFalse]\n", errout_str()); check("void f(bool x) {\n" " int buf[42];\n" " if( buf || x ) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:8]: (style) Condition 'buf' is always true [knownConditionTrueFalse]\n", errout_str()); check("void f(int * p) {\n" " int buf[42];\n" " if( buf == p ) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " int buf[42];\n" " int p[42];\n" " if( buf == p ) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " int buf[42];\n" " if( buf == 1) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); + check("std::string a[3];\n" // #15065 + "void f() {\n" + " if (a) {}\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:3:8]: (style) Condition 'a' is always true [knownConditionTrueFalse]\n", errout_str()); + // Avoid FP when condition comes from macro check("#define NOT !\n" "void f() {\n" " int x = 0;\n" " if (a) { return; }\n" // <- this is just here to fool simplifyKnownVariabels " if (NOT x) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("#define M x != 0\n" @@ -3408,27 +3481,27 @@ class TestCondition : public TestFixture { " int x = 0;\n" " if (a) { return; }\n" // <- this is just here to fool simplifyKnownVariabels " if (M) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("#define IF(X) if (X && x())\n" "void f() {\n" " IF(1) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Avoid FP for sizeof condition check("void f() {\n" " if (sizeof(char) != 123) {}\n" " if (123 != sizeof(char)) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " int x = 123;\n" " if (sizeof(char) != x) {}\n" " if (x != sizeof(char)) {}\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:3]: (style) Condition 'sizeof(char)!=x' is always true\n" "[test.cpp:4]: (style) Condition 'x!=sizeof(char)' is always true\n", "", errout_str()); @@ -3437,7 +3510,7 @@ class TestCondition : public TestFixture { check("void f() {\n" " int x = 0;\n" " assert(x == 0);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #9363 - do not warn about value passed to function @@ -3445,7 +3518,7 @@ class TestCondition : public TestFixture { " if (b) {\n" " if (bar(!b)) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); @@ -3460,7 +3533,7 @@ class TestCondition : public TestFixture { " assert((int)(0==0));\n" " assert((int)(0==0) && \"bla\");\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #7750 char literals in boolean expressions @@ -3469,7 +3542,7 @@ class TestCondition : public TestFixture { " if(L'b'){}\n" " if(1 && 'c'){}\n" " int x = 'd' ? 1 : 2;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #8206 - knownCondition always false @@ -3477,26 +3550,26 @@ class TestCondition : public TestFixture { "{\n" " if(i > 4)\n" " for( int x = 0; i < 3; ++x){}\n" // << - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:14] -> [test.cpp:4:29]: (style) Condition 'i<3' is always false [knownConditionTrueFalse]\n", errout_str()); // Skip literals - check("void f() { if(true) {} }"); + check("void f() { if(true) {} }\n"); ASSERT_EQUALS("", errout_str()); - check("void f() { if(false) {} }"); + check("void f() { if(false) {} }\n"); ASSERT_EQUALS("", errout_str()); - check("void f() { if(!true) {} }"); + check("void f() { if(!true) {} }\n"); ASSERT_EQUALS("", errout_str()); - check("void f() { if(!false) {} }"); + check("void f() { if(!false) {} }\n"); ASSERT_EQUALS("", errout_str()); - check("void f() { if(0) {} }"); + check("void f() { if(0) {} }\n"); ASSERT_EQUALS("", errout_str()); - check("void f() { if(1) {} }"); + check("void f() { if(1) {} }\n"); ASSERT_EQUALS("", errout_str()); check("void f(int i) {\n" @@ -3505,37 +3578,37 @@ class TestCondition : public TestFixture { " else if (!b && i == 1) {}\n" " if (b)\n" " {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:14]: (style) Condition '!b' is always true [knownConditionTrueFalse]\n", errout_str()); - check("bool f() { return nullptr; }"); + check("bool f() { return nullptr; }\n"); ASSERT_EQUALS("", errout_str()); check("enum E { A };\n" - "bool f() { return A; }"); + "bool f() { return A; }\n"); ASSERT_EQUALS("", errout_str()); check("bool f() {\n" " const int x = 0;\n" " return x;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); - check("int f(void){return 1/abs(10);}"); + check("int f(void){return 1/abs(10);}\n"); ASSERT_EQUALS("", errout_str()); check("bool f() {\n" " int x = 0;\n" " return x;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("bool f() {\n" " const int a = 50;\n" " const int b = 52;\n" " return a+b;\n" - "}"); - ASSERT_EQUALS("[test.cpp:4:13]: (style) Return value 'a+b' is always true [knownConditionTrueFalse]\n", errout_str()); + "}\n"); + ASSERT_EQUALS("", errout_str()); check("int f() {\n" " int a = 50;\n" @@ -3543,7 +3616,7 @@ class TestCondition : public TestFixture { " a++;\n" " b++;\n" " return a+b;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("bool& g();\n" @@ -3551,7 +3624,7 @@ class TestCondition : public TestFixture { " bool & b = g();\n" " b = false;\n" " return b;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct A {\n" @@ -3560,13 +3633,13 @@ class TestCondition : public TestFixture { " b = false;\n" " return b;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("bool f(long maxtime) {\n" " if (std::time(0) > maxtime)\n" " return std::time(0) > maxtime;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(double param) {\n" @@ -3576,7 +3649,7 @@ class TestCondition : public TestFixture { " }\n" " if (param<0.)\n" " return;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int i) {\n" @@ -3586,7 +3659,7 @@ class TestCondition : public TestFixture { " }\n" " if (cond && (42==i))\n" " return;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // 8842 crash @@ -3596,15 +3669,15 @@ class TestCondition : public TestFixture { " void f() {\n" " if (b) return;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("void f(const char* x, const char* t) {\n" " if (!(strcmp(x, y) == 0)) { return; }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); - check("void f(const int a[]){ if (a == 0){} }"); + check("void f(const int a[]){ if (a == 0){} }\n"); ASSERT_EQUALS("", errout_str()); check("struct S {\n" @@ -3615,44 +3688,44 @@ class TestCondition : public TestFixture { " bool c = s [test.cpp:3:9]: (style) Condition 'handle' is always true [knownConditionTrueFalse]\n", errout_str()); check("int f(void *handle) {\n" " if (handle == 0) return 0;\n" " if (handle) return 1;\n" " else return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:9]: (style) Condition 'handle' is always true [knownConditionTrueFalse]\n", errout_str()); check("int f(void *handle) {\n" " if (handle != 0) return 0;\n" " if (handle) return 1;\n" " else return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:16] -> [test.cpp:3:9]: (warning) Identical condition 'handle!=0', second condition is always false [identicalConditionAfterEarlyExit]\n", errout_str()); check("int f(void *handle) {\n" " if (handle != nullptr) return 0;\n" " if (handle) return 1;\n" " else return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:16] -> [test.cpp:3:9]: (warning) Identical condition 'handle!=nullptr', second condition is always false [identicalConditionAfterEarlyExit]\n", errout_str()); check("void f(const char* p) {\n" // #13716 @@ -3660,7 +3733,7 @@ class TestCondition : public TestFixture { " if (p == NULL) {}\n" " if (p == nullptr) {}\n" " if (p == 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9] -> [test.cpp:3:11]: (style) The if condition is the same as the previous if condition [duplicateCondition]\n", errout_str()); @@ -3668,7 +3741,7 @@ class TestCondition : public TestFixture { " if (p) {}\n" " else if (!p) {}\n" " else if (p == NULL) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9] -> [test.cpp:3:14]: (style) Expression is always true because 'else if' condition is opposite to previous condition at line 2. [multiCondition]\n" "[test.cpp:4:16]: (style) Expression is always false because 'else if' condition matches previous condition at line 3. [multiCondition]\n", errout_str()); @@ -3678,7 +3751,7 @@ class TestCondition : public TestFixture { " return;\n" " if (x == nullptr || y == nullptr)\n" " return;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void* g();\n" @@ -3689,7 +3762,7 @@ class TestCondition : public TestFixture { " break;\n" " }\n" " if (a) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void* g();\n" @@ -3698,26 +3771,26 @@ class TestCondition : public TestFixture { " a = g();\n" " }\n" " if (a) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:12] -> [test.cpp:6:9]: (style) Condition 'a' is always false [knownConditionTrueFalse]\n", errout_str()); check("void f(int * x, bool b) {\n" " if (!x && b) {}\n" " else if (x) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " const std::string x=\"xyz\";\n" " if(!x.empty()){}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:8]: (style) Condition '!x.empty()' is always true [knownConditionTrueFalse]\n", errout_str()); check("std::string g();\n" "void f() {\n" " const std::string msg = g();\n" " if(!msg.empty()){}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int *array, int size ) {\n" @@ -3726,7 +3799,7 @@ class TestCondition : public TestFixture { " continue;\n" " if(array){}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:12]: (style) Condition 'array' is always true [knownConditionTrueFalse]\n", errout_str()); check("void f(int *array, int size ) {\n" @@ -3735,7 +3808,7 @@ class TestCondition : public TestFixture { " continue;\n" " else if(array){}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:17]: (style) Condition 'array' is always true [knownConditionTrueFalse]\n", errout_str()); // #9277 @@ -3745,7 +3818,7 @@ class TestCondition : public TestFixture { " return 0;\n" " else\n" " return 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #9954 @@ -3767,7 +3840,7 @@ class TestCondition : public TestFixture { " bool x = false;\n" " g({0, 1}, x);\n" " if (x) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #9318 @@ -3778,7 +3851,7 @@ class TestCondition : public TestFixture { " return;\n" " auto b = dynamic_cast(x);\n" " if (b) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int foo() {\n" @@ -3796,7 +3869,7 @@ class TestCondition : public TestFixture { check("bool g();\n" "void f(bool x) {\n" " if (x) while(x) x = g();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // isLikelyStream @@ -3806,7 +3879,7 @@ class TestCondition : public TestFixture { " iss >> x;\n" " if (!iss) break;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #9332 @@ -3818,14 +3891,14 @@ class TestCondition : public TestFixture { " void* c = a.g();\n" " if (!c) return;\n" " bool compare = c == b;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #9361 check("void f(char c) {\n" " if (c == '.') {}\n" " else if (isdigit(c) != 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #9351 @@ -3833,7 +3906,7 @@ class TestCondition : public TestFixture { " const bool b = x < 42;\n" " if(b) return b?0:-1;\n" " return 42;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:8] -> [test.cpp:3:18]: (style) Condition 'b' is always true [knownConditionTrueFalse]\n", errout_str()); // #9362 @@ -3843,7 +3916,7 @@ class TestCondition : public TestFixture { " if((v != 0x00)) {\n" " if( (v & 0x01) == 0x00) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #9367 @@ -3852,25 +3925,25 @@ class TestCondition : public TestFixture { " return;\n" " if (x % 360L == 0)\n" " return;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int f(int a, int b) {\n" " static const int x = 10;\n" " return x == 1 ? a : b;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("const bool x = false;\n" "void f() {\n" " if (x) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("const bool x = false;\n" "void f() {\n" " if (!x) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #9709 @@ -3881,7 +3954,7 @@ class TestCondition : public TestFixture { " if (r != nullptr)\n" " ok = a != 0;\n" " if (ok) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #9816 @@ -4070,7 +4143,7 @@ class TestCondition : public TestFixture { check("void foo(unsigned int max) {\n" " unsigned int num = max - 1;\n" " if (num < 0) {}\n" // <- do not report knownConditionTrueFalse - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #10297 @@ -4125,7 +4198,7 @@ class TestCondition : public TestFixture { check("bool f(bool a, bool b) {\n" " return a || ! b || ! a;\n" "}\n"); - ASSERT_EQUALS("[test.cpp:2:12] -> [test.cpp:2:24]: (style) Return value '!a' is always true [knownConditionTrueFalse]\n", errout_str()); + ASSERT_EQUALS("[test.cpp:2:12] -> [test.cpp:2:24]: (style) Condition '!a' is always true [knownConditionTrueFalse]\n", errout_str()); // #10148 check("void f(int i) {\n" @@ -4352,8 +4425,7 @@ class TestCondition : public TestFixture { " if (w) {}\n" " }\n" "}\n"); - ASSERT_EQUALS("[test.cpp:4:24]: (style) Condition 'v<2' is always true [knownConditionTrueFalse]\n" - "[test.cpp:5:7]: (style) Condition 'w' is always true [knownConditionTrueFalse]\n", + ASSERT_EQUALS("[test.cpp:5:7]: (style) Condition 'w' is always true [knownConditionTrueFalse]\n", errout_str()); check("void f(double d) {\n" // #10792 @@ -4409,7 +4481,7 @@ class TestCondition : public TestFixture { ASSERT_EQUALS("", errout_str()); // #8358 - check("void f(double d) { if ((d * 0) != 0) {} }"); + check("void f(double d) { if ((d * 0) != 0) {} }\n"); ASSERT_EQUALS("", errout_str()); // #6870 @@ -4456,9 +4528,12 @@ class TestCondition : public TestFixture { "void foo() {\n" " if (bar(1) == 0 && bar(1) > 0) {}\n" "}\n"); - ASSERT_EQUALS("[test.cpp:3:16]: (style) Condition 'bar(1)==0' is always false [knownConditionTrueFalse]\n" - "[test.cpp:3:31]: (style) Condition 'bar(1)>0' is always true [knownConditionTrueFalse]\n", - errout_str()); + // TODO handle function calls without side effects better + // these warnings are shown if isConstExpression is removed from the checker + TODO_ASSERT_EQUALS("[test.cpp:3:16]: (style) Condition 'bar(1)==0' is always false [knownConditionTrueFalse]\n" + "[test.cpp:3:31]: (style) Condition 'bar(1)>0' is always true [knownConditionTrueFalse]\n", + "", + errout_str()); check("struct S { int bar(int i) const; };\n" "void foo(const S& s) {\n" @@ -4533,9 +4608,12 @@ class TestCondition : public TestFixture { "void f() {\n" " if (g() == 1 && g() == -1) {}\n" "}\n"); - ASSERT_EQUALS("[test.cpp:3:13]: (style) Condition 'g()==1' is always false [knownConditionTrueFalse]\n" - "[test.cpp:3:25]: (style) Condition 'g()==-1' is always true [knownConditionTrueFalse]\n", - errout_str()); + // TODO handle function calls without side effects better + // these warnings are shown if isConstExpression is removed from the checker + TODO_ASSERT_EQUALS("[test.cpp:3:13]: (style) Condition 'g()==1' is always false [knownConditionTrueFalse]\n" + "[test.cpp:3:25]: (style) Condition 'g()==-1' is always true [knownConditionTrueFalse]\n", + "", + errout_str()); // #9817 check("void f(float x) {\n" @@ -4556,8 +4634,7 @@ class TestCondition : public TestFixture { ASSERT_EQUALS("[test.cpp:3:12]: (style) Condition '!s.empty()' is always false [knownConditionTrueFalse]\n" "[test.cpp:4:19]: (style) Condition 's.empty()' is always true [knownConditionTrueFalse]\n" "[test.cpp:5:16]: (style) Condition 's.empty()' is always true [knownConditionTrueFalse]\n" - "[test.cpp:6:9]: (style) Condition '(bool)0' is always false [knownConditionTrueFalse]\n" - "[test.cpp:7:19]: (style) Return value 's.empty()' is always true [knownConditionTrueFalse]\n", + "[test.cpp:6:9]: (style) Condition '(bool)0' is always false [knownConditionTrueFalse]\n", errout_str()); check("int f(bool b) {\n" @@ -4568,17 +4645,17 @@ class TestCondition : public TestFixture { " if (b) return static_cast(1);\n" " return (int)0;\n" "}\n"); - ASSERT_EQUALS("[test.cpp:6:35]: (style) Return value 'static_cast(1)' is always true [knownConditionTrueFalse]\n" - "[test.cpp:7:12]: (style) Return value '(int)0' is always false [knownConditionTrueFalse]\n", - errout_str()); + ASSERT_EQUALS("", errout_str()); check("int f() { return 3; }\n" "int g() { return f(); }\n" "int h() { if (f()) {} }\n" "int i() { return f() == 3; }\n"); - ASSERT_EQUALS("[test.cpp:3:16]: (style) Condition 'f()' is always true [knownConditionTrueFalse]\n" - "[test.cpp:4:22]: (style) Return value 'f()==3' is always true [knownConditionTrueFalse]\n", - errout_str()); + // TODO handle function calls without side effects better + // if the isConstExpression is removed from the checker then this warning is shown + TODO_ASSERT_EQUALS("[test.cpp:3:16]: (style) Condition 'f()' is always true [knownConditionTrueFalse]\n", + "", + errout_str()); check("int f() {\n" " const char *n;\n" @@ -4645,12 +4722,10 @@ class TestCondition : public TestFixture { "void f() {\n" " int i = 5;\n" " int* p = &i;\n" - " g(i == 7);\n" - " g(p == nullptr);\n" + " g(i == 7);\n" // <- argument is always false but cannot be removed therefore warning should NOT be written + " g(p == nullptr);\n" // <- argument is always false but cannot be removed therefore warning should NOT be written "}\n"); - ASSERT_EQUALS("[test.cpp:5:9]: (style) Condition 'i==7' is always false [knownConditionTrueFalse]\n" - "[test.cpp:6:9]: (style) Condition 'p==nullptr' is always false [knownConditionTrueFalse]\n", - errout_str()); + ASSERT_EQUALS("", errout_str()); check("enum E { E0, E1 };\n" "void f() {\n" @@ -4695,7 +4770,8 @@ class TestCondition : public TestFixture { " int i = 0;\n" " if ((i = g(), 1) != 0) {}\n" "}\n"); - ASSERT_EQUALS("[test.cpp:3:22]: (style) Condition '(i=g(),1)!=0' is always true [knownConditionTrueFalse]\n", errout_str()); + // the condition is always true but we can't remove the condition therefore no warning is expected + ASSERT_EQUALS("", errout_str()); check("void f(unsigned i) {\n" " const int a[2] = {};\n" @@ -4769,7 +4845,7 @@ class TestCondition : public TestFixture { " return 9;\n" " \n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int g();\n" // #10561 @@ -4784,7 +4860,7 @@ class TestCondition : public TestFixture { " }\n" " catch (...) {}\n" " return b ? 1 : 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct S {\n" @@ -4828,7 +4904,18 @@ class TestCondition : public TestFixture { " }\n" " return false;\n" "}\n"); - ASSERT_EQUALS("[test.cpp:6:12] -> [test.cpp:7:21]: (style) Assigned value 's.g()' is always true [knownConditionTrueFalse]\n", errout_str()); + TODO_ASSERT_EQUALS("[test.cpp:6:12] -> [test.cpp:7:21]: (style) Assigned value 's.g()' is always true [knownConditionTrueFalse]\n", "", errout_str()); + + check("static bool parse(int r) {\n" // #15031 + " bool res = false;\n" + " return res;\n" + "}\n" + "\n" + "int main (void) {\n" + " bool res = parse(1101);\n" + " return res;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); check("void f(const void* p) {\n" // #11519 " bool b = false;\n" @@ -4852,6 +4939,86 @@ class TestCondition : public TestFixture { "}\n"); ASSERT_EQUALS("[test.cpp:6:9] -> [test.cpp:9:9]: (warning) Identical condition 'a', second condition is always false [identicalConditionAfterEarlyExit]\n", errout_str()); + + check("bool b() { return false; }\n" // #10452 + "void f() {\n" + " if (b()) {}\n" + " if (!b()) {}\n" + "}\n"); + // TODO handle function calls without side effects better + // these warnings are shown if isConstExpression is removed from the checker + TODO_ASSERT_EQUALS("[test.cpp:3:10]: (style) Condition 'b()' is always false [knownConditionTrueFalse]\n" + "[test.cpp:4:9]: (style) Condition '!b()' is always true [knownConditionTrueFalse]\n", + "", + errout_str()); + + check("int g();\n" // a value modified inside a nested branch must be lowered to possible + "void f(int outer, int inner) {\n" + " int bits = 0;\n" + " if (outer) {\n" + " if (inner == 1)\n" + " bits = g();\n" + " }\n" + " if (bits > 0) {}\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("int g();\n" // the modifying branch has an escaping sibling - still must be lowered + "void f(int t, int u) {\n" + " int v = 0;\n" + " if (t) {\n" + " if (u == 2)\n" + " v = g();\n" + " else\n" + " return;\n" + " }\n" + " if (v > 0) {}\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("bool f(int x) {\n" // only the innermost else escapes - x is 0 or >1 afterwards, not known + " if (!x) {}\n" + " else if (x > 1) {}\n" + " else return false;\n" + " return x ? false : true;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("bool f(int x) {\n" // the branch's fall-through path must clear the escape + " if (x) { if (x > 1) {} else return false; }\n" + " return x ? false : true;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("void f() {\n" + " if (42) {}\n" + " if (42U) {}\n" + " if (42L) {}\n" + " if (42UL) {}\n" + " if (42LL) {}\n" + " if (042) {}\n" + " if (0x42) {}\n" + " if (0b101010) {}\n" + " if (!42) {}\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:2:9]: (style) Condition '42' is always true [knownConditionTrueFalse]\n" + "[test.cpp:3:9]: (style) Condition '42U' is always true [knownConditionTrueFalse]\n" + "[test.cpp:4:9]: (style) Condition '42L' is always true [knownConditionTrueFalse]\n" + "[test.cpp:5:9]: (style) Condition '42UL' is always true [knownConditionTrueFalse]\n" + "[test.cpp:6:9]: (style) Condition '42LL' is always true [knownConditionTrueFalse]\n" + "[test.cpp:7:9]: (style) Condition '042' is always true [knownConditionTrueFalse]\n" + "[test.cpp:8:9]: (style) Condition '0x42' is always true [knownConditionTrueFalse]\n" + "[test.cpp:9:9]: (style) Condition '0b101010' is always true [knownConditionTrueFalse]\n" + "[test.cpp:10:9]: (style) Condition '!42' is always false [knownConditionTrueFalse]\n", + errout_str()); + + check("bool g(int = 1) __attribute__((pure));\n" + "void f() {\n" + " int n = 0;\n" + " if (g(), n) {}\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:4:12]: (style) Condition 'g(),n' is always false [knownConditionTrueFalse]\n", + errout_str()); } void alwaysTrueSymbolic() @@ -4871,10 +5038,10 @@ class TestCondition : public TestFixture { "}\n"); ASSERT_EQUALS("[test.cpp:2:10] -> [test.cpp:4:14]: (style) Condition 'x==y' is always false [knownConditionTrueFalse]\n", errout_str()); - check("void f(bool a, bool b) { if (a == b && a && !b){} }"); + check("void f(bool a, bool b) { if (a == b && a && !b){} }\n"); ASSERT_EQUALS("[test.cpp:1:41] -> [test.cpp:1:46]: (style) Condition '!b' is always false [knownConditionTrueFalse]\n", errout_str()); - check("bool f(bool a, bool b) { if(a && b && (!a)){} }"); + check("bool f(bool a, bool b) { if(a && b && (!a)){} }\n"); ASSERT_EQUALS("[test.cpp:1:29] -> [test.cpp:1:40]: (style) Condition '!a' is always false [knownConditionTrueFalse]\n", errout_str()); check("void f(int x, int y) {\n" @@ -4893,7 +5060,7 @@ class TestCondition : public TestFixture { " return (index++) >= s;\n" " }\n" "}\n"); - ASSERT_EQUALS("[test.cpp:2:15] -> [test.cpp:6:26]: (style) Return value '(index++)>=s' is always false [knownConditionTrueFalse]\n", errout_str()); + ASSERT_EQUALS("", errout_str()); check("struct a {\n" " a *b() const;\n" @@ -5085,6 +5252,52 @@ class TestCondition : public TestFixture { " if (i < 0) {}\n" "}\n"); ASSERT_EQUALS("", errout_str()); + + check("struct A { int x; int y; };\n" + "void use(int);\n" + "void test(A a) {\n" + " int i = a.x;\n" + " int j = a.x;\n" + " use(j);\n" + " if (i == j) {}\n" + " if (i == a.x) {}\n" + " if (j == a.x) {}\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:7:11]: (style) Condition 'i==j' is always true [knownConditionTrueFalse]\n" + "[test.cpp:8:11]: (style) Condition 'i==a.x' is always true [knownConditionTrueFalse]\n" + "[test.cpp:9:11]: (style) Condition 'j==a.x' is always true [knownConditionTrueFalse]\n", + errout_str()); + + check("struct S { int i; };\n" // #12795 + "struct T {\n" + " std::map m;\n" + " S* get(const std::string& s) { return m[s]; }\n" + " void modify() { for (const auto& e : m) e.second->i = 0; }\n" + "};\n" + "void f(T& t) {\n" + " const S* p = t.get(\"abc\");\n" + " const int o = p->i;\n" + " t.modify();\n" + " if (p->i == o) {}\n" + "}\n"); + TODO_ASSERT_EQUALS("", + "[test.cpp:11:14]: (style) Condition 'p->i==o' is always true [knownConditionTrueFalse]\n", + errout_str()); + + check("void f(int x) {\n" // #12320 + " int a = 0, b = 0, c = 0;\n" + " a = x;\n" + " if (a) b = x;\n" + " if (b) c = x;\n" + " if (c) {}\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("void f(int a, int b) {\n" // #11437 + " a = a < b ? b : a;\n" + " if (a != b) {}\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); } void alwaysTrueInfer() { @@ -5093,7 +5306,7 @@ class TestCondition : public TestFixture { " x++;\n" " if (x == 1) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11] -> [test.cpp:4:15]: (style) Condition 'x==1' is always false [knownConditionTrueFalse]\n", errout_str()); check("void f(int x) {\n" @@ -5101,7 +5314,7 @@ class TestCondition : public TestFixture { " x++;\n" " if (x != 1) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11] -> [test.cpp:4:15]: (style) Condition 'x!=1' is always true [knownConditionTrueFalse]\n", errout_str()); // #6890 @@ -5113,7 +5326,7 @@ class TestCondition : public TestFixture { " if (x == -1) {}\n" " else {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:11] -> [test.cpp:6:15]: (style) Condition 'x==-1' is always false [knownConditionTrueFalse]\n", errout_str()); check("void f(int i) {\n" @@ -5124,7 +5337,7 @@ class TestCondition : public TestFixture { " if (x != -1) {}\n" " else {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:11] -> [test.cpp:6:15]: (style) Condition 'x!=-1' is always true [knownConditionTrueFalse]\n", errout_str()); check("void f(int i) {\n" @@ -5135,7 +5348,7 @@ class TestCondition : public TestFixture { " if (x >= -1) {}\n" " else {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:11] -> [test.cpp:6:15]: (style) Condition 'x>=-1' is always true [knownConditionTrueFalse]\n", errout_str()); check("void f(int i) {\n" @@ -5146,7 +5359,7 @@ class TestCondition : public TestFixture { " if (x > -1) {}\n" " else {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:11] -> [test.cpp:6:15]: (style) Condition 'x>-1' is always true [knownConditionTrueFalse]\n", errout_str()); check("void f(int i) {\n" @@ -5157,7 +5370,7 @@ class TestCondition : public TestFixture { " if (x < -1) {}\n" " else {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:11] -> [test.cpp:6:15]: (style) Condition 'x<-1' is always false [knownConditionTrueFalse]\n", errout_str()); check("void f(int i) {\n" @@ -5168,7 +5381,7 @@ class TestCondition : public TestFixture { " if (x <= -1) {}\n" " else {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:11] -> [test.cpp:6:15]: (style) Condition 'x<=-1' is always false [knownConditionTrueFalse]\n", errout_str()); check("void f(int i) {\n" @@ -5179,7 +5392,7 @@ class TestCondition : public TestFixture { " if (x > 7) {}\n" " else {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:11] -> [test.cpp:6:15]: (style) Condition 'x>7' is always true [knownConditionTrueFalse]\n", errout_str()); check("void f(int i) {\n" @@ -5190,7 +5403,7 @@ class TestCondition : public TestFixture { " if (x > 9) {}\n" " else {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int i) {\n" @@ -5201,7 +5414,7 @@ class TestCondition : public TestFixture { " if (x > 10) {}\n" " else {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #11100 @@ -5226,7 +5439,7 @@ class TestCondition : public TestFixture { " if(pos > 0)\n" " --pos;\n" " return pos;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:13] -> [test.cpp:5:16]: (style) Condition 'pos>0' is always true [knownConditionTrueFalse]\n", errout_str()); // #9721 @@ -5235,7 +5448,7 @@ class TestCondition : public TestFixture { " if ( (x>255) || (-128>x) )\n" " return;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11] -> [test.cpp:3:30]: (style) Condition '-128>x' is always false [knownConditionTrueFalse]\n", errout_str()); // #8778 @@ -5249,7 +5462,7 @@ class TestCondition : public TestFixture { check("void f() {\n" " for(int x = 0; x < 3; ++x)\n" " if(x == -5) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:14]: (style) Condition 'x==-5' is always false [knownConditionTrueFalse]\n", errout_str()); // #8407 @@ -5258,7 +5471,7 @@ class TestCondition : public TestFixture { " if(i == 0) return 1; \n" // << " else return 0;\n" " return -1;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:14]: (style) Condition 'i==0' is always true [knownConditionTrueFalse]\n", errout_str()); check("void f(unsigned int u1, unsigned int u2) {\n" @@ -5284,7 +5497,7 @@ class TestCondition : public TestFixture { check("bool f(const int *p, const int *q) {\n" " return p != NULL && q != NULL && p == NULL;\n" "}\n"); - ASSERT_EQUALS("[test.cpp:2:40]: (style) Return value 'p==NULL' is always false [knownConditionTrueFalse]\n", errout_str()); + ASSERT_EQUALS("[test.cpp:2:40]: (style) Condition 'p==NULL' is always false [knownConditionTrueFalse]\n", errout_str()); check("struct S {\n" // #11789 " std::vector v;\n" @@ -5316,6 +5529,21 @@ class TestCondition : public TestFixture { " std::size_t x;\n" "};\n"); ASSERT_EQUALS("", errout_str()); + + check("int g();\n" // #13441 + "void f() {\n" + " int (*p)() = g;\n" + " if (p == nullptr) {}\n" + " if (g == nullptr) {}\n" + " auto q = g;\n" + " if (q == nullptr) {}\n" + "};\n"); + TODO_ASSERT_EQUALS("[test.cpp:4:11]: (style) Condition 'p==nullptr' is always false [knownConditionTrueFalse]\n" + "[test.cpp:5:11]: (style) Condition 'g==nullptr' is always false [knownConditionTrueFalse]\n" + "[test.cpp:7:11]: (style) Condition 'q==nullptr' is always false [knownConditionTrueFalse]\n", + "[test.cpp:4:11]: (style) Condition 'p==nullptr' is always false [knownConditionTrueFalse]\n" + "[test.cpp:5:11]: (style) Condition 'g==nullptr' is always false [knownConditionTrueFalse]\n", + errout_str()); } void alwaysTrueContainer() { @@ -5330,7 +5558,7 @@ class TestCondition : public TestFixture { " c2(g );\n" " if ( !g.empty() )\n" " return;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int flag) {\n" @@ -5341,7 +5569,7 @@ class TestCondition : public TestFixture { " c2(g );\n" " if ( !g.empty() )\n" " return;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct A {\n" @@ -5412,7 +5640,7 @@ class TestCondition : public TestFixture { check("bool f(const std::string& a, const std::string& b) {\n" " return a.empty() || (b.empty() && a.empty());\n" "}\n"); - ASSERT_EQUALS("[test.cpp:2:19] -> [test.cpp:2:46]: (style) Return value 'a.empty()' is always false [knownConditionTrueFalse]\n", errout_str()); + ASSERT_EQUALS("[test.cpp:2:19] -> [test.cpp:2:46]: (style) Condition 'a.empty()' is always false [knownConditionTrueFalse]\n", errout_str()); check("struct A {\n" " struct iterator;\n" @@ -5552,7 +5780,7 @@ class TestCondition : public TestFixture { " {;}\n" " }\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:20] -> [test.cpp:7:27]: (style) Condition 'i==1' is always true [knownConditionTrueFalse]\n", errout_str()); // #10863 @@ -5581,7 +5809,7 @@ class TestCondition : public TestFixture { check("void f() {\n" // #8192 " for (int i = 0; i > 10; ++i) {}\n" "}\n"); - TODO_ASSERT_EQUALS("[test.cpp:2]: (style) Condition 'i>10' is always false\n", "", errout_str()); + ASSERT_EQUALS("[test.cpp:2:23]: (style) Condition 'i>10' is always false [knownConditionTrueFalse]\n", errout_str()); check("void f() {\n" " for (int i = 1000; i < 20; ++i) {}\n" @@ -5666,7 +5894,7 @@ class TestCondition : public TestFixture { void alwaysTruePremiumMisra() { const char code[] = "void f() {\n" " if (3 > 2) {}\n" - "}"; + "}\n"; check(code); ASSERT_EQUALS("", errout_str()); @@ -5682,7 +5910,7 @@ class TestCondition : public TestFixture { " int val = 0;\n" " if (val < 0) continue;\n" " if (val > 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -5690,7 +5918,7 @@ class TestCondition : public TestFixture { " if (val < 0) {\n" " if (val > 0) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -5698,7 +5926,7 @@ class TestCondition : public TestFixture { " if (val < 0) {\n" " if (val < 0) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -5706,7 +5934,7 @@ class TestCondition : public TestFixture { " int foo = 0;\n" " if (activate) {}\n" " else if (foo) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:7]: (style) Condition 'activate' is always false [knownConditionTrueFalse]\n" "[test.cpp:5:12]: (style) Condition 'foo' is always false [knownConditionTrueFalse]\n", errout_str()); @@ -5714,7 +5942,7 @@ class TestCondition : public TestFixture { check("void f() {\n" " const int b[2] = { 1,0 };\n" " if(b[1] == 2) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:11]: (style) Condition 'b[1]==2' is always false [knownConditionTrueFalse]\n", errout_str()); // #9878 @@ -5731,40 +5959,40 @@ class TestCondition : public TestFixture { check("void f(bool x) {\n" " if(x) {}\n" " if(x) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:8] -> [test.cpp:3:8]: (style) The if condition is the same as the previous if condition [duplicateCondition]\n", errout_str()); check("void f(int x) {\n" " if(x == 1) {}\n" " if(x == 1) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:10] -> [test.cpp:3:10]: (style) The if condition is the same as the previous if condition [duplicateCondition]\n", errout_str()); check("void f(int x) {\n" " if(x == 1) {}\n" " if(x == 2) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" " if(x == 1) {}\n" " if(x != 1) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(bool x) {\n" " if(x) {}\n" " g();\n" " if(x) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" " if(x == 1) { x++; }\n" " if(x == 1) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #8996 @@ -5776,7 +6004,7 @@ class TestCondition : public TestFixture { " g(d);\n" " if (a) {}\n" " if (b) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #9311 @@ -5791,7 +6019,7 @@ class TestCondition : public TestFixture { " g(d);\n" " if (a) {}\n" " if (b) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #8993 @@ -5799,7 +6027,7 @@ class TestCondition : public TestFixture { " auto y = x;\n" " if (x.empty()) y = \"1\";\n" " if (y.empty()) return;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #9106 @@ -5807,7 +6035,7 @@ class TestCondition : public TestFixture { "void f(A a, int c) {\n" " if (a.b) a.b = c;\n" " if (a.b) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct A {\n" @@ -5929,7 +6157,7 @@ class TestCondition : public TestFixture { " ;\n" " if (other.mPA.cols > 0 && other.mPA.rows > 0)\n" " ;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" // #11202 @@ -5978,27 +6206,27 @@ class TestCondition : public TestFixture { void checkInvalidTestForOverflow() { check("void f(char *p, unsigned int x) {\n" " assert((p + x) < p);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:20]: (warning) Invalid test for overflow '(p+x)= p);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:20]: (warning) Invalid test for overflow '(p+x)>=p'; pointer overflow is undefined behavior. Some mainstream compilers remove such overflow tests when optimising the code and assume it's always true. [invalidTestForOverflow]\n", errout_str()); check("void f(char *p, unsigned int x) {\n" " assert(p > (p + x));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:14]: (warning) Invalid test for overflow 'p>(p+x)'; pointer overflow is undefined behavior. Some mainstream compilers remove such overflow tests when optimising the code and assume it's always false. [invalidTestForOverflow]\n", errout_str()); check("void f(char *p, unsigned int x) {\n" " assert(p <= (p + x));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:14]: (warning) Invalid test for overflow 'p<=(p+x)'; pointer overflow is undefined behavior. Some mainstream compilers remove such overflow tests when optimising the code and assume it's always true. [invalidTestForOverflow]\n", errout_str()); check("void f(signed int x) {\n" // unsigned overflow => don't warn " assert(x + 100U < x);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); @@ -6006,57 +6234,57 @@ class TestCondition : public TestFixture { #define MSG(EXPR, RESULT) "[test.cpp:1:30]: (warning) Invalid test for overflow '" EXPR "'; signed integer overflow is undefined behavior. Some mainstream compilers remove such overflow tests when optimising the code and assume it's always " RESULT ". [invalidTestForOverflow]\n" - check("int f(int x) { return x + 10 > x; }"); + check("int f(int x) { return x + 10 > x; }\n"); ASSERT_EQUALS(MSG("x+10>x", "true"), errout_str()); - check("int f(int x) { return x + 10 >= x; }"); + check("int f(int x) { return x + 10 >= x; }\n"); ASSERT_EQUALS(MSG("x+10>=x", "true"), errout_str()); - check("int f(int x) { return x + 10 < x; }"); + check("int f(int x) { return x + 10 < x; }\n"); ASSERT_EQUALS(MSG("x+10 x; }"); + check("int f(int x) { return x - 10 > x; }\n"); ASSERT_EQUALS(MSG("x-10>x", "false"), errout_str()); - check("int f(int x) { return x - 10 >= x; }"); + check("int f(int x) { return x - 10 >= x; }\n"); ASSERT_EQUALS(MSG("x-10>=x", "false"), errout_str()); - check("int f(int x) { return x - 10 < x; }"); + check("int f(int x) { return x - 10 < x; }\n"); ASSERT_EQUALS(MSG("x-10 x; }"); + check("int f(int x, int y) { return x + y > x; }\n"); ASSERT_EQUALS(MSG("x+y>x", "y>0"), errout_str()); - check("int f(int x, int y) { return x + y >= x; }"); + check("int f(int x, int y) { return x + y >= x; }\n"); ASSERT_EQUALS(MSG("x+y>=x", "y>=0"), errout_str()); // x - y < x - check("int f(int x, int y) { return x - y < x; }"); + check("int f(int x, int y) { return x - y < x; }\n"); ASSERT_EQUALS(MSG("x-y0"), errout_str()); - check("int f(int x, int y) { return x - y <= x; }"); + check("int f(int x, int y) { return x - y <= x; }\n"); ASSERT_EQUALS(MSG("x-y<=x", "y>=0"), errout_str()); - check("int f(int x, int y) { return x - y > x; }"); + check("int f(int x, int y) { return x - y > x; }\n"); ASSERT_EQUALS(MSG("x-y>x", "y<0"), errout_str()); - check("int f(int x, int y) { return x - y >= x; }"); + check("int f(int x, int y) { return x - y >= x; }\n"); ASSERT_EQUALS(MSG("x-y>=x", "y<=0"), errout_str()); } @@ -6065,62 +6293,62 @@ class TestCondition : public TestFixture { " enum states {A,B,C};\n" " const unsigned g_flags = B|C;\n" " if(g_flags & A) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:16]: (style) Condition 'g_flags&A' is always false [knownConditionTrueFalse]\n", errout_str()); check("void f() {\n" " int a = 5;" " if(a) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:22]: (style) Condition 'a' is always true [knownConditionTrueFalse]\n", errout_str()); check("void f() {\n" " int a = 5;" " while(a + 1) { a--; }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " int a = 5;" " while(a + 1) { return; }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:27]: (style) Condition 'a+1' is always true [knownConditionTrueFalse]\n", errout_str()); } void alwaysTrueFalseInLogicalOperators() { check("bool f();\n" - "void foo() { bool x = true; if(x||f()) {}}"); + "void foo() { bool x = true; if(x||f()) {}}\n"); ASSERT_EQUALS("[test.cpp:2:32]: (style) Condition 'x' is always true [knownConditionTrueFalse]\n", errout_str()); - check("void foo(bool b) { bool x = true; if(x||b) {}}"); + check("void foo(bool b) { bool x = true; if(x||b) {}}\n"); ASSERT_EQUALS("[test.cpp:1:38]: (style) Condition 'x' is always true [knownConditionTrueFalse]\n", errout_str()); - check("void foo(bool b) { if(true||b) {}}"); + check("void foo(bool b) { if(true||b) {}}\n"); ASSERT_EQUALS("", errout_str()); check("bool f();\n" - "void foo() { bool x = false; if(x||f()) {}}"); + "void foo() { bool x = false; if(x||f()) {}}\n"); ASSERT_EQUALS("[test.cpp:2:33]: (style) Condition 'x' is always false [knownConditionTrueFalse]\n", errout_str()); check("bool f();\n" - "void foo() { bool x = false; if(x&&f()) {}}"); + "void foo() { bool x = false; if(x&&f()) {}}\n"); ASSERT_EQUALS("[test.cpp:2:33]: (style) Condition 'x' is always false [knownConditionTrueFalse]\n", errout_str()); - check("void foo(bool b) { bool x = false; if(x&&b) {}}"); + check("void foo(bool b) { bool x = false; if(x&&b) {}}\n"); ASSERT_EQUALS("[test.cpp:1:39]: (style) Condition 'x' is always false [knownConditionTrueFalse]\n", errout_str()); - check("void foo(bool b) { if(false&&b) {}}"); + check("void foo(bool b) { if(false&&b) {}}\n"); ASSERT_EQUALS("", errout_str()); check("bool f();\n" - "void foo() { bool x = true; if(x&&f()) {}}"); + "void foo() { bool x = true; if(x&&f()) {}}\n"); ASSERT_EQUALS("[test.cpp:2:32]: (style) Condition 'x' is always true [knownConditionTrueFalse]\n", errout_str()); // #9578 check("bool f(const std::string &s) {\n" " return s.size()>2U && s[0]=='4' && s[0]=='2';\n" "}\n"); - ASSERT_EQUALS("[test.cpp:2:35] -> [test.cpp:2:48]: (style) Return value 's[0]=='2'' is always false [knownConditionTrueFalse]\n", errout_str()); + ASSERT_EQUALS("[test.cpp:2:35] -> [test.cpp:2:48]: (style) Condition 's[0]=='2'' is always false [knownConditionTrueFalse]\n", errout_str()); check("void f(int i) { if (i == 1 || 2) {} }\n"); // #12487 ASSERT_EQUALS("[test.cpp:1:28]: (style) Condition 'i==1||2' is always true [knownConditionTrueFalse]\n", errout_str()); @@ -6128,12 +6356,26 @@ class TestCondition : public TestFixture { check("enum E { E1 = 1, E2 = 2 };\n" "void f(int i) { if (i == E1 || E2) {} }\n"); ASSERT_EQUALS("[test.cpp:2:29]: (style) Condition 'i==E1||E2' is always true [knownConditionTrueFalse]\n", errout_str()); + + check("void f(bool a, bool b) {\n" // #11614 + " if (b) {\n" + " bool x = !b || a;\n" + " }\n" + "}\n" + "void g(bool a, bool b) {\n" + " if (!b) {\n" + " bool x = a || b;\n" + " }\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:2:9] -> [test.cpp:3:18]: (style) Condition '!b' is always false [knownConditionTrueFalse]\n" + "[test.cpp:7:9] -> [test.cpp:8:23]: (style) Condition 'b' is always false [knownConditionTrueFalse]\n", + errout_str()); } void pointerAdditionResultNotNull() { check("void f(char *ptr) {\n" " if (ptr + 1 != 0);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:15]: (warning) Comparison is wrong. Result of 'ptr+1' can't be 0 unless there is pointer overflow, and pointer overflow is undefined behaviour. [pointerAdditionResultNotNull]\n", errout_str()); } @@ -6143,7 +6385,7 @@ class TestCondition : public TestFixture { check("void f(int& x, int y) {\n" " if (x == y)\n" " x = y;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:11]: style: Assignment 'x=y' is redundant with condition 'x==y'. [duplicateConditionalAssign]\n" "[test.cpp:2:11]: note: Condition 'x==y'\n" "[test.cpp:3:11]: note: Assignment 'x=y' is redundant\n", errout_str()); @@ -6151,7 +6393,7 @@ class TestCondition : public TestFixture { check("void f(int& x, int y) {\n" " if (x != y)\n" " x = y;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11]: style: The statement 'if (x!=y) x=y' is logically equivalent to 'x=y'. [duplicateConditionalAssign]\n" "[test.cpp:3:11]: note: Assignment 'x=y'\n" "[test.cpp:2:11]: note: Condition 'x!=y' is redundant\n", errout_str()); @@ -6161,7 +6403,7 @@ class TestCondition : public TestFixture { " x = y;\n" " else\n" " x = 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:11]: style: Assignment 'x=y' is redundant with condition 'x==y'. [duplicateConditionalAssign]\n" "[test.cpp:2:11]: note: Condition 'x==y'\n" "[test.cpp:3:11]: note: Assignment 'x=y' is redundant\n", errout_str()); @@ -6171,13 +6413,13 @@ class TestCondition : public TestFixture { " x = y;\n" " else\n" " x = 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int& x, int y) {\n" " if (x == y)\n" " x = y + 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void g();\n" @@ -6186,7 +6428,7 @@ class TestCondition : public TestFixture { " x = y;\n" " g();\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("bool f(bool b) {\n" @@ -6227,25 +6469,69 @@ class TestCondition : public TestFixture { "[test.cpp:7:19]: note: Assignment 'b=false'\n" "[test.cpp:7:13]: note: Condition '!b' is redundant\n", errout_str()); + + check("void f(bool& b) {\n" // #14915 + " if (b == true)\n" + " b = false;\n" + "}\n" + "void g(bool& b) {\n" + " if (b == false)\n" + " b = false;\n" + "}\n" + "void h(bool& b) {\n" + " if (b != true)\n" + " b = false;\n" + "}\n" + "void i(bool& b) {\n" + " if (b != false)\n" + " b = false;\n" + "}\n" + "void j(bool& b) {\n" + " if (b == true)\n" + " b = true;\n" + "}\n" + "void k(bool& b) {\n" + " if (true == b)\n" + " b = false;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:2:11]: style: The statement 'if (b==true) b=false' is logically equivalent to 'b=false'. [duplicateConditionalAssign]\n" + "[test.cpp:3:11]: note: Assignment 'b=false'\n" + "[test.cpp:2:11]: note: Condition 'b==true' is redundant\n" + "[test.cpp:6:11]: style: The statement 'if (b==false) b=false' is redundant. [duplicateConditionalAssign]\n" + "[test.cpp:7:11]: note: Assignment 'b=false'\n" + "[test.cpp:6:11]: note: Condition 'b==false' is redundant\n" + "[test.cpp:10:11]: style: The statement 'if (b!=true) b=false' is redundant. [duplicateConditionalAssign]\n" + "[test.cpp:11:11]: note: Assignment 'b=false'\n" + "[test.cpp:10:11]: note: Condition 'b!=true' is redundant\n" + "[test.cpp:14:11]: style: The statement 'if (b!=false) b=false' is logically equivalent to 'b=false'. [duplicateConditionalAssign]\n" + "[test.cpp:15:11]: note: Assignment 'b=false'\n" + "[test.cpp:14:11]: note: Condition 'b!=false' is redundant\n" + "[test.cpp:18:11]: style: The statement 'if (b==true) b=true' is redundant. [duplicateConditionalAssign]\n" + "[test.cpp:19:11]: note: Assignment 'b=true'\n" + "[test.cpp:18:11]: note: Condition 'b==true' is redundant\n" + "[test.cpp:22:14]: style: The statement 'if (true==b) b=false' is logically equivalent to 'b=false'. [duplicateConditionalAssign]\n" + "[test.cpp:23:11]: note: Assignment 'b=false'\n" + "[test.cpp:22:14]: note: Condition 'true==b' is redundant\n", + errout_str()); } void checkAssignmentInCondition() { check("void f(std::string s) {\n" " if (s=\"123\"){}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:10]: (style) Suspicious assignment in condition. Condition 's=\"123\"' is always true. [assignmentInCondition]\n", errout_str()); check("void f(std::string *p) {\n" " if (p=foo()){}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); + // the assignments are always false/true.. but "assignment in condition" would be a different checker + // knownConditionTrueFalse should only warn if code can be removed. check("void f(uint32_t u) {\n" // #2490 " if ((u = 0x00000000) || (u = 0xffffffff)) {}\n" "}\n"); - ASSERT_EQUALS("[test.cpp:2:12]: (style) Condition 'u=0x00000000' is always false [knownConditionTrueFalse]\n" - "[test.cpp:2:32]: (style) Condition 'u=0xffffffff' is always true [knownConditionTrueFalse]\n", - errout_str()); + ASSERT_EQUALS("", errout_str()); } void compareOutOfTypeRange() { @@ -6253,65 +6539,65 @@ class TestCondition : public TestFixture { check("void f(unsigned char c) {\n" " if (c == 256) {}\n" - "}", settingsUnix64); + "}\n", settingsUnix64); ASSERT_EQUALS("[test.cpp:2:12]: (style) Comparing expression of type 'unsigned char' against value 256. Condition is always false. [compareValueOutOfTypeRangeError]\n", errout_str()); check("void f(unsigned char* b, int i) {\n" // #6372 " if (b[i] == 256) {}\n" - "}", settingsUnix64); + "}\n", settingsUnix64); ASSERT_EQUALS("[test.cpp:2:15]: (style) Comparing expression of type 'unsigned char' against value 256. Condition is always false. [compareValueOutOfTypeRangeError]\n", errout_str()); check("void f(unsigned char c) {\n" " if (c == 255) {}\n" - "}", settingsUnix64); + "}\n", settingsUnix64); ASSERT_EQUALS("", errout_str()); check("void f(bool b) {\n" " if (b == true) {}\n" - "}", settingsUnix64); + "}\n", settingsUnix64); ASSERT_EQUALS("", errout_str()); // #10372 check("void f(signed char x) {\n" " if (x == 0xff) {}\n" - "}", settingsUnix64); + "}\n", settingsUnix64); ASSERT_EQUALS("[test.cpp:2:12]: (style) Comparing expression of type 'signed char' against value 255. Condition is always false. [compareValueOutOfTypeRangeError]\n", errout_str()); check("void f(short x) {\n" " if (x == 0xffff) {}\n" - "}", settingsUnix64); + "}\n", settingsUnix64); ASSERT_EQUALS("[test.cpp:2:12]: (style) Comparing expression of type 'signed short' against value 65535. Condition is always false. [compareValueOutOfTypeRangeError]\n", errout_str()); check("void f(int x) {\n" " if (x == 0xffffffff) {}\n" - "}", settingsUnix64); + "}\n", settingsUnix64); ASSERT_EQUALS("", errout_str()); check("void f(long x) {\n" " if (x == ~0L) {}\n" - "}", settingsUnix64); + "}\n", settingsUnix64); ASSERT_EQUALS("", errout_str()); check("void f(long long x) {\n" " if (x == ~0LL) {}\n" - "}", settingsUnix64); + "}\n", settingsUnix64); ASSERT_EQUALS("", errout_str()); check("int f(int x) {\n" " const int i = 0xFFFFFFFF;\n" " if (x == i) {}\n" - "}", settingsUnix64); + "}\n", settingsUnix64); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " char c;\n" " if ((c = foo()) != -1) {}\n" - "}", settingsUnix64); + "}\n", settingsUnix64); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" " if (x < 3000000000) {}\n" - "}", settingsUnix64); + "}\n", settingsUnix64); ASSERT_EQUALS("[test.cpp:2:11]: (style) Comparing expression of type 'signed int' against value 3000000000. Condition is always true. [compareValueOutOfTypeRangeError]\n", errout_str()); check("void f(const signed char i) {\n" // #8545 @@ -6366,6 +6652,32 @@ class TestCondition : public TestFixture { ASSERT_EQUALS("[test.cpp:3:14]: (style) Comparing expression of type 'const unsigned int &' against value 4294967295. Condition is always true. [compareValueOutOfTypeRangeError]\n" "[test.cpp:4:13]: (style) Comparing expression of type 'const unsigned int &' against value 4294967295. Condition is always false. [compareValueOutOfTypeRangeError]\n", errout_str()); + + check("void f() {\n" + " long long ll = 1024 * 1024 * 1024;\n" + " if (ll * 8 < INT_MAX) {}\n" + " if (INT_MAX > ll * 8) {}\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("bool f(int a, int b) {\n" // #12896 + " if (a < INT_MIN && b > INT_MAX)\n" + " return true;\n" + " return false;\n" + "}\n" + "bool g(int x) {\n" // #6796 + " return (x > INT_MAX) ? true : false;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:2:13]: (style) Comparing expression of type 'signed int' against value -2147483648. Condition is always false. [compareValueOutOfTypeRangeError]\n" + "[test.cpp:2:28]: (style) Comparing expression of type 'signed int' against value 2147483647. Condition is always false. [compareValueOutOfTypeRangeError]\n" + "[test.cpp:7:17]: (style) Comparing expression of type 'signed int' against value 2147483647. Condition is always false. [compareValueOutOfTypeRangeError]\n", + errout_str()); + + check("void f(char c) {\n" // #11592 + " if (c == 128) {}\n" + "}\n", settingsUnix64); + ASSERT_EQUALS("[test.cpp:2:14]: (style) Comparing expression of type 'char' against value 128. Condition is always false. [compareValueOutOfTypeRangeError]\n", + errout_str()); } void knownConditionCast() { @@ -6425,7 +6737,7 @@ class TestCondition : public TestFixture { " }\n" "\n" " S1 mS;\n" - "};" + "};\n" ); ASSERT_EQUALS("[test.cpp:13:11] -> [test.cpp:18:13]: (style) Condition 'mS.b' is always false [knownConditionTrueFalse]\n", errout_str()); } diff --git a/test/testconstructors.cpp b/test/testconstructors.cpp index 54af2d0a1c3..47a5512fc79 100644 --- a/test/testconstructors.cpp +++ b/test/testconstructors.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -52,7 +52,7 @@ class TestConstructors : public TestFixture { ASSERT_LOC(tokenizer.tokenize(code), file, line); // Check class constructors.. - CheckClass checkClass(&tokenizer, &settings1, this); + CheckClassImpl checkClass(&tokenizer, settings1, *this); checkClass.constructors(); } @@ -92,6 +92,7 @@ class TestConstructors : public TestFixture { TEST_CASE(noConstructor13); // #9998 TEST_CASE(noConstructor14); // #10770 TEST_CASE(noConstructor15); // #5499 + TEST_CASE(noConstructor16); TEST_CASE(forwardDeclaration); // ticket #4290/#3190 @@ -231,6 +232,18 @@ class TestConstructors : public TestFixture { TEST_CASE(uninitTemplate1); // ticket #7372 TEST_CASE(unknownTemplateType); + + TEST_CASE(inheritedCtor1); + TEST_CASE(inheritedCtor2); + TEST_CASE(inheritedCtor3); + TEST_CASE(inheritedCtor4); + TEST_CASE(inheritedCtor5); + TEST_CASE(inheritedCtor6); + TEST_CASE(inheritedCtor7); + TEST_CASE(inheritedCtor8); + TEST_CASE(inheritedCtor9); + TEST_CASE(inheritedCtor10); + TEST_CASE(inheritedCtor11); } @@ -239,21 +252,21 @@ class TestConstructors : public TestFixture { "{\n" "public:\n" " int i;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("class Fred\n" "{\n" "private:\n" " int i;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:1:1]: (style) The class 'Fred' does not declare a constructor although it has private member variables which likely require initialization. [noConstructor]\n", errout_str()); check("struct Fred\n" "{\n" "private:\n" " int i;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:1:1]: (style) The struct 'Fred' does not declare a constructor although it has private member variables which likely require initialization. [noConstructor]\n", errout_str()); } @@ -266,7 +279,7 @@ class TestConstructors : public TestFixture { " Fred(Fred const & other) : i(other.i) {}\n" " Fred(Fred && other) : i(other.i) {}\n" " int i;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("class Fred\n" @@ -276,7 +289,7 @@ class TestConstructors : public TestFixture { " Fred(Fred const & other) {i=other.i}\n" " Fred(Fred && other) {i=other.i}\n" " int i;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("class Fred\n" @@ -286,7 +299,7 @@ class TestConstructors : public TestFixture { " Fred(Fred const & other) {}\n" " Fred(Fred && other) {}\n" " int i;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:5]: (warning) Member variable 'Fred::i' is not initialized in the constructor. [uninitMemberVar]\n" "[test.cpp:5:5]: (warning) Member variable 'Fred::i' is not initialized in the copy constructor. [uninitMemberVar]\n" "[test.cpp:6:5]: (warning) Member variable 'Fred::i' is not initialized in the move constructor. [uninitMemberVar]\n", errout_str()); @@ -297,7 +310,7 @@ class TestConstructors : public TestFixture { " Fred(Fred const & other) {}\n" " Fred(Fred && other) {}\n" " int i;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:5]: (warning) Member variable 'Fred::i' is not initialized in the constructor. [uninitMemberVar]\n" "[test.cpp:4:5]: (warning) Member variable 'Fred::i' is not initialized in the copy constructor. [uninitMemberVar]\n" "[test.cpp:5:5]: (warning) Member variable 'Fred::i' is not initialized in the move constructor. [uninitMemberVar]\n", errout_str()); @@ -311,7 +324,7 @@ class TestConstructors : public TestFixture { " int i;\n" "};\n" "Fred::Fred() :i(0)\n" - "{ }"); + "{ }\n"); ASSERT_EQUALS("", errout_str()); check("struct Fred\n" @@ -320,7 +333,7 @@ class TestConstructors : public TestFixture { " int i;\n" "};\n" "Fred::Fred()\n" - "{ i = 0; }"); + "{ i = 0; }\n"); ASSERT_EQUALS("", errout_str()); check("struct Fred\n" @@ -329,7 +342,7 @@ class TestConstructors : public TestFixture { " int i;\n" "};\n" "Fred::Fred()\n" - "{ }"); + "{ }\n"); ASSERT_EQUALS("[test.cpp:6:7]: (warning) Member variable 'Fred::i' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); } @@ -364,7 +377,7 @@ class TestConstructors : public TestFixture { " }\n" "private:\n" " int mValue;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -374,7 +387,7 @@ class TestConstructors : public TestFixture { " A(const T & t) { x = t.x; }\n" "private:\n" " int x;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("template struct A {\n" @@ -382,7 +395,7 @@ class TestConstructors : public TestFixture { " A(const T & t) : x(t.x) { }\n" "private:\n" " int x;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("template struct A {\n" @@ -391,7 +404,7 @@ class TestConstructors : public TestFixture { "private:\n" " int x;\n" " int y;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:2:5]: (warning) Member variable 'A::y' is not initialized in the constructor. [uninitMemberVar]\n" "[test.cpp:3:5]: (warning) Member variable 'A::y' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); } @@ -400,14 +413,14 @@ class TestConstructors : public TestFixture { check("class Fred;\n" "struct Fred {\n" " int x;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } void simple8() { check("struct Fred { int x; };\n" "class Barney { Fred fred; };\n" - "class Wilma { struct Betty { int x; } betty; };"); + "class Wilma { struct Betty { int x; } betty; };\n"); ASSERT_EQUALS("[test.cpp:2:1]: (style) The class 'Barney' does not declare a constructor although it has private member variables which likely require initialization. [noConstructor]\n" "[test.cpp:3:1]: (style) The class 'Wilma' does not declare a constructor although it has private member variables which likely require initialization. [noConstructor]\n", errout_str()); } @@ -418,7 +431,7 @@ class TestConstructors : public TestFixture { " Fred() : x(0) { }\n" "private:\n" " int x;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -428,7 +441,7 @@ class TestConstructors : public TestFixture { " Fred() = default;\n" "private:\n" " int x;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:5]: (warning) Member variable 'Fred::x' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); check("struct S {\n" // #9391 @@ -456,7 +469,7 @@ class TestConstructors : public TestFixture { " int b{1}, c{2};\n" " int d, e{3};\n" " int f{4}, g;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:5]: (warning) Member variable 'Fred::d' is not initialized in the constructor. [uninitMemberVar]\n" "[test.cpp:3:5]: (warning) Member variable 'Fred::g' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); } @@ -468,7 +481,7 @@ class TestConstructors : public TestFixture { " Fred() { Init(); }\n" " void Init(int i = 0);\n" "};\n" - "void Fred::Init(int i) { x = i; }"); + "void Fred::Init(int i) { x = i; }\n"); ASSERT_EQUALS("", errout_str()); check("class Fred {\n" @@ -478,7 +491,7 @@ class TestConstructors : public TestFixture { " Fred() { Init(0); }\n" " void Init(int i, int j = 0);\n" "};\n" - "void Fred::Init(int i, int j) { x = i; y = j; }"); + "void Fred::Init(int i, int j) { x = i; y = j; }\n"); ASSERT_EQUALS("", errout_str()); } @@ -486,7 +499,7 @@ class TestConstructors : public TestFixture { check("class Fred {\n" " int x=1;\n" " int *y=0;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -499,7 +512,7 @@ class TestConstructors : public TestFixture { " {}\n" "private:\n" " int x;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("class Fred : public Base {" @@ -510,7 +523,7 @@ class TestConstructors : public TestFixture { " {}\n" "private:\n" " int x;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -589,7 +602,7 @@ class TestConstructors : public TestFixture { check("class Fred\n" "{\n" " int i;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:1:1]: (style) The class 'Fred' does not declare a constructor although it has private member variables which likely require initialization. [noConstructor]\n", errout_str()); } @@ -601,7 +614,7 @@ class TestConstructors : public TestFixture { "};\n" "\n" "void Fred::foobar()\n" - "{ }"); + "{ }\n"); ASSERT_EQUALS("", errout_str()); } @@ -610,7 +623,7 @@ class TestConstructors : public TestFixture { "{\n" "private:\n" " static int foobar;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -619,7 +632,7 @@ class TestConstructors : public TestFixture { "{\n" "public:\n" " int foobar;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -627,7 +640,7 @@ class TestConstructors : public TestFixture { check("namespace Foo\n" "{\n" " int i;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -641,14 +654,14 @@ class TestConstructors : public TestFixture { "private:\n" " cpucyclesT m_v;\n" " bool m_b;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } void noConstructor7() { // ticket #4391 check("short bar;\n" - "class foo;"); + "class foo;\n"); ASSERT_EQUALS("", errout_str()); } @@ -656,7 +669,7 @@ class TestConstructors : public TestFixture { // ticket #4404 check("class LineSegment;\n" "class PointArray { };\n" - "void* tech_ = NULL;"); + "void* tech_ = NULL;\n"); ASSERT_EQUALS("", errout_str()); } @@ -667,7 +680,7 @@ class TestConstructors : public TestFixture { " CGreeting() : CGreetingBase(), MessageSet(false) {}\n" "private:\n" " bool MessageSet;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -687,28 +700,28 @@ class TestConstructors : public TestFixture { " virtual ~A();\n" "private:\n" " wxTimer *WxTimer1;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } void noConstructor11() { // #3552 check("class Fred { int x; };\n" - "union U { int y; Fred fred; };"); + "union U { int y; Fred fred; };\n"); ASSERT_EQUALS("", errout_str()); } void noConstructor12() { // #8951 - check("class Fred { int x{0}; };"); + check("class Fred { int x{0}; };\n"); ASSERT_EQUALS("", errout_str()); - check("class Fred { int x=0; };"); + check("class Fred { int x=0; };\n"); ASSERT_EQUALS("", errout_str()); - check("class Fred { int x[1]={0}; };"); // #8850 + check("class Fred { int x[1]={0}; };\n"); // #8850 ASSERT_EQUALS("", errout_str()); - check("class Fred { int x[1]{0}; };"); + check("class Fred { int x[1]{0}; };\n"); ASSERT_EQUALS("", errout_str()); } @@ -758,19 +771,190 @@ class TestConstructors : public TestFixture { ASSERT_EQUALS("[test.cpp:3:5]: (warning) Member variable 'C::i2' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); } + void noConstructor16() { + check("struct S {\n" // #14546 + " int a = 0, b;\n" + "};\n"); + ASSERT_EQUALS("[test.cpp:2:16]: (warning) Member variable 'S::b' has no initializer. [uninitMemberVarNoCtor]\n", errout_str()); + + check("struct S {\n" + " int a, b;\n" + "};\n"); + ASSERT_EQUALS("", errout_str()); + + check("struct S {\n" + " explicit S(int);\n" + " S(const S&);\n" + " int i;\n" + "};\n" + "struct T {\n" + " S s;\n" + " int j{};\n" + "};\n"); + ASSERT_EQUALS("", errout_str()); + + const char code[] = "struct S { int i = 0; };\n" // #14697 + "struct T {\n" + " S s;\n" + " int j;\n" + "};\n" + "struct U {\n" + " std::string a;\n" + " int k;\n" + "};\n"; + const Settings s = settingsBuilder(settings).cpp(Standards::CPP11).build(); + check(code, s); + ASSERT_EQUALS("", errout_str()); + check(code); + ASSERT_EQUALS("[test.cpp:4:9]: (warning) Member variable 'T::j' has no initializer. [uninitMemberVarNoCtor]\n" + "[test.cpp:8:9]: (warning) Member variable 'U::k' has no initializer. [uninitMemberVarNoCtor]\n", + errout_str()); + + check("struct S {\n" // #15080 + " int a = 0;\n" + " const int b;\n" + " int k;\n" + "};\n"); + ASSERT_EQUALS("", errout_str()); + + check("struct S {\n" + " int a = 0;\n" + " int& c;\n" + " int k;\n" + "};\n"); + ASSERT_EQUALS("", errout_str()); + + check("struct S {\n" + " int a = 0;\n" + " const int* d;\n" + "};\n"); + ASSERT_EQUALS("[test.cpp:3:16]: (warning) Member variable 'S::d' has no initializer. [uninitMemberVarNoCtor]\n", errout_str()); + + check("struct S {\n" + " int a = 0;\n" + " int* const p;\n" + " int k;\n" + "};\n"); + ASSERT_EQUALS("", errout_str()); + + check("struct S {\n" // static members don't affect the default constructor + " int a = 0;\n" + " static const int N;\n" + " static int& r;\n" + " int k;\n" + "};\n"); + ASSERT_EQUALS("[test.cpp:5:9]: (warning) Member variable 'S::k' has no initializer. [uninitMemberVarNoCtor]\n", errout_str()); + + check("struct S {\n" // function pointers returning a reference are no references + " int a = 0;\n" + " std::string& (*f)(int, int);\n" + " int& (*g[2])();\n" + " int k;\n" + "};\n"); + ASSERT_EQUALS("[test.cpp:3:20]: (warning) Member variable 'S::f' has no initializer. [uninitMemberVarNoCtor]\n" + "[test.cpp:4:12]: (warning) Member variable 'S::g' has no initializer. [uninitMemberVarNoCtor]\n" + "[test.cpp:5:9]: (warning) Member variable 'S::k' has no initializer. [uninitMemberVarNoCtor]\n", + errout_str()); + + check("struct S {\n" + " int a = 0;\n" + " int (*&f)();\n" + " int k;\n" + "};\n"); + ASSERT_EQUALS("", errout_str()); + + check("struct S {\n" + " int a = 0;\n" + " int (&arr)[2];\n" + " int k;\n" + "};\n"); + ASSERT_EQUALS("", errout_str()); + + check("typedef const unsigned int Mode;\n" + "struct S {\n" + " std::vector v;\n" + " Mode m;\n" + " int k;\n" + "};\n"); + ASSERT_EQUALS("", errout_str()); + + check("struct S {\n" + " std::string a;\n" + " const std::string s;\n" + " int k;\n" + "};\n"); + ASSERT_EQUALS("[test.cpp:4:9]: (warning) Member variable 'S::k' has no initializer. [uninitMemberVarNoCtor]\n", errout_str()); + + check("struct S {\n" + " const std::array a;\n" + " const std::vector::const_iterator it;\n" + "};\n" + "S f(const std::vector& v) {\n" + " return S{ {}, v.cbegin() };\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + const char code2[] = "struct S {\n" // #15177 + " int i = 0;\n" + " unsigned u : 1;\n" + " int a[];\n" + " int b[0];\n" + "};\n"; + const Settings s17 = settingsBuilder(settings).cpp(Standards::CPP17).build(); + check(code2, s17); + ASSERT_EQUALS("", errout_str()); + check(code2); + ASSERT_EQUALS("[test.cpp:3:14]: (warning) Member variable 'S::u' has no initializer. [uninitMemberVarNoCtor]\n", + errout_str()); + + const char code3[] = "struct S {\n" + " S() = default;\n" + " unsigned u : 1;\n" + "};\n"; + check(code3, s17); + ASSERT_EQUALS("[test.cpp:2:5]: (warning) Member variable 'S::u' is not initialized in the constructor. [uninitMemberVar]\n", + errout_str()); + check(code3); + ASSERT_EQUALS("[test.cpp:2:5]: (warning) Member variable 'S::u' is not initialized in the constructor. [uninitMemberVar]\n", + errout_str()); + + check("struct S {\n" + " int i = 0;\n" + " int a[];\n" + "};\n" + "struct T {\n" + " T() : i(0) {} \n" + " int i;\n" + " int a[];\n" + "};"); + ASSERT_EQUALS("", errout_str()); + + check("struct S {\n" + " static std::string s;\n" + " int x;\n" + "};\n" + "struct T {\n" + " static const int i = 0;\n" + " int x;\n" + "};"); + ASSERT_EQUALS("[test.cpp:3:9]: (warning) Member variable 'S::x' has no initializer. [uninitMemberVarNoCtor]\n" + "[test.cpp:7:9]: (warning) Member variable 'T::x' has no initializer. [uninitMemberVarNoCtor]\n", + errout_str()); + } + // ticket #4290 "False Positive: style (noConstructor): The class 'foo' does not have a constructor." // ticket #3190 "SymbolDatabase: Parse of sub class constructor fails" void forwardDeclaration() { check("class foo;\n" - "int bar;"); + "int bar;\n"); ASSERT_EQUALS("", errout_str()); check("class foo;\n" - "class foo;"); + "class foo;\n"); ASSERT_EQUALS("", errout_str()); check("class foo{};\n" - "class foo;"); + "class foo;\n"); ASSERT_EQUALS("", errout_str()); } @@ -780,7 +964,7 @@ class TestConstructors : public TestFixture { " Fred()\n" " { this->i = 0; }\n" " int i;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -795,7 +979,7 @@ class TestConstructors : public TestFixture { " i = 1;\n" " }\n" " int i;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -814,7 +998,7 @@ class TestConstructors : public TestFixture { "\n" " const Fred & operator=(const Fred &fred)\n" " { i = fred.i; return *this; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct Fred {\n" @@ -825,7 +1009,7 @@ class TestConstructors : public TestFixture { "\n" " const Fred & operator=(const Fred &fred)\n" " { i = fred.i; return *this; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct A\n" @@ -840,7 +1024,7 @@ class TestConstructors : public TestFixture { "\n" " int i;\n" " int j;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -851,7 +1035,7 @@ class TestConstructors : public TestFixture { " Fred() { i = 0; }\n" " void operator=(const Fred &fred) { }\n" " int i;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:10]: (warning) Member variable 'Fred::i' is not assigned a value in 'Fred::operator='. [operatorEqVarError]\n", errout_str()); } @@ -863,7 +1047,7 @@ class TestConstructors : public TestFixture { "private:\n" " void Init() { i = 0; }\n" " int i;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -880,7 +1064,7 @@ class TestConstructors : public TestFixture { " }\n" " return *this\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:6:12]: (warning) Member variable 'Fred::i' is not assigned a value in 'Fred::operator='. [operatorEqVarError]\n", errout_str()); check("class Fred\n" @@ -895,7 +1079,7 @@ class TestConstructors : public TestFixture { " }\n" " return *this\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:6:12]: (warning) Member variable 'Fred::i' is not assigned a value in 'Fred::operator='. [operatorEqVarError]\n", errout_str()); check("class Fred\n" @@ -910,7 +1094,7 @@ class TestConstructors : public TestFixture { " }\n" " return *this\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:6:12]: (warning) Member variable 'Fred::i' is not assigned a value in 'Fred::operator='. [operatorEqVarError]\n", errout_str()); check("class Fred\n" @@ -925,7 +1109,7 @@ class TestConstructors : public TestFixture { " }\n" " return *this\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -940,7 +1124,7 @@ class TestConstructors : public TestFixture { " Fred(rhs).swap(*this);\n" " return *this;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -950,7 +1134,7 @@ class TestConstructors : public TestFixture { " Fred & operator=(const Fred &rhs) {\n" " return *this;\n" " }\n" - "};",dinit(CheckOptions, $.inconclusive = true)); + "};\n",dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("[test.cpp:3:12]: (warning, inconclusive) Member variable 'Fred::data' is not assigned a value in 'Fred::operator='. [operatorEqVarError]\n", errout_str()); check("struct Fred {\n" @@ -958,7 +1142,7 @@ class TestConstructors : public TestFixture { " Fred & operator=(const Fred &rhs) {\n" " return *this;\n" " }\n" - "};",dinit(CheckOptions, $.inconclusive = true)); + "};\n",dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("[test.cpp:3:12]: (warning, inconclusive) Member variable 'Fred::ints' is not assigned a value in 'Fred::operator='. [operatorEqVarError]\n", errout_str()); check("struct Fred {\n" @@ -966,7 +1150,7 @@ class TestConstructors : public TestFixture { " Fred & operator=(const Fred &rhs) {\n" " return *this;\n" " }\n" - "};",dinit(CheckOptions, $.inconclusive = true)); + "};\n",dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("[test.cpp:3:12]: (warning, inconclusive) Member variable 'Fred::data' is not assigned a value in 'Fred::operator='. [operatorEqVarError]\n", errout_str()); } @@ -1072,7 +1256,7 @@ class TestConstructors : public TestFixture { " int b;\n" " Fred() { b = 0; }\n" " };\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); @@ -1092,7 +1276,7 @@ class TestConstructors : public TestFixture { " int b;\n" " Fred() { b = 0; }\n" " };\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); @@ -1112,7 +1296,7 @@ class TestConstructors : public TestFixture { " int b;\n" " Fred() { b = 0; }\n" " };\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); @@ -1134,7 +1318,7 @@ class TestConstructors : public TestFixture { " Fred() { b = 0; }\n" " };\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); @@ -1156,7 +1340,7 @@ class TestConstructors : public TestFixture { " Fred() { }\n" " };\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:7:13]: (warning) Member variable 'Fred::a' is not initialized in the constructor. [uninitMemberVar]\n" "[test.cpp:16:13]: (warning) Member variable 'Fred::b' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); @@ -1176,7 +1360,7 @@ class TestConstructors : public TestFixture { "c::c()\n" "{\n" " m_iMyInt1 = m_iMyInt2 = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1209,7 +1393,7 @@ class TestConstructors : public TestFixture { "void c::InitInt()\n" "{\n" " m_iMyInt = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1222,7 +1406,7 @@ class TestConstructors : public TestFixture { " Fred();\n" "};\n" "Fred::Fred() : s(NULL)\n" - "{ }"); + "{ }\n"); ASSERT_EQUALS("", errout_str()); check("struct Fred\n" @@ -1231,7 +1415,7 @@ class TestConstructors : public TestFixture { " Fred();\n" "};\n" "Fred::Fred()\n" - "{ s = NULL; }"); + "{ s = NULL; }\n"); ASSERT_EQUALS("", errout_str()); check("struct Fred\n" @@ -1240,7 +1424,7 @@ class TestConstructors : public TestFixture { " Fred();\n" "};\n" "Fred::Fred()\n" - "{ }"); + "{ }\n"); ASSERT_EQUALS("[test.cpp:6:7]: (warning) Member variable 'Fred::s' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); } @@ -1252,7 +1436,7 @@ class TestConstructors : public TestFixture { "private:\n" " void update() const;\n" " mutable int x;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -1263,7 +1447,7 @@ class TestConstructors : public TestFixture { "public:\n" " Fred() { }\n" " static void *p;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -1276,7 +1460,7 @@ class TestConstructors : public TestFixture { "\n" "private:\n" " std::map * values_{};\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -1294,7 +1478,7 @@ class TestConstructors : public TestFixture { " {\n" " U.a = 0;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("class Fred\n" @@ -1308,7 +1492,7 @@ class TestConstructors : public TestFixture { " Fred()\n" " {\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:9:5]: (warning) Member variable 'Fred::U' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); } @@ -1319,7 +1503,7 @@ class TestConstructors : public TestFixture { "public:\n" " A(int n) { }\n" " A() : A(42) {}\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:5]: (warning) Member variable 'A::number' is not initialized in the constructor. [uninitMemberVar]\n" "[test.cpp:5:5]: (warning) Member variable 'A::number' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); @@ -1328,7 +1512,7 @@ class TestConstructors : public TestFixture { "public:\n" " A(int n) { number = n; }\n" " A() : A(42) {}\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("class A {\n" @@ -1336,7 +1520,7 @@ class TestConstructors : public TestFixture { "public:\n" " A(int n) : A() { }\n" " A() {}\n" - "};", dinit(CheckOptions, $.inconclusive = true)); + "};\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("[test.cpp:4:5]: (warning) Member variable 'A::number' is not initialized in the constructor. [uninitMemberVar]\n" "[test.cpp:5:5]: (warning, inconclusive) Member variable 'A::number' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); @@ -1345,7 +1529,7 @@ class TestConstructors : public TestFixture { "public:\n" " A(int n) : A() { }\n" " A() { number = 42; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("class A {\n" @@ -1353,7 +1537,7 @@ class TestConstructors : public TestFixture { "public:\n" " A(int n) { }\n" " A() : A{42} {}\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:5]: (warning) Member variable 'A::number' is not initialized in the constructor. [uninitMemberVar]\n" "[test.cpp:5:5]: (warning) Member variable 'A::number' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); @@ -1362,7 +1546,7 @@ class TestConstructors : public TestFixture { "public:\n" " A(int n) { number = n; }\n" " A() : A{42} {}\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("class A {\n" @@ -1370,7 +1554,7 @@ class TestConstructors : public TestFixture { "public:\n" " A(int n) : A{} { }\n" " A() {}\n" - "};", dinit(CheckOptions, $.inconclusive = true)); + "};\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("[test.cpp:4:5]: (warning) Member variable 'A::number' is not initialized in the constructor. [uninitMemberVar]\n" "[test.cpp:5:5]: (warning, inconclusive) Member variable 'A::number' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); @@ -1379,7 +1563,7 @@ class TestConstructors : public TestFixture { "public:\n" " A(int n) : A{} { }\n" " A() { number = 42; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // Ticket #6675 @@ -1389,7 +1573,7 @@ class TestConstructors : public TestFixture { " int foo_;\n" "};\n" "Foo::Foo() : Foo(0) {}\n" - "Foo::Foo(int foo) : foo_(foo) {}"); + "Foo::Foo(int foo) : foo_(foo) {}\n"); ASSERT_EQUALS("", errout_str()); // Noexcept ctors @@ -1401,7 +1585,7 @@ class TestConstructors : public TestFixture { " A() noexcept;\n" "};\n" "\n" - "A::A() noexcept: A(0) {}"); + "A::A() noexcept: A(0) {}\n"); ASSERT_EQUALS("", errout_str()); // Ticket #8581 @@ -1411,7 +1595,7 @@ class TestConstructors : public TestFixture { "public:\n" " A(int a) : _a(a) {}\n" " A(float a) : A(int(a)) {}\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // Ticket #8258 @@ -1423,7 +1607,7 @@ class TestConstructors : public TestFixture { " int _a;\n" " int _b;\n" " F _f;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -1447,7 +1631,7 @@ class TestConstructors : public TestFixture { " : a(true)\n" " , b(0)\n" "{\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1462,7 +1646,7 @@ class TestConstructors : public TestFixture { "public:\n" " Derived() {}\n" " void foo() override;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:9:3]: (warning) Member variable 'Base::x' is not initialized in the constructor. Maybe it should be initialized directly in the class Base? [uninitDerivedMemberVar]\n", errout_str()); check("struct A {\n" // #3462 @@ -1549,7 +1733,7 @@ class TestConstructors : public TestFixture { "class C : public S {\n" "public:\n" " C() { all = 0; tick = 0; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct S {\n" @@ -1565,7 +1749,7 @@ class TestConstructors : public TestFixture { "class C : public S {\n" "public:\n" " C() { flag1 = flag2 = 0; tick = 0; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct S {\n" @@ -1581,7 +1765,7 @@ class TestConstructors : public TestFixture { "class C : public S {\n" "public:\n" " C() {}\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:13:5]: (warning) Member variable 'S::all' is not initialized in the constructor. Maybe it should be initialized directly in the class S? [uninitDerivedMemberVar]\n" "[test.cpp:13:5]: (warning) Member variable 'S::flag1' is not initialized in the constructor. Maybe it should be initialized directly in the class S? [uninitDerivedMemberVar]\n" "[test.cpp:13:5]: (warning) Member variable 'S::flag2' is not initialized in the constructor. Maybe it should be initialized directly in the class S? [uninitDerivedMemberVar]\n", errout_str()); @@ -1597,7 +1781,7 @@ class TestConstructors : public TestFixture { " Fred();\n" "};\n" "Fred::Fred()\n" - "{ }", s); + "{ }\n", s); ASSERT_EQUALS("[test.cpp:7:7]: (warning) Member variable 'Fred::var' is not initialized in the constructor. [uninitMemberVarPrivate]\n", errout_str()); } @@ -1610,7 +1794,7 @@ class TestConstructors : public TestFixture { " Fred();\n" "};\n" "Fred::Fred()\n" - "{ }", s); + "{ }\n", s); ASSERT_EQUALS("", errout_str()); } } @@ -1632,7 +1816,7 @@ class TestConstructors : public TestFixture { "public:\n" " Fred() { };\n" " Fred(const Fred &) { };\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("class Fred\n" @@ -1642,7 +1826,7 @@ class TestConstructors : public TestFixture { "public:\n" " Fred() { };\n" " Fred(const Fred &) { };\n" - "};", dinit(CheckOptions, $.inconclusive = true)); + "};\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("[test.cpp:7:5]: (warning, inconclusive) Member variable 'Fred::var' is not assigned in the copy constructor. Should it be copied? [missingMemberCopy]\n", errout_str()); check("class Fred\n" @@ -1690,7 +1874,7 @@ class TestConstructors : public TestFixture { "A::A(){}\n" "A::B::B(int x){}\n" "A::B::C::C(int y){}\n" - "A::B::C::D::D(int z){}"); + "A::B::C::D::D(int z){}\n"); // Note that the example code is not compilable. The A constructor must // explicitly initialize A::b. A warning for A::b is not necessary. ASSERT_EQUALS("[test.cpp:20:4]: (warning) Member variable 'A::a' is not initialized in the constructor. [uninitMemberVar]\n" @@ -1720,7 +1904,7 @@ class TestConstructors : public TestFixture { "A::A(){}\n" "A::B::B(int x){}\n" "A::B::C::C(int y){}\n" - "A::B::C::D::D(const A::B::C::D & d){}"); + "A::B::C::D::D(const A::B::C::D & d){}\n"); // Note that the example code is not compilable. The A constructor must // explicitly initialize A::b. A warning for A::b is not necessary. ASSERT_EQUALS("[test.cpp:20:4]: (warning) Member variable 'A::a' is not initialized in the constructor. [uninitMemberVar]\n" @@ -1751,7 +1935,7 @@ class TestConstructors : public TestFixture { "A::A(){}\n" "A::B::B(int x){}\n" "A::B::C::C(int y){}\n" - "A::B::C::D::D(const A::B::C::D::E & e){}"); + "A::B::C::D::D(const A::B::C::D::E & e){}\n"); // Note that the example code is not compilable. The A constructor must // explicitly initialize A::b. A warning for A::b is not necessary. ASSERT_EQUALS("[test.cpp:21:4]: (warning) Member variable 'A::a' is not initialized in the constructor. [uninitMemberVar]\n" @@ -1773,7 +1957,7 @@ class TestConstructors : public TestFixture { " A(const A&){}\n" " A(A &&){}\n" " const A& operator=(const A&){return *this;}\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("class B\n" @@ -1788,7 +1972,7 @@ class TestConstructors : public TestFixture { " A(const A&){}\n" " A(A &&){}\n" " const A& operator=(const A&){return *this;}\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("class B\n" @@ -1805,7 +1989,7 @@ class TestConstructors : public TestFixture { " A(const A&){}\n" " A(A &&){}\n" " const A& operator=(const A&){return *this;}\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("class B\n" @@ -1821,7 +2005,7 @@ class TestConstructors : public TestFixture { " A(const A&){}\n" " A(A &&){}\n" " const A& operator=(const A&){return *this;}\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("class A : public std::vector\n" @@ -1837,7 +2021,7 @@ class TestConstructors : public TestFixture { " B(const B&){}\n" " B(B &&){}\n" " const B& operator=(const B&){return *this;}\n" - "};", dinit(CheckOptions, $.inconclusive = true)); + "};\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("[test.cpp:11:5]: (warning, inconclusive) Member variable 'B::a' is not assigned in the copy constructor. Should it be copied? [missingMemberCopy]\n" "[test.cpp:12:5]: (warning, inconclusive) Member variable 'B::a' is not assigned in the move constructor. Should it be moved? [missingMemberCopy]\n" "[test.cpp:13:14]: (warning, inconclusive) Member variable 'B::a' is not assigned a value in 'B::operator='. [operatorEqVarError]\n", @@ -1857,7 +2041,7 @@ class TestConstructors : public TestFixture { " A(const A&){}\n" " A(A &&){}\n" " const A& operator=(const A&){return *this;}\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:13:5]: (warning) Member variable 'A::m_SemVar' is not initialized in the move constructor. [uninitMemberVar]\n", errout_str()); check("class B\n" @@ -1875,7 +2059,7 @@ class TestConstructors : public TestFixture { " A(const A&){}\n" " A(A &&){}\n" " const A& operator=(const A&){return *this;}\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("class A\n" @@ -1885,7 +2069,7 @@ class TestConstructors : public TestFixture { " A(){}\n" " A(const A&){}\n" " const A& operator=(const A&){return *this;}\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -1903,7 +2087,7 @@ class TestConstructors : public TestFixture { " A(){}\n" " A(const A&){}\n" " const A& operator=(const A&){return *this;}\n" - "};", dinit(CheckOptions, $.inconclusive = true)); + "};\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); check("class B\n" @@ -1920,7 +2104,7 @@ class TestConstructors : public TestFixture { " A(){}\n" " A(const A&){}\n" " const A& operator=(const A&){return *this;}\n" - "};", dinit(CheckOptions, $.inconclusive = true)); + "};\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("[test.cpp:12:5]: (warning) Member variable 'A::m_SemVar' is not initialized in the constructor. [uninitMemberVar]\n" "[test.cpp:13:5]: (warning) Member variable 'A::m_SemVar' is not initialized in the copy constructor. [uninitMemberVar]\n" "[test.cpp:14:14]: (warning) Member variable 'A::m_SemVar' is not assigned a value in 'A::operator='. [operatorEqVarError]\n", errout_str()); @@ -1931,14 +2115,14 @@ class TestConstructors : public TestFixture { " B b;\n" " A() {}\n" " A(const A& rhs) {}\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct A {\n" " B b;\n" " A() {}\n" " A(const A& rhs) {}\n" - "};", dinit(CheckOptions, $.inconclusive = true)); + "};\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("[test.cpp:4:5]: (warning, inconclusive) Member variable 'A::b' is not assigned in the copy constructor. Should it be copied? [missingMemberCopy]\n", errout_str()); } @@ -1954,7 +2138,7 @@ class TestConstructors : public TestFixture { "}\n" "void S::Set(const T& val) {\n" " t = val;\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); } @@ -1963,7 +2147,7 @@ class TestConstructors : public TestFixture { " Foo(int m) { this->setMember(m); }\n" " void setMember(int m) { member = m; }\n" " int member;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -1975,7 +2159,7 @@ class TestConstructors : public TestFixture { "public:\n" " Fred() : var(0) {}\n" " ~Fred() {}\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -1983,7 +2167,7 @@ class TestConstructors : public TestFixture { check("class something {\n" " int * ( something :: * process()) () { return 0; }\n" " something() { process(); }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -1994,7 +2178,7 @@ class TestConstructors : public TestFixture { " int& pa = a;\n" " pa = 4;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct S {\n" @@ -2003,7 +2187,7 @@ class TestConstructors : public TestFixture { " int* pa = &a;\n" " *pa = 4;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct S {\n" @@ -2013,7 +2197,7 @@ class TestConstructors : public TestFixture { " for (int i = 0; i < 2; i++)\n" " *pa++ = i;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct S {\n" @@ -2022,7 +2206,7 @@ class TestConstructors : public TestFixture { " int* pa = a[1];\n" " *pa = 0;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:5]: (warning) Member variable 'S::a' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); check("struct S {\n" @@ -2031,7 +2215,7 @@ class TestConstructors : public TestFixture { " int pa = a;\n" " pa = 4;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:5]: (warning) Member variable 'S::a' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); } @@ -2050,7 +2234,7 @@ class TestConstructors : public TestFixture { " A() {\n" " Wrapper::foo(x);\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -2068,7 +2252,7 @@ class TestConstructors : public TestFixture { " d = rhs.get();\n" " }\n" " double d;\n" - "};", s); + "};\n", s); ASSERT_EQUALS("", errout_str()); check("struct S {\n" // #8485 @@ -2095,7 +2279,7 @@ class TestConstructors : public TestFixture { "{ }\n" "\n" "void Fred::operator=(const Fred &f)\n" - "{ }", dinit(CheckOptions, $.inconclusive = true)); + "{ }\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("[test.cpp:13:12]: (warning, inconclusive) Member variable 'Fred::ints' is not assigned a value in 'Fred::operator='. [operatorEqVarError]\n", errout_str()); const Settings s = settingsBuilder().certainty(Certainty::inconclusive).severity(Severity::style).severity(Severity::warning).library("std.cfg").build(); @@ -2120,7 +2304,7 @@ class TestConstructors : public TestFixture { "\n" "private:\n" " ECODES _code;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:10:5]: (warning) Member variable 'Fred::_code' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); @@ -2133,7 +2317,7 @@ class TestConstructors : public TestFixture { " B() {}\n" "private:\n" " float f;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:6:3]: (warning) Member variable 'B::f' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); check("class C\n" @@ -2146,7 +2330,7 @@ class TestConstructors : public TestFixture { "\n" "C::C(FILE *fp) {\n" " C::fp = fp;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2157,7 +2341,7 @@ class TestConstructors : public TestFixture { " John() { (*this).i = 0; }\n" "private:\n" " int i;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -2172,7 +2356,7 @@ class TestConstructors : public TestFixture { " Bar();\n" " };\n" " Bar bars[2];\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // Using struct that doesn't have constructor @@ -2185,7 +2369,7 @@ class TestConstructors : public TestFixture { " int x;\n" " };\n" " Bar bars[2];\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:5]: (warning) Member variable 'Foo::bars' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); } @@ -2199,7 +2383,7 @@ class TestConstructors : public TestFixture { " int x;\n" " };\n" " struct Bar bar;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -2211,7 +2395,7 @@ class TestConstructors : public TestFixture { " Foo &operator=(const Foo &)\n" " { return *this; }\n" " static int i;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -2222,7 +2406,7 @@ class TestConstructors : public TestFixture { " explicit Foo(int i) : Bar(mi=i) { }\n" "private:\n" " int mi;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("class Foo : public Bar\n" @@ -2231,7 +2415,7 @@ class TestConstructors : public TestFixture { " explicit Foo(int i) : Bar{mi=i} { }\n" "private:\n" " int mi;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -2252,7 +2436,7 @@ class TestConstructors : public TestFixture { " Foo copy(rhs);\n" " copy.Swap(*this);\n" " return *this;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2269,7 +2453,7 @@ class TestConstructors : public TestFixture { " {\n" " }\n" " return *this;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:11]: (warning) Member variable 'Foo::a' is not assigned a value in 'Foo::operator='. [operatorEqVarError]\n", errout_str()); } @@ -2283,7 +2467,7 @@ class TestConstructors : public TestFixture { "Prefs::Prefs(wxSize size)\n" "{\n" " SetMinSize( wxSize( 48,48 ) );\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:8]: (warning) Member variable 'Prefs::xasd' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); } @@ -2295,7 +2479,7 @@ class TestConstructors : public TestFixture { " int var1;\n" " int var2;\n" "};\n" - "A::A() : var1(0) { }"); + "A::A() : var1(0) { }\n"); ASSERT_EQUALS("[test.cpp:8:4]: (warning) Member variable 'A::var2' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); } @@ -2306,7 +2490,7 @@ class TestConstructors : public TestFixture { "private:\n" " int var;\n" "};\n" - "A::A(int a) { }"); + "A::A(int a) { }\n"); ASSERT_EQUALS("[test.cpp:7:4]: (warning) Member variable 'A::var' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); } @@ -2322,7 +2506,7 @@ class TestConstructors : public TestFixture { " : x(x), y(y)\n" " {}\n" " int x, y;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:5]: (warning) Member variable 'Point::x' is not initialized in the constructor. [uninitMemberVar]\n" "[test.cpp:4:5]: (warning) Member variable 'Point::y' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); } @@ -2334,7 +2518,7 @@ class TestConstructors : public TestFixture { "public:\n" " A()\n" " {}\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:5:5]: (warning) Member variable 'A::ints' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); } @@ -2349,7 +2533,7 @@ class TestConstructors : public TestFixture { "};\n" "Foo::Foo()\n" "{\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:6]: (warning) Member variable 'Foo::mMember' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); // single namespace @@ -2365,7 +2549,7 @@ class TestConstructors : public TestFixture { " Foo::Foo()\n" " {\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:10:10]: (warning) Member variable 'Foo::mMember' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); // constructor outside namespace @@ -2381,7 +2565,7 @@ class TestConstructors : public TestFixture { "}\n" "Foo::Foo()\n" "{\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // constructor outside namespace @@ -2397,7 +2581,7 @@ class TestConstructors : public TestFixture { "}\n" "Output::Foo::Foo()\n" "{\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:11:14]: (warning) Member variable 'Foo::mMember' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); // constructor outside namespace with using, #4792 @@ -2414,7 +2598,7 @@ class TestConstructors : public TestFixture { "using namespace Output;" "Foo::Foo()\n" "{\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:11:29]: (warning) Member variable 'Foo::mMember' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); // constructor in separate namespace @@ -2433,7 +2617,7 @@ class TestConstructors : public TestFixture { " Foo::Foo()\n" " {\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:13:10]: (warning) Member variable 'Foo::mMember' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); // constructor in different separate namespace @@ -2452,7 +2636,7 @@ class TestConstructors : public TestFixture { " Foo::Foo()\n" " {\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // constructor in different separate namespace (won't compile) @@ -2471,7 +2655,7 @@ class TestConstructors : public TestFixture { " Output::Foo::Foo()\n" " {\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // constructor in nested separate namespace @@ -2493,7 +2677,7 @@ class TestConstructors : public TestFixture { " {\n" " }\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:15:14]: (warning) Member variable 'Foo::mMember' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); // constructor in nested different separate namespace @@ -2515,7 +2699,7 @@ class TestConstructors : public TestFixture { " {\n" " }\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // constructor in nested different separate namespace @@ -2537,7 +2721,7 @@ class TestConstructors : public TestFixture { " {\n" " }\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2551,7 +2735,7 @@ class TestConstructors : public TestFixture { "};\n" "Fred::~Fred()\n" "{\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2569,7 +2753,7 @@ class TestConstructors : public TestFixture { " {\n" " foo.set(0);\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct Foo\n" @@ -2584,7 +2768,7 @@ class TestConstructors : public TestFixture { " Bar()\n" " {\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:10:5]: (warning) Member variable 'Bar::foo' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); } @@ -2601,7 +2785,7 @@ class TestConstructors : public TestFixture { " {\n" " foo[0].a = 0;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct Foo\n" @@ -2615,7 +2799,7 @@ class TestConstructors : public TestFixture { " Bar()\n" " {\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:9:5]: (warning) Member variable 'Bar::foo' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); } @@ -2631,7 +2815,7 @@ class TestConstructors : public TestFixture { " A() { }\n" "private:\n" " Altren value;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -2655,7 +2839,7 @@ class TestConstructors : public TestFixture { " {\n" " free(m_str);\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -2667,7 +2851,7 @@ class TestConstructors : public TestFixture { " std::streamsize dataLength_;\n" " double bitsInData_;\n" " } obj;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:9]: (warning) Member variable 'LocalClass::bitsInData_' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); check("struct copy_protected;\n" @@ -2678,7 +2862,7 @@ class TestConstructors : public TestFixture { " std::streamsize dataLength_;\n" " double bitsInData_;\n" " } obj;\n" - "};"); + "};\n"); ASSERT_EQUALS( "[test.cpp:5:9]: (warning) Member variable 'LocalClass::bitsInData_' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); @@ -2691,7 +2875,7 @@ class TestConstructors : public TestFixture { " std::streamsize dataLength_;\n" " double bitsInData_;\n" " } obj;\n" - "};"); + "};\n"); ASSERT_EQUALS( "[test.cpp:5:9]: (warning) Member variable 'LocalClass::bitsInData_' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); @@ -2706,7 +2890,7 @@ class TestConstructors : public TestFixture { "};\n" "Fred::Fred() {\n" " a[x::y] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2723,7 +2907,7 @@ class TestConstructors : public TestFixture { "Fred & Fred::clone(const Fred & other) {\n" " x = 0;\n" " return *this;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("class Fred {\n" @@ -2737,7 +2921,7 @@ class TestConstructors : public TestFixture { "}\n" "Fred & Fred::clone(const Fred & other) {\n" " return *this;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:14]: (warning) Member variable 'Fred::x' is not assigned a value in 'Fred::operator='. [operatorEqVarError]\n", errout_str()); } @@ -2755,7 +2939,7 @@ class TestConstructors : public TestFixture { "Fred::Fred(struct C c, D d) { }\n" "Fred::Fred(E e, struct F f) { }\n" "Fred::Fred(G g, H h) { }\n" - "Fred::Fred(struct I i, struct J j) { }"); + "Fred::Fred(struct I i, struct J j) { }\n"); ASSERT_EQUALS("[test.cpp:10:7]: (warning) Member variable 'Fred::x' is not initialized in the constructor. [uninitMemberVar]\n" "[test.cpp:11:7]: (warning) Member variable 'Fred::x' is not initialized in the constructor. [uninitMemberVar]\n" "[test.cpp:12:7]: (warning) Member variable 'Fred::x' is not initialized in the constructor. [uninitMemberVar]\n" @@ -2864,7 +3048,7 @@ class TestConstructors : public TestFixture { " A(int s) {\n" " v = new int [sz = s];\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -2875,7 +3059,7 @@ class TestConstructors : public TestFixture { " A() {\n" " rtl::math::setNan(&d);\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("class A {\n" " double d;\n" @@ -2883,7 +3067,7 @@ class TestConstructors : public TestFixture { " A() {\n" " ::rtl::math::setNan(&d);\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -2898,7 +3082,7 @@ class TestConstructors : public TestFixture { " }\n" " void foo(int a) { i = a; }\n" " void foo(float a) { f = a; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -2930,7 +3114,7 @@ class TestConstructors : public TestFixture { " D() { foo(); }\n" " void foo() { }\n" " void foo() const { i = 0; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:18:5]: (warning) Member variable 'C::i' is not initialized in the constructor. [uninitMemberVar]\n" "[test.cpp:25:5]: (warning) Member variable 'D::i' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); } @@ -2947,7 +3131,7 @@ class TestConstructors : public TestFixture { "}\n" "using namespace NS;\n" "MyClass::~MyClass() { }\n" - "MyClass::MyClass() : SomeVar(false) { }"); + "MyClass::MyClass() : SomeVar(false) { }\n"); ASSERT_EQUALS("", errout_str()); } @@ -2965,7 +3149,7 @@ class TestConstructors : public TestFixture { "}\n" "void MyClass::Restart() {\n" " m_retCode = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2978,7 +3162,7 @@ class TestConstructors : public TestFixture { " {\n" " if (1) {}\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:5:4]: (warning) Member variable 'Foo::member' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); check("class Foo {\n" " friend class Bar;\n" @@ -2988,7 +3172,7 @@ class TestConstructors : public TestFixture { " {\n" " while (1) {}\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:5:4]: (warning) Member variable 'Foo::member' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); check("class Foo {\n" " friend class Bar;\n" @@ -2998,7 +3182,7 @@ class TestConstructors : public TestFixture { " {\n" " for (;;) {}\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:5:4]: (warning) Member variable 'Foo::member' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); } @@ -3010,7 +3194,7 @@ class TestConstructors : public TestFixture { " int x;\n" " };\n" "};\n" - "app::B::B(void){}"); + "app::B::B(void){}\n"); ASSERT_EQUALS("[test.cpp:8:9]: (warning) Member variable 'B::x' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); } @@ -3062,7 +3246,7 @@ class TestConstructors : public TestFixture { "\n" "private:\n" " char name[255];\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:5]: (warning) Member variable 'John::name' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); check("class John\n" @@ -3072,7 +3256,7 @@ class TestConstructors : public TestFixture { "\n" "private:\n" " char name[255];\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("class John\n" @@ -3082,7 +3266,7 @@ class TestConstructors : public TestFixture { "\n" "private:\n" " char name[255];\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("class John\n" @@ -3091,7 +3275,7 @@ class TestConstructors : public TestFixture { " John() { }\n" "\n" " double operator[](const unsigned long i);\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("class A;\n" @@ -3100,7 +3284,7 @@ class TestConstructors : public TestFixture { "public:\n" " John() { }\n" " A a[5];\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("class A;\n" @@ -3109,7 +3293,7 @@ class TestConstructors : public TestFixture { "public:\n" " John() { }\n" " A (*a)[5];\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:5:5]: (warning) Member variable 'John::a' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); } @@ -3121,7 +3305,7 @@ class TestConstructors : public TestFixture { "\n" "private:\n" " char name[255];\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // #5754 @@ -3132,7 +3316,7 @@ class TestConstructors : public TestFixture { "\n" "private:\n" " char name[255];\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -3149,7 +3333,7 @@ class TestConstructors : public TestFixture { " memset(a,0,sizeof(a));\n" " memset(b,0,sizeof(b));\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -3166,7 +3350,7 @@ class TestConstructors : public TestFixture { " if (snprintf(a,10,\"a\")) { }\n" " if (snprintf(b,10,\"b\")) { }\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -3178,7 +3362,7 @@ class TestConstructors : public TestFixture { "public:\n" " Foo()\n" " { }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -3190,7 +3374,7 @@ class TestConstructors : public TestFixture { " static const char STR[];\n" "};\n" "const char Foo::STR[] = \"abc\";\n" - "Foo::Foo() { }"); + "Foo::Foo() { }\n"); ASSERT_EQUALS("", errout_str()); } @@ -3200,7 +3384,7 @@ class TestConstructors : public TestFixture { " int array[10];\n" "public:\n" " Foo() { }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:5:5]: (warning) Member variable 'Foo::array' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); check("class Foo\n" @@ -3208,7 +3392,7 @@ class TestConstructors : public TestFixture { " int array[10];\n" "public:\n" " Foo() { memset(array, 0, sizeof(array)); }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("class Foo\n" @@ -3216,7 +3400,7 @@ class TestConstructors : public TestFixture { " int array[10];\n" "public:\n" " Foo() { ::memset(array, 0, sizeof(array)); }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -3225,7 +3409,7 @@ class TestConstructors : public TestFixture { " char a[10];\n" "public:\n" " Foo() { ::ZeroMemory(a); }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -3236,7 +3420,7 @@ class TestConstructors : public TestFixture { " BaseGDL* eArr[3];\n" "public:\n" " IxExprListT() {}\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:6:5]: (warning) Member variable 'IxExprListT::eArr' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); check("struct sRAIUnitDefBL {\n" " sRAIUnitDefBL();\n" @@ -3245,7 +3429,7 @@ class TestConstructors : public TestFixture { "struct sRAIUnitDef {\n" " sRAIUnitDef() {}\n" " sRAIUnitDefBL *List[35];\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:6:3]: (warning) Member variable 'sRAIUnitDef::List' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); } @@ -3292,7 +3476,7 @@ class TestConstructors : public TestFixture { "\n" "private:\n" " char a[2][2];\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -3303,7 +3487,7 @@ class TestConstructors : public TestFixture { " char a[2][2][2];\n" "public:\n" " John() { a[0][0][0] = 0; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -3314,7 +3498,7 @@ class TestConstructors : public TestFixture { " Foo()\n" " : bar({\"a\", \"b\"})\n" " {}\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -3330,7 +3514,7 @@ class TestConstructors : public TestFixture { " Fred() : f{0, true} { }\n" " float get() const;\n" "};\n" - "float Fred::get() const { return g; }"); + "float Fred::get() const { return g; }\n"); ASSERT_EQUALS("[test.cpp:9:5]: (warning) Member variable 'Fred::g' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); } @@ -3346,7 +3530,7 @@ class TestConstructors : public TestFixture { "public:\n" " A() {\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("class A\n" @@ -3360,7 +3544,7 @@ class TestConstructors : public TestFixture { "public:\n" " A() {\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:10:5]: (warning) Member variable 'A::b' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); check("class A\n" @@ -3375,7 +3559,7 @@ class TestConstructors : public TestFixture { "public:\n" " A() {\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -3392,7 +3576,7 @@ class TestConstructors : public TestFixture { "public:\n" " Fred()\n" " { }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:11:5]: (warning) Member variable 'Fred::p' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); check("struct POINT\n" @@ -3408,7 +3592,7 @@ class TestConstructors : public TestFixture { "public:\n" " Fred()\n" " { }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct POINT\n" @@ -3424,7 +3608,7 @@ class TestConstructors : public TestFixture { "public:\n" " Fred()\n" " { }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // non static data-member initialization @@ -3440,7 +3624,7 @@ class TestConstructors : public TestFixture { "public:\n" " Fred()\n" " { }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -3452,13 +3636,13 @@ class TestConstructors : public TestFixture { "public:\n" " Fred()\n" " { a = 0; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("class Fred {\n" "private:\n" " union { int a{}; int b; };\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -3472,7 +3656,7 @@ class TestConstructors : public TestFixture { "public:\n" " Fred() : data_type(0)\n" " { }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:8:5]: (warning) Member variable 'Fred::data' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); } @@ -3485,7 +3669,7 @@ class TestConstructors : public TestFixture { "private:\n" " void Init();" " int i;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // Unknown non-member function (friend class) @@ -3496,7 +3680,7 @@ class TestConstructors : public TestFixture { "private:\n" " friend ABC;\n" " int i;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // Unknown non-member function (is Init a virtual function?) @@ -3506,7 +3690,7 @@ class TestConstructors : public TestFixture { " Fred() { Init(); }\n" "private:\n" " int i;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:5]: (warning) Member variable 'Fred::i' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); // Unknown non-member function @@ -3516,7 +3700,7 @@ class TestConstructors : public TestFixture { " Fred() { Init(); }\n" "private:\n" " int i;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:5]: (warning) Member variable 'Fred::i' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); // Unknown non-member function @@ -3527,7 +3711,7 @@ class TestConstructors : public TestFixture { " Fred() { Init(); }\n" "private:\n" " int i;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:5:5]: (warning) Member variable 'Fred::i' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); // Unknown member functions and unknown static functions @@ -3547,7 +3731,7 @@ class TestConstructors : public TestFixture { " static void static_f();\n" "private:\n" " int i;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:7:5]: (warning) Member variable 'Fred::i' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); // Unknown overloaded member functions @@ -3560,7 +3744,7 @@ class TestConstructors : public TestFixture { " void func();\n" "private:\n" " int i;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -3572,7 +3756,7 @@ class TestConstructors : public TestFixture { " Fred() {}\n" "private:\n" " unsigned int i;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:5:5]: (warning) Member variable 'Fred::i' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); } @@ -3583,7 +3767,7 @@ class TestConstructors : public TestFixture { "struct Y {\n" " Y() {}\n" " X x;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -3599,7 +3783,7 @@ class TestConstructors : public TestFixture { " if(!(in >> foo))\n" " throw 0;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -3611,7 +3795,7 @@ class TestConstructors : public TestFixture { " typedef int * pointer;\n" " Foo() : a(0) {}\n" " pointer a;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -3622,7 +3806,7 @@ class TestConstructors : public TestFixture { "public:\n" " int * pointer;\n" " Foo() { memset(this, 0, sizeof(*this)); }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); @@ -3631,7 +3815,7 @@ class TestConstructors : public TestFixture { "public:\n" " int * pointer;\n" " Foo() { ::memset(this, 0, sizeof(*this)); }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); @@ -3640,13 +3824,13 @@ class TestConstructors : public TestFixture { " int * p;\n" " char c;\n" " Foo() { memset(p, 0, sizeof(int)); }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:5]: (warning) Member variable 'Foo::c' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); check("struct Foo {\n" " int i;\n" " char c;\n" " Foo() { memset(&i, 0, sizeof(int)); }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:5]: (warning) Member variable 'Foo::c' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); check("struct Foo { int f; };\n" "struct Bar { int b; };\n" @@ -3660,7 +3844,7 @@ class TestConstructors : public TestFixture { "int main() {\n" " FooBar foobar;\n" " return foobar.foo.f + foobar.bar.b;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:3]: (warning) Member variable 'FooBar::bar' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); check("struct Foo { int f; };\n" "struct Bar { int b; };\n" @@ -3674,7 +3858,7 @@ class TestConstructors : public TestFixture { "int main() {\n" " FooBar foobar;\n" " return foobar.foo.f + foobar.bar.b;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:3]: (warning) Member variable 'FooBar::bar' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); // #7755 @@ -3683,7 +3867,7 @@ class TestConstructors : public TestFixture { " memset(this->data, 0, 42);\n" " }\n" " char data[42];\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -3693,7 +3877,7 @@ class TestConstructors : public TestFixture { check("class Foo {\n" " int foo;\n" " Foo() { }\n" - "};", s); + "};\n", s); ASSERT_EQUALS("", errout_str()); } @@ -3702,7 +3886,7 @@ class TestConstructors : public TestFixture { check("class Foo {\n" " int foo;\n" " Foo() { }\n" - "};", s); + "};\n", s); ASSERT_EQUALS("[test.cpp:3:5]: (warning) Member variable 'Foo::foo' is not initialized in the constructor. [uninitMemberVarPrivate]\n", errout_str()); } } @@ -3715,7 +3899,7 @@ class TestConstructors : public TestFixture { " Foo() { }\n" "public:\n" " explicit Foo(int _i) { }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:7:14]: (warning) Member variable 'Foo::foo' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); } @@ -3736,7 +3920,7 @@ class TestConstructors : public TestFixture { "int* A::f(int* p)\n" "{\n" " return p;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -3750,7 +3934,7 @@ class TestConstructors : public TestFixture { " int *i;\n" "public:\n" " Fred() { }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -3764,7 +3948,7 @@ class TestConstructors : public TestFixture { " Fred() { }\n" "private:\n" " int x;\n" - "};", s); + "};\n", s); ASSERT_EQUALS("", errout_str()); } @@ -3774,7 +3958,7 @@ class TestConstructors : public TestFixture { "public:\n" " Fred() { }\n" " int *operator [] (int index) { return 0; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -3788,7 +3972,7 @@ class TestConstructors : public TestFixture { " { f.d = 0; }\n" "\n" " double d;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("class Fred\n" @@ -3800,7 +3984,7 @@ class TestConstructors : public TestFixture { " { }\n" "\n" " double d;\n" - "};"); + "};\n"); TODO_ASSERT_EQUALS("[test.cpp:4:5]: (warning) Member variable 'Fred::d' is not initialized in the constructor. [uninitMemberVar]\n", "", errout_str()); } @@ -3814,7 +3998,7 @@ class TestConstructors : public TestFixture { " { f.d = 0; return true; }\n" "\n" " double d;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("class Fred\n" @@ -3826,7 +4010,7 @@ class TestConstructors : public TestFixture { " { return true; }\n" "\n" " double d;\n" - "};"); + "};\n"); TODO_ASSERT_EQUALS("[test.cpp:4:5]: (warning) Member variable 'Fred::d' is not initialized in the constructor. [uninitMemberVar]\n", "", errout_str()); } @@ -3840,7 +4024,7 @@ class TestConstructors : public TestFixture { " { d = 0; return true; }\n" "\n" " double d;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("class Fred\n" @@ -3852,7 +4036,7 @@ class TestConstructors : public TestFixture { " { return true; }\n" "\n" " double d;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:5]: (warning) Member variable 'Fred::d' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); } @@ -3866,7 +4050,7 @@ class TestConstructors : public TestFixture { " { f.d = 0; }\n" "\n" " double d;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("class Fred\n" @@ -3878,7 +4062,7 @@ class TestConstructors : public TestFixture { " { }\n" "\n" " double d;\n" - "};"); + "};\n"); TODO_ASSERT_EQUALS("[test.cpp:4:5]: (warning) Member variable 'Fred::d' is not initialized in the constructor. [uninitMemberVar]\n", "", errout_str()); } @@ -3892,7 +4076,7 @@ class TestConstructors : public TestFixture { "public:\n" " A() { Init( B ); };\n" " void Init( Structure& S ) { S.C = 0; };\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct Structure {\n" @@ -3904,7 +4088,7 @@ class TestConstructors : public TestFixture { "public:\n" " A() { Init( B ); };\n" " void Init(const Structure& S) { }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:8:5]: (warning) Member variable 'A::B' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); } @@ -3929,7 +4113,7 @@ class TestConstructors : public TestFixture { "A::B::B()\n" "{\n" " i = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("class B\n" @@ -3955,7 +4139,7 @@ class TestConstructors : public TestFixture { "\n" "A::B::B()\n" "{\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:18:4]: (warning) Member variable 'B::j' is not initialized in the constructor. [uninitMemberVar]\n" "[test.cpp:22:7]: (warning) Member variable 'B::i' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); @@ -3976,7 +4160,7 @@ class TestConstructors : public TestFixture { "public:\n" " Foo() { }\n" "};\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("namespace n1\n" @@ -3998,7 +4182,7 @@ class TestConstructors : public TestFixture { "public:\n" " Foo() { }\n" "};\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:11:10]: (warning) Member variable 'Foo::i' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); check("namespace n1\n" @@ -4020,7 +4204,7 @@ class TestConstructors : public TestFixture { "public:\n" " Foo() { }\n" "};\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -4041,7 +4225,7 @@ class TestConstructors : public TestFixture { "\n" " void init(int value)\n" " { i = value; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("class A\n" @@ -4059,7 +4243,7 @@ class TestConstructors : public TestFixture { "\n" " void init(int value)\n" " { }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:7:5]: (warning) Member variable 'A::i' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); check("class bar {\n" // #9887 @@ -4080,7 +4264,7 @@ class TestConstructors : public TestFixture { " int a;\n" " A() { a=0; }\n" " A(A const &a) { operator=(a); }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct A {\n" @@ -4091,7 +4275,7 @@ class TestConstructors : public TestFixture { " a = rhs.a;\n" " return *this;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct A {\n" @@ -4101,7 +4285,7 @@ class TestConstructors : public TestFixture { " A & operator = (const A & rhs) {\n" " return *this;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:5]: (warning) Member variable 'A::a' is not initialized in the copy constructor. [uninitMemberVar]\n" "[test.cpp:5:9]: (warning) Member variable 'A::a' is not assigned a value in 'A::operator='. [operatorEqVarError]\n", errout_str()); } @@ -4113,28 +4297,28 @@ class TestConstructors : public TestFixture { "struct B {\n" " A* a;\n" " B() { }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:6:5]: (warning) Member variable 'B::a' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); check("struct A;\n" "struct B {\n" " A* a;\n" " B() { }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:5]: (warning) Member variable 'B::a' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); check("struct A;\n" "struct B {\n" " const A* a;\n" " B() { }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:5]: (warning) Member variable 'B::a' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); check("struct A;\n" "struct B {\n" " A* const a;\n" " B() { }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:5]: (warning) Member variable 'B::a' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); check("class Test {\n" // #8498 @@ -4185,7 +4369,7 @@ class TestConstructors : public TestFixture { " A* const a;\n" " B() { }\n" " B(B& b) { }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:5]: (warning) Member variable 'B::a' is not initialized in the constructor. [uninitMemberVar]\n" "[test.cpp:5:5]: (warning) Member variable 'B::a' is not initialized in the copy constructor. [uninitMemberVar]\n", errout_str()); @@ -4193,21 +4377,21 @@ class TestConstructors : public TestFixture { "struct B {\n" " A* const a;\n" " B& operator=(const B& r) { }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // #3804 check("struct B {\n" " const int a;\n" " B() { }\n" " B(B& b) { }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:5]: (warning) Member variable 'B::a' is not initialized in the constructor. [uninitMemberVar]\n" "[test.cpp:4:5]: (warning) Member variable 'B::a' is not initialized in the copy constructor. [uninitMemberVar]\n", errout_str()); check("struct B {\n" " const int a;\n" " B& operator=(const B& r) { }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -4217,7 +4401,7 @@ class TestConstructors : public TestFixture { "public:\n" " C() _STLP_NOTHROW {}\n" " C(const C&) _STLP_NOTHROW {}\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -4226,7 +4410,7 @@ class TestConstructors : public TestFixture { " int mValue;\n" "public:\n" " operatorX() : mValue(0) {}\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -4236,7 +4420,7 @@ class TestConstructors : public TestFixture { " T* mElements;\n" "};\n" "template Container::Container() : mElements(nullptr) {}\n" - "Container intContainer;"); + "Container intContainer;\n"); ASSERT_EQUALS("", errout_str()); } @@ -4246,7 +4430,7 @@ class TestConstructors : public TestFixture { "public:\n" " SelectionPosition() {}\n" " const rvec &x() const;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -4261,7 +4445,7 @@ class TestConstructors : public TestFixture { " x = 1;\n" " return true;\n" " }\n" - "};", dinit(CheckOptions, $.inconclusive = true)); + "};\n", dinit(CheckOptions, $.inconclusive = true)); TODO_ASSERT_EQUALS("[test.cpp:3]: (warning, inconclusive) Member variable 'C::x' is not initialized in the constructor.\n", "[test.cpp:3:5]: (warning) Member variable 'C::x' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); @@ -4275,7 +4459,7 @@ class TestConstructors : public TestFixture { " x = 1;\n" " return true;\n" " }\n" - "};", dinit(CheckOptions, $.inconclusive = true)); + "};\n", dinit(CheckOptions, $.inconclusive = true)); TODO_ASSERT_EQUALS("[test.cpp:3]: (warning, inconclusive) Member variable 'C::x' is not initialized in the constructor.\n", "[test.cpp:3:5]: (warning) Member variable 'C::x' is not initialized in the constructor. [uninitMemberVar]\n", errout_str()); @@ -4289,7 +4473,7 @@ class TestConstructors : public TestFixture { " x = 1;\n" " return true;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct C {\n" @@ -4302,7 +4486,7 @@ class TestConstructors : public TestFixture { " x = 1;\n" " return true;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct C {\n" @@ -4312,7 +4496,7 @@ class TestConstructors : public TestFixture { " i = i * SetValue();\n" " }\n" " int SetValue() { return x = 1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct C {\n" @@ -4322,7 +4506,7 @@ class TestConstructors : public TestFixture { " i = i / SetValue();\n" " }\n" " int SetValue() { return x = 1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct C {\n" @@ -4332,7 +4516,7 @@ class TestConstructors : public TestFixture { " i = i % SetValue();\n" " }\n" " int SetValue() { return x = 1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct C {\n" @@ -4342,7 +4526,7 @@ class TestConstructors : public TestFixture { " i = i + SetValue();\n" " }\n" " int SetValue() { return x = 1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct C {\n" @@ -4352,7 +4536,7 @@ class TestConstructors : public TestFixture { " i = i - SetValue();\n" " }\n" " int SetValue() { return x = 1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct C {\n" @@ -4362,7 +4546,7 @@ class TestConstructors : public TestFixture { " i = i << SetValue();\n" " }\n" " int SetValue() { return x = 1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct C {\n" @@ -4372,7 +4556,7 @@ class TestConstructors : public TestFixture { " i = i >> SetValue();\n" " }\n" " int SetValue() { return x = 1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct C {\n" @@ -4382,7 +4566,7 @@ class TestConstructors : public TestFixture { " i = i ^ SetValue();\n" " }\n" " int SetValue() { return x = 1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -4397,7 +4581,7 @@ class TestConstructors : public TestFixture { " x = 1;\n" " return true;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct C {\n" @@ -4410,7 +4594,7 @@ class TestConstructors : public TestFixture { " x = 1;\n" " return true;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct C {\n" @@ -4420,7 +4604,7 @@ class TestConstructors : public TestFixture { " i *= SetValue();\n" " }\n" " int SetValue() { return x = 1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct C {\n" @@ -4430,7 +4614,7 @@ class TestConstructors : public TestFixture { " i /= SetValue();\n" " }\n" " int SetValue() { return x = 1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct C {\n" @@ -4440,7 +4624,7 @@ class TestConstructors : public TestFixture { " i %= SetValue();\n" " }\n" " int SetValue() { return x = 1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct C {\n" @@ -4450,7 +4634,7 @@ class TestConstructors : public TestFixture { " i += SetValue();\n" " }\n" " int SetValue() { return x = 1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct C {\n" @@ -4460,7 +4644,7 @@ class TestConstructors : public TestFixture { " i -= SetValue();\n" " }\n" " int SetValue() { return x = 1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct C {\n" @@ -4470,7 +4654,7 @@ class TestConstructors : public TestFixture { " i <<= SetValue();\n" " }\n" " int SetValue() { return x = 1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct C {\n" @@ -4480,7 +4664,7 @@ class TestConstructors : public TestFixture { " i >>= SetValue();\n" " }\n" " int SetValue() { return x = 1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct C {\n" @@ -4490,7 +4674,7 @@ class TestConstructors : public TestFixture { " i ^= SetValue();\n" " }\n" " int SetValue() { return x = 1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -4505,7 +4689,7 @@ class TestConstructors : public TestFixture { " x = 1;\n" " return true;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct C {\n" @@ -4518,7 +4702,7 @@ class TestConstructors : public TestFixture { " x = 1;\n" " return true;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct C {\n" @@ -4527,7 +4711,7 @@ class TestConstructors : public TestFixture { " bool b = (0 < SetValue());\n" " }\n" " int SetValue() { return x = 1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct C {\n" @@ -4536,7 +4720,7 @@ class TestConstructors : public TestFixture { " bool b = (0 <= SetValue());\n" " }\n" " int SetValue() { return x = 1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct C {\n" @@ -4545,7 +4729,7 @@ class TestConstructors : public TestFixture { " bool b = (0 > SetValue());\n" " }\n" " int SetValue() { return x = 1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct C {\n" @@ -4554,7 +4738,7 @@ class TestConstructors : public TestFixture { " bool b = (0 >= SetValue());\n" " }\n" " int SetValue() { return x = 1; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -4572,7 +4756,7 @@ class TestConstructors : public TestFixture { "class C {\n" " private:\n" " A* b;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("template class A{};\n" @@ -4584,7 +4768,7 @@ class TestConstructors : public TestFixture { " bool m_value;\n" "};\n" "template\n" - "A>::A() : m_value(false) {}"); + "A>::A() : m_value(false) {}\n"); ASSERT_EQUALS("", errout_str()); check("template struct S;\n" // #11177 @@ -4603,8 +4787,146 @@ class TestConstructors : public TestFixture { "public:\n" " A& operator=() { return *this; }\n" "};\n" - "A a;"); + "A a;\n"); + ASSERT_EQUALS("", errout_str()); + } + + void inheritedCtor1() { + check("struct Base { Base(int); };\n" + "struct Derived : Base {\n" + " using Base::Base;\n" + " int x;\n" + "};\n"); + ASSERT_EQUALS("[test.cpp:3:17]: (warning) Member variable 'Derived::x' is not initialized in the constructor inherited from 'Base'. [uninitMemberVar]\n", errout_str()); + } + + void inheritedCtor2() { + check("struct Base { Base(int); };\n" + "struct Derived : Base {\n" + " using Base::Base;\n" + " int x = 0;\n" + " int y{};\n" + "};\n"); + ASSERT_EQUALS("", errout_str()); + } + + void inheritedCtor3() { + check("struct Base { Base(int); };\n" + "struct S { S(); int i; };\n" + "struct Derived : Base {\n" + " using Base::Base;\n" + " S s;\n" + "};\n"); + ASSERT_EQUALS("", errout_str()); + } + + void inheritedCtor4() { + check("struct Base { Base(int); };\n" + "struct Derived : Base {\n" + " using Base::Base;\n" + " int x = 0;\n" + " int y;\n" + "};\n"); + ASSERT_EQUALS("[test.cpp:3:17]: (warning) Member variable 'Derived::y' is not initialized in the constructor inherited from 'Base'. [uninitMemberVar]\n", errout_str()); + } + + void inheritedCtor5() { + check("struct Base { Base(int); };\n" + "struct Derived : Base {\n" + " using Base::Base;\n" + " Derived() : x(0) {}\n" + " int x;\n" + "};\n"); + ASSERT_EQUALS("[test.cpp:3:17]: (warning) Member variable 'Derived::x' is not initialized in the constructor inherited from 'Base'. [uninitMemberVar]\n", errout_str()); + } + + void inheritedCtor6() { + check("struct A { A(int); };\n" + "struct B { B(double); };\n" + "struct D : A, B {\n" + " using A::A;\n" + " using B::B;\n" + " int x;\n" + "};\n"); + ASSERT_EQUALS("[test.cpp:4:14]: (warning) Member variable 'D::x' is not initialized in the constructor inherited from 'A'. [uninitMemberVar]\n" + "[test.cpp:5:14]: (warning) Member variable 'D::x' is not initialized in the constructor inherited from 'B'. [uninitMemberVar]\n", + errout_str()); + } + + void inheritedCtor7() { + check("struct Base { void f(); };\n" + "struct Derived : Base {\n" + " using Base::f;\n" + " int x = 0;\n" + " int y;\n" + "};\n"); + ASSERT_EQUALS("[test.cpp:5:9]: (warning) Member variable 'Derived::y' has no initializer. [uninitMemberVarNoCtor]\n", errout_str()); + } + + void inheritedCtor8() { + check("struct Base { Base(int); };\n" + "struct Derived : Base {\n" + " using Base::Base;\n" + " Derived(int i) : Base(i), x(0) {}\n" + " int x;\n" + "};\n"); + ASSERT_EQUALS("", errout_str()); + + check("struct Base { Base(int); Base(double); };\n" + "struct Derived : Base {\n" + " using Base::Base;\n" + " Derived(int i) : Base(i), x(0) {}\n" + " int x;\n" + "};\n"); + ASSERT_EQUALS("[test.cpp:3:17]: (warning) Member variable 'Derived::x' is not initialized in the constructor inherited from 'Base'. [uninitMemberVar]\n", errout_str()); + } + + void inheritedCtor9() { + check("struct Base { Base(); };\n" + "struct Derived : Base {\n" + " using Base::Base;\n" + " int x;\n" + "};\n"); ASSERT_EQUALS("", errout_str()); + + check("struct Base {};\n" + "struct Derived : Base {\n" + " using Base::Base;\n" + " int x;\n" + "};\n"); + ASSERT_EQUALS("", errout_str()); + + check("struct Base { Base(const Base&); Base(Base&&); };\n" + "struct Derived : Base {\n" + " using Base::Base;\n" + " int x;\n" + "};\n"); + ASSERT_EQUALS("", errout_str()); + } + + void inheritedCtor10() { + check("struct Base { Base(int) = delete; };\n" + "struct Derived : Base {\n" + " using Base::Base;\n" + " int x;\n" + "};\n"); + ASSERT_EQUALS("", errout_str()); + + check("class Base { Base(int); };\n" + "struct Derived : Base {\n" + " using Base::Base;\n" + " int x;\n" + "};\n"); + ASSERT_EQUALS("", errout_str()); + } + + void inheritedCtor11() { + check("namespace X { struct Base { Base(int); }; }\n" + "struct Derived : X::Base {\n" + " using X::Base::Base;\n" + " int x;\n" + "};\n"); + ASSERT_EQUALS("[test.cpp:3:20]: (warning) Member variable 'Derived::x' is not initialized in the constructor inherited from 'Base'. [uninitMemberVar]\n", errout_str()); } }; diff --git a/test/testcppcheck.cpp b/test/testcppcheck.cpp index 84db6db6c28..d88daceb0d9 100644 --- a/test/testcppcheck.cpp +++ b/test/testcppcheck.cpp @@ -17,6 +17,7 @@ */ #include "addoninfo.h" +#include "analyzerinfo.h" #include "color.h" #include "cppcheck.h" #include "errorlogger.h" @@ -42,6 +43,22 @@ #include +namespace { + class FilesDeleter { + public: + FilesDeleter() = default; + ~FilesDeleter() { + for (const std::string& fileName: mFilenames) + std::remove(fileName.c_str()); + } + void addFile(const std::string& fileName) { + mFilenames.push_back(fileName); + } + private: + std::vector mFilenames; + }; +} + class TestCppcheck : public TestFixture { public: TestCppcheck() : TestFixture("TestCppcheck") {} @@ -84,6 +101,7 @@ class TestCppcheck : public TestFixture { TEST_CASE(checkPlistOutput); TEST_CASE(premiumResultsCache); TEST_CASE(purgedConfiguration); + TEST_CASE(recheckInclude); } void getErrorMessages() const { @@ -175,7 +193,7 @@ class TestCppcheck : public TestFixture { "void f()\n" "{\n" " (void)(*((int*)0));\n" - "}"); + "}\n"); int called = 0; std::unordered_set addons; @@ -259,7 +277,7 @@ class TestCppcheck : public TestFixture { "void f()\n" "{\n" " (void)(*((int*)0));\n" - "}"); + "}\n"); int called = 0; std::unordered_set addons; @@ -342,7 +360,7 @@ class TestCppcheck : public TestFixture { "void f()\n" "{\n" " (void)(*((int*)0));\n" - "}"); + "}\n"); const char xmldata[] = R"()"; const Settings s = settingsBuilder().libraryxml(xmldata).build(); @@ -364,11 +382,11 @@ class TestCppcheck : public TestFixture { "inline void f()\n" "{\n" " (void)(*((int*)0));\n" - "}"); + "}\n"); ScopedFile test_file_a("a.c", - "#include \"inc.h\""); + "#include \"inc.h\"\n"); ScopedFile test_file_b("b.c", - "#include \"inc.h\""); + "#include \"inc.h\"\n"); // this is the "simple" format const auto s = dinit(Settings, $.templateFormat = templateFormat); // TODO: remove when we only longer rely on toString() in unique message handling @@ -400,7 +418,7 @@ class TestCppcheck : public TestFixture { "long a=m[9], b=m[9];\n" "(void)a;\n" "(void)b;\n" - "}"); + "}\n"); // this is the "simple" format const auto s = dinit(Settings, $.templateFormat = templateFormat); // TODO: remove when we only longer rely on toString() in unique message handling? @@ -490,7 +508,7 @@ class TestCppcheck : public TestFixture { ";\n"; simplecpp::OutputList outputList; - const simplecpp::TokenList tokens2(code, files, "", &outputList); + const simplecpp::TokenList tokens2(code, files, "", {}, &outputList); const std::string expected2 = " \n" " \n" " \n" @@ -527,7 +545,6 @@ class TestCppcheck : public TestFixture { void checkPlistOutput() const { Suppressions supprs; ErrorLogger2 errorLogger; - std::vector files = {"textfile.txt"}; { const auto s = dinit(Settings, $.templateFormat = templateFormat, $.plistOutput = "output"); @@ -535,7 +552,7 @@ class TestCppcheck : public TestFixture { CppCheck cppcheck(s, supprs, errorLogger, nullptr, false, {}); const FileWithDetails fileWithDetails {file.path(), Path::identify(file.path(), false), 0}; - cppcheck.checkPlistOutput(fileWithDetails, files); + cppcheck.checkPlistOutput(fileWithDetails); const std::string outputFile {"outputfile_" + std::to_string(std::hash {}(fileWithDetails.spath())) + ".plist"}; ASSERT(Path::exists(outputFile)); std::remove(outputFile.c_str()); @@ -547,7 +564,7 @@ class TestCppcheck : public TestFixture { CppCheck cppcheck(s, supprs, errorLogger, nullptr, false, {}); const FileWithDetails fileWithDetails {file.path(), Path::identify(file.path(), false), 0}; - cppcheck.checkPlistOutput(fileWithDetails, files); + cppcheck.checkPlistOutput(fileWithDetails); const std::string outputFile {"outputfile_" + std::to_string(std::hash {}(fileWithDetails.spath())) + ".plist"}; ASSERT(Path::exists(outputFile)); std::remove(outputFile.c_str()); @@ -557,7 +574,7 @@ class TestCppcheck : public TestFixture { Settings s; const ScopedFile file("file.c", ""); CppCheck cppcheck(s, supprs, errorLogger, nullptr, false, {}); - cppcheck.checkPlistOutput(FileWithDetails(file.path(), Path::identify(file.path(), false), 0), files); + cppcheck.checkPlistOutput(FileWithDetails(file.path(), Path::identify(file.path(), false), 0)); } } @@ -574,7 +591,7 @@ class TestCppcheck : public TestFixture { simplecpp::TokenList tokens(code, files, "m1.c"); Preprocessor preprocessor(tokens, settings, errorLogger, Standards::Language::C); - ASSERT(preprocessor.loadFiles(files)); + ASSERT(preprocessor.loadAllIncludes(files)); AddonInfo premiumaddon; premiumaddon.name = "premiumaddon.json"; @@ -622,6 +639,124 @@ class TestCppcheck : public TestFixture { it->toString(false, templateFormat, "")); } + void recheckInclude() const + { + const auto settings = dinit(Settings, + $.templateFormat = templateFormat, + $.debugainfo = true, + $.buildDir = "test-build-dir"); + + ScopedFile build_dir("empty", "", "test-build-dir"); + + FilesDeleter filesDeleter; + filesDeleter.addFile("test-build-dir/files.txt"); + filesDeleter.addFile("test-build-dir/test.a1"); + filesDeleter.addFile("test-build-dir/test.s1"); + + AnalyzerInformation::writeFilesTxt(settings.buildDir, {"test.cpp"}, {}); + + // First check + { + REDIRECT; + + ScopedFile source_file("test.cpp", + "#include \"test1.h\"\n" + "#include \"test2.h\"\n"); + ScopedFile header1_file("test1.h", + "class TestClass1 {};\n"); + ScopedFile header2_file("test2.h", + "class TestClass2 {};\n"); + + Suppressions suppressions; + ErrorLogger2 errorLogger; + CppCheck cppcheck(settings, suppressions, errorLogger, nullptr, false, {}); + + const auto ret = cppcheck.check(FileWithDetails(source_file.path(), Standards::Language::CPP, 0)); + const auto out = GET_REDIRECT_OUTPUT; + + ASSERT_EQUALS(0, ret); + ASSERT_EQUALS("no cached result 'test-build-dir/test.a1' for 'test.cpp' found\n", out); + } + + // No change, don't recheck + { + REDIRECT; + + ScopedFile source_file("test.cpp", + "#include \"test1.h\"\n" + "#include \"test2.h\"\n"); + ScopedFile header1_file("test1.h", + "class TestClass1 {};\n"); + ScopedFile header2_file("test2.h", + "class TestClass2 {};\n"); + + Suppressions suppressions; + ErrorLogger2 errorLogger; + CppCheck cppcheck(settings, suppressions, errorLogger, nullptr, false, {}); + + const auto ret = cppcheck.check(FileWithDetails(source_file.path(), Standards::Language::CPP, 0)); + const auto out = GET_REDIRECT_OUTPUT; + + ASSERT_EQUALS(0, ret); + ASSERT_EQUALS("skipping analysis - loaded 0 cached finding(s) from 'test-build-dir/test.a1' for 'test.cpp'\n", out); + } + + // Header changed, recheck + { + REDIRECT; + + ScopedFile source_file("test.cpp", + "#include \"test1.h\"\n" + "#include \"test2.h\"\n"); + ScopedFile header1_file("test1.h", + "class TestClass1 {};\n"); + ScopedFile header2_file("test2.h", + "class TestClass2 { int a; };\n"); + + Suppressions suppressions; + ErrorLogger2 errorLogger; + CppCheck cppcheck(settings, suppressions, errorLogger, nullptr, false, {}); + + const auto ret = cppcheck.check(FileWithDetails(source_file.path(), Standards::Language::CPP, 0)); + const auto out = GET_REDIRECT_OUTPUT; + + ASSERT_EQUALS(0, ret); + ASSERT_EQUALS("discarding cached result from 'test-build-dir/test.a1' for 'test.cpp' - hash mismatch\n", out); + } + + // Source changed, recheck + { + REDIRECT; + + // Inclusion removed + ScopedFile source_file("test.cpp", + "#include \"test1.h\"\n"); + ScopedFile header1_file("test1.h", + "class TestClass1 {};\n"); + ScopedFile header2_file("test2.h", + "class TestClass2 { int a; };\n"); + + Suppressions suppressions; + ErrorLogger2 errorLogger; + CppCheck cppcheck(settings, suppressions, errorLogger, nullptr, false, {}); + + const auto ret = cppcheck.check(FileWithDetails(source_file.path(), Standards::Language::CPP, 0)); + const auto out = GET_REDIRECT_OUTPUT; + + ASSERT_EQUALS(0, ret); + ASSERT_EQUALS("discarding cached result from 'test-build-dir/test.a1' for 'test.cpp' - hash mismatch\n", out); + } + + // Inclusion removed from source, check that it's removed from analyzer info + { + AnalyzerInformation analyzerInfo; + const auto includes = analyzerInfo.getIncludes("test-build-dir", "test.cpp", "", 0); + + ASSERT_EQUALS(1, includes.size()); + ASSERT_EQUALS("test1.h", *includes.begin()); + } + } + // TODO: test suppressions // TODO: test all with FS }; diff --git a/test/testerrorlogger.cpp b/test/testerrorlogger.cpp index 812ea500299..68d473420b9 100644 --- a/test/testerrorlogger.cpp +++ b/test/testerrorlogger.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -49,12 +49,14 @@ class TestErrorLogger : public TestFixture { TEST_CASE(FileLocationSetFile2); TEST_CASE(ErrorMessageConstruct); TEST_CASE(ErrorMessageConstructLocations); + TEST_CASE(ErrorMessageHashFallback); TEST_CASE(ErrorMessageVerbose); TEST_CASE(ErrorMessageVerboseLocations); TEST_CASE(ErrorMessageVerboseSymbol); TEST_CASE(ErrorMessageVerboseNewline); TEST_CASE(ErrorMessageFromInternalError); TEST_CASE(ErrorMessageCode); + TEST_CASE(ErrorMessageNoCode); TEST_CASE(CustomFormat); TEST_CASE(CustomFormat2); TEST_CASE(CustomFormatLocations); @@ -140,7 +142,7 @@ class TestErrorLogger : public TestFixture { ASSERT_EQUALS("info", loc.getinfo()); } { - const SimpleTokenList tokenlist("a", "dir/a.cpp"); + const SimpleTokenList tokenlist("a\n", "dir/a.cpp"); { const ErrorMessage::FileLocation loc(tokenlist.front(), &tokenlist.get()); ASSERT_EQUALS("dir/a.cpp", loc.getOrigFile(false)); @@ -173,7 +175,7 @@ class TestErrorLogger : public TestFixture { } } { - const SimpleTokenList tokenlist("a", "dir\\a.cpp"); + const SimpleTokenList tokenlist("a\n", "dir\\a.cpp"); { const ErrorMessage::FileLocation loc(tokenlist.front(), &tokenlist.get()); ASSERT_EQUALS("dir\\a.cpp", loc.getOrigFile(false)); @@ -213,12 +215,12 @@ class TestErrorLogger : public TestFixture { ASSERT_EQUALS("dir/a.cpp", ErrorMessage::FileLocation("dir/a.cpp", "info", 1, 1).getfile(false)); ASSERT_EQUALS("dir/a.cpp", ErrorMessage::FileLocation("dir\\a.cpp", "info", 1, 1).getfile(false)); { - const SimpleTokenList tokenlist("a", "dir/a.cpp"); + const SimpleTokenList tokenlist("a\n", "dir/a.cpp"); ASSERT_EQUALS("dir/a.cpp", ErrorMessage::FileLocation(tokenlist.front(), &tokenlist.get()).getfile(false)); ASSERT_EQUALS("dir/a.cpp", ErrorMessage::FileLocation(tokenlist.front(), "info", &tokenlist.get()).getfile(false)); } { - const SimpleTokenList tokenlist("a", "dir\\a.cpp"); + const SimpleTokenList tokenlist("a\n", "dir\\a.cpp"); ASSERT_EQUALS("dir/a.cpp", ErrorMessage::FileLocation(tokenlist.front(), &tokenlist.get()).getfile(false)); ASSERT_EQUALS("dir/a.cpp", ErrorMessage::FileLocation(tokenlist.front(), "info", &tokenlist.get()).getfile(false)); } @@ -267,6 +269,77 @@ class TestErrorLogger : public TestFixture { ASSERT_EQUALS("[foo.cpp:5] -> [bar.cpp:8]: (error) Programming error.", msg.toString(true, templateFormat, "")); } + // unusedFunction/staticFunction/ctu* warnings carry no token information, so + // ErrorMessage::calculateWarningHash() (which needs tokens) can't compute a + // hash for them. calculateWarningHashFromLocations() is the fallback used + // instead - it hashes the id, message and all location filenames/notes. + void ErrorMessageHashFallback() const { + // ids that don't need a fallback hash still get none + { + std::list locs(1, fooCpp5); + ErrorMessage msg(std::move(locs), "", Severity::style, "Some warning.", "someOtherId", Certainty::normal); + ASSERT_EQUALS(0, msg.hash); + } + + // unusedFunction, staticFunction and any ctu* id get a non-zero fallback hash + for (const std::string& id : { std::string("unusedFunction"), std::string("staticFunction"), std::string("ctuOneDefinitionRuleViolation") }) { + std::list locs(1, fooCpp5); + ErrorMessage msg(std::move(locs), "", Severity::style, "Some warning.", id, Certainty::normal); + ASSERT(msg.hash != 0); + } + + // same id/message/locations => same hash + { + std::list locs1(1, fooCpp5); + ErrorMessage msg1(std::move(locs1), "", Severity::style, "Some warning.", "unusedFunction", Certainty::normal); + + std::list locs2(1, fooCpp5); + ErrorMessage msg2(std::move(locs2), "", Severity::style, "Some warning.", "unusedFunction", Certainty::normal); + + ASSERT_EQUALS(msg1.hash, msg2.hash); + } + + // different message => different hash + { + std::list locs1(1, fooCpp5); + ErrorMessage msg1(std::move(locs1), "", Severity::style, "Some warning.", "unusedFunction", Certainty::normal); + + std::list locs2(1, fooCpp5); + ErrorMessage msg2(std::move(locs2), "", Severity::style, "Some other warning.", "unusedFunction", Certainty::normal); + + ASSERT(msg1.hash != msg2.hash); + } + + // different location filename => different hash + { + std::list locs1(1, fooCpp5); + ErrorMessage msg1(std::move(locs1), "", Severity::style, "Some warning.", "unusedFunction", Certainty::normal); + + std::list locs2(1, barCpp8); + ErrorMessage msg2(std::move(locs2), "", Severity::style, "Some warning.", "unusedFunction", Certainty::normal); + + ASSERT(msg1.hash != msg2.hash); + } + + // different location note (info) => different hash + { + std::list locs1(1, barCpp8); + ErrorMessage msg1(std::move(locs1), "", Severity::error, "Some warning.", "ctuOneDefinitionRuleViolation", Certainty::normal); + + std::list locs2(1, barCpp8_i); + ErrorMessage msg2(std::move(locs2), "", Severity::error, "Some warning.", "ctuOneDefinitionRuleViolation", Certainty::normal); + + ASSERT(msg1.hash != msg2.hash); + } + + // hash shows up in the XML output + { + std::list locs(1, fooCpp5); + ErrorMessage msg(std::move(locs), "", Severity::style, "Some warning.", "unusedFunction", Certainty::normal); + ASSERT(msg.toXML().find(" hash=\"") != std::string::npos); + } + } + void ErrorMessageVerbose() const { std::list locs(1, fooCpp5); ErrorMessage msg(std::move(locs), "", Severity::error, "Programming error.\nVerbose error", "errorId", Certainty::normal); @@ -386,6 +459,22 @@ class TestErrorLogger : public TestFixture { msg.toString(false, "{file}:{line}:{column}: {severity}:{inconclusive:inconclusive:} {message} [{id}]\n{code}", "")); } + void ErrorMessageNoCode() const { + ScopedFile file("code.cpp", + "int i;\n" + "int i2;\n" + "int i3;\n" + ); + + ErrorMessage::FileLocation code{"code.cpp", 3, 5}; + std::list locs = { code }; + ErrorMessage msg(std::move(locs), "", Severity::error, "Programming error.\nVerbose error", "errorId", Certainty::normal); + ASSERT_EQUALS(1, msg.callStack.size()); + const bool noCode = true; + ASSERT_EQUALS("code.cpp:3:5: error: Programming error. [errorId]\n", + msg.toString(false, "{file}:{line}:{column}: {severity}:{inconclusive:inconclusive:} {message} [{id}]\n{code}", "", noCode)); + } + void CustomFormat() const { std::list locs(1, fooCpp5); ErrorMessage msg(std::move(locs), "", Severity::error, "Programming error.\nVerbose error", "errorId", Certainty::normal); @@ -599,7 +688,7 @@ class TestErrorLogger : public TestFixture { "0 " "0 "; ErrorMessage msg; - ASSERT_THROW_INTERNAL_EQUALS(msg.deserialize(str), INTERNAL, "Internal Error: Deserialization of error message failed - invalid CWE ID - not an integer"); + ASSERT_THROW_INTERNAL_EQUALS(msg.deserialize(str), INTERNAL, "Internal Error: Deserialization of error message failed - invalid CWE ID - not an integer (invalid_argument)"); } { // invalid hash @@ -615,7 +704,7 @@ class TestErrorLogger : public TestFixture { "0 " "0 "; ErrorMessage msg; - ASSERT_THROW_INTERNAL_EQUALS(msg.deserialize(str), INTERNAL, "Internal Error: Deserialization of error message failed - invalid hash - not an integer"); + ASSERT_THROW_INTERNAL_EQUALS(msg.deserialize(str), INTERNAL, "Internal Error: Deserialization of error message failed - invalid hash - not an integer (invalid_argument)"); } { // out-of-range CWE ID diff --git a/test/testexceptionsafety.cpp b/test/testexceptionsafety.cpp index 58b26ecbc27..7e3de4405b0 100644 --- a/test/testexceptionsafety.cpp +++ b/test/testexceptionsafety.cpp @@ -75,8 +75,8 @@ class TestExceptionSafety : public TestFixture { SimpleTokenizer tokenizer(settings1, *this); ASSERT_LOC(tokenizer.tokenize(code), file, line); - // Check char variable usage.. - runChecks(tokenizer, this); + CheckExceptionSafety check; + runChecks(check, tokenizer, *this); } void destructors() { @@ -84,7 +84,7 @@ class TestExceptionSafety : public TestFixture { " ~x() {\n" " throw e;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:9]: (warning) Class x is not safe, destructor throws exception [exceptThrowInDestructor]\n" "[test.cpp:3:9]: (error) Unhandled exception thrown in function declared not to throw exceptions. [throwInNoexceptFunction]\n", errout_str()); @@ -93,7 +93,7 @@ class TestExceptionSafety : public TestFixture { "};\n" "x::~x() {\n" " throw e;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:5]: (warning) Class x is not safe, destructor throws exception [exceptThrowInDestructor]\n" "[test.cpp:5:5]: (error) Unhandled exception thrown in function declared not to throw exceptions. [throwInNoexceptFunction]\n", errout_str()); @@ -105,7 +105,7 @@ class TestExceptionSafety : public TestFixture { " } catch (...) {\n" " }\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("class x {\n" @@ -114,7 +114,7 @@ class TestExceptionSafety : public TestFixture { " throw e;\n" " }\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:13]: (error) Unhandled exception thrown in function declared not to throw exceptions. [throwInNoexceptFunction]\n", errout_str()); // #11031 should not warn when noexcept false @@ -123,7 +123,7 @@ class TestExceptionSafety : public TestFixture { " ~A() noexcept(false) {\n" " throw 30;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -134,7 +134,7 @@ class TestExceptionSafety : public TestFixture { " if (x)\n" " throw 123;\n" " p = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:9]: (warning) Exception thrown in invalid state, 'p' points at deallocated memory. [exceptDeallocThrow]\n", errout_str()); check("void f() {\n" @@ -143,7 +143,7 @@ class TestExceptionSafety : public TestFixture { " if (foo)\n" " throw 1;\n" " p = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:9]: (warning) Exception thrown in invalid state, 'p' points at deallocated memory. [exceptDeallocThrow]\n", errout_str()); } @@ -154,7 +154,7 @@ class TestExceptionSafety : public TestFixture { " if (foo)\n" " throw 1;\n" " p = new int;\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -162,7 +162,7 @@ class TestExceptionSafety : public TestFixture { " delete p;\n" " reset(p);\n" " throw 1;\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); } @@ -171,14 +171,14 @@ class TestExceptionSafety : public TestFixture { " static int* p = 0;\n" " delete p;\n" " throw 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " static int* p = 0;\n" " delete p;\n" " throw 1;\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("[test.cpp:4:5]: (warning) Exception thrown in invalid state, 'p' points at deallocated memory. [exceptDeallocThrow]\n", errout_str()); } @@ -192,7 +192,7 @@ class TestExceptionSafety : public TestFixture { " {\n" " throw err;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:9]: (style) Throwing a copy of the caught exception instead of rethrowing the original exception. [exceptRethrowCopy]\n", errout_str()); } @@ -206,7 +206,7 @@ class TestExceptionSafety : public TestFixture { " {\n" " throw err;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:9]: (style) Throwing a copy of the caught exception instead of rethrowing the original exception. [exceptRethrowCopy]\n", errout_str()); } @@ -218,7 +218,7 @@ class TestExceptionSafety : public TestFixture { " catch(std::runtime_error& err) {\n" " throw err;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:9]: (style) Throwing a copy of the caught exception instead of rethrowing the original exception. [exceptRethrowCopy]\n", errout_str()); } @@ -233,7 +233,7 @@ class TestExceptionSafety : public TestFixture { " exception err2;\n" " throw err2;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -250,7 +250,7 @@ class TestExceptionSafety : public TestFixture { " throw inner;\n" " }\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:10:13]: (style) Throwing a copy of the caught exception instead of rethrowing the original exception. [exceptRethrowCopy]\n", errout_str()); check("void f() {\n" @@ -265,7 +265,7 @@ class TestExceptionSafety : public TestFixture { " throw outer;\n" " }\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -277,7 +277,7 @@ class TestExceptionSafety : public TestFixture { " catch( ::std::exception err) {\n" " foo(err);\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:5]: (style) Exception should be caught by reference. [catchExceptionByValue]\n", errout_str()); check("void f() {\n" @@ -287,7 +287,7 @@ class TestExceptionSafety : public TestFixture { " catch(const exception err) {\n" " foo(err);\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:5]: (style) Exception should be caught by reference. [catchExceptionByValue]\n", errout_str()); check("void f() {\n" @@ -297,7 +297,7 @@ class TestExceptionSafety : public TestFixture { " catch( ::std::exception& err) {\n" " foo(err);\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -307,7 +307,7 @@ class TestExceptionSafety : public TestFixture { " catch(exception* err) {\n" " foo(err);\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -317,7 +317,7 @@ class TestExceptionSafety : public TestFixture { " catch(const exception& err) {\n" " foo(err);\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -327,7 +327,7 @@ class TestExceptionSafety : public TestFixture { " catch(int err) {\n" " foo(err);\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -337,7 +337,7 @@ class TestExceptionSafety : public TestFixture { " catch(exception* const err) {\n" " foo(err);\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -347,14 +347,14 @@ class TestExceptionSafety : public TestFixture { "void func3() noexcept(true) { throw 1; }\n" "void func4() noexcept(false) { throw 1; }\n" "void func5() noexcept(true) { func1(); }\n" - "void func6() noexcept(false) { func1(); }"); + "void func6() noexcept(false) { func1(); }\n"); ASSERT_EQUALS("[test.cpp:2:25]: (error) Unhandled exception thrown in function declared not to throw exceptions. [throwInNoexceptFunction]\n" "[test.cpp:3:31]: (error) Unhandled exception thrown in function declared not to throw exceptions. [throwInNoexceptFunction]\n" "[test.cpp:5:31]: (error) Unhandled exception thrown in function declared not to throw exceptions. [throwInNoexceptFunction]\n", errout_str()); // avoid false positives check("const char *func() noexcept { return 0; }\n" - "const char *func1() noexcept { try { throw 1; } catch(...) {} return 0; }"); + "const char *func1() noexcept { try { throw 1; } catch(...) {} return 0; }\n"); ASSERT_EQUALS("", errout_str()); check("struct A {\n" // #14526 @@ -378,12 +378,12 @@ class TestExceptionSafety : public TestFixture { "void func2() throw() { throw 1; }\n" "void func3() throw(int) { throw 1; }\n" "void func4() throw() { func1(); }\n" - "void func5() throw(int) { func1(); }"); + "void func5() throw(int) { func1(); }\n"); ASSERT_EQUALS("[test.cpp:2:24]: (error) Unhandled exception thrown in function declared not to throw exceptions. [throwInNoexceptFunction]\n" "[test.cpp:4:24]: (error) Unhandled exception thrown in function declared not to throw exceptions. [throwInNoexceptFunction]\n", errout_str()); // avoid false positives - check("const char *func() throw() { return 0; }"); + check("const char *func() throw() { return 0; }\n"); ASSERT_EQUALS("", errout_str()); @@ -451,12 +451,12 @@ class TestExceptionSafety : public TestFixture { void nothrowAttributeThrow() { check("void func1() throw(int) { throw 1; }\n" "void func2() __attribute((nothrow)); void func2() { throw 1; }\n" - "void func3() __attribute((nothrow)); void func3() { func1(); }"); + "void func3() __attribute((nothrow)); void func3() { func1(); }\n"); ASSERT_EQUALS("[test.cpp:2:53]: (error) Unhandled exception thrown in function declared not to throw exceptions. [throwInNoexceptFunction]\n" "[test.cpp:3:53]: (error) Unhandled exception thrown in function declared not to throw exceptions. [throwInNoexceptFunction]\n", errout_str()); // avoid false positives - check("const char *func() __attribute((nothrow)); void func1() { return 0; }"); + check("const char *func() __attribute((nothrow)); void func1() { return 0; }\n"); ASSERT_EQUALS("", errout_str()); } @@ -465,30 +465,30 @@ class TestExceptionSafety : public TestFixture { " void copyMemberValues() throw () {\n" " copyMemberValues();\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } void nothrowDeclspecThrow() { check("void func1() throw(int) { throw 1; }\n" "void __declspec(nothrow) func2() { throw 1; }\n" - "void __declspec(nothrow) func3() { func1(); }"); + "void __declspec(nothrow) func3() { func1(); }\n"); ASSERT_EQUALS("[test.cpp:2:36]: (error) Unhandled exception thrown in function declared not to throw exceptions. [throwInNoexceptFunction]\n" "[test.cpp:3:36]: (error) Unhandled exception thrown in function declared not to throw exceptions. [throwInNoexceptFunction]\n", errout_str()); // avoid false positives - check("const char *func() __attribute((nothrow)); void func1() { return 0; }"); + check("const char *func() __attribute((nothrow)); void func1() { return 0; }\n"); ASSERT_EQUALS("", errout_str()); } void rethrowNoCurrentException1() { - check("void func1(const bool flag) { try{ if(!flag) throw; } catch (int&) { ; } }"); + check("void func1(const bool flag) { try{ if(!flag) throw; } catch (int&) { ; } }\n"); ASSERT_EQUALS("[test.cpp:1:46]: (error) Rethrowing current exception with 'throw;', it seems there is no current exception to rethrow." " If there is no current exception this calls std::terminate(). More: https://isocpp.org/wiki/faq/exceptions#throw-without-an-object [rethrowNoCurrentException]\n", errout_str()); } void rethrowNoCurrentException2() { - check("void func1() { try{ ; } catch (...) { ; } throw; }"); + check("void func1() { try{ ; } catch (...) { ; } throw; }\n"); ASSERT_EQUALS("[test.cpp:1:43]: (error) Rethrowing current exception with 'throw;', it seems there is no current exception to rethrow." " If there is no current exception this calls std::terminate(). More: https://isocpp.org/wiki/faq/exceptions#throw-without-an-object [rethrowNoCurrentException]\n", errout_str()); } @@ -496,7 +496,7 @@ class TestExceptionSafety : public TestFixture { void rethrowNoCurrentException3() { check("void on_error() { try { throw; } catch (const int &) { ; } catch (...) { ; } }\n" // exception dispatcher idiom "void func2() { try{ ; } catch (const int&) { throw; } ; }\n" - "void func3() { throw 0; }"); + "void func3() { throw 0; }\n"); ASSERT_EQUALS("", errout_str()); } diff --git a/test/testfilelister.cpp b/test/testfilelister.cpp index 13057735513..b90a8cb55e7 100644 --- a/test/testfilelister.cpp +++ b/test/testfilelister.cpp @@ -36,6 +36,7 @@ class TestFileLister : public TestFixture { void run() override { TEST_CASE(recursiveAddFiles); TEST_CASE(recursiveAddFilesEmptyPath); + TEST_CASE(recursiveAddFilesNulInPath); TEST_CASE(excludeFile1); TEST_CASE(excludeFile2); TEST_CASE(excludeDir); @@ -110,6 +111,13 @@ class TestFileLister : public TestFixture { ASSERT_EQUALS("no path specified", err); } + void recursiveAddFilesNulInPath() const { + std::list files; + const std::string err = FileLister::recursiveAddFiles(files, std::string("lib\0", 4), {}, PathMatch()); + ASSERT_EQUALS("path 'lib' contains a NUL character", err); + ASSERT(files.empty()); + } + void excludeFile1() const { const std::string basedir = findBaseDir(); diff --git a/test/testfunctions.cpp b/test/testfunctions.cpp index c3fe10afe39..58228b58b07 100644 --- a/test/testfunctions.cpp +++ b/test/testfunctions.cpp @@ -59,6 +59,7 @@ class TestFunctions : public TestFixture { // Invalid function argument TEST_CASE(invalidFunctionArg1); + TEST_CASE(invalidFunctionArgErrorPath); // Math function usage TEST_CASE(mathfunctionCall_fmod); @@ -86,6 +87,8 @@ class TestFunctions : public TestFixture { TEST_CASE(checkMissingReturn5); TEST_CASE(checkMissingReturn6); // #13180 TEST_CASE(checkMissingReturn7); // #14370 - FN try/catch + TEST_CASE(checkMissingReturn8); + TEST_CASE(checkMissingReturn9); TEST_CASE(checkMissingReturnStdInt); // #14482 - FN std::int32_t // std::move for locar variable @@ -132,21 +135,22 @@ class TestFunctions : public TestFixture { SimpleTokenizer tokenizer(s, *this, cpp); ASSERT_LOC(tokenizer.tokenize(code), file, line); - runChecks(tokenizer, this); + CheckFunctions check; + runChecks(check, tokenizer, *this); } void prohibitedFunctions_posix() { check("void f()\n" "{\n" " bsd_signal(SIGABRT, SIG_IGN);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (style) Obsolescent function 'bsd_signal' called. It is recommended to use 'sigaction' instead. [bsd_signalCalled]\n", errout_str()); check("int f()\n" "{\n" " int bsd_signal(0);\n" " return bsd_signal;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f()\n" @@ -155,7 +159,7 @@ class TestFunctions : public TestFixture { " if(!hp = gethostbyname(\"127.0.0.1\")) {\n" " exit(1);\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:14]: (style) Obsolescent function 'gethostbyname' called. It is recommended to use 'getaddrinfo' instead. [gethostbynameCalled]\n", errout_str()); check("void f()\n" @@ -165,13 +169,13 @@ class TestFunctions : public TestFixture { " if(!hp = gethostbyaddr((char *) &addr, sizeof(addr), AF_INET)) {\n" " exit(1);\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:14]: (style) Obsolescent function 'gethostbyaddr' called. It is recommended to use 'getnameinfo' instead. [gethostbyaddrCalled]\n", errout_str()); check("void f()\n" "{\n" " usleep( 1000 );\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (style) Obsolescent function 'usleep' called. It is recommended to use 'nanosleep' or 'setitimer' instead. [usleepCalled]\n", errout_str()); } @@ -183,7 +187,7 @@ class TestFunctions : public TestFixture { "{\n" " n1::index();\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("std::size_t f()\n" @@ -191,19 +195,19 @@ class TestFunctions : public TestFixture { " std::size_t index(0);\n" " index++;\n" " return index;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int f()\n" "{\n" " return this->index();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f()\n" "{\n" " int index( 0 );\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("const char f()\n" @@ -211,7 +215,7 @@ class TestFunctions : public TestFixture { " const char var[6] = \"index\";\n" " const char i = index(var, 0);\n" " return i;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:20]: (style) Obsolescent function 'index' called. It is recommended to use 'strchr' instead. [indexCalled]\n", errout_str()); } @@ -219,7 +223,7 @@ class TestFunctions : public TestFixture { void prohibitedFunctions_qt_index() { check("void TDataModel::forceRowRefresh(int row) {\n" " emit dataChanged(index(row, 0), index(row, columnCount() - 1));\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:2:22]: (style) Obsolescent function 'index' called. It is recommended to use 'strchr' instead. [indexCalled]\n" "[test.cpp:2:37]: (style) Obsolescent function 'index' called. It is recommended to use 'strchr' instead. [indexCalled]\n", @@ -230,14 +234,14 @@ class TestFunctions : public TestFixture { check("void f()\n" "{\n" " int rindex( 0 );\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f()\n" "{\n" " const char var[7] = \"rindex\";\n" " print(rindex(var, 0));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:11]: (style) Obsolescent function 'rindex' called. It is recommended to use 'strrchr' instead. [rindexCalled]\n", errout_str()); } @@ -247,7 +251,7 @@ class TestFunctions : public TestFixture { "public:\n" " Fred() : index(0) { }\n" " int index;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -255,13 +259,13 @@ class TestFunctions : public TestFixture { check("void f()\n" "{\n" " char *x = gets(a);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:15]: (warning) Obsolete function 'gets' called. It is recommended to use 'fgets' or 'gets_s' instead. [getsCalled]\n", errout_str()); check("void f()\n" "{\n" " foo(x, gets(a));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:12]: (warning) Obsolete function 'gets' called. It is recommended to use 'fgets' or 'gets_s' instead. [getsCalled]\n", errout_str()); } @@ -269,32 +273,32 @@ class TestFunctions : public TestFixture { check("void f()\n" "{\n" " char *x = alloca(10);\n" - "}"); // #4382 - there are no VLAs in C++ + "}\n"); // #4382 - there are no VLAs in C++ ASSERT_EQUALS("[test.cpp:3:15]: (warning) Obsolete function 'alloca' called. [allocaCalled]\n", errout_str()); check("void f()\n" "{\n" " char *x = alloca(10);\n" - "}", dinit(CheckOptions, $.cpp = false)); + "}\n", dinit(CheckOptions, $.cpp = false)); ASSERT_EQUALS("[test.c:3:15]: (warning) Obsolete function 'alloca' called. In C99 and later it is recommended to use a variable length array instead. [allocaCalled]\n", errout_str()); const Settings s = settingsBuilder(settings).c(Standards::C89).cpp(Standards::CPP03).build(); check("void f()\n" "{\n" " char *x = alloca(10);\n" - "}", s); // #4382 - there are no VLAs in C++ + "}\n", s); // #4382 - there are no VLAs in C++ ASSERT_EQUALS("", errout_str()); check("void f()\n" "{\n" " char *x = alloca(10);\n" - "}", s, false); // #7558 - no alternative to alloca in C89 + "}\n", s, false); // #7558 - no alternative to alloca in C89 ASSERT_EQUALS("", errout_str()); check("void f()\n" "{\n" " char *x = alloca(10);\n" - "}", s, false); + "}\n", s, false); ASSERT_EQUALS("", errout_str()); } @@ -308,7 +312,7 @@ class TestFunctions : public TestFixture { "{\n" " int b ; b = ftime ( 1 ) ;\n" " return 0 ;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -318,7 +322,7 @@ class TestFunctions : public TestFixture { "{\n" " char *x = std::gets(str);\n" " char *y = gets(str);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:20]: (warning) Obsolete function 'gets' called. It is recommended to use 'fgets' or 'gets_s' instead. [getsCalled]\n" "[test.cpp:4:15]: (warning) Obsolete function 'gets' called. It is recommended to use 'fgets' or 'gets_s' instead. [getsCalled]\n", errout_str()); } @@ -329,7 +333,7 @@ class TestFunctions : public TestFixture { "{\n" " char *x = std::gets(str);\n" " usleep( 1000 );\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:20]: (warning) Obsolete function 'gets' called. It is recommended to use 'fgets' or 'gets_s' instead. [getsCalled]\n" "[test.cpp:4:5]: (style) Obsolescent function 'usleep' called. It is recommended to use 'nanosleep' or 'setitimer' instead. [usleepCalled]\n", errout_str()); } @@ -341,14 +345,14 @@ class TestFunctions : public TestFixture { " char s [ 10 ] ;\n" " gets ( s ) ;\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:5]: (warning) Obsolete function 'gets' called. It is recommended to use 'fgets' or 'gets_s' instead. [getsCalled]\n", errout_str()); check("int getcontext(ucontext_t *ucp);\n" "void f (ucontext_t *ucp)\n" "{\n" " getcontext ( ucp ) ;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:5]: (portability) Obsolescent function 'getcontext' called. Applications are recommended to be rewritten to use POSIX threads. [getcontextCalled]\n", errout_str()); } @@ -359,7 +363,7 @@ class TestFunctions : public TestFixture { " char s [ 10 ] ;\n" " gets ( s ) ;\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -368,7 +372,7 @@ class TestFunctions : public TestFixture { "{\n" " char *cpwd;" " crypt(pwd, cpwd);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:20]: (warning) Return value of function crypt() is not used. [ignoredReturnValue]\n" "[test.cpp:3:20]: (portability) Non reentrant function 'crypt' called. For threadsafe applications it is recommended to use the reentrant replacement function 'crypt_r'. [cryptCalled]\n", errout_str()); @@ -377,7 +381,7 @@ class TestFunctions : public TestFixture { " char *pwd = getpass(\"Password:\");" " char *cpwd;" " crypt(pwd, cpwd);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:57]: (warning) Return value of function crypt() is not used. [ignoredReturnValue]\n" "[test.cpp:3:57]: (portability) Non reentrant function 'crypt' called. For threadsafe applications it is recommended to use the reentrant replacement function 'crypt_r'. [cryptCalled]\n", errout_str()); @@ -385,7 +389,7 @@ class TestFunctions : public TestFixture { "{\n" " int crypt = 0;" " return crypt;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -394,14 +398,14 @@ class TestFunctions : public TestFixture { "{\n" " time_t t = 0;" " auto lt = std::localtime(&t);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:37]: (portability) Non reentrant function 'localtime' called. For threadsafe applications it is recommended to use the reentrant replacement function 'localtime_r'. [localtimeCalled]\n", errout_str()); // Passed as function argument check("void f()\n" "{\n" " printf(\"Magic guess: %d\", getpwent());\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:31]: (portability) Non reentrant function 'getpwent' called. For threadsafe applications it is recommended to use the reentrant replacement function 'getpwent_r'. [getpwentCalled]\n", errout_str()); // Pass return value @@ -409,14 +413,14 @@ class TestFunctions : public TestFixture { "{\n" " time_t t = 0;" " struct tm *foo = localtime(&t);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:39]: (portability) Non reentrant function 'localtime' called. For threadsafe applications it is recommended to use the reentrant replacement function 'localtime_r'. [localtimeCalled]\n", errout_str()); // Access via global namespace check("void f()\n" "{\n" " ::getpwent();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:7]: (warning) Return value of function ::getpwent() is not used. [ignoredReturnValue]\n" "[test.cpp:3:7]: (portability) Non reentrant function 'getpwent' called. For threadsafe applications it is recommended to use the reentrant replacement function 'getpwent_r'. [getpwentCalled]\n", errout_str()); @@ -424,68 +428,68 @@ class TestFunctions : public TestFixture { check("int getpwent()\n" "{\n" " return 123;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Be quiet on other namespaces check("void f()\n" "{\n" " foobar::getpwent();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Be quiet on class member functions check("void f()\n" "{\n" " foobar.getpwent();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } void invalidFunctionUsage1() { - check("void f() { memset(a,b,sizeof(a)!=12); }"); + check("void f() { memset(a,b,sizeof(a)!=12); }\n"); ASSERT_EQUALS("[test.cpp:1:32]: (error) Invalid memset() argument nr 3. A non-boolean value is required. [invalidFunctionArgBool]\n", errout_str()); - check("void f() { memset(a,b,sizeof(a)!=0); }"); + check("void f() { memset(a,b,sizeof(a)!=0); }\n"); ASSERT_EQUALS("[test.cpp:1:32]: (error) Invalid memset() argument nr 3. A non-boolean value is required. [invalidFunctionArgBool]\n", errout_str()); - check("void f() { memset(a,b,!c); }"); + check("void f() { memset(a,b,!c); }\n"); ASSERT_EQUALS("[test.cpp:1:23]: (error) Invalid memset() argument nr 3. A non-boolean value is required. [invalidFunctionArgBool]\n", errout_str()); // Ticket #6990 - check("void f(bool c) { memset(a,b,c); }"); + check("void f(bool c) { memset(a,b,c); }\n"); ASSERT_EQUALS("[test.cpp:1:29]: (error) Invalid memset() argument nr 3. A non-boolean value is required. [invalidFunctionArgBool]\n", errout_str()); - check("void f() { memset(a,b,true); }"); + check("void f() { memset(a,b,true); }\n"); ASSERT_EQUALS("[test.cpp:1:23]: (error) Invalid memset() argument nr 3. A non-boolean value is required. [invalidFunctionArgBool]\n", errout_str()); // Ticket #6588 (c mode) check("void record(char* buf, int n) {\n" " memset(buf, 0, n < 255);\n" /* KO */ " memset(buf, 0, n < 255 ? n : 255);\n" /* OK */ - "}", dinit(CheckOptions, $.cpp = false)); + "}\n", dinit(CheckOptions, $.cpp = false)); ASSERT_EQUALS("[test.c:2:20]: (error) Invalid memset() argument nr 3. A non-boolean value is required. [invalidFunctionArgBool]\n", errout_str()); // Ticket #6588 (c++ mode) check("void record(char* buf, int n) {\n" " memset(buf, 0, n < 255);\n" /* KO */ " memset(buf, 0, n < 255 ? n : 255);\n" /* OK */ - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:20]: (error) Invalid memset() argument nr 3. A non-boolean value is required. [invalidFunctionArgBool]\n", errout_str()); check("int boolArgZeroIsInvalidButOneIsValid(int a, int param) {\n" " return div(a, param > 0);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:23]: (error) Invalid div() argument nr 2. The value is 0 or 1 (boolean) but the valid values are ':-1,1:'. [invalidFunctionArg]\n", errout_str()); check("void boolArgZeroIsValidButOneIsInvalid(int param) {\n" " strtol(a, b, param > 0);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:22]: (error) Invalid strtol() argument nr 3. The value is 0 or 1 (boolean) but the valid values are '0,2:36'. [invalidFunctionArg]\n", errout_str()); - check("void f() { strtol(a,b,1); }"); + check("void f() { strtol(a,b,1); }\n"); ASSERT_EQUALS("[test.cpp:1:23]: (error) Invalid strtol() argument nr 3. The value is 1 but the valid values are '0,2:36'. [invalidFunctionArg]\n", errout_str()); - check("void f() { strtol(a,b,10); }"); + check("void f() { strtol(a,b,10); }\n"); ASSERT_EQUALS("", errout_str()); check("void f(std::vector& v) {\n" // #10754 @@ -528,16 +532,16 @@ class TestFunctions : public TestFixture { } void invalidFunctionUsageStrings() { - check("size_t f() { char x = 'x'; return strlen(&x); }"); + check("size_t f() { char x = 'x'; return strlen(&x); }\n"); ASSERT_EQUALS("[test.cpp:1:42]: (error) Invalid strlen() argument nr 1. A nul-terminated string is required. [invalidFunctionArgStr]\n", errout_str()); - check("size_t f() { return strlen(&x); }"); + check("size_t f() { return strlen(&x); }\n"); ASSERT_EQUALS("", errout_str()); - check("size_t f(char x) { return strlen(&x); }"); + check("size_t f(char x) { return strlen(&x); }\n"); ASSERT_EQUALS("[test.cpp:1:34]: (error) Invalid strlen() argument nr 1. A nul-terminated string is required. [invalidFunctionArgStr]\n", errout_str()); - check("size_t f() { char x = '\\0'; return strlen(&x); }"); + check("size_t f() { char x = '\\0'; return strlen(&x); }\n"); ASSERT_EQUALS("", errout_str()); check("size_t f() {\n" @@ -547,41 +551,41 @@ class TestFunctions : public TestFixture { " else\n" " x = 'a';\n" " return strlen(&x);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:17]: (error) Invalid strlen() argument nr 1. A nul-terminated string is required. [invalidFunctionArgStr]\n", errout_str()); - check("int f() { char x = '\\0'; return strcmp(\"Hello world\", &x); }"); + check("int f() { char x = '\\0'; return strcmp(\"Hello world\", &x); }\n"); ASSERT_EQUALS("", errout_str()); - check("int f() { char x = 'x'; return strcmp(\"Hello world\", &x); }"); + check("int f() { char x = 'x'; return strcmp(\"Hello world\", &x); }\n"); ASSERT_EQUALS("[test.cpp:1:54]: (error) Invalid strcmp() argument nr 2. A nul-terminated string is required. [invalidFunctionArgStr]\n", errout_str()); - check("size_t f(char x) { char * y = &x; return strlen(y); }"); + check("size_t f(char x) { char * y = &x; return strlen(y); }\n"); TODO_ASSERT_EQUALS("[test.cpp:1:42]: (error) Invalid strlen() argument nr 1. A nul-terminated string is required. [invalidFunctionArgStr]\n", "", errout_str()); - check("size_t f(char x) { char * y = &x; char *z = y; return strlen(z); }"); + check("size_t f(char x) { char * y = &x; char *z = y; return strlen(z); }\n"); TODO_ASSERT_EQUALS("[test.cpp:1:42]: (error) Invalid strlen() argument nr 1. A nul-terminated string is required. [invalidFunctionArgStr]\n", "", errout_str()); - check("size_t f() { char x = 'x'; char * y = &x; char *z = y; return strlen(z); }"); + check("size_t f() { char x = 'x'; char * y = &x; char *z = y; return strlen(z); }\n"); TODO_ASSERT_EQUALS("[test.cpp:1:42]: (error) Invalid strlen() argument nr 1. A nul-terminated string is required. [invalidFunctionArgStr]\n", "", errout_str()); - check("size_t f() { char x = '\\0'; char * y = &x; char *z = y; return strlen(z); }"); + check("size_t f() { char x = '\\0'; char * y = &x; char *z = y; return strlen(z); }\n"); ASSERT_EQUALS("", errout_str()); - check("size_t f() { char x[] = \"Hello world\"; return strlen(x); }"); + check("size_t f() { char x[] = \"Hello world\"; return strlen(x); }\n"); ASSERT_EQUALS("", errout_str()); - check("size_t f(char x[]) { return strlen(x); }"); + check("size_t f(char x[]) { return strlen(x); }\n"); ASSERT_EQUALS("", errout_str()); - check("int f(char x, char y) { return strcmp(&x, &y); }"); + check("int f(char x, char y) { return strcmp(&x, &y); }\n"); ASSERT_EQUALS("[test.cpp:1:39]: (error) Invalid strcmp() argument nr 1. A nul-terminated string is required. [invalidFunctionArgStr]\n" "[test.cpp:1:43]: (error) Invalid strcmp() argument nr 2. A nul-terminated string is required. [invalidFunctionArgStr]\n", errout_str()); - check("size_t f() { char x[] = \"Hello world\"; return strlen(&x[0]); }"); + check("size_t f() { char x[] = \"Hello world\"; return strlen(&x[0]); }\n"); ASSERT_EQUALS("", errout_str()); - check("size_t f() { char* x = \"Hello world\"; return strlen(&x[0]); }"); + check("size_t f() { char* x = \"Hello world\"; return strlen(&x[0]); }\n"); ASSERT_EQUALS("", errout_str()); check("struct S {\n" @@ -594,11 +598,11 @@ class TestFunctions : public TestFixture { " size_t l1 = strlen(&s1.x);\n" " size_t l2 = strlen(&s2.x);\n" " return l1 + l2;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:22]: (error) Invalid strlen() argument nr 1. A nul-terminated string is required. [invalidFunctionArgStr]\n" "[test.cpp:9:22]: (error) Invalid strlen() argument nr 1. A nul-terminated string is required. [invalidFunctionArgStr]\n", errout_str()); - check("const char x = 'x'; size_t f() { return strlen(&x); }"); + check("const char x = 'x'; size_t f() { return strlen(&x); }\n"); ASSERT_EQUALS("[test.cpp:1:48]: (error) Invalid strlen() argument nr 1. A nul-terminated string is required. [invalidFunctionArgStr]\n", errout_str()); check("struct someStruct {\n" @@ -613,7 +617,7 @@ class TestFunctions : public TestFixture { "int f(struct someStruct * const tp, const int k)\n" "{\n" " return strcmp(&tp->x.buf[k], \"needle\");\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct someStruct {\n" @@ -622,29 +626,29 @@ class TestFunctions : public TestFixture { "int f(struct someStruct * const tp, const int k)\n" "{\n" " return strcmp(&tp->buf[k], \"needle\");\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); - check("const char x = 'x'; size_t f() { char y = x; return strlen(&y); }"); + check("const char x = 'x'; size_t f() { char y = x; return strlen(&y); }\n"); ASSERT_EQUALS("[test.cpp:1:60]: (error) Invalid strlen() argument nr 1. A nul-terminated string is required. [invalidFunctionArgStr]\n", errout_str()); - check("const char x = '\\0'; size_t f() { return strlen(&x); }"); + check("const char x = '\\0'; size_t f() { return strlen(&x); }\n"); ASSERT_EQUALS("", errout_str()); - check("const char x = '\\0'; size_t f() { char y = x; return strlen(&y); }"); + check("const char x = '\\0'; size_t f() { char y = x; return strlen(&y); }\n"); ASSERT_EQUALS("", errout_str()); check("size_t f() {\n" " char * a = \"Hello world\";\n" " char ** b = &a;\n" " return strlen(&b[0][0]);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("size_t f() {\n" " char ca[] = \"asdf\";\n" " return strlen((char*) &ca);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #5225 @@ -655,67 +659,67 @@ class TestFunctions : public TestFixture { " strcat(str, &d);\n" " puts(str);\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:15]: (error) Invalid strcat() argument nr 2. A nul-terminated string is required. [invalidFunctionArgStr]\n", errout_str()); check("FILE* f(void) {\n" " const char fileName[1] = { \'x\' };\n" " return fopen(fileName, \"r\"); \n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:16]: (error) Invalid fopen() argument nr 1. A nul-terminated string is required. [invalidFunctionArgStr]\n", errout_str()); check("FILE* f(void) {\n" " const char fileName[2] = { \'x\', \'y\' };\n" " return fopen(fileName, \"r\"); \n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:16]: (error) Invalid fopen() argument nr 1. A nul-terminated string is required. [invalidFunctionArgStr]\n", errout_str()); check("FILE* f(void) {\n" " const char fileName[3] = { \'x\', \'y\' ,\'\\0\' };\n" " return fopen(fileName, \"r\"); \n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("FILE* f(void) {\n" " const char fileName[3] = { \'x\', \'\\0\' ,\'y\' };\n" " return fopen(fileName, \"r\"); \n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("FILE* f(void) {\n" " const char fileName[3] = { \'x\', \'y\' };\n" // implicit '\0' added at the end " return fopen(fileName, \"r\"); \n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("FILE* f(void) {\n" " const char fileName[] = { \'\\0\' };\n" " return fopen(fileName, \"r\"); \n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("FILE* f(void) {\n" " const char fileName[] = { \'0\' + 42 };\n" // no size is explicitly defined, no implicit '\0' is added " return fopen(fileName, \"r\"); \n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:16]: (error) Invalid fopen() argument nr 1. A nul-terminated string is required. [invalidFunctionArgStr]\n", errout_str()); check("FILE* f(void) {\n" " const char fileName[] = { \'0\' + 42, \'x\' };\n" // no size is explicitly defined, no implicit '\0' is added " return fopen(fileName, \"r\"); \n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:16]: (error) Invalid fopen() argument nr 1. A nul-terminated string is required. [invalidFunctionArgStr]\n", errout_str()); check("FILE* f(void) {\n" " const char fileName[2] = { \'0\' + 42 };\n" // implicitly '\0' added at the end because size is set to 2 " return fopen(fileName, \"r\"); \n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("FILE* f(void) {\n" " const char fileName[] = { };\n" " return fopen(fileName, \"r\"); \n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void scanMetaTypes()\n" // don't crash @@ -726,7 +730,7 @@ class TestFunctions : public TestFixture { " if (strstr(name, \"GammaRay::\") != name)\n" " metaTypes.push_back(mtId);\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int f() {\n" @@ -761,10 +765,17 @@ class TestFunctions : public TestFixture { "}\n"); ASSERT_EQUALS("", errout_str()); - check("size_t f() { wchar_t x = L'x'; return wcslen(&x); }"); + check("char* f() {\n" // #14715 + " char a[3] = { 'a', 'b', 'c' };\n" + " *a = 0;\n" + " return strdup(a);\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("size_t f() { wchar_t x = L'x'; return wcslen(&x); }\n"); ASSERT_EQUALS("[test.cpp:1:46]: (error) Invalid wcslen() argument nr 1. A nul-terminated string is required. [invalidFunctionArgStr]\n", errout_str()); - check("void f() { char a[10] = \"1234567890\"; puts(a); }", dinit(CheckOptions, $.cpp = false)); // #1770 + check("void f() { char a[10] = \"1234567890\"; puts(a); }\n", dinit(CheckOptions, $.cpp = false)); // #1770 ASSERT_EQUALS("[test.c:1:44]: (error) Invalid puts() argument nr 1. A nul-terminated string is required. [invalidFunctionArgStr]\n", errout_str()); } @@ -783,6 +794,22 @@ class TestFunctions : public TestFixture { ASSERT_EQUALS("", errout_str()); } + void invalidFunctionArgErrorPath() { + setMultiline(); + Settings s = settings; + s.templateLocation = "{file}:{line}:note:{info}\n"; + check("double f(bool b) {\n" + " double d = -1.0;\n" + " if (b)\n" + " return d;\n" + " return std::log(d);\n" + "}\n", s); + ASSERT_EQUALS("[test.cpp:5:21]: warning: Invalid std::log() argument nr 1. The value is -1 but the valid values are '4.94066e-324:'. [invalidFunctionArg]\n" + "[test.cpp:2:18]: note: Assignment 'd=-1.0', assigned value is -1.0\n" + "[test.cpp:3:9]: note: Assuming condition is false\n" + "[test.cpp:5:21]: note: Invalid argument\n", errout_str()); + } + void mathfunctionCall_sqrt() { // sqrt, sqrtf, sqrtl check("void foo()\n" @@ -790,7 +817,7 @@ class TestFunctions : public TestFixture { " std::cout << sqrt(-1) << std::endl;\n" " std::cout << sqrtf(-1) << std::endl;\n" " std::cout << sqrtl(-1) << std::endl;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:25]: (error) Invalid sqrt() argument nr 1. The value is -1 but the valid values are '0.0:'. [invalidFunctionArg]\n" "[test.cpp:4:26]: (error) Invalid sqrtf() argument nr 1. The value is -1 but the valid values are '0.0:'. [invalidFunctionArg]\n" "[test.cpp:5:26]: (error) Invalid sqrtl() argument nr 1. The value is -1 but the valid values are '0.0:'. [invalidFunctionArg]\n", errout_str()); @@ -801,7 +828,7 @@ class TestFunctions : public TestFixture { " std::cout << sqrt(-0.) << std::endl;\n" " std::cout << sqrtf(-0.) << std::endl;\n" " std::cout << sqrtl(-0.) << std::endl;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo()\n" @@ -809,7 +836,7 @@ class TestFunctions : public TestFixture { " std::cout << sqrt(1) << std::endl;\n" " std::cout << sqrtf(1) << std::endl;\n" " std::cout << sqrtl(1) << std::endl;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -829,7 +856,7 @@ class TestFunctions : public TestFixture { " std::cout << log1p(-3) << std::endl;\n" " std::cout << log1pf(-3) << std::endl;\n" " std::cout << log1pl(-3) << std::endl;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:24]: (error) Invalid log() argument nr 1. The value is -2 but the valid values are '4.94066e-324:'. [invalidFunctionArg]\n" "[test.cpp:4:25]: (error) Invalid logf() argument nr 1. The value is -2 but the valid values are '1.4013e-45:'. [invalidFunctionArg]\n" "[test.cpp:5:25]: (error) Invalid logl() argument nr 1. The value is -2 but the valid values are '4.94066e-324:'. [invalidFunctionArg]\n" @@ -866,7 +893,7 @@ class TestFunctions : public TestFixture { " std::cout << log1p(-2) << std::endl;\n" " std::cout << log1pf(-2) << std::endl;\n" " std::cout << log1pl(-2) << std::endl;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:24]: (error) Invalid log() argument nr 1. The value is -1 but the valid values are '4.94066e-324:'. [invalidFunctionArg]\n" "[test.cpp:4:25]: (error) Invalid logf() argument nr 1. The value is -1 but the valid values are '1.4013e-45:'. [invalidFunctionArg]\n" "[test.cpp:5:25]: (error) Invalid logl() argument nr 1. The value is -1 but the valid values are '4.94066e-324:'. [invalidFunctionArg]\n" @@ -903,7 +930,7 @@ class TestFunctions : public TestFixture { " std::cout << log1p(-2.0) << std::endl;\n" " std::cout << log1pf(-2.0) << std::endl;\n" " std::cout << log1pl(-2.0) << std::endl;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:25]: (error) Invalid log() argument nr 1. The value is -1 but the valid values are '4.94066e-324:'. [invalidFunctionArg]\n" "[test.cpp:4:26]: (error) Invalid logf() argument nr 1. The value is -1 but the valid values are '1.4013e-45:'. [invalidFunctionArg]\n" "[test.cpp:5:26]: (error) Invalid logl() argument nr 1. The value is -1 but the valid values are '4.94066e-324:'. [invalidFunctionArg]\n" @@ -940,7 +967,7 @@ class TestFunctions : public TestFixture { " std::cout << log1p(-1.1) << std::endl;\n" " std::cout << log1pf(-1.1) << std::endl;\n" " std::cout << log1pl(-1.1) << std::endl;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:25]: (error) Invalid log() argument nr 1. The value is -0.1 but the valid values are '4.94066e-324:'. [invalidFunctionArg]\n" "[test.cpp:4:26]: (error) Invalid logf() argument nr 1. The value is -0.1 but the valid values are '1.4013e-45:'. [invalidFunctionArg]\n" "[test.cpp:5:26]: (error) Invalid logl() argument nr 1. The value is -0.1 but the valid values are '4.94066e-324:'. [invalidFunctionArg]\n" @@ -977,7 +1004,7 @@ class TestFunctions : public TestFixture { " std::cout << log1p(-1.) << std::endl;\n" " std::cout << log1pf(-1.0) << std::endl;\n" " std::cout << log1pl(-1) << std::endl;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:23]: (error) Invalid log() argument nr 1. The value is 0 but the valid values are '4.94066e-324:'. [invalidFunctionArg]\n" "[test.cpp:4:25]: (error) Invalid logf() argument nr 1. The value is 0 but the valid values are '1.4013e-45:'. [invalidFunctionArg]\n" "[test.cpp:5:25]: (error) Invalid logl() argument nr 1. The value is 0 but the valid values are '4.94066e-324:'. [invalidFunctionArg]\n" @@ -1050,21 +1077,21 @@ class TestFunctions : public TestFixture { " std::cout << log1p(2.0) << std::endl;\n" " std::cout << log1pf(2.0) << std::endl;\n" " std::cout << log1pf(2.0f) << std::endl;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo()\n" "{\n" " std::string *log(0);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #3473 - no warning if "log" is a variable - check("Fred::Fred() : log(0) { }"); + check("Fred::Fred() : log(0) { }\n"); ASSERT_EQUALS("", errout_str()); // #5748 - check("void f() { foo.log(0); }"); + check("void f() { foo.log(0); }\n"); ASSERT_EQUALS("", errout_str()); } @@ -1102,7 +1129,7 @@ class TestFunctions : public TestFixture { " + acosl(0.1E-1) \n" " + acosl(+0.1E-1) \n" " + acosl(-0.1E-1);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo()\n" @@ -1110,7 +1137,7 @@ class TestFunctions : public TestFixture { " std::cout << acos(1.1) << std::endl;\n" " std::cout << acosf(1.1) << std::endl;\n" " std::cout << acosl(1.1) << std::endl;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:25]: (error) Invalid acos() argument nr 1. The value is 1.1 but the valid values are '-1.0:1.0'. [invalidFunctionArg]\n" "[test.cpp:4:26]: (error) Invalid acosf() argument nr 1. The value is 1.1 but the valid values are '-1.0:1.0'. [invalidFunctionArg]\n" "[test.cpp:5:26]: (error) Invalid acosl() argument nr 1. The value is 1.1 but the valid values are '-1.0:1.0'. [invalidFunctionArg]\n", errout_str()); @@ -1120,7 +1147,7 @@ class TestFunctions : public TestFixture { " std::cout << acos(-1.1) << std::endl;\n" " std::cout << acosf(-1.1) << std::endl;\n" " std::cout << acosl(-1.1) << std::endl;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:26]: (error) Invalid acos() argument nr 1. The value is -1.1 but the valid values are '-1.0:1.0'. [invalidFunctionArg]\n" "[test.cpp:4:27]: (error) Invalid acosf() argument nr 1. The value is -1.1 but the valid values are '-1.0:1.0'. [invalidFunctionArg]\n" "[test.cpp:5:27]: (error) Invalid acosl() argument nr 1. The value is -1.1 but the valid values are '-1.0:1.0'. [invalidFunctionArg]\n", errout_str()); @@ -1163,7 +1190,7 @@ class TestFunctions : public TestFixture { " + asinl(0.1E-1) \n" " + asinl(+0.1E-1) \n" " + asinl(-0.1E-1);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo()\n" @@ -1171,7 +1198,7 @@ class TestFunctions : public TestFixture { " std::cout << asin(1.1) << std::endl;\n" " std::cout << asinf(1.1) << std::endl;\n" " std::cout << asinl(1.1) << std::endl;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:25]: (error) Invalid asin() argument nr 1. The value is 1.1 but the valid values are '-1.0:1.0'. [invalidFunctionArg]\n" "[test.cpp:4:26]: (error) Invalid asinf() argument nr 1. The value is 1.1 but the valid values are '-1.0:1.0'. [invalidFunctionArg]\n" "[test.cpp:5:26]: (error) Invalid asinl() argument nr 1. The value is 1.1 but the valid values are '-1.0:1.0'. [invalidFunctionArg]\n", errout_str()); @@ -1181,7 +1208,7 @@ class TestFunctions : public TestFixture { " std::cout << asin(-1.1) << std::endl;\n" " std::cout << asinf(-1.1) << std::endl;\n" " std::cout << asinl(-1.1) << std::endl;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:26]: (error) Invalid asin() argument nr 1. The value is -1.1 but the valid values are '-1.0:1.0'. [invalidFunctionArg]\n" "[test.cpp:4:27]: (error) Invalid asinf() argument nr 1. The value is -1.1 but the valid values are '-1.0:1.0'. [invalidFunctionArg]\n" "[test.cpp:5:27]: (error) Invalid asinl() argument nr 1. The value is -1.1 but the valid values are '-1.0:1.0'. [invalidFunctionArg]\n", errout_str()); @@ -1194,7 +1221,7 @@ class TestFunctions : public TestFixture { " std::cout << pow(0,-10) << std::endl;\n" " std::cout << powf(0,-10) << std::endl;\n" " std::cout << powl(0,-10) << std::endl;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:19]: (warning) Passing values 0 and -10 to pow() leads to implementation-defined result. [wrongmathcall]\n" "[test.cpp:4:19]: (warning) Passing values 0 and -10 to powf() leads to implementation-defined result. [wrongmathcall]\n" "[test.cpp:5:19]: (warning) Passing values 0 and -10 to powl() leads to implementation-defined result. [wrongmathcall]\n", errout_str()); @@ -1204,7 +1231,7 @@ class TestFunctions : public TestFixture { " std::cout << pow(0,10) << std::endl;\n" " std::cout << powf(0,10) << std::endl;\n" " std::cout << powl(0,10) << std::endl;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1245,7 +1272,7 @@ class TestFunctions : public TestFixture { " std::cout << atan2l(0.1E-1,3) ;\n" " std::cout << atan2l(+0.1E-1,1) ;\n" " std::cout << atan2l(-0.1E-1,8) ;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo()\n" @@ -1253,7 +1280,7 @@ class TestFunctions : public TestFixture { " std::cout << atan2(0,0) << std::endl;\n" " std::cout << atan2f(0,0) << std::endl;\n" " std::cout << atan2l(0,0) << std::endl;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:19]: (warning) Passing values 0 and 0 to atan2() leads to implementation-defined result. [wrongmathcall]\n" "[test.cpp:4:19]: (warning) Passing values 0 and 0 to atan2f() leads to implementation-defined result. [wrongmathcall]\n" "[test.cpp:5:19]: (warning) Passing values 0 and 0 to atan2l() leads to implementation-defined result. [wrongmathcall]\n", errout_str()); @@ -1266,7 +1293,7 @@ class TestFunctions : public TestFixture { " std::cout << fmod(1.0,0) << std::endl;\n" " std::cout << fmodf(1.0,0) << std::endl;\n" " std::cout << fmodl(1.0,0) << std::endl;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:28]: (error) Invalid fmod() argument nr 2. The value is 0 but the valid values are '!0.0'. [invalidFunctionArg]\n" "[test.cpp:4:29]: (error) Invalid fmodf() argument nr 2. The value is 0 but the valid values are '!0.0'. [invalidFunctionArg]\n" "[test.cpp:5:29]: (error) Invalid fmodl() argument nr 2. The value is 0 but the valid values are '!0.0'. [invalidFunctionArg]\n" @@ -1279,7 +1306,7 @@ class TestFunctions : public TestFixture { " std::cout << fmod(1.0,1) << std::endl;\n" " std::cout << fmodf(1.0,1) << std::endl;\n" " std::cout << fmodl(1.0,1) << std::endl;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1288,7 +1315,7 @@ class TestFunctions : public TestFixture { " print(exp(x) - 1);\n" " print(log(1 + x));\n" " print(1 - erf(x));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11]: (style) Expression 'exp(x) - 1' can be replaced by 'expm1(x)' to avoid loss of precision. [unpreciseMathCall]\n" "[test.cpp:3:11]: (style) Expression 'log(1 + x)' can be replaced by 'log1p(x)' to avoid loss of precision. [unpreciseMathCall]\n" "[test.cpp:4:11]: (style) Expression '1 - erf(x)' can be replaced by 'erfc(x)' to avoid loss of precision. [unpreciseMathCall]\n", errout_str()); @@ -1297,7 +1324,7 @@ class TestFunctions : public TestFixture { " print(exp(x) - 1.0);\n" " print(log(1.0 + x));\n" " print(1.0 - erf(x));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11]: (style) Expression 'exp(x) - 1' can be replaced by 'expm1(x)' to avoid loss of precision. [unpreciseMathCall]\n" "[test.cpp:3:11]: (style) Expression 'log(1 + x)' can be replaced by 'log1p(x)' to avoid loss of precision. [unpreciseMathCall]\n" "[test.cpp:4:12]: (style) Expression '1 - erf(x)' can be replaced by 'erfc(x)' to avoid loss of precision. [unpreciseMathCall]\n", errout_str()); @@ -1306,7 +1333,7 @@ class TestFunctions : public TestFixture { " print(exp(3 + x*f(a)) - 1);\n" " print(log(x*4 + 1));\n" " print(1 - erf(34*x + f(x) - c));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11]: (style) Expression 'exp(x) - 1' can be replaced by 'expm1(x)' to avoid loss of precision. [unpreciseMathCall]\n" "[test.cpp:3:11]: (style) Expression 'log(1 + x)' can be replaced by 'log1p(x)' to avoid loss of precision. [unpreciseMathCall]\n" "[test.cpp:4:11]: (style) Expression '1 - erf(x)' can be replaced by 'erfc(x)' to avoid loss of precision. [unpreciseMathCall]\n", errout_str()); @@ -1314,7 +1341,7 @@ class TestFunctions : public TestFixture { check("void foo() {\n" " print(2*exp(x) - 1);\n" " print(1 - erf(x)/2.0);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1331,67 +1358,67 @@ class TestFunctions : public TestFixture { check("void foo() {\n" " mystrcmp(a, b);\n" - "}", settings2); + "}\n", settings2); ASSERT_EQUALS("[test.cpp:2:3]: (warning) Return value of function mystrcmp() is not used. [ignoredReturnValue]\n", errout_str()); check("void foo() {\n" " foo::mystrcmp(a, b);\n" - "}", settings2); + "}\n", settings2); ASSERT_EQUALS("[test.cpp:2:8]: (warning) Return value of function foo::mystrcmp() is not used. [ignoredReturnValue]\n", errout_str()); check("void f() {\n" " foo x;\n" " x.mystrcmp(a, b);\n" - "}", settings2); + "}\n", settings2); ASSERT_EQUALS("[test.cpp:3:5]: (warning) Return value of function x.mystrcmp() is not used. [ignoredReturnValue]\n", errout_str()); check("bool mystrcmp(char* a, char* b);\n" // cppcheck sees a custom strcmp definition, but it returns a value. Assume it is the one specified in the library. "void foo() {\n" " mystrcmp(a, b);\n" - "}", settings2); + "}\n", settings2); ASSERT_EQUALS("[test.cpp:3:5]: (warning) Return value of function mystrcmp() is not used. [ignoredReturnValue]\n", errout_str()); check("void mystrcmp(char* a, char* b);\n" // cppcheck sees a custom strcmp definition which returns void! "void foo() {\n" " mystrcmp(a, b);\n" - "}", settings2); + "}\n", settings2); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" " class mystrcmp { mystrcmp() {} };\n" // strcmp is a constructor definition here - "}", settings2); + "}\n", settings2); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" " return mystrcmp(a, b);\n" - "}", settings2); + "}\n", settings2); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" " return foo::mystrcmp(a, b);\n" - "}", settings2); + "}\n", settings2); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" " if(mystrcmp(a, b));\n" - "}", settings2); + "}\n", settings2); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" " bool b = mystrcmp(a, b);\n" - "}", settings2); + "}\n", settings2); ASSERT_EQUALS("", errout_str()); // #6194 check("void foo() {\n" " MyStrCmp mystrcmp(x, y);\n" - "}", settings2); + "}\n", settings2); ASSERT_EQUALS("", errout_str()); // #6197 check("void foo() {\n" " abc::def.mystrcmp(a,b);\n" - "}", settings2); + "}\n", settings2); ASSERT_EQUALS("", errout_str()); // #6233 @@ -1402,30 +1429,30 @@ class TestFunctions : public TestFixture { " };\n" " lambda(13.3);\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // TODO: use settings2? // #6669 check("void foo(size_t size) {\n" " void * res{malloc(size)};\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // TODO: use settings2? // #7447 check("void foo() {\n" " int x{mystrcmp(a,b)};\n" - "}", settings2); + "}\n", settings2); ASSERT_EQUALS("", errout_str()); // #7905 check("void foo() {\n" " int x({mystrcmp(a,b)});\n" - "}", settings2); + "}\n", settings2); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" // don't crash " DEBUG(123)(mystrcmp(a,b))(fd);\n" - "}", settings2); + "}\n", settings2); check("struct teststruct {\n" " int testfunc1() __attribute__ ((warn_unused_result)) { return 1; }\n" " [[nodiscard]] int testfunc2() { return 1; }\n" @@ -1436,7 +1463,7 @@ class TestFunctions : public TestFixture { " TestStruct1.testfunc1();\n" " TestStruct1.testfunc2();\n" " return 0;\n" - "}", settings2); + "}\n", settings2); ASSERT_EQUALS("[test.cpp:4:18]: (warning) Return value of function testfunc1() is not used. [ignoredReturnValue]\n" "[test.cpp:4:31]: (warning) Return value of function testfunc2() is not used. [ignoredReturnValue]\n" "[test.cpp:8:17]: (warning) Return value of function TestStruct1.testfunc1() is not used. [ignoredReturnValue]\n" @@ -1446,7 +1473,7 @@ class TestFunctions : public TestFixture { check("template uint8_t b(std::tuple d) {\n" " std::tuple c{std::move(d)};\n" " return std::get<0>(c);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // TODO: use settings2? check("struct A { int x; };\n" @@ -1454,13 +1481,13 @@ class TestFunctions : public TestFixture { "A f(int x, Ts... xs) {\n" " return {std::move(x), static_cast(xs)...};\n" "}\n" - "A g() { return f(1); }"); + "A g() { return f(1); }\n"); ASSERT_EQUALS("", errout_str()); // TODO: use settings2? // #8412 - unused operator result check("void foo() {\n" " !mystrcmp(a, b);\n" - "}", settings2); + "}\n", settings2); ASSERT_EQUALS("[test.cpp:2:4]: (warning) Return value of function mystrcmp() is not used. [ignoredReturnValue]\n", errout_str()); check("void f(std::vector v) {\n" @@ -1492,24 +1519,24 @@ class TestFunctions : public TestFixture { check("void foo() {\n" " mystrcmp(a, b);\n" - "}", settings2); + "}\n", settings2); ASSERT_EQUALS("[test.cpp:2:3]: (style) Error code from the return value of function mystrcmp() is not used. [ignoredReturnErrorCode]\n", errout_str()); } void memsetZeroBytes() { check("void f() {\n" " memset(p, 10, 0x0);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:5]: (warning) memset() called to fill 0 bytes. [memsetZeroBytes]\n", errout_str()); check("void f() {\n" " memset(p, sizeof(p), 0);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:5]: (warning) memset() called to fill 0 bytes. [memsetZeroBytes]\n", errout_str()); check("void f() {\n" " memset(p, sizeof(p), i);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #6269 false positives in case of overloaded standard library functions @@ -1518,13 +1545,13 @@ class TestFunctions : public TestFixture { " void f( void ) {\n" " memset( 0 );\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // #7285 check("void f() {\n" " memset(&tm, sizeof(tm), 0);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:5]: (warning) memset() called to fill 0 bytes. [memsetZeroBytes]\n", errout_str()); } @@ -1535,7 +1562,7 @@ class TestFunctions : public TestFixture { " memset(is, 1.0f, 40);\n" " int* is2 = new int[10];\n" " memset(is2, 0.1f, 40);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:17]: (portability) The 2nd memset() argument '1.0f' is a float, its representation is implementation defined. [memsetFloat]\n" "[test.cpp:5:18]: (portability) The 2nd memset() argument '0.1f' is a float, its representation is implementation defined. [memsetFloat]\n", errout_str()); @@ -1543,19 +1570,19 @@ class TestFunctions : public TestFixture { " int* is = new int[10];\n" " float g = computeG();\n" " memset(is, g, 40);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:16]: (portability) The 2nd memset() argument 'g' is a float, its representation is implementation defined. [memsetFloat]\n", errout_str()); check("void f() {\n" " int* is = new int[10];\n" " memset(is, 0.0f, 40);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" // FP " float x = 2.3f;\n" " memset(a, (x?64:0), 40);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -1563,7 +1590,7 @@ class TestFunctions : public TestFixture { " memset(ss, 256, 4);\n" " short ss2[2];\n" " memset(ss2, -129, 4);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:16]: (warning) The 2nd memset() argument '256' doesn't fit into an 'unsigned char'. [memsetValueOutOfRange]\n" "[test.cpp:5:18]: (warning) The 2nd memset() argument '-129' doesn't fit into an 'unsigned char'. [memsetValueOutOfRange]\n", errout_str()); @@ -1578,23 +1605,23 @@ class TestFunctions : public TestFixture { " memset(cs2, 255, 30);\n" " char cs3[30];\n" " memset(cs3, 0, 30);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " int is[10];\n" " const int i = g();\n" " memset(is, 1.0f + i, 40);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:21]: (portability) The 2nd memset() argument '1.0f+i' is a float, its representation is implementation defined. [memsetFloat]\n", errout_str()); } void checkMissingReturn1() { - check("int f() {}"); + check("int f() {}\n"); ASSERT_EQUALS("[test.cpp:1:10]: (error) Found an exit path from function with non-void return type that has missing return statement [missingReturn]\n", errout_str()); { - const char code[] = "int main(void) {}"; + const char code[] = "int main(void) {}\n"; { const Settings s = settingsBuilder().c(Standards::C89).build(); @@ -1612,16 +1639,16 @@ class TestFunctions : public TestFixture { } } - check("F(A,B) { x=1; }"); + check("F(A,B) { x=1; }\n"); ASSERT_EQUALS("", errout_str()); - check("auto foo4() -> void {}"); + check("auto foo4() -> void {}\n"); ASSERT_EQUALS("", errout_str()); - check("void STDCALL foo() {}"); + check("void STDCALL foo() {}\n"); ASSERT_EQUALS("", errout_str()); - check("void operator=(int y) { x=y; }"); + check("void operator=(int y) { x=y; }\n"); ASSERT_EQUALS("", errout_str()); check("int f() {\n" @@ -1637,14 +1664,14 @@ class TestFunctions : public TestFixture { check("int foo(int x) {\n" " return 1;\n" " (void)x;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int foo(int x) {\n" " if (x) goto out;\n" " return 1;\n" "out:\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:11]: (error) Found an exit path from function with non-void return type that has missing return statement [missingReturn]\n", errout_str()); // switch @@ -1653,7 +1680,7 @@ class TestFunctions : public TestFixture { " case 1: break;\n" // <- error " case 2: return 1;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:17]: (error) Found an exit path from function with non-void return type that has missing return statement [missingReturn]\n", errout_str()); check("int f() {\n" @@ -1661,7 +1688,7 @@ class TestFunctions : public TestFixture { " case 1: return 2; break;\n" " default: return 1;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("bool test(unsigned char v1, int v2) {\n" @@ -1675,7 +1702,7 @@ class TestFunctions : public TestFixture { " default:\n" " return true;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // if/else @@ -1683,7 +1710,7 @@ class TestFunctions : public TestFixture { " if (x) {\n" " return 1;\n" " }\n" // <- error (missing else) - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:5]: (error) Found an exit path from function with non-void return type that has missing return statement [missingReturn]\n", errout_str()); check("int f(int x) {\n" @@ -1692,19 +1719,19 @@ class TestFunctions : public TestFixture { " } else {\n" " return 1;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:9]: (error) Found an exit path from function with non-void return type that has missing return statement [missingReturn]\n", errout_str()); check("int f() {\n" " if (!0) {\n" " return 1;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int f() {\n" " if (!0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:14]: (error) Found an exit path from function with non-void return type that has missing return statement [missingReturn]\n", errout_str()); // loop @@ -1712,7 +1739,7 @@ class TestFunctions : public TestFixture { " while (1) {\n" " dostuff();\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // return {..} @@ -1721,20 +1748,20 @@ class TestFunctions : public TestFixture { " return {};\n" " else\n" " return {1, 2};\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // noreturn function - check("int f(int x) { exit(0); }"); + check("int f(int x) { exit(0); }\n"); ASSERT_EQUALS("", errout_str()); - check("int f(int x) { assert(0); }"); + check("int f(int x) { assert(0); }\n"); ASSERT_EQUALS("", errout_str()); - check("int f(int x) { if (x) return 1; else return bar({1}, {}); }"); + check("int f(int x) { if (x) return 1; else return bar({1}, {}); }\n"); ASSERT_EQUALS("", errout_str()); - check("auto f() -> void {}"); // #10342 + check("auto f() -> void {}\n"); // #10342 ASSERT_EQUALS("", errout_str()); check("struct S1 {\n" // #7433 @@ -1754,36 +1781,36 @@ class TestFunctions : public TestFixture { errout_str()); // #11171 - check("std::enable_if_t f() {}"); + check("std::enable_if_t f() {}\n"); ASSERT_EQUALS("", errout_str()); - check("std::enable_if_t f() {}"); + check("std::enable_if_t f() {}\n"); ASSERT_EQUALS( "[test.cpp:1:51]: (error) Found an exit path from function with non-void return type that has missing return statement [missingReturn]\n", errout_str()); - check("template std::enable_if_t{}, int> f(T) {}"); + check("template std::enable_if_t{}, int> f(T) {}\n"); ASSERT_EQUALS( "[test.cpp:1:71]: (error) Found an exit path from function with non-void return type that has missing return statement [missingReturn]\n", errout_str()); - check("template std::enable_if_t{}> f(T) {}"); + check("template std::enable_if_t{}> f(T) {}\n"); ASSERT_EQUALS("", errout_str()); - check("typename std::enable_if::type f() {}"); + check("typename std::enable_if::type f() {}\n"); ASSERT_EQUALS("", errout_str()); - check("typename std::enable_if::type f() {}"); + check("typename std::enable_if::type f() {}\n"); ASSERT_EQUALS( "[test.cpp:1:64]: (error) Found an exit path from function with non-void return type that has missing return statement [missingReturn]\n", errout_str()); - check("template typename std::enable_if{}, int>::type f(T) {}"); + check("template typename std::enable_if{}, int>::type f(T) {}\n"); ASSERT_EQUALS( "[test.cpp:1:84]: (error) Found an exit path from function with non-void return type that has missing return statement [missingReturn]\n", errout_str()); - check("template typename std::enable_if{}>::type f(T) {}"); + check("template typename std::enable_if{}>::type f(T) {}\n"); ASSERT_EQUALS("", errout_str()); check("struct S {\n" @@ -1919,6 +1946,24 @@ class TestFunctions : public TestFixture { ASSERT_EQUALS("[test.cpp:3:19]: (error) Found an exit path from function with non-void return type that has missing return statement [missingReturn]\n", errout_str()); } + void checkMissingReturn8() { + const Settings s = settingsBuilder(settings).cpp(Standards::CPP20).build(); + check("boost::asio::awaitable test() {\n" + " co_return;\n" + "}\n",s); + ASSERT_EQUALS("", errout_str()); + } + + void checkMissingReturn9() { + check("struct S { int v; };\n" + " S operator/(S x, S y) { return { x.v / y.v }; }\n" + " S f(int a, int b) {\n" + " if (b) { return S{ a } / S{ b }; }\n" + " else { return {}; }\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + } + void checkMissingReturnStdInt() {// #14482 - FN check("std::int32_t f() {}\n"); ASSERT_EQUALS("[test.cpp:1:19]: (error) Found an exit path from function with non-void return type that has missing return statement [missingReturn]\n", errout_str()); @@ -1926,35 +1971,35 @@ class TestFunctions : public TestFixture { // NRVO check void returnLocalStdMove1() { - check("struct A{}; A f() { A var; return std::move(var); }"); + check("struct A{}; A f() { A var; return std::move(var); }\n"); ASSERT_EQUALS("[test.cpp:1:45]: (performance) Using std::move for returning object by-value from function will affect copy elision optimization." " More: https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rf-return-move-local [returnStdMoveLocal]\n", errout_str()); } // RVO, C++03 ctor style void returnLocalStdMove2() { - check("struct A{}; A f() { return std::move( A() ); }"); + check("struct A{}; A f() { return std::move( A() ); }\n"); ASSERT_EQUALS("[test.cpp:1:40]: (performance) Using std::move for returning object by-value from function will affect copy elision optimization." " More: https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rf-return-move-local [returnStdMoveLocal]\n", errout_str()); } // RVO, new ctor style void returnLocalStdMove3() { - check("struct A{}; A f() { return std::move(A{}); }"); + check("struct A{}; A f() { return std::move(A{}); }\n"); ASSERT_EQUALS("[test.cpp:1:39]: (performance) Using std::move for returning object by-value from function will affect copy elision optimization." " More: https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rf-return-move-local [returnStdMoveLocal]\n", errout_str()); } // Function argument void returnLocalStdMove4() { - check("struct A{}; A f(A a) { return std::move(A{}); }"); + check("struct A{}; A f(A a) { return std::move(A{}); }\n"); ASSERT_EQUALS("[test.cpp:1:42]: (performance) Using std::move for returning object by-value from function will affect copy elision optimization." " More: https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rf-return-move-local [returnStdMoveLocal]\n", errout_str()); } void returnLocalStdMove5() { check("struct A{} a; A f1() { return std::move(a); }\n" - "A f2() { volatile A var; return std::move(var); }"); + "A f2() { volatile A var; return std::move(var); }\n"); ASSERT_EQUALS("", errout_str()); check("struct S { std::string msg{ \"abc\" }; };\n" @@ -1981,14 +2026,14 @@ class TestFunctions : public TestFixture { " int *a;\n" " a = malloc( -10 );\n" " free(a);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:17]: (error) Invalid malloc() argument nr 1. The value is -10 but the valid values are '0:'. [invalidFunctionArg]\n", errout_str()); check("void f() {\n" " int *a;\n" " a = alloca( -10 );\n" " free(a);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:8]: (warning) Obsolete function 'alloca' called. [allocaCalled]\n" "[test.cpp:3:17]: (error) Invalid alloca() argument nr 1. The value is -10 but the valid values are '0:'. [invalidFunctionArg]\n", errout_str()); } @@ -1999,12 +2044,12 @@ class TestFunctions : public TestFixture { check("void f() {\n" " lib_func();" - "}", s); + "}\n", s); ASSERT_EQUALS("[test.cpp:2:5]: (information) --check-library: There is no matching configuration for function lib_func() [checkLibraryFunction]\n", errout_str()); check("void f(void* v) {\n" " lib_func(v);" - "}", s); + "}\n", s); ASSERT_EQUALS("[test.cpp:2:5]: (information) --check-library: There is no matching configuration for function lib_func() [checkLibraryFunction]\n", errout_str()); // #10105 @@ -2020,7 +2065,7 @@ class TestFunctions : public TestFixture { "\n" " void testFunctionReturnType() {\n" " }\n" - "};", s); + "};\n", s); ASSERT_EQUALS("", errout_str()); // #11183 @@ -2030,7 +2075,7 @@ class TestFunctions : public TestFixture { "\n" "void f() {\n" " cb(std::string(\"\"));\n" - "}", s); + "}\n", s); TODO_ASSERT_EQUALS("", "[test.cpp:6:5]: (information) --check-library: There is no matching configuration for function cb() [checkLibraryFunction]\n", errout_str()); // #7375 diff --git a/test/testgarbage.cpp b/test/testgarbage.cpp index 5f6b67867f7..c798950b8e3 100644 --- a/test/testgarbage.cpp +++ b/test/testgarbage.cpp @@ -17,6 +17,7 @@ */ #include "check.h" +#include "checks.h" #include "errortypes.h" #include "fixture.h" #include "helpers.h" @@ -285,8 +286,8 @@ class TestGarbage : public TestFixture { ASSERT_LOC(tokenizer.tokenize(code), file, line); // call all "runChecks" in all registered Check classes - for (auto it = Check::instances().cbegin(); it != Check::instances().cend(); ++it) { - (*it)->runChecks(tokenizer, this); + for (Check * const c : CheckInstances::get()) { + c->runChecks(tokenizer, *this); } return tokenizer.tokens()->stringifyList(false, false, false, true, false, nullptr, nullptr); @@ -320,8 +321,7 @@ class TestGarbage : public TestFixture { void final_class_x() { - - const char code[] = "class __declspec(dllexport) x final { };"; + const char code[] = "class __declspec(dllexport) x final { };\n"; SimpleTokenizer tokenizer(settings, *this); ASSERT(tokenizer.tokenize(code)); ASSERT_EQUALS("", errout_str()); @@ -329,18 +329,18 @@ class TestGarbage : public TestFixture { void wrong_syntax1() { { - const char code[] ="TR(kvmpio, PROTO(int rw), ARGS(rw), TP_(aa->rw;))"; + const char code[] ="TR(kvmpio, PROTO(int rw), ARGS(rw), TP_(aa->rw;))\n"; ASSERT_THROW_INTERNAL(checkCode(code), UNKNOWN_MACRO); ASSERT_EQUALS("", errout_str()); } { - const char code[] ="struct A { template struct { }; };"; + const char code[] ="struct A { template struct { }; };\n"; ASSERT_THROW_INTERNAL(checkCode(code), SYNTAX); } { - const char code[] ="enum ABC { A,B, typedef enum { C } };"; + const char code[] ="enum ABC { A,B, typedef enum { C } };\n"; ASSERT_THROW_INTERNAL(checkCode(code), SYNTAX); } } @@ -351,7 +351,7 @@ class TestGarbage : public TestFixture { " Y y;\n" "}\n" "\n" - "void G( template class (j) ) {}"; + "void G( template class (j) ) {}\n"; // don't segfault.. ASSERT_THROW_INTERNAL(checkCode(code), SYNTAX); @@ -366,7 +366,7 @@ class TestGarbage : public TestFixture { " for( #endif typedef typedef cb[N] )\n" " ca[N]; = cb[i]\n" " )\n" - "}"; + "}\n"; SimpleTokenizer tokenizer(settings, *this); try { @@ -380,25 +380,25 @@ class TestGarbage : public TestFixture { } void wrong_syntax4() { // #3618 - const char code[] = "typedef void (x) (int); return x&"; + const char code[] = "typedef void (x) (int); return x&\n"; ASSERT_THROW_INTERNAL(checkCode(code), SYNTAX); } void wrong_syntax_if_macro() { // #2518 #4171 - ASSERT_THROW_INTERNAL(checkCode("void f() { if MACRO(); }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("void f() { if MACRO(); }\n"), SYNTAX); // #4668 - note there is no semicolon after MACRO() - ASSERT_THROW_INTERNAL(checkCode("void f() { if (x) MACRO() {} }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("void f() { if (x) MACRO() {} }\n"), SYNTAX); // #4810 - note there is no semicolon after MACRO() - ASSERT_THROW_INTERNAL(checkCode("void f() { if (x) MACRO() else ; }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("void f() { if (x) MACRO() else ; }\n"), SYNTAX); } void wrong_syntax_class_x_y() { // #3585 - const char code[] = "class x y { };"; + const char code[] = "class x y { };\n"; { SimpleTokenizer tokenizer(settings, *this, false); @@ -413,65 +413,65 @@ class TestGarbage : public TestFixture { } void wrong_syntax_anonymous_struct() { - ASSERT_THROW_INTERNAL(checkCode("struct { int x; } = {0};"), SYNTAX); - ASSERT_THROW_INTERNAL(checkCode("struct { int x; } * = {0};"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("struct { int x; } = {0};\n"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("struct { int x; } * = {0};\n"), SYNTAX); } void syntax_case_default() { - ASSERT_THROW_INTERNAL(checkCode("void f() {switch (n) { case: z(); break;}}"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("void f() {switch (n) { case: z(); break;}}\n"), SYNTAX); - ASSERT_THROW_INTERNAL(checkCode("void f() {switch (n) { case;: z(); break;}}"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("void f() {switch (n) { case;: z(); break;}}\n"), SYNTAX); - ASSERT_THROW_INTERNAL(checkCode("void f() {switch (n) { case {}: z(); break;}}"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("void f() {switch (n) { case {}: z(); break;}}\n"), SYNTAX); - ASSERT_THROW_INTERNAL(checkCode("void f() {switch (n) { case 0?{1}:{2} : z(); break;}}"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("void f() {switch (n) { case 0?{1}:{2} : z(); break;}}\n"), SYNTAX); - ASSERT_THROW_INTERNAL(checkCode("void f() {switch (n) { case 0?1;:{2} : z(); break;}}"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("void f() {switch (n) { case 0?1;:{2} : z(); break;}}\n"), SYNTAX); - ASSERT_THROW_INTERNAL(checkCode("void f() {switch (n) { case 0?(1?{3:4}):2 : z(); break;}}"), AST); + ASSERT_THROW_INTERNAL(checkCode("void f() {switch (n) { case 0?(1?{3:4}):2 : z(); break;}}\n"), AST); //ticket #4234 - ASSERT_THROW_INTERNAL(checkCode("( ) { switch break ; { switch ( x ) { case } y break ; : } }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("( ) { switch break ; { switch ( x ) { case } y break ; : } }\n"), SYNTAX); //ticket #4267 - ASSERT_THROW_INTERNAL(checkCode("f ( ) { switch break; { switch ( x ) { case } case break; -6: ( ) ; } }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("f ( ) { switch break; { switch ( x ) { case } case break; -6: ( ) ; } }\n"), SYNTAX); // Missing semicolon - ASSERT_THROW_INTERNAL(checkCode("void foo () { switch(0) case 0 : default : }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("void foo () { switch(0) case 0 : default : }\n"), SYNTAX); } void garbageCode1() { - (void)checkCode("struct x foo_t; foo_t typedef y;"); + (void)checkCode("struct x foo_t; foo_t typedef y;\n"); } void garbageCode2() { //#4300 (segmentation fault) - TODO_ASSERT_THROW(checkCode("enum { D = 1 struct { } ; } s.b = D;"), InternalError); + TODO_ASSERT_THROW(checkCode("enum { D = 1 struct { } ; } s.b = D;\n"), InternalError); } void garbageCode3() { //#4849 (segmentation fault in Tokenizer::simplifyStructDecl (invalid code)) - TODO_ASSERT_THROW(checkCode("enum { D = 2 s ; struct y { x } ; } { s.a = C ; s.b = D ; }"), InternalError); + TODO_ASSERT_THROW(checkCode("enum { D = 2 s ; struct y { x } ; } { s.a = C ; s.b = D ; }\n"), InternalError); } void garbageCode4() { // #4887 - ASSERT_THROW_INTERNAL(checkCode("void f ( ) { = a ; if ( 1 ) if = ( 0 ) ; }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("void f ( ) { = a ; if ( 1 ) if = ( 0 ) ; }\n"), SYNTAX); } void garbageCode5() { // #5168 (segmentation fault) - ASSERT_THROW_INTERNAL(checkCode("( asm : ; void : );"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("( asm : ; void : );\n"), SYNTAX); } void garbageCode6() { // #5214 - ASSERT_THROW_INTERNAL(checkCode("int b = ( 0 ? ? ) 1 : 0 ;"), SYNTAX); - ASSERT_THROW_INTERNAL(checkCode("int a = int b = ( 0 ? ? ) 1 : 0 ;"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("int b = ( 0 ? ? ) 1 : 0 ;\n"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("int a = int b = ( 0 ? ? ) 1 : 0 ;\n"), SYNTAX); } void garbageCode7() { - ASSERT_THROW_INTERNAL(checkCode("1 (int j) { return return (c) * sizeof } y[1];"), SYNTAX); - ASSERT_THROW_INTERNAL(checkCode("foo(Args&&...) fn void = { } auto template { { e = T::error }; };\n" "ScopedEnum1 se1; { enum class E : T { e = 0 = e ScopedEnum2 struct UnscopedEnum3 { T{ e = 4 }; };\n" "arr[(int) E::e]; }; UnscopedEnum3 e2 = f()\n" @@ -479,48 +479,48 @@ class TestGarbage : public TestFixture { "namespace UnscopedEnum { template struct UnscopedEnum1 { E{ e = T::error }; }; UnscopedEnum1 { enum E : { e = 0 }; };\n" "UnscopedEnum2 ue3; template struct UnscopedEnum3 { enum { }; }; int arr[E::e]; };\n" "UnscopedEnum3 namespace template int f() { enum E { e }; T::error }; return (int) E(); } int test1 int g() { enum E { e = E };\n" - "E::e; } int test2 = g(); }"), InternalError); + "E::e; } int test2 = g(); }\n"), InternalError); } void garbageCode9() { - TODO_ASSERT_THROW(checkCode("enum { e = { } } ( ) { { enum { } } } { e } "), InternalError); + TODO_ASSERT_THROW(checkCode("enum { e = { } } ( ) { { enum { } } } { e } \n"), InternalError); } void garbageCode10() { // #6127 - ASSERT_THROW_INTERNAL(checkCode("for( rl=reslist; rl!=NULL; rl=rl->next )"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("for( rl=reslist; rl!=NULL; rl=rl->next )\n"), SYNTAX); } void garbageCode12() { // do not crash - (void)checkCode("{ g; S (void) { struct } { } int &g; }"); + (void)checkCode("{ g; S (void) { struct } { } int &g; }\n"); ignore_errout(); // we do not care about the output } void garbageCode13() { // Ticket #2607 - crash - (void)checkCode("struct C {} {} x"); + (void)checkCode("struct C {} {} x\n"); } void garbageCode15() { // Ticket #5203 - ASSERT_THROW_INTERNAL(checkCode("int f ( int* r ) { { int s[2] ; f ( s ) ; if ( ) } }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("int f ( int* r ) { { int s[2] ; f ( s ) ; if ( ) } }\n"), SYNTAX); } void garbageCode16() { // #6080 (segmentation fault) - (void)checkCode("{ } A() { delete }"); // #6080 + (void)checkCode("{ } A() { delete }\n"); // #6080 ignore_errout(); // we do not care about the output } void garbageCode17() { ASSERT_THROW_INTERNAL(checkCode("void h(int l) {\n" " while\n" // Don't crash (#3870) - "}"), SYNTAX); + "}\n"), SYNTAX); } void garbageCode18() { - ASSERT_THROW_INTERNAL(checkCode("switch(){case}"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("switch(){case}\n"), SYNTAX); } void garbageCode20() { // #3953 (valgrind errors on garbage code) - ASSERT_EQUALS("void f ( 0 * ) ;", checkCode("void f ( 0 * ) ;")); + ASSERT_EQUALS("void f ( 0 * ) ;", checkCode("void f ( 0 * ) ;\n")); } void garbageCode21() { @@ -531,7 +531,7 @@ class TestGarbage : public TestFixture { " x;\n" " int a, a2, a2*x; if () ;\n" " )\n" - "}"), SYNTAX); + "}\n"), SYNTAX); } void garbageCode22() { @@ -539,7 +539,7 @@ class TestGarbage : public TestFixture { ASSERT_THROW_INTERNAL(checkCode("int f()\n" "{\n" " return if\n" - "}"), SYNTAX); + "}\n"), SYNTAX); } void garbageCode23() { @@ -547,7 +547,7 @@ class TestGarbage : public TestFixture { ASSERT_THROW_INTERNAL_EQUALS(checkCode("{\n" " if (1) = x\n" " else abort s[2]\n" - "}"), + "}\n"), SYNTAX, "syntax error"); } @@ -570,7 +570,7 @@ class TestGarbage : public TestFixture { " switch ( x ) {\n" " case struct Tree : break;\n" " }\n" - "}"), SYNTAX); + "}\n"), SYNTAX); ignore_errout(); // we do not care about the output } @@ -582,13 +582,13 @@ class TestGarbage : public TestFixture { " case 0:\n" " return;\n" " }\n" - "}"), SYNTAX); + "}\n"), SYNTAX); } void garbageCode27() { ASSERT_THROW_INTERNAL(checkCode("int f() {\n" " return if\n" - "}"), SYNTAX); + "}\n"), SYNTAX); } void garbageCode28() { // #5702 (segmentation fault) @@ -596,7 +596,7 @@ class TestGarbage : public TestFixture { (void)checkCode("struct R1 {\n" " int a;\n" " R1 () : a { }\n" - "};"); + "};\n"); ignore_errout(); // we do not care about the output } @@ -604,16 +604,16 @@ class TestGarbage : public TestFixture { // simply survive - a syntax error would be even better (#5867) (void)checkCode("void f(int x) {\n" " x = 42\n" - "}"); + "}\n"); ignore_errout(); // we do not care about the output } void garbageCode31() { - ASSERT_THROW_INTERNAL(checkCode("typedef struct{}x[([],)]typedef e y;(y,x 0){}"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("typedef struct{}x[([],)]typedef e y;(y,x 0){}\n"), SYNTAX); } void garbageCode33() { // #6613 (segmentation fault) - (void)checkCode("main(()B{});"); + (void)checkCode("main(()B{});\n"); } // Bug #6626 crash: Token::astOperand2() const ( do while ) @@ -621,13 +621,13 @@ class TestGarbage : public TestFixture { const char code[] = "void foo(void) {\n" " do\n" " while (0);\n" - "}"; + "}\n"; ASSERT_THROW_INTERNAL(checkCode(code), SYNTAX); } void garbageCode35() { // ticket #2604 segmentation fault - ASSERT_THROW_INTERNAL(checkCode("sizeof <= A"), AST); + ASSERT_THROW_INTERNAL(checkCode("sizeof <= A\n"), AST); } void garbageCode36() { // #6334 @@ -641,311 +641,311 @@ class TestGarbage : public TestFixture { void garbageCode37() { // #5166 segmentation fault (invalid code) in lib/checkother.cpp:329 ( void * f { } void b ( ) { * f } ) - (void)checkCode("void * f { } void b ( ) { * f }"); + (void)checkCode("void * f { } void b ( ) { * f }\n"); ignore_errout(); // we do not care about the output } void garbageCode38() { // Ticket #6666 (segmentation fault) - (void)checkCode("{ f2 { } } void f3 () { delete[] } { }"); + (void)checkCode("{ f2 { } } void f3 () { delete[] } { }\n"); ignore_errout(); // we do not care about the output } void garbageCode40() { // #6620 (segmentation fault) - (void)checkCode("{ ( ) () { virtual } ; { } E } A { : { } ( ) } * const ( ) const { }"); + (void)checkCode("{ ( ) () { virtual } ; { } E } A { : { } ( ) } * const ( ) const { }\n"); // test doesn't seem to work on any platform: ASSERT_THROW(checkCode("{ ( ) () { virtual } ; { } E } A { : { } ( ) } * const ( ) const { }", "test.c"), InternalError); } void garbageCode41() { // #6685 (segmentation fault) - (void)checkCode(" { } { return } *malloc(__SIZE_TYPE__ size); *memcpy(void n); static * const () { memcpy (*slot, 3); } { (); } { }"); + (void)checkCode(" { } { return } *malloc(__SIZE_TYPE__ size); *memcpy(void n); static * const () { memcpy (*slot, 3); } { (); } { }\n"); } void garbageCode42() { // #5760 (segmentation fault) - (void)checkCode("{ } * const ( ) { }"); + (void)checkCode("{ } * const ( ) { }\n"); } void garbageCode43() { // #6703 (segmentation fault) - (void)checkCode("int { }; struct A a = { }"); + (void)checkCode("int { }; struct A a = { }\n"); } void garbageCode44() { // #6704 - ASSERT_THROW_INTERNAL(checkCode("{ { }; }; { class A : }; public typedef b;"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("{ { }; }; { class A : }; public typedef b;\n"), SYNTAX); } void garbageCode45() { // #6608 - ASSERT_THROW_INTERNAL(checkCode("struct true template < > { = } > struct Types \"s\" ; static_assert < int > ;"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("struct true template < > { = } > struct Types \"s\" ; static_assert < int > ;\n"), SYNTAX); } void garbageCode46() { // #6705 (segmentation fault) - (void)checkCode(" { bar(char *x); void foo (int ...) { struct } va_list ap; va_start(ap, size); va_arg(ap, (d)); }"); + (void)checkCode(" { bar(char *x); void foo (int ...) { struct } va_list ap; va_start(ap, size); va_arg(ap, (d)); }\n"); ignore_errout(); // we do not care about the output } void garbageCode47() { // #6706 (segmentation fault) - (void)checkCode(" { { }; }; * new private: B: B;"); + (void)checkCode(" { { }; }; * new private: B: B;\n"); } void garbageCode48() { // #6712 (segmentation fault) - ASSERT_THROW_INTERNAL(checkCode(" { d\" ) d ...\" } int main ( ) { ( ) catch ( A a ) { { } catch ( ) \"\" } }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode(" { d\" ) d ...\" } int main ( ) { ( ) catch ( A a ) { { } catch ( ) \"\" } }\n"), SYNTAX); } void garbageCode49() { // #6715 - ASSERT_THROW_INTERNAL(checkCode(" ( ( ) ) { } ( { ( __builtin_va_arg_pack ( ) ) ; } ) { ( int { ( ) ( ( ) ) } ( ) { } ( ) ) += ( ) }"), AST); + ASSERT_THROW_INTERNAL(checkCode(" ( ( ) ) { } ( { ( __builtin_va_arg_pack ( ) ) ; } ) { ( int { ( ) ( ( ) ) } ( ) { } ( ) ) += ( ) }\n"), AST); } void garbageCode51() { // #6719 - ASSERT_THROW_INTERNAL(checkCode(" (const \"C\" ...); struct base { int f2; base (int arg1, int arg2); }; global_base(0x55, 0xff); { ((global_base.f1 0x55) (global_base.f2 0xff)) { } } base::base(int arg1, int arg2) { f2 = }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode(" (const \"C\" ...); struct base { int f2; base (int arg1, int arg2); }; global_base(0x55, 0xff); { ((global_base.f1 0x55) (global_base.f2 0xff)) { } } base::base(int arg1, int arg2) { f2 = }\n"), SYNTAX); } void garbageCode53() { // #6721 - ASSERT_THROW_INTERNAL(checkCode("{ { } }; void foo (struct int i) { x->b[i] = = }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("{ { } }; void foo (struct int i) { x->b[i] = = }\n"), SYNTAX); } void garbageCode54() { // #6722 - ASSERT_THROW_INTERNAL(checkCode("{ typedef long ((pf) p) (); }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("{ typedef long ((pf) p) (); }\n"), SYNTAX); } void garbageCode55() { // #6724 - ASSERT_THROW_INTERNAL(checkCode("() __attribute__((constructor)); { } { }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("() __attribute__((constructor)); { } { }\n"), SYNTAX); } void garbageCode56() { // #6713 - ASSERT_THROW_INTERNAL(checkCode("void foo() { int a = 0; int b = ???; }"), AST); + ASSERT_THROW_INTERNAL(checkCode("void foo() { int a = 0; int b = ???; }\n"), AST); } void garbageCode57() { // #6731 - ASSERT_THROW_INTERNAL(checkCode("{ } if () try { } catch (...) B::~B { }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("{ } if () try { } catch (...) B::~B { }\n"), SYNTAX); } void garbageCode58() { // #6732, #6762 - ASSERT_THROW_INTERNAL(checkCode("{ }> {= ~A()^{} }P { }"), SYNTAX); - ASSERT_THROW_INTERNAL(checkCode("{= ~A()^{} }P { } { }> is"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("{ }> {= ~A()^{} }P { }\n"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("{= ~A()^{} }P { } { }> is\n"), SYNTAX); } void garbageCode59() { // #6735 - ASSERT_THROW_INTERNAL(checkCode("{ { } }; char font8x8[256][8]"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("{ { } }; char font8x8[256][8]\n"), SYNTAX); } void garbageCode60() { // #6736 - ASSERT_THROW_INTERNAL(checkCode("{ } { } typedef int int_array[]; int_array &right ="), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("{ } { } typedef int int_array[]; int_array &right =\n"), SYNTAX); } void garbageCode61() { // #6737 - ASSERT_THROW_INTERNAL(checkCode("{ (const U&) }; { }; { }; struct U : virtual public"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("{ (const U&) }; { }; { }; struct U : virtual public\n"), SYNTAX); } void garbageCode63() { // #6739 - ASSERT_THROW_INTERNAL(checkCode("{ } { } typedef int u_array[]; typedef u_array &u_array_ref; (u_array_ref arg) { } u_array_ref u_array_ref_gbl_obj0"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("{ } { } typedef int u_array[]; typedef u_array &u_array_ref; (u_array_ref arg) { } u_array_ref u_array_ref_gbl_obj0\n"), SYNTAX); } void garbageCode64() { // #6740 - ASSERT_THROW_INTERNAL(checkCode("{ } foo(void (*bar)(void))"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("{ } foo(void (*bar)(void))\n"), SYNTAX); } void garbageCode65() { // #6741 (segmentation fault) // TODO write some syntax error - (void)checkCode("{ } { } typedef int u_array[]; typedef u_array &u_array_ref; (u_array_ref arg) { } u_array_ref"); + (void)checkCode("{ } { } typedef int u_array[]; typedef u_array &u_array_ref; (u_array_ref arg) { } u_array_ref\n"); } void garbageCode66() { // #6742 - ASSERT_THROW_INTERNAL(checkCode("{ { } }; { { } }; { }; class bar : public virtual"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("{ { } }; { { } }; { }; class bar : public virtual\n"), SYNTAX); } void garbageCode68() { // #6745 (segmentation fault) - (void)checkCode("(int a[3]); typedef void (*fp) (void); fp"); + (void)checkCode("(int a[3]); typedef void (*fp) (void); fp\n"); } void garbageCode69() { // #6746 - ASSERT_THROW_INTERNAL(checkCode("{ (make_mess, aux); } typedef void F(void); aux(void (*x)()) { } (void (*y)()) { } F*"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("{ (make_mess, aux); } typedef void F(void); aux(void (*x)()) { } (void (*y)()) { } F*\n"), SYNTAX); } void garbageCode70() { // #6747 - ASSERT_THROW_INTERNAL(checkCode("{ } __attribute__((constructor)) void"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("{ } __attribute__((constructor)) void\n"), SYNTAX); } void garbageCode71() { // #6748 - ASSERT_THROW_INTERNAL(checkCode("( ) { } typedef void noattr_t ( ) ; noattr_t __attribute__ ( )"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("( ) { } typedef void noattr_t ( ) ; noattr_t __attribute__ ( )\n"), SYNTAX); } void garbageCode72() { // #6749 - ASSERT_THROW_INTERNAL(checkCode("{ } { } typedef void voidfn(void); = } } unsigned *d = (b b--) --*d"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("void foo (int **p) { { { };>= } } unsigned *d = (b b--) --*d\n"), SYNTAX); } void garbageCode78() { // #6756 - ASSERT_THROW_INTERNAL(checkCode("( ) { [ ] } ( ) { } const_array_of_int ( ) { } typedef int A [ ] [ ] ; A a = { { } { } }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("( ) { [ ] } ( ) { } const_array_of_int ( ) { } typedef int A [ ] [ ] ; A a = { { } { } }\n"), SYNTAX); ignore_errout(); // we do not care about the output } void garbageCode79() { // #6757 - ASSERT_THROW_INTERNAL(checkCode("{ } { } typedef void ( func_type ) ( ) ; func_type & ( )"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("{ } { } typedef void ( func_type ) ( ) ; func_type & ( )\n"), SYNTAX); } void garbageCode80() { // #6759 - ASSERT_THROW_INTERNAL(checkCode("( ) { ; ( ) ; ( * ) [ ] ; [ ] = ( ( ) ( ) h ) ! ( ( ) ) } { ; } { } head heads [ ] = ; = & heads [ 2 ]"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("( ) { ; ( ) ; ( * ) [ ] ; [ ] = ( ( ) ( ) h ) ! ( ( ) ) } { ; } { } head heads [ ] = ; = & heads [ 2 ]\n"), SYNTAX); } void garbageCode81() { // #6760 - ASSERT_THROW_INTERNAL(checkCode("{ } [ ] { ( ) } { } typedef void ( *fptr1 ) ( ) const"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("{ } [ ] { ( ) } { } typedef void ( *fptr1 ) ( ) const\n"), SYNTAX); } void garbageCode82() { // #6761 - ASSERT_THROW_INTERNAL(checkCode("p(\"Hello \" 14) _yn(const size_t) typedef bool pfunk (*pfunk)(const size_t)"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("p(\"Hello \" 14) _yn(const size_t) typedef bool pfunk (*pfunk)(const size_t)\n"), SYNTAX); } void garbageCode83() { // #6771 - ASSERT_THROW_INTERNAL(checkCode("namespace A { class } class A { friend C ; } { } ;"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("namespace A { class } class A { friend C ; } { } ;\n"), SYNTAX); } void garbageCode84() { // #6780 - ASSERT_THROW_INTERNAL(checkCode("int main ( [ ] ) { " " [ ] ; int i = 0 ; do { } ; } ( [ ] ) { }"), SYNTAX); // do not crash + ASSERT_THROW_INTERNAL(checkCode("int main ( [ ] ) { " " [ ] ; int i = 0 ; do { } ; } ( [ ] ) { }\n"), SYNTAX); // do not crash } void garbageCode85() { // #6784 (segmentation fault) - (void)checkCode("{ } { } typedef void ( *VoidFunc() ) ( ) ; VoidFunc"); // do not crash + (void)checkCode("{ } { } typedef void ( *VoidFunc() ) ( ) ; VoidFunc\n"); // do not crash } void garbageCode86() { // #6785 - ASSERT_THROW_INTERNAL(checkCode("{ } typedef char ( *( X ) ( void) , char ) ;"), SYNTAX); // do not crash + ASSERT_THROW_INTERNAL(checkCode("{ } typedef char ( *( X ) ( void) , char ) ;\n"), SYNTAX); // do not crash } void garbageCode87() { // #6788 - ASSERT_THROW_INTERNAL(checkCode("((X (128))) (int a) { v[ = {} (x 42) a] += }"), SYNTAX); // do not crash + ASSERT_THROW_INTERNAL(checkCode("((X (128))) (int a) { v[ = {} (x 42) a] += }\n"), SYNTAX); // do not crash } void garbageCode88() { // #6786 - ASSERT_THROW_INTERNAL(checkCode("( ) { ( 0 ) { ( ) } } g ( ) { i( ( false ?) ( ) : 1 ) ; } ;"), SYNTAX); // do not crash + ASSERT_THROW_INTERNAL(checkCode("( ) { ( 0 ) { ( ) } } g ( ) { i( ( false ?) ( ) : 1 ) ; } ;\n"), SYNTAX); // do not crash } void garbageCode90() { // #6790 - ASSERT_THROW_INTERNAL(checkCode("{ } { } typedef int u_array [[ ] ; typedef u_array & u_array_ref] ( ) { } u_array_ref_gbl_obj0"), SYNTAX); // do not crash + ASSERT_THROW_INTERNAL(checkCode("{ } { } typedef int u_array [[ ] ; typedef u_array & u_array_ref] ( ) { } u_array_ref_gbl_obj0\n"), SYNTAX); // do not crash } void garbageCode91() { // #6791 - ASSERT_THROW_INTERNAL(checkCode("typedef __attribute__((vector_size (16))) { return[ (v2df){ } ;] }"), SYNTAX); // throw syntax error + ASSERT_THROW_INTERNAL(checkCode("typedef __attribute__((vector_size (16))) { return[ (v2df){ } ;] }\n"), SYNTAX); // throw syntax error } void garbageCode92() { // #6792 - ASSERT_THROW_INTERNAL(checkCode("template < typename _Tp ( ( ) ; _Tp ) , decltype > { } { ( ) ( ) }"), SYNTAX); // do not crash + ASSERT_THROW_INTERNAL(checkCode("template < typename _Tp ( ( ) ; _Tp ) , decltype > { } { ( ) ( ) }\n"), SYNTAX); // do not crash } void garbageCode94() { // #6803 //checkCode("typedef long __m256i __attribute__ ( ( ( ) ) )[ ; ( ) { } typedef __m256i __attribute__ ( ( ( ) ) ) < ] ( ) { ; }"); - ASSERT_THROW_INTERNAL(checkCode("typedef long __m256i __attribute__ ( ( ( ) ) )[ ; ( ) { } typedef __m256i __attribute__ ( ( ( ) ) ) < ] ( ) { ; }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("typedef long __m256i __attribute__ ( ( ( ) ) )[ ; ( ) { } typedef __m256i __attribute__ ( ( ( ) ) ) < ] ( ) { ; }\n"), SYNTAX); } void garbageCode95() { // #6804 - ASSERT_THROW_INTERNAL(checkCode("{ } x x ; { } h h [ ] ( ) ( ) { struct x ( x ) ; int __attribute__ ( ) f ( ) { h - > first = & x ; struct x * n = h - > first ; ( ) n > } }"), SYNTAX); // do not crash + ASSERT_THROW_INTERNAL(checkCode("{ } x x ; { } h h [ ] ( ) ( ) { struct x ( x ) ; int __attribute__ ( ) f ( ) { h - > first = & x ; struct x * n = h - > first ; ( ) n > } }\n"), SYNTAX); // do not crash } void garbageCode96() { // #6807 - ASSERT_THROW_INTERNAL(checkCode("typedef J J[ ; typedef ( ) ( ) { ; } typedef J J ;] ( ) ( J cx ) { n } ;"), SYNTAX); // throw syntax error + ASSERT_THROW_INTERNAL(checkCode("typedef J J[ ; typedef ( ) ( ) { ; } typedef J J ;] ( ) ( J cx ) { n } ;\n"), SYNTAX); // throw syntax error } void garbageCode97() { // #6808 - ASSERT_THROW_INTERNAL(checkCode("namespace A {> } class A{ { }} class A : T< ;"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("namespace A {> } class A{ { }} class A : T< ;\n"), SYNTAX); } void garbageCode98() { // #6838 ASSERT_THROW_INTERNAL(checkCode("for (cocon To::ta@Taaaaaforconst oken aaaaaaaaaaaa5Dl()\n" "const unsigned in;\n" - "fon *tok = f);.s(Token i = d-)L;"), SYNTAX); + "fon *tok = f);.s(Token i = d-)L;\n"), SYNTAX); } void garbageCode99() { // #6726 ASSERT_THROW_INTERNAL_EQUALS(checkCode("{ xs :: i(:) ! ! x/5 ! !\n" "i, :: a :: b integer, } foo2(x) :: j(:)\n" "b type(*), d(:), a x :: end d(..), foo end\n" - "foo4 b d(..), a a x type(*), b foo2 b"), SYNTAX, "syntax error"); + "foo4 b d(..), a a x type(*), b foo2 b\n"), SYNTAX, "syntax error"); } void garbageCode100() { // #6840 - ASSERT_THROW_INTERNAL(checkCode("( ) { ( i< ) } int foo ( ) { int i ; ( for ( i => 1 ) ; ) }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("( ) { ( i< ) } int foo ( ) { int i ; ( for ( i => 1 ) ; ) }\n"), SYNTAX); } void garbageCode101() { // #6835 // Reported case - ASSERT_THROW_INTERNAL(checkCode("template < class , =( , int) X = 1 > struct A { } ( ) { = } [ { } ] ( ) { A < void > 0 }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("template < class , =( , int) X = 1 > struct A { } ( ) { = } [ { } ] ( ) { A < void > 0 }\n"), SYNTAX); // Reduced case - ASSERT_THROW_INTERNAL(checkCode("template < class =( , ) X = 1> struct A {}; A a;"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("template < class =( , ) X = 1> struct A {}; A a;\n"), SYNTAX); } void garbageCode102() { // #6846 (segmentation fault) - ASSERT_THROW_INTERNAL(checkCode("struct Object { ( ) ; Object & operator= ( Object ) { ( ) { } if ( this != & b ) } }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("struct Object { ( ) ; Object & operator= ( Object ) { ( ) { } if ( this != & b ) } }\n"), SYNTAX); } void garbageCode103() { // #6824 - ASSERT_THROW_INTERNAL(checkCode("a f(r) int * r; { { int s[2]; [f(s); if () ] } }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("a f(r) int * r; { { int s[2]; [f(s); if () ] } }\n"), SYNTAX); } void garbageCode104() { // #6847 - ASSERT_THROW_INTERNAL(checkCode("template < Types > struct S {> ( S < ) S >} { ( ) { } } ( ) { return S < void > ( ) } { ( )> >} { ( ) { } } ( ) { ( ) }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("template < Types > struct S {> ( S < ) S >} { ( ) { } } ( ) { return S < void > ( ) } { ( )> >} { ( ) { } } ( ) { ( ) }\n"), SYNTAX); } void garbageCode105() { // #6859 - ASSERT_THROW_INTERNAL(checkCode("void foo (int i) { int a , for (a 1; a( < 4; a++) if (a) (b b++) (b);) n++; }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("void foo (int i) { int a , for (a 1; a( < 4; a++) if (a) (b b++) (b);) n++; }\n"), SYNTAX); } void garbageCode106() { // #6880 - ASSERT_THROW_INTERNAL(checkCode("[ ] typedef typedef b_array b_array_ref [ ; ] ( ) b_array_ref b_array_ref_gbl_obj0 { ; { b_array_ref b_array_ref_gbl_obj0 } }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("[ ] typedef typedef b_array b_array_ref [ ; ] ( ) b_array_ref b_array_ref_gbl_obj0 { ; { b_array_ref b_array_ref_gbl_obj0 } }\n"), SYNTAX); } void garbageCode107() { // #6881 - TODO_ASSERT_THROW(checkCode("enum { val = 1{ }; { const} }; { } Bar { const int A = val const } ;"), InternalError); + TODO_ASSERT_THROW(checkCode("enum { val = 1{ }; { const} }; { } Bar { const int A = val const } ;\n"), InternalError); } void garbageCode108() { // #6895 "segmentation fault (invalid code) in CheckCondition::isOppositeCond" - ASSERT_THROW_INTERNAL(checkCode("A( ) { } bool f( ) { ( ) F; ( ) { ( == ) if ( !=< || ( !A( ) && r[2] ) ) ( !A( ) ) ( ) } }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("A( ) { } bool f( ) { ( ) F; ( ) { ( == ) if ( !=< || ( !A( ) && r[2] ) ) ( !A( ) ) ( ) } }\n"), SYNTAX); } void garbageCode109() { // #6900 "segmentation fault (invalid code) in CheckStl::runSimplifiedChecks" - (void)checkCode("( *const<> (( ) ) { } ( *const ( ) ( ) ) { } ( * const<> ( size_t )) ) { } ( * const ( ) ( ) ) { }"); + (void)checkCode("( *const<> (( ) ) { } ( *const ( ) ( ) ) { } ( * const<> ( size_t )) ) { } ( * const ( ) ( ) ) { }\n"); } void garbageCode110() { // #6902 "segmentation fault (invalid code) in CheckStl::string_c_str" - ASSERT_THROW_INTERNAL(checkCode("( *const<> ( size_t ) ; foo ) { } * ( *const ( size_t ) ( ) ;> foo )< { }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("( *const<> ( size_t ) ; foo ) { } * ( *const ( size_t ) ( ) ;> foo )< { }\n"), SYNTAX); } void garbageCode111() { // #6907 - TODO_ASSERT_THROW(checkCode("enum { FOO = 1( ,) } {{ FOO }} ;"), InternalError); + TODO_ASSERT_THROW(checkCode("enum { FOO = 1( ,) } {{ FOO }} ;\n"), InternalError); } void garbageCode112() { // #6909 - TODO_ASSERT_THROW(checkCode("enum { FOO = ( , ) } {{ }}>> enum { FOO< = ( ) } { { } } ;"), InternalError); + TODO_ASSERT_THROW(checkCode("enum { FOO = ( , ) } {{ }}>> enum { FOO< = ( ) } { { } } ;\n"), InternalError); } void garbageCode114() { // #2118 ASSERT_NO_THROW(checkCode("Q_GLOBAL_STATIC_WITH_INITIALIZER(Qt4NodeStaticData, qt4NodeStaticData, {\n" " for (unsigned i = 0 ; i < count; i++) {\n" " }\n" - "});")); + "});\n")); } void garbageCode115() { // #5506 - ASSERT_THROW_INTERNAL(checkCode("A template < int { int = -1 ; } template < int N > struct B { int [ A < N > :: zero ] ; } ; B < 0 > b ;"), UNKNOWN_MACRO); + ASSERT_THROW_INTERNAL(checkCode("A template < int { int = -1 ; } template < int N > struct B { int [ A < N > :: zero ] ; } ; B < 0 > b ;\n"), UNKNOWN_MACRO); } void garbageCode116() { // #5356 - ASSERT_THROW_INTERNAL(checkCode("struct template struct B { }; B < 0 > b;"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("struct template struct B { }; B < 0 > b;\n"), SYNTAX); } void garbageCode117() { // #6121 TODO_ASSERT_THROW(checkCode("enum E { f = {} };\n" - "int a = f;"), InternalError); + "int a = f;\n"), InternalError); } void garbageCode118() { // #5600 - missing include causes invalid enum @@ -955,32 +955,32 @@ class TestGarbage : public TestFixture { "};\n" "struct bytecode {};\n" "jv jq_next() { opcode = ((opcode) +NUM_OPCODES);\n" - "}"), SYNTAX); + "}\n"), SYNTAX); } void garbageCode119() { // #5598 (segmentation fault) - (void)checkCode("{ { void foo() { struct }; template struct S { Used x; void bar() } auto f = [this] { }; } };"); + (void)checkCode("{ { void foo() { struct }; template struct S { Used x; void bar() } auto f = [this] { }; } };\n"); ignore_errout(); // we do not care about the output } void garbageCode120() { // #4927 (segmentation fault) (void)checkCode("int main() {\n" " return 0\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } void garbageCode121() { // #2585 ASSERT_THROW_INTERNAL(checkCode("abcdef?" "?<" "123456?" "?>" - "+?" "?="), SYNTAX); + "+?" "?=\n"), SYNTAX); } void garbageCode122() { // #6303 (segmentation fault) (void)checkCode("void foo() {\n" "char *a = malloc(10);\n" "a[0]\n" - "}"); + "}\n"); ignore_errout(); // we do not care about the output } @@ -989,18 +989,18 @@ class TestGarbage : public TestFixture { " class C {\n" " C tpl_mem(T *) { return }\n" " };\n" - "}"); + "}\n"); ignore_errout(); // we do not care about the output } void garbageCode125() { - ASSERT_THROW_INTERNAL(checkCode("{ T struct B : T valueA_AA ; } T : [ T > ( ) { B } template < T > struct A < > : ] { ( ) { return valueA_AC struct { : } } b A < int > AC ( ) a_aa.M ; ( ) ( ) }"), SYNTAX); - ASSERT_THROW_INTERNAL(checkCode("template < Types > struct S :{ ( S < ) S >} { ( ) { } } ( ) { return S < void > ( ) }"), + ASSERT_THROW_INTERNAL(checkCode("{ T struct B : T valueA_AA ; } T : [ T > ( ) { B } template < T > struct A < > : ] { ( ) { return valueA_AC struct { : } } b A < int > AC ( ) a_aa.M ; ( ) ( ) }\n"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("template < Types > struct S :{ ( S < ) S >} { ( ) { } } ( ) { return S < void > ( ) }\n"), SYNTAX); } void garbageCode126() { - ASSERT_THROW_INTERNAL(checkCode("{ } float __ieee754_sinhf ( float x ) { float t , , do { gf_u ( jx ) { } ( 0 ) return ; ( ) { } t } ( 0x42b17180 ) { } }"), + ASSERT_THROW_INTERNAL(checkCode("{ } float __ieee754_sinhf ( float x ) { float t , , do { gf_u ( jx ) { } ( 0 ) return ; ( ) { } t } ( 0x42b17180 ) { } }\n"), SYNTAX); } @@ -1013,38 +1013,38 @@ class TestGarbage : public TestFixture { " ~A() { printf(\"A d'tor\\n\"); }\n" "};\n" " const A& foo(const A& arg) { return arg; }\n" - " foo(A(12)).Var"); + " foo(A(12)).Var\n"); ignore_errout(); // we do not care about the output } void garbageCode128() { - TODO_ASSERT_THROW(checkCode("enum { FOO = ( , ) } {{ }} enum {{ FOO << = } ( ) } {{ }} ;"), + TODO_ASSERT_THROW(checkCode("enum { FOO = ( , ) } {{ }} enum {{ FOO << = } ( ) } {{ }} ;\n"), InternalError); } void garbageCode129() { - ASSERT_THROW_INTERNAL(checkCode("operator - ( { } typedef typename x ; ( ) ) { ( { { ( ( ) ) } ( { } ) } ) }"), + ASSERT_THROW_INTERNAL(checkCode("operator - ( { } typedef typename x ; ( ) ) { ( { { ( ( ) ) } ( { } ) } ) }\n"), SYNTAX); } void garbageCode130() { - TODO_ASSERT_THROW(checkCode("enum { FOO = ( , ){ } { { } } { { FOO} = } ( ) } { { } } enumL\" ( enumL\" { { FOO } ( ) } { { } } ;"), + TODO_ASSERT_THROW(checkCode("enum { FOO = ( , ){ } { { } } { { FOO} = } ( ) } { { } } enumL\" ( enumL\" { { FOO } ( ) } { { } } ;\n"), InternalError); } void garbageCode131() { - ASSERT_THROW_INTERNAL(checkCode("( void ) { ( ) } ( ) / { ( ) }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("( void ) { ( ) } ( ) / { ( ) }\n"), SYNTAX); // actually the invalid code should trigger an syntax error... } void garbageCode132() { // #7022 - ASSERT_THROW_INTERNAL(checkCode("() () { } { () () ({}) i() } void i(void(*ptr) ()) { ptr(!) () }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("() () { } { () () ({}) i() } void i(void(*ptr) ()) { ptr(!) () }\n"), SYNTAX); } void garbageCode133() { - ASSERT_THROW_INTERNAL(checkCode("void f() {{}"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("void f() {{}\n"), SYNTAX); - ASSERT_THROW_INTERNAL(checkCode("void f()) {}"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("void f()) {}\n"), SYNTAX); ASSERT_THROW_INTERNAL(checkCode("void f()\n" "{\n" @@ -1095,32 +1095,32 @@ class TestGarbage : public TestFixture { void garbageCode134() { // Ticket #5605, #5759, #5762, #5774, #5823, #6059 - ASSERT_THROW_INTERNAL(checkCode("foo() template struct tuple Args> tuple { } main() { foo(); }"), SYNTAX); - ASSERT_THROW_INTERNAL(checkCode("( ) template < T1 = typename = unused> struct Args { } main ( ) { foo < int > ( ) ; }"), SYNTAX); - ASSERT_THROW_INTERNAL(checkCode("() template < T = typename = x > struct a {} { f () }"), SYNTAX); - ASSERT_THROW_INTERNAL(checkCode("template < T = typename = > struct a { f }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("foo() template struct tuple Args> tuple { } main() { foo(); }\n"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("( ) template < T1 = typename = unused> struct Args { } main ( ) { foo < int > ( ) ; }\n"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("() template < T = typename = x > struct a {} { f () }\n"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("template < T = typename = > struct a { f }\n"), SYNTAX); (void)checkCode("struct S { int i, j; }; " "template struct X {}; " "X<&S::i, int> x = X<&S::i, int>(); " - "X<&S::j, int> y = X<&S::j, int>();"); + "X<&S::j, int> y = X<&S::j, int>();\n"); ignore_errout(); // we are not interested in the output (void)checkCode("template struct A {}; " "template <> struct A {}; " - "void foo(const void* f = 0) {}"); + "void foo(const void* f = 0) {}\n"); (void)checkCode("template struct A { " " static const int s = 0; " "}; " - "A a;"); + "A a;\n"); (void)checkCode("template class A {}; " "template > class B {}; " "template > class C { " " C() : _a(0), _b(0) {} " " int _a, _b; " - "};"); + "};\n"); (void)checkCode("template struct A { " " static int i; " "}; " - "void f() { A::i = 0; }"); + "void f() { A::i = 0; }\n"); ignore_errout(); // we are not interested in the output } @@ -1129,17 +1129,17 @@ class TestGarbage : public TestFixture { " return a >> extern\n" "}\n" "long a = 1 ;\n" - "long b = 2 ;"); + "long b = 2 ;\n"); ignore_errout(); // we are not interested in the output } void garbageCode136() { // #7033 - ASSERT_THROW_INTERNAL(checkCode("{ } () { void f() { node_t * n; for (; -n) {} } } { }"), + ASSERT_THROW_INTERNAL(checkCode("{ } () { void f() { node_t * n; for (; -n) {} } } { }\n"), SYNTAX); } void garbageCode137() { // #7034 - ASSERT_THROW_INTERNAL(checkCode("\" \" typedef signed char f; \" \"; void a() { f * s = () &[]; (; ) (; ) }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("\" \" typedef signed char f; \" \"; void a() { f * s = () &[]; (; ) (; ) }\n"), SYNTAX); } void garbageCode138() { // #6660 (segmentation fault) @@ -1151,7 +1151,7 @@ class TestGarbage : public TestFixture { " {};\n" " } halo;\n" "}\n" - "CS_PLUGIN_NAMESPACE_END(csparser)"); + "CS_PLUGIN_NAMESPACE_END(csparser)\n"); ignore_errout(); // we are not interested in the output } @@ -1160,19 +1160,19 @@ class TestGarbage : public TestFixture { " de_ctrl = (nDirection == RIGHT_TO_LEFT) ?\n" " ( (0 & ~(((1 << (1 - (0 ? DE_CONTROL_DIRECTION))) - 1) << (0 ? DE_CONTROL_DIRECTION))) )\n" " : 42;\n" - "}"), SYNTAX); + "}\n"), SYNTAX); } void garbageCode140() { // #7035 - ASSERT_THROW_INTERNAL(checkCode("int foo(int align) { int off(= 0 % align; return off) ? \\ align - off : 0; \\ }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("int foo(int align) { int off(= 0 % align; return off) ? \\ align - off : 0; \\ }\n"), SYNTAX); } void garbageCode141() { // #7043 - TODO_ASSERT_THROW(checkCode("enum { X = << { X } } enum { X = X } = X ;"), InternalError); + TODO_ASSERT_THROW(checkCode("enum { X = << { X } } enum { X = X } = X ;\n"), InternalError); } void garbageCode142() { // #7050 - ASSERT_THROW_INTERNAL(checkCode("{ } ( ) { void mapGraphs ( ) { node_t * n ; for (!oid n ) { } } } { }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("{ } ( ) { void mapGraphs ( ) { node_t * n ; for (!oid n ) { } } } { }\n"), SYNTAX); } void garbageCode143() { // #6922 @@ -1186,18 +1186,18 @@ class TestGarbage : public TestFixture { " VGAwCR(0x64,0x?? );\n" " }\n" " }\n" - "}"), AST); + "}\n"), AST); } void garbageCode144() { // #6865 - ASSERT_THROW_INTERNAL(checkCode("template < typename > struct A { } ; template < typename > struct A < INVALID > : A < int[ > { }] ;"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("template < typename > struct A { } ; template < typename > struct A < INVALID > : A < int[ > { }] ;\n"), SYNTAX); } void garbageCode146() { // #7081 ASSERT_THROW_INTERNAL(checkCode("void foo() {\n" " ? std::cout << pow((, 1) << std::endl;\n" " double ( ); }"), SYNTAX); - ASSERT_THROW_INTERNAL(checkCode("void f() { x = , * [ | + 0xff | > 0xff]; }"), SYNTAX); - ASSERT_THROW_INTERNAL(checkCode("void f() { x = , | 0xff , 0.1 < ; }"), SYNTAX); - ASSERT_THROW_INTERNAL(checkCode("void f() { x = [ 1 || ] ; }"), SYNTAX); - ASSERT_THROW_INTERNAL(checkCode("void f1() { x = name6 1 || ? name3 [ ( 1 || +) ] ; }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("void f() { x= name2 & name3 name2 = | 0.1 , | 0.1 , | 0.1 name4 <= >( ); }\n"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("void f() { x = , * [ | + 0xff | > 0xff]; }\n"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("void f() { x = , | 0xff , 0.1 < ; }\n"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("void f() { x = [ 1 || ] ; }\n"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("void f1() { x = name6 1 || ? name3 [ ( 1 || +) ] ; }\n"), SYNTAX); } void garbageValueFlow() { @@ -1306,44 +1306,46 @@ class TestGarbage : public TestFixture { } { // 6122 survive garbage code - const char code[] = "; { int i ; for ( i = 0 ; = 123 ; ) - ; }"; + const char code[] = "; { int i ; for ( i = 0 ; = 123 ; ) - ; }\n"; ASSERT_THROW_INTERNAL(checkCode(code), SYNTAX); } { - const char code[] = "void f1() { for (int n = 0 n < 10 n++); }"; + const char code[] = "void f1() { for (int n = 0 n < 10 n++); }\n"; ASSERT_THROW_INTERNAL(checkCode(code), SYNTAX); } } void garbageSymbolDatabase() { - (void)checkCode("void f( { u = 1 ; } ) { }"); + (void)checkCode("void f( { u = 1 ; } ) { }\n"); - ASSERT_THROW_INTERNAL(checkCode("{ }; void namespace A::f; { g() { int } }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("{ }; void namespace A::f; { g() { int } }\n"), SYNTAX); - ASSERT_THROW_INTERNAL(checkCode("class Foo {}; class Bar : public Foo"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("class Foo {}; class Bar : public Foo\n"), SYNTAX); // #5663 (segmentation fault) (void)checkCode("YY_DECL { switch (yy_act) {\n" " case 65: YY_BREAK\n" " case YY_STATE_EOF(block):\n" " yyterminate();\n" - "} }"); + "} }\n"); ignore_errout(); // we are not interested in the output } void garbageAST() { ASSERT_THROW_INTERNAL(checkCode("N 1024 float a[N], b[N + 3], c[N]; void N; (void) i;\n" "int #define for (i = avx_test i < c[i]; i++)\n" - "b[i + 3] = a[i] * {}"), SYNTAX); // Don't hang (#5787) + "b[i + 3] = a[i] * {}\n"), SYNTAX); // Don't hang (#5787) - (void)checkCode("START_SECTION([EXTRA](bool isValid(const String &filename)))"); // Don't crash (#5991) + (void)checkCode("START_SECTION([EXTRA](bool isValid(const String &filename)))\n"); // Don't crash (#5991) // #8352 ASSERT_THROW_INTERNAL(checkCode("else return % name5 name2 - =name1 return enum | { - name3 1 enum != >= 1 >= ++ { { || " - "{ return return { | { - name3 1 enum != >= 1 >= ++ { name6 | ; ++}}}}}}}"), UNKNOWN_MACRO); + "{ return return { | { - name3 1 enum != >= 1 >= ++ { name6 | ; ++}}}}}}}\n"), UNKNOWN_MACRO); ASSERT_THROW_INTERNAL(checkCode("else return % name5 name2 - =name1 return enum | { - name3 1 enum != >= 1 >= ++ { { || " - "{ return return { | { - name3 1 enum != >= 1 >= ++ { { || ; ++}}}}}}}}"), UNKNOWN_MACRO); + "{ return return { | { - name3 1 enum != >= 1 >= ++ { { || ; ++}}}}}}}}\n"), UNKNOWN_MACRO); + + ASSERT_THROW_INTERNAL(checkCode("f(*p, requires(x));\n"), AST); // #14740 } void templateSimplifierCrashes() { @@ -1367,7 +1369,7 @@ class TestGarbage : public TestFixture { " void f() {\n" " s.operator A >();\n" " }\n" - "}"); + "}\n"); ASSERT_NO_THROW(checkCode( // #6034 "template class T, typename... Args>\n" @@ -1381,7 +1383,7 @@ class TestGarbage : public TestFixture { "\n" "int main() {\n" " foo >::value;\n" - "}")); + "}\n")); (void)checkCode( // #6117 (segmentation fault) "template struct something_like_tuple\n" @@ -1399,7 +1401,7 @@ class TestGarbage : public TestFixture { "\n" "typedef something_like_tuple something_like_tuple_t;\n" "SA ((is_last::value == false));\n" - "SA ((is_last::value == false));"); + "SA ((is_last::value == false));\n"); ignore_errout(); // we are not interested in the output (void)checkCode( // #6225 (use-after-free) @@ -1410,7 +1412,7 @@ class TestGarbage : public TestFixture { " template \n" " struct A {};\n" " using AliasA = A;\n" - "}"); + "}\n"); // #3449 ASSERT_EQUALS("template < typename T > struct A ;\n" @@ -1418,72 +1420,72 @@ class TestGarbage : public TestFixture { "{ } ;", checkCode("template struct A;\n" "struct B { template struct C };\n" - "{};")); + "{};\n")); } void garbageCode161() { //7200 - ASSERT_THROW_INTERNAL(checkCode("{ }{ if () try { } catch (...)} B : : ~B { }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("{ }{ if () try { } catch (...)} B : : ~B { }\n"), SYNTAX); } void garbageCode162() { //7208 - ASSERT_THROW_INTERNAL(checkCode("return << >> x return << >> x ", false), UNKNOWN_MACRO); + ASSERT_THROW_INTERNAL(checkCode("return << >> x return << >> x \n", false), UNKNOWN_MACRO); } void garbageCode163() { //7228 - ASSERT_THROW_INTERNAL(checkCode("typedef s f[](){typedef d h(;f)}", false), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("typedef s f[](){typedef d h(;f)}\n", false), SYNTAX); } void garbageCode164() { //7234 - ASSERT_THROW_INTERNAL(checkCode("class d{k p;}(){d::d():B<()}"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("class d{k p;}(){d::d():B<()}\n"), SYNTAX); } void garbageCode165() { //7235 - ASSERT_THROW_INTERNAL(checkCode("for(;..)", false),SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("for(;..)\n", false),SYNTAX); } void garbageCode167() { //7237 - ASSERT_THROW_INTERNAL(checkCode("class D00i000{:D00i000::}i"),SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("class D00i000{:D00i000::}i\n"),SYNTAX); } void garbageCode168() { // #7246 (segmentation fault) - (void)checkCode("long foo(void) { return *bar; }", false); + (void)checkCode("long foo(void) { return *bar; }\n", false); ignore_errout(); // we do not care about the output } void garbageCode169() { // 6713 ASSERT_THROW_INTERNAL(checkCode("( ) { ( ) ; { return } switch ( ) i\n" - "set case break ; default: ( ) }", false), SYNTAX); + "set case break ; default: ( ) }\n", false), SYNTAX); } void garbageCode170() { // 7255 - ASSERT_THROW_INTERNAL(checkCode("d i(){{f*s=typeid(()0,)}}", false), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("d i(){{f*s=typeid(()0,)}}\n", false), SYNTAX); } void garbageCode171() { // 7270 - ASSERT_THROW_INTERNAL(checkCode("(){case()?():}:", false), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("(){case()?():}:\n", false), SYNTAX); } void garbageCode172() { // #7357 - ASSERT_THROW_INTERNAL(checkCode("p>,"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("p>,\n"), SYNTAX); } void garbageCode173() { // #6781 heap corruption ; TemplateSimplifier::simplifyTemplateInstantiations - ASSERT_THROW_INTERNAL(checkCode(" template < Types > struct S : >( S < ...Types... > S <) > { ( ) { } } ( ) { return S < void > ( ) }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode(" template < Types > struct S : >( S < ...Types... > S <) > { ( ) { } } ( ) { return S < void > ( ) }\n"), SYNTAX); } void garbageCode174() { // #7356 - ASSERT_THROW_INTERNAL(checkCode("{r e() { w*constD = (())D = cast< }}"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("{r e() { w*constD = (())D = cast< }}\n"), SYNTAX); } void garbageCode175() { // #7027 @@ -1492,32 +1494,32 @@ class TestGarbage : public TestFixture { " for ( i = t3 , i < t1 ; i++ )\n" " for ( j = 0 ; j < = j++ )\n" " return t1 ,\n" - "}"), SYNTAX); + "}\n"), SYNTAX); } void garbageCode176() { // #7257 (segmentation fault) - (void)checkCode("class t { { struct } enum class f : unsigned { q } b ; operator= ( T ) { switch ( b ) { case f::q: } } { assert ( b ) ; } } { ; & ( t ) ( f::t ) ; } ;"); + (void)checkCode("class t { { struct } enum class f : unsigned { q } b ; operator= ( T ) { switch ( b ) { case f::q: } } { assert ( b ) ; } } { ; & ( t ) ( f::t ) ; } ;\n"); ignore_errout(); // we are not interested in the output } void garbageCode181() { - ASSERT_THROW_INTERNAL(checkCode("int test() { int +; }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("int test() { int +; }\n"), SYNTAX); } // #4195 - segfault for "enum { int f ( ) { return = } r = f ( ) ; }" void garbageCode182() { - ASSERT_THROW_INTERNAL(checkCode("enum { int f ( ) { return = } r = f ( ) ; }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("enum { int f ( ) { return = } r = f ( ) ; }\n"), SYNTAX); } // #7505 - segfault void garbageCode183() { - ASSERT_THROW_INTERNAL(checkCode("= { int } enum return { r = f() f(); }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("= { int } enum return { r = f() f(); }\n"), SYNTAX); } void garbageCode184() { // #7699 ASSERT_THROW_INTERNAL(checkCode("unsigned int AquaSalSystem::GetDisplayScreenCount() {\n" " NSArray* pScreens = [NSScreen screens];\n" " return pScreens ? [pScreens count] : 1;\n" - "}"), SYNTAX); + "}\n"), SYNTAX); } void garbageCode185() { // #6011 crash in libreoffice failure to create proper AST @@ -1529,29 +1531,29 @@ class TestGarbage : public TestFixture { " pOut->CreateObject( nIndex, GDI_BRUSH, new WinMtfFillStyle( ReadColor(), ( nStyle == BS_HOLLOW ) ? TRUE : FALSE ) );\n" " return bStatus;\n" " };\n" - "}"); + "}\n"); ignore_errout(); // we are not interested in the output } // #8151 - segfault due to incorrect template syntax void garbageCode186() { - ASSERT_THROW_INTERNAL(checkCode("A<>C"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("A<>C\n"), SYNTAX); } void garbageCode187() { // # 8152 - segfault in handling const char inp[] = "0|\0|0>;\n"; ASSERT_THROW_INTERNAL(checkCode(inp), SYNTAX); - (void)checkCode("template struct S : A< B || C > {};"); // No syntax error: #8390 - (void)checkCode("static_assert(A || B, ab);"); + (void)checkCode("template struct S : A< B || C > {};\n"); // No syntax error: #8390 + (void)checkCode("static_assert(A || B, ab);\n"); } void garbageCode188() { // #8255 - ASSERT_THROW_INTERNAL(checkCode("{z r(){(){for(;<(x);){if(0==0)}}}}"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("{z r(){(){for(;<(x);){if(0==0)}}}}\n"), SYNTAX); } void garbageCode189() { // #8317 (segmentation fault) - (void)checkCode("t&n(){()()[](){()}}$"); + (void)checkCode("t&n(){()()[](){()}}$\n"); } void garbageCode190() { // #8307 @@ -1559,31 +1561,31 @@ class TestGarbage : public TestFixture { " int i;\n" " i *= 0;\n" " !i <;\n" - "}"), + "}\n"), AST); } void garbageCode191() { // #8333 - ASSERT_THROW_INTERNAL(checkCode("struct A { int f(const); };"), SYNTAX); - ASSERT_THROW_INTERNAL(checkCode("struct A { int f(int, const, char); };"), SYNTAX); - ASSERT_THROW_INTERNAL(checkCode("struct A { int f(struct); };"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("struct A { int f(const); };\n"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("struct A { int f(int, const, char); };\n"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("struct A { int f(struct); };\n"), SYNTAX); // The following code is valid and should not trigger any error - ASSERT_NO_THROW(checkCode("struct A { int f ( char ) ; } ;")); + ASSERT_NO_THROW(checkCode("struct A { int f ( char ) ; } ;\n")); } void garbageCode192() { // #8386 (segmentation fault) - ASSERT_THROW_INTERNAL(checkCode("{(()[((0||0xf||))]0[])}"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("{(()[((0||0xf||))]0[])}\n"), SYNTAX); } // #8740 void garbageCode193() { - ASSERT_THROW_INTERNAL(checkCode("d f(){!=[]&&0()!=0}"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("d f(){!=[]&&0()!=0}\n"), SYNTAX); } // #8384 void garbageCode194() { - ASSERT_THROW_INTERNAL(checkCode("{((()))(return 1||);}"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("{((()))(return 1||);}\n"), SYNTAX); } // #8709 - no garbage but to avoid stability regression (segmentation fault) @@ -1593,19 +1595,19 @@ class TestGarbage : public TestFixture { " switch (d) { case b:; }\n" " double e(b);\n" " if(e <= 0) {}\n" - "}"); + "}\n"); ignore_errout(); // we do not care about the output } // #8265 void garbageCode196() { - ASSERT_THROW_INTERNAL(checkCode("0|,0<= void { ( ()) } 1 name3 return >= >= ( ) >= name5 (\n" " name5 name6 :nam00 [ ()])}))})})})};\n" - "}"), SYNTAX); + "}\n"), SYNTAX); } // #8752 (segmentation fault) void garbageCode199() { - ASSERT_THROW_INTERNAL(checkCode("d f(){e n00e0[]n00e0&" "0+f=0}"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("d f(){e n00e0[]n00e0&" "0+f=0}\n"), SYNTAX); } // #8757 void garbageCode200() { - ASSERT_THROW_INTERNAL(checkCode("(){e break,{(case)!{e:[]}}}"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("(){e break,{(case)!{e:[]}}}\n"), SYNTAX); } // #8873 void garbageCode201() { - ASSERT_THROW_INTERNAL(checkCode("void f() { std::string s=\"abc\"; return s + }"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("void f() { std::string s=\"abc\"; return s + }\n"), SYNTAX); } // #8907 void garbageCode202() { - ASSERT_THROW_INTERNAL(checkCode("void f() { UNKNOWN_MACRO(return); }"), UNKNOWN_MACRO); - ASSERT_THROW_INTERNAL(checkCode("void f() { UNKNOWN_MACRO(throw); }"), UNKNOWN_MACRO); + ASSERT_THROW_INTERNAL(checkCode("void f() { UNKNOWN_MACRO(return); }\n"), UNKNOWN_MACRO); + ASSERT_THROW_INTERNAL(checkCode("void f() { UNKNOWN_MACRO(throw); }\n"), UNKNOWN_MACRO); ignore_errout(); } void garbageCode203() { // #8972 (segmentation fault) - ASSERT_THROW_INTERNAL(checkCode("{ > () {} }"), SYNTAX); - (void)checkCode("template <> a > ::b();"); + ASSERT_THROW_INTERNAL(checkCode("{ > () {} }\n"), SYNTAX); + (void)checkCode("template <> a > ::b();\n"); } void garbageCode204() { - ASSERT_THROW_INTERNAL(checkCode("template ()> c; template a as() {} as>();"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("template ()> c; template a as() {} as>();\n"), SYNTAX); } void garbageCode205() { @@ -1665,55 +1667,55 @@ class TestGarbage : public TestFixture { "CodeSnippetsEvent :: CodeSnippetsEvent ( const CodeSnippetsEvent & Event )\n" ": wxCommandEvent ( Event )\n" ", m_SnippetID ( 0 ) {\n" - "}"), + "}\n"), INTERNAL); } void garbageCode206() { - ASSERT_EQUALS("[test.cpp:1] syntax error: operator", getSyntaxError("void foo() { for (auto operator new : int); }")); - ASSERT_EQUALS("[test.cpp:1] syntax error", getSyntaxError("void foo() { for (a operator== :) }")); + ASSERT_EQUALS("[test.cpp:1] syntax error: operator", getSyntaxError("void foo() { for (auto operator new : int); }\n")); + ASSERT_EQUALS("[test.cpp:1] syntax error", getSyntaxError("void foo() { for (a operator== :) }\n")); } void garbageCode207() { // #8750 - ASSERT_THROW_INTERNAL(checkCode("d f(){(.n00e0(return%n00e0''('')));}"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("d f(){(.n00e0(return%n00e0''('')));}\n"), SYNTAX); } void garbageCode208() { // #8753 - ASSERT_THROW_INTERNAL(checkCode("d f(){(for(((((0{t b;((((((((()))))))))}))))))}"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("d f(){(for(((((0{t b;((((((((()))))))))}))))))}\n"), SYNTAX); } void garbageCode209() { // #8756 - ASSERT_THROW_INTERNAL(checkCode("{(- -##0xf/-1 0)[]}"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("{(- -##0xf/-1 0)[]}\n"), SYNTAX); } void garbageCode210() { // #8762 - ASSERT_THROW_INTERNAL(checkCode("{typedef typedef c n00e0[]c000(;n00e0&c000)}"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("{typedef typedef c n00e0[]c000(;n00e0&c000)}\n"), SYNTAX); } void garbageCode211() { // #8764 - ASSERT_THROW_INTERNAL(checkCode("{typedef f typedef[]({typedef e e,>;typedef(((typedef;typedef(((typedef struct A {};\n" "template struct A {}; \n" - "A a;"); + "A a;\n"); } void garbageCode217() { // #10011 @@ -1721,11 +1723,11 @@ class TestGarbage : public TestFixture { " auto p;\n" " if (g(p)) {}\n" " assert();\n" - "}"), AST); + "}\n"), AST); } void garbageCode218() { // #8763 - ASSERT_THROW_INTERNAL(checkCode("d f(){t n0000 const[]n0000+0!=n0000,(0)}"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("d f(){t n0000 const[]n0000+0!=n0000,(0)}\n"), SYNTAX); } void garbageCode219() { // #10101 (void)checkCode("typedef void (*func) (addr) ;\n" @@ -1745,111 +1747,111 @@ class TestGarbage : public TestFixture { ASSERT_THROW_INTERNAL(checkCode("template\n"), SYNTAX); // don't crash } void garbageCode223() { // #11639 - ASSERT_THROW_INTERNAL(checkCode("struct{}*"), SYNTAX); // don't crash + ASSERT_THROW_INTERNAL(checkCode("struct{}*\n"), SYNTAX); // don't crash } void garbageCode224() { - ASSERT_THROW_INTERNAL(checkCode("void f(){ auto* b = dynamic_cast int { return 0; }() };")); + ASSERT_NO_THROW(checkCode("void f() { enum { A = [=]() mutable { return 0; }() }; }\n")); + ASSERT_NO_THROW(checkCode("enum { A = [=](void) mutable -> int { return 0; }() };\n")); } void garbageCode229() { // #14126 - ASSERT_THROW_INTERNAL(checkCode("void f() {} [[maybe_unused]]"), SYNTAX); - ASSERT_THROW_INTERNAL(checkCode("void f() {} [[unused]]"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("void f() {} [[maybe_unused]]\n"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("void f() {} [[unused]]\n"), SYNTAX); } void garbageCode230() { // #14432 - ASSERT_THROW_INTERNAL(checkCode("e U U,i"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("e U U,i\n"), SYNTAX); } void garbageCode231() { - ASSERT_THROW_INTERNAL(checkCode("char char* [] = {\"a\" \"b\"}"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("char char* [] = {\"a\" \"b\"}\n"), SYNTAX); } void syntaxErrorFirstToken() { - ASSERT_THROW_INTERNAL(checkCode("&operator(){[]};"), SYNTAX); // #7818 - ASSERT_THROW_INTERNAL(checkCode("*(*const<> (size_t); foo) { } *(*const (size_t)() ; foo) { }"), SYNTAX); // #6858 - ASSERT_THROW_INTERNAL(checkCode(">{ x while (y) z int = }"), SYNTAX); // #4175 - ASSERT_THROW_INTERNAL(checkCode("&p(!{}e x){({(0?:?){({})}()})}"), SYNTAX); // #7118 - ASSERT_THROW_INTERNAL(checkCode(" { struct { typename D4:typename Base }; };"), SYNTAX); // #3533 + ASSERT_THROW_INTERNAL(checkCode("&operator(){[]};\n"), SYNTAX); // #7818 + ASSERT_THROW_INTERNAL(checkCode("*(*const<> (size_t); foo) { } *(*const (size_t)() ; foo) { }\n"), SYNTAX); // #6858 + ASSERT_THROW_INTERNAL(checkCode(">{ x while (y) z int = }\n"), SYNTAX); // #4175 + ASSERT_THROW_INTERNAL(checkCode("&p(!{}e x){({(0?:?){({})}()})}\n"), SYNTAX); // #7118 + ASSERT_THROW_INTERNAL(checkCode(" { struct { typename D4:typename Base }; };\n"), SYNTAX); // #3533 ASSERT_THROW_INTERNAL(checkCode(" > template < . > struct Y < T > { = } ;\n"), SYNTAX); // #6108 } void syntaxErrorLastToken() { - ASSERT_THROW_INTERNAL(checkCode("int *"), SYNTAX); // #7821 - ASSERT_THROW_INTERNAL(checkCode("x[y]"), SYNTAX); // #2986 - ASSERT_THROW_INTERNAL(checkCode("( ) &"), SYNTAX); - ASSERT_THROW_INTERNAL(checkCode("|| #if #define <="), SYNTAX); // #2601 - ASSERT_THROW_INTERNAL(checkCode("f::y:y : \n"), SYNTAX); - ASSERT_THROW_INTERNAL(checkCode("++4++ + + E++++++++++ + ch " "tp.oed5[.]"), SYNTAX); // #7074 - ASSERT_THROW_INTERNAL(checkCode("d a(){f s=0()8[]s?():0}*()?:0", false), SYNTAX); // #7236 - ASSERT_THROW_INTERNAL(checkCode("!2 : #h2 ?:", false), SYNTAX); // #7769 - ASSERT_THROW_INTERNAL(checkCode("--"), SYNTAX); - ASSERT_THROW_INTERNAL(checkCode("volatile true , test < test < #ifdef __ppc__ true ,"), SYNTAX); // #4169 + ASSERT_THROW_INTERNAL(checkCode("++4++ + + E++++++++++ + ch " "tp.oed5[.]\n"), SYNTAX); // #7074 + ASSERT_THROW_INTERNAL(checkCode("d a(){f s=0()8[]s?():0}*()?:0\n", false), SYNTAX); // #7236 + ASSERT_THROW_INTERNAL(checkCode("!2 : #h2 ?:\n", false), SYNTAX); // #7769 + ASSERT_THROW_INTERNAL(checkCode("--\n"), SYNTAX); + ASSERT_THROW_INTERNAL(checkCode("volatile true , test < test < #ifdef __ppc__ true ,\n"), SYNTAX); // #4169 ASSERT_THROW_INTERNAL(checkCode("a,b--\n"), SYNTAX); // #2847 - ASSERT_THROW_INTERNAL(checkCode("x a[0] ="), SYNTAX); // #2682 + ASSERT_THROW_INTERNAL(checkCode("x a[0] =\n"), SYNTAX); // #2682 ASSERT_THROW_INTERNAL(checkCode("auto_ptr\n"), SYNTAX); // #2967 ASSERT_THROW_INTERNAL(checkCode("char a[1]\n"), SYNTAX); // #2865 - ASSERT_THROW_INTERNAL(checkCode("<><<"), SYNTAX); // #2612 - ASSERT_THROW_INTERNAL(checkCode("z"), SYNTAX); // #2831 - ASSERT_THROW_INTERNAL(checkCode("><,f<<\n"), SYNTAX); // #2612 + ASSERT_THROW_INTERNAL(checkCode("z\n"), SYNTAX); // #2831 + ASSERT_THROW_INTERNAL(checkCode("><,f typedef name5 | ( , ;){ } (); }"), SYNTAX); // #9067 - ASSERT_THROW_INTERNAL(checkCode("void f() { x= {}( ) ( 'x')[ ] (); }"), SYNTAX); // #9068 - ASSERT_THROW_INTERNAL(checkCode("void f() { x= y{ } name5 y[ ] + y ^ name5 ^ name5 for ( ( y y y && y y y && name5 ++ int )); }"), SYNTAX); // #9069 + ASSERT_THROW_INTERNAL(checkCode("void f() { x= 'x' > typedef name5 | ( , ;){ } (); }\n"), SYNTAX); // #9067 + ASSERT_THROW_INTERNAL(checkCode("void f() { x= {}( ) ( 'x')[ ] (); }\n"), SYNTAX); // #9068 + ASSERT_THROW_INTERNAL(checkCode("void f() { x= y{ } name5 y[ ] + y ^ name5 ^ name5 for ( ( y y y && y y y && name5 ++ int )); }\n"), SYNTAX); // #9069 } void cliCode() { @@ -1860,12 +1862,12 @@ class TestGarbage : public TestFixture { "bool GetDeviceInformation(String ^ port, LibCECConfiguration ^configuration, uint32_t timeoutMs) {\n" "bool bReturn(false);\n" "}\n" - "};")); + "};\n")); ignore_errout(); // we are not interested in the output } void enumTrailingComma() { - ASSERT_THROW_INTERNAL(checkCode("enum ssl_shutdown_t {ssl_shutdown_none = 0,ssl_shutdown_close_notify = , } ;"), SYNTAX); // #8079 + ASSERT_THROW_INTERNAL(checkCode("enum ssl_shutdown_t {ssl_shutdown_none = 0,ssl_shutdown_close_notify = , } ;\n"), SYNTAX); // #8079 } void nonGarbageCode1() { @@ -1878,25 +1880,31 @@ class TestGarbage : public TestFixture { "template< class T >\n" "template< class Predicate > int\n" "List::DeleteIf( const Predicate &pred )\n" - "{}")); + "{}\n")); ignore_errout(); // we are not interested in the output // #8749 ASSERT_NO_THROW(checkCode( "struct A {\n" " void operator+=(A&) && = delete;\n" - "};")); + "};\n")); // #8788 ASSERT_NO_THROW(checkCode( "struct foo;\n" "void f() {\n" " auto fn = []() -> foo* { return new foo(); };\n" - "}")); + "}\n")); ignore_errout(); // we do not care about the output // #13892 - ASSERT_NO_THROW(checkCode("void foovm(int x[const *]);")); + ASSERT_NO_THROW(checkCode("void foovm(int x[const *]);\n")); + + // #14676 + ASSERT_NO_THROW(checkCode("int main() {\n" + " auto value = m[1 + qRow<>];\n" + "}\n")); + ignore_errout(); } }; diff --git a/test/testimportproject.cpp b/test/testimportproject.cpp index f7c53f3d3a6..47d756db563 100644 --- a/test/testimportproject.cpp +++ b/test/testimportproject.cpp @@ -18,15 +18,16 @@ #include "filesettings.h" #include "fixture.h" +#include "helpers.h" #include "importproject.h" #include "redirect.h" #include "settings.h" #include "standards.h" #include "suppressions.h" -#include "xml.h" +#include +#include #include -#include #include #include #include @@ -35,13 +36,13 @@ class TestImporter final : public ImportProject { public: - using ImportProject::importCompileCommands; + using ImportProject::processCompileCommands; using ImportProject::importCppcheckGuiProject; - using ImportProject::importVcxproj; - using ImportProject::SharedItemsProject; using ImportProject::collectArgs; using ImportProject::fsSetDefines; using ImportProject::fsSetIncludePaths; + using ImportProject::setRelativePaths; + using ImportProject::setProjectPath; }; @@ -56,29 +57,35 @@ class TestImportProject : public TestFixture { TEST_CASE(setIncludePaths1); TEST_CASE(setIncludePaths2); TEST_CASE(setIncludePaths3); // macro names are case insensitive - TEST_CASE(importCompileCommands1); - TEST_CASE(importCompileCommands2); // #8563, #9567 - TEST_CASE(importCompileCommands3); // check with existing trailing / in directory - TEST_CASE(importCompileCommands4); // only accept certain file types - TEST_CASE(importCompileCommands5); // Windows/CMake/Ninja generated compile_commands.json - TEST_CASE(importCompileCommands6); // Windows/CMake/Ninja generated compile_commands.json with spaces - TEST_CASE(importCompileCommands7); // linux: "/home/danielm/cppcheck 2" - TEST_CASE(importCompileCommands8); // Windows: "C:\Users\danielm\cppcheck" - TEST_CASE(importCompileCommands9); - TEST_CASE(importCompileCommands10); // #10887: include path with space - TEST_CASE(importCompileCommands11); // include path order - TEST_CASE(importCompileCommands12); // #13040: "directory" is parent directory, relative include paths - TEST_CASE(importCompileCommands13); // #13333: duplicate file entries - TEST_CASE(importCompileCommands14); // #14156 - TEST_CASE(importCompileCommands15); // #14306 - TEST_CASE(importCompileCommandsArgumentsSection); // Handle arguments section - TEST_CASE(importCompileCommandsNoCommandSection); // gracefully handles malformed json - TEST_CASE(importCompileCommandsDirectoryMissing); // 'directory' field missing - TEST_CASE(importCompileCommandsDirectoryInvalid); // 'directory' field not a string + TEST_CASE(setIncludePathsUnknownMacro); // unknown macros expand to empty string + TEST_CASE(setIncludePathsRootRelative); // \foo takes the project directory's drive + TEST_CASE(setRelativePathsInclude); // #14746 + TEST_CASE(processCompileCommands1); + TEST_CASE(processCompileCommands2); // #8563, #9567 + TEST_CASE(processCompileCommands3); // check with existing trailing / in directory + TEST_CASE(processCompileCommands4); // only accept certain file types + TEST_CASE(processCompileCommands5); // Windows/CMake/Ninja generated compile_commands.json + TEST_CASE(processCompileCommands6); // Windows/CMake/Ninja generated compile_commands.json with spaces + TEST_CASE(processCompileCommands7); // linux: "/home/danielm/cppcheck 2" + TEST_CASE(processCompileCommands8); // Windows: "C:\Users\danielm\cppcheck" + TEST_CASE(processCompileCommands9); + TEST_CASE(processCompileCommands10); // #10887: include path with space + TEST_CASE(processCompileCommands11); // include path order + TEST_CASE(processCompileCommands12); // #13040: "directory" is parent directory, relative include paths + TEST_CASE(processCompileCommands13); // #13333: duplicate file entries + TEST_CASE(processCompileCommands14); // #14156 + TEST_CASE(processCompileCommands15); // #14306 + TEST_CASE(processCompileCommandsForcedInclude); // -include / /FI force-include + TEST_CASE(processCompileCommandsArgumentsSection); // Handle arguments section + TEST_CASE(processCompileCommandsNoCommandSection); // gracefully handles malformed json + TEST_CASE(processCompileCommandsDirectoryMissing); // 'directory' field missing + TEST_CASE(processCompileCommandsDirectoryInvalid); // 'directory' field not a string TEST_CASE(importCppcheckGuiProject); + TEST_CASE(importCppcheckGuiProjectDuplicateSuppressions); TEST_CASE(importCppcheckGuiProjectPremiumMisra); + TEST_CASE(importCppcheckGuiProjectAbsPath); // absolute Unix path must not be prepended with mPath + TEST_CASE(importCppcheckGuiProjectRelPathWithBase); // relative path must be prepended with mPath TEST_CASE(ignorePaths); - TEST_CASE(testVcxprojUnicode); TEST_CASE(testCollectArgs1); TEST_CASE(testCollectArgs2); TEST_CASE(testCollectArgs3); @@ -87,6 +94,13 @@ class TestImportProject : public TestFixture { TEST_CASE(testCollectArgs6); TEST_CASE(testCollectArgs7); TEST_CASE(testVcxprojConditions); + TEST_CASE(testVcxprojConditionEqualityIsNotVersionComparison); + TEST_CASE(testPropertyNameAllowsHyphen); + TEST_CASE(testMSBuildStaticFunctions); + TEST_CASE(testVcxitemsPathResolution); + TEST_CASE(testMissingChildImportNonFatal); // missing child imports must not abort the project + TEST_CASE(testCurrentToolsVersionFromProps); // "Current" keyword uses VisualStudioVersion property + TEST_CASE(testMetadataSelfReferenceCaseInsensitive); } void setDefines() const { @@ -103,13 +117,28 @@ class TestImportProject : public TestFixture { TestImporter::fsSetDefines(fs, "A;;B"); ASSERT_EQUALS("A=1;B=1", fs.defines); + + // M-2: %(metadata) tokens at the start of the string must be stripped + TestImporter::fsSetDefines(fs, "%(PreprocessorDefinitions)"); + ASSERT_EQUALS("", fs.defines); + + TestImporter::fsSetDefines(fs, "%(PreprocessorDefinitions);A"); + ASSERT_EQUALS("A=1", fs.defines); + + TestImporter::fsSetDefines(fs, "%(PreprocessorDefinitions);A;B"); + ASSERT_EQUALS("A=1;B=1", fs.defines); + + // Leading %(…) followed by a mid-string %(…) — both must be stripped + TestImporter::fsSetDefines(fs, "%(PreprocessorDefinitions);A;%(OtherMeta);B"); + ASSERT_EQUALS("A=1;B=1", fs.defines); } void setIncludePaths1() const { FileSettings fs{"test.cpp", Standards::Language::CPP, 0}; std::list in(1, "../include"); - std::map variables; - TestImporter::fsSetIncludePaths(fs, "abc/def/", in, variables); + PropertiesMap properties; + TestImporter importer; + importer.fsSetIncludePaths(fs, "abc/def/", in, properties); ASSERT_EQUALS(1U, fs.includePaths.size()); ASSERT_EQUALS("abc/include/", fs.includePaths.front()); } @@ -117,9 +146,10 @@ class TestImportProject : public TestFixture { void setIncludePaths2() const { FileSettings fs{"test.cpp", Standards::Language::CPP, 0}; std::list in(1, "$(SolutionDir)other"); - std::map variables; - variables["SolutionDir"] = "c:/abc/"; - TestImporter::fsSetIncludePaths(fs, "/home/fred", in, variables); + PropertiesMap properties; + properties["SolutionDir"] = "c:/abc/"; + TestImporter importer; + importer.fsSetIncludePaths(fs, "/home/fred", in, properties); ASSERT_EQUALS(1U, fs.includePaths.size()); ASSERT_EQUALS("c:/abc/other/", fs.includePaths.front()); } @@ -127,14 +157,69 @@ class TestImportProject : public TestFixture { void setIncludePaths3() const { // macro names are case insensitive FileSettings fs{"test.cpp", Standards::Language::CPP, 0}; std::list in(1, "$(SOLUTIONDIR)other"); - std::map variables; - variables["SolutionDir"] = "c:/abc/"; - TestImporter::fsSetIncludePaths(fs, "/home/fred", in, variables); + PropertiesMap properties; + properties["SolutionDir"] = "c:/abc/"; + TestImporter importer; + importer.fsSetIncludePaths(fs, "/home/fred", in, properties); ASSERT_EQUALS(1U, fs.includePaths.size()); ASSERT_EQUALS("c:/abc/other/", fs.includePaths.front()); } - void importCompileCommands1() const { + void setIncludePathsUnknownMacro() const { + // MSBuild/Visual Studio expand an undefined property to the empty string. + FileSettings fs{"test.cpp", Standards::Language::CPP, 0}; + std::list in; + in.emplace_back("$(UnknownMacro)include"); // -> relative to project dir + in.emplace_back("$(SolutionDir)$(UnknownMacro)other"); // -> known part kept + in.emplace_back("$(UnknownMacro)"); // -> dropped + in.emplace_back("$(UnknownMacro)\\abs\\dir"); // -> root-relative + in.emplace_back("$(Unknown1)$(Unknown2)include"); // duplicate after expansion + PropertiesMap properties; + properties["SolutionDir"] = "c:/abc/"; + TestImporter importer; + importer.fsSetIncludePaths(fs, "/home/fred/", in, properties); + ASSERT_EQUALS(3U, fs.includePaths.size()); + auto it = fs.includePaths.cbegin(); + ASSERT_EQUALS("/home/fred/include/", *it++); + ASSERT_EQUALS("c:/abc/other/", *it++); + ASSERT_EQUALS("/abs/dir/", *it); + for (const std::string &p : fs.includePaths) + ASSERT(p.find("$(") == std::string::npos); + } + + void setIncludePathsRootRelative() const { + FileSettings fs{"test.cpp", Standards::Language::CPP, 0}; + std::list in; + in.emplace_back("\\inc"); // root-relative -> project drive + in.emplace_back("$(UnknownMacro)\\abs\\dir"); // root-relative after expansion + in.emplace_back("D:\\other\\..\\lib"); // drive-absolute, simplified + in.emplace_back("\\\\server\\share\\inc"); // UNC kept + in.emplace_back("sub\\"); // relative -> project dir + PropertiesMap properties; + TestImporter importer; + importer.fsSetIncludePaths(fs, "C:/proj/", in, properties); + ASSERT_EQUALS(5U, fs.includePaths.size()); + auto it = fs.includePaths.cbegin(); + ASSERT_EQUALS("C:/inc/", *it++); + ASSERT_EQUALS("C:/abs/dir/", *it++); + ASSERT_EQUALS("D:/lib/", *it++); + ASSERT_EQUALS("//server/share/inc/", *it++); + ASSERT_EQUALS("C:/proj/sub/", *it); + } + + void setRelativePathsInclude() const { + const std::string cwd = Path::fromNativeSeparators(Path::getCurrentPath()); + TestImporter importer; + FileSettings fs{cwd + "/sub/a.c", Standards::Language::C, 0}; + fs.includePaths.push_back(cwd + "/"); + importer.fileSettings.push_back(fs); + importer.setRelativePaths("compile_commands.json"); + fs = importer.fileSettings.front(); + ASSERT_EQUALS(".", fs.includePaths.front()); + ASSERT_EQUALS("sub/a.c", fs.filename()); + } + + void processCompileCommands1() const { REDIRECT; constexpr char json[] = R"([{ "directory": "/tmp", @@ -143,12 +228,12 @@ class TestImportProject : public TestFixture { }])"; std::istringstream istr(json); TestImporter importer; - ASSERT_EQUALS(true, importer.importCompileCommands(istr)); + ASSERT_EQUALS(true, importer.processCompileCommands(istr)); ASSERT_EQUALS(1, importer.fileSettings.size()); ASSERT_EQUALS("TEST1=1;TEST2=2", importer.fileSettings.cbegin()->defines); } - void importCompileCommands2() const { + void processCompileCommands2() const { REDIRECT; // Absolute file path #ifdef _WIN32 @@ -159,7 +244,7 @@ class TestImportProject : public TestFixture { }])"; std::istringstream istr(json); TestImporter importer; - ASSERT_EQUALS(true, importer.importCompileCommands(istr)); + ASSERT_EQUALS(true, importer.processCompileCommands(istr)); ASSERT_EQUALS(1, importer.fileSettings.size()); ASSERT_EQUALS("C:/bar.c", importer.fileSettings.cbegin()->filename()); #else @@ -170,13 +255,13 @@ class TestImportProject : public TestFixture { }])"; std::istringstream istr(json); TestImporter importer; - ASSERT_EQUALS(true, importer.importCompileCommands(istr)); + ASSERT_EQUALS(true, importer.processCompileCommands(istr)); ASSERT_EQUALS(1, importer.fileSettings.size()); ASSERT_EQUALS("/bar.c", importer.fileSettings.cbegin()->filename()); #endif } - void importCompileCommands3() const { + void processCompileCommands3() const { REDIRECT; const char json[] = R"([{ "directory": "/tmp/", @@ -185,12 +270,12 @@ class TestImportProject : public TestFixture { }])"; std::istringstream istr(json); TestImporter importer; - ASSERT_EQUALS(true, importer.importCompileCommands(istr)); + ASSERT_EQUALS(true, importer.processCompileCommands(istr)); ASSERT_EQUALS(1, importer.fileSettings.size()); ASSERT_EQUALS("/tmp/src.c", importer.fileSettings.cbegin()->filename()); } - void importCompileCommands4() const { + void processCompileCommands4() const { REDIRECT; constexpr char json[] = R"([{ "directory": "/tmp/", @@ -199,11 +284,11 @@ class TestImportProject : public TestFixture { }])"; std::istringstream istr(json); TestImporter importer; - ASSERT_EQUALS(true, importer.importCompileCommands(istr)); + ASSERT_EQUALS(true, importer.processCompileCommands(istr)); ASSERT_EQUALS(0, importer.fileSettings.size()); } - void importCompileCommands5() const { + void processCompileCommands5() const { REDIRECT; constexpr char json[] = R"([{ @@ -218,12 +303,12 @@ class TestImportProject : public TestFixture { }])"; std::istringstream istr(json); TestImporter importer; - ASSERT_EQUALS(true, importer.importCompileCommands(istr)); + ASSERT_EQUALS(true, importer.processCompileCommands(istr)); ASSERT_EQUALS(2, importer.fileSettings.size()); ASSERT_EQUALS("C:/Users/dan/git/test-cppcheck/mylib/src/", importer.fileSettings.cbegin()->includePaths.front()); } - void importCompileCommands6() const { + void processCompileCommands6() const { REDIRECT; constexpr char json[] = R"([{ @@ -238,14 +323,14 @@ class TestImportProject : public TestFixture { }])"; std::istringstream istr(json); TestImporter importer; - ASSERT_EQUALS(true, importer.importCompileCommands(istr)); + ASSERT_EQUALS(true, importer.processCompileCommands(istr)); ASSERT_EQUALS(2, importer.fileSettings.size()); ASSERT_EQUALS("C:/Users/dan/git/test-cppcheck/mylib/src/", importer.fileSettings.cbegin()->includePaths.front()); ASSERT_EQUALS("C:/Users/dan/git/test-cppcheck/mylib/second src/", importer.fileSettings.cbegin()->includePaths.back()); } - void importCompileCommands7() const { + void processCompileCommands7() const { REDIRECT; // cmake -DFILESDIR="/some/path" .. constexpr char json[] = @@ -256,7 +341,7 @@ class TestImportProject : public TestFixture { }])"; std::istringstream istr(json); TestImporter importer; - ASSERT_EQUALS(true, importer.importCompileCommands(istr)); + ASSERT_EQUALS(true, importer.processCompileCommands(istr)); ASSERT_EQUALS(1, importer.fileSettings.size()); ASSERT_EQUALS("FILESDIR=\"/some/path\"", importer.fileSettings.cbegin()->defines); ASSERT_EQUALS(1, importer.fileSettings.cbegin()->includePaths.size()); @@ -266,7 +351,7 @@ class TestImportProject : public TestFixture { importer.fileSettings.cbegin()->includePaths.back()); } - void importCompileCommands8() const { + void processCompileCommands8() const { REDIRECT; // cmake -DFILESDIR="C:\Program Files\Cppcheck" -G"NMake Makefiles" .. constexpr char json[] = @@ -277,10 +362,10 @@ class TestImportProject : public TestFixture { }])"; std::istringstream istr(json); TestImporter importer; - ASSERT_EQUALS(true, importer.importCompileCommands(istr)); // Do not crash + ASSERT_EQUALS(true, importer.processCompileCommands(istr)); // Do not crash } - void importCompileCommands9() const { + void processCompileCommands9() const { REDIRECT; // IAR output (https://sourceforge.net/p/cppcheck/discussion/general/thread/608af51e0a/) constexpr char json[] = @@ -294,10 +379,10 @@ class TestImportProject : public TestFixture { }])"; std::istringstream istr(json); TestImporter importer; - ASSERT_EQUALS(true, importer.importCompileCommands(istr)); + ASSERT_EQUALS(true, importer.processCompileCommands(istr)); } - void importCompileCommands10() const { // #10887 + void processCompileCommands10() const { // #10887 REDIRECT; constexpr char json[] = R"([{ @@ -311,13 +396,13 @@ class TestImportProject : public TestFixture { }])"; std::istringstream istr(json); TestImporter importer; - ASSERT_EQUALS(true, importer.importCompileCommands(istr)); + ASSERT_EQUALS(true, importer.processCompileCommands(istr)); ASSERT_EQUALS(1, importer.fileSettings.size()); const FileSettings &fs = importer.fileSettings.front(); ASSERT_EQUALS("/home/danielm/cppcheck/test folder/", fs.includePaths.front()); } - void importCompileCommands11() const { // include path order + void processCompileCommands11() const { // include path order REDIRECT; constexpr char json[] = R"([{ @@ -333,14 +418,14 @@ class TestImportProject : public TestFixture { }])"; std::istringstream istr(json); TestImporter importer; - ASSERT_EQUALS(true, importer.importCompileCommands(istr)); + ASSERT_EQUALS(true, importer.processCompileCommands(istr)); ASSERT_EQUALS(1, importer.fileSettings.size()); const FileSettings &fs = importer.fileSettings.front(); ASSERT_EQUALS("/x/def/", fs.includePaths.front()); ASSERT_EQUALS("/x/abc/", fs.includePaths.back()); } - void importCompileCommands12() const { // #13040 + void processCompileCommands12() const { // #13040 REDIRECT; constexpr char json[] = R"([{ @@ -350,14 +435,14 @@ class TestImportProject : public TestFixture { }])"; std::istringstream istr(json); TestImporter importer; - ASSERT_EQUALS(true, importer.importCompileCommands(istr)); + ASSERT_EQUALS(true, importer.processCompileCommands(istr)); ASSERT_EQUALS(1, importer.fileSettings.size()); const FileSettings &fs = importer.fileSettings.front(); ASSERT_EQUALS(1, fs.includePaths.size()); ASSERT_EQUALS("/x/", fs.includePaths.front()); } - void importCompileCommands13() const { // #13333 + void processCompileCommands13() const { // #13333 REDIRECT; constexpr char json[] = R"([{ @@ -371,7 +456,7 @@ class TestImportProject : public TestFixture { }])"; std::istringstream istr(json); TestImporter importer; - ASSERT_EQUALS(true, importer.importCompileCommands(istr)); + ASSERT_EQUALS(true, importer.processCompileCommands(istr)); ASSERT_EQUALS(2, importer.fileSettings.size()); const FileSettings &fs1 = importer.fileSettings.front(); const FileSettings &fs2 = importer.fileSettings.back(); @@ -379,7 +464,7 @@ class TestImportProject : public TestFixture { ASSERT_EQUALS(1, fs2.file.fsFileId()); } - void importCompileCommands14() const { // #14156 + void processCompileCommands14() const { // #14156 REDIRECT; constexpr char json[] = R"([{ @@ -396,13 +481,13 @@ class TestImportProject : public TestFixture { }])"; std::istringstream istr(json); TestImporter importer; - ASSERT_EQUALS(true, importer.importCompileCommands(istr)); + ASSERT_EQUALS(true, importer.processCompileCommands(istr)); ASSERT_EQUALS(1, importer.fileSettings.size()); const FileSettings &fs = importer.fileSettings.front(); ASSERT_EQUALS("TFS_LINUX_MODULE_NAME=\"tfs_linux\"", fs.defines); } - void importCompileCommands15() const { // #14306 + void processCompileCommands15() const { // #14306 REDIRECT; constexpr char json[] = R"([ @@ -415,55 +500,73 @@ class TestImportProject : public TestFixture { ])"; std::istringstream istr(json); TestImporter importer; - ASSERT_EQUALS(true, importer.importCompileCommands(istr)); + ASSERT_EQUALS(true, importer.processCompileCommands(istr)); ASSERT_EQUALS(1, importer.fileSettings.size()); const FileSettings &fs = importer.fileSettings.front(); ASSERT_EQUALS(1, fs.includePaths.size()); ASSERT_EQUALS("C:/Users/abcd/efg/hijk/path/123/", fs.includePaths.front()); } - void importCompileCommandsArgumentsSection() const { + void processCompileCommandsForcedInclude() const { // -include / /FI force-include + REDIRECT; + constexpr char json[] = + R"([{ + "file": "/x/a.c", + "directory": "/x", + "command": "cc -include prefix.h /FIplatform.h -c a.c" + }])"; + std::istringstream istr(json); + TestImporter importer; + ASSERT_EQUALS(true, importer.processCompileCommands(istr)); + ASSERT_EQUALS(1, importer.fileSettings.size()); + const FileSettings &fs = importer.fileSettings.front(); + ASSERT_EQUALS(2, fs.forcedIncludes.size()); + ASSERT_EQUALS("prefix.h", fs.forcedIncludes.front()); // gcc/clang -include + ASSERT_EQUALS("platform.h", fs.forcedIncludes.back()); // MSVC/clang-cl /FI + } + + void processCompileCommandsArgumentsSection() const { REDIRECT; constexpr char json[] = "[ { \"directory\": \"/tmp/\"," "\"arguments\": [\"gcc\", \"-c\", \"src.c\"]," "\"file\": \"src.c\" } ]"; std::istringstream istr(json); TestImporter importer; - ASSERT_EQUALS(true, importer.importCompileCommands(istr)); + ASSERT_EQUALS(true, importer.processCompileCommands(istr)); ASSERT_EQUALS(1, importer.fileSettings.size()); ASSERT_EQUALS("/tmp/src.c", importer.fileSettings.cbegin()->filename()); } - void importCompileCommandsNoCommandSection() const { + void processCompileCommandsNoCommandSection() const { REDIRECT; constexpr char json[] = "[ { \"directory\": \"/tmp/\"," "\"file\": \"src.mm\" } ]"; std::istringstream istr(json); TestImporter importer; - ASSERT_EQUALS(false, importer.importCompileCommands(istr)); + ASSERT_EQUALS(false, importer.processCompileCommands(istr)); ASSERT_EQUALS(0, importer.fileSettings.size()); ASSERT_EQUALS(1, importer.errors.size()); ASSERT_EQUALS("no 'arguments' or 'command' field found in compilation database entry", importer.errors[0]); } - void importCompileCommandsDirectoryMissing() const { + void processCompileCommandsDirectoryMissing() const { REDIRECT; constexpr char json[] = "[ { \"file\": \"src.mm\" } ]"; std::istringstream istr(json); TestImporter importer; - ASSERT_EQUALS(false, importer.importCompileCommands(istr)); + ASSERT_EQUALS(false, importer.processCompileCommands(istr)); ASSERT_EQUALS(0, importer.fileSettings.size()); ASSERT_EQUALS(1, importer.errors.size()); ASSERT_EQUALS("'directory' field in compilation database entry missing", importer.errors[0]); } - void importCompileCommandsDirectoryInvalid() const { + void processCompileCommandsDirectoryInvalid() const { REDIRECT; constexpr char json[] = "[ { \"directory\": 123," "\"file\": \"src.mm\" } ]"; std::istringstream istr(json); TestImporter importer; - ASSERT_EQUALS(false, importer.importCompileCommands(istr)); + ASSERT_EQUALS(false, importer.processCompileCommands(istr)); ASSERT_EQUALS(0, importer.fileSettings.size()); ASSERT_EQUALS(1, importer.errors.size()); ASSERT_EQUALS("'directory' field in compilation database entry is not a string", importer.errors[0]); @@ -482,6 +585,7 @@ class TestImportProject : public TestFixture { " \n" " \n" " \n" + " gcc-macros.h\n" " \n" " \n" " \n" @@ -497,9 +601,31 @@ class TestImportProject : public TestFixture { ASSERT_EQUALS("cli/", project.guiProject.pathNames[0]); ASSERT_EQUALS(1, s.includePaths.size()); ASSERT_EQUALS("lib/", s.includePaths.front()); + ASSERT_EQUALS(1, s.userIncludes.size()); + ASSERT_EQUALS("gcc-macros.h", s.userIncludes.front()); ASSERT_EQUALS(true, s.inlineSuppressions); } + void importCppcheckGuiProjectDuplicateSuppressions() const { + REDIRECT; + constexpr char xml[] = "\n" + "\n" + " \n" + " test test\n" + " \n" + " uninitvar\n" + " uninitvar\n" + " \n" + "\n"; + std::istringstream istr(xml); + Settings s; + Suppressions supprs; + TestImporter project; + ASSERT_EQUALS(false, project.importCppcheckGuiProject(istr, s, supprs)); + ASSERT_EQUALS(1, project.errors.size()); + ASSERT_EQUALS("suppression 'uninitvar' already exists", project.errors[0]); + } + void importCppcheckGuiProjectPremiumMisra() const { REDIRECT; constexpr char xml[] = "\n" @@ -521,6 +647,51 @@ class TestImportProject : public TestFixture { ASSERT(s.addons.empty()); } + void importCppcheckGuiProjectAbsPath() const { + // Regression test: absolute Unix paths in must not be prepended with the + // project directory (mPath). Before the fix, joinRelativePath() classified "/foo" + // as RootRelative (no drive letter) and prepended mPath, yielding "/proj//foo". + REDIRECT; + constexpr char xml[] = "\n" + "\n" + " \n" + " \n" + " \n" + "\n"; + std::istringstream istr(xml); + Settings s; + Suppressions supprs; + TestImporter project; + project.setProjectPath("/project/dir/"); + ASSERT_EQUALS(true, project.importCppcheckGuiProject(istr, s, supprs)); + ASSERT_EQUALS(1, project.guiProject.pathNames.size()); + ASSERT_EQUALS("/abs/path/file.cpp", project.guiProject.pathNames[0]); + } + + void importCppcheckGuiProjectRelPathWithBase() const { + // Relative paths in must be prepended with the project directory (mPath), + // matching MSBuild/GUI behaviour where relative entries are project-dir-relative. + // Also covers the mixed-CWD scenario: when --project=../foo.cppcheck is used, + // mPath is "../", so a relative entry like "src/a.cpp" becomes "../src/a.cpp". + REDIRECT; + constexpr char xml[] = "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + "\n"; + std::istringstream istr(xml); + Settings s; + Suppressions supprs; + TestImporter project; + project.setProjectPath("../"); + ASSERT_EQUALS(true, project.importCppcheckGuiProject(istr, s, supprs)); + ASSERT_EQUALS(2, project.guiProject.pathNames.size()); + ASSERT_EQUALS("/abs/file.cpp", project.guiProject.pathNames[0]); + ASSERT_EQUALS("../src/rel.cpp", project.guiProject.pathNames[1]); + } + void ignorePaths() const { FileSettings fs1{"foo/bar", Standards::Language::CPP, 0}; FileSettings fs2{"qwe/rty", Standards::Language::CPP, 0}; @@ -538,59 +709,6 @@ class TestImportProject : public TestFixture { ASSERT_EQUALS(0, project.fileSettings.size()); } - void testVcxprojUnicode() const - { - const char vcxproj[] = R"-( - - - - - Debug - Win32 - - - Release - Win32 - - - - - Unicode - - - Application - true - v143 - Unicode - - - Application - false - v143 - NotSet - Static - - - - - -)-"; - tinyxml2::XMLDocument doc; - ASSERT_EQUALS(tinyxml2::XML_SUCCESS, doc.Parse(vcxproj, sizeof(vcxproj))); - TestImporter project; - std::map variables; - std::vector cache; - ASSERT_EQUALS(project.importVcxproj("test.vcxproj", doc, variables, {}, {}, cache), true); - ASSERT_EQUALS(project.fileSettings.size(), 2); - ASSERT(project.fileSettings.front().defines.find(";UNICODE=1;") != std::string::npos); - ASSERT(project.fileSettings.front().defines.find(";_UNICODE=1") != std::string::npos); - ASSERT(project.fileSettings.front().defines.find(";_UNICODE=1;") == std::string::npos); // No duplicates - ASSERT_EQUALS(project.fileSettings.front().useMfc, false); - ASSERT(project.fileSettings.back().defines.find(";UNICODE=1;") == std::string::npos); - ASSERT(project.fileSettings.back().defines.find(";_UNICODE=1") == std::string::npos); - ASSERT_EQUALS(project.fileSettings.back().useMfc, true); - } - void testCollectArgs1() const { std::vector args; @@ -696,11 +814,97 @@ class TestImportProject : public TestFixture { ASSERT(cppcheck::testing::evaluateVcxprojCondition(" '$(Configuration)' == 'Debug' And '$(Platform)' == 'Win32'", "Debug", "Win32")); ASSERT(cppcheck::testing::evaluateVcxprojCondition(" '$(Configuration)' == 'Debug' Or '$(Platform)' == 'Win32'", "Release", "Win32")); ASSERT(cppcheck::testing::evaluateVcxprojCondition(" $(Configuration.StartsWith('Debug'))", "Debug-AddressSanitizer", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition(" $(Configuration.ToUpper().StartsWith('DEBUG'))", "Debug", "Win32")); ASSERT(cppcheck::testing::evaluateVcxprojCondition(" $(Configuration.EndsWith('AddressSanitizer'))", "Debug-AddressSanitizer", "Win32")); ASSERT(cppcheck::testing::evaluateVcxprojCondition(" $(Configuration.Contains('Address'))", "Debug-AddressSanitizer", "Win32")); ASSERT(cppcheck::testing::evaluateVcxprojCondition(" $(Configuration.Contains ( 'Address' ) )", "Debug-AddressSanitizer", "Win32")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition(" $(Configuration.StartsWith('Release'))", "Debug-AddressSanitizer", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition(" $(Platform.Contains('32'))", "Debug", "Win32")); ASSERT(cppcheck::testing::evaluateVcxprojCondition(" $(Configuration.Contains('Address')) And '$(Platform)' == 'Win32'", "Debug-AddressSanitizer", "Win32")); ASSERT(cppcheck::testing::evaluateVcxprojCondition(" ($(Configuration.Contains('Address')) ) And ( '$(Platform)' == 'Win32')", "Debug-AddressSanitizer", "Win32")); + // Relational operators - integer + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'14' >= '14'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'15' > '14'", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'13' > '14'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'13' < '14'", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'15' < '14'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'13' <= '14'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'14' <= '14'", "", "")); + // Relational operators - version + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'14.0' >= '14.0'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'14.1' >= '14.0'", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'13.0' >= '14.0'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'1.10.0.0' > '1.9.0.0'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'v14.0' >= '14.0'", "", "")); + // Version comparison: full 4-part #.#.#.# + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'1.2.3.4' == '1.2.3.4'", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'1.2.3.4' == '1.2.3.5'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'1.2.3.5' > '1.2.3.4'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'1.2.3.4' < '1.2.3.5'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'2.0.0.0' > '1.9.9.9'", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'1.9.9.9' > '2.0.0.0'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'1.2.3.4' >= '1.2.3.4'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'1.2.3.4' <= '1.2.3.4'", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'1.2.3.4' > '1.2.3.4'", "", "")); + // Version comparison: more than 4 parts (no truncation) + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'1.2.3.4.5' > '1.2.3.4.4'", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'1.2.3.4.4' > '1.2.3.4.5'", "", "")); + // == and != are NOT relational operators: real MSBuild documents them + // as ordinary equality/inequality (numeric if both sides are plain + // numbers, case-insensitive string otherwise), never the zero-padded, + // component-wise equality $([MSBuild]::VersionEquals(...)) implements + // (see testMSBuildStaticFunctions() for that function's own, + // deliberately different, semantics). '1.2.3.4.0' and '1.2.3.4' are + // different strings and neither parses as a plain number, so == and + // != see them as unequal -- even though relationally (see + // '17'/'17.0.0.0' below) MSBuildVersion's own comparator would treat + // a shorter version as simply less than a longer one, not equal to it + // either. + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'1.2.3.4.0' == '1.2.3.4'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'1.2.3.4.0' != '1.2.3.4'", "", "")); + // Relational operators (<, >, <=, >=) DO use MSBuildVersion's + // comparator, which treats a version's omitted trailing components as + // -1 (see MSBuildVersion::cmp()'s doc comment) -- so a shorter + // version string sorts strictly before a longer one, '17' < '17.0.0.0', + // rather than comparing equal to it under either == or a relational op. + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'17' == '17.0.0.0'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'17' != '17.0.0.0'", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'17' >= '17.0.0.0'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'17' <= '17.0.0.0'", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'17' > '17.0.0.0'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'17' < '17.0.0.0'", "", "")); + // Same reasoning as '17'/'17.0.0.0' above: == is plain (numeric- or + // string-, never version-) comparison, so none of these are equal -- + // '17.0', '17.0.0.0' and '17.0.0' are all different strings from each + // other and from '17', and none of them parses as a plain integer + // (they contain '.'). + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'17.0' == '17'", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'17.0' == '17.0.0.0'", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'17.0.0' == '17'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'17.1' > '17.0.0.0'", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'16.9' > '17'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'1' < '1.0.0.1'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'1.2.3' < '1.2.3.1'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'1.2.3' > '1.2.2.9'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'2' > '1.9.9.9'", "", "")); + // 'Current' on LHS + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'Current' > '14'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'Current' >= '18'", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'Current' < '14'", "", "")); + // 'Current' on RHS + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'14' < 'Current'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'18' <= 'Current'", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'19' < 'Current'", "", "")); + // Static property functions in conditions + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$([MSBuild]::Add(1, 2)) == '3'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$([MSBuild]::EnsureTrailingSlash('foo')) == 'foo/'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$([System.String]::IsNullOrEmpty('')) == 'True'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$([System.Math]::Max(1, 2)) == '2'", "", "")); + // Unknown variable + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'$(DoesNotExist)' == ''", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'$(PATH)' != ''", "", "")); + // Relational operators - error case + ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("'14.0' >= ''", "", ""), std::runtime_error, "Cannot compare '14.0' and ''"); ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("And", "", ""), std::runtime_error, "Invalid condition: 'And'"); ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("Or", "", ""), std::runtime_error, "Invalid condition: 'Or'"); ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("!", "", ""), std::runtime_error, "Invalid condition: '!'"); @@ -709,9 +913,639 @@ class TestImportProject : public TestFixture { ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("'' == '')", "", ""), std::runtime_error, "unmatched ')' in condition '' == '')"); ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("''", "", ""), std::runtime_error, "Invalid condition: ''''"); ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("'' == '", "", ""), std::runtime_error, "Can not tokenize condition"); - ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.Lower())", "", ""), std::runtime_error, "Missing operator"); - // invalid expression in => no error. We are ok with that as long as we don't crash - ASSERT(!cppcheck::testing::evaluateVcxprojCondition("' ' && ' '", "", "")); + // ToUpper / ToLower + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.ToUpper()) == 'DEBUG'", "Debug", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.ToLower()) == 'debug'", "Debug", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.ToUpper()) == 'debug'", "Debug", "Win32")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("$(Configuration.ToUpper()) == 'RELEASE'", "Debug", "Win32")); + // C-style && is not a valid MSBuild operator — throws rather than silently returning false + ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("' ' && ' '", "", ""), std::runtime_error, "Invalid condition: '' ' && ' ''"); + // case insensitive + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'Debug' == 'DEBUG'", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'Debug' != 'DEBUG'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(configuration) == 'Debug'", "Debug", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(configuration) == 'Debug'", "Debug", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(CONFIGURATION) == 'Debug'", "Debug", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(Configuration) == 'Debug'", "Debug", "Win32")); + + ASSERT(cppcheck::testing::evaluateVcxprojCondition("true", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("TRUE", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("false", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("FALSE", "", "")); + + ASSERT(cppcheck::testing::evaluateVcxprojCondition("true And true", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("true Or false", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("true And false", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("false Or false", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("!false", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("!true", "", "")); + + // HasTrailingSlash + ASSERT(cppcheck::testing::evaluateVcxprojCondition("HasTrailingSlash('foo/')", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("HasTrailingSlash('foo\\')", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("HasTrailingSlash('foo')", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("HasTrailingSlash('')", "", "")); + + // string manipulation + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.Trim()) == 'Debug'", " Debug ", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.TrimStart()) == 'Debug '", " Debug ", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.TrimEnd()) == ' Debug'", " Debug ", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.Substring(0, 5)) == 'Debug'", "Debug-Test", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.Substring(6)) == 'Test'", "Debug-Test", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.Replace('-', '_')) == 'Debug_Test'", "Debug-Test", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition( "$(Configuration.Trim().ToUpper()) == 'DEBUG'", " Debug ", "Win32")); + + ASSERT(cppcheck::testing::evaluateVcxprojCondition("true", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("false", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("true And false", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("!false", "", "")); + + ASSERT(cppcheck::testing::evaluateVcxprojCondition( "$(Configuration.Substring(5)) == ''", "Debug", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition( "$(Configuration.Substring(5, 0)) == ''", "Debug", "Win32")); + ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.Substring(-1)) == ''", "Debug", "Win32"), std::runtime_error, "Substring start index out of range"); + ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition( "$(Configuration.Substring(4, 2)) == ''", "Debug", "Win32"), std::runtime_error, "Substring length out of range"); + + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.Trim()) == 'Debug'", " \tDebug\r\n", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.TrimStart()) == 'Debug '", " Debug ", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.TrimEnd()) == ' Debug'", " Debug ", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.Trim('-')) == 'Debug'", "--Debug--", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.TrimStart('-')) == 'Debug--'", "--Debug--", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.TrimEnd('-')) == '--Debug'", "--Debug--", "Win32")); + + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.Replace('-', '_')) == 'Debug_Test'","Debug-Test", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.Replace('Debug', 'Release')) == 'Release-Test'", "Debug-Test", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.Replace('x', 'y')) == 'Debug-Test'", "Debug-Test", "Win32")); + ASSERT_THROW_EQUALS(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.Replace('', 'x')) == 'Debug'", "Debug", "Win32"), std::runtime_error, "Replace search string cannot be empty"); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.Replace('Debug', 'Release')) == 'Release-Test'", "Debug-Test", "Win32")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("$(Configuration.Replace('debug', 'Release')) == 'Release-Test'", "Debug-Test", "Win32")); + + // Length property access (no parentheses) + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.Length) == '5'", "Debug", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.Length) == '0'", "", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(Platform.Length) == '5'", "Debug", "Win32")); + // Length after a method call in a chain + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.ToUpper().Length) == '5'", "Debug", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.Replace('-', '').Length) == '9'", "Debug-Test", "Win32")); + // Length in PropertyValueExpander (non-condition) path + ASSERT_EQUALS("5", cppcheck::testing::expandMSBuildProperties("$(Configuration.Length)", "Debug", "Win32")); + ASSERT_EQUALS("0", cppcheck::testing::expandMSBuildProperties("$(Configuration.Length)", "", "Win32")); + ASSERT_EQUALS("5", cppcheck::testing::expandMSBuildProperties("$(Platform.Length)", "Debug", "Win32")); + ASSERT_EQUALS("5", cppcheck::testing::expandMSBuildProperties("$(Configuration.ToUpper().Length)", "Debug", "Win32")); + + // IndexOf / LastIndexOf + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.IndexOf('-')) == '5'", "Debug-Test", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.IndexOf('x')) == '-1'", "Debug-Test", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.IndexOf('e')) == '1'", "Debug-Test", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.IndexOf('e', '3')) == '7'", "Debug-Test", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.LastIndexOf('e')) == '7'", "Debug-Test", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.LastIndexOf('x')) == '-1'", "Debug-Test", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.LastIndexOf('e', '3')) == '1'", "Debug-Test", "Win32")); + ASSERT_EQUALS("5", cppcheck::testing::expandMSBuildProperties("$(Configuration.IndexOf('-'))", "Debug-Test", "Win32")); + ASSERT_EQUALS("-1", cppcheck::testing::expandMSBuildProperties("$(Configuration.IndexOf('x'))", "Debug-Test", "Win32")); + ASSERT_EQUALS("7", cppcheck::testing::expandMSBuildProperties("$(Configuration.LastIndexOf('e'))", "Debug-Test", "Win32")); + + // PadLeft / PadRight + ASSERT_EQUALS(" hi", cppcheck::testing::expandMSBuildProperties("$(Configuration.PadLeft('4'))", "hi", "Win32")); + ASSERT_EQUALS("hi ", cppcheck::testing::expandMSBuildProperties("$(Configuration.PadRight('4'))", "hi", "Win32")); + ASSERT_EQUALS("00hi", cppcheck::testing::expandMSBuildProperties("$(Configuration.PadLeft('4', '0'))", "hi", "Win32")); + ASSERT_EQUALS("hi00", cppcheck::testing::expandMSBuildProperties("$(Configuration.PadRight('4', '0'))", "hi", "Win32")); + // no padding needed when value already long enough + ASSERT_EQUALS("hello", cppcheck::testing::expandMSBuildProperties("$(Configuration.PadLeft('3'))", "hello", "Win32")); + ASSERT_EQUALS("hello", cppcheck::testing::expandMSBuildProperties("$(Configuration.PadRight('3'))", "hello", "Win32")); + // width exactly equal to value length — no padding + ASSERT_EQUALS("hi", cppcheck::testing::expandMSBuildProperties("$(Configuration.PadLeft('2'))", "hi", "Win32")); + ASSERT_EQUALS("hi", cppcheck::testing::expandMSBuildProperties("$(Configuration.PadRight('2'))", "hi", "Win32")); + // width 0 — no padding + ASSERT_EQUALS("hi", cppcheck::testing::expandMSBuildProperties("$(Configuration.PadLeft('0'))", "hi", "Win32")); + // chaining: pad then measure length + ASSERT_EQUALS("6", cppcheck::testing::expandMSBuildProperties("$(Configuration.PadLeft('6', '0').Length)", "hi", "Win32")); + ASSERT_EQUALS("6", cppcheck::testing::expandMSBuildProperties("$(Configuration.PadRight('6', '0').Length)", "hi", "Win32")); + // condition evaluation path + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.PadLeft('4', '0')) == '00hi'", "hi", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(Configuration.PadRight('4', '0')) == 'hi00'", "hi", "Win32")); + + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(Configuration) == DEBUG", "Debug", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$(configuration) == 'Debug'", "Debug", "Win32")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'0x10' > '0x0F'", "", "")); + + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'0x0F' < '0x10'", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'0x10' < '0x0F'", "", "")); + + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'0x10' > '0x0F'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'0x0F' < '0x10'", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'0x10' < '0x0F'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'010' > '9'", "", "")); + // Equality comparison: numeric, Boolean, then string fallback -- NOT + // MSBuildVersion's zero-padded comparison (see the '17'/'17.0.0.0' + // block above in testVcxprojConditions() for why): '0x10' and '16' + // are both plain (hex/decimal) integers, so == compares them + // numerically and they're equal; '1.0' is not a plain integer (it + // contains '.') and is a different string from '1', so == falls back + // to string comparison and they're NOT equal. + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'0x10' == '16'", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'1.0' == '1'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'true' == 'TRUE'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'Alpha' == 'alpha'", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'Alpha' == 'Beta'", "", "")); + // Boolean literals and quoted boolean strings are case-insensitive (H-2) + // Unquoted keywords (matchWord is already case-insensitive) + ASSERT(cppcheck::testing::evaluateVcxprojCondition("true", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("false", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("True", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("False", "", "")); + // Quoted boolean strings from property expansion (e.g. true) + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'true'", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'false'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'TRUE'", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'FALSE'", "", "")); + // Composition with mixed-case booleans + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'true' And 'True'", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'true' And 'false'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'false' Or 'TRUE'", "", "")); + // Equality comparison: numeric, Boolean, then string fallback -- NOT + // MSBuildVersion's zero-padded comparison (see the '17'/'17.0.0.0' + // block above in testVcxprojConditions() for why): '0x10' and '16' + // are both plain (hex/decimal) integers, so == compares them + // numerically and they're equal; '1.0' is not a plain integer (it + // contains '.') and is a different string from '1', so == falls back + // to string comparison and they're NOT equal. + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'0x10' == '16'", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'1.0' == '1'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'true' == 'TRUE'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'Alpha' == 'alpha'", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'Alpha' == 'Beta'", "", "")); + } + + // Regression coverage for compare()'s == / != previously sharing the + // same numeric -> Boolean -> Version -> string cascade as the relational + // operators <, >, <=, >=. Real MSBuild documents <, >, <=, >= as usable + // only with numeric values -- a dotted, multi-component string isn't a + // valid number there, which is exactly why the dedicated + // $([MSBuild]::VersionGreaterThan(...)) etc. functions exist -- while == + // and != are documented as ordinary equality/inequality: numeric if both + // sides are plain numbers, case-insensitive string otherwise. Before the + // fix, == and != additionally fell through to MSBuildVersion's own + // comparator when both sides parsed as a dotted version string, and that + // comparator's own "==" implementation deliberately treats an omitted + // trailing component as zero (1 == 1.0 == 1.0.0) -- correct for the + // dedicated VersionEquals() function this codebase also exposes (see + // testMSBuildStaticFunctions()'s $([MSBuild]::VersionEquals(...)) + // coverage), but not something a plain == in an ordinary + // Condition="..." attribute should ever do. This meant e.g. + // Condition="'$(SomeVersion)' == '1.1.0'" could spuriously match + // '$(SomeVersion)'=='1.1', a real vcxproj-authoring hazard given how + // often toolset/SDK version properties look exactly like this. + void testVcxprojConditionEqualityIsNotVersionComparison() const { + // The exact shape of bug report: a shorter and a longer dotted + // version string must NOT compare equal under == or != , even though + // MSBuildVersion's own zero-padded equality (used by + // $([MSBuild]::VersionEquals(...)) -- and, before this fix, leaked + // into plain ==) would treat them as the same value. + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'1.1' == '1.1.0'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'1.1' != '1.1.0'", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'1' == '1.0'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'1' != '1.0'", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'1' == '1.0.0'", "", "")); + + // Relationally (<, >, <=, >=), MSBuildVersion's comparator treats an + // omitted trailing component as -1, not 0 (matching .NET's + // System.Version semantics), so a shorter version sorts strictly + // BEFORE a longer one -- neither "equal" under == nor under the + // relational operators either, it is simply less. + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'1.1' < '1.1.0'", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'1.1' >= '1.1.0'", "", "")); + + // Two identical dotted version strings are still equal under == -- + // this is ordinary string equality doing its job, not version + // comparison; nothing about excluding == from the version-comparison + // path should affect the case where both sides are literally the + // same text. + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'1.1.0' == '1.1.0'", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'1.1.0' != '1.1.0'", "", "")); + + // Genuinely plain numbers (no dots) are still compared numerically + // under == -- this really is documented MSBuild behavior for ==, + // unlike the dotted-version case above, so leading zeros and the + // like still numerically match. + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'01' == '1'", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'01' != '1'", "", "")); + + // $([MSBuild]::VersionEquals(...)) is a distinct, dedicated function + // with its own, deliberately zero-padding, semantics -- unaffected + // by any of the above, since it never goes through compare() at all. + // See testMSBuildStaticFunctions() for its general coverage; this is + // exactly the '1.1'/'1.1.0' pair that a plain == above now rejects. + ASSERT_EQUALS("True", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::VersionEquals('1.1', '1.1.0'))")); + } + + // Regression coverage for property-name identifier parsing rejecting '-'. + // Microsoft documents a valid MSBuild property name as + // [A-Za-z_][A-Za-z0-9_-]* -- '-' IS allowed after the first character, + // e.g. foo referenced as $(My-Property) -- but + // PropertyValueExpander::parseIdentifier() (used by expandMSBuildVariables(), + // the general $(...) expansion path), ConditionParser's property-name + // parsing (used by $(...) inside a Condition="..."), and + // hasOnlyInvariantItemPathVariables()'s inline scanner (used for ClCompile + // item Include/Update/Remove paths, see vcxproj_item_macro_path_hyphen_test.py + // for CLI-level coverage of that one) all previously stopped at the first + // '-', silently truncating the name -- so $(My-Property) was parsed as a + // reference to a property literally named "My" followed by the literal + // text "-Property)", which (barring an actual property named exactly + // "My") just left the whole expression unexpanded, rather than resolving + // "My-Property" as one property name the way real MSBuild does. Method + // and member names are NOT affected -- MSBuild's own grammar restricts + // those to [A-Za-z_][A-Za-z0-9_]* with no '-' (see + // ConditionParser::parseMethodName()'s doc comment and the inline method + // scans in PropertyValueExpander::tryParseExpr()) -- so this test also + // confirms a hyphen right after a '.' in a method position is correctly + // NOT consumed as part of the method name. + // + // expandMSBuildExpression()/evaluateVcxprojCondition() (the testing hooks + // used here) only let a caller set Configuration/Platform in the + // properties map directly, so an OS environment variable -- the other + // source PropertyValueExpander::lookup() and ConditionParser::getPropertyValue() + // already fall back to for any name not in the properties map -- is the + // only way to inject an arbitrarily-named property through them. + void testPropertyNameAllowsHyphen() const { + const char *const envName = "CppcheckTest-Hyphen-Prop"; +#ifdef _WIN32 + _putenv_s(envName, "hyphen-value"); +#else + setenv(envName, "hyphen-value", 1); +#endif + + // General property expansion (expandMSBuildVariables(), via + // PropertyValueExpander::parseIdentifier()). + ASSERT_EQUALS("hyphen-value", cppcheck::testing::expandMSBuildExpression("$(CppcheckTest-Hyphen-Prop)")); + ASSERT_EQUALS("prefix-hyphen-value-suffix", + cppcheck::testing::expandMSBuildExpression("prefix-$(CppcheckTest-Hyphen-Prop)-suffix")); + + // Condition evaluation (ConditionParser::parsePropertyName(), via + // parsePropertyExpression()). + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'$(CppcheckTest-Hyphen-Prop)' == 'hyphen-value'", "", "")); + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'$(CppcheckTest-Hyphen-Prop)' == 'wrong-value'", "", "")); + + // A hyphen is still correctly rejected in a METHOD name -- the + // property name parses in full ("CppcheckTest-Hyphen-Prop"), but the + // method-name scan then stops at the '-' in ".To-Upper", leaving + // "To" as an unrecognized no-paren property access (passing `value` + // through unchanged, exactly like an unrelated unknown property + // accessor would) and "-Upper)" behind as literal, unconsumed text + // that the surrounding expansion then copies through verbatim -- + // rather than treating "-" as part of the method name and matching + // some (nonexistent) "To-Upper" method. + ASSERT_EQUALS("hyphen-value-Upper)", cppcheck::testing::expandMSBuildExpression("$(CppcheckTest-Hyphen-Prop.To-Upper)")); + +#ifdef _WIN32 + _putenv_s(envName, ""); +#else + unsetenv(envName); +#endif + } + + void testMSBuildStaticFunctions() const { + // --- $([MSBuild]::...) arithmetic --- + ASSERT_EQUALS("3", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::Add(1, 2))")); + ASSERT_EQUALS("5", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::Subtract(8, 3))")); + ASSERT_EQUALS("12", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::Multiply(3, 4))")); + ASSERT_EQUALS("3", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::Divide(9, 3))")); + ASSERT_EQUALS("2", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::Modulo(5, 3))")); + + // Overflow wraps around like MSBuild (.NET unchecked arithmetic). + ASSERT_EQUALS("-9223372036854775808", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::Add(9223372036854775807, 1))")); + ASSERT_EQUALS("9223372036854775807", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::Subtract(-9223372036854775808, 1))")); + ASSERT_EQUALS("7766279611452241921", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::Multiply(9999999999, 9999999999))")); + // .NET throws OverflowException for LLONG_MIN / -1 and LLONG_MIN % -1. + ASSERT_EQUALS("", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::Divide(-9223372036854775808, -1))")); + ASSERT_EQUALS("", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::Modulo(-9223372036854775808, -1))")); + + // --- $([MSBuild]::...) path helpers --- + ASSERT_EQUALS("foo/", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::EnsureTrailingSlash('foo'))")); + ASSERT_EQUALS("foo/", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::EnsureTrailingSlash('foo/'))")); + // NormalizePath: join segments, normalise separators, resolve . and .. + // Use absolute first segments so results are deterministic (CWD-independent). + ASSERT_EQUALS("/a/b/c", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::NormalizePath('/a', 'b', 'c'))")); + ASSERT_EQUALS("/a/b/c", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::NormalizePath('/a\\b\\c'))")); + ASSERT_EQUALS("/a/c", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::NormalizePath('/a/b/../c'))")); + ASSERT_EQUALS("/a/b/c", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::NormalizePath('/a/b/./c'))")); + ASSERT_EQUALS("C:/a/c", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::NormalizePath('C:\\a\\b\\..\\c'))")); + ASSERT_EQUALS("C:/a/b/c", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::NormalizePath('C:\\a', 'b', 'c'))")); + // NormalizeDirectory: same as NormalizePath but always has a trailing slash + ASSERT_EQUALS("/a/b/c/", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::NormalizeDirectory('/a', 'b', 'c'))")); + ASSERT_EQUALS("/a/b/c/", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::NormalizeDirectory('/a\\b\\c'))")); + ASSERT_EQUALS("C:/a/b/c/", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::NormalizeDirectory('C:\\a', 'b', 'c'))")); + // Regression: space between method name and '(' must not drop the arg list. + // Before the fix this produced "" and a "unknown class MSBuild or member + // NormalizeDirectory" debug entry because the arg parser never saw '('. + ASSERT_EQUALS("/a/b/c/", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::NormalizeDirectory ('/a', 'b', 'c'))")); + ASSERT_EQUALS("/a/b/c", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::NormalizePath ('/a', 'b', 'c'))")); + // $(…) references inside quoted args must be expanded before the function + // runs, so that '..' resolution operates on real path components. + // expandMSBuildProperties pre-populates Configuration and Platform; use + // them here as stand-ins for real path properties. + ASSERT_EQUALS("Debug/", cppcheck::testing::expandMSBuildProperties("$([MSBuild]::NormalizeDirectory('$(Configuration)'))", "Debug", "Win32")); + ASSERT_EQUALS("Win32/sub/", cppcheck::testing::expandMSBuildProperties("$([MSBuild]::NormalizeDirectory('$(Platform)', 'sub'))", "Debug", "Win32")); + // The critical real-world pattern: NormalizeDirectory with an inner + // property and '..' segments — must NOT collapse to empty. + ASSERT_EQUALS("Debug/", cppcheck::testing::expandMSBuildProperties("$([MSBuild]::NormalizeDirectory('$(Configuration)', 'sub', '..'))", "Debug", "Win32")); + // Unquoted $(Var)trailing/text arg: trailing bare-word text must be + // concatenated onto the expanded value so that e.g. + // $(MSBuildThisFileDirectory)\..\tools → one concatenated arg, not two. + // Use $(Configuration) as a stand-in for a real directory property. + ASSERT_EQUALS("sub/", cppcheck::testing::expandMSBuildProperties("$([MSBuild]::NormalizeDirectory($(Configuration)\\..\\sub))", "Debug", "Win32")); + ASSERT_EQUALS("sub", cppcheck::testing::expandMSBuildProperties("$([MSBuild]::NormalizePath($(Configuration)\\..\\sub))", "Debug", "Win32")); + // Multi-arg quoted form with three inner $(…) args + ASSERT_EQUALS("Debug/Win32/sub/", cppcheck::testing::expandMSBuildProperties("$([MSBuild]::NormalizeDirectory('$(Configuration)', '$(Platform)', 'sub'))", "Debug", "Win32")); + // Version comparison functions (missing trailing components treated as 0) + ASSERT_EQUALS("True", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::VersionGreaterThan('2.0', '1.9'))")); + ASSERT_EQUALS("False", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::VersionGreaterThan('1.9', '2.0'))")); + ASSERT_EQUALS("False", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::VersionGreaterThan('1.0', '1.0'))")); + ASSERT_EQUALS("True", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::VersionGreaterThanOrEquals('1.0', '1.0'))")); + ASSERT_EQUALS("True", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::VersionGreaterThanOrEquals('2.0', '1.0'))")); + ASSERT_EQUALS("False", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::VersionGreaterThanOrEquals('1.0', '2.0'))")); + ASSERT_EQUALS("True", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::VersionLessThan('1.9', '2.0'))")); + ASSERT_EQUALS("False", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::VersionLessThan('2.0', '1.9'))")); + ASSERT_EQUALS("True", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::VersionLessThanOrEquals('1.0', '1.0'))")); + ASSERT_EQUALS("True", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::VersionEquals('1.0', '1.0.0'))")); // missing component = 0 + ASSERT_EQUALS("True", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::VersionEquals('1.2.3', '1.2.3'))")); + ASSERT_EQUALS("False", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::VersionEquals('1.0', '1.1'))")); + ASSERT_EQUALS("True", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::VersionNotEquals('1.0', '1.1'))")); + ASSERT_EQUALS("False", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::VersionNotEquals('1.0', '1.0.0'))")); + // multi-component: 1.2.3.4 vs 1.2.3.5 + ASSERT_EQUALS("True", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::VersionLessThan('1.2.3.4', '1.2.3.5'))")); + ASSERT_EQUALS("False", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::VersionGreaterThan('1.2.3.4', '1.2.3.5'))")); + // usable in a condition + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$([MSBuild]::VersionGreaterThan('17.0', '16.9')) == 'True'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$([MSBuild]::VersionEquals('14.0', '14.0.0')) == 'True'", "", "")); + + // ValueOrDefault: return first arg when non-empty, else second + ASSERT_EQUALS("x", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::ValueOrDefault('x', 'y'))")); + ASSERT_EQUALS("y", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::ValueOrDefault('', 'y'))")); + // GetCurrentToolsVersion + ASSERT_EQUALS("Current", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::GetCurrentToolsVersion())")); + + // --- $([MSBuild]::...) bitwise --- + ASSERT_EQUALS("2", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::BitwiseAnd(6, 3))")); + ASSERT_EQUALS("7", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::BitwiseOr(5, 3))")); + ASSERT_EQUALS("6", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::BitwiseXor(5, 3))")); + + // --- $([MSBuild]::...) Escape / Unescape --- + ASSERT_EQUALS("%3B", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::Escape(';'))")); + ASSERT_EQUALS(";", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::Unescape('%3B'))")); + ASSERT_EQUALS("%24", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::Escape('$'))")); + ASSERT_EQUALS("$", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::Unescape('%24'))")); + + // --- $([System.String]::...) --- + ASSERT_EQUALS("True", cppcheck::testing::expandMSBuildExpression("$([System.String]::IsNullOrEmpty(''))")); + ASSERT_EQUALS("False", cppcheck::testing::expandMSBuildExpression("$([System.String]::IsNullOrEmpty('x'))")); + ASSERT_EQUALS("True", cppcheck::testing::expandMSBuildExpression("$([System.String]::IsNullOrWhiteSpace(' '))")); + ASSERT_EQUALS("False", cppcheck::testing::expandMSBuildExpression("$([System.String]::IsNullOrWhiteSpace('x'))")); + ASSERT_EQUALS("ab", cppcheck::testing::expandMSBuildExpression("$([System.String]::Concat('a', 'b'))")); + ASSERT_EQUALS("a,b", cppcheck::testing::expandMSBuildExpression("$([System.String]::Join(',', 'a', 'b'))")); + // String.Format: plain substitution + ASSERT_EQUALS("foo and bar", cppcheck::testing::expandMSBuildExpression("$([System.String]::Format('{0} and {1}', 'foo', 'bar'))")); + // String.Format: escaped braces + ASSERT_EQUALS("{literal}", cppcheck::testing::expandMSBuildExpression("$([System.String]::Format('{{literal}}'))")); + // String.Format: D (decimal, optional zero-padding) + ASSERT_EQUALS("42", cppcheck::testing::expandMSBuildExpression("$([System.String]::Format('{0:D}', '42'))")); + ASSERT_EQUALS("000042", cppcheck::testing::expandMSBuildExpression("$([System.String]::Format('{0:D6}', '42'))")); + ASSERT_EQUALS("-000005", cppcheck::testing::expandMSBuildExpression("$([System.String]::Format('{0:D6}', '-5'))")); + // String.Format: X / x (hexadecimal) + ASSERT_EQUALS("FF", cppcheck::testing::expandMSBuildExpression("$([System.String]::Format('{0:X}', '255'))")); + ASSERT_EQUALS("00FF", cppcheck::testing::expandMSBuildExpression("$([System.String]::Format('{0:X4}', '255'))")); + ASSERT_EQUALS("00ff", cppcheck::testing::expandMSBuildExpression("$([System.String]::Format('{0:x4}', '255'))")); + // String.Format: F (fixed-point) + ASSERT_EQUALS("3.14", cppcheck::testing::expandMSBuildExpression("$([System.String]::Format('{0:F2}', '3.14159'))")); + ASSERT_EQUALS("3.1416", cppcheck::testing::expandMSBuildExpression("$([System.String]::Format('{0:F4}', '3.14159'))")); + // String.Format: non-numeric arg with a numeric specifier — pass through unchanged + ASSERT_EQUALS("abc", cppcheck::testing::expandMSBuildExpression("$([System.String]::Format('{0:D6}', 'abc'))")); + + // --- $([System.Math]::...) --- + ASSERT_EQUALS("10", cppcheck::testing::expandMSBuildExpression("$([System.Math]::Max(5, 10))")); + ASSERT_EQUALS("5", cppcheck::testing::expandMSBuildExpression("$([System.Math]::Min(5, 10))")); + ASSERT_EQUALS("5", cppcheck::testing::expandMSBuildExpression("$([System.Math]::Abs(-5))")); + ASSERT_EQUALS("5", cppcheck::testing::expandMSBuildExpression("$([System.Math]::Abs(5))")); + ASSERT_EQUALS("2", cppcheck::testing::expandMSBuildExpression("$([System.Math]::Floor(2.9))")); + ASSERT_EQUALS("3", cppcheck::testing::expandMSBuildExpression("$([System.Math]::Ceiling(2.1))")); + ASSERT_EQUALS("2", cppcheck::testing::expandMSBuildExpression("$([System.Math]::Round(2.5))")); + ASSERT_EQUALS("4", cppcheck::testing::expandMSBuildExpression("$([System.Math]::Round(3.5))")); + ASSERT_EQUALS("-2", cppcheck::testing::expandMSBuildExpression("$([System.Math]::Round(-2.5))")); + ASSERT_EQUALS("1000000000000000019884624838656.000000", cppcheck::testing::expandMSBuildExpression("$([System.Math]::Pow(10, 30))")); + ASSERT_EQUALS("1000000000000000019884624838656.000000", cppcheck::testing::expandMSBuildExpression("$([System.Math]::Round(1e30))")); + ASSERT_EQUALS("-1000000000000000019884624838656.000000", cppcheck::testing::expandMSBuildExpression("$([System.Math]::Floor(-1e30))")); + + // --- $([System.IO.Path]::...) --- + ASSERT_EQUALS("bar.cpp", cppcheck::testing::expandMSBuildExpression("$([System.IO.Path]::GetFileName('C:/foo/bar.cpp'))")); + ASSERT_EQUALS("bar", cppcheck::testing::expandMSBuildExpression("$([System.IO.Path]::GetFileNameWithoutExtension('C:/foo/bar.cpp'))")); + ASSERT_EQUALS("C:/foo", cppcheck::testing::expandMSBuildExpression("$([System.IO.Path]::GetDirectoryName('C:/foo/bar.cpp'))")); + ASSERT_EQUALS(".cpp", cppcheck::testing::expandMSBuildExpression("$([System.IO.Path]::GetExtension('bar.cpp'))")); + ASSERT_EQUALS("True", cppcheck::testing::expandMSBuildExpression("$([System.IO.Path]::IsPathRooted('C:/foo'))")); + ASSERT_EQUALS("True", cppcheck::testing::expandMSBuildExpression("$([System.IO.Path]::IsPathRooted('C:\\\\foo'))")); + ASSERT_EQUALS("True", cppcheck::testing::expandMSBuildExpression("$([System.IO.Path]::IsPathRooted('\\\\server\\\\share'))")); + ASSERT_EQUALS("True", cppcheck::testing::expandMSBuildExpression("$([System.IO.Path]::IsPathRooted('C:file.cpp'))")); // drive-relative: .NET returns True (has drive letter) + ASSERT_EQUALS("False", cppcheck::testing::expandMSBuildExpression("$([System.IO.Path]::IsPathRooted('foo'))")); + // Path.Combine: 1, 2, 3, N args; absolute segment resets path + ASSERT_EQUALS("a", cppcheck::testing::expandMSBuildExpression("$([System.IO.Path]::Combine('a'))")); + ASSERT_EQUALS("a/b", cppcheck::testing::expandMSBuildExpression("$([System.IO.Path]::Combine('a', 'b'))")); + ASSERT_EQUALS("a/b/c", cppcheck::testing::expandMSBuildExpression("$([System.IO.Path]::Combine('a', 'b', 'c'))")); + ASSERT_EQUALS("a/b/c/d", cppcheck::testing::expandMSBuildExpression("$([System.IO.Path]::Combine('a', 'b', 'c', 'd'))")); + ASSERT_EQUALS("/abs/b", cppcheck::testing::expandMSBuildExpression("$([System.IO.Path]::Combine('a', '/abs', 'b'))")); + ASSERT_EQUALS("/abs", cppcheck::testing::expandMSBuildExpression("$([System.IO.Path]::Combine('a', 'b', '/abs'))")); + ASSERT_EQUALS(Path::fromNativeSeparators(Path::getCurrentPath()) + "/a/b/c/d", cppcheck::testing::expandMSBuildExpression("$([System.IO.Path]::GetFullPath($([System.IO.Path]::Combine('a', 'b', 'c', 'd')))")); + + // --- $([System.IO.FileInfo]::new(...)) / $([System.IO.DirectoryInfo]::new(...)) --- + // ::new returns the normalized path; chains resolve on that string. + ASSERT_EQUALS("C:/foo/bar.cpp", cppcheck::testing::expandMSBuildExpression("$([System.IO.FileInfo]::new('C:/foo/bar.cpp').FullName)")); + ASSERT_EQUALS("C:/foo/", cppcheck::testing::expandMSBuildExpression("$([System.IO.FileInfo]::new('C:/foo/bar.cpp').DirectoryName)")); + ASSERT_EQUALS("bar.cpp", cppcheck::testing::expandMSBuildExpression("$([System.IO.FileInfo]::new('C:/foo/bar.cpp').Name)")); + ASSERT_EQUALS("C:/foo", cppcheck::testing::expandMSBuildExpression("$([System.IO.DirectoryInfo]::new('C:/foo').FullName)")); + // .Method(args) chain after ::new — e.g. replace in the resolved path + ASSERT_EQUALS("C:/foo/baz.cpp", cppcheck::testing::expandMSBuildExpression("$([System.IO.FileInfo]::new('C:/foo/bar.cpp').FullName.Replace('bar', 'baz'))")); + + // --- Composite / nesting --- + ASSERT_EQUALS("6", cppcheck::testing::expandMSBuildExpression("$([MSBuild]::Add($([MSBuild]::Multiply(2, 2)), 2))")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$([MSBuild]::Add(1, 2)) == '3'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$([System.String]::IsNullOrEmpty('')) == 'True'", "", "")); + ASSERT(cppcheck::testing::evaluateVcxprojCondition("$([System.Math]::Max(10, 5)) == '10'", "", "")); + } + + void testVcxitemsPathResolution() const { + // importVcxitems must absolutise relative paths relative to ProjectDir, + // MSBuild always sets ProjectDir with a trailing slash — use that canonical form. + // Path styles differ by platform: Path::isAbsolute requires C:/ on Windows, / on Linux. +#ifdef _WIN32 + const std::string projDir = "C:/proj/"; + const std::string projSub = "C:/proj/sub/Shared.vcxitems"; + const std::string projRoot = "C:/proj/Shared.vcxitems"; + const std::string absPath = "C:/absolute/Shared.vcxitems"; + const std::string absInput = "C:/absolute/Shared.vcxitems"; +#else + const std::string projDir = "/proj/"; + const std::string projSub = "/proj/sub/Shared.vcxitems"; + const std::string projRoot = "/proj/Shared.vcxitems"; + const std::string absPath = "/absolute/Shared.vcxitems"; + const std::string absInput = "/absolute/Shared.vcxitems"; +#endif + // Relative path + ProjectDir → absolutised result + ASSERT_EQUALS(projSub, cppcheck::testing::resolveVcxitemsFilename("sub/Shared.vcxitems", projDir)); + ASSERT_EQUALS(projRoot, cppcheck::testing::resolveVcxitemsFilename("Shared.vcxitems", projDir)); + + // Already-absolute path must pass through unchanged regardless of ProjectDir + ASSERT_EQUALS(absPath, cppcheck::testing::resolveVcxitemsFilename(absInput, projDir)); + + // No ProjectDir → relative path is returned as-is + ASSERT_EQUALS("Shared.vcxitems", cppcheck::testing::resolveVcxitemsFilename("Shared.vcxitems", "")); + } + + void testMissingChildImportNonFatal() const { + // A .vcxproj that imports a non-existent .props file must still import + // successfully. Before the fix, result > NotResolvable caused errors.emplace_back() + // + return false, aborting the whole project. After the fix it is a debug warning + // and the remaining ClCompile items are still collected. + + // Use the current directory (no subdirectory) to avoid ScopedFile throwing + // "directory already exists" when the test is re-run without a clean build. + const ScopedFile mainCpp("testm3_main.cpp", ""); + const ScopedFile vcxproj( + "testm3.vcxproj", + "\n" + "\n" + " \n" + " \n" + " Debug\n" + " Win32\n" + " \n" + " \n" + " \n" + " Application\n" + " v143\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"); + + ImportProject project; + const ImportProject::Type result = project.import(vcxproj.path()); + + // Must succeed — missing child imports must not abort the project. + ASSERT_EQUALS(static_cast(ImportProject::Type::VS_VCXPROJ), static_cast(result)); + + // No hard errors — the missing import is demoted to a debug warning. + ASSERT(project.errors.empty()); + + // The source file must still have been collected despite the missing import. + const bool foundMain = std::any_of(project.fileSettings.begin(), project.fileSettings.end(), [](const FileSettings &fs) { + return fs.filename().find("main.cpp") != std::string::npos; + }); + ASSERT(foundMain); + } + + void testCurrentToolsVersionFromProps() const { + // L-3: "Current" keyword in relational conditions must use VisualStudioVersion + // from the properties map rather than a hardcoded major version number. + // + // evaluateVcxprojCondition uses an empty properties map, so VisualStudioVersion + // is absent and the fallback value { 18 } applies. These assertions verify + // the comparison logic is correct for the fallback case; an end-to-end + // .vcxproj test with set would be needed to exercise + // the property-lookup path directly. + + // fallback Current = MSBuildVersion::parse("18.0") = {18,0} (VS 2026) + // Now that the fallback is a two-component version "18.0", the equal-major + // comparisons work correctly without .NET single-component ambiguity. + // "Current" >= "18.0" -> {18,0} >= {18,0} -> true + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'Current' >= '18.0'", "Debug", "Win32")); + // "Current" <= "18.0" -> {18,0} <= {18,0} -> true + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'Current' <= '18.0'", "Debug", "Win32")); + // "Current" > "17.0" -> {18,0} > {17,0} -> true + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'Current' > '17.0'", "Debug", "Win32")); + // "17.0" < "Current" -> {17,0} < {18,0} -> true + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'17.0' < 'Current'", "Debug", "Win32")); + // "Current" < "19.0" -> {18,0} < {19,0} -> true + ASSERT(cppcheck::testing::evaluateVcxprojCondition("'Current' < '19.0'", "Debug", "Win32")); + // "Current" > "19.0" -> {18,0} > {19,0} -> false + ASSERT(!cppcheck::testing::evaluateVcxprojCondition("'Current' > '19.0'", "Debug", "Win32")); + } + + void testMetadataSelfReferenceCaseInsensitive() const { + // MSBuild item metadata names are case-insensitive (MetadataMap already + // uses cppcheck::stricmp for exactly this reason). Some .vcxproj/.props + // files use property-style accumulation in an ItemDefinitionGroup (e.g. + // $(PreprocessorDefinitions);EXTRA) + // instead of the %(...) item-metadata-reference syntax -- addMetadata() + // handles that "$(eName) self-reference" idiom by substituting in the + // metadata value accumulated so far. That substitution used to go through + // the plain, case-sensitive findAndReplace() instead of + // findAndReplaceCaseInsensitive() (used everywhere else in this file for + // exactly this kind of property/metadata name comparison), so a + // self-reference spelled with different casing than the element's own + // tag name -- e.g. $(preprocessordefinitions) inside a + // element -- was not recognized and was left + // in the output as literal, unresolved macro text instead of being + // replaced by the value accumulated from the preceding + // ItemDefinitionGroup. + // + // Two sequential ItemDefinitionGroup blocks exercise this: the first + // establishes the base value; the second references it back via + // $(preprocessordefinitions) in a different case than the + // tag itself. + + const ScopedFile mainCpp("testcasemeta_main.cpp", ""); + const ScopedFile vcxproj( + "testcasemeta.vcxproj", + "\n" + "\n" + " \n" + " \n" + " Debug\n" + " Win32\n" + " \n" + " \n" + " \n" + " Application\n" + " v143\n" + " \n" + " \n" + " \n" + " BASE_DEFINE\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " $(preprocessordefinitions);EXTRA_DEFINE\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"); + + ImportProject project; + const ImportProject::Type result = project.import(vcxproj.path()); + ASSERT_EQUALS(static_cast(ImportProject::Type::VS_VCXPROJ), static_cast(result)); + + const auto it = std::find_if(project.fileSettings.begin(), project.fileSettings.end(), [](const FileSettings &fs) { + return fs.filename().find("testcasemeta_main.cpp") != std::string::npos; + }); + ASSERT(it != project.fileSettings.end()); + + // Must end with the fully-resolved accumulation + // "BASE_DEFINE=1;EXTRA_DEFINE=1" -- not a leftover literal + // "$(preprocessordefinitions)" macro reference, which would mean the + // case-mismatched self-reference was never recognized and substituted. + // (fs.defines is prefixed with the toolset's own predefined macros -- + // e.g. _MSC_VER -- which this test does not otherwise care about.) + ASSERT(it->defines.find("$(preprocessordefinitions)") == std::string::npos); + const std::string expectedSuffix = ";BASE_DEFINE=1;EXTRA_DEFINE=1"; + ASSERT(it->defines.size() >= expectedSuffix.size()); + ASSERT_EQUALS(expectedSuffix, it->defines.substr(it->defines.size() - expectedSuffix.size())); } // TODO: test fsParseCommand() diff --git a/test/testincompletestatement.cpp b/test/testincompletestatement.cpp index 3d8f5d4d713..8e523e3d06e 100644 --- a/test/testincompletestatement.cpp +++ b/test/testincompletestatement.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -50,7 +50,7 @@ class TestIncompleteStatement : public TestFixture { ASSERT_LOC(tokenizer.simplifyTokens1(""), file, line); // Check for incomplete statements.. - CheckOther checkOther(&tokenizer, &settings1, this); + CheckOtherImpl checkOther(&tokenizer, settings1, *this); checkOther.checkIncompleteStatement(); } @@ -93,7 +93,7 @@ class TestIncompleteStatement : public TestFixture { "{\n" " const char def[] =\n" " \"abc\";\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -102,7 +102,7 @@ class TestIncompleteStatement : public TestFixture { check("void foo()\n" "{\n" " \"abc\";\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (warning) Redundant code: Found a statement that begins with string constant. [constStatement]\n", errout_str()); } @@ -111,7 +111,7 @@ class TestIncompleteStatement : public TestFixture { check("void foo()\n" "{\n" " const char *str[] = { \"abc\" };\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -125,7 +125,7 @@ class TestIncompleteStatement : public TestFixture { "\"more \"\n" "\"world\"\n" "};\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -134,7 +134,7 @@ class TestIncompleteStatement : public TestFixture { check("void foo()\n" "{\n" " 50;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (warning) Redundant code: Found a statement that begins with numeric constant. [constStatement]\n", errout_str()); } @@ -145,7 +145,7 @@ class TestIncompleteStatement : public TestFixture { " 1 == (two + three);\n" " 2 != (two + three);\n" " (one + two) != (two + three);\n" - "}"); + "}\n"); } void test7() { // #9335 @@ -160,7 +160,7 @@ class TestIncompleteStatement : public TestFixture { " for (C c(S); ; ) {\n" " (void)c;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -174,22 +174,38 @@ class TestIncompleteStatement : public TestFixture { " {\n" " { 346.1,114.1 }, { 347.1,111.1 }\n" " };\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } void void0() { // #6327 - check("void f() { (void*)0; }"); + check("#define assert(x) ((void)0)\n" + "void f(int* p) {\n" + " assert(p);\n" + "}\n"); ASSERT_EQUALS("", errout_str()); + check("void f() { (void*)0; }\n"); + ASSERT_EQUALS("[test.cpp:1:12]: (warning) Redundant code: Found unused cast in expression '(void*)0'. [constStatement]\n", errout_str()); + + check("void f() {\n" // #13148 + " static_cast(1);\n" + " static_cast(nullptr);\n" + " (void)NULL;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:2:22]: (warning) Redundant code: Found unused cast in expression 'static_cast(1)'. [constStatement]\n" + "[test.cpp:3:22]: (warning) Redundant code: Found unused cast in expression 'static_cast(nullptr)'. [constStatement]\n" + "[test.cpp:4:5]: (warning) Redundant code: Found unused cast in expression '(void)NULL'. [constStatement]\n", + errout_str()); + check("#define X 0\n" - "void f() { X; }"); + "void f() { X; }\n"); ASSERT_EQUALS("", errout_str()); } void intarray() { - check("int arr[] = { 100/2, 1*100 };"); + check("int arr[] = { 100/2, 1*100 };\n"); ASSERT_EQUALS("", errout_str()); } @@ -197,7 +213,7 @@ class TestIncompleteStatement : public TestFixture { check("struct st arr[] = {\n" " { 100/2, 1*100 }\n" " { 90, 70 }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -205,14 +221,14 @@ class TestIncompleteStatement : public TestFixture { check("struct st arr[] = {\n" " { 100/2, 1*100 }\n" " { 90, 70 }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } void conditionalcall() { check("void f() {\n" " 0==x ? X() : Y();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -220,7 +236,7 @@ class TestIncompleteStatement : public TestFixture { // #2462 - C++11 struct initialization check("void f() {\n" " ABC abc{1,2,3};\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #6260 - C++11 array initialization @@ -228,30 +244,30 @@ class TestIncompleteStatement : public TestFixture { " static const char* a[][2] {\n" " {\"b\", \"\"},\n" " };\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #2482 - false positive for empty struct - check("struct A {};"); + check("struct A {};\n"); ASSERT_EQUALS("", errout_str()); // #4387 - C++11 initializer list - check("A::A() : abc{0} {}"); + check("A::A() : abc{0} {}\n"); ASSERT_EQUALS("", errout_str()); // #5042 - C++11 initializer list - check("A::A() : abc::def{0} {}"); + check("A::A() : abc::def{0} {}\n"); ASSERT_EQUALS("", errout_str()); // #4503 - vector init - check("void f() { vector v{1}; }"); + check("void f() { vector v{1}; }\n"); ASSERT_EQUALS("", errout_str()); } void returnstruct() { check("struct s foo() {\n" " return (struct s){0,0};\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #4754 @@ -260,19 +276,19 @@ class TestIncompleteStatement : public TestFixture { " {\"hi\", \"there\"},\n" " {\"happy\", \"sad\"}\n" " };\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct s foo() {\n" " return (struct s){0};\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } void cast() { check("void f() {\n" " ((struct foo *)(0x1234))->xy = 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("bool f(const std::exception& e) {\n" // #10918 @@ -291,14 +307,14 @@ class TestIncompleteStatement : public TestFixture { check("void f() {\n" " int x = 1;\n" " x++, x++;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } void cpp11init() { check("void f() {\n" " int x{1};\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("std::vector f(int* p) {\n" @@ -310,7 +326,7 @@ class TestIncompleteStatement : public TestFixture { void cpp11init2() { check("x handlers{\n" " { \"mode2\", []() { return 2; } },\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -318,33 +334,33 @@ class TestIncompleteStatement : public TestFixture { check("struct A { void operator()(int); };\n" "void f() {\n" "A{}(0);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("template struct A { void operator()(int); };\n" "void f() {\n" "A{}(0);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } void block() { check("void f() {\n" " ({ do_something(); 0; });\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" "out:\n" " ({ do_something(); 0; });\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } void mapindex() { check("void f() {\n" " map[{\"1\",\"2\"}]=0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -352,7 +368,7 @@ class TestIncompleteStatement : public TestFixture { check("void foo(int,const char*,int);\n" // #8827 "void f(int value) {\n" " foo(42,\"test\",42),(value&42);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:22]: (warning) Found suspicious operator ',', result is not used. [constStatement]\n", errout_str()); check("int f() {\n" // #11257 @@ -432,22 +448,22 @@ class TestIncompleteStatement : public TestFixture { "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("[test.cpp:2:5]: (warning) Redundant code: Found a statement that begins with numeric constant. [constStatement]\n" "[test.cpp:3:6]: (warning) Redundant code: Found a statement that begins with numeric constant. [constStatement]\n" - "[test.cpp:4:5]: (warning) Redundant code: Found a statement that begins with numeric constant. [constStatement]\n" - "[test.cpp:5:6]: (warning) Redundant code: Found a statement that begins with numeric constant. [constStatement]\n" + "[test.cpp:4:5]: (warning) Redundant code: Found unused cast in expression '(char)1'. [constStatement]\n" + "[test.cpp:5:6]: (warning) Redundant code: Found unused cast in expression '(char)1'. [constStatement]\n" "[test.cpp:6:5]: (warning, inconclusive) Found suspicious operator '!', result is not used. [constStatement]\n" "[test.cpp:7:6]: (warning, inconclusive) Found suspicious operator '!', result is not used. [constStatement]\n" - "[test.cpp:8:5]: (warning) Redundant code: Found unused cast of expression '!x'. [constStatement]\n" + "[test.cpp:8:5]: (warning) Redundant code: Found unused cast in expression '(unsigned int)!x'. [constStatement]\n" "[test.cpp:9:5]: (warning, inconclusive) Found suspicious operator '~', result is not used. [constStatement]\n", errout_str()); - check("void f1(int x) { x; }", dinit(CheckOptions, $.inconclusive = true)); + check("void f1(int x) { x; }\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("[test.cpp:1:18]: (warning) Unused variable value 'x' [constStatement]\n", errout_str()); - check("void f() { if (Type t; g(t)) {} }"); // #9776 + check("void f() { if (Type t; g(t)) {} }\n"); // #9776 ASSERT_EQUALS("", errout_str()); - check("void f(int x) { static_cast(x); }"); - ASSERT_EQUALS("[test.cpp:1:38]: (warning) Redundant code: Found unused cast of expression 'x'. [constStatement]\n", errout_str()); + check("void f(int x) { static_cast(x); }\n"); + ASSERT_EQUALS("[test.cpp:1:38]: (warning) Redundant code: Found unused cast in expression 'static_cast(x)'. [constStatement]\n", errout_str()); check("void f(int x, int* p) {\n" " static_cast(x);\n" @@ -457,7 +473,7 @@ class TestIncompleteStatement : public TestFixture { "}\n"); ASSERT_EQUALS("", errout_str()); - check("void f() { false; }"); // #10856 + check("void f() { false; }\n"); // #10856 ASSERT_EQUALS("[test.cpp:1:12]: (warning) Redundant code: Found a statement that begins with bool constant. [constStatement]\n", errout_str()); check("void f(int i) {\n" @@ -465,9 +481,9 @@ class TestIncompleteStatement : public TestFixture { " static_cast((char)i);\n" " (char)static_cast(i);\n" "}\n"); - ASSERT_EQUALS("[test.cpp:2:5]: (warning) Redundant code: Found unused cast of expression 'i'. [constStatement]\n" - "[test.cpp:3:23]: (warning) Redundant code: Found unused cast of expression 'i'. [constStatement]\n" - "[test.cpp:4:5]: (warning) Redundant code: Found unused cast of expression 'i'. [constStatement]\n", + ASSERT_EQUALS("[test.cpp:2:5]: (warning) Redundant code: Found unused cast in expression '(float)(char)i'. [constStatement]\n" + "[test.cpp:3:23]: (warning) Redundant code: Found unused cast in expression 'static_cast((char)i)'. [constStatement]\n" + "[test.cpp:4:5]: (warning) Redundant code: Found unused cast in expression '(char)static_cast(i)'. [constStatement]\n", errout_str()); check("namespace M {\n" @@ -476,7 +492,7 @@ class TestIncompleteStatement : public TestFixture { "void f(int i) {\n" " (M::N::T)i;\n" "}\n"); - ASSERT_EQUALS("[test.cpp:5:5]: (warning) Redundant code: Found unused cast of expression 'i'. [constStatement]\n", errout_str()); + ASSERT_EQUALS("[test.cpp:5:5]: (warning) Redundant code: Found unused cast in expression '(T)i'. [constStatement]\n", errout_str()); check("void f(int (g)(int a, int b)) {\n" // #10873 " int p = 0, q = 1;\n" @@ -528,7 +544,7 @@ class TestIncompleteStatement : public TestFixture { " for (L\"y\"; ;) {}\n" "}\n"); ASSERT_EQUALS("[test.cpp:2:10]: (warning) Unused variable value 'i' [constStatement]\n" - "[test.cpp:3:10]: (warning) Redundant code: Found unused cast of expression 'i'. [constStatement]\n" + "[test.cpp:3:10]: (warning) Redundant code: Found unused cast in expression '(long)i'. [constStatement]\n" "[test.cpp:4:10]: (warning) Redundant code: Found a statement that begins with numeric constant. [constStatement]\n" "[test.cpp:5:10]: (warning) Redundant code: Found a statement that begins with bool constant. [constStatement]\n" "[test.cpp:6:10]: (warning) Redundant code: Found a statement that begins with character constant. [constStatement]\n" @@ -696,8 +712,8 @@ class TestIncompleteStatement : public TestFixture { " NULL;\n" " nullptr;\n" "}\n"); - ASSERT_EQUALS("[test.cpp:2:5]: (warning) Redundant code: Found a statement that begins with NULL constant. [constStatement]\n" - "[test.cpp:3:5]: (warning) Redundant code: Found a statement that begins with NULL constant. [constStatement]\n", + ASSERT_EQUALS("[test.cpp:2:5]: (warning) Redundant code: Found a statement that begins with null constant. [constStatement]\n" + "[test.cpp:3:5]: (warning) Redundant code: Found a statement that begins with null constant. [constStatement]\n", errout_str()); check("struct S { int i; };\n" // #6504 @@ -758,59 +774,64 @@ class TestIncompleteStatement : public TestFixture { "}\n"); ASSERT_EQUALS("[test.cpp:4:6]: (warning) Redundant code: Found unused array access. [constStatement]\n", errout_str()); + + check("int f(int i) {\n" // #14889 + " return i ? 8 : ({ int x = 2; x; });\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); } void vardecl() { // #8984 - check("void f() { a::b *c = d(); }", dinit(CheckOptions, $.inconclusive = true)); + check("void f() { a::b *c = d(); }\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); - check("void f() { std::vector *c; }", dinit(CheckOptions, $.inconclusive = true)); + check("void f() { std::vector *c; }\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); - check("void f() { a::b &c = d(); }", dinit(CheckOptions, $.inconclusive = true)); + check("void f() { a::b &c = d(); }\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); - check("void f() { std::vector &c; }", dinit(CheckOptions, $.inconclusive = true)); + check("void f() { std::vector &c; }\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); - check("void f() { a::b &&c = d(); }", dinit(CheckOptions, $.inconclusive = true)); + check("void f() { a::b &&c = d(); }\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); - check("void f() { std::vector &&c; }", dinit(CheckOptions, $.inconclusive = true)); + check("void f() { std::vector &&c; }\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); - check("void f() { char * const * a, * const * b; }", dinit(CheckOptions, $.inconclusive = true)); + check("void f() { char * const * a, * const * b; }\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); - check("void f() { char * const * a = 0, * volatile restrict * b; }", dinit(CheckOptions, $.inconclusive = true, $.cpp = false)); + check("void f() { char * const * a = 0, * volatile restrict * b; }\n", dinit(CheckOptions, $.inconclusive = true, $.cpp = false)); ASSERT_EQUALS("", errout_str()); - check("void f() { char * const * a = 0, * volatile const * b; }", dinit(CheckOptions, $.inconclusive = true)); + check("void f() { char * const * a = 0, * volatile const * b; }\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); } void archive() { check("void f(Archive &ar) {\n" " ar & x;\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); check("void f(int ar) {\n" " ar & x;\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("[test.cpp:2:6]: (warning, inconclusive) Found suspicious operator '&', result is not used. [constStatement]\n", errout_str()); } void ast() { - check("struct c { void a() const { for (int x=0; x;); } };", dinit(CheckOptions, $.inconclusive = true)); + check("struct c { void a() const { for (int x=0; x;); } };\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); } void oror() { check("void foo() {\n" " params_given (params, \"overrides\") || (overrides = \"1\");\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); check("void f(std::ifstream& file) {\n" // #10930 diff --git a/test/testinternal.cpp b/test/testinternal.cpp index 009129c5dc1..61c36a8e7dd 100644 --- a/test/testinternal.cpp +++ b/test/testinternal.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -56,8 +56,8 @@ class TestInternal : public TestFixture { SimpleTokenizer tokenizer(settings, *this); ASSERT_LOC(tokenizer.tokenize(code), file, line); - // Check.. - runChecks(tokenizer, this); + CheckInternal check; + runChecks(check, tokenizer, *this); } void simplePatternInTokenMatch() { diff --git a/test/testio.cpp b/test/testio.cpp index ec6a87a1e16..ad95345e1fd 100644 --- a/test/testio.cpp +++ b/test/testio.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -44,13 +44,16 @@ class TestIO : public TestFixture { TEST_CASE(fileIOwithoutPositioning); TEST_CASE(seekOnAppendedFile); TEST_CASE(fflushOnInputStream); + TEST_CASE(ftellCompatibility); TEST_CASE(incompatibleFileOpen); + TEST_CASE(testWrongfeofUsage); // #958 TEST_CASE(testScanf1); // Scanf without field limiters TEST_CASE(testScanf2); TEST_CASE(testScanf3); // #3494 TEST_CASE(testScanf4); // #ticket 2553 TEST_CASE(testScanf5); // #10632 + TEST_CASE(testScanf6); mNewTemplate = false; TEST_CASE(testScanfArgument); @@ -109,61 +112,62 @@ class TestIO : public TestFixture { // Check.. if (options.onlyFormatStr) { - CheckIO checkIO(&tokenizer, &settings1, this); + CheckIOImpl checkIO(&tokenizer, settings1, *this); checkIO.checkWrongPrintfScanfArguments(); return; } - runChecks(tokenizer, this); + CheckIO check; + runChecks(check, tokenizer, *this); } void coutCerrMisusage() { check( "void foo() {\n" " std::cout << std::cout;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:3]: (error) Invalid usage of output stream: '<< std::cout'. [coutCerrMisusage]\n", errout_str()); check( "void foo() {\n" " std::cout << (std::cout);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:3]: (error) Invalid usage of output stream: '<< std::cout'. [coutCerrMisusage]\n", errout_str()); check( "void foo() {\n" " std::cout << \"xyz\" << std::cout;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:3]: (error) Invalid usage of output stream: '<< std::cout'. [coutCerrMisusage]\n", errout_str()); check( "void foo(int i) {\n" " std::cout << i << std::cerr;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:3]: (error) Invalid usage of output stream: '<< std::cerr'. [coutCerrMisusage]\n", errout_str()); check( "void foo() {\n" " std::cout << \"xyz\";\n" " std::cout << \"xyz\";\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check( "void foo() {\n" " std::cout << std::cout.good();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check( "void foo() {\n" " unknownObject << std::cout;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check( "void foo() {\n" " MACRO(std::cout <<, << std::cout)\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -176,7 +180,7 @@ class TestIO : public TestFixture { " fread(buffer, 5, 6, f);\n" " rewind(f);\n" " fwrite(buffer, 5, 6, f);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:5]: (error) Write operation on a file that was opened only for reading. [writeReadOnlyFile]\n", errout_str()); check("void foo(FILE*& f) {\n" @@ -184,7 +188,7 @@ class TestIO : public TestFixture { " fread(buffer, 5, 6, f);\n" " rewind(f);\n" " fwrite(buffer, 5, 6, f);\n" - "}", dinit(CheckOptions, $.platform = Platform::Type::Win32W)); + "}\n", dinit(CheckOptions, $.platform = Platform::Type::Win32W)); ASSERT_EQUALS("[test.cpp:5:5]: (error) Write operation on a file that was opened only for reading. [writeReadOnlyFile]\n", errout_str()); check("void foo(FILE*& f) {\n" @@ -192,7 +196,7 @@ class TestIO : public TestFixture { " fread(buffer, 5, 6, f);\n" " rewind(f);\n" " fwrite(buffer, 5, 6, f);\n" - "}", dinit(CheckOptions, $.platform = Platform::Type::Win32A)); + "}\n", dinit(CheckOptions, $.platform = Platform::Type::Win32A)); ASSERT_EQUALS("[test.cpp:5:5]: (error) Write operation on a file that was opened only for reading. [writeReadOnlyFile]\n", errout_str()); check("void foo(FILE*& f) {\n" @@ -200,7 +204,7 @@ class TestIO : public TestFixture { " fread(buffer, 5, 6, f);\n" " rewind(f);\n" " fwrite(buffer, 5, 6, f);\n" - "}", dinit(CheckOptions, $.platform = Platform::Type::Win32W)); + "}\n", dinit(CheckOptions, $.platform = Platform::Type::Win32W)); ASSERT_EQUALS("[test.cpp:5:5]: (error) Write operation on a file that was opened only for reading. [writeReadOnlyFile]\n", errout_str()); check("void foo(FILE*& f) {\n" @@ -208,7 +212,7 @@ class TestIO : public TestFixture { " fread(buffer, 5, 6, f);\n" " rewind(f);\n" " fwrite(buffer, 5, 6, f);\n" - "}", dinit(CheckOptions, $.platform = Platform::Type::Win32W)); + "}\n", dinit(CheckOptions, $.platform = Platform::Type::Win32W)); ASSERT_EQUALS("[test.cpp:5:5]: (error) Write operation on a file that was opened only for reading. [writeReadOnlyFile]\n", errout_str()); check("void foo(FILE*& f) {\n" @@ -216,7 +220,7 @@ class TestIO : public TestFixture { " fread(buffer, 5, 6, f);\n" " rewind(f);\n" " fwrite(buffer, 5, 6, f);\n" - "}", dinit(CheckOptions, $.platform = Platform::Type::Win32A)); + "}\n", dinit(CheckOptions, $.platform = Platform::Type::Win32A)); ASSERT_EQUALS("[test.cpp:5:5]: (error) Write operation on a file that was opened only for reading. [writeReadOnlyFile]\n", errout_str()); check("void foo(FILE*& f) {\n" @@ -224,55 +228,55 @@ class TestIO : public TestFixture { " fread(buffer, 5, 6, f);\n" " rewind(f);\n" " fwrite(buffer, 5, 6, f);\n" - "}", dinit(CheckOptions, $.platform = Platform::Type::Win32W)); + "}\n", dinit(CheckOptions, $.platform = Platform::Type::Win32W)); ASSERT_EQUALS("[test.cpp:5:5]: (error) Write operation on a file that was opened only for reading. [writeReadOnlyFile]\n", errout_str()); check("void foo(FILE*& f) {\n" " f = fopen(name, \"r+\");\n" " fwrite(buffer, 5, 6, f);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(FILE*& f) {\n" " f = _wfopen(name, L\"r+\");\n" " fwrite(buffer, 5, 6, f);\n" - "}", dinit(CheckOptions, $.platform = Platform::Type::Win32W)); + "}\n", dinit(CheckOptions, $.platform = Platform::Type::Win32W)); ASSERT_EQUALS("", errout_str()); check("void foo(FILE*& f) {\n" " f = _tfopen(name, _T(\"r+\"));\n" " fwrite(buffer, 5, 6, f);\n" - "}", dinit(CheckOptions, $.platform = Platform::Type::Win32A)); + "}\n", dinit(CheckOptions, $.platform = Platform::Type::Win32A)); ASSERT_EQUALS("", errout_str()); check("void foo(FILE*& f) {\n" " f = _tfopen(name, _T(\"r+\"));\n" " fwrite(buffer, 5, 6, f);\n" - "}", dinit(CheckOptions, $.platform = Platform::Type::Win32W)); + "}\n", dinit(CheckOptions, $.platform = Platform::Type::Win32W)); ASSERT_EQUALS("", errout_str()); check("void foo(FILE*& f) {\n" " _wfopen_s(&f, name, L\"r+\");\n" " fwrite(buffer, 5, 6, f);\n" - "}", dinit(CheckOptions, $.platform = Platform::Type::Win32W)); + "}\n", dinit(CheckOptions, $.platform = Platform::Type::Win32W)); ASSERT_EQUALS("", errout_str()); check("void foo(FILE*& f) {\n" " _tfopen_s(&f, name, _T(\"r+\"));\n" " fwrite(buffer, 5, 6, f);\n" - "}", dinit(CheckOptions, $.platform = Platform::Type::Win32A)); + "}\n", dinit(CheckOptions, $.platform = Platform::Type::Win32A)); ASSERT_EQUALS("", errout_str()); check("void foo(FILE*& f) {\n" " _tfopen_s(&f, name, _T(\"r+\"));\n" " fwrite(buffer, 5, 6, f);\n" - "}", dinit(CheckOptions, $.platform = Platform::Type::Win32W)); + "}\n", dinit(CheckOptions, $.platform = Platform::Type::Win32W)); ASSERT_EQUALS("", errout_str()); check("void foo(FILE*& f) {\n" " f = tmpfile();\n" " fwrite(buffer, 5, 6, f);\n" - "}", dinit(CheckOptions, $.platform = Platform::Type::Win32W)); + "}\n", dinit(CheckOptions, $.platform = Platform::Type::Win32W)); ASSERT_EQUALS("", errout_str()); // Write mode @@ -281,19 +285,19 @@ class TestIO : public TestFixture { " fwrite(buffer, 5, 6, f);\n" " rewind(f);\n" " fread(buffer, 5, 6, f);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:5]: (error) Read operation on a file that was opened only for writing. [readWriteOnlyFile]\n", errout_str()); check("void foo(FILE*& f) {\n" " f = fopen(name, \"w+\");\n" " fread(buffer, 5, 6, f);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(FILE*& f) {\n" " f = tmpfile();\n" " fread(buffer, 5, 6, f);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Append mode @@ -302,14 +306,14 @@ class TestIO : public TestFixture { " fwrite(buffer, 5, 6, f);\n" " rewind(f);\n" " fread(buffer, 5, 6, f);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:5]: (warning) Repositioning operation performed on a file opened in append mode has no effect. [seekOnAppendedFile]\n" "[test.cpp:5:5]: (error) Read operation on a file that was opened only for writing. [readWriteOnlyFile]\n", errout_str()); check("void foo(FILE*& f) {\n" " f = fopen(name, \"a+\");\n" " fread(buffer, 5, 6, f);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Variable declared locally @@ -317,7 +321,7 @@ class TestIO : public TestFixture { " FILE* f = fopen(name, \"r\");\n" " fwrite(buffer, 5, 6, f);\n" " fclose(f);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (error) Write operation on a file that was opened only for reading. [writeReadOnlyFile]\n", errout_str()); // Call unknown function @@ -326,60 +330,60 @@ class TestIO : public TestFixture { " fwrite(buffer, 5, 6, f);\n" " bar(f);\n" " fread(buffer, 5, 6, f);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // freopen and tmpfile check("void foo(FILE*& f) {\n" " f = freopen(name, \"r\", f);\n" " fwrite(buffer, 5, 6, f);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (error) Write operation on a file that was opened only for reading. [writeReadOnlyFile]\n", errout_str()); check("void foo(FILE*& f) {\n" " f = _wfreopen(name, L\"r\", f);\n" " fwrite(buffer, 5, 6, f);\n" - "}", dinit(CheckOptions, $.platform = Platform::Type::Win32W)); + "}\n", dinit(CheckOptions, $.platform = Platform::Type::Win32W)); ASSERT_EQUALS("[test.cpp:3:5]: (error) Write operation on a file that was opened only for reading. [writeReadOnlyFile]\n", errout_str()); check("void foo(FILE*& f) {\n" " f = _tfreopen(name, _T(\"r\"), f);\n" " fwrite(buffer, 5, 6, f);\n" - "}", dinit(CheckOptions, $.platform = Platform::Type::Win32A)); + "}\n", dinit(CheckOptions, $.platform = Platform::Type::Win32A)); ASSERT_EQUALS("[test.cpp:3:5]: (error) Write operation on a file that was opened only for reading. [writeReadOnlyFile]\n", errout_str()); check("void foo(FILE*& f) {\n" " f = _tfreopen(name, _T(\"r\"), f);\n" " fwrite(buffer, 5, 6, f);\n" - "}", dinit(CheckOptions, $.platform = Platform::Type::Win32W)); + "}\n", dinit(CheckOptions, $.platform = Platform::Type::Win32W)); ASSERT_EQUALS("[test.cpp:3:5]: (error) Write operation on a file that was opened only for reading. [writeReadOnlyFile]\n", errout_str()); check("void foo(FILE*& f) {\n" " f = _wfreopen_s(&f, name, L\"r\", f);\n" " fwrite(buffer, 5, 6, f);\n" - "}", dinit(CheckOptions, $.platform = Platform::Type::Win32W)); + "}\n", dinit(CheckOptions, $.platform = Platform::Type::Win32W)); ASSERT_EQUALS("[test.cpp:3:5]: (error) Write operation on a file that was opened only for reading. [writeReadOnlyFile]\n", errout_str()); check("void foo(FILE*& f) {\n" " f = _tfreopen_s(&f, name, _T(\"r\"), f);\n" " fwrite(buffer, 5, 6, f);\n" - "}", dinit(CheckOptions, $.platform = Platform::Type::Win32A)); + "}\n", dinit(CheckOptions, $.platform = Platform::Type::Win32A)); ASSERT_EQUALS("[test.cpp:3:5]: (error) Write operation on a file that was opened only for reading. [writeReadOnlyFile]\n", errout_str()); check("void foo(FILE*& f) {\n" " f = _tfreopen_s(&f, name, _T(\"r\"), f);\n" " fwrite(buffer, 5, 6, f);\n" - "}", dinit(CheckOptions, $.platform = Platform::Type::Win32W)); + "}\n", dinit(CheckOptions, $.platform = Platform::Type::Win32W)); ASSERT_EQUALS("[test.cpp:3:5]: (error) Write operation on a file that was opened only for reading. [writeReadOnlyFile]\n", errout_str()); // Crash tests check("void foo(FILE*& f) {\n" " f = fopen(name, mode);\n" // No assertion failure (#3830) " fwrite(buffer, 5, 6, f);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); - check("void fopen(std::string const &filepath, std::string const &mode);"); // #3832 + check("void fopen(std::string const &filepath, std::string const &mode);\n"); // #3832 } void wrongMode_complex() { @@ -388,7 +392,7 @@ class TestIO : public TestFixture { " else f = fopen(name, \"r\");\n" " if(a) fwrite(buffer, 5, 6, f);\n" " else fread(buffer, 5, 6, f);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" @@ -397,14 +401,14 @@ class TestIO : public TestFixture { " else f = fopen(name, \"r\");\n" " if(a) fwrite(buffer, 5, 6, f);\n" " else fread(buffer, 5, 6, f);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" " FILE* f = fopen(name, \"w\");\n" " if(a) fwrite(buffer, 5, 6, f);\n" " else fread(buffer, 5, 6, f);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:11]: (error) Read operation on a file that was opened only for writing. [readWriteOnlyFile]\n", errout_str()); } @@ -417,7 +421,7 @@ class TestIO : public TestFixture { " ungetc('a', f);\n" " ungetwc(L'a', f);\n" " rewind(f);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (error) Used file that is not opened. [useClosedFile]\n" "[test.cpp:4:5]: (error) Used file that is not opened. [useClosedFile]\n" "[test.cpp:5:5]: (error) Used file that is not opened. [useClosedFile]\n" @@ -431,40 +435,40 @@ class TestIO : public TestFixture { " return;" " }\n" " fwrite(buffer, 5, 6, f);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(FILE*& f) {\n" " fclose(f);\n" " f = fopen(name, \"r\");\n" " fread(buffer, 5, 6, f);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(FILE*& f) {\n" " f = fopen(name, \"r\");\n" " f = g;\n" " fwrite(buffer, 5, 6, f);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" " FILE* f;\n" " fwrite(buffer, 5, 6, f);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (error) Used file that is not opened. [useClosedFile]\n", errout_str()); check("void foo() {\n" " FILE* f(stdout);\n" " fwrite(buffer, 5, 6, f);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" // #3965 " FILE* f[3];\n" " f[0] = fopen(name, mode);\n" " fclose(f[0]);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #4368: multiple functions @@ -487,7 +491,7 @@ class TestIO : public TestFixture { " dump();\n" " close();\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("static FILE *fp = NULL;\n" @@ -501,7 +505,7 @@ class TestIO : public TestFixture { "{\n" " fclose(fp);\n" " fprintf(fp, \"Here's the output.\\n\");\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:11:3]: (error) Used file that is not opened. [useClosedFile]\n", errout_str()); // #4466 @@ -514,7 +518,7 @@ class TestIO : public TestFixture { " fclose(infile);\n" " return;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void chdcd_parse_nero(FILE *infile) {\n" @@ -526,7 +530,7 @@ class TestIO : public TestFixture { " fclose(infile);\n" " return;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #4649 @@ -536,15 +540,37 @@ class TestIO : public TestFixture { " a.f2 = fopen(name,mode);\n" " fclose(a.f1);\n" " fclose(a.f2);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #1473 check("void foo() {\n" " FILE *a = fopen(\"aa\", \"r\");\n" " while (fclose(a)) {}\n" - "}"); - TODO_ASSERT_EQUALS("[test.cpp:3:5]: (error) Used file that is not opened. [useClosedFile]\n", "", errout_str()); + "}\n"); + ASSERT_EQUALS("[test.cpp:3:12]: (warning) fclose() used as loop condition may skip loop body or double-close file handle. [fcloseInLoopCondition]\n", errout_str()); + + check("void foo() {\n" + " FILE *a = fopen(\"aa\", \"r\");\n" + " while (fclose(a)) {\n" + " break;\n" + " }\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("void foo() {\n" + " FILE *a = fopen(\"aa\", \"r\");\n" + " while (fclose(a)) {\n" + " a = fopen(\"aa\", \"r\");\n" + " }\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("void foo() {\n" + " FILE *a = fopen(\"aa\", \"r\");\n" + " do {} while (fclose(a));\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:3:18]: (warning) fclose() used as loop condition may skip loop body or double-close file handle. [fcloseInLoopCondition]\n", errout_str()); // #6823 check("void foo() {\n" @@ -553,7 +579,7 @@ class TestIO : public TestFixture { " f[1] = fopen(\"2\", \"w\");\n" " fclose(f[0]);\n" " fclose(f[1]);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #12236 @@ -569,13 +595,13 @@ class TestIO : public TestFixture { check("void foo(FILE* f) {\n" " fwrite(buffer, 5, 6, f);\n" " fread(buffer, 5, 6, f);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (error) Read and write operations without a call to a positioning function (fseek, fsetpos or rewind) or fflush in between result in undefined behaviour. [IOWithoutPositioning]\n", errout_str()); check("void foo(FILE* f) {\n" " fread(buffer, 5, 6, f);\n" " fwrite(buffer, 5, 6, f);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (error) Read and write operations without a call to a positioning function (fseek, fsetpos or rewind) or fflush in between result in undefined behaviour. [IOWithoutPositioning]\n", errout_str()); check("void foo(FILE* f, bool read) {\n" @@ -583,42 +609,42 @@ class TestIO : public TestFixture { " fread(buffer, 5, 6, f);\n" " else\n" " fwrite(buffer, 5, 6, f);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(FILE* f) {\n" " fread(buffer, 5, 6, f);\n" " fflush(f);\n" " fwrite(buffer, 5, 6, f);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(FILE* f) {\n" " fread(buffer, 5, 6, f);\n" " rewind(f);\n" " fwrite(buffer, 5, 6, f);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(FILE* f) {\n" " fread(buffer, 5, 6, f);\n" " fsetpos(f, pos);\n" " fwrite(buffer, 5, 6, f);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(FILE* f) {\n" " fread(buffer, 5, 6, f);\n" " fseek(f, 0, SEEK_SET);\n" " fwrite(buffer, 5, 6, f);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(FILE* f) {\n" " fread(buffer, 5, 6, f);\n" " long pos = ftell(f);\n" " fwrite(buffer, 5, 6, f);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:5]: (error) Read and write operations without a call to a positioning function (fseek, fsetpos or rewind) or fflush in between result in undefined behaviour. [IOWithoutPositioning]\n", errout_str()); // #6452 - member functions @@ -631,7 +657,7 @@ class TestIO : public TestFixture { " int bytesRead = fread(aboutToOverwrite.data(), 1, bufferLength, d->file);\n" " seek(writePosition);\n" " fwrite(buffer.data(), sizeof(char), buffer.size(), d->file);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("class FileStream {\n" @@ -642,7 +668,7 @@ class TestIO : public TestFixture { " int bytesRead = fread(aboutToOverwrite.data(), 1, bufferLength, d->file);\n" " unknown(writePosition);\n" " fwrite(buffer.data(), sizeof(char), buffer.size(), d->file);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("class FileStream {\n" @@ -654,7 +680,7 @@ class TestIO : public TestFixture { " int bytesRead = fread(aboutToOverwrite.data(), 1, bufferLength, d->file);\n" " known(writePosition);\n" " fwrite(buffer.data(), sizeof(char), buffer.size(), d->file);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:9:5]: (error) Read and write operations without a call to a positioning function (fseek, fsetpos or rewind) or fflush in between result in undefined behaviour. [IOWithoutPositioning]\n", errout_str()); check("class FileStream {\n" @@ -666,33 +692,50 @@ class TestIO : public TestFixture { " int bytesRead = fread(X::data(), 1, bufferLength, d->file);\n" " known(writePosition);\n" " fwrite(X::data(), sizeof(char), buffer.size(), d->file);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:9:5]: (error) Read and write operations without a call to a positioning function (fseek, fsetpos or rewind) or fflush in between result in undefined behaviour. [IOWithoutPositioning]\n", errout_str()); + + check("struct MemStream {\n" + " char buf[1024];\n" + " int pos = 0;\n" + " void fwrite(const void *ptr, size_t n) { memcpy(buf + pos, ptr, n); pos += (int)n; }\n" + "};\n" + "struct FileStream {\n" + " FILE *fp;\n" + " size_t _fread(void *ptr, size_t n) { return ::fread(ptr, 1, n, fp); }\n" + " size_t fread(void *ptr, size_t n) { return _fread(ptr, n); }\n" + " void copy_to(MemStream *dst, size_t n) {\n" + " char tmp[256];\n" + " fread(tmp, n);\n" + " dst->fwrite(tmp, n);\n" + " }\n" + "};\n"); + ASSERT_EQUALS("", errout_str()); } void seekOnAppendedFile() { check("void foo() {\n" " FILE* f = fopen(\"\", \"a+\");\n" " fseek(f, 0, SEEK_SET);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" " FILE* f = fopen(\"\", \"w\");\n" " fseek(f, 0, SEEK_SET);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" " FILE* f = fopen(\"\", \"a\");\n" " fseek(f, 0, SEEK_SET);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (warning) Repositioning operation performed on a file opened in append mode has no effect. [seekOnAppendedFile]\n", errout_str()); check("void foo() {\n" " FILE* f = fopen(\"\", \"a\");\n" " fflush(f);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #5578 check("void foo() {\n" @@ -700,38 +743,54 @@ class TestIO : public TestFixture { " fclose(f);\n" " f = fopen(\"\", \"r\");\n" " fseek(f, 0, SEEK_SET);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #6566 } + void ftellCompatibility() { + + check("void foo() {\n" + " FILE *f = fopen(\"\", \"rt\");\n" + " if (f)\n" + " {\n" + " extern long position;\n" + " fseek(f, 0, SEEK_END);\n" + " position = ftell(f);\n" + " fclose(f);\n" + " }\n" + "}\n", dinit(CheckOptions, $.portability = true)); + ASSERT_EQUALS("[test.cpp:7:21]: (portability) ftell() result is unspecified when file is opened in mode \"t\" [ftellTextModeFile]\n", errout_str()); + } + + void fflushOnInputStream() { check("void foo()\n" "{\n" " fflush(stdin);\n" - "}", dinit(CheckOptions, $.portability = true)); + "}\n", dinit(CheckOptions, $.portability = true)); ASSERT_EQUALS("[test.cpp:3:5]: (portability) fflush() called on input stream 'stdin' may result in undefined behaviour on non-linux systems. [fflushOnInputStream]\n", errout_str()); check("void foo()\n" "{\n" " fflush(stdout);\n" - "}", dinit(CheckOptions, $.portability = true)); + "}\n", dinit(CheckOptions, $.portability = true)); ASSERT_EQUALS("", errout_str()); check("void foo(FILE*& f) {\n" " f = fopen(path, \"r\");\n" " fflush(f);\n" - "}", dinit(CheckOptions, $.portability = true)); + "}\n", dinit(CheckOptions, $.portability = true)); ASSERT_EQUALS("[test.cpp:3:5]: (portability) fflush() called on input stream 'f' may result in undefined behaviour on non-linux systems. [fflushOnInputStream]\n", errout_str()); check("void foo(FILE*& f) {\n" " f = fopen(path, \"w\");\n" " fflush(f);\n" - "}", dinit(CheckOptions, $.portability = true)); + "}\n", dinit(CheckOptions, $.portability = true)); ASSERT_EQUALS("", errout_str()); check("void foo(FILE*& f) {\n" " fflush(f);\n" - "}", dinit(CheckOptions, $.portability = true)); + "}\n", dinit(CheckOptions, $.portability = true)); ASSERT_EQUALS("", errout_str()); } @@ -739,10 +798,59 @@ class TestIO : public TestFixture { check("void foo() {\n" " FILE *f1 = fopen(\"tmp\", \"wt\");\n" " FILE *f2 = fopen(\"tmp\", \"rt\");\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:16]: (warning) The file '\"tmp\"' is opened for read and write access at the same time on different streams [incompatibleFileOpen]\n", errout_str()); } + void testWrongfeofUsage() { // ticket #958 + check("void foo(FILE * fp) {\n" + " while (!feof(fp)) \n" + " {\n" + " char line[100];\n" + " fgets(line, sizeof(line), fp);\n" + " dostuff(line);\n" + " }\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:2:10]: (warning) Using feof() as a loop condition causes the last line to be processed twice. [wrongfeofUsage]\n", errout_str()); + + check("int foo(FILE *fp) {\n" + " char line[100];\n" + " while (fgets(line, sizeof(line), fp)) {}\n" + " if (!feof(fp))\n" + " return 1;\n" + " return 0;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("void foo(FILE *fp){\n" + " char line[100];\n" + " fgets(line, sizeof(line), fp);\n" + " while (!feof(fp)){\n" + " dostuff(line);\n" + " fgets(line, sizeof(line), fp);" + " }\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("void foo(FILE *fp) {\n" + " char line[100];\n" + " do {\n" + " fgets(line, sizeof(line), fp);\n" + " dostuff(line);\n" + " } while (!feof(fp));\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:6:12]: (warning) Using feof() as a loop condition causes the last line to be processed twice. [wrongfeofUsage]\n", errout_str()); + + check("void foo(FILE *fp) {\n" + " char line[100];\n" + " do {\n" + " dostuff(line);\n" + " fgets(line, sizeof(line), fp);\n" + " } while (!feof(fp));\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + } + void testScanf1() { check("void foo() {\n" @@ -754,7 +862,7 @@ class TestIO : public TestFixture { " sscanf(foo, \"%[^~]\", bar);\n" " scanf(\"%dx%s\", &b, bar);\n" " fclose(file);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:9]: (warning) fscanf() without field width limits can crash with huge input data. [invalidscanf]\n" "[test.cpp:5:9]: (warning) scanf() without field width limits can crash with huge input data. [invalidscanf]\n" "[test.cpp:6:9]: (warning) scanf() without field width limits can crash with huge input data. [invalidscanf]\n" @@ -770,7 +878,7 @@ class TestIO : public TestFixture { " scanf(\"aa%d\", &a);\n" // No %s " scanf(\"aa%ld\", &a);\n" // No %s " scanf(\"%*[^~]\");\n" // Ignore input - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:5]: (warning) scanf format string requires 0 parameters but 1 is given. [wrongPrintfScanfArgNum]\n", errout_str()); } @@ -780,7 +888,7 @@ class TestIO : public TestFixture { " scanf(\"%7c\", str);\n" " scanf(\"%8c\", str);\n" " scanf(\"%9c\", str);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:3]: (error) Width 9 given in format string (no. 1) is larger than destination buffer 'str[8]', use %8c to prevent overflowing it. [invalidScanfFormatWidth]\n", errout_str()); } @@ -789,7 +897,7 @@ class TestIO : public TestFixture { "{\n" " char str [8];\n" " scanf (\"%70s\",str);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:3]: (error) Width 70 given in format string (no. 1) is larger than destination buffer 'str[8]', use %7s to prevent overflowing it. [invalidScanfFormatWidth]\n", errout_str()); } @@ -797,14 +905,22 @@ class TestIO : public TestFixture { check("char s1[42], s2[42];\n" "void test() {\n" " scanf(\"%42s%42[a-z]\", s1, s2);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (error) Width 42 given in format string (no. 1) is larger than destination buffer 's1[42]', use %41s to prevent overflowing it. [invalidScanfFormatWidth]\n" "[test.cpp:3:5]: (error) Width 42 given in format string (no. 2) is larger than destination buffer 's2[42]', use %41[a-z] to prevent overflowing it. [invalidScanfFormatWidth]\n", errout_str()); } + void testScanf6() { + ASSERT_NO_THROW(check("int f(const char *p) {\n" + " char a[3];\n" + " return sscanf(p, \"%02s\", a);\n" + "}\n")); + ASSERT_EQUALS("", errout_str()); + } + #define TEST_SCANF_CODE(format, type) \ - "void f(){" type " x; scanf(\"" format "\", &x);}" + "void f(){" type " x; scanf(\"" format "\", &x);}\n" #define TEST_SCANF_ERR(format, formatStr, type) \ "[test.c:1]: (warning) " format " in format string (no. 1) requires '" formatStr " *' but the argument type is '" type " *'.\n" @@ -819,7 +935,7 @@ class TestIO : public TestFixture { TEST_SCANF_ERR_AKA_("test.cpp", format, formatStr, type, akaType) #define TEST_PRINTF_CODE(format, type) \ - "void f(" type " x){printf(\"" format "\", x);}" + "void f(" type " x){printf(\"" format "\", x);}\n" #define TEST_PRINTF_ERR(format, requiredType, actualType) \ "[test.c:1]: (warning) " format " in format string (no. 1) requires '" requiredType "' but the argument type is '" actualType "'.\n" @@ -958,7 +1074,7 @@ class TestIO : public TestFixture { " scanf(\"%*1x %1x %29s\", &count, KeyName);\n" // #3373 " fscanf(f, \"%7ms\", &ref);\n" // #3461 " sscanf(ip_port, \"%*[^:]:%4d\", &port);\n" // #3468 - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" @@ -966,7 +1082,7 @@ class TestIO : public TestFixture { " scanf(\"%1d\", &foo, &bar);\n" " fscanf(bar, \"%1d\", &foo, &bar);\n" " scanf(\"%*1x %1x %29s\", &count, KeyName, foo);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) scanf format string requires 0 parameters but 1 is given.\n" "[test.cpp:3]: (warning) scanf format string requires 1 parameter but 2 are given.\n" "[test.cpp:4]: (warning) fscanf format string requires 1 parameter but 2 are given.\n" @@ -977,7 +1093,7 @@ class TestIO : public TestFixture { " scanf(\"%1u%1u\", bar());\n" " sscanf(bar, \"%1d%1d\", &foo);\n" " scanf(\"%*1x %1x %29s\", &count);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2]: (error) scanf format string requires 1 parameter but only 0 are given.\n" "[test.cpp:3]: (error) scanf format string requires 2 parameters but only 1 is given.\n" "[test.cpp:4]: (error) sscanf format string requires 2 parameters but only 1 is given.\n" @@ -989,7 +1105,7 @@ class TestIO : public TestFixture { " sscanf(input, \"%3s\", output);\n" " sscanf(input, \"%4s\", output);\n" " sscanf(input, \"%5s\", output);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6]: (error) Width 5 given in format string (no. 1) is larger than destination buffer 'output[5]', use %4s to prevent overflowing it.\n", errout_str()); check("void foo() {\n" @@ -999,7 +1115,7 @@ class TestIO : public TestFixture { " sscanf(input, \"%3s\", output);\n" " sscanf(input, \"%4s\", output);\n" " sscanf(input, \"%5s\", output);\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("[test.cpp:5]: (warning, inconclusive) Width 3 given in format string (no. 1) is smaller than destination buffer 'output[5]'.\n" "[test.cpp:7]: (error) Width 5 given in format string (no. 1) is larger than destination buffer 'output[5]', use %4s to prevent overflowing it.\n" "[test.cpp:4]: (warning) sscanf() without field width limits can crash with huge input data.\n", errout_str()); @@ -1011,7 +1127,7 @@ class TestIO : public TestFixture { " bufT projectId= {0};\n" " const int scanrc=sscanf(line, \"Project(\\\"{%36s}\\\")\", projectId);\n" " sscanf(input, \"%5s\", output);\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("[test.cpp:6]: (warning, inconclusive) Width 36 given in format string (no. 1) is smaller than destination buffer 'projectId[2048]'.\n", errout_str()); check("void foo(unsigned int i) {\n" @@ -1024,7 +1140,7 @@ class TestIO : public TestFixture { " scanf(\"%t\", &i);\n" " scanf(\"%L\", &i);\n" " scanf(\"%I\", &i);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) 'h' in format string (no. 1) is a length modifier and cannot be used without a conversion specifier.\n" "[test.cpp:3]: (warning) 'hh' in format string (no. 1) is a length modifier and cannot be used without a conversion specifier.\n" "[test.cpp:4]: (warning) 'l' in format string (no. 1) is a length modifier and cannot be used without a conversion specifier.\n" @@ -1080,13 +1196,13 @@ class TestIO : public TestFixture { check("void foo() {\n" " scanf(\"%u\", \"s3\");\n" " scanf(\"%u\", L\"s5W\");\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("[test.cpp:2]: (warning) %u in format string (no. 1) requires 'unsigned int *' but the argument type is 'const char *'.\n" "[test.cpp:3]: (warning) %u in format string (no. 1) requires 'unsigned int *' but the argument type is 'const wchar_t *'.\n", errout_str()); check("void foo(long l) {\n" " scanf(\"%u\", l);\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("[test.cpp:2]: (warning) %u in format string (no. 1) requires 'unsigned int *' but the argument type is 'signed long'.\n", errout_str()); TEST_SCANF_WARN("%lu","unsigned long","bool"); @@ -1468,13 +1584,13 @@ class TestIO : public TestFixture { check("void foo() {\n" " scanf(\"%Ld\", \"s3\");\n" " scanf(\"%Ld\", L\"s5W\");\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("[test.cpp:2]: (warning) %Ld in format string (no. 1) requires 'long long *' but the argument type is 'const char *'.\n" "[test.cpp:3]: (warning) %Ld in format string (no. 1) requires 'long long *' but the argument type is 'const wchar_t *'.\n", errout_str()); check("void foo(int i) {\n" " scanf(\"%Ld\", i);\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("[test.cpp:2]: (warning) %Ld in format string (no. 1) requires 'long long *' but the argument type is 'signed int'.\n", errout_str()); TEST_SCANF_WARN("%ju", "uintmax_t", "bool"); @@ -1945,13 +2061,13 @@ class TestIO : public TestFixture { check("void foo() {\n" " scanf(\"%d\", \"s3\");\n" " scanf(\"%d\", L\"s5W\");\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("[test.cpp:2]: (warning) %d in format string (no. 1) requires 'int *' but the argument type is 'const char *'.\n" "[test.cpp:3]: (warning) %d in format string (no. 1) requires 'int *' but the argument type is 'const wchar_t *'.\n", errout_str()); check("void foo(long l) {\n" " scanf(\"%d\", l);\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("[test.cpp:2]: (warning) %d in format string (no. 1) requires 'int *' but the argument type is 'signed long'.\n", errout_str()); TEST_SCANF_WARN("%x", "unsigned int", "bool"); @@ -1989,13 +2105,13 @@ class TestIO : public TestFixture { check("void foo() {\n" " scanf(\"%x\", \"s3\");\n" " scanf(\"%x\", L\"s5W\");\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("[test.cpp:2]: (warning) %x in format string (no. 1) requires 'unsigned int *' but the argument type is 'const char *'.\n" "[test.cpp:3]: (warning) %x in format string (no. 1) requires 'unsigned int *' but the argument type is 'const wchar_t *'.\n", errout_str()); check("void foo(long l) {\n" " scanf(\"%x\", l);\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("[test.cpp:2]: (warning) %x in format string (no. 1) requires 'unsigned int *' but the argument type is 'signed long'.\n", errout_str()); TEST_SCANF_WARN("%f", "float", "bool"); @@ -2029,13 +2145,13 @@ class TestIO : public TestFixture { check("void foo() {\n" " scanf(\"%f\", \"s3\");\n" " scanf(\"%f\", L\"s5W\");\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("[test.cpp:2]: (warning) %f in format string (no. 1) requires 'float *' but the argument type is 'const char *'.\n" "[test.cpp:3]: (warning) %f in format string (no. 1) requires 'float *' but the argument type is 'const wchar_t *'.\n", errout_str()); check("void foo(float f) {\n" " scanf(\"%f\", f);\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("[test.cpp:2]: (warning) %f in format string (no. 1) requires 'float *' but the argument type is 'float'.\n", errout_str()); TEST_SCANF_WARN("%lf", "double", "bool"); @@ -2125,13 +2241,13 @@ class TestIO : public TestFixture { check("void foo() {\n" " scanf(\"%n\", \"s3\");\n" " scanf(\"%n\", L\"s5W\");\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("[test.cpp:2]: (warning) %n in format string (no. 1) requires 'int *' but the argument type is 'const char *'.\n" "[test.cpp:3]: (warning) %n in format string (no. 1) requires 'int *' but the argument type is 'const wchar_t *'.\n", errout_str()); check("void foo(long l) {\n" " scanf(\"%n\", l);\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("[test.cpp:2]: (warning) %n in format string (no. 1) requires 'int *' but the argument type is 'signed long'.\n", errout_str()); check("void g() {\n" // #5104 @@ -2145,7 +2261,7 @@ class TestIO : public TestFixture { " scanf(\"%lf\",&v4[0]);\n" " myvector v5(1);\n" " scanf(\"%10s\",v5[0]);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); { @@ -2177,7 +2293,7 @@ class TestIO : public TestFixture { check("void g() {\n" " const char c[]=\"42\";\n" " scanf(\"%s\", c);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3]: (warning) %s in format string (no. 1) requires a 'char *' but the argument type is 'const char *'.\n" "[test.cpp:3]: (warning) scanf() without field width limits can crash with huge input data.\n", errout_str()); } @@ -2187,7 +2303,7 @@ class TestIO : public TestFixture { "}\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %i in format string (no. 1) requires 'int *' but the argument type is 'const char *'.\n", errout_str()); - check("struct S { unsigned char a[1]; };" // #14302 + check("struct S { unsigned char a[1]; };\n" // #14302 "void f(S s) {\n" " scanf(\"%hhu\", s.a);\n" "}\n"); @@ -2205,7 +2321,7 @@ class TestIO : public TestFixture { " snprintf(str,10,\"%u%s\");\n" " sprintf(string1, \"%-*.*s\", 32, string2);\n" // #3364 " snprintf(a, 9, \"%s%d\", \"11223344\");\n" // #3655 - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2]: (error) printf format string requires 1 parameter but only 0 are given.\n" "[test.cpp:3]: (error) printf format string requires 2 parameters but only 1 is given.\n" "[test.cpp:4]: (error) printf format string requires 3 parameters but only 2 are given.\n" @@ -2220,7 +2336,7 @@ class TestIO : public TestFixture { " printf(\"\", 0);\n" " printf(\"%i\", 123, bar());\n" " printf(\"%i%s\", 0, bar(), 43123);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) printf format string requires 0 parameters but 1 is given.\n" "[test.cpp:3]: (warning) printf format string requires 1 parameter but 2 are given.\n" "[test.cpp:4]: (warning) printf format string requires 2 parameters but 3 are given.\n", errout_str()); @@ -2230,7 +2346,7 @@ class TestIO : public TestFixture { " swprintf(string1, L\"%s%s\", L\"a\", string2);\n" // MSVC implementation " swprintf(string1, 6, L\"%i\", 32, string2);\n" // Standard implementation " swprintf(string1, 6, L\"%i%s\", 32, string2);\n" // Standard implementation - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) swprintf format string requires 1 parameter but 2 are given.\n" "[test.cpp:4]: (warning) swprintf format string requires 1 parameter but 2 are given.\n", errout_str()); @@ -2247,7 +2363,7 @@ class TestIO : public TestFixture { " printf(\"string: %.*s\", len, string);\n" // #3311 " fprintf(stderr, \"%*cText.\", indent, ' ');\n" // #3313 " sprintf(string1, \"%*\", 32);\n" // #3364 - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(char* s, const char* s2, std::string s3, int i) {\n" @@ -2257,7 +2373,7 @@ class TestIO : public TestFixture { " printf(\"%s\", s3);\n" " printf(\"%s\", \"s4\");\n" " printf(\"%u\", s);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3]: (warning) %s in format string (no. 1) requires 'char *' but the argument type is 'signed int'.\n" "[test.cpp:4]: (warning) %s in format string (no. 2) requires 'char *' but the argument type is 'signed int'.\n" "[test.cpp:5]: (warning) %s in format string (no. 1) requires 'char *' but the argument type is 'std::string'.\n" @@ -2270,7 +2386,7 @@ class TestIO : public TestFixture { " printf(\"%jo\", s3);\n" " printf(\"%jx\", i);\n" " printf(\"%jX\", i);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %jd in format string (no. 1) requires 'intmax_t' but the argument type is 'char *'.\n" "[test.cpp:3]: (warning) %ji in format string (no. 1) requires 'intmax_t' but the argument type is 'char *'.\n" "[test.cpp:4]: (warning) %ju in format string (no. 1) requires 'uintmax_t' but the argument type is 'const char *'.\n" @@ -2284,7 +2400,7 @@ class TestIO : public TestFixture { " printf(\"%jd\", ui);\n" " printf(\"%jd\", s3);\n" " printf(\"%jd\", i);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3]: (warning) %ju in format string (no. 1) requires 'uintmax_t' but the argument type is 'unsigned int'.\n" "[test.cpp:4]: (warning) %jd in format string (no. 1) requires 'intmax_t' but the argument type is 'unsigned int'.\n" "[test.cpp:5]: (warning) %jd in format string (no. 1) requires 'intmax_t' but the argument type is 'std::string'.\n" @@ -2297,7 +2413,7 @@ class TestIO : public TestFixture { " printf(\"%n\", pi);\n" " printf(\"%n\", s);\n" " printf(\"%n\", \"s4\");\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3]: (warning) %n in format string (no. 1) requires 'int *' but the argument type is 'signed int'.\n" "[test.cpp:4]: (warning) %n in format string (no. 1) requires 'int *' but the argument type is 'signed int'.\n" "[test.cpp:6]: (warning) %n in format string (no. 1) requires 'int *' but the argument type is 'std::string'.\n" @@ -2305,7 +2421,7 @@ class TestIO : public TestFixture { check("void foo() {\n" " printf(\"%n\", L\"s5W\");\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %n in format string (no. 1) requires 'int *' but the argument type is 'const wchar_t *'.\n", errout_str()); check("class foo {};\n" @@ -2318,7 +2434,7 @@ class TestIO : public TestFixture { " printf(\"%X\", bp);\n" " printf(\"%X\", u);\n" " printf(\"%X\", i);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3]: (warning) %X in format string (no. 1) requires 'unsigned int' but the argument type is 'foo'.\n" "[test.cpp:4]: (warning) %c in format string (no. 1) requires 'unsigned int' but the argument type is 'const char *'.\n" "[test.cpp:5]: (warning) %o in format string (no. 1) requires 'unsigned int' but the argument type is 'double'.\n" @@ -2329,7 +2445,7 @@ class TestIO : public TestFixture { "void foo(const char* cpc, char* pc) {\n" " printf(\"%x\", cpc);\n" " printf(\"%x\", pc);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3]: (warning) %x in format string (no. 1) requires 'unsigned int' but the argument type is 'const char *'.\n" "[test.cpp:4]: (warning) %x in format string (no. 1) requires 'unsigned int' but the argument type is 'char *'.\n", errout_str()); @@ -2339,7 +2455,7 @@ class TestIO : public TestFixture { " printf(\"%X\", L\"s5W\");\n" " printf(\"%c\", L\"s5W\");\n" " printf(\"%o\", L\"s5W\");\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3]: (warning) %x in format string (no. 1) requires 'unsigned int' but the argument type is 'const wchar_t *'.\n" "[test.cpp:4]: (warning) %X in format string (no. 1) requires 'unsigned int' but the argument type is 'const wchar_t *'.\n" "[test.cpp:5]: (warning) %c in format string (no. 1) requires 'unsigned int' but the argument type is 'const wchar_t *'.\n" @@ -2355,7 +2471,7 @@ class TestIO : public TestFixture { " printf(\"%i\", b);\n" " printf(\"%i\", bp);\n" " printf(\"%i\", uc);\n" // char is smaller than int, so there shouldn't be a problem - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3]: (warning) %i in format string (no. 1) requires 'int' but the argument type is 'foo'.\n" "[test.cpp:4]: (warning) %d in format string (no. 1) requires 'int' but the argument type is 'const char *'.\n" "[test.cpp:5]: (warning) %d in format string (no. 1) requires 'int' but the argument type is 'double'.\n" @@ -2367,7 +2483,7 @@ class TestIO : public TestFixture { "void foo() {\n" " printf(\"%i\", L\"s5W\");\n" " printf(\"%d\", L\"s5W\");\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3]: (warning) %i in format string (no. 1) requires 'int' but the argument type is 'const wchar_t *'.\n" "[test.cpp:4]: (warning) %d in format string (no. 1) requires 'int' but the argument type is 'const wchar_t *'.\n", errout_str()); @@ -2381,7 +2497,7 @@ class TestIO : public TestFixture { " printf(\"%u\", b);\n" " printf(\"%u\", bp);\n" " printf(\"%u\", bo);\n" // bool shouldn't have a negative sign - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3]: (warning) %u in format string (no. 1) requires 'unsigned int' but the argument type is 'foo'.\n" "[test.cpp:4]: (warning) %u in format string (no. 1) requires 'unsigned int' but the argument type is 'const char *'.\n" "[test.cpp:5]: (warning) %u in format string (no. 1) requires 'unsigned int' but the argument type is 'double'.\n" @@ -2392,7 +2508,7 @@ class TestIO : public TestFixture { check("class foo {};\n" "void foo(const int* cpi, foo f, bar b, bar* bp, double d, int i, bool bo) {\n" " printf(\"%u\", L\"s5W\");\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3]: (warning) %u in format string (no. 1) requires 'unsigned int' but the argument type is 'const wchar_t *'.\n", errout_str()); check("class foo {};\n" @@ -2402,7 +2518,7 @@ class TestIO : public TestFixture { " printf(\"%p\", bp);\n" " printf(\"%p\", cpi);\n" " printf(\"%p\", b);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3]: (warning) %p in format string (no. 1) requires an address but the argument type is 'foo'.\n" "[test.cpp:4]: (warning) %p in format string (no. 1) requires an address but the argument type is 'char'.\n", errout_str()); @@ -2414,7 +2530,7 @@ class TestIO : public TestFixture { " printf(\"%p\", cpwc);\n" " printf(\"%p\", \"s4\");\n" " printf(\"%p\", L\"s5W\");\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("class foo {};\n" @@ -2426,7 +2542,7 @@ class TestIO : public TestFixture { " printf(\"%f\", d);\n" " printf(\"%f\", b);\n" " printf(\"%f\", (float)cpi);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3]: (warning) %e in format string (no. 1) requires 'double' but the argument type is 'foo'.\n" "[test.cpp:4]: (warning) %E in format string (no. 1) requires 'double' but the argument type is 'const char *'.\n" "[test.cpp:5]: (warning) %f in format string (no. 1) requires 'double' but the argument type is 'const signed int *'.\n" @@ -2439,7 +2555,7 @@ class TestIO : public TestFixture { " printf(\"%f\", cpc);\n" " printf(\"%G\", pc);\n" " printf(\"%f\", pc);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3]: (warning) %e in format string (no. 1) requires 'double' but the argument type is 'const char *'.\n" "[test.cpp:4]: (warning) %E in format string (no. 1) requires 'double' but the argument type is 'char *'.\n" "[test.cpp:5]: (warning) %f in format string (no. 1) requires 'double' but the argument type is 'const char *'.\n" @@ -2452,7 +2568,7 @@ class TestIO : public TestFixture { " printf(\"%E\", L\"s5W\");\n" " printf(\"%f\", L\"s5W\");\n" " printf(\"%G\", L\"s5W\");\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3]: (warning) %e in format string (no. 1) requires 'double' but the argument type is 'const wchar_t *'.\n" "[test.cpp:4]: (warning) %E in format string (no. 1) requires 'double' but the argument type is 'const wchar_t *'.\n" "[test.cpp:5]: (warning) %f in format string (no. 1) requires 'double' but the argument type is 'const wchar_t *'.\n" @@ -2463,7 +2579,7 @@ class TestIO : public TestFixture { " printf(\"%u\", f);\n" " printf(\"%f\", f);\n" " printf(\"%p\", f);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3]: (warning) %u in format string (no. 1) requires 'unsigned int' but the argument type is 'foo'.\n" "[test.cpp:4]: (warning) %f in format string (no. 1) requires 'double' but the argument type is 'foo'.\n" "[test.cpp:5]: (warning) %p in format string (no. 1) requires an address but the argument type is 'foo'.\n", errout_str()); @@ -2473,14 +2589,14 @@ class TestIO : public TestFixture { check("void foo(signed char sc, unsigned char uc, short int si, unsigned short int usi) {\n" " printf(\"%hhx %hhd\", sc, uc);\n" " printf(\"%hd %hu\", si, usi);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %hhx in format string (no. 1) requires 'unsigned char' but the argument type is 'signed char'.\n" "[test.cpp:2]: (warning) %hhd in format string (no. 2) requires 'char' but the argument type is 'unsigned char'.\n", errout_str()); check("void foo(long long int lli, unsigned long long int ulli, long int li, unsigned long int uli) {\n" " printf(\"%llo %llx\", lli, ulli);\n" " printf(\"%ld %lu\", li, uli);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(intmax_t im, uintmax_t uim, size_t s, ptrdiff_t p, long double ld, std::size_t ss, std::ptrdiff_t sp) {\n" @@ -2490,7 +2606,7 @@ class TestIO : public TestFixture { " printf(\"%Lf\", ld);\n" " printf(\"%zx\", ss);\n" " printf(\"%ti\", sp);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Unrecognized (and non-existent in standard library) specifiers. @@ -2502,31 +2618,31 @@ class TestIO : public TestFixture { " printf(\"%La\", ld);\n" " printf(\"%zv\", ss);\n" " printf(\"%tp\", sp);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(long long l, ptrdiff_t p, std::ptrdiff_t sp) {\n" " printf(\"%td\", p);\n" " printf(\"%td\", sp);\n" " printf(\"%td\", l);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4]: (warning) %td in format string (no. 1) requires 'ptrdiff_t' but the argument type is 'signed long long'.\n", errout_str()); check("void foo(int i, long double ld) {\n" " printf(\"%zx %zu\", i, ld);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %zx in format string (no. 1) requires 'size_t' but the argument type is 'signed int'.\n" "[test.cpp:2]: (warning) %zu in format string (no. 2) requires 'size_t' but the argument type is 'long double'.\n", errout_str()); check("void foo(unsigned int ui, long double ld) {\n" " printf(\"%zu %zx\", ui, ld);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %zu in format string (no. 1) requires 'size_t' but the argument type is 'unsigned int'.\n" "[test.cpp:2]: (warning) %zx in format string (no. 2) requires 'size_t' but the argument type is 'long double'.\n", errout_str()); check("void foo(int i, long double ld) {\n" " printf(\"%tx %tu\", i, ld);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %tx in format string (no. 1) requires 'unsigned ptrdiff_t' but the argument type is 'signed int'.\n" "[test.cpp:2]: (warning) %tu in format string (no. 2) requires 'unsigned ptrdiff_t' but the argument type is 'long double'.\n", errout_str()); @@ -2541,7 +2657,7 @@ class TestIO : public TestFixture { " printf(\"%t\", i);\n" " printf(\"%L\", i);\n" " printf(\"%I\", i);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) 'h' in format string (no. 1) is a length modifier and cannot be used without a conversion specifier.\n" "[test.cpp:3]: (warning) 'hh' in format string (no. 1) is a length modifier and cannot be used without a conversion specifier.\n" "[test.cpp:4]: (warning) 'l' in format string (no. 1) is a length modifier and cannot be used without a conversion specifier.\n" @@ -2557,7 +2673,7 @@ class TestIO : public TestFixture { " printf(\"%hhd\", i);\n" " printf(\"%ld\", i);\n" " printf(\"%lld\", i);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %hd in format string (no. 1) requires 'short' but the argument type is 'unsigned int'.\n" "[test.cpp:3]: (warning) %hhd in format string (no. 1) requires 'char' but the argument type is 'unsigned int'.\n" "[test.cpp:4]: (warning) %ld in format string (no. 1) requires 'long' but the argument type is 'unsigned int'.\n" @@ -2566,56 +2682,56 @@ class TestIO : public TestFixture { check("void foo(size_t s, ptrdiff_t p) {\n" " printf(\"%zd\", s);\n" " printf(\"%tu\", p);\n" - "}", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Unix32)); + "}\n", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Unix32)); ASSERT_EQUALS("[test.cpp:2]: (portability) %zd in format string (no. 1) requires 'ssize_t' but the argument type is 'size_t {aka unsigned long}'.\n" "[test.cpp:3]: (portability) %tu in format string (no. 1) requires 'unsigned ptrdiff_t' but the argument type is 'ptrdiff_t {aka signed long}'.\n", errout_str()); check("void foo(std::size_t s, std::ptrdiff_t p) {\n" " printf(\"%zd\", s);\n" " printf(\"%tu\", p);\n" - "}", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Unix32)); + "}\n", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Unix32)); ASSERT_EQUALS("[test.cpp:2]: (portability) %zd in format string (no. 1) requires 'ssize_t' but the argument type is 'std::size_t {aka unsigned long}'.\n" "[test.cpp:3]: (portability) %tu in format string (no. 1) requires 'unsigned ptrdiff_t' but the argument type is 'std::ptrdiff_t {aka signed long}'.\n", errout_str()); check("void foo(size_t s, ptrdiff_t p) {\n" " printf(\"%zd\", s);\n" " printf(\"%tu\", p);\n" - "}", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Unix64)); + "}\n", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Unix64)); ASSERT_EQUALS("[test.cpp:2]: (portability) %zd in format string (no. 1) requires 'ssize_t' but the argument type is 'size_t {aka unsigned long}'.\n" "[test.cpp:3]: (portability) %tu in format string (no. 1) requires 'unsigned ptrdiff_t' but the argument type is 'ptrdiff_t {aka signed long}'.\n", errout_str()); check("void foo(std::size_t s, std::ptrdiff_t p) {\n" " printf(\"%zd\", s);\n" " printf(\"%tu\", p);\n" - "}", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Unix64)); + "}\n", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Unix64)); ASSERT_EQUALS("[test.cpp:2]: (portability) %zd in format string (no. 1) requires 'ssize_t' but the argument type is 'std::size_t {aka unsigned long}'.\n" "[test.cpp:3]: (portability) %tu in format string (no. 1) requires 'unsigned ptrdiff_t' but the argument type is 'std::ptrdiff_t {aka signed long}'.\n", errout_str()); check("void foo(size_t s, ptrdiff_t p) {\n" " printf(\"%zd\", s);\n" " printf(\"%tu\", p);\n" - "}", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win32A)); + "}\n", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win32A)); ASSERT_EQUALS("[test.cpp:2]: (portability) %zd in format string (no. 1) requires 'ssize_t' but the argument type is 'size_t {aka unsigned long}'.\n" "[test.cpp:3]: (portability) %tu in format string (no. 1) requires 'unsigned ptrdiff_t' but the argument type is 'ptrdiff_t {aka signed long}'.\n", errout_str()); check("void foo(std::size_t s, std::ptrdiff_t p) {\n" " printf(\"%zd\", s);\n" " printf(\"%tu\", p);\n" - "}", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win32A)); + "}\n", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win32A)); ASSERT_EQUALS("[test.cpp:2]: (portability) %zd in format string (no. 1) requires 'ssize_t' but the argument type is 'std::size_t {aka unsigned long}'.\n" "[test.cpp:3]: (portability) %tu in format string (no. 1) requires 'unsigned ptrdiff_t' but the argument type is 'std::ptrdiff_t {aka signed long}'.\n", errout_str()); check("void foo(size_t s, ptrdiff_t p) {\n" " printf(\"%zd\", s);\n" " printf(\"%tu\", p);\n" - "}", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win64)); + "}\n", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win64)); ASSERT_EQUALS("[test.cpp:2]: (portability) %zd in format string (no. 1) requires 'ssize_t' but the argument type is 'size_t {aka unsigned long long}'.\n" "[test.cpp:3]: (portability) %tu in format string (no. 1) requires 'unsigned ptrdiff_t' but the argument type is 'ptrdiff_t {aka signed long long}'.\n", errout_str()); check("void foo(std::size_t s, std::ptrdiff_t p) {\n" " printf(\"%zd\", s);\n" " printf(\"%tu\", p);\n" - "}", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win64)); + "}\n", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win64)); ASSERT_EQUALS("[test.cpp:2]: (portability) %zd in format string (no. 1) requires 'ssize_t' but the argument type is 'std::size_t {aka unsigned long long}'.\n" "[test.cpp:3]: (portability) %tu in format string (no. 1) requires 'unsigned ptrdiff_t' but the argument type is 'std::ptrdiff_t {aka signed long long}'.\n", errout_str()); @@ -2624,7 +2740,7 @@ class TestIO : public TestFixture { " printf(\"%lu\", um);\n" " printf(\"%llu\", s);\n" " printf(\"%llu\", um);\n" - "}", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win64)); + "}\n", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win64)); ASSERT_EQUALS("[test.cpp:2]: (portability) %lu in format string (no. 1) requires 'unsigned long' but the argument type is 'size_t {aka unsigned long long}'.\n" "[test.cpp:3]: (portability) %lu in format string (no. 1) requires 'unsigned long' but the argument type is 'uintmax_t {aka unsigned long long}'.\n" "[test.cpp:4]: (portability) %llu in format string (no. 1) requires 'unsigned long long' but the argument type is 'size_t {aka unsigned long long}'.\n" @@ -2637,7 +2753,7 @@ class TestIO : public TestFixture { " printf(\"%llu\", i);\n" " printf(\"%lx\", i);\n" " printf(\"%llx\", i);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %ld in format string (no. 1) requires 'long' but the argument type is 'unsigned int'.\n" "[test.cpp:3]: (warning) %lld in format string (no. 1) requires 'long long' but the argument type is 'unsigned int'.\n" "[test.cpp:4]: (warning) %lu in format string (no. 1) requires 'unsigned long' but the argument type is 'unsigned int'.\n" @@ -2649,13 +2765,13 @@ class TestIO : public TestFixture { " printf(\"%lld\", i);\n" " printf(\"%lld\", im);\n" " printf(\"%lld\", p);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %lld in format string (no. 1) requires 'long long' but the argument type is 'signed int'.\n", errout_str()); check("void foo(intmax_t im, ptrdiff_t p) {\n" " printf(\"%lld\", im);\n" " printf(\"%lld\", p);\n" - "}", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win64)); + "}\n", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win64)); ASSERT_EQUALS("[test.cpp:2]: (portability) %lld in format string (no. 1) requires 'long long' but the argument type is 'intmax_t {aka signed long long}'.\n" "[test.cpp:3]: (portability) %lld in format string (no. 1) requires 'long long' but the argument type is 'ptrdiff_t {aka signed long long}'.\n", errout_str()); @@ -2674,7 +2790,7 @@ class TestIO : public TestFixture { " printf(\"%d %f %f %d %f %f\",\n" " foo->d, foo->bar[0].i, a[0],\n" " f[0].d, f[0].baz.i, f[0].bar[0].i);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:13]: (warning) %d in format string (no. 1) requires 'int' but the argument type is 'double'.\n" "[test.cpp:13]: (warning) %f in format string (no. 2) requires 'double' but the argument type is 'signed int'.\n" "[test.cpp:13]: (warning) %f in format string (no. 3) requires 'double' but the argument type is 'int'.\n" @@ -2683,7 +2799,7 @@ class TestIO : public TestFixture { "[test.cpp:13]: (warning) %f in format string (no. 6) requires 'double' but the argument type is 'int'.\n", errout_str()); check("short f() { return 0; }\n" - "void foo() { printf(\"%d %u %lu %I64u %I64d %f %Lf %p\", f(), f(), f(), f(), f(), f(), f(), f()); }"); + "void foo() { printf(\"%d %u %lu %I64u %I64d %f %Lf %p\", f(), f(), f(), f(), f(), f(), f(), f()); }\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %u in format string (no. 2) requires 'unsigned int' but the argument type is 'signed short'.\n" "[test.cpp:2]: (warning) %lu in format string (no. 3) requires 'unsigned long' but the argument type is 'signed short'.\n" "[test.cpp:2]: (warning) %I64u in format string (no. 4) requires 'unsigned __int64' but the argument type is 'signed short'.\n" @@ -2693,7 +2809,7 @@ class TestIO : public TestFixture { "[test.cpp:2]: (warning) %p in format string (no. 8) requires an address but the argument type is 'signed short'.\n", errout_str()); check("unsigned short f() { return 0; }\n" - "void foo() { printf(\"%u %d %ld %I64d %I64u %f %Lf %p\", f(), f(), f(), f(), f(), f(), f(), f()); }"); + "void foo() { printf(\"%u %d %ld %I64d %I64u %f %Lf %p\", f(), f(), f(), f(), f(), f(), f(), f()); }\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %ld in format string (no. 3) requires 'long' but the argument type is 'unsigned short'.\n" "[test.cpp:2]: (warning) %I64d in format string (no. 4) requires '__int64' but the argument type is 'unsigned short'.\n" "[test.cpp:2]: (warning) %I64u in format string (no. 5) requires 'unsigned __int64' but the argument type is 'unsigned short'.\n" @@ -2702,7 +2818,7 @@ class TestIO : public TestFixture { "[test.cpp:2]: (warning) %p in format string (no. 8) requires an address but the argument type is 'unsigned short'.\n", errout_str()); check("int f() { return 0; }\n" - "void foo() { printf(\"%d %u %lu %I64u %I64d %f %Lf %p\", f(), f(), f(), f(), f(), f(), f(), f()); }"); + "void foo() { printf(\"%d %u %lu %I64u %I64d %f %Lf %p\", f(), f(), f(), f(), f(), f(), f(), f()); }\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %u in format string (no. 2) requires 'unsigned int' but the argument type is 'signed int'.\n" "[test.cpp:2]: (warning) %lu in format string (no. 3) requires 'unsigned long' but the argument type is 'signed int'.\n" "[test.cpp:2]: (warning) %I64u in format string (no. 4) requires 'unsigned __int64' but the argument type is 'signed int'.\n" @@ -2712,7 +2828,7 @@ class TestIO : public TestFixture { "[test.cpp:2]: (warning) %p in format string (no. 8) requires an address but the argument type is 'signed int'.\n", errout_str()); check("unsigned int f() { return 0; }\n" - "void foo() { printf(\"%u %d %ld %I64d %I64u %f %Lf %p\", f(), f(), f(), f(), f(), f(), f(), f()); }"); + "void foo() { printf(\"%u %d %ld %I64d %I64u %f %Lf %p\", f(), f(), f(), f(), f(), f(), f(), f()); }\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %d in format string (no. 2) requires 'int' but the argument type is 'unsigned int'.\n" "[test.cpp:2]: (warning) %ld in format string (no. 3) requires 'long' but the argument type is 'unsigned int'.\n" "[test.cpp:2]: (warning) %I64d in format string (no. 4) requires '__int64' but the argument type is 'unsigned int'.\n" @@ -2722,7 +2838,7 @@ class TestIO : public TestFixture { "[test.cpp:2]: (warning) %p in format string (no. 8) requires an address but the argument type is 'unsigned int'.\n", errout_str()); check("long f() { return 0; }\n" - "void foo() { printf(\"%ld %u %lu %I64u %I64d %f %Lf %p\", f(), f(), f(), f(), f(), f(), f(), f()); }"); + "void foo() { printf(\"%ld %u %lu %I64u %I64d %f %Lf %p\", f(), f(), f(), f(), f(), f(), f(), f()); }\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %u in format string (no. 2) requires 'unsigned int' but the argument type is 'signed long'.\n" "[test.cpp:2]: (warning) %lu in format string (no. 3) requires 'unsigned long' but the argument type is 'signed long'.\n" "[test.cpp:2]: (warning) %I64u in format string (no. 4) requires 'unsigned __int64' but the argument type is 'signed long'.\n" @@ -2732,7 +2848,7 @@ class TestIO : public TestFixture { "[test.cpp:2]: (warning) %p in format string (no. 8) requires an address but the argument type is 'signed long'.\n", errout_str()); check("unsigned long f() { return 0; }\n" - "void foo() { printf(\"%lu %d %ld %I64d %I64u %f %Lf %p\", f(), f(), f(), f(), f(), f(), f(), f()); }"); + "void foo() { printf(\"%lu %d %ld %I64d %I64u %f %Lf %p\", f(), f(), f(), f(), f(), f(), f(), f()); }\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %d in format string (no. 2) requires 'int' but the argument type is 'unsigned long'.\n" "[test.cpp:2]: (warning) %ld in format string (no. 3) requires 'long' but the argument type is 'unsigned long'.\n" "[test.cpp:2]: (warning) %I64d in format string (no. 4) requires '__int64' but the argument type is 'unsigned long'.\n" @@ -2742,7 +2858,7 @@ class TestIO : public TestFixture { "[test.cpp:2]: (warning) %p in format string (no. 8) requires an address but the argument type is 'unsigned long'.\n", errout_str()); check("long long f() { return 0; }\n" - "void foo() { printf(\"%lld %u %lu %I64u %I64d %f %Lf %p\", f(), f(), f(), f(), f(), f(), f(), f()); }"); + "void foo() { printf(\"%lld %u %lu %I64u %I64d %f %Lf %p\", f(), f(), f(), f(), f(), f(), f(), f()); }\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %u in format string (no. 2) requires 'unsigned int' but the argument type is 'signed long long'.\n" "[test.cpp:2]: (warning) %lu in format string (no. 3) requires 'unsigned long' but the argument type is 'signed long long'.\n" "[test.cpp:2]: (warning) %I64u in format string (no. 4) requires 'unsigned __int64' but the argument type is 'signed long long'.\n" @@ -2751,7 +2867,7 @@ class TestIO : public TestFixture { "[test.cpp:2]: (warning) %p in format string (no. 8) requires an address but the argument type is 'signed long long'.\n", errout_str()); check("unsigned long long f() { return 0; }\n" - "void foo() { printf(\"%llu %d %ld %I64d %I64u %f %Lf %p\", f(), f(), f(), f(), f(), f(), f(), f()); }"); + "void foo() { printf(\"%llu %d %ld %I64d %I64u %f %Lf %p\", f(), f(), f(), f(), f(), f(), f(), f()); }\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %d in format string (no. 2) requires 'int' but the argument type is 'unsigned long long'.\n" "[test.cpp:2]: (warning) %ld in format string (no. 3) requires 'long' but the argument type is 'unsigned long long'.\n" "[test.cpp:2]: (warning) %I64d in format string (no. 4) requires '__int64' but the argument type is 'unsigned long long'.\n" @@ -2760,7 +2876,7 @@ class TestIO : public TestFixture { "[test.cpp:2]: (warning) %p in format string (no. 8) requires an address but the argument type is 'unsigned long long'.\n", errout_str()); check("float f() { return 0; }\n" - "void foo() { printf(\"%f %d %ld %u %lu %I64d %I64u %Lf %p\", f(), f(), f(), f(), f(), f(), f(), f(), f()); }"); + "void foo() { printf(\"%f %d %ld %u %lu %I64d %I64u %Lf %p\", f(), f(), f(), f(), f(), f(), f(), f(), f()); }\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %d in format string (no. 2) requires 'int' but the argument type is 'float'.\n" "[test.cpp:2]: (warning) %ld in format string (no. 3) requires 'long' but the argument type is 'float'.\n" "[test.cpp:2]: (warning) %u in format string (no. 4) requires 'unsigned int' but the argument type is 'float'.\n" @@ -2771,7 +2887,7 @@ class TestIO : public TestFixture { "[test.cpp:2]: (warning) %p in format string (no. 9) requires an address but the argument type is 'float'.\n", errout_str()); check("double f() { return 0; }\n" - "void foo() { printf(\"%f %d %ld %u %lu %I64d %I64u %Lf %p\", f(), f(), f(), f(), f(), f(), f(), f(), f()); }"); + "void foo() { printf(\"%f %d %ld %u %lu %I64d %I64u %Lf %p\", f(), f(), f(), f(), f(), f(), f(), f(), f()); }\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %d in format string (no. 2) requires 'int' but the argument type is 'double'.\n" "[test.cpp:2]: (warning) %ld in format string (no. 3) requires 'long' but the argument type is 'double'.\n" "[test.cpp:2]: (warning) %u in format string (no. 4) requires 'unsigned int' but the argument type is 'double'.\n" @@ -2782,7 +2898,7 @@ class TestIO : public TestFixture { "[test.cpp:2]: (warning) %p in format string (no. 9) requires an address but the argument type is 'double'.\n", errout_str()); check("long double f() { return 0; }\n" - "void foo() { printf(\"%Lf %d %ld %u %lu %I64d %I64u %f %p\", f(), f(), f(), f(), f(), f(), f(), f(), f()); }"); + "void foo() { printf(\"%Lf %d %ld %u %lu %I64d %I64u %f %p\", f(), f(), f(), f(), f(), f(), f(), f(), f()); }\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %d in format string (no. 2) requires 'int' but the argument type is 'long double'.\n" "[test.cpp:2]: (warning) %ld in format string (no. 3) requires 'long' but the argument type is 'long double'.\n" "[test.cpp:2]: (warning) %u in format string (no. 4) requires 'unsigned int' but the argument type is 'long double'.\n" @@ -2793,37 +2909,37 @@ class TestIO : public TestFixture { "[test.cpp:2]: (warning) %p in format string (no. 9) requires an address but the argument type is 'long double'.\n", errout_str()); check("int f() { return 0; }\n" - "void foo() { printf(\"%I64d %I64u %I64x %d\", f(), f(), f(), f()); }"); + "void foo() { printf(\"%I64d %I64u %I64x %d\", f(), f(), f(), f()); }\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %I64d in format string (no. 1) requires '__int64' but the argument type is 'signed int'.\n" "[test.cpp:2]: (warning) %I64u in format string (no. 2) requires 'unsigned __int64' but the argument type is 'signed int'.\n" "[test.cpp:2]: (warning) %I64x in format string (no. 3) requires 'unsigned __int64' but the argument type is 'signed int'.\n", errout_str()); check("long long f() { return 0; }\n" - "void foo() { printf(\"%I32d %I32u %I32x %lld\", f(), f(), f(), f()); }"); + "void foo() { printf(\"%I32d %I32u %I32x %lld\", f(), f(), f(), f()); }\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %I32d in format string (no. 1) requires '__int32' but the argument type is 'signed long long'.\n" "[test.cpp:2]: (warning) %I32u in format string (no. 2) requires 'unsigned __int32' but the argument type is 'signed long long'.\n" "[test.cpp:2]: (warning) %I32x in format string (no. 3) requires 'unsigned __int32' but the argument type is 'signed long long'.\n", errout_str()); check("unsigned long long f() { return 0; }\n" - "void foo() { printf(\"%I32d %I32u %I32x %llx\", f(), f(), f(), f()); }"); + "void foo() { printf(\"%I32d %I32u %I32x %llx\", f(), f(), f(), f()); }\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %I32d in format string (no. 1) requires '__int32' but the argument type is 'unsigned long long'.\n" "[test.cpp:2]: (warning) %I32u in format string (no. 2) requires 'unsigned __int32' but the argument type is 'unsigned long long'.\n" "[test.cpp:2]: (warning) %I32x in format string (no. 3) requires 'unsigned __int32' but the argument type is 'unsigned long long'.\n", errout_str()); check("signed char f() { return 0; }\n" - "void foo() { printf(\"%Id %Iu %Ix %hhi\", f(), f(), f(), f()); }"); + "void foo() { printf(\"%Id %Iu %Ix %hhi\", f(), f(), f(), f()); }\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %Id in format string (no. 1) requires 'ptrdiff_t' but the argument type is 'signed char'.\n" "[test.cpp:2]: (warning) %Iu in format string (no. 2) requires 'size_t' but the argument type is 'signed char'.\n" "[test.cpp:2]: (warning) %Ix in format string (no. 3) requires 'size_t' but the argument type is 'signed char'.\n", errout_str()); check("unsigned char f() { return 0; }\n" - "void foo() { printf(\"%Id %Iu %Ix %hho\", f(), f(), f(), f()); }"); + "void foo() { printf(\"%Id %Iu %Ix %hho\", f(), f(), f(), f()); }\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %Id in format string (no. 1) requires 'ptrdiff_t' but the argument type is 'unsigned char'.\n" "[test.cpp:2]: (warning) %Iu in format string (no. 2) requires 'size_t' but the argument type is 'unsigned char'.\n" "[test.cpp:2]: (warning) %Ix in format string (no. 3) requires 'size_t' but the argument type is 'unsigned char'.\n", errout_str()); check("namespace bar { int f() { return 0; } }\n" - "void foo() { printf(\"%d %u %lu %f %Lf %p\", bar::f(), bar::f(), bar::f(), bar::f(), bar::f(), bar::f()); }"); + "void foo() { printf(\"%d %u %lu %f %Lf %p\", bar::f(), bar::f(), bar::f(), bar::f(), bar::f(), bar::f()); }\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %u in format string (no. 2) requires 'unsigned int' but the argument type is 'signed int'.\n" "[test.cpp:2]: (warning) %lu in format string (no. 3) requires 'unsigned long' but the argument type is 'signed int'.\n" "[test.cpp:2]: (warning) %f in format string (no. 4) requires 'double' but the argument type is 'signed int'.\n" @@ -2831,7 +2947,7 @@ class TestIO : public TestFixture { "[test.cpp:2]: (warning) %p in format string (no. 6) requires an address but the argument type is 'signed int'.\n", errout_str()); check("struct Fred { int i; } f;\n" - "void foo() { printf(\"%d %u %lu %f %Lf %p\", f.i, f.i, f.i, f.i, f.i, f.i); }"); + "void foo() { printf(\"%d %u %lu %f %Lf %p\", f.i, f.i, f.i, f.i, f.i, f.i); }\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %u in format string (no. 2) requires 'unsigned int' but the argument type is 'signed int'.\n" "[test.cpp:2]: (warning) %lu in format string (no. 3) requires 'unsigned long' but the argument type is 'signed int'.\n" "[test.cpp:2]: (warning) %f in format string (no. 4) requires 'double' but the argument type is 'signed int'.\n" @@ -2839,7 +2955,7 @@ class TestIO : public TestFixture { "[test.cpp:2]: (warning) %p in format string (no. 6) requires an address but the argument type is 'signed int'.\n", errout_str()); check("struct Fred { unsigned int u; } f;\n" - "void foo() { printf(\"%u %d %ld %f %Lf %p\", f.u, f.u, f.u, f.u, f.u, f.u); }"); + "void foo() { printf(\"%u %d %ld %f %Lf %p\", f.u, f.u, f.u, f.u, f.u, f.u); }\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %d in format string (no. 2) requires 'int' but the argument type is 'unsigned int'.\n" "[test.cpp:2]: (warning) %ld in format string (no. 3) requires 'long' but the argument type is 'unsigned int'.\n" "[test.cpp:2]: (warning) %f in format string (no. 4) requires 'double' but the argument type is 'unsigned int'.\n" @@ -2847,7 +2963,7 @@ class TestIO : public TestFixture { "[test.cpp:2]: (warning) %p in format string (no. 6) requires an address but the argument type is 'unsigned int'.\n", errout_str()); check("struct Fred { unsigned int ui() { return 0; } } f;\n" - "void foo() { printf(\"%u %d %ld %f %Lf %p\", f.ui(), f.ui(), f.ui(), f.ui(), f.ui(), f.ui()); }"); + "void foo() { printf(\"%u %d %ld %f %Lf %p\", f.ui(), f.ui(), f.ui(), f.ui(), f.ui(), f.ui()); }\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %d in format string (no. 2) requires 'int' but the argument type is 'unsigned int'.\n" "[test.cpp:2]: (warning) %ld in format string (no. 3) requires 'long' but the argument type is 'unsigned int'.\n" "[test.cpp:2]: (warning) %f in format string (no. 4) requires 'double' but the argument type is 'unsigned int'.\n" @@ -2858,12 +2974,12 @@ class TestIO : public TestFixture { check("void f(int len, int newline) {\n" " printf(\"%s\", newline ? a : str + len);\n" " printf(\"%s\", newline + newline);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3]: (warning) %s in format string (no. 1) requires 'char *' but the argument type is 'signed int'.\n", errout_str()); check("struct Fred { int i; } f;\n" "struct Fred & bar() { };\n" - "void foo() { printf(\"%d %u %lu %f %Lf %p\", bar().i, bar().i, bar().i, bar().i, bar().i, bar().i); }"); + "void foo() { printf(\"%d %u %lu %f %Lf %p\", bar().i, bar().i, bar().i, bar().i, bar().i, bar().i); }\n"); ASSERT_EQUALS("[test.cpp:3]: (warning) %u in format string (no. 2) requires 'unsigned int' but the argument type is 'signed int'.\n" "[test.cpp:3]: (warning) %lu in format string (no. 3) requires 'unsigned long' but the argument type is 'signed int'.\n" "[test.cpp:3]: (warning) %f in format string (no. 4) requires 'double' but the argument type is 'signed int'.\n" @@ -2872,7 +2988,7 @@ class TestIO : public TestFixture { check("struct Fred { int i; } f;\n" "const struct Fred & bar() { };\n" - "void foo() { printf(\"%d %u %lu %f %Lf %p\", bar().i, bar().i, bar().i, bar().i, bar().i, bar().i); }"); + "void foo() { printf(\"%d %u %lu %f %Lf %p\", bar().i, bar().i, bar().i, bar().i, bar().i, bar().i); }\n"); ASSERT_EQUALS("[test.cpp:3]: (warning) %u in format string (no. 2) requires 'unsigned int' but the argument type is 'signed int'.\n" "[test.cpp:3]: (warning) %lu in format string (no. 3) requires 'unsigned long' but the argument type is 'signed int'.\n" "[test.cpp:3]: (warning) %f in format string (no. 4) requires 'double' but the argument type is 'signed int'.\n" @@ -2881,7 +2997,7 @@ class TestIO : public TestFixture { check("struct Fred { int i; } f;\n" "static const struct Fred & bar() { };\n" - "void foo() { printf(\"%d %u %lu %f %Lf %p\", bar().i, bar().i, bar().i, bar().i, bar().i, bar().i); }"); + "void foo() { printf(\"%d %u %lu %f %Lf %p\", bar().i, bar().i, bar().i, bar().i, bar().i, bar().i); }\n"); ASSERT_EQUALS("[test.cpp:3]: (warning) %u in format string (no. 2) requires 'unsigned int' but the argument type is 'signed int'.\n" "[test.cpp:3]: (warning) %lu in format string (no. 3) requires 'unsigned long' but the argument type is 'signed int'.\n" "[test.cpp:3]: (warning) %f in format string (no. 4) requires 'double' but the argument type is 'signed int'.\n" @@ -2890,7 +3006,7 @@ class TestIO : public TestFixture { check("struct Fred { int i; } f[2];\n" "struct Fred * bar() { return f; };\n" - "void foo() { printf(\"%d %u %lu %f %Lf %p\", bar()[0].i, bar()[0].i, bar()[0].i, bar()[0].i, bar()[0].i, bar()[0].i); }"); + "void foo() { printf(\"%d %u %lu %f %Lf %p\", bar()[0].i, bar()[0].i, bar()[0].i, bar()[0].i, bar()[0].i, bar()[0].i); }\n"); ASSERT_EQUALS("[test.cpp:3]: (warning) %u in format string (no. 2) requires 'unsigned int' but the argument type is 'signed int'.\n" "[test.cpp:3]: (warning) %lu in format string (no. 3) requires 'unsigned long' but the argument type is 'signed int'.\n" "[test.cpp:3]: (warning) %f in format string (no. 4) requires 'double' but the argument type is 'signed int'.\n" @@ -2899,7 +3015,7 @@ class TestIO : public TestFixture { check("struct Fred { int i; } f[2];\n" "const struct Fred * bar() { return f; };\n" - "void foo() { printf(\"%d %u %lu %f %Lf %p\", bar()[0].i, bar()[0].i, bar()[0].i, bar()[0].i, bar()[0].i, bar()[0].i); }"); + "void foo() { printf(\"%d %u %lu %f %Lf %p\", bar()[0].i, bar()[0].i, bar()[0].i, bar()[0].i, bar()[0].i, bar()[0].i); }\n"); ASSERT_EQUALS("[test.cpp:3]: (warning) %u in format string (no. 2) requires 'unsigned int' but the argument type is 'signed int'.\n" "[test.cpp:3]: (warning) %lu in format string (no. 3) requires 'unsigned long' but the argument type is 'signed int'.\n" "[test.cpp:3]: (warning) %f in format string (no. 4) requires 'double' but the argument type is 'signed int'.\n" @@ -2908,7 +3024,7 @@ class TestIO : public TestFixture { check("struct Fred { int i; } f[2];\n" "static const struct Fred * bar() { return f; };\n" - "void foo() { printf(\"%d %u %lu %f %Lf %p\", bar()[0].i, bar()[0].i, bar()[0].i, bar()[0].i, bar()[0].i, bar()[0].i); }"); + "void foo() { printf(\"%d %u %lu %f %Lf %p\", bar()[0].i, bar()[0].i, bar()[0].i, bar()[0].i, bar()[0].i, bar()[0].i); }\n"); ASSERT_EQUALS("[test.cpp:3]: (warning) %u in format string (no. 2) requires 'unsigned int' but the argument type is 'signed int'.\n" "[test.cpp:3]: (warning) %lu in format string (no. 3) requires 'unsigned long' but the argument type is 'signed int'.\n" "[test.cpp:3]: (warning) %f in format string (no. 4) requires 'double' but the argument type is 'signed int'.\n" @@ -2917,7 +3033,7 @@ class TestIO : public TestFixture { check("struct Fred { int32_t i; } f;\n" "struct Fred & bar() { };\n" - "void foo() { printf(\"%d %ld %u %lu %f %Lf\", bar().i, bar().i, bar().i, bar().i, bar().i, bar().i); }"); + "void foo() { printf(\"%d %ld %u %lu %f %Lf\", bar().i, bar().i, bar().i, bar().i, bar().i, bar().i); }\n"); ASSERT_EQUALS("[test.cpp:3]: (warning) %ld in format string (no. 2) requires 'long' but the argument type is 'signed int'.\n" "[test.cpp:3]: (warning) %u in format string (no. 3) requires 'unsigned int' but the argument type is 'signed int'.\n" "[test.cpp:3]: (warning) %lu in format string (no. 4) requires 'unsigned long' but the argument type is 'signed int'.\n" @@ -2928,27 +3044,27 @@ class TestIO : public TestFixture { // #4984 check("void f(double *x) {\n" " printf(\"%f\", x[0]);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int array[10];\n" "int * foo() { return array; }\n" "void f() {\n" " printf(\"%f\", foo()[0]);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4]: (warning) %f in format string (no. 1) requires 'double' but the argument type is 'signed int'.\n", errout_str()); check("struct Base { int length() { } };\n" "struct Derived : public Base { };\n" "void foo(Derived * d) {\n" " printf(\"%f\", d.length());\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4]: (warning) %f in format string (no. 1) requires 'double' but the argument type is 'signed int'.\n", errout_str()); check("std::vector v;\n" "void foo() {\n" " printf(\"%d %u %f\", v[0], v[0], v[0]);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3]: (warning) %u in format string (no. 2) requires 'unsigned int' but the argument type is 'signed int'.\n" "[test.cpp:3]: (warning) %f in format string (no. 3) requires 'double' but the argument type is 'signed int'.\n", errout_str()); @@ -2956,7 +3072,7 @@ class TestIO : public TestFixture { check("int bar(int a);\n" "void foo() {\n" " printf(\"%d\", bar(0));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("std::vector v;\n" @@ -3202,20 +3318,20 @@ class TestIO : public TestFixture { check("void foo() {\n" " printf(\"%f %d\", static_cast(1.0f), reinterpret_cast(0));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %f in format string (no. 1) requires 'double' but the argument type is 'signed int'.\n" "[test.cpp:2]: (warning) %d in format string (no. 2) requires 'int' but the argument type is 'const void *'.\n", errout_str()); check("void foo() {\n" " UNKNOWN * u;\n" " printf(\"%d %x %u %f\", u[i], u[i], u[i], u[i]);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" " long * l;\n" " printf(\"%d %x %u %f\", l[i], l[i], l[i], l[i]);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3]: (warning) %d in format string (no. 1) requires 'int' but the argument type is 'signed long'.\n" "[test.cpp:3]: (warning) %x in format string (no. 2) requires 'unsigned int' but the argument type is 'signed long'.\n" "[test.cpp:3]: (warning) %u in format string (no. 3) requires 'unsigned int' but the argument type is 'signed long'.\n" @@ -3236,27 +3352,27 @@ class TestIO : public TestFixture { " printf(\"%u\",v6[0]);\n" " myvector v7(1,0);\n" " printf(\"%s\",v7[0]);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("std::vector v;\n" // #5151 "void foo() {\n" " printf(\"%c %u %f\", v.at(32), v.at(32), v.at(32));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3]: (warning) %u in format string (no. 2) requires 'unsigned int' but the argument type is 'char'.\n" "[test.cpp:3]: (warning) %f in format string (no. 3) requires 'double' but the argument type is 'char'.\n", errout_str()); // #5195 (segmentation fault) check("void T::a(const std::vector& vx) {\n" " printf(\"%f\", vx.at(0));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #5486 check("void foo() {\n" " ssize_t test = 0;\n" " printf(\"%zd\", test);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #6009 @@ -3264,7 +3380,7 @@ class TestIO : public TestFixture { "extern int IntByReturnValue();\n" "void MyFunction() {\n" " printf( \"%s - %s\", StringByReturnValue(), IntByReturnValue() );\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4]: (warning) %s in format string (no. 1) requires 'char *' but the argument type is 'std::string'.\n" "[test.cpp:4]: (warning) %s in format string (no. 2) requires 'char *' but the argument type is 'signed int'.\n", errout_str()); @@ -3277,7 +3393,7 @@ class TestIO : public TestFixture { " Array array1;\n" " Array array2;\n" " printf(\"%u %u\", array1[0], array2[0]);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:9]: (warning) %u in format string (no. 1) requires 'unsigned int' but the argument type is 'int'.\n" "[test.cpp:9]: (warning) %u in format string (no. 2) requires 'unsigned int' but the argument type is 'float'.\n", errout_str()); @@ -3285,19 +3401,19 @@ class TestIO : public TestFixture { check("struct S { unsigned short x; } s = {0};\n" "void foo() {\n" " printf(\"%d\", s.x);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Ticket #7601 check("void foo(int i, unsigned int ui, long long ll, unsigned long long ull) {\n" " printf(\"%Ld %Lu %Ld %Lu\", i, ui, ll, ull);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %Ld in format string (no. 1) requires 'long long' but the argument type is 'signed int'.\n" "[test.cpp:2]: (warning) %Lu in format string (no. 2) requires 'unsigned long long' but the argument type is 'unsigned int'.\n", errout_str()); check("void foo(char c, unsigned char uc, short s, unsigned short us, int i, unsigned int ui, long l, unsigned long ul) {\n" " printf(\"%hhd %hhd %hhd %hhd %hhd %hhd %hhd %hhd\", c, uc, s, us, i, ui, l, ul);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %hhd in format string (no. 2) requires 'char' but the argument type is 'unsigned char'.\n" "[test.cpp:2]: (warning) %hhd in format string (no. 3) requires 'char' but the argument type is 'signed short'.\n" "[test.cpp:2]: (warning) %hhd in format string (no. 4) requires 'char' but the argument type is 'unsigned short'.\n" @@ -3308,7 +3424,7 @@ class TestIO : public TestFixture { check("void foo(char c, unsigned char uc, short s, unsigned short us, int i, unsigned int ui, long l, unsigned long ul) {\n" " printf(\"%hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu\", c, uc, s, us, i, ui, l, ul);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %hhu in format string (no. 1) requires 'unsigned char' but the argument type is 'char'.\n" "[test.cpp:2]: (warning) %hhu in format string (no. 3) requires 'unsigned char' but the argument type is 'signed short'.\n" "[test.cpp:2]: (warning) %hhu in format string (no. 4) requires 'unsigned char' but the argument type is 'unsigned short'.\n" @@ -3319,7 +3435,7 @@ class TestIO : public TestFixture { check("void foo(char c, unsigned char uc, short s, unsigned short us, int i, unsigned int ui, long l, unsigned long ul) {\n" " printf(\"%hhx %hhx %hhx %hhx %hhx %hhx %hhx %hhx\", c, uc, s, us, i, ui, l, ul);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %hhx in format string (no. 1) requires 'unsigned char' but the argument type is 'char'.\n" "[test.cpp:2]: (warning) %hhx in format string (no. 3) requires 'unsigned char' but the argument type is 'signed short'.\n" "[test.cpp:2]: (warning) %hhx in format string (no. 4) requires 'unsigned char' but the argument type is 'unsigned short'.\n" @@ -3330,7 +3446,7 @@ class TestIO : public TestFixture { check("void foo(char c, unsigned char uc, short s, unsigned short us, int i, unsigned int ui, long l, unsigned long ul) {\n" " printf(\"%hd %hd %hd %hd %hd %hd %hd %hd\", c, uc, s, us, i, ui, l, ul);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %hd in format string (no. 1) requires 'short' but the argument type is 'char'.\n" "[test.cpp:2]: (warning) %hd in format string (no. 2) requires 'short' but the argument type is 'unsigned char'.\n" "[test.cpp:2]: (warning) %hd in format string (no. 4) requires 'short' but the argument type is 'unsigned short'.\n" @@ -3341,7 +3457,7 @@ class TestIO : public TestFixture { check("void foo(char c, unsigned char uc, short s, unsigned short us, int i, unsigned int ui, long l, unsigned long ul) {\n" " printf(\"%hu %hu %hu %hu %hu %hu %hu %hu\", c, uc, s, us, i, ui, l, ul);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %hu in format string (no. 1) requires 'unsigned short' but the argument type is 'char'.\n" "[test.cpp:2]: (warning) %hu in format string (no. 2) requires 'unsigned short' but the argument type is 'unsigned char'.\n" "[test.cpp:2]: (warning) %hu in format string (no. 3) requires 'unsigned short' but the argument type is 'signed short'.\n" @@ -3352,7 +3468,7 @@ class TestIO : public TestFixture { check("void foo(char c, unsigned char uc, short s, unsigned short us, int i, unsigned int ui, long l, unsigned long ul) {\n" " printf(\"%hx %hx %hx %hx %hx %hx %hx %hx\", c, uc, s, us, i, ui, l, ul);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2]: (warning) %hx in format string (no. 1) requires 'unsigned short' but the argument type is 'char'.\n" "[test.cpp:2]: (warning) %hx in format string (no. 2) requires 'unsigned short' but the argument type is 'unsigned char'.\n" "[test.cpp:2]: (warning) %hx in format string (no. 3) requires 'unsigned short' but the argument type is 'signed short'.\n" @@ -3368,9 +3484,23 @@ class TestIO : public TestFixture { "\n" "void foo(struct S x) {\n" " printf(\"%f\", x.f(4.0));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); + check("enum E : uint8_t { E0 }; \n" // #7959 + "void f(E e) {\n" + " printf(\"%hhu\", e);\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("enum E : uint8_t { E0 }; \n" + "void f(E e) {\n" + " printf(\"%lu\", e);\n" + "}\n"); + TODO_ASSERT_EQUALS("[test.cpp:3]: (warning) %lu in format string (no. 1) requires 'unsigned long' but the argument type is 'uint8_t'.\n", + "", + errout_str()); + check("void f() {\n" " printf(\"%lu\", sizeof(char));\n" "}\n", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win64)); @@ -4129,7 +4259,7 @@ class TestIO : public TestFixture { " printf(\"%1$d\", 1);" " printf(\"%1$d, %d, %1$d\", 1, 2);" " scanf(\"%1$d\", &bar);" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" @@ -4138,7 +4268,7 @@ class TestIO : public TestFixture { " printf(\"%1$d, %d, %4$d\", 1, 2, 3);\n" " scanf(\"%2$d\", &bar);\n" " printf(\"%0$f\", 0.0);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:3]: (error) printf format string requires 1 parameter but only 0 are given. [wrongPrintfScanfArgNum]\n" "[test.cpp:4:3]: (warning) printf: referencing parameter 4 while 3 arguments given [wrongPrintfScanfParameterPositionError]\n" "[test.cpp:5:3]: (warning) scanf: referencing parameter 2 while 1 arguments given [wrongPrintfScanfParameterPositionError]\n" @@ -4161,7 +4291,7 @@ class TestIO : public TestFixture { " printf(\"%I32d %I32u %I32x\", u32, u32, u32);\n" " printf(\"%I64d %I64u %I64x\", i64, i64, i64);\n" " printf(\"%I64d %I64u %I64x\", u64, u64, u64);\n" - "}", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win32A)); + "}\n", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win32A)); ASSERT_EQUALS("[test.cpp:8:5]: (portability) %Id in format string (no. 1) requires 'ptrdiff_t' but the argument type is 'size_t {aka unsigned long}'. [invalidPrintfArgType_sint]\n" "[test.cpp:9:5]: (portability) %Iu in format string (no. 2) requires 'size_t' but the argument type is 'ptrdiff_t {aka signed long}'. [invalidPrintfArgType_uint]\n" "[test.cpp:9:5]: (portability) %Ix in format string (no. 3) requires 'size_t' but the argument type is 'ptrdiff_t {aka signed long}'. [invalidPrintfArgType_uint]\n" @@ -4183,7 +4313,7 @@ class TestIO : public TestFixture { " printf(\"%I32d %I32u %I32x\", u32, u32, u32);\n" " printf(\"%I64d %I64u %I64x\", i64, i64, i64);\n" " printf(\"%I64d %I64u %I64x\", u64, u64, u64);\n" - "}", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win64)); + "}\n", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win64)); ASSERT_EQUALS("[test.cpp:8:5]: (portability) %Id in format string (no. 1) requires 'ptrdiff_t' but the argument type is 'size_t {aka unsigned long long}'. [invalidPrintfArgType_sint]\n" "[test.cpp:9:5]: (portability) %Iu in format string (no. 2) requires 'size_t' but the argument type is 'ptrdiff_t {aka signed long long}'. [invalidPrintfArgType_uint]\n" "[test.cpp:9:5]: (portability) %Ix in format string (no. 3) requires 'size_t' but the argument type is 'ptrdiff_t {aka signed long long}'. [invalidPrintfArgType_uint]\n" @@ -4209,7 +4339,7 @@ class TestIO : public TestFixture { " printf(\"%I16x%i\", s, i);\n" " printf(\"%I32%i\", s, i);\n" " printf(\"%I64%i\", s, i);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:5]: (warning) 'I' in format string (no. 1) is a length modifier and cannot be used without a conversion specifier. [invalidLengthModifierError]\n" "[test.cpp:5:5]: (warning) 'I' in format string (no. 1) is a length modifier and cannot be used without a conversion specifier. [invalidLengthModifierError]\n" "[test.cpp:6:5]: (warning) 'I' in format string (no. 1) is a length modifier and cannot be used without a conversion specifier. [invalidLengthModifierError]\n" @@ -4254,7 +4384,7 @@ class TestIO : public TestFixture { " printf(\"%zd\", s);\n" " printf(\"%zd%i\", s, i);\n" " printf(\"%zu\", s);\n" - "}", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win32A)); + "}\n", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win32A)); ASSERT_EQUALS("[test.cpp:6:5]: (portability) %zu in format string (no. 1) requires 'size_t' but the argument type is 'SSIZE_T {aka signed long}'. [invalidPrintfArgType_uint]\n", errout_str()); check("void foo() {\n" @@ -4263,7 +4393,7 @@ class TestIO : public TestFixture { " printf(\"%zd\", s);\n" " printf(\"%zd%i\", s, i);\n" " printf(\"%zu\", s);\n" - "}", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win64)); + "}\n", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win64)); ASSERT_EQUALS("[test.cpp:6:5]: (portability) %zu in format string (no. 1) requires 'size_t' but the argument type is 'SSIZE_T {aka signed long long}'. [invalidPrintfArgType_uint]\n", errout_str()); check("void foo() {\n" @@ -4272,7 +4402,7 @@ class TestIO : public TestFixture { " printf(\"%zd\", s);\n" " printf(\"%zd%i\", s, i);\n" " printf(\"%zu\", s);\n" - "}", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Unix64)); + "}\n", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Unix64)); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" @@ -4282,7 +4412,7 @@ class TestIO : public TestFixture { " printf(\"%zd\", s);\n" " printf(\"%zd%i\", s, i);\n" " printf(\"%zu\", s);\n" - "}", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win64)); + "}\n", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win64)); ASSERT_EQUALS("[test.cpp:7:5]: (portability) %zu in format string (no. 1) requires 'size_t' but the argument type is 'SSIZE_T {aka signed long long}'. [invalidPrintfArgType_uint]\n", errout_str()); } @@ -4301,7 +4431,7 @@ class TestIO : public TestFixture { " scanf(\"%I32d %I32u %I32x\", &u32, &u32, &u32);\n" " scanf(\"%I64d %I64u %I64x\", &i64, &i64, &i64);\n" " scanf(\"%I64d %I64u %I64x\", &u64, &u64, &u64);\n" - "}", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win32A)); + "}\n", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win32A)); ASSERT_EQUALS("[test.cpp:8:5]: (portability) %Id in format string (no. 1) requires 'ptrdiff_t *' but the argument type is 'size_t * {aka unsigned long *}'. [invalidScanfArgType_int]\n" "[test.cpp:9:5]: (portability) %Iu in format string (no. 2) requires 'size_t *' but the argument type is 'ptrdiff_t * {aka signed long *}'. [invalidScanfArgType_int]\n" "[test.cpp:9:5]: (portability) %Ix in format string (no. 3) requires 'size_t *' but the argument type is 'ptrdiff_t * {aka signed long *}'. [invalidScanfArgType_int]\n" @@ -4325,7 +4455,7 @@ class TestIO : public TestFixture { " scanf(\"%I32d %I32u %I32x\", &u32, &u32, &u32);\n" " scanf(\"%I64d %I64u %I64x\", &i64, &i64, &i64);\n" " scanf(\"%I64d %I64u %I64x\", &u64, &u64, &u64);\n" - "}", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win64)); + "}\n", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win64)); ASSERT_EQUALS("[test.cpp:8:5]: (portability) %Id in format string (no. 1) requires 'ptrdiff_t *' but the argument type is 'size_t * {aka unsigned long long *}'. [invalidScanfArgType_int]\n" "[test.cpp:9:5]: (portability) %Iu in format string (no. 2) requires 'size_t *' but the argument type is 'ptrdiff_t * {aka signed long long *}'. [invalidScanfArgType_int]\n" "[test.cpp:9:5]: (portability) %Ix in format string (no. 3) requires 'size_t *' but the argument type is 'ptrdiff_t * {aka signed long long *}'. [invalidScanfArgType_int]\n" @@ -4353,7 +4483,7 @@ class TestIO : public TestFixture { " scanf(\"%I16x%i\", &s, &i);\n" " scanf(\"%I32%i\", &s, &i);\n" " scanf(\"%I64%i\", &s, &i);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:5]: (warning) 'I' in format string (no. 1) is a length modifier and cannot be used without a conversion specifier. [invalidLengthModifierError]\n" "[test.cpp:5:5]: (warning) 'I' in format string (no. 1) is a length modifier and cannot be used without a conversion specifier. [invalidLengthModifierError]\n" "[test.cpp:6:5]: (warning) 'I' in format string (no. 1) is a length modifier and cannot be used without a conversion specifier. [invalidLengthModifierError]\n" @@ -4375,7 +4505,7 @@ class TestIO : public TestFixture { " scanf(\"%zd\", &s);\n" " scanf(\"%zd%i\", &s, &i);\n" " scanf(\"%zu\", &s);\n" - "}", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win32A)); + "}\n", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win32A)); ASSERT_EQUALS("[test.cpp:6:5]: (portability) %zu in format string (no. 1) requires 'size_t *' but the argument type is 'SSIZE_T * {aka signed long *}'. [invalidScanfArgType_int]\n", errout_str()); check("void foo() {\n" @@ -4384,7 +4514,7 @@ class TestIO : public TestFixture { " scanf(\"%zd\", &s);\n" " scanf(\"%zd%i\", &s, &i);\n" " scanf(\"%zu\", &s);\n" - "}", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win64)); + "}\n", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win64)); ASSERT_EQUALS("[test.cpp:6:5]: (portability) %zu in format string (no. 1) requires 'size_t *' but the argument type is 'SSIZE_T * {aka signed long long *}'. [invalidScanfArgType_int]\n", errout_str()); check("void foo() {\n" @@ -4393,7 +4523,7 @@ class TestIO : public TestFixture { " scanf(\"%zd\", &s);\n" " scanf(\"%zd%i\", &s, &i);\n" " scanf(\"%zu\", &s);\n" - "}", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Unix64)); + "}\n", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Unix64)); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" @@ -4403,7 +4533,7 @@ class TestIO : public TestFixture { " scanf(\"%zd\", &s);\n" " scanf(\"%zd%i\", &s, &i);\n" " scanf(\"%zu\", &s);\n" - "}", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win64)); + "}\n", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win64)); ASSERT_EQUALS("[test.cpp:7:5]: (portability) %zu in format string (no. 1) requires 'size_t *' but the argument type is 'SSIZE_T * {aka signed long long *}'. [invalidScanfArgType_int]\n", errout_str()); } @@ -4414,7 +4544,7 @@ class TestIO : public TestFixture { " String string;\n" " string.Format(\"%I32d\", u32);\n" " string.AppendFormat(\"%I32d\", u32);\n" - "}", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win32A)); + "}\n", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win32A)); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" @@ -4422,7 +4552,7 @@ class TestIO : public TestFixture { " CString string;\n" " string.Format(\"%I32d\", u32);\n" " string.AppendFormat(\"%I32d\", u32);\n" - "}", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Unix32)); + "}\n", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Unix32)); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" @@ -4431,7 +4561,7 @@ class TestIO : public TestFixture { " string.Format(\"%I32d\", u32);\n" " string.AppendFormat(\"%I32d\", u32);\n" " CString::Format(\"%I32d\", u32);\n" - "}", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win32A)); + "}\n", dinit(CheckOptions, $.portability = true, $.platform = Platform::Type::Win32A)); ASSERT_EQUALS("[test.cpp:4:12]: (portability) %I32d in format string (no. 1) requires '__int32' but the argument type is 'unsigned __int32 {aka unsigned int}'. [invalidPrintfArgType_sint]\n" "[test.cpp:5:12]: (portability) %I32d in format string (no. 1) requires '__int32' but the argument type is 'unsigned __int32 {aka unsigned int}'. [invalidPrintfArgType_sint]\n" "[test.cpp:6:14]: (portability) %I32d in format string (no. 1) requires '__int32' but the argument type is 'unsigned __int32 {aka unsigned int}'. [invalidPrintfArgType_sint]\n", errout_str()); @@ -4661,6 +4791,18 @@ class TestIO : public TestFixture { "[test.cpp:17:5]: (warning) %s in format string (no. 5) requires 'char *' but the argument type is 'signed int'. [invalidPrintfArgType_s]\n" "[test.cpp:17:5]: (warning) sprintf_s format string requires 5 parameters but 6 are given. [wrongPrintfScanfArgNum]\n", errout_str()); + check("int main()\n" + "{\n" + " double value = 3.14;\n" + " const size_t buffer_size = 64;\n" + " char buffer[buffer_size];\n" + " int precision = 2;\n" + " _locale_t locale = _create_locale(LC_ALL, \"C\");\n" + " _sprintf_s_l(buffer, buffer_size, \"%.*f\", locale, precision, value);\n" + " _free_locale(locale);\n" + " return 0;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); } void testMicrosoftSecureScanfArgument() { @@ -4813,20 +4955,20 @@ class TestIO : public TestFixture { check("void foo(float f) {\n" " QString string;\n" " string.sprintf(\"%d\", f);\n" - "}", dinit(CheckOptions, $.platform = Platform::Type::Win32A)); + "}\n", dinit(CheckOptions, $.platform = Platform::Type::Win32A)); ASSERT_EQUALS("[test.cpp:3:12]: (warning) %d in format string (no. 1) requires 'int' but the argument type is 'float'. [invalidPrintfArgType_sint]\n", errout_str()); check("void foo(float f) {\n" " QString string;\n" " string = QString::asprintf(\"%d\", f);\n" - "}", dinit(CheckOptions, $.platform = Platform::Type::Win32A)); + "}\n", dinit(CheckOptions, $.platform = Platform::Type::Win32A)); ASSERT_EQUALS("[test.cpp:3:23]: (warning) %d in format string (no. 1) requires 'int' but the argument type is 'float'. [invalidPrintfArgType_sint]\n", errout_str()); } void testTernary() { // ticket #6182 check("void test(const std::string &val) {\n" " printf(\"%s\", val.empty() ? \"I like to eat bananas\" : val.c_str());\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -4834,30 +4976,30 @@ class TestIO : public TestFixture { check("void test() {\n" " unsigned const x = 5;\n" " printf(\"%u\", x);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } void testAstType() { // ticket #7014 check("void test() {\n" " printf(\"%c\", \"hello\"[0]);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void test() {\n" " printf(\"%lld\", (long long)1);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void test() {\n" " printf(\"%i\", (short *)x);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:5]: (warning) %i in format string (no. 1) requires 'int' but the argument type is 'signed short *'. [invalidPrintfArgType_sint]\n", errout_str()); check("int (*fp)();\n" // #7178 - function pointer call "void test() {\n" " printf(\"%i\", fp());\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -4865,7 +5007,7 @@ class TestIO : public TestFixture { check("void foo() {\n" " printf(\"%u %lu %llu\", 0U, 0UL, 0ULL);\n" " printf(\"%u %lu %llu\", 0u, 0ul, 0ull);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -4873,7 +5015,7 @@ class TestIO : public TestFixture { check("void f() {\n" " const char *s = \"0\";\n" " printf(\"%ld%lld\", atol(s), atoll(s));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // 8141 @@ -4892,7 +5034,7 @@ class TestIO : public TestFixture { "PCINT pci;" "void foo() {\n" " printf(\"%d %p %p\", i, pi, pci);\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("using INT = int;\n\n" @@ -4903,7 +5045,7 @@ class TestIO : public TestFixture { "PCINT pci;" "void foo() {\n" " printf(\"%f %f %f\", i, pi, pci);\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:8:5]: (warning) %f in format string (no. 1) requires 'double' but the argument type is 'signed int'. [invalidPrintfArgType_float]\n" "[test.cpp:8:5]: (warning) %f in format string (no. 2) requires 'double' but the argument type is 'signed int *'. [invalidPrintfArgType_float]\n" "[test.cpp:8:5]: (warning) %f in format string (no. 3) requires 'double' but the argument type is 'const signed int *'. [invalidPrintfArgType_float]\n", errout_str()); @@ -4921,17 +5063,17 @@ class TestIO : public TestFixture { void testPrintfParenthesis() { // #8489 check("void f(int a) {\n" " printf(\"%f\", (a >> 24) & 0xff);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:5]: (warning) %f in format string (no. 1) requires 'double' but the argument type is 'signed int'. [invalidPrintfArgType_float]\n", errout_str()); check("void f(int a) {\n" " printf(\"%f\", 0xff & (a >> 24));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:5]: (warning) %f in format string (no. 1) requires 'double' but the argument type is 'signed int'. [invalidPrintfArgType_float]\n", errout_str()); check("void f(int a) {\n" " printf(\"%f\", ((a >> 24) + 1) & 0xff);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:5]: (warning) %f in format string (no. 1) requires 'double' but the argument type is 'signed int'. [invalidPrintfArgType_float]\n", errout_str()); } @@ -4939,7 +5081,7 @@ class TestIO : public TestFixture { check("void foo(const std::vector& IO, const int* pio) {\n" "const auto Idx = std::distance(&IO.front(), pio);\n" "printf(\"Idx = %td\", Idx);\n" - "}", dinit(CheckOptions, $.portability = true)); + "}\n", dinit(CheckOptions, $.portability = true)); ASSERT_EQUALS("", errout_str()); } diff --git a/test/testleakautovar.cpp b/test/testleakautovar.cpp index 300db8d9855..f644958c13c 100644 --- a/test/testleakautovar.cpp +++ b/test/testleakautovar.cpp @@ -154,6 +154,7 @@ class TestLeakAutoVar : public TestFixture { TEST_CASE(ifelse27); TEST_CASE(ifelse28); // #11038 TEST_CASE(ifelse29); + TEST_CASE(ifelse30); // switch TEST_CASE(switch1); @@ -201,6 +202,7 @@ class TestLeakAutoVar : public TestFixture { TEST_CASE(configuration4); TEST_CASE(configuration5); TEST_CASE(configuration6); + TEST_CASE(configurationUse); TEST_CASE(ptrptr); @@ -210,6 +212,7 @@ class TestLeakAutoVar : public TestFixture { TEST_CASE(inlineFunction); // #3989 TEST_CASE(smartPtrInContainer); // #8262 + TEST_CASE(unconditionalScope); TEST_CASE(functionCallCastConfig); // #9652 TEST_CASE(functionCallLeakIgnoreConfig); // #7923 @@ -225,23 +228,17 @@ class TestLeakAutoVar : public TestFixture { template void check_(const char* file, int line, const char (&code)[size], const CheckOptions& options = make_default_obj()) { const Settings& settings1 = options.s ? *options.s : settings; - - // Tokenize.. - SimpleTokenizer tokenizer(settings1, *this, options.cpp); - ASSERT_LOC(tokenizer.tokenize(code), file, line); - - // Check for leaks.. - runChecks(tokenizer, this); + check_(file, line, code, settings1, options.cpp); } template - void check_(const char* file, int line, const char (&code)[size], const Settings & s) { + void check_(const char* file, int line, const char (&code)[size], const Settings & s, bool cpp = true) { // Tokenize.. - SimpleTokenizer tokenizer(s, *this); + SimpleTokenizer tokenizer(s, *this, cpp); ASSERT_LOC(tokenizer.tokenize(code), file, line); - // Check for leaks.. - runChecks(tokenizer, this); + CheckLeakAutoVar check; + runChecks(check, tokenizer, *this); } void assign1() { @@ -249,7 +246,7 @@ class TestLeakAutoVar : public TestFixture { " char *p = malloc(10);\n" " p = NULL;\n" " free(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:3:5]: (error) Memory leak: p [memleak]\n", errout_str()); } @@ -258,7 +255,7 @@ class TestLeakAutoVar : public TestFixture { " char *p = malloc(10);\n" " char *q = p;\n" " free(q);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -267,7 +264,7 @@ class TestLeakAutoVar : public TestFixture { " char *p = malloc(10);\n" " char *q = p + 1;\n" " free(q - 1);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -276,7 +273,7 @@ class TestLeakAutoVar : public TestFixture { " char *a = malloc(10);\n" " a += 10;\n" " free(a - 10);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -285,7 +282,7 @@ class TestLeakAutoVar : public TestFixture { "{\n" " char *p = new char[100];\n" " list += p;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -294,7 +291,7 @@ class TestLeakAutoVar : public TestFixture { " char *p = malloc(10);\n" " p = strcpy(p,q);\n" " free(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -302,7 +299,7 @@ class TestLeakAutoVar : public TestFixture { check("void foo(struct str *d) {\n" " struct str *p = malloc(10);\n" " d->p = p;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -310,7 +307,7 @@ class TestLeakAutoVar : public TestFixture { check("void foo(struct str *d) {\n" " struct str *p = malloc(10);\n" " d->p = &p->x;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -319,7 +316,7 @@ class TestLeakAutoVar : public TestFixture { " char *p = x();\n" " free(p);\n" " p = NULL;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -329,7 +326,7 @@ class TestLeakAutoVar : public TestFixture { " if (x) { p = malloc(10); }\n" " if (!x) { p = NULL; }\n" " free(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -337,7 +334,7 @@ class TestLeakAutoVar : public TestFixture { check("void f() {\n" " char *p = malloc(10);\n" " x = a(b(p));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:4:1]: (information) --check-library: Function b() should have / configuration [checkLibraryUseIgnore]\n", errout_str()); } @@ -347,7 +344,7 @@ class TestLeakAutoVar : public TestFixture { " free(p);\n" " bar(&p);\n" " free(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -357,22 +354,22 @@ class TestLeakAutoVar : public TestFixture { " char * &ref = p;\n" " p = malloc(10);\n" " free(ref);\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); TODO_ASSERT_EQUALS("", "[test.cpp:6:1]: (error) Memory leak: p [memleak]\n", errout_str()); } void assign14() { check("void f(int x) {\n" " char *p;\n" - " if (x && (p = malloc(10))) { }" - "}"); - ASSERT_EQUALS("[test.c:3:35]: (error) Memory leak: p [memleak]\n", errout_str()); + " if (x && (p = malloc(10))) { }\n" + "}\n"); + ASSERT_EQUALS("[test.c:4:1]: (error) Memory leak: p [memleak]\n", errout_str()); check("void f(int x) {\n" " char *p;\n" - " if (x && (p = new char[10])) { }" - "}", dinit(CheckOptions, $.cpp = true)); - ASSERT_EQUALS("[test.cpp:3:37]: (error) Memory leak: p [memleak]\n", errout_str()); + " if (x && (p = new char[10])) { }\n" + "}\n", dinit(CheckOptions, $.cpp = true)); + ASSERT_EQUALS("[test.cpp:4:1]: (error) Memory leak: p [memleak]\n", errout_str()); } void assign15() { @@ -383,7 +380,7 @@ class TestLeakAutoVar : public TestFixture { " free(p);\n" " p = malloc(sizeof *p);\n" " free(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -392,48 +389,48 @@ class TestLeakAutoVar : public TestFixture { " char *p = malloc(10);\n" " free(p);\n" " if (p=dostuff()) *p = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } void assign17() { // #9047 check("void f() {\n" " char *p = (char*)malloc(10);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:3:1]: (error) Memory leak: p [memleak]\n", errout_str()); check("void f() {\n" " char *p = (char*)(int*)malloc(10);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:3:1]: (error) Memory leak: p [memleak]\n", errout_str()); } void assign18() { check("void f(int x) {\n" " char *p;\n" - " if (x && (p = (char*)malloc(10))) { }" - "}"); - ASSERT_EQUALS("[test.c:3:42]: (error) Memory leak: p [memleak]\n", errout_str()); + " if (x && (p = (char*)malloc(10))) { }\n" + "}\n"); + ASSERT_EQUALS("[test.c:4:1]: (error) Memory leak: p [memleak]\n", errout_str()); check("void f(int x) {\n" " char *p;\n" - " if (x && (p = (char*)(int*)malloc(10))) { }" - "}"); - ASSERT_EQUALS("[test.c:3:48]: (error) Memory leak: p [memleak]\n", errout_str()); + " if (x && (p = (char*)(int*)malloc(10))) { }\n" + "}\n"); + ASSERT_EQUALS("[test.c:4:1]: (error) Memory leak: p [memleak]\n", errout_str()); } void assign19() { check("void f() {\n" " char *p = malloc(10);\n" " free((void*)p);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } void assign20() { // #9187 check("void f() {\n" " char *p = static_cast(malloc(10));\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("[test.cpp:3:1]: (error) Memory leak: p [memleak]\n", errout_str()); } @@ -441,14 +438,14 @@ class TestLeakAutoVar : public TestFixture { check("void f(int **x) {\n" // #10186 " void *p = malloc(10);\n" " *x = (int*)p;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check("struct S { int i; };\n" // #10996 "void f() {\n" " S* s = new S();\n" " (void)s;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("[test.cpp:5:1]: (error) Memory leak: s [memleak]\n", errout_str()); } @@ -456,12 +453,12 @@ class TestLeakAutoVar : public TestFixture { const Settings s = settingsBuilder().library("posix.cfg").checkLibrary().build(); check("void f(char tempFileName[256]) {\n" " const int fd = socket(AF_INET, SOCK_PACKET, 0 );\n" - "}", dinit(CheckOptions, $.cpp = true, $.s = &s)); + "}\n", dinit(CheckOptions, $.cpp = true, $.s = &s)); ASSERT_EQUALS("[test.cpp:3:1]: (error) Resource leak: fd [resourceLeak]\n", errout_str()); check("void f() {\n" " const void * const p = malloc(10);\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("[test.cpp:3:1]: (error) Memory leak: p [memleak]\n", errout_str()); } @@ -569,7 +566,7 @@ class TestLeakAutoVar : public TestFixture { check("void f() {\n" // #11796 " int* p{ new int };\n" " int* q(new int);\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("[test.cpp:4:1]: (error) Memory leak: p [memleak]\n" "[test.cpp:4:1]: (error) Memory leak: q [memleak]\n", errout_str()); @@ -633,13 +630,13 @@ class TestLeakAutoVar : public TestFixture { check("void f(int*& x) {\n" // #8235 " int* p = (int*)malloc(10);\n" " x = p ? p : nullptr;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check("void f(int*& x) {\n" " int* p = (int*)malloc(10);\n" " x = p != nullptr ? p : nullptr;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check("void f(const char* n) {\n" // #12724 @@ -670,17 +667,17 @@ class TestLeakAutoVar : public TestFixture { void isAutoDealloc() { check("void f() {\n" " char *p = new char[100];" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("[test.cpp:2:29]: (error) Memory leak: p [memleak]\n", errout_str()); check("void f() {\n" " Fred *fred = new Fred;" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " std::string *str = new std::string;" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("[test.cpp:2:40]: (error) Memory leak: str [memleak]\n", errout_str()); check("class TestType {\n" // #9028 @@ -689,13 +686,13 @@ class TestLeakAutoVar : public TestFixture { "};\n" "void f() {\n" " TestType *tt = new TestType();\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("[test.cpp:7:1]: (error) Memory leak: tt [memleak]\n", errout_str()); check("void f(Bar& b) {\n" // #7622 " char* data = new char[10];\n" " b = Bar(*new Foo(data));\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("[test.cpp:4:1]: (information) --check-library: Function Foo() should have / configuration [checkLibraryUseIgnore]\n", errout_str()); check("class B {};\n" @@ -703,7 +700,7 @@ class TestLeakAutoVar : public TestFixture { " void g() {\n" " auto d = new D();\n" " if (d) {}\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("[test.cpp:6:1]: (error) Memory leak: d [memleak]\n", errout_str()); check("struct S {\n" // #12354 @@ -714,7 +711,7 @@ class TestLeakAutoVar : public TestFixture { " if (b)\n" " p = new S();\n" " p->f();\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("[test.cpp:9:1]: (error) Memory leak: p [memleak]\n", errout_str()); } @@ -723,7 +720,7 @@ class TestLeakAutoVar : public TestFixture { " void *p = malloc(10);\n" " void *q = realloc(p, 20);\n" " free(q)\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -731,7 +728,7 @@ class TestLeakAutoVar : public TestFixture { check("void f() {\n" " void *p = malloc(10);\n" " void *q = realloc(p, 20);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:4:1]: (error) Memory leak: q [memleak]\n", errout_str()); } @@ -739,7 +736,7 @@ class TestLeakAutoVar : public TestFixture { check("void f() {\n" " char *p = malloc(10);\n" " char *q = (char*) realloc(p, 20);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:4:1]: (error) Memory leak: q [memleak]\n", errout_str()); } @@ -748,7 +745,7 @@ class TestLeakAutoVar : public TestFixture { " void * q = realloc(p, 10);\n" " if (q == NULL)\n" " return;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:5:1]: (error) Memory leak: q [memleak]\n", errout_str()); } @@ -759,7 +756,7 @@ class TestLeakAutoVar : public TestFixture { " if (size && !datap)\n" " free(ptr);\n" " return datap;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #9990 @@ -773,7 +770,7 @@ class TestLeakAutoVar : public TestFixture { " return;\n" " }\n" " free(p2);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -782,7 +779,7 @@ class TestLeakAutoVar : public TestFixture { " void *p = fopen(name,a);\n" " void *q = freopen(name, b, p);\n" " fclose(q)\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -790,7 +787,7 @@ class TestLeakAutoVar : public TestFixture { check("void f() {\n" " void *p = fopen(name,a);\n" " void *q = freopen(name, b, p);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:4:1]: (error) Resource leak: q [resourceLeak]\n", errout_str()); } @@ -798,13 +795,13 @@ class TestLeakAutoVar : public TestFixture { check("void f(char *p) {\n" " free(p);\n" " *p = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:3:6]: (error) Dereferencing 'p' after it is deallocated / released [deallocuse]\n", errout_str()); check("void f(char *p) {\n" " free(p);\n" " char c = *p;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:3:15]: (error) Dereferencing 'p' after it is deallocated / released [deallocuse]\n", errout_str()); } @@ -812,7 +809,7 @@ class TestLeakAutoVar : public TestFixture { check("void f(struct str *p) {\n" " free(p);\n" " p = p->next;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:3:9]: (error) Dereferencing 'p' after it is deallocated / released [deallocuse]\n", errout_str()); } @@ -820,25 +817,25 @@ class TestLeakAutoVar : public TestFixture { check("void f(char *p) {\n" " free(p);\n" " return p;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:2:5] -> [test.c:3:5]: (error) Returning/dereferencing 'p' after it is deallocated / released [deallocret]\n", errout_str()); check("void f(char *p) {\n" " if (!p) free(p);\n" " return p;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(char *p) {\n" " if (!p) delete p;\n" " return p;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check("void f(char *p) {\n" " if (!p) delete [] p;\n" " return p;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check("void f(void* p) {\n" @@ -848,7 +845,7 @@ class TestLeakAutoVar : public TestFixture { " }\n" " g(p);\n" " return;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -856,7 +853,7 @@ class TestLeakAutoVar : public TestFixture { check("void f(char *p) {\n" " free(p), p = 0;\n" " *p = 0;\n" // <- Make sure pointer info is reset. It is NOT a freed pointer dereference - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -864,7 +861,7 @@ class TestLeakAutoVar : public TestFixture { check("void f(char *p) {\n" " free(p);\n" " x = p = foo();\n" // <- p is not dereferenced - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -873,14 +870,14 @@ class TestLeakAutoVar : public TestFixture { "void f(Foo* foo) {\n" " delete foo->ptr;\n" " foo->ptr = new Foo;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check("struct Foo { int* ptr; };\n" "void f(Foo* foo) {\n" " delete foo->ptr;\n" " x = *foo->ptr;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); TODO_ASSERT_EQUALS("[test.cpp:4:6]: (error) Dereferencing 'ptr' after it is deallocated / released [deallocuse]\n", "", errout_str()); check("void parse() {\n" @@ -889,7 +886,7 @@ class TestLeakAutoVar : public TestFixture { " ~Buf() { delete[]m_buf; }\n" " uint8_t *m_buf;\n" " };\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check("struct Foo {\n" @@ -901,14 +898,14 @@ class TestLeakAutoVar : public TestFixture { " delete foo->ptr;\n" " foo->ptr = new Foo;\n" " foo->ptr->func();\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check("void foo(void (*conv)(char**)) {\n" " char * ptr=(char*)malloc(42);\n" " free(ptr);\n" " (*conv)(&ptr);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -917,7 +914,7 @@ class TestLeakAutoVar : public TestFixture { " int *ptr = new int;\n" " delete(ptr);\n" " *ptr = 0;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("[test.cpp:4:6]: (error) Dereferencing 'ptr' after it is deallocated / released [deallocuse]\n", errout_str()); } @@ -968,14 +965,14 @@ class TestLeakAutoVar : public TestFixture { " int *array = new int[42];\n" " delete [] array;\n" " return array[1];" // << - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("[test.cpp:3:3] -> [test.cpp:4:3]: (error) Returning/dereferencing 'array' after it is deallocated / released [deallocret]\n", errout_str()); check("int f() {\n" " int *array = (int*)malloc(40);\n" " free(array);\n" " return array[1];" // << - "}"); + "}\n"); ASSERT_EQUALS("[test.c:3:3] -> [test.c:4:3]: (error) Returning/dereferencing 'array' after it is deallocated / released [deallocret]\n", errout_str()); } @@ -986,7 +983,7 @@ class TestLeakAutoVar : public TestFixture { "}\n" "void f2(struct foo *f, int *out) {\n" " *out = f->x;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1124,35 +1121,35 @@ class TestLeakAutoVar : public TestFixture { " else\n" " p = 0;\n" " free(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:3:9] -> [test.c:6:5]: (error) Memory pointed to by 'p' is freed twice. [doubleFree]\n", errout_str()); check( "void foo(char *p) {\n" " free(p);\n" " free(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:2:3] -> [test.c:3:3]: (error) Memory pointed to by 'p' is freed twice. [doubleFree]\n", errout_str()); check( "void foo(char *p, char *r) {\n" " free(p);\n" " free(r);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check( "void foo() {\n" " free(p);\n" " free(r);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check( "void foo(char *p) {\n" " if (x < 3) free(p);\n" " else { if (x > 9) free(p); }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check( @@ -1160,7 +1157,7 @@ class TestLeakAutoVar : public TestFixture { " free(p);\n" " getNext(&p);\n" " free(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check( @@ -1168,7 +1165,7 @@ class TestLeakAutoVar : public TestFixture { " free(p);\n" " bar();\n" " free(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:2:3] -> [test.c:4:3]: (error) Memory pointed to by 'p' is freed twice. [doubleFree]\n", errout_str()); check( @@ -1176,7 +1173,7 @@ class TestLeakAutoVar : public TestFixture { " free(p);\n" " printf(\"Freed memory at location %x\", p);\n" " free(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:3:41]: (error) Dereferencing 'p' after it is deallocated / released [deallocuse]\n" "[test.c:2:3] -> [test.c:4:3]: (error) Memory pointed to by 'p' is freed twice. [doubleFree]\n", errout_str()); @@ -1185,21 +1182,21 @@ class TestLeakAutoVar : public TestFixture { "void foo(FILE *p) {\n" " fclose(p);\n" " fclose(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:2:3] -> [test.c:3:3]: (error) Resource handle 'p' freed twice. [doubleFree]\n", errout_str()); check( "void foo(FILE *p, FILE *r) {\n" " fclose(p);\n" " fclose(r);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check( "void foo(FILE *p) {\n" " if (x < 3) fclose(p);\n" " else { if (x > 9) fclose(p); }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check( @@ -1207,7 +1204,7 @@ class TestLeakAutoVar : public TestFixture { " fclose(p);\n" " gethandle(&p);\n" " fclose(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check( @@ -1215,14 +1212,14 @@ class TestLeakAutoVar : public TestFixture { " fclose(p);\n" " gethandle();\n" " fclose(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:2:3] -> [test.c:4:3]: (error) Resource handle 'p' freed twice. [doubleFree]\n", errout_str()); check( "void foo(Data* p) {\n" " free(p->a);\n" " free(p->b);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check( @@ -1233,7 +1230,7 @@ class TestLeakAutoVar : public TestFixture { " exit();\n" " }\n" " free(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check( @@ -1244,7 +1241,7 @@ class TestLeakAutoVar : public TestFixture { " x = 0;\n" " }\n" " free(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:4:9] -> [test.c:7:5]: (error) Memory pointed to by 'p' is freed twice. [doubleFree]\n", errout_str()); check( @@ -1253,35 +1250,35 @@ class TestLeakAutoVar : public TestFixture { " free(p);\n" " p = do_something();\n" " free(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check( "void foo(char *p) {\n" " delete p;\n" " delete p;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("[test.cpp:2:10] -> [test.cpp:3:10]: (error) Memory pointed to by 'p' is freed twice. [doubleFree]\n", errout_str()); check( "void foo(char *p, char *r) {\n" " delete p;\n" " delete r;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check( "void foo(P p) {\n" " delete p.x;\n" " delete p;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check( "void foo(char **p) {\n" " delete p[0];\n" " delete p[1];\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check( @@ -1289,7 +1286,7 @@ class TestLeakAutoVar : public TestFixture { " delete p;\n" " getNext(&p);\n" " delete p;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check( @@ -1297,21 +1294,21 @@ class TestLeakAutoVar : public TestFixture { " delete p;\n" " bar();\n" " delete p;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("[test.cpp:2:10] -> [test.cpp:4:10]: (error) Memory pointed to by 'p' is freed twice. [doubleFree]\n", errout_str()); check( "void foo(char *p) {\n" " delete[] p;\n" " delete[] p;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("[test.cpp:2:12] -> [test.cpp:3:12]: (error) Memory pointed to by 'p' is freed twice. [doubleFree]\n", errout_str()); check( "void foo(char *p, char *r) {\n" " delete[] p;\n" " delete[] r;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check( @@ -1319,7 +1316,7 @@ class TestLeakAutoVar : public TestFixture { " delete[] p;\n" " getNext(&p);\n" " delete[] p;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check( @@ -1327,7 +1324,7 @@ class TestLeakAutoVar : public TestFixture { " delete[] p;\n" " bar();\n" " delete[] p;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("[test.cpp:2:12] -> [test.cpp:4:12]: (error) Memory pointed to by 'p' is freed twice. [doubleFree]\n", errout_str()); check( @@ -1338,7 +1335,7 @@ class TestLeakAutoVar : public TestFixture { " delete pxpm;\n" " pxpm = NULL;\n" " return *this;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check( @@ -1355,7 +1352,7 @@ class TestLeakAutoVar : public TestFixture { " throw;\n" " }\n" " delete ptr;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check( @@ -1371,7 +1368,7 @@ class TestLeakAutoVar : public TestFixture { " if(doDelete)\n" " delete a;\n" " return 0;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); TODO_ASSERT_EQUALS("", "[test.cpp:8:8] -> [test.cpp:11:15]: (error) Memory pointed to by 'a' is freed twice. [doubleFree]\n", errout_str()); check( @@ -1385,7 +1382,7 @@ class TestLeakAutoVar : public TestFixture { " delete[] x;\n" " }\n" " delete[] x;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check( @@ -1397,7 +1394,7 @@ class TestLeakAutoVar : public TestFixture { " delete[] x;\n" " }\n" " delete[] x;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); TODO_ASSERT_EQUALS("[test.cpp:8]: (error) Memory pointed to by 'x' is freed twice.\n", "", errout_str()); check( @@ -1409,7 +1406,7 @@ class TestLeakAutoVar : public TestFixture { " delete[] x;\n" " }\n" " delete[] x;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); TODO_ASSERT_EQUALS("[test.cpp:8]: (error) Memory pointed to by 'x' is freed twice.\n", "", errout_str()); check( @@ -1421,7 +1418,7 @@ class TestLeakAutoVar : public TestFixture { " free(x);\n" " }\n" " free(x);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.c:8]: (error) Memory pointed to by 'x' is freed twice.\n", "", errout_str()); check( @@ -1435,7 +1432,7 @@ class TestLeakAutoVar : public TestFixture { " delete[] x;\n" " }\n" " delete[] x;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check( @@ -1449,7 +1446,7 @@ class TestLeakAutoVar : public TestFixture { " delete[] x;\n" " } while (true);\n" " delete[] x;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check( @@ -1461,7 +1458,7 @@ class TestLeakAutoVar : public TestFixture { " free(p);\n" " }\n" " free(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:6:9] -> [test.c:8:5]: (error) Memory pointed to by 'p' is freed twice. [doubleFree]\n", errout_str()); check( @@ -1482,7 +1479,7 @@ class TestLeakAutoVar : public TestFixture { "void MyThrow(err)\n" "{\n" " throw(err);\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check( @@ -1503,7 +1500,7 @@ class TestLeakAutoVar : public TestFixture { "void MyExit(err)\n" "{\n" " exit(err);\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check( // #6252 @@ -1518,14 +1515,14 @@ class TestLeakAutoVar : public TestFixture { " delete m_thing;\n" " m_thing = new Thing;\n" " }\n" - "};", dinit(CheckOptions, $.cpp = true)); + "};\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); // #7401 check("void pCodeLabelDestruct(pCode *pc) {\n" " free(PCL(pc)->label);\n" " free(pc);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1535,7 +1532,7 @@ class TestLeakAutoVar : public TestFixture { " if (a == 2) { free(p); return ((void*)1); }\n" " free(p);\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1552,7 +1549,7 @@ class TestLeakAutoVar : public TestFixture { " free(bar)\n" " } while(!done);\n" " return;" - "}" + "}\n" ); ASSERT_EQUALS("", errout_str()); } @@ -1564,7 +1561,7 @@ class TestLeakAutoVar : public TestFixture { " exit(1);\n" " }\n" " free(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(void* p, int i) {\n" // #11391 @@ -1584,7 +1581,7 @@ class TestLeakAutoVar : public TestFixture { " free(p);\n" " x = (q == p);\n" " free(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:2:3] -> [test.c:4:3]: (error) Memory pointed to by 'p' is freed twice. [doubleFree]\n", errout_str()); } @@ -1592,7 +1589,7 @@ class TestLeakAutoVar : public TestFixture { check("void do_wordexp(FILE *f) {\n" " free(getword(f));\n" " fclose(f);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1601,14 +1598,14 @@ class TestLeakAutoVar : public TestFixture { " free(p);\n" " if (x && (p = malloc(10)))\n" " free(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(char *p, int x) {\n" " delete[] p;\n" " if (x && (p = new char[10]))\n" " delete[] p;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); } @@ -1721,7 +1718,7 @@ class TestLeakAutoVar : public TestFixture { " if (q == NULL)\n" " return;\n" " free(q);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:3:16] -> [test.c:8:5]: (error) Memory pointed to by 'p' is freed twice. [doubleFree]\n", errout_str()); } @@ -1757,7 +1754,7 @@ class TestLeakAutoVar : public TestFixture { " int *i = new int;\n" " unique_ptr x(i);\n" " delete i;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("[test.cpp:6:5] -> [test.cpp:7:12]: (error) Memory pointed to by 'i' is freed twice. [doubleFree]\n", errout_str()); check("using namespace std;\n" @@ -1766,14 +1763,14 @@ class TestLeakAutoVar : public TestFixture { "{\n" " int *i = new int;\n" " unique_ptr x(i);\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); } void doublefree15() { // #11966 check("void f(FILE* fp) {\n" " static_cast(fclose(fp));\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); } @@ -1835,7 +1832,7 @@ class TestLeakAutoVar : public TestFixture { check("void f() {\n" " char *p = malloc(10);\n" " exit(0);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1843,7 +1840,7 @@ class TestLeakAutoVar : public TestFixture { check("void f() {\n" " char *p = malloc(10);\n" " fatal_error();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:3:18]: (information) --check-library: Function fatal_error() should have configuration [checkLibraryNoReturn]\n", errout_str()); } @@ -1854,9 +1851,9 @@ class TestLeakAutoVar : public TestFixture { " if (x) {\n" " free(p);\n" " ::exit(0);\n" - " }" + " }\n" " free(p);\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -1864,9 +1861,9 @@ class TestLeakAutoVar : public TestFixture { " if (x) {\n" " free(p);\n" " std::exit(0);\n" - " }" + " }\n" " free(p);\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); } @@ -1879,7 +1876,7 @@ class TestLeakAutoVar : public TestFixture { " func_notret();\n" " }\n" " free(ptr);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1888,7 +1885,7 @@ class TestLeakAutoVar : public TestFixture { " p->x = malloc(10);\n" " free(p->x);\n" " p->x = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(s_t s) {\n" // #11061 @@ -1910,7 +1907,7 @@ class TestLeakAutoVar : public TestFixture { check("void g(size_t len) {\n" // #12365 " char* b = new char[len + 1]{};\n" " std::string str = std::string(b);\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("[test.cpp:4:1]: (error) Memory leak: b [memleak]\n", errout_str()); } @@ -1921,7 +1918,7 @@ class TestLeakAutoVar : public TestFixture { " if (err) {\n" " free(reg);\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1934,7 +1931,7 @@ class TestLeakAutoVar : public TestFixture { " goto x;\n" " }\n" " return p;\n" // no error since there is a goto - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1959,7 +1956,7 @@ class TestLeakAutoVar : public TestFixture { " if (x) { p = malloc(10); }\n" " else { return 0; }\n" " free(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1968,7 +1965,7 @@ class TestLeakAutoVar : public TestFixture { " char *p = NULL;\n" " if (x) { p = malloc(10); }\n" " else { return 0; }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:5:1]: (error) Memory leak: p [memleak]\n", errout_str()); } @@ -1977,7 +1974,7 @@ class TestLeakAutoVar : public TestFixture { " char *p = malloc(10);\n" " if (!p) { return; }\n" " free(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("char * f(size_t size) {" @@ -1985,14 +1982,14 @@ class TestLeakAutoVar : public TestFixture { " if (!p && size != 0)" " return NULL;" " return p;" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " char *p = malloc(10);\n" " if (p) { } else { return; }\n" " free(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #3866 - UNLIKELY @@ -2000,7 +1997,7 @@ class TestLeakAutoVar : public TestFixture { " char *p = malloc(10);\n" " if (UNLIKELY(!p)) { return; }\n" " free(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2009,7 +2006,7 @@ class TestLeakAutoVar : public TestFixture { " char *p;\n" " if (x) { p = malloc(10); }\n" " if (x) { free(p); }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" @@ -2017,7 +2014,7 @@ class TestLeakAutoVar : public TestFixture { " if (x) { p = malloc(10); }\n" " if (!x) { return; }\n" " free(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2026,7 +2023,7 @@ class TestLeakAutoVar : public TestFixture { " char *p = malloc(10);\n" " if (!p && x) { p = malloc(10); }\n" " free(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2037,7 +2034,7 @@ class TestLeakAutoVar : public TestFixture { " free(a);\n" " else\n" " a = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:6:9]: (error) Memory leak: a [memleak]\n", errout_str()); } @@ -2047,7 +2044,7 @@ class TestLeakAutoVar : public TestFixture { " if (x < 0)\n" // assume negative value indicates its unallocated " return;\n" " free(x);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2057,7 +2054,7 @@ class TestLeakAutoVar : public TestFixture { " if (fd == -1)\n" " return -1;\n" " return fd;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int f() {\n" @@ -2065,7 +2062,7 @@ class TestLeakAutoVar : public TestFixture { " if (fd != -1)\n" " return fd;\n" " return -1;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2075,7 +2072,7 @@ class TestLeakAutoVar : public TestFixture { " if (dostuff(p==NULL,0))\n" " return;\n" " free(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2085,7 +2082,7 @@ class TestLeakAutoVar : public TestFixture { " if (!(x != NULL))\n" " return;\n" " free(x);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2095,7 +2092,7 @@ class TestLeakAutoVar : public TestFixture { " if (NULL == (p = malloc(4)))\n" " return;\n" " free(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2103,7 +2100,7 @@ class TestLeakAutoVar : public TestFixture { check("void f(char **p) {\n" " if ((*p = malloc(4)) == NULL)\n" " return;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2114,7 +2111,7 @@ class TestLeakAutoVar : public TestFixture { " return 1;\n" " free(path);\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int f(int fd, const char *mode) {\n" @@ -2123,7 +2120,7 @@ class TestLeakAutoVar : public TestFixture { " return 1;\n" // <- memory leak " free(path);\n" " return 0;\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:4] memory leak", "", errout_str()); } @@ -2133,7 +2130,7 @@ class TestLeakAutoVar : public TestFixture { " if (buf == (char*)NULL)\n" " return NULL;\n" " return buf;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2143,14 +2140,14 @@ class TestLeakAutoVar : public TestFixture { "void test_alloc() {\n" " if ( global_ptr = new SSS()) {}\n" " return;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check("FILE* hFile;\n" "int openFile( void ) {\n" " if ((hFile = fopen(\"1.txt\", \"wb\" )) == NULL) return 0;\n" " return 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2161,7 +2158,7 @@ class TestLeakAutoVar : public TestFixture { " return;\n" " free(p);\n" " return;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(void) {\n" @@ -2170,7 +2167,7 @@ class TestLeakAutoVar : public TestFixture { " return;\n" " free(p);\n" " return;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2180,7 +2177,7 @@ class TestLeakAutoVar : public TestFixture { " if (!p)\n" " return NULL;\n" " return p;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int *f() {\n" @@ -2188,7 +2185,7 @@ class TestLeakAutoVar : public TestFixture { " if (!!(!p))\n" " return NULL;\n" " return p;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2199,7 +2196,7 @@ class TestLeakAutoVar : public TestFixture { " if (q == 0)\n" " return;\n" " free(q);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:5:9]: (error) Memory leak: p [memleak]\n", errout_str()); check("void f() {\n" @@ -2210,7 +2207,7 @@ class TestLeakAutoVar : public TestFixture { " return;\n" " } else\n" " return;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:8:9]: (error) Memory leak: p [memleak]\n", errout_str()); } @@ -2221,7 +2218,7 @@ class TestLeakAutoVar : public TestFixture { " if (!b)\n" " return;\n" " a = b;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2232,7 +2229,7 @@ class TestLeakAutoVar : public TestFixture { " else\n" " void * p2 = malloc(2);\n" " return;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:3:30]: (error) Memory leak: p1 [memleak]\n" "[test.c:5:30]: (error) Memory leak: p2 [memleak]\n", errout_str()); @@ -2241,7 +2238,7 @@ class TestLeakAutoVar : public TestFixture { " void * p1 = malloc(5);\n" " else\n" " void * p2 = malloc(2);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:3:30]: (error) Memory leak: p1 [memleak]\n" "[test.c:5:30]: (error) Memory leak: p2 [memleak]\n", errout_str()); } @@ -2254,7 +2251,7 @@ class TestLeakAutoVar : public TestFixture { " p = malloc(5);\n" " }\n" " return;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:6:5]: (error) Memory leak: p [memleak]\n", errout_str()); } @@ -2264,7 +2261,7 @@ class TestLeakAutoVar : public TestFixture { " if (fd >= 0)\n" " return fd;\n" " return -1;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int f(const char * pathname, int flags) {\n" @@ -2272,7 +2269,7 @@ class TestLeakAutoVar : public TestFixture { " if (fd <= -1)\n" " return -1;\n" " return fd;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2390,6 +2387,22 @@ class TestLeakAutoVar : public TestFixture { ASSERT_EQUALS("", errout_str()); // don't crash } + void ifelse30() { + check("void f(void** pp) {\n" // #14709 + " void* p = malloc(8);\n" + " if ((void*)p == 0)\n" + " return;\n" + " *pp = p;\n" + "}\n" + "void g(void** pp) {\n" + " void* p = malloc(8);\n" + " if (static_cast(p) == 0)\n" + " return;\n" + " *pp = p;\n" + "}\n", dinit(CheckOptions, $.cpp = true)); + ASSERT_EQUALS("", errout_str()); + } + void switch1() { check("void f() {\n" " char *p = 0;\n" @@ -2398,7 +2411,7 @@ class TestLeakAutoVar : public TestFixture { " default: return;\n" " }\n" " free(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2409,7 +2422,7 @@ class TestLeakAutoVar : public TestFixture { " for (i=0;i<5;i++) { }\n" " if (x) { free(p) }\n" " else { a = p; }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2444,13 +2457,13 @@ class TestLeakAutoVar : public TestFixture { check("void f() {\n" " FILE*f=fopen(fname,a);\n" " free(f);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:2:12] -> [test.c:3:5]: (error) Mismatching allocation and deallocation: f [mismatchAllocDealloc]\n", errout_str()); check("void f() {\n" " FILE*f=fopen(fname,a);\n" " free((void*)f);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:2:12] -> [test.c:3:5]: (error) Mismatching allocation and deallocation: f [mismatchAllocDealloc]\n", errout_str()); check("void f() {\n" @@ -2460,18 +2473,18 @@ class TestLeakAutoVar : public TestFixture { " delete[] cPtr;\n" " cPtr = new char[100];\n" " delete cPtr;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("[test.cpp:6:12] -> [test.cpp:7:12]: (error) Mismatching allocation and deallocation: cPtr [mismatchAllocDealloc]\n", errout_str()); check("void f() {\n" " char *cPtr = new char[100];\n" " free(cPtr);\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("[test.cpp:2:18] -> [test.cpp:3:5]: (error) Mismatching allocation and deallocation: cPtr [mismatchAllocDealloc]\n", errout_str()); check("void f() {\n" " char *cPtr = new (buf) char[100];\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -2490,7 +2503,7 @@ class TestLeakAutoVar : public TestFixture { " void* a = malloc(1);\n" " void* b = freopen(f, p, a);\n" " free(b);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:2:14] -> [test.c:3:14]: (error) Mismatching allocation and deallocation: a [mismatchAllocDealloc]\n" "[test.c:3:14] -> [test.c:4:4]: (error) Mismatching allocation and deallocation: b [mismatchAllocDealloc]\n", errout_str()); @@ -2498,14 +2511,14 @@ class TestLeakAutoVar : public TestFixture { " void* a;\n" " void* b = realloc(a, 10);\n" " free(b);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " int * i = new int;\n" " int * j = realloc(i, 2 * sizeof(int));\n" " delete[] j;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("[test.cpp:2:14] -> [test.cpp:3:14]: (error) Mismatching allocation and deallocation: i [mismatchAllocDealloc]\n" "[test.cpp:3:14] -> [test.cpp:4:13]: (error) Mismatching allocation and deallocation: j [mismatchAllocDealloc]\n", errout_str()); @@ -2535,26 +2548,26 @@ class TestLeakAutoVar : public TestFixture { check("void f() {\n" " FILE*f=fopen(fname,a);\n" " std::unique_ptr fp{f};\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("[test.cpp:2:12] -> [test.cpp:3:30]: (error) Mismatching allocation and deallocation: f [mismatchAllocDealloc]\n", errout_str()); check("void f() {\n" " FILE*f=fopen(fname,a);\n" " std::unique_ptr fp{f, &fclose};\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " FILE*f=fopen(fname,a);\n" " std::shared_ptr fp{f, &fclose};\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check("struct deleter { void operator()(FILE* f) { fclose(f); }};\n" "void f() {\n" " FILE*f=fopen(fname,a);\n" " std::unique_ptr fp{f};\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check("int * create();\n" @@ -2576,39 +2589,39 @@ class TestLeakAutoVar : public TestFixture { check("void f() {\n" " FILE*f=fopen(fname,a);\n" " std::shared_ptr fp{f, [](FILE* x) { fclose(x); }};\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " FILE*f=fopen(fname,a);\n" " std::shared_ptr fp{f, +[](FILE* x) { fclose(x); }};\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " FILE*f=fopen(fname,a);\n" " std::shared_ptr fp{f, [](FILE* x) { free(f); }};\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("[test.cpp:2:12] -> [test.cpp:3:30]: (error) Mismatching allocation and deallocation: f [mismatchAllocDealloc]\n", errout_str()); check("void f() {\n" " FILE*f=fopen(fname,a);\n" " std::shared_ptr fp{f, [](FILE* x) {}};\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("[test.cpp:2:12] -> [test.cpp:3:30]: (error) Mismatching allocation and deallocation: f [mismatchAllocDealloc]\n", errout_str()); check("class C;\n" "void f() {\n" " C* c = new C{};\n" " std::shared_ptr a{c, [](C*) {}};\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check("class C;\n" "void f() {\n" " C* c = new C{};\n" " std::shared_ptr a{c, [](C* x) { delete x; }};\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check("struct DirDeleter {\n" // #12544 @@ -2640,7 +2653,7 @@ class TestLeakAutoVar : public TestFixture { check("int f() {\n" " char *p = malloc(100);\n" " return 123;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:3:5]: (error) Memory leak: p [memleak]\n", errout_str()); } @@ -2648,7 +2661,7 @@ class TestLeakAutoVar : public TestFixture { check("char *f() {\n" " char *p = malloc(100);\n" " return p;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2656,7 +2669,7 @@ class TestLeakAutoVar : public TestFixture { check("struct dev * f() {\n" " struct ABC *abc = malloc(100);\n" " return &abc->dev;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2668,7 +2681,7 @@ class TestLeakAutoVar : public TestFixture { " throw 1;\n" " }\n" " free(p);\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check("void f(char *p, int x) {\n" @@ -2677,7 +2690,7 @@ class TestLeakAutoVar : public TestFixture { " throw 1;\n" " }\n" " delete p;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check("void f(char *p, int x) {\n" @@ -2686,7 +2699,7 @@ class TestLeakAutoVar : public TestFixture { " throw 1;\n" " }\n" " delete [] p;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); } @@ -2700,7 +2713,7 @@ class TestLeakAutoVar : public TestFixture { " return;\n" " }\n" " *p = 0;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); } @@ -2708,7 +2721,7 @@ class TestLeakAutoVar : public TestFixture { check("std::pair f(size_t n) {\n" " char* p = (char* )malloc(n);\n" " return {p, p};\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); } @@ -2716,43 +2729,43 @@ class TestLeakAutoVar : public TestFixture { check("uint8_t *f() {\n" " void *x = malloc(1);\n" " return (uint8_t *)x;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check("uint8_t f() {\n" " void *x = malloc(1);\n" " return (uint8_t)x;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("[test.cpp:3:5]: (error) Memory leak: x [memleak]\n", errout_str()); check("void** f() {\n" " void *x = malloc(1);\n" " return (void**)x;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check("void* f() {\n" " void *x = malloc(1);\n" " return (long long)x;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check("void* f() {\n" " void *x = malloc(1);\n" " return (void*)(short)x;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("[test.cpp:3:5]: (error) Memory leak: x [memleak]\n", errout_str()); check("void* f() {\n" " void *x = malloc(1);\n" " return (mytype)x;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check("void* f() {\n" // Do not crash " void *x = malloc(1);\n" " return (mytype)y;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("[test.cpp:3:5]: (error) Memory leak: x [memleak]\n", errout_str()); } @@ -2760,25 +2773,25 @@ class TestLeakAutoVar : public TestFixture { check("void* f() {\n" " void *x = malloc(1);\n" " return (x);\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check("void* f() {\n" " void *x = malloc(1);\n" " return ((x));\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check("void* f() {\n" " void *x = malloc(1);\n" " return ((((x))));\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check("char* f() {\n" " void *x = malloc(1);\n" " return (char*)(x);\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); } @@ -2786,7 +2799,7 @@ class TestLeakAutoVar : public TestFixture { check("void* f() {\n" " void *x = malloc (sizeof (struct alloc));\n" " return x + sizeof (struct alloc);\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); } @@ -2795,7 +2808,7 @@ class TestLeakAutoVar : public TestFixture { " char* p = (char*)malloc(1);\n" " p[0] = 'x';\n" " return p[0];\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:4:5]: (error) Memory leak: p [memleak]\n", errout_str()); check("struct S { int f(); };\n" // #11746 @@ -2803,14 +2816,14 @@ class TestLeakAutoVar : public TestFixture { " S* s = new S;\n" " delete s;\n" " return s->f();\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("[test.cpp:4:5] -> [test.cpp:5:5]: (error) Returning/dereferencing 's' after it is deallocated / released [deallocret]\n", errout_str()); check("int f() {\n" " int* p = new int(3);\n" " delete p;\n" " return *p;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("[test.cpp:3:5] -> [test.cpp:4:5]: (error) Returning/dereferencing 'p' after it is deallocated / released [deallocret]\n", errout_str()); } @@ -2866,7 +2879,7 @@ class TestLeakAutoVar : public TestFixture { void test1() { check("void f(double*&p) {\n" // 3809 " p = malloc(0x100);\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); check("void f(int*& p) {\n" // #4400 @@ -2895,7 +2908,7 @@ class TestLeakAutoVar : public TestFixture { check("struct Fred {\n" " char *p;\n" " void f1() { free(p); }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -2903,7 +2916,7 @@ class TestLeakAutoVar : public TestFixture { check("void f() {\n" " char *&p = x();\n" " p = malloc(10);\n" - "};", dinit(CheckOptions, $.cpp = true)); + "};\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); } @@ -2912,21 +2925,21 @@ class TestLeakAutoVar : public TestFixture { " static char *p;\n" " if (!p) p = malloc(10);\n" " if (x) { free(p); p = 0; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } void test5() { // unknown type - check("void f() { Fred *p = malloc(10); }", dinit(CheckOptions, $.cpp = true)); + check("void f() { Fred *p = malloc(10); }\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("[test.cpp:1:34]: (error) Memory leak: p [memleak]\n", errout_str()); - check("void f() { Fred *p = malloc(10); }"); + check("void f() { Fred *p = malloc(10); }\n"); ASSERT_EQUALS("[test.c:1:34]: (error) Memory leak: p [memleak]\n", errout_str()); - check("void f() { Fred *p = new Fred; }", dinit(CheckOptions, $.cpp = true)); + check("void f() { Fred *p = new Fred; }\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); - check("void f() { Fred fred = malloc(10); }", dinit(CheckOptions, $.cpp = true)); + check("void f() { Fred fred = malloc(10); }\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); } @@ -2934,7 +2947,7 @@ class TestLeakAutoVar : public TestFixture { check("void f() {\n" " char *p = malloc(10);\n" " throw 123;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("[test.cpp:3:5]: (error) Memory leak: p [memleak]\n", errout_str()); check("void f() {\n" @@ -2944,7 +2957,7 @@ class TestLeakAutoVar : public TestFixture { " throw 123;\n" " } catch (...) { }\n" " free(p);\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); } @@ -2961,7 +2974,7 @@ class TestLeakAutoVar : public TestFixture { " throw ::NS::Except();\n" " }\n" " delete pi;\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("", errout_str()); } @@ -2974,7 +2987,7 @@ class TestLeakAutoVar : public TestFixture { check("void f() {\n" " char *p = malloc(10);\n" " x(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:3:9]: (information) --check-library: Function x() should have configuration [checkLibraryNoReturn]\n" "[test.c:4:1]: (information) --check-library: Function x() should have / configuration [checkLibraryUseIgnore]\n", errout_str()); @@ -2992,7 +3005,7 @@ class TestLeakAutoVar : public TestFixture { check("void f() {\n" " char *p = malloc(10);\n" " x(&p);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:3:10]: (information) --check-library: Function x() should have configuration [checkLibraryNoReturn]\n" "[test.c:4:1]: (information) --check-library: Function x() should have / configuration [checkLibraryUseIgnore]\n", errout_str()); @@ -3003,7 +3016,7 @@ class TestLeakAutoVar : public TestFixture { const char code[] = "void f() {\n" " char *p = malloc(10);\n" " if (set_data(p)) { }\n" - "}"; + "}\n"; check(code); ASSERT_EQUALS("[test.c:4:1]: (information) --check-library: Function set_data() should have / configuration [checkLibraryUseIgnore]\n", errout_str()); check(code, dinit(CheckOptions, $.cpp = true)); @@ -3014,7 +3027,7 @@ class TestLeakAutoVar : public TestFixture { const char code[] = "void f() {\n" " char *p = malloc(10);\n" " if (set_data(p)) { return; }\n" - "}"; + "}\n"; check(code); ASSERT_EQUALS("[test.c:3:24]: (information) --check-library: Function set_data() should have / configuration [checkLibraryUseIgnore]\n" "[test.c:4:1]: (information) --check-library: Function set_data() should have / configuration [checkLibraryUseIgnore]\n" @@ -3031,7 +3044,7 @@ class TestLeakAutoVar : public TestFixture { " char *p = malloc(10);\n" " int ret = set_data(p);\n" " return ret;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:4:5]: (information) --check-library: Function set_data() should have / configuration [checkLibraryUseIgnore]\n", errout_str()); } @@ -3090,6 +3103,29 @@ class TestLeakAutoVar : public TestFixture { ASSERT_EQUALS("[test.cpp:5:5]: (error) Resource leak: file [resourceLeak]\n", errout_str()); } + void configurationUse() { + // A function configured with takes care of the memory, so it needs + // no further configuration. + constexpr char xmldata[] = "\n" + "\n" + " \n" + " myalloc\n" + " set_data\n" + " myfree\n" + " \n" + " \n" + " false\n" + " \n" + " \n" + "\n"; + const Settings settingsUse = settingsBuilder().libraryxml(xmldata).checkLibrary().build(); + check("void f() {\n" + " char *p = myalloc(10);\n" + " set_data(p);\n" + "}\n", settingsUse); + ASSERT_EQUALS("", errout_str()); + } + void configuration6() { check("void f() {}\n" // #11198 "void g() {\n" @@ -3109,7 +3145,7 @@ class TestLeakAutoVar : public TestFixture { void ptrptr() { check("void f() {\n" " char **p = malloc(10);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.c:3:1]: (error) Memory leak: p [memleak]\n", errout_str()); } @@ -3117,19 +3153,19 @@ class TestLeakAutoVar : public TestFixture { check("void QueueDSMCCPacket(unsigned char *data, int length) {\n" " unsigned char *dataCopy = malloc(length * sizeof(unsigned char));\n" " m_dsmccQueue.enqueue(new DSMCCPacket(dataCopy));\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("[test.cpp:4:1]: (information) --check-library: Function DSMCCPacket() should have / configuration [checkLibraryUseIgnore]\n", errout_str()); check("void QueueDSMCCPacket(unsigned char *data, int length) {\n" " unsigned char *dataCopy = malloc(length * sizeof(unsigned char));\n" " m_dsmccQueue.enqueue(new DSMCCPacket(somethingunrelated));\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("[test.cpp:4:1]: (error) Memory leak: dataCopy [memleak]\n", errout_str()); check("void f() {\n" " char *buf = new char[1000];\n" " clist.push_back(new (std::nothrow) C(buf));\n" - "}", dinit(CheckOptions, $.cpp = true)); + "}\n", dinit(CheckOptions, $.cpp = true)); ASSERT_EQUALS("[test.cpp:4:1]: (information) --check-library: Function C() should have / configuration [checkLibraryUseIgnore]\n", errout_str()); } @@ -3138,7 +3174,7 @@ class TestLeakAutoVar : public TestFixture { " double *new = malloc(1*sizeof(double));\n" " free(new);\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -3151,7 +3187,7 @@ class TestLeakAutoVar : public TestFixture { " }\n" " c = malloc(128);\n" " return ret();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -3167,6 +3203,34 @@ class TestLeakAutoVar : public TestFixture { ASSERT_EQUALS("", errout_str()); } + void unconditionalScope() { + check("void f() {\n" // #14945 + " {\n" + " int* p = new int;\n" + " *p = 1;\n" + " }\n" + " {\n" + " int* q = new int;\n" + " *q = 2;\n" + " delete q;\n" + " }\n" + " int* r = new int;\n" + " *r = 3;\n" + " delete r;\n" + "}\n", dinit(CheckOptions, $.cpp = true)); + ASSERT_EQUALS("[test.cpp:5:5]: (error) Memory leak: p [memleak]\n", errout_str()); + + check("void f() {\n" + " int* p = new int;\n" + " {\n" + " (void)p;\n" + " }\n" + " *p = 1;\n" + " delete p;\n" + "}\n", dinit(CheckOptions, $.cpp = true)); + ASSERT_EQUALS("", errout_str()); + } + void functionCallCastConfig() { // #9652 constexpr char xmldata[] = "\n" "\n" @@ -3186,7 +3250,7 @@ class TestLeakAutoVar : public TestFixture { "{\n" " char * buf = malloc(4);\n" " free_func((void *)(1), buf);\n" - "}", settingsFunctionCall); + "}\n", settingsFunctionCall); ASSERT_EQUALS("[test.cpp:5:1]: (information) --check-library: Function free_func() should have / configuration [checkLibraryUseIgnore]\n", errout_str()); check("void g(void*);\n" @@ -3255,8 +3319,8 @@ class TestLeakAutoVarRecursiveCountLimit : public TestFixture { // Tokenizer.. ASSERT_LOC(tokenizer.simplifyTokens1(""), file, line); - // Check for leaks.. - runChecks(tokenizer, this); + CheckLeakAutoVar check; + runChecks(check, tokenizer, *this); } void run() override { @@ -3272,7 +3336,7 @@ class TestLeakAutoVarRecursiveCountLimit : public TestFixture { "void foo() {\n" " if (0) { }\n" " THOU THOU\n" - "}"), LIMIT, "Internal limit: CheckLeakAutoVar::checkScope() Maximum recursive count of 1000 reached."); + "}\n"), LIMIT, "Internal limit: CheckLeakAutoVar::checkScope() Maximum recursive count of 1000 reached."); ASSERT_NO_THROW(checkP("#define ONE if (0) { }\n" "#define TEN ONE ONE ONE ONE ONE ONE ONE ONE ONE ONE\n" "#define HUN TEN TEN TEN TEN TEN TEN TEN TEN TEN TEN\n" @@ -3280,7 +3344,7 @@ class TestLeakAutoVarRecursiveCountLimit : public TestFixture { "void foo() {\n" " if (0) { }\n" " THOU THOU\n" - "}")); + "}\n")); } }; @@ -3302,8 +3366,8 @@ class TestLeakAutoVarStrcpy : public TestFixture { SimpleTokenizer tokenizer(settings, *this); ASSERT_LOC(tokenizer.tokenize(code), file, line); - // Check for leaks.. - runChecks(tokenizer, this); + CheckLeakAutoVar check; + runChecks(check, tokenizer, *this); } void run() override { @@ -3322,7 +3386,7 @@ class TestLeakAutoVarStrcpy : public TestFixture { "{\n" " char* ptr = new char[strlen(str)+1];\n" " m = strcpy(ptr, str);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -3330,7 +3394,7 @@ class TestLeakAutoVarStrcpy : public TestFixture { check("void f(char *p) {\n" " free(p);\n" " strcpy(a, p);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:15]: (error) Dereferencing 'p' after it is deallocated / released [deallocuse]\n", errout_str()); check("void f(char *p, const char *q) {\n" // #11665 @@ -3343,12 +3407,12 @@ class TestLeakAutoVarStrcpy : public TestFixture { check("void f(char *p) {\n" // #3041 - assigning pointer when it's used " free(p);\n" " strcpy(a, p=b());\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } void fclose_false_positive() { // #9575 - check("int f(FILE *fp) { return fclose(fp); }"); + check("int f(FILE *fp) { return fclose(fp); }\n"); ASSERT_EQUALS("", errout_str()); } @@ -3378,7 +3442,7 @@ class TestLeakAutoVarStrcpy : public TestFixture { " if (b)\n" " p = new S();\n" " p->f();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:9:1]: (error) Memory leak: p [memleak]\n", errout_str()); check("class B { std::string s; };\n" // #12062 @@ -3407,8 +3471,8 @@ class TestLeakAutoVarWindows : public TestFixture { SimpleTokenizer tokenizer(settings, *this, false); ASSERT_LOC(tokenizer.tokenize(code), file, line); - // Check for leaks.. - runChecks(tokenizer, this); + CheckLeakAutoVar check; + runChecks(check, tokenizer, *this); } void run() override { @@ -3480,8 +3544,8 @@ class TestLeakAutoVarPosix : public TestFixture { SimpleTokenizer tokenizer(settings, *this); ASSERT_LOC(tokenizer.tokenize(code), file, line); - // Check for leaks.. - runChecks(tokenizer, this); + CheckLeakAutoVar check; + runChecks(check, tokenizer, *this); } void run() override { diff --git a/test/testlibrary.cpp b/test/testlibrary.cpp index a531ac4cba9..c0ccbea0831 100644 --- a/test/testlibrary.cpp +++ b/test/testlibrary.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -109,7 +109,7 @@ class TestLibrary : public TestFixture { " \n" ""; - const char code[] = "foo();"; + const char code[] = "foo();\n"; SimpleTokenList tokenList(code); tokenList.front()->next()->astOperand1(tokenList.front()); @@ -131,13 +131,13 @@ class TestLibrary : public TestFixture { Library library; ASSERT(LibraryHelper::loadxmldata(library, xmldata, sizeof(xmldata))); { - const char code[] = "fred.foo(123);"; // <- wrong scope, not library function + const char code[] = "fred.foo(123);\n"; // <- wrong scope, not library function const SimpleTokenList tokenList(code); ASSERT(library.isNotLibraryFunction(tokenList.front()->tokAt(2))); } { - const char code[] = "Fred::foo(123);"; // <- wrong scope, not library function + const char code[] = "Fred::foo(123);\n"; // <- wrong scope, not library function const SimpleTokenList tokenList(code); ASSERT(library.isNotLibraryFunction(tokenList.front()->tokAt(2))); @@ -153,7 +153,7 @@ class TestLibrary : public TestFixture { ""; TokenList tokenList(settingsDefault, Standards::Language::CPP); - const char code[] = "foo();"; // <- too few arguments, not library function + const char code[] = "foo();\n"; // <- too few arguments, not library function ASSERT(tokenList.createTokensFromString(code)); Token::createMutualLinks(tokenList.front()->next(), tokenList.back()->previous()); tokenList.createAst(); @@ -177,7 +177,7 @@ class TestLibrary : public TestFixture { { TokenList tokenList(settingsDefault, Standards::Language::CPP); - const char code[] = "foo();"; // <- too few arguments, not library function + const char code[] = "foo();\n"; // <- too few arguments, not library function ASSERT(tokenList.createTokensFromString(code)); Token::createMutualLinks(tokenList.front()->next(), tokenList.back()->previous()); tokenList.createAst(); @@ -186,7 +186,7 @@ class TestLibrary : public TestFixture { } { TokenList tokenList(settingsDefault, Standards::Language::CPP); - const char code[] = "foo(a);"; // <- library function + const char code[] = "foo(a);\n"; // <- library function ASSERT(tokenList.createTokensFromString(code)); Token::createMutualLinks(tokenList.front()->next(), tokenList.back()->previous()); tokenList.createAst(); @@ -197,7 +197,7 @@ class TestLibrary : public TestFixture { } { TokenList tokenList(settingsDefault, Standards::Language::CPP); - const char code[] = "foo(a, b);"; // <- library function + const char code[] = "foo(a, b);\n"; // <- library function ASSERT(tokenList.createTokensFromString(code)); Token::createMutualLinks(tokenList.front()->next(), tokenList.back()->previous()); tokenList.createAst(); @@ -208,7 +208,7 @@ class TestLibrary : public TestFixture { } { TokenList tokenList(settingsDefault, Standards::Language::CPP); - const char code[] = "foo(a, b, c);"; // <- too much arguments, not library function + const char code[] = "foo(a, b, c);\n"; // <- too much arguments, not library function ASSERT(tokenList.createTokensFromString(code)); Token::createMutualLinks(tokenList.front()->next(), tokenList.back()->previous()); tokenList.createAst(); @@ -225,7 +225,7 @@ class TestLibrary : public TestFixture { " \n" ""; - const char code[] = "Fred foo(123);"; // <- Variable declaration, not library function + const char code[] = "Fred foo(123);\n"; // <- Variable declaration, not library function SimpleTokenList tokenList(code); tokenList.front()->next()->astOperand1(tokenList.front()); tokenList.front()->next()->varId(1); @@ -285,7 +285,7 @@ class TestLibrary : public TestFixture { ASSERT(LibraryHelper::loadxmldata(library, xmldata, sizeof(xmldata))); ASSERT_EQUALS(0, library.functions().at("foo").argumentChecks.at(-1).notuninit); - const char code[] = "foo(a,b,c,d,e);"; + const char code[] = "foo(a,b,c,d,e);\n"; SimpleTokenList tokenList(code); tokenList.front()->next()->astOperand1(tokenList.front()); @@ -309,7 +309,7 @@ class TestLibrary : public TestFixture { Library library; ASSERT(LibraryHelper::loadxmldata(library, xmldata, sizeof(xmldata))); - const char code[] = "foo(a,b,c,d);"; + const char code[] = "foo(a,b,c,d);\n"; SimpleTokenList tokenList(code); tokenList.front()->next()->astOperand1(tokenList.front()); @@ -340,7 +340,7 @@ class TestLibrary : public TestFixture { Library library; ASSERT(LibraryHelper::loadxmldata(library, xmldata, sizeof(xmldata))); - const char code[] = "foo(a,b,c,d,e,f,g,h,i,j,k);"; + const char code[] = "foo(a,b,c,d,e,f,g,h,i,j,k);\n"; SimpleTokenList tokenList(code); tokenList.front()->next()->astOperand1(tokenList.front()); @@ -481,7 +481,7 @@ class TestLibrary : public TestFixture { Library library; ASSERT(LibraryHelper::loadxmldata(library, xmldata, sizeof(xmldata))); - const char code[] = "foo(a,b,c,d,e);"; + const char code[] = "foo(a,b,c,d,e);\n"; SimpleTokenList tokenList(code); tokenList.front()->next()->astOperand1(tokenList.front()); @@ -543,13 +543,13 @@ class TestLibrary : public TestFixture { ASSERT(library.functions().at("bar").argumentChecks.empty()); { - const char code[] = "Foo::foo();"; + const char code[] = "Foo::foo();\n"; const SimpleTokenList tokenList(code); ASSERT(library.isnotnoreturn(tokenList.front()->tokAt(2))); } { - const char code[] = "bar();"; + const char code[] = "bar();\n"; const SimpleTokenList tokenList(code); ASSERT(library.isnotnoreturn(tokenList.front())); } @@ -569,14 +569,14 @@ class TestLibrary : public TestFixture { { SimpleTokenizer tokenizer(settingsDefault, *this); - const char code[] = "CString str; str.Format();"; + const char code[] = "CString str; str.Format();\n"; ASSERT(tokenizer.tokenize(code)); ASSERT(library.isnotnoreturn(Token::findsimplematch(tokenizer.tokens(), "Format"))); } { SimpleTokenizer tokenizer(settingsDefault, *this); - const char code[] = "HardDrive hd; hd.Format();"; + const char code[] = "HardDrive hd; hd.Format();\n"; ASSERT(tokenizer.tokenize(code)); ASSERT(!library.isnotnoreturn(Token::findsimplematch(tokenizer.tokens(), "Format"))); } @@ -595,14 +595,14 @@ class TestLibrary : public TestFixture { { SimpleTokenizer tokenizer(settingsDefault, *this); - const char code[] = "struct X : public Base { void dostuff() { f(0); } };"; + const char code[] = "struct X : public Base { void dostuff() { f(0); } };\n"; ASSERT(tokenizer.tokenize(code)); ASSERT(library.isnullargbad(Token::findsimplematch(tokenizer.tokens(), "f"),1)); } { SimpleTokenizer tokenizer(settingsDefault, *this); - const char code[] = "struct X : public Base { void dostuff() { f(1,2); } };"; + const char code[] = "struct X : public Base { void dostuff() { f(1,2); } };\n"; ASSERT(tokenizer.tokenize(code)); ASSERT(!library.isnullargbad(Token::findsimplematch(tokenizer.tokens(), "f"),1)); } @@ -622,7 +622,7 @@ class TestLibrary : public TestFixture { Library library; ASSERT(LibraryHelper::loadxmldata(library, xmldata, sizeof(xmldata))); - const char code[] = "a(); b();"; + const char code[] = "a(); b();\n"; const SimpleTokenList tokenList(code); const Library::WarnInfo* a = library.getWarnInfo(tokenList.front()); @@ -872,7 +872,7 @@ class TestLibrary : public TestFixture { { SimpleTokenizer var(*this); - ASSERT(var.tokenize("std::A a;")); + ASSERT(var.tokenize("std::A a;\n")); ASSERT_EQUALS(&A, library.detectContainer(var.tokens())); ASSERT(!library.detectIterator(var.tokens())); bool isIterator; @@ -882,7 +882,7 @@ class TestLibrary : public TestFixture { { SimpleTokenizer var(*this); - ASSERT(var.tokenize("std::A::size_type a_s;")); + ASSERT(var.tokenize("std::A::size_type a_s;\n")); ASSERT(!library.detectContainer(var.tokens())); ASSERT(!library.detectIterator(var.tokens())); ASSERT(!library.detectContainerOrIterator(var.tokens())); @@ -890,7 +890,7 @@ class TestLibrary : public TestFixture { { SimpleTokenizer var(*this); - ASSERT(var.tokenize("std::A::iterator a_it;")); + ASSERT(var.tokenize("std::A::iterator a_it;\n")); ASSERT(!library.detectContainer(var.tokens())); ASSERT_EQUALS(&A, library.detectIterator(var.tokens())); bool isIterator; @@ -900,7 +900,7 @@ class TestLibrary : public TestFixture { { SimpleTokenizer var(*this); - ASSERT(var.tokenize("std::B b;")); + ASSERT(var.tokenize("std::B b;\n")); ASSERT_EQUALS(&B, library.detectContainer(var.tokens())); ASSERT(!library.detectIterator(var.tokens())); bool isIterator; @@ -910,7 +910,7 @@ class TestLibrary : public TestFixture { { SimpleTokenizer var(*this); - ASSERT(var.tokenize("std::B::size_type b_s;")); + ASSERT(var.tokenize("std::B::size_type b_s;\n")); ASSERT(!library.detectContainer(var.tokens())); ASSERT(!library.detectIterator(var.tokens())); ASSERT(!library.detectContainerOrIterator(var.tokens())); @@ -918,7 +918,7 @@ class TestLibrary : public TestFixture { { SimpleTokenizer var(*this); - ASSERT(var.tokenize("std::B::iterator b_it;")); + ASSERT(var.tokenize("std::B::iterator b_it;\n")); ASSERT(!library.detectContainer(var.tokens())); ASSERT_EQUALS(&B, library.detectIterator(var.tokens())); bool isIterator; @@ -928,7 +928,7 @@ class TestLibrary : public TestFixture { { SimpleTokenizer var(*this); - ASSERT(var.tokenize("C c;")); + ASSERT(var.tokenize("C c;\n")); ASSERT(!library.detectContainer(var.tokens())); ASSERT(!library.detectIterator(var.tokens())); ASSERT(!library.detectContainerOrIterator(var.tokens())); @@ -936,7 +936,7 @@ class TestLibrary : public TestFixture { { SimpleTokenizer var(*this); - ASSERT(var.tokenize("D d;")); + ASSERT(var.tokenize("D d;\n")); ASSERT(!library.detectContainer(var.tokens())); ASSERT(!library.detectIterator(var.tokens())); ASSERT(!library.detectContainerOrIterator(var.tokens())); @@ -944,7 +944,7 @@ class TestLibrary : public TestFixture { { SimpleTokenizer var(*this); - ASSERT(var.tokenize("std::E e;")); + ASSERT(var.tokenize("std::E e;\n")); ASSERT(library.detectContainer(var.tokens())); ASSERT(!library.detectIterator(var.tokens())); bool isIterator; @@ -955,7 +955,7 @@ class TestLibrary : public TestFixture { { SimpleTokenizer var(*this); - ASSERT(var.tokenize("E e;")); + ASSERT(var.tokenize("E e;\n")); ASSERT(!library.detectContainer(var.tokens())); ASSERT(!library.detectIterator(var.tokens())); ASSERT(!library.detectContainerOrIterator(var.tokens())); @@ -964,7 +964,7 @@ class TestLibrary : public TestFixture { { SimpleTokenizer var(*this); - ASSERT(var.tokenize("std::E::iterator I;")); + ASSERT(var.tokenize("std::E::iterator I;\n")); ASSERT(!library.detectContainer(var.tokens())); ASSERT(!library.detectIterator(var.tokens())); ASSERT(!library.detectContainerOrIterator(var.tokens())); @@ -973,7 +973,7 @@ class TestLibrary : public TestFixture { { SimpleTokenizer var(*this); - ASSERT(var.tokenize("std::E::size_type p;")); + ASSERT(var.tokenize("std::E::size_type p;\n")); ASSERT(!library.detectContainer(var.tokens())); ASSERT(!library.detectIterator(var.tokens())); ASSERT(!library.detectContainerOrIterator(var.tokens())); @@ -982,7 +982,7 @@ class TestLibrary : public TestFixture { { SimpleTokenizer var(*this); - ASSERT(var.tokenize("std::F f;")); + ASSERT(var.tokenize("std::F f;\n")); ASSERT(library.detectContainer(var.tokens())); ASSERT(!library.detectIterator(var.tokens())); bool isIterator; @@ -992,7 +992,7 @@ class TestLibrary : public TestFixture { { SimpleTokenizer var(*this); - ASSERT(var.tokenize("std::F::iterator I;")); + ASSERT(var.tokenize("std::F::iterator I;\n")); ASSERT(!library.detectContainer(var.tokens())); TODO_ASSERT(library.detectIterator(var.tokens())); bool isIterator = false; @@ -1002,7 +1002,7 @@ class TestLibrary : public TestFixture { { SimpleTokenizer var(*this); - ASSERT(var.tokenize("F::iterator I;")); + ASSERT(var.tokenize("F::iterator I;\n")); ASSERT(!library.detectContainer(var.tokens())); ASSERT(!library.detectIterator(var.tokens())); ASSERT(!library.detectContainerOrIterator(var.tokens())); @@ -1156,6 +1156,23 @@ class TestLibrary : public TestFixture { // comma followed by dot LOADLIB_ERROR_INVALID_RANGE("-10:0,.5:"); + + // arg indirect out of range (index into ArgumentChecks::direction) + LOADLIBERROR("\n" + "\n" + " \n" + " \n" + " \n" + "", + Library::ErrorCode::BAD_ATTRIBUTE_VALUE); + + LOADLIBERROR("\n" + "\n" + " \n" + " \n" + " \n" + "", + Library::ErrorCode::BAD_ATTRIBUTE_VALUE); } void loadLibCombinations() const { diff --git a/test/testmathlib.cpp b/test/testmathlib.cpp index 8cbe5b50019..800334926e5 100644 --- a/test/testmathlib.cpp +++ b/test/testmathlib.cpp @@ -22,6 +22,7 @@ #include "token.h" #include "tokenlist.h" +#include #include #include #include @@ -44,6 +45,7 @@ class TestMathLib : public TestFixture { TEST_CASE(isnegative); TEST_CASE(ispositive); TEST_CASE(isFloat); + TEST_CASE(isLongDoubleOutOfDoubleRange); TEST_CASE(isDecimalFloat); TEST_CASE(isGreater); TEST_CASE(isGreaterEqual); @@ -598,6 +600,16 @@ class TestMathLib : public TestFixture { // TODO: test with 128-bit values } + void isLongDoubleOutOfDoubleRange() const { + ASSERT_EQUALS(true, MathLib::isLongDoubleOutOfDoubleRange("1e400L")); + ASSERT_EQUALS(true, MathLib::isLongDoubleOutOfDoubleRange("1.0e4931l")); + ASSERT_EQUALS(false, MathLib::isLongDoubleOutOfDoubleRange("1e300L")); + ASSERT_EQUALS(false, MathLib::isLongDoubleOutOfDoubleRange("1e10000")); + ASSERT_EQUALS(false, MathLib::isLongDoubleOutOfDoubleRange("1e10000f")); + ASSERT_EQUALS(false, MathLib::isLongDoubleOutOfDoubleRange("10L")); + ASSERT_EQUALS(false, MathLib::isLongDoubleOutOfDoubleRange("")); + } + void toDoubleNumber() const { // float values ASSERT_EQUALS_DOUBLE(1.0, MathLib::toDoubleNumber("1."), 0.001); @@ -677,6 +689,13 @@ class TestMathLib : public TestFixture { ASSERT_EQUALS_DOUBLE(static_cast('\200'), MathLib::toDoubleNumber("'\\200'"), 0.000001); ASSERT_EQUALS_DOUBLE(static_cast(L'A'), MathLib::toDoubleNumber("L'A'"), 0.000001); + // out of the range of double, like glibc's HUGE_VAL for compilers other than GCC + ASSERT_EQUALS(true, std::isinf(MathLib::toDoubleNumber("1e10000"))); + ASSERT_EQUALS(true, std::isinf(MathLib::toDoubleNumber("1e10000f"))); + ASSERT_EQUALS(true, std::isinf(MathLib::toDoubleNumber("1e10000L"))); + ASSERT_EQUALS(true, std::isinf(MathLib::toDoubleNumber("1.5E+400"))); + ASSERT_EQUALS(true, std::isinf(MathLib::toDoubleNumber("1.0e4931L"))); // #12282 + ASSERT_THROW_INTERNAL_EQUALS(MathLib::toDoubleNumber("invalid"), INTERNAL, "Internal Error. MathLib::toDoubleNumber: conversion failed: invalid"); // AppleClang before 18 reports a different error diff --git a/test/testmemleak.cpp b/test/testmemleak.cpp index 08ea1530f90..e05698ae23e 100644 --- a/test/testmemleak.cpp +++ b/test/testmemleak.cpp @@ -39,33 +39,32 @@ class TestMemleak : public TestFixture { #define functionReturnType(...) functionReturnType_(__FILE__, __LINE__, __VA_ARGS__) template - CheckMemoryLeak::AllocType functionReturnType_(const char* file, int line, const char (&code)[size]) { + CheckMemoryLeakImpl::AllocType functionReturnType_(const char* file, int line, const char (&code)[size]) { // Tokenize.. SimpleTokenizer tokenizer(settingsDefault, *this); ASSERT_LOC(tokenizer.tokenize(code), file, line); - const CheckMemoryLeak c(&tokenizer, this, &settingsDefault); - + const CheckMemoryLeakImpl c(&tokenizer, settingsDefault, *this); return (c.functionReturnType)(&tokenizer.getSymbolDatabase()->scopeList.front().functionList.front()); } void testFunctionReturnType() { { const char code[] = "const char *foo()\n" - "{ return 0; }"; - ASSERT_EQUALS(CheckMemoryLeak::No, functionReturnType(code)); + "{ return 0; }\n"; + ASSERT_EQUALS(CheckMemoryLeakImpl::No, functionReturnType(code)); } { const char code[] = "Fred *newFred()\n" - "{ return new Fred; }"; - ASSERT_EQUALS(CheckMemoryLeak::New, functionReturnType(code)); + "{ return new Fred; }\n"; + ASSERT_EQUALS(CheckMemoryLeakImpl::New, functionReturnType(code)); } { const char code[] = "char *foo()\n" - "{ return new char[100]; }"; - ASSERT_EQUALS(CheckMemoryLeak::NewArray, functionReturnType(code)); + "{ return new char[100]; }\n"; + ASSERT_EQUALS(CheckMemoryLeakImpl::NewArray, functionReturnType(code)); } { @@ -73,8 +72,8 @@ class TestMemleak : public TestFixture { "{\n" " char *p = new char[100];\n" " return p;\n" - "}"; - ASSERT_EQUALS(CheckMemoryLeak::NewArray, functionReturnType(code)); + "}\n"; + ASSERT_EQUALS(CheckMemoryLeakImpl::NewArray, functionReturnType(code)); } } @@ -94,8 +93,8 @@ class TestMemleak : public TestFixture { // there is no allocation const Token *tok = Token::findsimplematch(tokenizer.tokens(), "ret ="); - const CheckMemoryLeak check(&tokenizer, nullptr, &settingsDefault); - ASSERT_EQUALS(CheckMemoryLeak::No, check.getAllocationType(tok->tokAt(2), 1)); + const CheckMemoryLeakImpl check(&tokenizer, settingsDefault, *this); + ASSERT_EQUALS(CheckMemoryLeakImpl::No, check.getAllocationType(tok->tokAt(2), 1)); } }; @@ -120,7 +119,7 @@ class TestMemleakInFunction : public TestFixture { ASSERT_LOC(tokenizer.tokenize(code), file, line); // Check for memory leaks.. - CheckMemoryLeakInFunction checkMemoryLeak(&tokenizer, &settings, this); + CheckMemoryLeakInFunctionImpl checkMemoryLeak(&tokenizer, settings, *this); checkMemoryLeak.checkReallocUsage(); } @@ -150,6 +149,7 @@ class TestMemleakInFunction : public TestFixture { TEST_CASE(realloc22); TEST_CASE(realloc23); TEST_CASE(realloc24); // #9228 + TEST_CASE(realloc25); } void realloc1() { @@ -157,7 +157,7 @@ class TestMemleakInFunction : public TestFixture { "{\n" " char *a = (char *)malloc(10);\n" " a = realloc(a, 100);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:5]: (error) Common realloc mistake: \'a\' nulled but not freed upon failure [memleakOnRealloc]\n", errout_str()); } @@ -167,7 +167,7 @@ class TestMemleakInFunction : public TestFixture { " char *a = (char *)malloc(10);\n" " a = (char *)realloc(a, 100);\n" " free(a);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:5]: (error) Common realloc mistake: \'a\' nulled but not freed upon failure [memleakOnRealloc]\n", errout_str()); } @@ -179,7 +179,7 @@ class TestMemleakInFunction : public TestFixture { " if ((a = realloc(a, 100)) == NULL)\n" " return;\n" " free(a);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -191,7 +191,7 @@ class TestMemleakInFunction : public TestFixture { " if ((a = realloc(a, 100)) == NULL)\n" " return;\n" " free(a);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:5]: (error) Memory leak: a\n", "[test.cpp:4:10]: (error) Common realloc mistake: \'a\' nulled but not freed upon failure [memleakOnRealloc]\n", @@ -209,7 +209,7 @@ class TestMemleakInFunction : public TestFixture { " free(buf);\n" " else\n" " free(new_buf);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -223,7 +223,7 @@ class TestMemleakInFunction : public TestFixture { " }\n" " free(pData);\n" " return true;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -235,7 +235,7 @@ class TestMemleakInFunction : public TestFixture { " if (!m_buf) {\n" " m_buf = origBuf;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -243,7 +243,7 @@ class TestMemleakInFunction : public TestFixture { check("void foo()\n" "{\n" " x = realloc(x,100);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -253,7 +253,7 @@ class TestMemleakInFunction : public TestFixture { " pa = pb = malloc(10);\n" " pa = realloc(pa, 20);" " exit();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -264,7 +264,7 @@ class TestMemleakInFunction : public TestFixture { " if (!p)\n" " error();\n" " usep(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -275,7 +275,7 @@ class TestMemleakInFunction : public TestFixture { " if ((a = realloc(a, x + 100)) == NULL)\n" " return;\n" " free(a);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -285,7 +285,7 @@ class TestMemleakInFunction : public TestFixture { " char **str;\n" " *str = realloc(*str,100);\n" " free (*str);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:6]: (error) Common realloc mistake: \'str\' nulled but not freed upon failure [memleakOnRealloc]\n", errout_str()); } @@ -296,7 +296,7 @@ class TestMemleakInFunction : public TestFixture { " if (!p)\n" " error();\n" " usep(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -307,7 +307,7 @@ class TestMemleakInFunction : public TestFixture { " if( m_options == NULL )\n" " return false;\n" " return true;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (error) Common realloc mistake: \'m_options\' nulled but not freed upon failure [memleakOnRealloc]\n", errout_str()); } @@ -317,7 +317,7 @@ class TestMemleakInFunction : public TestFixture { " if (zLine) {\n" " free(zLine);\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -326,7 +326,7 @@ class TestMemleakInFunction : public TestFixture { "{\n" " void ***a = malloc(sizeof(a));\n" " ***a = realloc(***(a), sizeof(a) * 2);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:8]: (error) Common realloc mistake: \'a\' nulled but not freed upon failure [memleakOnRealloc]\n", errout_str()); } @@ -335,7 +335,7 @@ class TestMemleakInFunction : public TestFixture { "{\n" " void *a = malloc(sizeof(a));\n" " a = realloc((void*)a, sizeof(a) * 2);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:5]: (error) Common realloc mistake: \'a\' nulled but not freed upon failure [memleakOnRealloc]\n", errout_str()); } @@ -344,7 +344,7 @@ class TestMemleakInFunction : public TestFixture { "{\n" " void *a = malloc(sizeof(a));\n" " a = (realloc((void*)((a)), sizeof(a) * 2));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:5]: (error) Common realloc mistake: \'a\' nulled but not freed upon failure [memleakOnRealloc]\n", errout_str()); } @@ -353,7 +353,7 @@ class TestMemleakInFunction : public TestFixture { "{\n" " void *a = malloc(sizeof(a));\n" " a = realloc((a) + 1, sizeof(a) * 2);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -364,7 +364,7 @@ class TestMemleakInFunction : public TestFixture { " bs = realloc(bs, 100);\n" " if (bs == NULL) return bs0;\n" " return bs;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -375,7 +375,7 @@ class TestMemleakInFunction : public TestFixture { " bs = realloc(bs, 100);\n" " if (bs == NULL) return;\n" " *bsp = bs;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -386,7 +386,7 @@ class TestMemleakInFunction : public TestFixture { " if (!(cigar = realloc(cigar, 100 * sizeof(*cigar))))\n" " return;\n" " s->cigar = cigar;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -394,28 +394,82 @@ class TestMemleakInFunction : public TestFixture { check("void f() {\n" "void *a = NULL;\n" "a = realloc(a, 20);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" "void *a = NULL;\n" "a = malloc(10);\n" "a = realloc(a, 20);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:1]: (error) Common realloc mistake: \'a\' nulled but not freed upon failure [memleakOnRealloc]\n", errout_str()); check("void f() {\n" "void *a = nullptr;\n" "a = malloc(10);\n" "a = realloc(a, 20);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:1]: (error) Common realloc mistake: \'a\' nulled but not freed upon failure [memleakOnRealloc]\n", errout_str()); check("void f(char *b) {\n" "void *a = NULL;\n" "a = b;\n" "a = realloc(a, 20);\n" - "}"); + "}\n"); + ASSERT_EQUALS("", errout_str()); + } + + void realloc25() { + check("struct T {\n" + " char* ptr;\n" + " size_t len;\n" + "};\n" + "struct S {\n" + " struct T t;\n" + "};\n" + "void f(struct S* s, size_t len) {\n" + " char* p = s->t.ptr;\n" + " p = realloc(p, len);\n" + " if (p) {\n" + " s->t.ptr = p;\n" + " s->t.len = len;\n" + " }\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("struct T {\n" + " char* ptr;\n" + " size_t len;\n" + "};\n" + "struct S {\n" + " struct T t[1];\n" + "};\n" + "void f(struct S* s, size_t len) {\n" + " char* p = s->t[0].ptr;\n" + " p = realloc(p, len);\n" + " if (p) {\n" + " s->t[0].ptr = p;\n" + " s->t[0].len = len;\n" + " }\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("struct T {\n" // #14718 + " char* ptr;\n" + " size_t len;\n" + "};\n" + "struct S {\n" + " struct T t;\n" + "};\n" + "char* get(struct T t) { return t.ptr; };\n" + "void f(struct S* s, size_t len) {\n" + " char* p = get(s->t);\n" + " p = realloc(p, len);\n" + " if (p) {\n" + " s->t.ptr = p;\n" + " s->t.len = len;\n" + " }\n" + "}\n"); ASSERT_EQUALS("", errout_str()); } }; @@ -434,7 +488,7 @@ class TestMemleakInClass : public TestFixture { TestMemleakInClass() : TestFixture("TestMemleakInClass") {} private: - const Settings settings = settingsBuilder().severity(Severity::warning).severity(Severity::style).library("std.cfg").build(); + const Settings settings = settingsBuilder().severity(Severity::warning).severity(Severity::style).library("std.cfg").library("posix.cfg").build(); /** * Tokenize and execute leak check for given code @@ -447,7 +501,7 @@ class TestMemleakInClass : public TestFixture { ASSERT_LOC(tokenizer.tokenize(code), file, line); // Check for memory leaks.. - CheckMemoryLeakInClass checkMemoryLeak(&tokenizer, &settings, this); + CheckMemoryLeakInClassImpl checkMemoryLeak(&tokenizer, settings, *this); (checkMemoryLeak.check)(); } @@ -479,6 +533,7 @@ class TestMemleakInClass : public TestFixture { TEST_CASE(class25); // ticket #4367 - false positive implementation for destructor is not seen TEST_CASE(class26); // ticket #10789 TEST_CASE(class27); // ticket #8126 + TEST_CASE(class28); // ticket #14954 TEST_CASE(staticvar); @@ -513,7 +568,7 @@ class TestMemleakInClass : public TestFixture { "Fred::~Fred()\n" "{\n" " delete [] str2;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:11]: (style) Class 'Fred' is unsafe, 'Fred::str1' can leak by wrong usage. [unsafeClassCanLeak]\n", errout_str()); check("class Fred\n" @@ -531,7 +586,7 @@ class TestMemleakInClass : public TestFixture { " {\n" " delete [] str2;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:11]: (style) Class 'Fred' is unsafe, 'Fred::str1' can leak by wrong usage. [unsafeClassCanLeak]\n", errout_str()); } @@ -553,7 +608,7 @@ class TestMemleakInClass : public TestFixture { "Fred::~Fred()\n" "{\n" " free(str1);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:17:5]: (error) Mismatching allocation and deallocation: Fred::str1 [mismatchAllocDealloc]\n", errout_str()); check("class Fred\n" @@ -569,7 +624,7 @@ class TestMemleakInClass : public TestFixture { " {\n" " free(str1);\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:12:9]: (error) Mismatching allocation and deallocation: Fred::str1 [mismatchAllocDealloc]\n", errout_str()); } @@ -605,7 +660,7 @@ class TestMemleakInClass : public TestFixture { " delete tok;\n" " tok = next;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); @@ -634,7 +689,7 @@ class TestMemleakInClass : public TestFixture { " tok = next;\n" " }\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -658,7 +713,7 @@ class TestMemleakInClass : public TestFixture { "{\n" " ABC *p = new ABC;\n" " addAbc( p );\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct ABC;\n" @@ -675,7 +730,7 @@ class TestMemleakInClass : public TestFixture { " ABC *p = new ABC;\n" " addAbc( p );\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -691,7 +746,7 @@ class TestMemleakInClass : public TestFixture { " char *str = new char[100];\n" " delete [] str;\n" " hello();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("class Fred\n" @@ -703,7 +758,7 @@ class TestMemleakInClass : public TestFixture { " delete [] str;\n" " hello();\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -723,7 +778,7 @@ class TestMemleakInClass : public TestFixture { "Fred::~Fred()\n" "{\n" " delete this->i;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("class Fred\n" @@ -738,7 +793,7 @@ class TestMemleakInClass : public TestFixture { " {\n" " delete this->i;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -755,7 +810,7 @@ class TestMemleakInClass : public TestFixture { " int* c = new int(1);\n" " delete c;\n" " doNothing(c);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("class A\n" @@ -768,7 +823,7 @@ class TestMemleakInClass : public TestFixture { " doNothing(c);\n" " }\n" " void doNothing() { }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -785,7 +840,7 @@ class TestMemleakInClass : public TestFixture { "{ p = new int; }\n" "\n" "A::~A()\n" - "{ delete (p); }"); + "{ delete (p); }\n"); ASSERT_EQUALS("", errout_str()); check("class A\n" @@ -796,7 +851,7 @@ class TestMemleakInClass : public TestFixture { " { p = new int; }\n" " ~A()\n" " { delete (p); }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -808,7 +863,7 @@ class TestMemleakInClass : public TestFixture { " A();\n" "};\n" "A::A()\n" - "{ p = new int; }"); + "{ p = new int; }\n"); ASSERT_EQUALS("[test.cpp:4:11]: (style) Class 'A' is unsafe, 'A::p' can leak by wrong usage. [unsafeClassCanLeak]\n", errout_str()); check("class A\n" @@ -816,7 +871,7 @@ class TestMemleakInClass : public TestFixture { "public:\n" " int * p;\n" " A() { p = new int; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:11]: (style) Class 'A' is unsafe, 'A::p' can leak by wrong usage. [unsafeClassCanLeak]\n", errout_str()); } @@ -826,8 +881,8 @@ class TestMemleakInClass : public TestFixture { "public:\n" " int * p;\n" " A() : p(new int[10])\n" - " { }" - "};"); + " { }\n" + "};\n"); ASSERT_EQUALS("[test.cpp:4:11]: (style) Class 'A' is unsafe, 'A::p' can leak by wrong usage. [unsafeClassCanLeak]\n", errout_str()); check("class A\n" @@ -837,7 +892,7 @@ class TestMemleakInClass : public TestFixture { " A();\n" "};\n" "A::A() : p(new int[10])\n" - "{ }"); + "{ }\n"); ASSERT_EQUALS("[test.cpp:4:11]: (style) Class 'A' is unsafe, 'A::p' can leak by wrong usage. [unsafeClassCanLeak]\n", errout_str()); } @@ -859,7 +914,7 @@ class TestMemleakInClass : public TestFixture { "{ }\n" "\n" "void A::cleanup()\n" - "{ delete [] p; }"); + "{ delete [] p; }\n"); ASSERT_EQUALS("[test.cpp:4:10]: (style) Class 'A' is unsafe, 'A::p' can leak by wrong usage. [unsafeClassCanLeak]\n", errout_str()); check("class A\n" @@ -873,7 +928,7 @@ class TestMemleakInClass : public TestFixture { " { }\n" " void cleanup()\n" " { delete [] p; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:4:10]: (style) Class 'A' is unsafe, 'A::p' can leak by wrong usage. [unsafeClassCanLeak]\n", errout_str()); } @@ -895,7 +950,7 @@ class TestMemleakInClass : public TestFixture { "{ }\n" "\n" "void A::foo()\n" - "{ p = new int[10]; delete [] p; }"); + "{ p = new int[10]; delete [] p; }\n"); ASSERT_EQUALS("[test.cpp:17:3]: (warning) Possible leak in public function. The pointer 'p' is not deallocated before it is allocated. [publicAllocationError]\n", errout_str()); check("class A\n" @@ -909,7 +964,7 @@ class TestMemleakInClass : public TestFixture { " { }\n" " void foo()\n" " { p = new int[10]; delete [] p; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:11:7]: (warning) Possible leak in public function. The pointer 'p' is not deallocated before it is allocated. [publicAllocationError]\n", errout_str()); } @@ -922,7 +977,7 @@ class TestMemleakInClass : public TestFixture { "};\n" "\n" "void A::init()\n" - "{ p = new int[10]; }"); + "{ p = new int[10]; }\n"); ASSERT_EQUALS("[test.cpp:9:3]: (warning) Possible leak in public function. The pointer 'p' is not deallocated before it is allocated. [publicAllocationError]\n" "[test.cpp:3:10]: (style) Class 'A' is unsafe, 'A::p' can leak by wrong usage. [unsafeClassCanLeak]\n", errout_str()); @@ -932,7 +987,7 @@ class TestMemleakInClass : public TestFixture { "public:\n" " void init()\n" " { p = new int[10]; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:6:7]: (warning) Possible leak in public function. The pointer 'p' is not deallocated before it is allocated. [publicAllocationError]\n" "[test.cpp:3:10]: (style) Class 'A' is unsafe, 'A::p' can leak by wrong usage. [unsafeClassCanLeak]\n", errout_str()); @@ -945,7 +1000,7 @@ class TestMemleakInClass : public TestFixture { "};\n" "\n" "void A::init()\n" - "{ p = new int; }"); + "{ p = new int; }\n"); ASSERT_EQUALS("[test.cpp:9:3]: (warning) Possible leak in public function. The pointer 'p' is not deallocated before it is allocated. [publicAllocationError]\n" "[test.cpp:3:10]: (style) Class 'A' is unsafe, 'A::p' can leak by wrong usage. [unsafeClassCanLeak]\n", errout_str()); @@ -955,7 +1010,7 @@ class TestMemleakInClass : public TestFixture { "public:\n" " void init()\n" " { p = new int; }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:6:7]: (warning) Possible leak in public function. The pointer 'p' is not deallocated before it is allocated. [publicAllocationError]\n" "[test.cpp:3:10]: (style) Class 'A' is unsafe, 'A::p' can leak by wrong usage. [unsafeClassCanLeak]\n", errout_str()); @@ -968,7 +1023,7 @@ class TestMemleakInClass : public TestFixture { "};\n" "\n" "void A::init()\n" - "{ p = malloc(sizeof(int)*10); }"); + "{ p = malloc(sizeof(int)*10); }\n"); ASSERT_EQUALS("[test.cpp:9:3]: (warning) Possible leak in public function. The pointer 'p' is not deallocated before it is allocated. [publicAllocationError]\n" "[test.cpp:3:10]: (style) Class 'A' is unsafe, 'A::p' can leak by wrong usage. [unsafeClassCanLeak]\n", errout_str()); @@ -978,7 +1033,7 @@ class TestMemleakInClass : public TestFixture { "public:\n" " void init()\n" " { p = malloc(sizeof(int)*10); }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:6:7]: (warning) Possible leak in public function. The pointer 'p' is not deallocated before it is allocated. [publicAllocationError]\n" "[test.cpp:3:10]: (style) Class 'A' is unsafe, 'A::p' can leak by wrong usage. [unsafeClassCanLeak]\n", errout_str()); } @@ -992,7 +1047,7 @@ class TestMemleakInClass : public TestFixture { " ~A() { delete [] p; }\n" "};\n" "A::A()\n" - "{ p = new int[10]; }"); + "{ p = new int[10]; }\n"); ASSERT_EQUALS("", errout_str()); check("class A\n" @@ -1002,7 +1057,7 @@ class TestMemleakInClass : public TestFixture { " A()\n" " { p = new int[10]; }\n" " ~A() { delete [] p; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); @@ -1014,7 +1069,7 @@ class TestMemleakInClass : public TestFixture { " ~A() { delete p; }\n" "};\n" "A::A()\n" - "{ p = new int; }"); + "{ p = new int; }\n"); ASSERT_EQUALS("", errout_str()); check("class A\n" @@ -1024,7 +1079,7 @@ class TestMemleakInClass : public TestFixture { " A()\n" " { p = new int; }\n" " ~A() { delete p; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); @@ -1036,7 +1091,7 @@ class TestMemleakInClass : public TestFixture { " ~A() { free(p); }\n" "};\n" "A::A()\n" - "{ p = malloc(sizeof(int)*10); }"); + "{ p = malloc(sizeof(int)*10); }\n"); ASSERT_EQUALS("", errout_str()); check("class A\n" @@ -1046,7 +1101,7 @@ class TestMemleakInClass : public TestFixture { " A()\n" " { p = malloc(sizeof(int)*10); }\n" " ~A() { free(p); }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -1059,7 +1114,7 @@ class TestMemleakInClass : public TestFixture { "public:\n" " A() { a = b = new int[10]; }\n" " ~A() { delete [] a; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -1076,7 +1131,7 @@ class TestMemleakInClass : public TestFixture { "{\n" " A::pd = new char[12];\n" " delete [] A::pd;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:10:5]: (warning) Possible leak in public function. The pointer 'pd' is not deallocated before it is allocated. [publicAllocationError]\n", errout_str()); check("class A {\n" @@ -1088,7 +1143,7 @@ class TestMemleakInClass : public TestFixture { " pd = new char[12];\n" " delete [] pd;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:7:9]: (warning) Possible leak in public function. The pointer 'pd' is not deallocated before it is allocated. [publicAllocationError]\n", errout_str()); check("class A {\n" @@ -1102,7 +1157,7 @@ class TestMemleakInClass : public TestFixture { "{\n" " pd = new char[12];\n" " delete [] pd;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:10:5]: (warning) Possible leak in public function. The pointer 'pd' is not deallocated before it is allocated. [publicAllocationError]\n", errout_str()); } @@ -1118,7 +1173,7 @@ class TestMemleakInClass : public TestFixture { " }\n" "private:\n" " char *a;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("class A : public x\n" @@ -1132,7 +1187,7 @@ class TestMemleakInClass : public TestFixture { "{\n" " a = new char[10];\n" " foo(a);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1150,7 +1205,7 @@ class TestMemleakInClass : public TestFixture { "{\n" " rp1 = new TRadioButton(this);\n" " rp2 = new TRadioButton(this);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("class TRadioButton { };\n" @@ -1166,7 +1221,7 @@ class TestMemleakInClass : public TestFixture { "{\n" " rp1 = new TRadioButton;\n" " rp2 = new TRadioButton;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:19]: (style) Class 'Foo' is unsafe, 'Foo::rp1' can leak by wrong usage. [unsafeClassCanLeak]\n" "[test.cpp:6:19]: (style) Class 'Foo' is unsafe, 'Foo::rp2' can leak by wrong usage. [unsafeClassCanLeak]\n", errout_str()); @@ -1189,7 +1244,7 @@ class TestMemleakInClass : public TestFixture { "{\n" " delete rp1;\n" " delete rp2;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1211,7 +1266,7 @@ class TestMemleakInClass : public TestFixture { " delete [] str2;\n" " }\n" " };\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:15]: (style) Class 'Fred' is unsafe, 'Fred::str1' can leak by wrong usage. [unsafeClassCanLeak]\n", errout_str()); check("namespace ns1 {\n" @@ -1235,7 +1290,7 @@ class TestMemleakInClass : public TestFixture { " {\n" " delete [] str2;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:15]: (style) Class 'Fred' is unsafe, 'Fred::str1' can leak by wrong usage. [unsafeClassCanLeak]\n", errout_str()); check("namespace ns1 {\n" @@ -1258,7 +1313,7 @@ class TestMemleakInClass : public TestFixture { "ns1::Fred::~Fred()\n" "{\n" " delete [] str2;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:15]: (style) Class 'Fred' is unsafe, 'Fred::str1' can leak by wrong usage. [unsafeClassCanLeak]\n", errout_str()); check("namespace ns1 {\n" @@ -1283,7 +1338,7 @@ class TestMemleakInClass : public TestFixture { "ns1::ns2::Fred::~Fred()\n" "{\n" " delete [] str2;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:19]: (style) Class 'Fred' is unsafe, 'Fred::str1' can leak by wrong usage. [unsafeClassCanLeak]\n", errout_str()); check("namespace ns1 {\n" @@ -1310,7 +1365,7 @@ class TestMemleakInClass : public TestFixture { "ns1::ns2::ns3::Fred::~Fred()\n" "{\n" " delete [] str2;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:23]: (style) Class 'Fred' is unsafe, 'Fred::str1' can leak by wrong usage. [unsafeClassCanLeak]\n", errout_str()); } @@ -1327,7 +1382,7 @@ class TestMemleakInClass : public TestFixture { " C *c;\n" "public:\n" " A() : b(new B()), c(new C(b)) { }\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:9:8]: (style) Class 'A' is unsafe, 'A::b' can leak by wrong usage. [unsafeClassCanLeak]\n" "[test.cpp:10]: (style) Class 'A' is unsafe, 'A::c' can leak by wrong usage.\n", "[test.cpp:9:8]: (style) Class 'A' is unsafe, 'A::b' can leak by wrong usage. [unsafeClassCanLeak]\n", @@ -1349,7 +1404,7 @@ class TestMemleakInClass : public TestFixture { " b = new B();\n" " c = new C(b);\n" " }\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:9:8]: (style) Class 'A' is unsafe, 'A::b' can leak by wrong usage. [unsafeClassCanLeak]\n" "[test.cpp:10]: (style) Class 'A' is unsafe, 'A::c' can leak by wrong usage.\n", "[test.cpp:9:8]: (style) Class 'A' is unsafe, 'A::b' can leak by wrong usage. [unsafeClassCanLeak]\n", @@ -1363,7 +1418,7 @@ class TestMemleakInClass : public TestFixture { "private:\n" " Fred() { a = new int; }\n" " ~Fred() { (delete(a), (a)=NULL); }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -1382,7 +1437,7 @@ class TestMemleakInClass : public TestFixture { " ~CData() { if (m_impl) m_impl->Release(); }\n" "private:\n" " CDataImpl *m_impl;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -1393,7 +1448,7 @@ class TestMemleakInClass : public TestFixture { "public:\n" " Fred(const Fred &fred) { a = new int; }\n" " ~Fred() { delete a; }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -1404,7 +1459,7 @@ class TestMemleakInClass : public TestFixture { "public:\n" " Fred() { a = new int; }\n" " ~Fred();\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -1430,6 +1485,15 @@ class TestMemleakInClass : public TestFixture { ASSERT_EQUALS("[test.cpp:6:11]: (style) Class 'S' is unsafe, 'S::a' can leak by wrong usage. [unsafeClassCanLeak]\n", errout_str()); } + void class28() { // ticket #14954 + check("struct S {\n" + " explicit S(char *name) { m_fd = mkstemp(name); }\n" + " ~S() { /* close(m_fd); */ }\n" + " int m_fd;\n" + "};\n"); + ASSERT_EQUALS("[test.cpp:4:9]: (style) Class 'S' is unsafe, 'S::m_fd' can leak by wrong usage. [unsafeClassCanLeak]\n", errout_str()); + } + void staticvar() { check("class A\n" "{\n" @@ -1441,7 +1505,7 @@ class TestMemleakInClass : public TestFixture { " if (!p)\n" " p = new int[100];\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -1473,7 +1537,7 @@ class TestMemleakInClass : public TestFixture { "void Tokenizer::deleteTokens(int *tok)\n" "{\n" " delete tok;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Global function @@ -1500,7 +1564,7 @@ class TestMemleakInClass : public TestFixture { "{\n" " deleteTokens(_tokens);\n" " _tokens = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1525,7 +1589,7 @@ class TestMemleakInClass : public TestFixture { "\n" "A::~A() {\n" " delete [] pkt_buffer;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:14:9]: (error) Mismatching allocation and deallocation: A::pkt_buffer [mismatchAllocDealloc]\n", errout_str()); check("struct S {\n" // 5678 @@ -1561,7 +1625,7 @@ class TestMemleakInClass : public TestFixture { " data_ = new char[42];\n" " delete data_;\n" " data_ = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:17:3]: (warning) Possible leak in public function. The pointer 'data_' is not deallocated before it is allocated. [publicAllocationError]\n" "[test.cpp:18:3]: (error) Mismatching allocation and deallocation: Foo::data_ [mismatchAllocDealloc]\n", errout_str()); @@ -1584,7 +1648,7 @@ class TestMemleakInClass : public TestFixture { " data_ = new char[42];\n" " delete data_;\n" " data_ = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:17:3]: (warning) Possible leak in public function. The pointer 'data_' is not deallocated before it is allocated. [publicAllocationError]\n" "[test.cpp:18:3]: (error) Mismatching allocation and deallocation: Foo::data_ [mismatchAllocDealloc]\n", errout_str()); } @@ -1599,7 +1663,7 @@ class TestMemleakInClass : public TestFixture { " ~Fred() { free(s); }\n" " void xy()\n" " { s = malloc(100); }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:9:7]: (warning) Possible leak in public function. The pointer 's' is not deallocated before it is allocated. [publicAllocationError]\n", errout_str()); check("class Fred\n" @@ -1611,7 +1675,7 @@ class TestMemleakInClass : public TestFixture { " { s = malloc(100); }\n" "private:\n" " char *s;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:7:7]: (warning) Possible leak in public function. The pointer 's' is not deallocated before it is allocated. [publicAllocationError]\n", errout_str()); } @@ -1625,7 +1689,7 @@ class TestMemleakInClass : public TestFixture { " ~Fred() { free(s); }\n" " const Fred & operator = (const Fred &f)\n" " { s = malloc(100); }\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:9:7]: (warning) Possible leak in public function. The pointer 's' is not deallocated before it is allocated. [publicAllocationError]\n", errout_str()); } }; @@ -1652,7 +1716,7 @@ class TestMemleakStructMember : public TestFixture { ASSERT_LOC(tokenizer.tokenize(code), file, line); // Check for memory leaks.. - CheckMemoryLeakStructMember checkMemoryLeakStructMember(&tokenizer, &settings, this); + CheckMemoryLeakStructMemberImpl checkMemoryLeakStructMember(&tokenizer, settings, *this); (checkMemoryLeakStructMember.check)(); } @@ -1684,6 +1748,7 @@ class TestMemleakStructMember : public TestFixture { TEST_CASE(function3); // #3024: kernel list TEST_CASE(function4); // #3038: Deallocating in function TEST_CASE(function5); // #10381, #10382, #10158 + TEST_CASE(function6); // Handle if-else TEST_CASE(ifelse); @@ -1717,14 +1782,14 @@ class TestMemleakStructMember : public TestFixture { " struct ABC *abc = malloc(sizeof(struct ABC));\n" " abc->a = malloc(10);\n" " free(abc);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:5]: (error) Memory leak: abc.a [memleak]\n", errout_str()); check("static void foo()\n" "{\n" " struct ABC *abc = malloc(sizeof(struct ABC));\n" " abc->a = malloc(10);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:1]: (error) Memory leak: abc.a [memleak]\n", errout_str()); check("static ABC * foo()\n" @@ -1737,7 +1802,7 @@ class TestMemleakStructMember : public TestFixture { " return 0;\n" " }\n" " return abc;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:9]: (error) Memory leak: abc.a [memleak]\n", errout_str()); check("static void foo(int a)\n" @@ -1749,7 +1814,7 @@ class TestMemleakStructMember : public TestFixture { " free(abc->a);\n" " return;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:10:1]: (error) Memory leak: abc.a [memleak]\n", errout_str()); } @@ -1765,7 +1830,7 @@ class TestMemleakStructMember : public TestFixture { "out:\n" " free(abc->a);\n" " free(abc);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1775,13 +1840,13 @@ class TestMemleakStructMember : public TestFixture { " struct ABC *abc = malloc(sizeof(struct ABC));\n" " abc->a = malloc(10);\n" " return abc;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("static void foo(struct ABC *abc)\n" "{\n" " abc->a = malloc(10);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #7302 @@ -1789,14 +1854,14 @@ class TestMemleakStructMember : public TestFixture { " struct ABC abc;\n" " abc.a = malloc(10);\n" " return abc.a;\n" - "}", false); + "}\n", false); ASSERT_EQUALS("", errout_str()); check("void* foo() {\n" " struct ABC abc;\n" " abc.a = malloc(10);\n" " return abc.b;\n" - "}", false); + "}\n", false); ASSERT_EQUALS("[test.c:4:5]: (error) Memory leak: abc.a [memleak]\n", errout_str()); } @@ -1806,7 +1871,7 @@ class TestMemleakStructMember : public TestFixture { " struct ABC *abc = malloc(sizeof(struct ABC));\n" " abc->a = malloc(10);\n" " return &abc->self;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1815,7 +1880,7 @@ class TestMemleakStructMember : public TestFixture { "{\n" " struct ABC *abc = abc1;\n" " abc->a = malloc(10);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("static void foo()\n" @@ -1823,7 +1888,7 @@ class TestMemleakStructMember : public TestFixture { " struct ABC *abc;\n" " abc1 = abc = malloc(sizeof(ABC));\n" " abc->a = malloc(10);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("static void foo()\n" @@ -1832,7 +1897,7 @@ class TestMemleakStructMember : public TestFixture { " ptr = malloc(sizeof(struct msn_entry));\n" " ptr->msn = malloc(100);\n" " back = ptr;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1841,7 +1906,7 @@ class TestMemleakStructMember : public TestFixture { check("static void foo() {\n" " struct ABC *abc = malloc(123);\n" " abc->a = abc->b = malloc(10);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1850,7 +1915,7 @@ class TestMemleakStructMember : public TestFixture { " struct s f2;\n" " f2.a = malloc(100);\n" " *f1 = f2;\n" - "}", false); + "}\n", false); ASSERT_EQUALS("", errout_str()); } @@ -1976,7 +2041,7 @@ class TestMemleakStructMember : public TestFixture { " return 0;\n" " }\n" " return abc;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1987,7 +2052,7 @@ class TestMemleakStructMember : public TestFixture { " struct ABC *abc = malloc(sizeof(struct ABC));\n" " abc->a = malloc(10);\n" " func(abc);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("static void foo()\n" @@ -1995,7 +2060,7 @@ class TestMemleakStructMember : public TestFixture { " struct ABC *abc = malloc(sizeof(struct ABC));\n" " abclist.push_back(abc);\n" " abc->a = malloc(10);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2005,7 +2070,7 @@ class TestMemleakStructMember : public TestFixture { " A a = { 0 };\n" " a.foo = (char *) malloc(10);\n" " assign(&a);\n" - "}", false); + "}\n", false); ASSERT_EQUALS("", errout_str()); } @@ -2015,7 +2080,7 @@ class TestMemleakStructMember : public TestFixture { " struct ABC *abc = malloc(100);\n" " abc.a = (char *) malloc(10);\n" " list_add_tail(&abc->list, head);\n" - "}", false); + "}\n", false); ASSERT_EQUALS("", errout_str()); } @@ -2026,7 +2091,7 @@ class TestMemleakStructMember : public TestFixture { " struct ABC abc;\n" " abc.a = (char *) malloc(10);\n" " a(abc.a);\n" - "}", false); + "}\n", false); ASSERT_EQUALS("", errout_str()); } @@ -2035,14 +2100,14 @@ class TestMemleakStructMember : public TestFixture { " struct s s1;\n" " s1->x = malloc(1);\n" " return (s1);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct nc_rpc nc_rpc_getconfig() {\n" // #10382 " struct nc_rpc rpc;\n" " rpc->filter = malloc(1);\n" " return (nc_rpc)rpc;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("T* f(const char *str) {\n" // #10158 @@ -2082,6 +2147,17 @@ class TestMemleakStructMember : public TestFixture { ASSERT_EQUALS("", errout_str()); } + void function6() { + check("struct S { int* p; };\n" // #14706 + "void g(void*);\n" + "void f() {\n" + " S* s = new S();\n" + " s->p = new int();\n" + " g((void*)s);\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + } + void ifelse() { check("static void foo()\n" "{\n" @@ -2097,7 +2173,7 @@ class TestMemleakStructMember : public TestFixture { " }\n" " free(abc->a);\n" " free(abc);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2113,7 +2189,7 @@ class TestMemleakStructMember : public TestFixture { " free(abc);\n" " abc = next;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2125,7 +2201,7 @@ class TestMemleakStructMember : public TestFixture { " abc = malloc(sizeof(struct ABC));\n" " abc->a = malloc(10);\n" " return;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2143,7 +2219,7 @@ class TestMemleakStructMember : public TestFixture { " a.f = fopen(\"test\", \"r\");\n" " a.c = new char[12];\n" " a.m = malloc(12);\n" - "}"; + "}\n"; check(code1, true); ASSERT_EQUALS("[test.cpp:12:1]: (error) Resource leak: a.f [resourceLeak]\n" @@ -2168,7 +2244,7 @@ class TestMemleakStructMember : public TestFixture { " fclose(a.f);\n" " delete [] a.c;\n" " free(a.m);\n" - "}"; + "}\n"; check(code2, true); ASSERT_EQUALS("", errout_str()); @@ -2177,7 +2253,7 @@ class TestMemleakStructMember : public TestFixture { const char code3[] = "void func() {\n" " struct A a;\n" " a.f = fopen(\"test\", \"r\");\n" - "}"; + "}\n"; check(code3, true); ASSERT_EQUALS("", errout_str()); @@ -2192,7 +2268,7 @@ class TestMemleakStructMember : public TestFixture { "void func() {\n" " struct A a;\n" " a.f = fopen(\"test\", \"r\");\n" - "}"; + "}\n"; check(code4, true); ASSERT_EQUALS("", errout_str()); @@ -2208,7 +2284,7 @@ class TestMemleakStructMember : public TestFixture { "{\n" " Test& y = *x;\n" " y.data = malloc(10);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2218,7 +2294,7 @@ class TestMemleakStructMember : public TestFixture { "unique_ptrotherbuffer{new char[otherbufsize+1]};\n" "char *const oldbuffer = otherbuffer.get();\n" "int const oldbufsize = otherbufsize;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2231,7 +2307,7 @@ class TestMemleakStructMember : public TestFixture { " (s).state_check_buff = (void* )malloc(1);\n" " if (s.state_check_buff == 0)\n" " return;\n" - "}", false); + "}\n", false); ASSERT_EQUALS("[test.c:9:1]: (error) Memory leak: s.state_check_buff [memleak]\n", errout_str()); } @@ -2241,7 +2317,7 @@ class TestMemleakStructMember : public TestFixture { " foo f;\n" " ((f)->realm) = strdup(realm);\n" " if(f->realm == NULL) {}\n" - "}", false); + "}\n", false); ASSERT_EQUALS("[test.c:6:1]: (error) Memory leak: f.realm [memleak]\n", errout_str()); } @@ -2252,7 +2328,7 @@ class TestMemleakStructMember : public TestFixture { "void func() {\n" " struct ABC abc;\n" " abc.a = myalloc();\n" - "}", false); + "}\n", false); ASSERT_EQUALS("[test.c:7:1]: (error) Memory leak: abc.a [memleak]\n", errout_str()); } @@ -2268,7 +2344,7 @@ class TestMemleakStructMember : public TestFixture { " }\n" " delete[] s.p;\n" " return 0;\n" - "}", true); + "}\n", true); ASSERT_EQUALS("", errout_str()); check( @@ -2322,7 +2398,7 @@ class TestMemleakNoVar : public TestFixture { ASSERT_LOC(tokenizer.tokenize(code), file, line); // Check for memory leaks.. - CheckMemoryLeakNoVar checkMemoryLeakNoVar(&tokenizer, &settings, this); + CheckMemoryLeakNoVarImpl checkMemoryLeakNoVar(&tokenizer, settings, *this); (checkMemoryLeakNoVar.check)(); } @@ -2349,23 +2425,23 @@ class TestMemleakNoVar : public TestFixture { // standard function.. check("void x() {\n" " strcpy(a, strdup(p));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:15]: (error) Allocation with strdup, strcpy doesn't release it. [leakNoVarFunctionCall]\n", errout_str()); check("char *x() {\n" " char *ret = strcpy(malloc(10), \"abc\");\n" " return ret;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("char *x() {\n" " return strcpy(malloc(10), \"abc\");\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void x() {\n" " free(malloc(10));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // user function.. @@ -2374,7 +2450,7 @@ class TestMemleakNoVar : public TestFixture { "\n" "void x() {\n" " set_error(strdup(p));\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:5:15]: (error) Allocation with strdup, set_error doesn't release it. [leakNoVarFunctionCall]\n", "", errout_str()); check("void f()\n" @@ -2382,68 +2458,68 @@ class TestMemleakNoVar : public TestFixture { " int fd;\n" " fd = mkstemp(strdup(\"/tmp/file.XXXXXXXX\"));\n" " close(fd);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:4]: (error) Allocation with strdup, mkstemp doesn't release it.\n", "", errout_str()); check("void f()\n" "{\n" " if(TRUE || strcmp(strdup(a), b));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:23]: (error) Allocation with strdup, strcmp doesn't release it. [leakNoVarFunctionCall]\n", errout_str()); check("void f()\n" "{\n" " if(!strcmp(strdup(a), b) == 0);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:16]: (error) Allocation with strdup, strcmp doesn't release it. [leakNoVarFunctionCall]\n", errout_str()); check("void f()\n" "{\n" " 42, strcmp(strdup(a), b);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:16]: (error) Allocation with strdup, strcmp doesn't release it. [leakNoVarFunctionCall]\n", errout_str()); check("void f() {\n" " assert(freopen(\"/dev/null\", \"r\", stdin));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void x() {\n" " strcpy(a, (void*)strdup(p));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:22]: (error) Allocation with strdup, strcpy doesn't release it. [leakNoVarFunctionCall]\n", errout_str()); check("void* malloc1() {\n" " return (malloc(1));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("char *x() {\n" " char *ret = (char*)strcpy(malloc(10), \"abc\");\n" " return ret;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " free(malloc(1));\n" " strcpy(a, strdup(p));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:15]: (error) Allocation with strdup, strcpy doesn't release it. [leakNoVarFunctionCall]\n", errout_str()); check("void f() {\n" " memcmp(calloc(10, 10), strdup(q), 100);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:12]: (error) Allocation with calloc, memcmp doesn't release it. [leakNoVarFunctionCall]\n" "[test.cpp:2:28]: (error) Allocation with strdup, memcmp doesn't release it. [leakNoVarFunctionCall]\n", errout_str()); check("void* f(int size) {\n" " return (void*) malloc(size);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int* f(int size) {\n" " return static_cast(malloc(size));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() { if (new int[42]) {} }\n" // #10857 @@ -2536,31 +2612,57 @@ class TestMemleakNoVar : public TestFixture { " a.g(new int);\n" "}\n"); ASSERT_EQUALS("", errout_str()); + + check("int f() {\n" // #11184 + " return strlen(new char[4]{});\n" + "}\n" + "int g() {\n" + " int i = strlen(new char[4]{});\n" + " return i;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:2:19]: (error) Allocation with new, strlen doesn't release it. [leakNoVarFunctionCall]\n" + "[test.cpp:5:20]: (error) Allocation with new, strlen doesn't release it. [leakNoVarFunctionCall]\n", + errout_str()); + + check("int* f1() { return new int; }\n" // #14808 + "std::string* f2() { return new std::string(\"abc\"); }\n" + "std::clock_t* f3() { return new std::clock_t; }\n" + "QWidget* f4(QObject* parent) { return new QWidget(parent); }\n" + "void g(QObject* parent) {\n" + " assert(f1());\n" + " assert(f2());\n" + " assert(f3());\n" + " assert(f4(parent));\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:6:12]: (error) Allocation with f1, assert doesn't release it. [leakNoVarFunctionCall]\n" + "[test.cpp:7:12]: (error) Allocation with f2, assert doesn't release it. [leakNoVarFunctionCall]\n" + "[test.cpp:8:12]: (error) Allocation with f3, assert doesn't release it. [leakNoVarFunctionCall]\n", + errout_str()); } void missingAssignment() { check("void x()\n" "{\n" " malloc(10);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (error) Return value of allocation function 'malloc' is not stored. [leakReturnValNotUsed]\n", errout_str()); check("void x()\n" "{\n" " calloc(10, 1);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (error) Return value of allocation function 'calloc' is not stored. [leakReturnValNotUsed]\n", errout_str()); check("void x()\n" "{\n" " strdup(\"Test\");\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (error) Return value of allocation function 'strdup' is not stored. [leakReturnValNotUsed]\n", errout_str()); check("void x()\n" "{\n" " (char*) malloc(10);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:13]: (error) Return value of allocation function 'malloc' is not stored. [leakReturnValNotUsed]\n", errout_str()); check("void x()\n" @@ -2568,7 +2670,7 @@ class TestMemleakNoVar : public TestFixture { " char* ptr = malloc(10);\n" " foo(ptr);\n" " free(ptr);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int f( void ) {\n" // FP: #11246 @@ -2577,19 +2679,19 @@ class TestMemleakNoVar : public TestFixture { " if ( ok )\n" " free( address ), address = 0;\n" " return 0;\n" - "} "); + "} \n"); ASSERT_EQUALS("", errout_str()); check("char** x(const char* str) {\n" " char* ptr[] = { malloc(10), malloc(5), strdup(str) };\n" " return ptr;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void x()\n" "{\n" " 42,malloc(42);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:8]: (error) Return value of allocation function 'malloc' is not stored. [leakReturnValNotUsed]\n", errout_str()); check("void *f()\n" @@ -2599,7 +2701,7 @@ class TestMemleakNoVar : public TestFixture { "void x()\n" "{\n" " f();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:5]: (error) Return value of allocation function 'f' is not stored. [leakReturnValNotUsed]\n", errout_str()); check("void f()\n" // #8100 @@ -2609,7 +2711,7 @@ class TestMemleakNoVar : public TestFixture { "void x()\n" "{\n" " f();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void *f() {\n" // #8848 @@ -2618,26 +2720,26 @@ class TestMemleakNoVar : public TestFixture { "void x()\n" "{\n" " f();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void x()\n" "{\n" " if(!malloc(5)) fail();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:9]: (error) Return value of allocation function 'malloc' is not stored. [leakReturnValNotUsed]\n", errout_str()); check("FOO* factory() {\n" " FOO* foo = new (std::nothrow) FOO;\n" " return foo;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Ticket #6536 check("struct S { S(int) {} };\n" "void foo(int i) {\n" " S socket(i);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Ticket #6693 @@ -2647,12 +2749,12 @@ class TestMemleakNoVar : public TestFixture { "};\n" "void CTest::Initialise() {\n" " malloc();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" // #7348 - cast " p = (::X*)malloc(42);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #7182 "crash: CheckMemoryLeak::functionReturnType()" @@ -2660,7 +2762,7 @@ class TestMemleakNoVar : public TestFixture { "template auto binary_left_comma (T x, Ts... ts) { return (x , ... , ts); }\n" "int main() {\n" " unary_right_comma (a);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -2676,7 +2778,7 @@ class TestMemleakNoVar : public TestFixture { " new int{};\n" " new int{ 1 };\n" " new uint8_t[4];\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:5]: (error) Return value of allocation function 'new' is not stored. [leakReturnValNotUsed]\n" "[test.cpp:3:5]: (error) Return value of allocation function 'new' is not stored. [leakReturnValNotUsed]\n" "[test.cpp:4:5]: (error) Return value of allocation function 'new' is not stored. [leakReturnValNotUsed]\n" @@ -2694,7 +2796,7 @@ class TestMemleakNoVar : public TestFixture { check("void f(int* p) {\n" " new auto('c');\n" " new(p) int;\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:1:29]: (error) Return value of allocation function 'new' is not stored. [leakReturnValNotUsed]\n" "[test.cpp:3]: (error) Return value of allocation function 'new' is not stored.\n", "", @@ -2731,7 +2833,7 @@ class TestMemleakNoVar : public TestFixture { " C{ new int(1), 1 };\n" " C{ new(p) int, 1 };\n" " C{ new QWidget, 1 };\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(bool b) { if (b && malloc(42)) {} }\n" // // #10858 @@ -2794,54 +2896,54 @@ class TestMemleakNoVar : public TestFixture { void smartPointerFunctionParam() { check("void x() {\n" " f(shared_ptr(new int(42)), g());\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:5]: (warning, inconclusive) Unsafe allocation. If g() throws, memory could be leaked. Use make_shared() instead. [leakUnsafeArgAlloc]\n", errout_str()); check("void x() {\n" " h(12, f(shared_ptr(new int(42)), g()));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11]: (warning, inconclusive) Unsafe allocation. If g() throws, memory could be leaked. Use make_shared() instead. [leakUnsafeArgAlloc]\n", errout_str()); check("void x() {\n" " f(unique_ptr(new int(42)), g());\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:5]: (warning, inconclusive) Unsafe allocation. If g() throws, memory could be leaked. Use make_unique() instead. [leakUnsafeArgAlloc]\n", errout_str()); check("void x() {\n" " f(g(), shared_ptr(new int(42)));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:5]: (warning, inconclusive) Unsafe allocation. If g() throws, memory could be leaked. Use make_shared() instead. [leakUnsafeArgAlloc]\n", errout_str()); check("void x() {\n" " f(g(), unique_ptr(new int(42)));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:5]: (warning, inconclusive) Unsafe allocation. If g() throws, memory could be leaked. Use make_unique() instead. [leakUnsafeArgAlloc]\n", errout_str()); check("void x() {\n" " f(shared_ptr(new char), make_unique(32));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:5]: (warning, inconclusive) Unsafe allocation. If make_unique() throws, memory could be leaked. Use make_shared() instead. [leakUnsafeArgAlloc]\n", errout_str()); check("void x() {\n" " f(g(124), h(\"test\", 234), shared_ptr(new char));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:5]: (warning, inconclusive) Unsafe allocation. If h() throws, memory could be leaked. Use make_shared() instead. [leakUnsafeArgAlloc]\n", errout_str()); check("void x() {\n" " f(shared_ptr(new std::string(\"\")), g());\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:5]: (warning, inconclusive) Unsafe allocation. If g() throws, memory could be leaked. Use make_shared() instead. [leakUnsafeArgAlloc]\n", errout_str()); check("void g(int x) throw() { }\n" "void x() {\n" " f(g(124), shared_ptr(new char));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void __declspec(nothrow) g(int x) { }\n" "void x() {\n" " f(g(124), shared_ptr(new char));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } void resourceLeak() { @@ -2852,23 +2954,23 @@ class TestMemleakNoVar : public TestFixture { " if (result) return !result;\n" " fclose(fp);\n" " return result;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" " fopen(\"file.txt\", \"r\");\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:3]: (error) Return value of allocation function 'fopen' is not stored. [leakReturnValNotUsed]\n", errout_str()); check("void foo() {\n" " FILE *f = fopen(\"file.txt\", \"r\");\n" " freopen(\"file.txt\", \"r\", f);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:3]: (error) Return value of allocation function 'freopen' is not stored. [leakReturnValNotUsed]\n", errout_str()); check("void foo() {\n" " freopen(\"file.txt\", \"r\", stdin);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct Holder {\n" @@ -2878,7 +2980,7 @@ class TestMemleakNoVar : public TestFixture { "};\n" "void foo() {\n" " Holder h ( fopen(\"file.txt\", \"r\"));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct Holder {\n" @@ -2888,7 +2990,7 @@ class TestMemleakNoVar : public TestFixture { "};\n" "void foo() {\n" " Holder ( fopen(\"file.txt\", \"r\"));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct Holder {\n" @@ -2898,7 +3000,7 @@ class TestMemleakNoVar : public TestFixture { "};\n" "void foo() {\n" " Holder h { fopen(\"file.txt\", \"r\")};\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct Holder {\n" @@ -2908,7 +3010,7 @@ class TestMemleakNoVar : public TestFixture { "};\n" "void foo() {\n" " Holder h = fopen(\"file.txt\", \"r\");\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct Holder {\n" @@ -2918,7 +3020,7 @@ class TestMemleakNoVar : public TestFixture { "};\n" "void foo() {\n" " Holder { fopen(\"file.txt\", \"r\")};\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct Holder {\n" @@ -2928,7 +3030,7 @@ class TestMemleakNoVar : public TestFixture { "};\n" "void foo() {\n" " Holder { 0, fopen(\"file.txt\", \"r\")};\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2939,7 +3041,7 @@ class TestMemleakNoVar : public TestFixture { "\n" "void f() {\n" " makeThing();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #10631 @@ -2966,12 +3068,12 @@ class TestMemleakNoVar : public TestFixture { void crash1() { // #10729 check("void foo() {\n" " extern void *realloc (void *ptr, size_t size);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" " extern void *malloc (size_t size);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } diff --git a/test/testnullpointer.cpp b/test/testnullpointer.cpp index a9ce5ff6f38..ca5c183c911 100644 --- a/test/testnullpointer.cpp +++ b/test/testnullpointer.cpp @@ -37,6 +37,7 @@ class TestNullPointer : public TestFixture { private: const Settings settings = settingsBuilder().library("std.cfg").severity(Severity::warning).build(); + const Settings settings_i = settingsBuilder(settings).certainty(Certainty::inconclusive).build(); void run() override { mNewTemplate = true; @@ -142,6 +143,11 @@ class TestNullPointer : public TestFixture { TEST_CASE(nullpointer103); TEST_CASE(nullpointer104); // #13881 TEST_CASE(nullpointer105); // #13861 + TEST_CASE(nullpointer106); // #13682 + TEST_CASE(nullpointer107); // #13682 (FP/FN cases around guards that depend on the pointer indirectly) + TEST_CASE(nullpointer108); + TEST_CASE(nullpointer109); + TEST_CASE(nullpointer110); // #14937 TEST_CASE(nullpointer_addressOf); // address of TEST_CASE(nullpointerSwitch); // #2626 TEST_CASE(nullpointer_cast); // #4692 @@ -183,20 +189,23 @@ class TestNullPointer : public TestFixture { { bool inconclusive = false; bool cpp = true; - Standards::cstd_t cstd = Standards::CLatest; }; #define check(...) check_(__FILE__, __LINE__, __VA_ARGS__) template void check_(const char* file, int line, const char (&code)[size], const CheckOptions& options = make_default_obj()) { - const Settings settings1 = settingsBuilder(settings).certainty(Certainty::inconclusive, options.inconclusive).c(options.cstd).build(); + const Settings& settings1 = options.inconclusive ? settings_i : settings; + check_(file, line, code, settings1, options.cpp); + } + template + void check_(const char* file, int line, const char (&code)[size], const Settings& s, bool cpp = true) { // Tokenize.. - SimpleTokenizer tokenizer(settings1, *this, options.cpp); + SimpleTokenizer tokenizer(s, *this, cpp); ASSERT_LOC(tokenizer.tokenize(code), file, line); - // Check for null pointer dereferences.. - runChecks(tokenizer, this); + CheckNullPointer check; + runChecks(check, tokenizer, *this); } #define checkP(...) checkP_(__FILE__, __LINE__, __VA_ARGS__) @@ -207,8 +216,8 @@ class TestNullPointer : public TestFixture { // Tokenizer.. ASSERT_LOC(tokenizer.simplifyTokens1(""), file, line); - // Check for null pointer dereferences.. - runChecks(tokenizer, this); + CheckNullPointer check; + runChecks(check, tokenizer, *this); } @@ -219,7 +228,7 @@ class TestNullPointer : public TestFixture { "{\n" " while (tok);\n" " tok = tok->next();\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("[test.cpp:3:12] -> [test.cpp:4:11]: (warning) Either the condition 'tok' is redundant or there is possible null pointer dereference: tok. [nullPointerRedundantCheck]\n", errout_str()); // #2681 @@ -244,7 +253,7 @@ class TestNullPointer : public TestFixture { " while (tok && tok->str() != \";\")\n" " tok = tok->next();\n" " }\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:5] -> [test.cpp:3]: (warning) Either the condition 'while' is redundant or there is possible null pointer dereference: tok.\n", "", errout_str()); check("void foo(Token &tok)\n" @@ -254,7 +263,7 @@ class TestNullPointer : public TestFixture { " while (!tok)\n" " char c = tok.read();\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo()\n" @@ -265,7 +274,7 @@ class TestNullPointer : public TestFixture { " tok = tok->next();\n" " if( !tok ) break;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo()\n" @@ -275,7 +284,7 @@ class TestNullPointer : public TestFixture { " while (tok && tok->str() != \";\")\n" " tok = tok->next();\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(A*a)\n" @@ -290,7 +299,7 @@ class TestNullPointer : public TestFixture { " a->b();\n" " break;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // dereference in outer scope.. @@ -299,7 +308,7 @@ class TestNullPointer : public TestFixture { " while (tok) tok = tok->next();\n" " }\n" " tok->str();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:16] -> [test.cpp:5:5]: (warning) Either the condition 'tok' is redundant or there is possible null pointer dereference: tok. [nullPointerRedundantCheck]\n", errout_str()); check("int foo(const Token *tok)\n" @@ -324,7 +333,7 @@ class TestNullPointer : public TestFixture { " while (d && d->i == 0)\n" " d = d->c;\n" " if (!d) throw;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct b {\n" @@ -349,7 +358,7 @@ class TestNullPointer : public TestFixture { " }\n" " else if (a->x == 2) { }\n" " if (a) { }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // ticket #2134 - sizeof doesn't dereference @@ -359,7 +368,7 @@ class TestNullPointer : public TestFixture { " sizeof(*list);\n" " if (!list)\n" " ;\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); // ticket #2245 - sizeof doesn't dereference @@ -367,7 +376,7 @@ class TestNullPointer : public TestFixture { " if (!p) {\n" " int sz = sizeof(p->x);\n" " }\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); } @@ -379,7 +388,7 @@ class TestNullPointer : public TestFixture { " Fred fred;\n" " while (fred);\n" " fred.hello();\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); } @@ -395,7 +404,7 @@ class TestNullPointer : public TestFixture { " int a = abc->a;\n" " if (!abc)\n" " ;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:9] -> [test.cpp:3:13]: (warning) Either the condition '!abc' is redundant or there is possible null pointer dereference: abc. [nullPointerRedundantCheck]\n", errout_str()); check("void foo(struct ABC *abc) {\n" @@ -404,7 +413,7 @@ class TestNullPointer : public TestFixture { " bar(x, y, abc->a);\n" " if (!abc)\n" " ;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:9] -> [test.cpp:2:9]: (warning) Either the condition '!abc' is redundant or there is possible null pointer dereference: abc. [nullPointerRedundantCheck]\n" "[test.cpp:5:9] -> [test.cpp:3:12]: (warning) Either the condition '!abc' is redundant or there is possible null pointer dereference: abc. [nullPointerRedundantCheck]\n" "[test.cpp:5:9] -> [test.cpp:4:15]: (warning) Either the condition '!abc' is redundant or there is possible null pointer dereference: abc. [nullPointerRedundantCheck]\n", errout_str()); @@ -414,7 +423,7 @@ class TestNullPointer : public TestFixture { " return;\n" " }\n" " if (abc) {}\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:5:9] -> [test.cpp:2:9]: (warning) Either the condition 'abc' is redundant or there is possible null pointer dereference: abc. [nullPointerRedundantCheck]\n", errout_str()); @@ -424,7 +433,7 @@ class TestNullPointer : public TestFixture { " return;\n" " }\n" " if (!abc);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:9] -> [test.cpp:2:9]: (warning) Either the condition '!abc' is redundant or there is possible null pointer dereference: abc. [nullPointerRedundantCheck]\n", errout_str()); // TODO: False negative if member of member is dereferenced @@ -432,14 +441,14 @@ class TestNullPointer : public TestFixture { " abc->next->a = 0;\n" " if (abc->next)\n" " ;\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:3] -> [test.cpp:2]: (warning) Possible null pointer dereference: abc - otherwise it is redundant to check it against null.\n", "", errout_str()); check("void foo(ABC *abc) {\n" " abc->a = 0;\n" " if (abc && abc->b == 0)\n" " ;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:3:9] -> [test.cpp:2:5]: (warning) Either the condition 'abc' is redundant or there is possible null pointer dereference: abc. [nullPointerRedundantCheck]\n", errout_str()); @@ -450,13 +459,13 @@ class TestNullPointer : public TestFixture { " if (abc && abc->a);\n" " if (!abc)\n" " ;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(struct ABC *abc) {\n" " int x = abc && a(abc->x);\n" " if (abc) { }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // ok to use a linked list.. @@ -465,13 +474,13 @@ class TestNullPointer : public TestFixture { " abc = abc->next;\n" " if (!abc)\n" " ;\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); check("void f(struct ABC *abc) {\n" " abc = (ABC *)(abc->_next);\n" - " if (abc) { }" - "}", dinit(CheckOptions, $.inconclusive = true)); + " if (abc) { }\n" + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); // reassign struct.. @@ -481,7 +490,7 @@ class TestNullPointer : public TestFixture { " abc = abc->next;\n" " if (!abc)\n" " ;\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); check("void foo(struct ABC *abc)\n" @@ -490,7 +499,7 @@ class TestNullPointer : public TestFixture { " f(&abc);\n" " if (!abc)\n" " ;\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); // goto.. @@ -504,7 +513,7 @@ class TestNullPointer : public TestFixture { "out:\n" " if (!abc)\n" " ;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // loops.. @@ -518,7 +527,7 @@ class TestNullPointer : public TestFixture { " --a;\n" " }\n" " while (a > 0);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f()\n" @@ -530,7 +539,7 @@ class TestNullPointer : public TestFixture { " if (!tok)\n" " return;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // dynamic_cast.. @@ -539,7 +548,7 @@ class TestNullPointer : public TestFixture { " int a = abc->a;\n" " if (!dynamic_cast(abc))\n" " ;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #2641 - global pointer, function call @@ -548,14 +557,14 @@ class TestNullPointer : public TestFixture { " abc->a = 0;\n" " do_stuff();\n" " if (abc) { }\n" - "}"); + "}\n"); ASSERT_EQUALS("",errout_str()); check("Fred *fred;\n" "void f() {\n" " fred->foo();\n" " if (fred) { }\n" - "}"); + "}\n"); ASSERT_EQUALS("",errout_str()); // #2641 - local pointer, function call @@ -564,7 +573,7 @@ class TestNullPointer : public TestFixture { " abc->a = 0;\n" " do_stuff();\n" " if (abc) { }\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:5:9] -> [test.cpp:3:5]: (warning) Either the condition 'abc' is redundant or there is possible null pointer dereference: abc. [nullPointerRedundantCheck]\n", errout_str()); @@ -574,7 +583,7 @@ class TestNullPointer : public TestFixture { " abc->a = 0;\n" " do_stuff();\n" " if (abc) { }\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:4:9] -> [test.cpp:2:5]: (warning) Either the condition 'abc' is redundant or there is possible null pointer dereference: abc. [nullPointerRedundantCheck]\n", errout_str()); @@ -589,32 +598,32 @@ class TestNullPointer : public TestFixture { " if ( abc ) {}\n" " break;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #3128 check("void f(ABC *abc) {\n" " x(!abc || y(abc->a));\n" " if (abc) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(ABC *abc) {\n" " x(def || !abc || y(def, abc->a));\n" " if (abc) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(ABC *abc) {\n" " x(abc && y(def, abc->a));\n" " if (abc) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(ABC *abc) {\n" " x(def && abc && y(def, abc->a));\n" " if (abc) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #3228 - calling function with null object @@ -622,7 +631,7 @@ class TestNullPointer : public TestFixture { const char code[] = "void f(Fred *fred) {\n" " fred->x();\n" " if (fred) { }\n" - "}"; + "}\n"; check(code); ASSERT_EQUALS( "[test.cpp:3:9] -> [test.cpp:2:5]: (warning) Either the condition 'fred' is redundant or there is possible null pointer dereference: fred. [nullPointerRedundantCheck]\n", @@ -634,7 +643,7 @@ class TestNullPointer : public TestFixture { "void f(struct FRED *fred) {\n" " fred->x = 0;\n" " IF(!fred){}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" @@ -642,7 +651,7 @@ class TestNullPointer : public TestFixture { " if (!buffer)\n" " uv_fatal_error();\n" " buffer->x = 11;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -656,14 +665,14 @@ class TestNullPointer : public TestFixture { " *p = 0;\n" " if (!p)\n" " ;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:9] -> [test.cpp:3:6]: (warning) Either the condition '!p' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", errout_str()); check("void foo(int *p)\n" "{\n" " *p = 0;\n" " if (p) { }\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:4:9] -> [test.cpp:3:6]: (warning) Either the condition 'p' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", errout_str()); @@ -672,7 +681,7 @@ class TestNullPointer : public TestFixture { "{\n" " *p = 0;\n" " if (p || q) { }\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:4:9] -> [test.cpp:3:6]: (warning) Either the condition 'p' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", errout_str()); @@ -682,7 +691,7 @@ class TestNullPointer : public TestFixture { " bar(*p);\n" " if (!p)\n" " ;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:9] -> [test.cpp:3:10]: (warning) Either the condition '!p' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", errout_str()); check("void foo(char *p)\n" @@ -690,14 +699,14 @@ class TestNullPointer : public TestFixture { " strcpy(p, \"abc\");\n" " if (!p)\n" " ;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:9] -> [test.cpp:3:12]: (warning) Either the condition '!p' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", errout_str()); check("void foo(char *p)\n" "{\n" " if (*p == 0) { }\n" " if (!p) { }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:9] -> [test.cpp:3:10]: (warning) Either the condition '!p' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", errout_str()); // no error @@ -707,7 +716,7 @@ class TestNullPointer : public TestFixture { " f(&p);\n" " if (!p)\n" " ;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo()\n" @@ -715,7 +724,7 @@ class TestNullPointer : public TestFixture { " int **p = f();\n" " if (!p)\n" " ;\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); check("void foo(int *p)\n" @@ -726,7 +735,7 @@ class TestNullPointer : public TestFixture { " *p = 0;\n" " if (!p)\n" " ;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int x)\n" @@ -734,7 +743,7 @@ class TestNullPointer : public TestFixture { " int a = 2 * x;" " if (x == 0)\n" " ;\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); check("void foo(int *p)\n" @@ -742,7 +751,7 @@ class TestNullPointer : public TestFixture { " int var1 = p ? *p : 0;\n" " if (!p)\n" " ;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int *p, bool x)\n" @@ -750,7 +759,7 @@ class TestNullPointer : public TestFixture { " int var1 = x ? *p : 5;\n" " if (!p)\n" " ;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:4:9] -> [test.cpp:3:21]: (warning) Either the condition '!p' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", errout_str()); @@ -759,13 +768,13 @@ class TestNullPointer : public TestFixture { check("void f(int *p) {\n" " *p = 0;\n" " while (p) { p = 0; }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int *p) {\n" " *p = 0;\n" " while (p) { }\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:3:12] -> [test.cpp:2:6]: (warning) Either the condition 'p' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", errout_str()); @@ -776,7 +785,7 @@ class TestNullPointer : public TestFixture { " int var1 = p ? (p->a) : 0;\n" " if (!p)\n" " ;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(ABC *p)\n" @@ -784,7 +793,7 @@ class TestNullPointer : public TestFixture { " int var1 = p ? (1 + p->a) : 0;\n" " if (!p)\n" " ;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -816,7 +825,7 @@ class TestNullPointer : public TestFixture { " break;\n" " else\n" " p = p->next();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(Document *doc) {\n" @@ -824,27 +833,27 @@ class TestNullPointer : public TestFixture { " if (!doc) {\n" " return;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #3128 - false positive check("void f(int *p) {\n" " assert(!p || (*p<=6));\n" " if (p) { *p = 0; }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int *p) {\n" " assert(p && (*p<=6));\n" " if (p) { *p = 0; }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int *p) {\n" " *p = 12;\n" " assert(p && (*p<=6));\n" " if (p) { *p = 0; }\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:3:12] -> [test.cpp:2:6]: (warning) Either the condition 'p' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", errout_str()); @@ -854,7 +863,7 @@ class TestNullPointer : public TestFixture { " p = p->next;\n" " if (!p)\n" " ;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(x *p)\n" @@ -862,7 +871,7 @@ class TestNullPointer : public TestFixture { " p = bar(p->next);\n" " if (!p)\n" " ;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(x *p)\n" @@ -870,7 +879,7 @@ class TestNullPointer : public TestFixture { " p = aa->bar(p->next);\n" " if (!p)\n" " ;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(x *p)\n" @@ -878,7 +887,7 @@ class TestNullPointer : public TestFixture { " p = *p2 = p->next;\n" " if (!p)\n" " ;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(struct ABC *abc)\n" @@ -886,32 +895,32 @@ class TestNullPointer : public TestFixture { " abc = abc ? abc->next : 0;\n" " if (!abc)\n" " ;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(struct ABC *abc) {\n" // #4523 " abc = (*abc).next;\n" " if (abc) { }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(struct ABC *abc) {\n" // #4523 " abc = (*abc->ptr);\n" " if (abc) { }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int f(Item *item) {\n" " x = item ? ab(item->x) : 0;\n" " if (item) { }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int f(Item *item) {\n" " item->x = 0;\n" " a = b ? c : d;\n" " if (item) { }\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:4:9] -> [test.cpp:2:5]: (warning) Either the condition 'item' is redundant or there is possible null pointer dereference: item. [nullPointerRedundantCheck]\n", errout_str()); @@ -925,7 +934,7 @@ class TestNullPointer : public TestFixture { "\n" " if( !pFrm )\n" " return FALSE;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Ticket #2463 @@ -945,7 +954,7 @@ class TestNullPointer : public TestFixture { " break;\n" " }\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #2525 - sizeof @@ -954,26 +963,26 @@ class TestNullPointer : public TestFixture { " int c = sizeof(test[0]);\n" " if (!test)\n" " ;\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); check("void f(type* p) {\n" // #4983 " x(sizeof p[0]);\n" " if (!p)\n" " ;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #3023 - checked deref check("void f(struct ABC *abc) {\n" " WARN_ON(!abc || abc->x == 0);\n" " if (!abc) { }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(struct ABC *abc) {\n" " WARN_ON(!abc || abc->x == 7);\n" " if (!abc) { }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #3425 - false positives when there are macros @@ -981,14 +990,14 @@ class TestNullPointer : public TestFixture { "void f(int *p) {\n" " *p = 0;\n" " IF(!p){}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" // #3914 - false positive " int *p;\n" " ((p=ret()) && (x=*p));\n" " if (p);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct S { struct T { char c; } *p; };\n" // #6541 @@ -1012,7 +1021,7 @@ class TestNullPointer : public TestFixture { " char c = a.c();\n" " if (!a)\n" " return;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1027,18 +1036,18 @@ class TestNullPointer : public TestFixture { " } else { if (a == 2) {\n" " p = new FooCar; } }\n" " p->abcd();\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:8]: (error) Possible null pointer dereference: p\n", "", errout_str()); check("static void foo() {\n" " int &r = *(int*)0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:15]: (error) Null pointer dereference: (int*)0 [nullPointer]\n", errout_str()); check("static void foo(int x) {\n" " int y = 5 + *(int*)0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:18]: (error) Null pointer dereference: (int*)0 [nullPointer]\n", errout_str()); { @@ -1053,7 +1062,7 @@ class TestNullPointer : public TestFixture { check("static void foo() {\n" " std::cout << *(int*)0;" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:19]: (error) Null pointer dereference: (int*)0 [nullPointer]\n", errout_str()); check("void f()\n" @@ -1063,12 +1072,12 @@ class TestNullPointer : public TestFixture { " delete c;\n" " }\n" " c[0] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:5]: (error) Null pointer dereference: c [nullPointer]\n", errout_str()); check("static void foo() {\n" " if (3 > *(int*)0);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:14]: (error) Null pointer dereference: (int*)0 [nullPointer]\n", errout_str()); // no false positive.. @@ -1077,20 +1086,20 @@ class TestNullPointer : public TestFixture { " Foo *p = 0;\n" " p = new Foo;\n" " p->abcd();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo()\n" "{\n" " int sz = sizeof((*(struct dummy *)0).x);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void get_offset(long &offset)\n" "{\n" " mystruct * temp; temp = 0;\n" " offset = (long)(&(temp->z));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Ticket #1893 - try/catch inside else @@ -1109,7 +1118,7 @@ class TestNullPointer : public TestFixture { " }\n" " *Q=1;\n" " return Q;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int *test(int *Z)\n" @@ -1125,7 +1134,7 @@ class TestNullPointer : public TestFixture { " }\n" " *Q=1;\n" " return Q;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:12:6]: (warning) Possible null pointer dereference: Q [nullPointer]\n", errout_str()); // Ticket #2052 (false positive for 'else continue;') @@ -1136,7 +1145,7 @@ class TestNullPointer : public TestFixture { " else continue;\n" " *p = 0;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // function pointer.. @@ -1145,7 +1154,7 @@ class TestNullPointer : public TestFixture { " void (*f)();\n" " f = 0;\n" " f();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:5]: (error) Null pointer dereference: f [nullPointer]\n", errout_str()); check("int* g();\n" // #11007 @@ -1163,7 +1172,7 @@ class TestNullPointer : public TestFixture { " for (int i = 0; i < 10; ++i) {\n" " int x = *p + 1;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:18]: (error) Null pointer dereference: p [nullPointer]\n", errout_str()); check("void f(int a) {\n" @@ -1174,21 +1183,21 @@ class TestNullPointer : public TestFixture { " for (int i = 0; i < 3; i++) {\n" " if (a && (p[i] == '1'));\n" " }\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); // ticket #2251: taking the address of member check("void f() {\n" " Fred *fred = 0;\n" " int x = &fred->x;\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); // ticket #3220: dereferencing a null pointer is UB check("void f() {\n" " Fred *fred = NULL;\n" " fred->do_something();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (error) Null pointer dereference: fred [nullPointer]\n", errout_str()); // ticket #3570 - parsing of conditions @@ -1198,21 +1207,21 @@ class TestNullPointer : public TestFixture { " if (x)\n" " p = q;\n" " if (p && *p) { }\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " int *p = NULL;\n" " if (x)\n" " p = q;\n" " if (!p || *p) { }\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " int *p = NULL;\n" " if (x)\n" " p = q;\n" " if (p || *p) { }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:15]: (warning) Possible null pointer dereference: p [nullPointer]\n", errout_str()); } @@ -1228,7 +1237,7 @@ class TestNullPointer : public TestFixture { "\n" "void g() {\n" " f(NULL, NULL);\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); } @@ -1253,7 +1262,7 @@ class TestNullPointer : public TestFixture { " for (int i = 0; i < n; i++) {\n" " argv32[i] = 0;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // No false negative: @@ -1268,7 +1277,7 @@ class TestNullPointer : public TestFixture { " for (int i = 0; i < n; i++) {\n" " argv32[i] = 0;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:10:9]: (warning) Possible null pointer dereference: argv32 [nullPointer]\n", errout_str()); // #2231 - error if assignment in loop is not used @@ -1284,7 +1293,7 @@ class TestNullPointer : public TestFixture { " }\n" "\n" " *p = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:11:6]: (warning) Possible null pointer dereference: p [nullPointer]\n", errout_str()); } @@ -1293,7 +1302,7 @@ class TestNullPointer : public TestFixture { "{\n" " wxLongLong x = 0;\n" " int y = x.GetValue();\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); } @@ -1302,7 +1311,7 @@ class TestNullPointer : public TestFixture { "{\n" " std::string * x = 0;\n" " *x = \"test\";\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:4]: (error) Null pointer dereference: x [nullPointer]\n", errout_str()); } @@ -1312,7 +1321,7 @@ class TestNullPointer : public TestFixture { "{\n" " struct my_type* p = 0;\n" " p->x = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:3]: (error) Null pointer dereference: p [nullPointer]\n", errout_str()); } @@ -1324,7 +1333,7 @@ class TestNullPointer : public TestFixture { " struct my_type* p;\n" " p = 0;\n" " return p->x;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:10]: (error) Null pointer dereference: p [nullPointer]\n", errout_str()); } @@ -1338,7 +1347,8 @@ class TestNullPointer : public TestFixture { check(code); // C++ file => nullptr means NULL ASSERT_EQUALS("[test.cpp:4:11]: (error) Null pointer dereference: i [nullPointer]\n", errout_str()); - check(code, dinit(CheckOptions, $.cpp = false, $.cstd = Standards::C17)); // C17 file => nullptr does not mean NULL + const Settings s = settingsBuilder(settings).c(Standards::C17).build(); + check(code, s, false); // C17 file => nullptr does not mean NULL ASSERT_EQUALS("", errout_str()); check(code, dinit(CheckOptions, $.cpp = false)); @@ -1350,7 +1360,7 @@ class TestNullPointer : public TestFixture { " char *p = 0;\n" " if (x) p = \"abcd\";\n" " return p ? f(*p) : f(0);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1359,7 +1369,7 @@ class TestNullPointer : public TestFixture { " int *p = 0;\n" " bar(&p);\n" " *p = 0;\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); } @@ -1368,14 +1378,14 @@ class TestNullPointer : public TestFixture { " int *p = 0;\n" " if (x) { return 0; }\n" " return !p || *p;\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); check("int foo() {\n" " int *p = 0;\n" " if (x) { return 0; }\n" " return p && *p;\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); } @@ -1388,14 +1398,14 @@ class TestNullPointer : public TestFixture { " {\n" " i++;\n" " };\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:10]: (error) Null pointer dereference: str [nullPointer]\n", errout_str()); } void nullpointer19() { // #3811 check("int foo() {\n" " perror(0);\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); } @@ -1404,14 +1414,14 @@ class TestNullPointer : public TestFixture { " struct xy *p = 0;\n" " if (x) p = q;\n" " if (p ? p->x || p->y : 0) { }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" // false negative " struct xy *p = 0;\n" " if (x) p = q;\n" " if (y ? p->x : p->y) { }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:13]: (warning) Possible null pointer dereference: p [nullPointer]\n" "[test.cpp:4:20]: (warning) Possible null pointer dereference: p [nullPointer]\n", errout_str()); @@ -1423,7 +1433,7 @@ class TestNullPointer : public TestFixture { " if (x) p = q;\n" " else return;\n" " *p = 0;\n" // <- p is not NULL - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1432,7 +1442,7 @@ class TestNullPointer : public TestFixture { " char *c = NULL;\n" " char cBuf[10];\n" " sprintf(cBuf, \"%s\", c ? c : \"0\" );\n" - "}"); + "}\n"); ASSERT_EQUALS("",errout_str()); } @@ -1441,7 +1451,7 @@ class TestNullPointer : public TestFixture { " char *c = NULL;\n" " x = c = new char[10];\n" " *c = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1451,7 +1461,7 @@ class TestNullPointer : public TestFixture { " int *array = NULL;\n" " if (data == 1 && array[i] == 0)\n" " std::cout << \"test\";\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:22]: (error) Null pointer dereference: array [nullPointer]\n", errout_str()); } @@ -1468,7 +1478,7 @@ class TestNullPointer : public TestFixture { " if (t2 && (!t1))\n" " return 0.0;\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1482,7 +1492,7 @@ class TestNullPointer : public TestFixture { " pointer_=NULL;\n" " *pointer_=0;\n" " return *this;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:6]: (error) Null pointer dereference: pointer_ [nullPointer]\n", errout_str()); } @@ -1492,7 +1502,7 @@ class TestNullPointer : public TestFixture { " int i = s ? s->value + 1\n" " : s->value - 1; // <-- null ptr dereference\n" " return i;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:3:11] -> [test.cpp:4:15]: (warning) Either the condition 's' is redundant or there is possible null pointer dereference: s. [nullPointerRedundantCheck]\n", errout_str()); @@ -1543,7 +1553,7 @@ class TestNullPointer : public TestFixture { " }\n" "\n" " (void)f->x;\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); } @@ -1655,7 +1665,7 @@ class TestNullPointer : public TestFixture { "void f(struct A *a) {\n" " if (a->x == NULL) {}\n" " *(a->x);\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:3:14] -> [test.cpp:4:8]: (warning) Either the condition 'a->x==NULL' is redundant or there is possible null pointer dereference: a->x. [nullPointerRedundantCheck]\n", errout_str()); @@ -1666,7 +1676,7 @@ class TestNullPointer : public TestFixture { "void f(struct A *a) {\n" " if (a->x == nullptr) {}\n" " *(a->x);\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:3:14] -> [test.cpp:4:8]: (warning) Either the condition 'a->x==nullptr' is redundant or there is possible null pointer dereference: a->x. [nullPointerRedundantCheck]\n", errout_str()); @@ -1677,7 +1687,7 @@ class TestNullPointer : public TestFixture { "void f(struct A *a) {\n" " if (a->g() == nullptr) {}\n" " *(a->g());\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:3:16] -> [test.cpp:4:11]: (warning) Either the condition 'a->g()==nullptr' is redundant or there is possible null pointer dereference: a->g(). [nullPointerRedundantCheck]\n", errout_str()); @@ -1686,7 +1696,7 @@ class TestNullPointer : public TestFixture { "void f(struct A *a) {\n" " if (a->g() == nullptr) {}\n" " *(a->g());\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1695,7 +1705,7 @@ class TestNullPointer : public TestFixture { "void f(struct A *a) {\n" " if (a->g() == nullptr) {}\n" " *(a->g());\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:3:16] -> [test.cpp:4:11]: (warning) Either the condition 'a->g()==nullptr' is redundant or there is possible null pointer dereference: a->g(). [nullPointerRedundantCheck]\n", errout_str()); @@ -1708,7 +1718,7 @@ class TestNullPointer : public TestFixture { " if (x) {\n" " (void)*a->x;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1720,7 +1730,7 @@ class TestNullPointer : public TestFixture { " if ( w == 0.0 )\n" " return 0;\n" " return b->get();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #9423 check("extern F* GetF();\n" @@ -1737,7 +1747,7 @@ class TestNullPointer : public TestFixture { " if (!lPtrOk)\n" " return;\n" " lPtr->Clear();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1754,7 +1764,7 @@ class TestNullPointer : public TestFixture { " g();\n" " a d = *c->b();\n" " return d;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct a {\n" @@ -1770,7 +1780,7 @@ class TestNullPointer : public TestFixture { " e();\n" " a d = *c->b();\n" " return d;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1778,7 +1788,7 @@ class TestNullPointer : public TestFixture { check("void f() {\n" " char* p = new(std::nothrow) char[1];\n" " if( p ) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1787,7 +1797,7 @@ class TestNullPointer : public TestFixture { " if(!p[0]) {}\n" " const int *const a = p;\n" " if(!a){}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:7] -> [test.cpp:2:8]: (warning) Either the condition '!a' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", errout_str()); } @@ -1796,7 +1806,7 @@ class TestNullPointer : public TestFixture { "auto f(T& x) -> decltype(x);\n" "int& g(int* x) {\n" " return f(*x);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1806,7 +1816,7 @@ class TestNullPointer : public TestFixture { " if(n > 10) q = p;\n" " *p +=2;\n" " if(n < 120) *q+=12;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:18]: (warning) Possible null pointer dereference: q [nullPointer]\n", errout_str()); check("void f(int *p, int n) {\n" @@ -1814,7 +1824,7 @@ class TestNullPointer : public TestFixture { " if(n > 10) q = p;\n" " *p +=2;\n" " if(n > 10) *q+=12;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1828,7 +1838,7 @@ class TestNullPointer : public TestFixture { " }\n" " }\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:2:8] -> [test.cpp:6:18]: (warning) Either the condition '!p' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", errout_str()); @@ -1844,7 +1854,7 @@ class TestNullPointer : public TestFixture { " if (c(e, \"\"))\n" " return nullptr;\n" " return e->b();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1854,7 +1864,7 @@ class TestNullPointer : public TestFixture { " if(b) c = b;\n" " if (!c) c = &a;\n" " return *c;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int f(int a, int* b) {\n" @@ -1863,7 +1873,7 @@ class TestNullPointer : public TestFixture { " bool d = !c;\n" " if (d) c = &a;\n" " return *c;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct A { int* x; };\n" @@ -1873,7 +1883,7 @@ class TestNullPointer : public TestFixture { " if(b) c.x = b;\n" " if (!c.x) c.x = &a;\n" " return *c.x;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct A { int* x; };\n" @@ -1884,7 +1894,7 @@ class TestNullPointer : public TestFixture { " bool d = !c.x;\n" " if (d) c.x = &a;\n" " return *c.x;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct A { int* x; };\n" @@ -1907,7 +1917,7 @@ class TestNullPointer : public TestFixture { "}\n" "void bar() {\n" " f(0, 0);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (warning) Possible null pointer dereference: params [nullPointer]\n", errout_str()); } @@ -1923,7 +1933,7 @@ class TestNullPointer : public TestFixture { "int bar() {\n" " int **array = NULL;\n" " foo (array, 0);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1933,7 +1943,7 @@ class TestNullPointer : public TestFixture { " while (tok3->astParent() && tok3->str() == \",\")\n" " tok3 = tok3->astParent();\n" " if (tok3 && tok3->str() == \"(\") {}\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:5:9] -> [test.cpp:3:12]: (warning) Either the condition 'tok3' is redundant or there is possible null pointer dereference: tok3. [nullPointerRedundantCheck]\n", errout_str()); @@ -1946,7 +1956,7 @@ class TestNullPointer : public TestFixture { " }\n" " if (!t1 || !t2)\n" " return;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("bool f(int* i);\n" @@ -1954,7 +1964,7 @@ class TestNullPointer : public TestFixture { " while(f(i) && *i == 0)\n" " i++;\n" " if (!i) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1966,7 +1976,7 @@ class TestNullPointer : public TestFixture { " ListEntry *prev = NULL;\n" " for (ListEntry *cursor = listHead; cursor != NULL; prev = cursor, cursor = cursor->next) {}\n" " if (prev) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -2443,6 +2453,9 @@ class TestNullPointer : public TestFixture { void nullpointer77() { + // No warning: 'i' is passed to the unknown function 'h' in the same condition that guards the + // dereference. 'h' may validate the pointer (e.g. return false for null), so '*i' can be safe + // - this is the common "if (check(p) && p->...)" pattern, so we must not assume 'i' is null. check("bool h(int*);\n" "void f(int* i) {\n" " int* i = nullptr;\n" @@ -2458,6 +2471,8 @@ class TestNullPointer : public TestFixture { "}\n"); ASSERT_EQUALS("", errout_str()); + // Likewise here, even though 'i' is null when the first 'h(i)' was true: the second 'h(i)' is an + // independent call that may validate 'i', so '*i' is not necessarily a null dereference. check("bool h(int*);\n" "void f(int* x) {\n" " int* i = x;\n" @@ -2484,7 +2499,7 @@ class TestNullPointer : public TestFixture { " int *p = 0;\n" " pp = &p;\n" " **pp = 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:6]: (error) Null pointer dereference: *pp [nullPointer]\n", errout_str()); } @@ -2720,7 +2735,7 @@ class TestNullPointer : public TestFixture { " }\n" " }\n" " return \"unknown\";\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:7:12] -> [test.cpp:3:7]: (warning) Either the condition 'ctx' is redundant or there is possible null pointer dereference: ctx. [nullPointerRedundantCheck]\n", errout_str()); @@ -2762,7 +2777,7 @@ class TestNullPointer : public TestFixture { " int* myNull = GetThing();\n" " *myNull=42;\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:10]: (error) Null pointer dereference: myNull [nullPointer]\n", errout_str()); check("struct foo {\n" @@ -2773,7 +2788,7 @@ class TestNullPointer : public TestFixture { " int* myNull = myFoo.GetThing();\n" " *myNull=42;\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:10]: (error) Null pointer dereference: myNull [nullPointer]\n", errout_str()); check("struct T { bool g() const; };\n" @@ -2870,7 +2885,7 @@ class TestNullPointer : public TestFixture { " int * buf = foo(true);\n" " buf[2] = 0;" // << " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:14:9]: (error) Null pointer dereference: buf [nullPointer]\n", errout_str()); } @@ -2961,17 +2976,188 @@ class TestNullPointer : public TestFixture { ASSERT_EQUALS("", errout_str()); } + void nullpointer106() // #13682 + { + // An unrelated condition between the null check and the dereference must not stop the analysis + check("struct S {\n" + " bool b;\n" + " bool f() const;\n" + "};\n" + "void f(const S* p, const S* o) {\n" + " const S* p1 = p;\n" + " if (p1 && p1->f())\n" + " return;\n" + " if (p == o)\n" + " return;\n" + " if (p1->b) {}\n" + "}\n"); + ASSERT_EQUALS( + "[test.cpp:7:9] -> [test.cpp:11:9]: (warning) Either the condition 'p1' is redundant or there is possible null pointer dereference: p1. [nullPointerRedundantCheck]\n", + errout_str()); + } + + void nullpointer107() // #13682 - guards that depend on the pointer indirectly + { + // cached null-check 'ok'; guard 'if (!ok)' is safe -> no FP + check("struct S { void g(); bool f() const; };\n" + "void f(S* p) {\n" + " bool ok = (p != nullptr);\n" + " if (p && p->f())\n" + " return;\n" + " if (!ok)\n" + " return;\n" + " p->g();\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + // unrelated bool guard -> conservative, no FP + check("struct S { void g(); bool f() const; };\n" + "void f(S* p, bool valid) {\n" + " S* p1 = p;\n" + " if (p1 && p1->f())\n" + " return;\n" + " if (!valid)\n" + " return;\n" + " p1->g();\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + // guard on a different pointer -> no FP + check("struct S { void g(); bool f() const; };\n" + "void f(S* p, S* q) {\n" + " S* p1 = p;\n" + " if (p1 && p1->f())\n" + " return;\n" + " if (!q)\n" + " return;\n" + " p1->g();\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + // direct null guard on the alias -> no FP + check("struct S { void g(); bool f() const; };\n" + "void f(S* p) {\n" + " S* p1 = p;\n" + " if (p1 && p1->f())\n" + " return;\n" + " if (!p)\n" + " return;\n" + " p1->g();\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + // FN: 'if (ok)' => survivor has p==nullptr, but the cached 'ok' is not followed -> should warn + check("struct S { void g(); bool f() const; };\n" + "void f(S* p) {\n" + " bool ok = (p != nullptr);\n" + " if (p && p->f())\n" + " return;\n" + " if (ok)\n" + " return;\n" + " p->g();\n" + "}\n"); + TODO_ASSERT_EQUALS( + "[test.cpp:4:9] -> [test.cpp:8:5]: (warning) Either the condition 'p' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", + "", + errout_str()); + + // FN: sink(q) drops the q==p symbolic, so guard 'if (q)' is no longer seen to relate to p -> should warn + check("struct S { void g(); bool f() const; };\n" + "void sink(S*&);\n" + "void f(S* p) {\n" + " S* q = p;\n" + " if (p && p->f())\n" + " return;\n" + " sink(q);\n" + " if (q)\n" + " return;\n" + " p->g();\n" + "}\n"); + TODO_ASSERT_EQUALS( + "[test.cpp:5:9] -> [test.cpp:10:5]: (warning) Either the condition 'p' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", + "", + errout_str()); + + // a conditional modification makes ProgramMemory drop the guard (FP-prone) -> must stay quiet: + // alias 'q==p' re-assigned to p under a condition + check("struct S { void g(); bool f() const; };\n" + "void f(S* p, bool c) {\n" + " S* q = p;\n" + " if (p && p->f())\n" + " return;\n" + " if (c)\n" + " q = p;\n" + " if (!q)\n" + " return;\n" + " p->g();\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + // cached 'ok' refreshed under a condition + check("struct S { void g(); bool f() const; };\n" + "void f(S* p, bool c) {\n" + " bool ok = (p != nullptr);\n" + " if (p && p->f())\n" + " return;\n" + " if (c)\n" + " ok = (p != nullptr);\n" + " if (!ok)\n" + " return;\n" + " p->g();\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + } + + void nullpointer108() { // #14422 + check("void f() {\n" + " int *p{};\n" + " int *&r{p};\n" + " if (*r) {}\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:4:10]: (error) Null pointer dereference: r [nullPointer]\n", errout_str()); + } + + void nullpointer109() + { + check("boost::asio::awaitable test()\n" + "{\n" + " const auto *s = getStr();\n" + " if(!s) co_return int{1};\n" + " std::print(\"{}\",*s);\n" + " co_return int{9};\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + } + + void nullpointer110() + { // #14937 - noreturn member function called on operator() result + check("struct A {\n" + " [[noreturn]] void g(int);\n" + "};\n" + "template\n" + "struct Thunk {\n" + " T& operator()() const;\n" + "};\n" + "void f(Thunk thunk, int* p) {\n" + " if (!p)\n" + " thunk().g(0);\n" + " *p = 1;\n" + "}\n", + dinit(CheckOptions, $.inconclusive = true)); + ASSERT_EQUALS("", errout_str()); + } + void nullpointer_addressOf() { // address of check("void f() {\n" " struct X *x = 0;\n" " if (addr == &x->y) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " struct X *x = 0;\n" " if (addr == &x->y.z[0]) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); checkP("typedef int Count;\n" // #10018 @@ -2997,7 +3183,7 @@ class TestNullPointer : public TestFixture { " break;\n" " }\n" " return p;\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("[test.cpp:7:10]: (warning) Possible null pointer dereference: p [nullPointer]\n", errout_str()); } @@ -3007,7 +3193,7 @@ class TestNullPointer : public TestFixture { " while (*p && nasm_isspace(*p))\n" " p++;\n" " return p;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(char* origin) {\n" // #11449 @@ -3022,7 +3208,7 @@ class TestNullPointer : public TestFixture { check("void f () {\n" " int *buf; buf = NULL;\n" " buf;\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); } @@ -3044,27 +3230,27 @@ class TestNullPointer : public TestFixture { " if (!p) {\n" " }\n" " *p = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9] -> [test.cpp:4:6]: (warning) Either the condition '!p' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", errout_str()); check("void foo(char *p) {\n" " if (p && *p == 0) {\n" " }\n" " printf(\"%c\", *p);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9] -> [test.cpp:4:19]: (warning) Either the condition 'p' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", errout_str()); check("void foo(char *p) {\n" " if (p && *p == 0) {\n" " } else { *p = 0; }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9] -> [test.cpp:3:15]: (warning) Either the condition 'p' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", errout_str()); check("void foo(char *p) {\n" " if (p) {\n" " }\n" " strcpy(p, \"abc\");\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9] -> [test.cpp:4:12]: (warning) Either the condition 'p' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", errout_str()); check("void foo(char *p) {\n" @@ -3072,7 +3258,7 @@ class TestNullPointer : public TestFixture { " }\n" " bar();\n" " strcpy(p, \"abc\");\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9] -> [test.cpp:5:12]: (warning) Either the condition 'p' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", errout_str()); check("void foo(abc *p) {\n" @@ -3080,7 +3266,7 @@ class TestNullPointer : public TestFixture { " }\n" " else { if (!p->x) {\n" " } }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); { @@ -3090,7 +3276,7 @@ class TestNullPointer : public TestFixture { " abort();\n" " }\n" " *p = 0;\n" - "}"; + "}\n"; check(code); ASSERT_EQUALS("", errout_str()); @@ -3103,7 +3289,7 @@ class TestNullPointer : public TestFixture { " (*bail)();\n" " }\n" " *p = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(char *p) {\n" @@ -3111,7 +3297,7 @@ class TestNullPointer : public TestFixture { " throw x;\n" " }\n" " *p = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(char *p) {\n" @@ -3119,21 +3305,21 @@ class TestNullPointer : public TestFixture { " ab.abort();\n" " }\n" " *p = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(char *p) {\n" " if (!p) {\n" " switch (x) { }\n" " }\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); check("void foo(char *p) {\n" " if (!p) {\n" " }\n" " return *x;\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); check("int foo(int *p) {\n" @@ -3141,7 +3327,7 @@ class TestNullPointer : public TestFixture { " x = *p;\n" " return 5+*p;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9] -> [test.cpp:3:14]: (warning) Either the condition '!p' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n" "[test.cpp:2:9] -> [test.cpp:4:19]: (warning) Either the condition '!p' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", errout_str()); @@ -3151,7 +3337,7 @@ class TestNullPointer : public TestFixture { " if (!a) {\n" " a.x();\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // This is why this check can't be used on the simplified token list @@ -3159,7 +3345,7 @@ class TestNullPointer : public TestFixture { " if (!dynamic_cast(foo)) {\n" " *foo = 0;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // ticket: #2300 - calling unknown function that may initialize the pointer @@ -3169,7 +3355,7 @@ class TestNullPointer : public TestFixture { " initfred();\n" " fred->x = 0;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // ticket #1219 @@ -3178,7 +3364,7 @@ class TestNullPointer : public TestFixture { " return;\n" " }\n" " *p = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9] -> [test.cpp:5:6]: (warning) Either the condition 'p' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", errout_str()); // #2467 - unknown macro may terminate the application @@ -3187,7 +3373,7 @@ class TestNullPointer : public TestFixture { " MACRO;\n" " }\n" " fred->a();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #2493 - switch @@ -3200,7 +3386,7 @@ class TestNullPointer : public TestFixture { " fred->a();\n" " break;\n" " };\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #4118 - second if @@ -3208,7 +3394,7 @@ class TestNullPointer : public TestFixture { " int x = 1;\n" " if (!p) x = 0;\n" " if (x) *p = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #2674 - different functions @@ -3225,7 +3411,7 @@ class TestNullPointer : public TestFixture { "\n" "void Fred::b() {\n" " wilma->Reload();\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); check("void test(int *i) {\n" @@ -3233,7 +3419,7 @@ class TestNullPointer : public TestFixture { " else {\n" " int b = *i;\n" " }\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); // #2696 - false positives nr 1 @@ -3246,7 +3432,7 @@ class TestNullPointer : public TestFixture { "\n" " if (pFoo)\n" " bar();\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); // #2696 - false positives nr 2 @@ -3259,7 +3445,7 @@ class TestNullPointer : public TestFixture { " pFoo = pFoo->next;\n" "\n" " len = sizeof(pFoo->data);\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); // #2696 - false positives nr 3 @@ -3272,13 +3458,13 @@ class TestNullPointer : public TestFixture { " pFoo = pFoo->next;\n" "\n" " len = decltype(*pFoo);\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); check("int foo(struct Fred *fred) {\n" " if (fred) { }\n" " return fred->a;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9] -> [test.cpp:3:12]: (warning) Either the condition 'fred' is redundant or there is possible null pointer dereference: fred. [nullPointerRedundantCheck]\n", errout_str()); // #2789 - assign and check pointer @@ -3286,7 +3472,7 @@ class TestNullPointer : public TestFixture { " char *p; p = x();\n" " if (!p) { }\n" " *p = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:9] -> [test.cpp:4:6]: (warning) Either the condition '!p' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", errout_str()); // check, assign and use @@ -3295,7 +3481,7 @@ class TestNullPointer : public TestFixture { " if (p == 0 && (p = malloc(10)) != 0) {\n" " *p = 0;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // check, assign and use @@ -3304,7 +3490,7 @@ class TestNullPointer : public TestFixture { " if (p == 0 && (p = malloc(10)) != a && (*p = a)) {\n" " *p = 0;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // check, and use @@ -3313,7 +3499,7 @@ class TestNullPointer : public TestFixture { " if (p == 0 && (*p = 0)) {\n" " return;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:11] -> [test.cpp:3:21]: (warning) Either the condition 'p==0' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", errout_str()); // check, and use @@ -3322,7 +3508,7 @@ class TestNullPointer : public TestFixture { " if (p == 0 && p->x == 10) {\n" " return;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:11] -> [test.cpp:3:19]: (warning) Either the condition 'p==0' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", errout_str()); // check, and use @@ -3331,7 +3517,7 @@ class TestNullPointer : public TestFixture { " if (p == 0 || p->x == 10) {\n" " return;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // check, and use @@ -3340,7 +3526,7 @@ class TestNullPointer : public TestFixture { " if (p == NULL && (*p = a)) {\n" " return;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:11] -> [test.cpp:3:24]: (warning) Either the condition 'p==NULL' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", errout_str()); // check, and use @@ -3348,14 +3534,14 @@ class TestNullPointer : public TestFixture { " if (!p && x==1 || p && p->x==0) {\n" " return;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); { const char code[] = "void f(Fred *fred) {\n" " if (fred == NULL) { }\n" " fred->x();\n" - "}"; + "}\n"; check(code); // inconclusive ASSERT_EQUALS("[test.cpp:2:14] -> [test.cpp:3:5]: (warning) Either the condition 'fred==NULL' is redundant or there is possible null pointer dereference: fred. [nullPointerRedundantCheck]\n", errout_str()); @@ -3364,52 +3550,52 @@ class TestNullPointer : public TestFixture { check("void f(char *s) {\n" // #3358 " if (s==0);\n" " strcpy(a, s?b:c);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // sizeof check("void f(struct fred_t *fred) {\n" " if (!fred)\n" " int sz = sizeof(fred->x);\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); // check in macro check("void f(int *x) {\n" " $if (!x) {}\n" " *x = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // return ?: check("int f(ABC *p) {\n" // FP : return ?: " if (!p) {}\n" " return p ? p->x : 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int f(ABC *p) {\n" // no fn " if (!p) {}\n" " return q ? p->x : 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9] -> [test.cpp:3:16]: (warning) Either the condition '!p' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", errout_str()); check("int f(ABC *p) {\n" // FP : return && " if (!p) {}\n" " return p && p->x;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x, int *p) {\n" " if (x || !p) {}\n" " *p = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:14] -> [test.cpp:3:6]: (warning) Either the condition '!p' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", errout_str()); // sizeof check("void f() {\n" " int *pointer = NULL;\n" " pointer = func(sizeof pointer[0]);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct T {\n" // #14164 @@ -3425,6 +3611,27 @@ class TestNullPointer : public TestFixture { "}\n"); ASSERT_EQUALS("[test.cpp:8:21] -> [test.cpp:10:18]: (warning) Either the condition 'tok=tok->next' is redundant or there is possible null pointer dereference: tok. [nullPointerRedundantCheck]\n", errout_str()); + + check("void f(const A* a, const B* b) {\n" // #6282 + " if (b && a) {}\n" + " if (a != b && b->value) {}\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:2:9] -> [test.cpp:3:19]: (warning) Either the condition 'b' is redundant or there is possible null pointer dereference: b. [nullPointerRedundantCheck]\n", + errout_str()); + + check("void f(int* a) {\n" // #12859 + " if (!a)\n" + " x;\n" + " *a = 0;\n" + "}\n" + "void g(int* a) {\n" + " if (!a)\n" + " x();\n" + " *a = 0;\n" + "}\n", dinit(CheckOptions, $.inconclusive = true)); + ASSERT_EQUALS("[test.cpp:2:9] -> [test.cpp:4:6]: (warning, inconclusive) Either the condition '!a' is redundant or there is possible null pointer dereference: a. [nullPointerRedundantCheck]\n" + "[test.cpp:7:9] -> [test.cpp:9:6]: (warning, inconclusive) Either the condition '!a' is redundant or there is possible null pointer dereference: a. [nullPointerRedundantCheck]\n", + errout_str()); } // Test CheckNullPointer::nullConstantDereference @@ -3432,12 +3639,12 @@ class TestNullPointer : public TestFixture { check("int f() {\n" " int* p = 0;\n" " return p[4];\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:12]: (error) Null pointer dereference: p [nullPointer]\n", errout_str()); check("void f() {\n" " typeof(*NULL) y;\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); check("int * f() {\n" @@ -3445,7 +3652,7 @@ class TestNullPointer : public TestFixture { "}\n" "int main() {\n" " return *f();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:12]: (error) Null pointer dereference: f() [nullPointer]\n", errout_str()); } @@ -3453,7 +3660,7 @@ class TestNullPointer : public TestFixture { // Ticket #2621 check("void f(struct ABC *abc) {\n" " ({ if (abc) dbg(); })\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -3461,7 +3668,7 @@ class TestNullPointer : public TestFixture { // Ticket #2840 check("void f() {\n" " int bytes = snprintf(0, 0, \"%u\", 1);\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); } @@ -3469,25 +3676,25 @@ class TestNullPointer : public TestFixture { // Ticket #2840 check("void f() {\n" " int bytes = snprintf(0, 10, \"%u\", 1);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:26]: (error) Null pointer dereference [nullPointer]\n", errout_str()); } void printf_with_invalid_va_argument() { check("void f() {\n" " printf(\"%s\", 0);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:18]: (error) Null pointer dereference [nullPointer]\n", errout_str()); check("void f(char* s) {\n" " printf(\"%s\", s);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " char* s = 0;\n" " printf(\"%s\", s);\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:3:18]: (error) Null pointer dereference: s [nullPointer]\n", errout_str()); @@ -3495,23 +3702,23 @@ class TestNullPointer : public TestFixture { check("void f() {\n" " char *s = 0;\n" " printf(\"%s\", s == 0 ? a : s);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " printf(\"%u%s\", 0, 0);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:23]: (error) Null pointer dereference [nullPointer]\n", errout_str()); check("void f(char* s) {\n" " printf(\"%u%s\", 0, s);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " char* s = 0;\n" " printf(\"%u%s\", 123, s);\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:3:25]: (error) Null pointer dereference: s [nullPointer]\n", errout_str()); @@ -3519,51 +3726,51 @@ class TestNullPointer : public TestFixture { check("void f() {\n" " printf(\"%%%s%%\", 0);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:22]: (error) Null pointer dereference [nullPointer]\n", errout_str()); check("void f(char* s) {\n" " printf(\"text: %s, %s\", s, 0);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:31]: (error) Null pointer dereference [nullPointer]\n", errout_str()); check("void f() {\n" " char* s = \"blabla\";\n" " printf(\"%s\", s);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(char* s) {\n" " printf(\"text: %m%s, %s\", s, 0);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:33]: (error) Null pointer dereference [nullPointer]\n", errout_str()); check("void f(char* s) {\n" " printf(\"text: %*s, %s\", s, 0);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:32]: (error) Null pointer dereference [nullPointer]\n", errout_str()); // Ticket #3364 check("void f() {\n" " printf(\"%-*.*s\", s, 0);\n" " sprintf(\"%*\", s);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } void scanf_with_invalid_va_argument() { check("void f(char* s) {\n" " sscanf(s, \"%s\", 0);\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:2:21]: (error) Null pointer dereference [nullPointer]\n", errout_str()); check("void f() {\n" " scanf(\"%d\", 0);\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:2:17]: (error) Null pointer dereference [nullPointer]\n", errout_str()); @@ -3572,19 +3779,19 @@ class TestNullPointer : public TestFixture { " char location[200];\n" " int width, height;\n" " sscanf(imgInfo, \"%s %d %d\", location, &width, &height);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // ticket #3207 check("void f(char *dummy) {\n" " int iVal;\n" " sscanf(dummy, \"%d%c\", &iVal);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // ticket #3211 check("void f(char *dummy) {\n" " int* iVal = 0;\n" " sscanf(dummy, \"%d\", iVal);\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:3:25]: (error) Null pointer dereference: iVal [nullPointer]\n", errout_str()); @@ -3592,18 +3799,18 @@ class TestNullPointer : public TestFixture { check("void f(char *dummy) {\n" " int* iVal;\n" " sscanf(dummy, \"%d\", foo(iVal));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(char *dummy) {\n" " int* iVal = 0;\n" " sscanf(dummy, \"%d%d\", foo(iVal), iVal);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(char* dummy) {\n" " sscanf(dummy, \"%*d%u\", 0);\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:2:28]: (error) Null pointer dereference [nullPointer]\n", errout_str()); @@ -3615,12 +3822,12 @@ class TestNullPointer : public TestFixture { " int* iVal = 0;\n" " if(maybe()) iVal = g();\n" " return iVal[0];\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:12]: (warning) Possible null pointer dereference: iVal [nullPointer]\n", errout_str()); check("int foo(int* iVal) {\n" " return iVal[0];\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); } @@ -3630,20 +3837,20 @@ class TestNullPointer : public TestFixture { "bool foo() {\n" " PolymorphicA* a = 0;\n" " return typeid(*a) == typeid(*a);\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); check("struct NonPolymorphicA { ~A() {} };\n" "bool foo() {\n" " NonPolymorphicA* a = 0;\n" " return typeid(*a) == typeid(*a);\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); check("bool foo() {\n" " char* c = 0;\n" " return typeid(*c) == typeid(*c);\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); } @@ -3652,40 +3859,40 @@ class TestNullPointer : public TestFixture { check("size_t foo() {\n" " char* c = 0;\n" " return alignof(*c);\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); check("size_t foo() {\n" " return alignof(*0);\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); check("void foo(int *p) {\n" " f(alignof(*p));\n" " if (p) {}\n" " return;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("size_t foo() {\n" " char* c = 0;\n" " return _Alignof(*c);\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); check("size_t foo() {\n" " return _alignof(*0);\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); check("size_t foo() {\n" " return __alignof(*0);\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); check("size_t foo() {\n" " return __alignof__(*0);\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); } @@ -3696,7 +3903,7 @@ class TestNullPointer : public TestFixture { " cnt = 0;\n" " for (int i = 0; i < cnt; ++i)\n" " *ptr++ = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #11635 @@ -3710,6 +3917,26 @@ class TestNullPointer : public TestFixture { " i++;\n" "}\n"); ASSERT_EQUALS("", errout_str()); + + check("void f(const int* p) {\n" // #6710 + " for (int i = *p; i < 5; ++i) {}\n" + " if (p) {}\n" + "}\n" + "struct S { int a; };\n" + "void g(const S* s) {\n" + " for (int i = s->a; i < 5; ++i) {}\n" + " if (s) {}\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:3:9] -> [test.cpp:2:19]: (warning) Either the condition 'p' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n" + "[test.cpp:8:9] -> [test.cpp:7:18]: (warning) Either the condition 's' is redundant or there is possible null pointer dereference: s. [nullPointerRedundantCheck]\n", + errout_str()); + + check("struct S { int a; };\n" // #6492 + "void h(const S* s) {\n" + " for (int i = s->a; s; ++i) {}\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:3:24] -> [test.cpp:3:18]: (warning) Either the condition 's' is redundant or there is possible null pointer dereference: s. [nullPointerRedundantCheck]\n", + errout_str()); } void nullpointerDeadCode() { @@ -3786,7 +4013,7 @@ class TestNullPointer : public TestFixture { "void f(int* x) {\n" " if (x)\n" " g(x);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct T {\n" // #14308 @@ -3828,7 +4055,7 @@ class TestNullPointer : public TestFixture { " std::string s4 = p;\n" " std::string s5(p);\n" " foo(std::string(p));\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("[test.cpp:9:10]: (error) Null pointer dereference: p [nullPointer]\n" "[test.cpp:10:22]: (error) Null pointer dereference: p [nullPointer]\n" "[test.cpp:11:20]: (error) Null pointer dereference: p [nullPointer]\n" @@ -3844,7 +4071,7 @@ class TestNullPointer : public TestFixture { " std::string s2 = nullptr;\n" " std::string s3(nullptr);\n" " foo(std::string(nullptr));\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("[test.cpp:2:10]: (error) Null pointer dereference [nullPointer]\n" "[test.cpp:3:22]: (error) Null pointer dereference [nullPointer]\n" "[test.cpp:4:17]: (error) Null pointer dereference [nullPointer]\n" @@ -3856,7 +4083,7 @@ class TestNullPointer : public TestFixture { " std::string s2 = NULL;\n" " std::string s3(NULL);\n" " foo(std::string(NULL));\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("[test.cpp:2:10]: (error) Null pointer dereference [nullPointer]\n" "[test.cpp:3:22]: (error) Null pointer dereference [nullPointer]\n" "[test.cpp:4:17]: (error) Null pointer dereference [nullPointer]\n" @@ -3872,7 +4099,7 @@ class TestNullPointer : public TestFixture { " foo(p == s1);\n" " foo(p == s2);\n" " foo(p == s3);\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("[test.cpp:4:15]: (error) Null pointer dereference: p [nullPointer]\n" "[test.cpp:5:15]: (error) Null pointer dereference: p [nullPointer]\n" "[test.cpp:7:9]: (error) Null pointer dereference: p [nullPointer]\n" @@ -3887,7 +4114,7 @@ class TestNullPointer : public TestFixture { " foo(s1.size() == 0);\n" " foo(s2.size() == 0);\n" " foo(s3->size() == 0);\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); check("void f(std::string s1, const std::string& s2) {\n" @@ -3896,7 +4123,7 @@ class TestNullPointer : public TestFixture { " foo(0 == s2[0]);\n" " foo(s1[0] == 0);\n" " foo(s2[0] == 0);\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); check("void f(std::string s1, const std::string& s2) {\n" @@ -3905,7 +4132,7 @@ class TestNullPointer : public TestFixture { " foo(s2 == '\\0');\n" " foo('\\0' == s1);\n" " foo('\\0' == s2);\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); check("class Bar {\n" @@ -3916,19 +4143,19 @@ class TestNullPointer : public TestFixture { " std::string s;\n" " Foo();\n" "};\n" - "Foo::Foo() : s(0) {}"); + "Foo::Foo() : s(0) {}\n"); ASSERT_EQUALS("[test.cpp:3:13]: (error) Null pointer dereference [nullPointer]\n" "[test.cpp:9:14]: (error) Null pointer dereference [nullPointer]\n", errout_str()); check("void f() {\n" " std::string s = 0 == x ? \"a\" : \"b\";\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " const std::string s = g();\n" " ASSERT_MESSAGE(\"Error on s\", 0 == s.compare(\"Some text\"));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int i, std::string s);\n" @@ -3939,7 +4166,7 @@ class TestNullPointer : public TestFixture { " foo(var, NULL);\n" " foo(var, nullptr);\n" " foo(0, var);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:10]: (error) Null pointer dereference [nullPointer]\n" "[test.cpp:5:12]: (error) Null pointer dereference [nullPointer]\n" "[test.cpp:6:12]: (error) Null pointer dereference [nullPointer]\n" @@ -3984,7 +4211,7 @@ class TestNullPointer : public TestFixture { check("void f(std::ifstream& is) {\n" " char* p = 0;\n" " is >> p;\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:3]: (error) Possible null pointer dereference: p\n", "", errout_str()); check("void f(const std::ostringstream& oss, char* q) {\n" @@ -3993,7 +4220,7 @@ class TestNullPointer : public TestFixture { " oss << foo << p;\n" " if(q == 0)\n" " oss << foo << q;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:12]: (error) Null pointer dereference: p [nullPointer]\n" "[test.cpp:4:19]: (error) Null pointer dereference: p [nullPointer]\n" "[test.cpp:5:10] -> [test.cpp:6:23]: (warning) Either the condition 'q==0' is redundant or there is possible null pointer dereference: q. [nullPointerRedundantCheck]\n", errout_str()); @@ -4005,7 +4232,7 @@ class TestNullPointer : public TestFixture { " std::cin >> p;\n" " std::cout << abc << p;\n" " }\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:2:10] -> [test.cpp:3:22]: (warning) Either the condition 'p==0' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n" "[test.cpp:2:10] -> [test.cpp:4:22]: (warning) Either the condition 'p==0' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n" "[test.cpp:2:10] -> [test.cpp:5]: (warning) Either the condition 'p==0' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n" @@ -4020,7 +4247,7 @@ class TestNullPointer : public TestFixture { " char* p2 = 0;\n" " std::cin >> (int)p;\n" // result casted " std::cout << (int)p;\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); check("void f(const std::string& str) {\n" @@ -4028,13 +4255,13 @@ class TestNullPointer : public TestFixture { " std::istringstream istr(str);\n" " istr >> std::hex >> ret;\n" // Read integer " return ret;\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); check("void f(int* i) {\n" " if(i) return;\n" " std::cout << i;\n" // Its no char* (#4240) - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); // #5811 false positive: (error) Null pointer dereference @@ -4043,7 +4270,7 @@ class TestNullPointer : public TestFixture { " stringstream out;\n" " out << ((ip >> 0) & 0xFF);\n" " return out.str();\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); // avoid regression from first fix attempt for #5811... check("void deserialize(const std::string &data) {\n" @@ -4063,42 +4290,42 @@ class TestNullPointer : public TestFixture { "void f(std::shared_ptr p) {\n" " if (p) {}\n" " dostuff(p->x);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:7] -> [test.cpp:4:11]: (warning) Either the condition 'p' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", errout_str()); check("struct Fred { int x; };\n" "void f(std::shared_ptr p) {\n" " p = nullptr;\n" " dostuff(p->x);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:11]: (error) Null pointer dereference: p [nullPointer]\n", errout_str()); check("struct Fred { int x; };\n" "void f(std::unique_ptr p) {\n" " if (p) {}\n" " dostuff(p->x);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:7] -> [test.cpp:4:11]: (warning) Either the condition 'p' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", errout_str()); check("struct Fred { int x; };\n" "void f(std::unique_ptr p) {\n" " p = nullptr;\n" " dostuff(p->x);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:11]: (error) Null pointer dereference: p [nullPointer]\n", errout_str()); check("struct Fred { int x; };\n" "void f() {\n" " std::shared_ptr p;\n" " dostuff(p->x);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:11]: (error) Null pointer dereference: p [nullPointer]\n", errout_str()); check("struct Fred { int x; };\n" "void f(std::shared_ptr p) {\n" " p.reset();\n" " dostuff(p->x);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:11]: (error) Null pointer dereference: p [nullPointer]\n", errout_str()); check("struct Fred { int x; };\n" @@ -4106,7 +4333,7 @@ class TestNullPointer : public TestFixture { " Fred * pp = nullptr;\n" " p.reset(pp);\n" " dostuff(p->x);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:11]: (error) Null pointer dereference: p [nullPointer]\n", errout_str()); check("struct Fred { int x; };\n" @@ -4114,28 +4341,28 @@ class TestNullPointer : public TestFixture { " std::shared_ptr p;\n" " p.reset(&f);\n" " dostuff(p->x);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct Fred { int x; };\n" "void f(std::shared_ptr p) {\n" " p.reset();\n" " dostuff(p->x);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:11]: (error) Null pointer dereference: p [nullPointer]\n", errout_str()); check("struct Fred { int x; };\n" "void f() {\n" " std::shared_ptr p(nullptr);\n" " dostuff(p->x);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:11]: (error) Null pointer dereference: p [nullPointer]\n", errout_str()); check("struct A {};\n" "void f(int n) {\n" " std::unique_ptr p;\n" " p.reset(new const A*[n]);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #9216 @@ -4146,7 +4373,7 @@ class TestNullPointer : public TestFixture { "void g(std::unique_ptr var) {\n" " var->reset();\n" " var->f();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #9439 @@ -4174,21 +4401,21 @@ class TestNullPointer : public TestFixture { " int *p = malloc(10);\n" " *p = 0;\n" " free(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:6]: (warning) If memory allocation fails, then there is a possible null pointer dereference: p [nullPointerOutOfMemory]\n", errout_str()); check("void f() {\n" " int *p = malloc(10);\n" " *(p+2) = 0;\n" " free(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:8]: (error) If memory allocation fails: pointer addition with NULL pointer. [nullPointerArithmeticOutOfMemory]\n", errout_str()); check("void f() {\n" // #13676 " int* q = static_cast(std::malloc(4));\n" " *q = 0;\n" " std::free(q);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:6]: (warning) If memory allocation fails, then there is a possible null pointer dereference: q [nullPointerOutOfMemory]\n", errout_str()); } @@ -4200,7 +4427,7 @@ class TestNullPointer : public TestFixture { " *p = 0;\n" " foo(p);\n" " if (p) { }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // function seen (taking pointer parameter) @@ -4210,7 +4437,7 @@ class TestNullPointer : public TestFixture { " *p = 0;\n" " foo(p);\n" " if (p) { }\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:6:9] -> [test.cpp:4:6]: (warning) Either the condition 'p' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", errout_str()); @@ -4222,7 +4449,7 @@ class TestNullPointer : public TestFixture { " *p = 0;\n" " foo(p);\n" " if (p) { }\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("", errout_str()); // function implementation not seen @@ -4232,7 +4459,7 @@ class TestNullPointer : public TestFixture { " *p = 0;\n" " foo(p);\n" " if (p) { }\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:6:9] -> [test.cpp:4:6]: (warning) Either the condition 'p' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", errout_str()); @@ -4242,7 +4469,7 @@ class TestNullPointer : public TestFixture { " *p = 0;\n" " foo(p);\n" " if (p) { }\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS( "[test.cpp:4:9] -> [test.cpp:2:6]: (warning, inconclusive) Either the condition 'p' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", errout_str()); @@ -4255,7 +4482,7 @@ class TestNullPointer : public TestFixture { " abc->a = 0;\n" " foo(abc);\n" " if (abc) { }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // function seen (taking pointer parameter) @@ -4265,7 +4492,7 @@ class TestNullPointer : public TestFixture { " abc->a = 0;\n" " foo(abc);\n" " if (abc) { }\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:6:9] -> [test.cpp:4:5]: (warning) Either the condition 'abc' is redundant or there is possible null pointer dereference: abc. [nullPointerRedundantCheck]\n", errout_str()); @@ -4277,7 +4504,7 @@ class TestNullPointer : public TestFixture { " abc->a = 0;\n" " foo(abc);\n" " if (abc) { }\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:6:9] -> [test.cpp:4:5]: (warning) Either the condition 'abc' is redundant or there is possible null pointer dereference: abc. [nullPointerRedundantCheck]\n", errout_str()); @@ -4287,7 +4514,7 @@ class TestNullPointer : public TestFixture { " abc->a = 0;\n" " foo(abc);\n" " if (abc) { }\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS( "[test.cpp:4:9] -> [test.cpp:2:5]: (warning, inconclusive) Either the condition 'abc' is redundant or there is possible null pointer dereference: abc. [nullPointerRedundantCheck]\n", errout_str()); @@ -4304,11 +4531,64 @@ class TestNullPointer : public TestFixture { "[test.cpp:3:13]: (warning) If resource allocation fails, then there is a possible null pointer dereference: fid [nullPointerOutOfResources]\n" "[test.cpp:4:12]: (warning) If resource allocation fails, then there is a possible null pointer dereference: fid [nullPointerOutOfResources]\n", errout_str()); + + // the guard might call an unknown, possibly noreturn function -> no warning + check("void f() {\n" + " FILE* fid = fopen(\"x.txt\", \"w\");\n" + " if (fid == NULL)\n" + " g();\n" + " fclose(fid);\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + // .. but an inconclusive warning is reported with --inconclusive + check("void f() {\n" + " FILE* fid = fopen(\"x.txt\", \"w\");\n" + " if (fid == NULL)\n" + " g();\n" + " fclose(fid);\n" + "}\n", + dinit(CheckOptions, $.inconclusive = true)); + ASSERT_EQUALS( + "[test.cpp:5:12]: (warning, inconclusive) If resource allocation fails, then there is a possible null pointer dereference: fid [nullPointerOutOfResources]\n", + errout_str()); + + check("int f(const int* p) {\n" + " if (p == nullptr)\n" + " g();\n" + " return *p;\n" + "}\n", + dinit(CheckOptions, $.inconclusive = true)); + ASSERT_EQUALS( + "[test.cpp:2:11] -> [test.cpp:4:13]: (warning, inconclusive) Either the condition 'p==nullptr' is redundant or there is possible null pointer dereference: p. [nullPointerRedundantCheck]\n", + errout_str()); + + check("void f() {\n" + " FILE* fid = fopen(\"x.txt\", \"w\");\n" + " if (fid != NULL)\n" + " ;\n" + " else\n" + " g();\n" + " fclose(fid);\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + // guard function is known to return -> warning + check("void g() {}\n" + "void f() {\n" + " FILE* fid = fopen(\"x.txt\", \"w\");\n" + " if (fid == NULL)\n" + " g();\n" + " fclose(fid);\n" + "}\n"); + ASSERT_EQUALS( + "[test.cpp:6:12]: (warning) If resource allocation fails, then there is a possible null pointer dereference: fid [nullPointerOutOfResources]\n", + errout_str()); } void functioncalllibrary() { SimpleTokenizer tokenizer(settingsDefault,*this,false); - const char code[] = "void f() { int a,b,c; x(a,b,c); }"; + const char code[] = "void f() { int a,b,c; x(a,b,c); }\n"; ASSERT_EQUALS(true, tokenizer.tokenize(code)); const Token *xtok = Token::findsimplematch(tokenizer.tokens(), "x"); @@ -4326,7 +4606,7 @@ class TestNullPointer : public TestFixture { Library library; ASSERT(LibraryHelper::loadxmldata(library, xmldata, sizeof(xmldata))); - const std::list null = CheckNullPointer::parseFunctionCall(*xtok, library); + const std::list null = CheckNullPointerImpl::parseFunctionCall(*xtok, library); ASSERT_EQUALS(0U, null.size()); } @@ -4344,7 +4624,7 @@ class TestNullPointer : public TestFixture { Library library; ASSERT(LibraryHelper::loadxmldata(library, xmldata, sizeof(xmldata))); - const std::list null = CheckNullPointer::parseFunctionCall(*xtok, library); + const std::list null = CheckNullPointerImpl::parseFunctionCall(*xtok, library); ASSERT_EQUALS(1U, null.size()); ASSERT_EQUALS("a", null.front()->str()); } @@ -4354,51 +4634,51 @@ class TestNullPointer : public TestFixture { check("void f(int *p = 0) {\n" " *p = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:6]: (warning) Possible null pointer dereference if the default parameter value is used: p [nullPointerDefaultArg]\n", errout_str()); check("void f(int *p = 0) {\n" " if (!p)\n" " return;\n" " *p = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(char a, int *p = 0) {\n" " *p = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:6]: (warning) Possible null pointer dereference if the default parameter value is used: p [nullPointerDefaultArg]\n", errout_str()); check("void f(int *p = 0) {\n" " printf(\"p = %d\", *p);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:23]: (warning) Possible null pointer dereference if the default parameter value is used: p [nullPointerDefaultArg]\n", errout_str()); check("void f(int *p = 0) {\n" " printf(\"p[1] = %d\", p[1]);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:25]: (warning) Possible null pointer dereference if the default parameter value is used: p [nullPointerDefaultArg]\n", errout_str()); check("void f(int *p = 0) {\n" " buf[p] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int *p = 0) {\n" " if (p != 0 && bar())\n" " *p = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int *p) {\n" " *p = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int *p = 0) {\n" " if (p != 0)\n" " *p = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int *p = 0) {\n" @@ -4406,13 +4686,13 @@ class TestNullPointer : public TestFixture { " if (p == 0)\n" " p = &y;\n" " *p = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int a, int *p = 0) {\n" " if (a != 0)\n" " *p = 0;\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS( "[test.cpp:3:8]: (warning) Possible null pointer dereference if the default parameter value is used: p [nullPointerDefaultArg]\n", errout_str()); @@ -4420,13 +4700,13 @@ class TestNullPointer : public TestFixture { check("void f(int *p = 0) {\n" " p = a;\n" " *p = 0;\n" // <- don't simplify and verify - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int *p = 0) {\n" " p += a;\n" " *p = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int f(int *p = 0) {\n" @@ -4434,17 +4714,17 @@ class TestNullPointer : public TestFixture { " return 0;\n" " }\n" " return *p;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int *p = 0) {\n" " std::cout << p ? *p : 0;\n" // Due to operator precedence, this is equivalent to: (std::cout << p) ? *p : 0; - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:23]: (warning) Possible null pointer dereference if the default parameter value is used: p [nullPointerDefaultArg]\n", errout_str()); // Check the first branch of ternary check("void f(char *p = 0) {\n" " std::cout << p ? *p : 0;\n" // Due to operator precedence, this is equivalent to: (std::cout << p) ? *p : 0; - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:2:18]: (warning) Possible null pointer dereference if the default parameter value is used: p [nullPointerDefaultArg]\n" "[test.cpp:2:23]: (warning) Possible null pointer dereference if the default parameter value is used: p [nullPointerDefaultArg]\n", // duplicate @@ -4452,27 +4732,27 @@ class TestNullPointer : public TestFixture { check("void f(int *p = 0) {\n" " std::cout << (p ? *p : 0);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int *p = 0) {\n" " std::cout << p;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int *p = 0) {\n" " std::cout << (p && p[0] ? *p : 42);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void isEmpty(int *p = 0) {\n" " return p && *p;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void g(int *p = 0) {\n" " return !p || *p;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); @@ -4481,13 +4761,13 @@ class TestNullPointer : public TestFixture { check("void f(int *p = 0) {\n" " bar(p);\n" " *p = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int *p = 0) {\n" " printf(\"%p\", p);\n" " *p = 0;\n" - "}", dinit(CheckOptions, $.inconclusive = true)); + "}\n", dinit(CheckOptions, $.inconclusive = true)); ASSERT_EQUALS("[test.cpp:3:6]: (warning) Possible null pointer dereference if the default parameter value is used: p [nullPointerDefaultArg]\n", errout_str()); // The init() function may or may not initialize p, but since the address @@ -4496,14 +4776,14 @@ class TestNullPointer : public TestFixture { check("void f(int *p = 0) {\n" " init(&p);\n" " *p = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void init(int* &g);\n" "void f(int *p = 0) {\n" " init(p);\n" " *p = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int *p = 0) {\n" @@ -4511,7 +4791,7 @@ class TestNullPointer : public TestFixture { " init(&p);\n" " }\n" " *p = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int *p = 0) {\n" @@ -4519,12 +4799,12 @@ class TestNullPointer : public TestFixture { " throw SomeException;\n" " }\n" " *p = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int x, int *p = 0) {\n" " int var1 = x ? *p : 5;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:21]: (warning) Possible null pointer dereference if the default parameter value is used: p [nullPointerDefaultArg]\n", errout_str()); check("void f(int* i = nullptr) { *i = 0; }\n" // #11567 @@ -4539,7 +4819,7 @@ class TestNullPointer : public TestFixture { " unsigned int j;\n" " for (j = 0; j < b[0].a->size; ++j) {\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -4563,33 +4843,33 @@ class TestNullPointer : public TestFixture { check("void foo(char *s) {\n" " char *p = s - 20;\n" "}\n" - "void bar() { foo(0); }"); + "void bar() { foo(0); }\n"); ASSERT_EQUALS("[test.cpp:2:15]: (error) Overflow in pointer arithmetic, NULL pointer is subtracted. [nullPointerArithmetic]\n", errout_str()); check("void foo(char *s) {\n" " if (!s) {}\n" " char *p = s - 20;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:7] -> [test.cpp:3:15]: (warning) Either the condition '!s' is redundant or there is overflow in pointer subtraction. [nullPointerArithmeticRedundantCheck]\n", errout_str()); check("void foo(char *s) {\n" " s -= 20;\n" "}\n" - "void bar() { foo(0); }"); + "void bar() { foo(0); }\n"); ASSERT_EQUALS("[test.cpp:2:5]: (error) Overflow in pointer arithmetic, NULL pointer is subtracted. [nullPointerArithmetic]\n", errout_str()); check("void foo(char *s) {\n" " if (!s) {}\n" " s -= 20;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:7] -> [test.cpp:3:5]: (warning) Either the condition '!s' is redundant or there is overflow in pointer subtraction. [nullPointerArithmeticRedundantCheck]\n", errout_str()); - check("int* f8() { int *x = NULL; return --x; }"); + check("int* f8() { int *x = NULL; return --x; }\n"); ASSERT_EQUALS("[test.cpp:1:35]: (error) Overflow in pointer arithmetic, NULL pointer is subtracted. [nullPointerArithmetic]\n", errout_str()); - check("int* f9() { int *x = NULL; return x--; }"); + check("int* f9() { int *x = NULL; return x--; }\n"); ASSERT_EQUALS("[test.cpp:1:36]: (error) Overflow in pointer arithmetic, NULL pointer is subtracted. [nullPointerArithmetic]\n", errout_str()); } @@ -4597,48 +4877,48 @@ class TestNullPointer : public TestFixture { check("void foo(char *s) {\n" " char * p = s + 20;\n" "}\n" - "void bar() { foo(0); }"); + "void bar() { foo(0); }\n"); ASSERT_EQUALS("[test.cpp:2:16]: (error) Pointer addition with NULL pointer. [nullPointerArithmetic]\n", errout_str()); check("void foo(char *s) {\n" " if (!s) {}\n" " char * p = s + 20;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:7] -> [test.cpp:3:16]: (warning) Either the condition '!s' is redundant or there is pointer arithmetic with NULL pointer. [nullPointerArithmeticRedundantCheck]\n", errout_str()); check("void foo(char *s) {\n" " char * p = 20 + s;\n" "}\n" - "void bar() { foo(0); }"); + "void bar() { foo(0); }\n"); ASSERT_EQUALS("[test.cpp:2:17]: (error) Pointer addition with NULL pointer. [nullPointerArithmetic]\n", errout_str()); check("void foo(char *s) {\n" " if (!s) {}\n" " char * p = 20 + s;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:7] -> [test.cpp:3:17]: (warning) Either the condition '!s' is redundant or there is pointer arithmetic with NULL pointer. [nullPointerArithmeticRedundantCheck]\n", errout_str()); check("void foo(char *s) {\n" " s += 20;\n" "}\n" - "void bar() { foo(0); }"); + "void bar() { foo(0); }\n"); ASSERT_EQUALS("[test.cpp:2:5]: (error) Pointer addition with NULL pointer. [nullPointerArithmetic]\n", errout_str()); check("void foo(char *s) {\n" " if (!s) {}\n" " s += 20;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:7] -> [test.cpp:3:5]: (warning) Either the condition '!s' is redundant or there is pointer arithmetic with NULL pointer. [nullPointerArithmeticRedundantCheck]\n", errout_str()); - check("int* f7() { int *x = NULL; return ++x; }"); + check("int* f7() { int *x = NULL; return ++x; }\n"); ASSERT_EQUALS("[test.cpp:1:35]: (error) Pointer addition with NULL pointer. [nullPointerArithmetic]\n", errout_str()); - check("int* f10() { int *x = NULL; return x++; }"); + check("int* f10() { int *x = NULL; return x++; }\n"); ASSERT_EQUALS("[test.cpp:1:37]: (error) Pointer addition with NULL pointer. [nullPointerArithmetic]\n", errout_str()); check("class foo {};\n" "const char* get() const { return 0; }\n" - "void f(foo x) { if (get()) x += get(); }"); + "void f(foo x) { if (get()) x += get(); }\n"); ASSERT_EQUALS("", errout_str()); check("typedef struct { uint8_t* buf, *buf_end; } S;\n" // #11117 @@ -4654,7 +4934,7 @@ class TestNullPointer : public TestFixture { } void isPointerDeRefFunctionDecl() { - check("const char** get() { return 0; }"); + check("const char** get() { return 0; }\n"); ASSERT_EQUALS("", errout_str()); } @@ -4665,11 +4945,11 @@ class TestNullPointer : public TestFixture { SimpleTokenizer tokenizer(settings, *this); ASSERT_LOC(tokenizer.tokenize(code), file, line); - CTU::FileInfo *ctu = CTU::getFileInfo(tokenizer); + const CTU::FileInfo *ctu = CTU::getFileInfo(tokenizer); - // Check code.. - std::list fileInfo; - Check& c = getCheck(); + CheckNullPointer check; + Check& c = getCheck(check); + std::list fileInfo; fileInfo.push_back(c.getFileInfo(tokenizer, settings, "")); c.analyseWholeProgram(*ctu, fileInfo, settings, *this); // TODO: check result while (!fileInfo.empty()) { @@ -4688,7 +4968,7 @@ class TestNullPointer : public TestFixture { "int main() {\n" " int *p = 0;\n" " f(p);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:10]: error: Null pointer dereference: fp [ctunullpointer]\n" "[test.cpp:5:12]: note: Assignment 'p=0', assigned value is 0\n" "[test.cpp:6:4]: note: Calling function f, 1st argument is null\n" @@ -4698,7 +4978,7 @@ class TestNullPointer : public TestFixture { "void call(int x, int *p) { x++; use(p); }\n" "int main() {\n" " call(4,0);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:1:25]: error: Null pointer dereference: p [ctunullpointer]\n" "[test.cpp:4:7]: note: Calling function call, 2nd argument is null\n" "[test.cpp:2:33]: note: Calling function use, 1st argument is null\n" @@ -4712,7 +4992,7 @@ class TestNullPointer : public TestFixture { "\n" "void f() {\n" " dostuff(a, 0);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); ctu("void dostuff(int *x, int *y) {\n" @@ -4723,7 +5003,7 @@ class TestNullPointer : public TestFixture { "\n" "void f() {\n" " dostuff(a, 0);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // else @@ -4734,7 +5014,7 @@ class TestNullPointer : public TestFixture { "\n" "void f() {\n" " dostuff(0, 0);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // ?, &&, || @@ -4744,14 +5024,14 @@ class TestNullPointer : public TestFixture { "\n" "void f() {\n" " dostuff(0, 0);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); ctu("void g(int* x) { *x; }\n" "void f(int* x) {\n" " if (x)\n" " g(x);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); ctu("size_t f(int* p) {\n" @@ -4799,6 +5079,15 @@ class TestNullPointer : public TestFixture { "[test.cpp:5:20]: note: Assignment 'f=fopen(notexist,t)', assigned value is 0\n" "[test.cpp:6:8]: note: Calling function foo, 1st argument is null\n" "[test.cpp:2:13]: note: Dereferencing argument f that is null\n", errout_str()); + + ctu("void g(std::optional& o) {\n" // #14728 + " *o = 1;\n" + "}\n" + "void f() {\n" + " std::optional x = 0;\n" + " g(x);\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); } }; diff --git a/test/testoptions.cpp b/test/testoptions.cpp index 0dec04927e4..af164fb43cc 100644 --- a/test/testoptions.cpp +++ b/test/testoptions.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/test/testother.cpp b/test/testother.cpp index 22ee7ba63f9..6908ce4b282 100644 --- a/test/testother.cpp +++ b/test/testother.cpp @@ -76,6 +76,7 @@ class TestOther : public TestFixture { TEST_CASE(zeroDiv22); TEST_CASE(zeroDivCond); // division by zero / useless condition + TEST_CASE(zeroDivErrorPath); TEST_CASE(nanInArithmeticExpression); @@ -142,6 +143,7 @@ class TestOther : public TestFixture { TEST_CASE(switchRedundantOperationTest); TEST_CASE(switchRedundantBitwiseOperationTest); TEST_CASE(unreachableCode); + TEST_CASE(unreachableSwitchCase); // #8442 TEST_CASE(redundantContinue); TEST_CASE(suspiciousCase); @@ -200,6 +202,9 @@ class TestOther : public TestFixture { TEST_CASE(duplicateExpression18); TEST_CASE(duplicateExpression19); TEST_CASE(duplicateExpression20); + TEST_CASE(duplicateExpression21); + TEST_CASE(duplicateExpression22); + TEST_CASE(duplicateExpression23); // #12282 TEST_CASE(duplicateExpressionLoop); TEST_CASE(duplicateValueTernary); TEST_CASE(duplicateValueTernarySizeof); // #13773 @@ -226,6 +231,7 @@ class TestOther : public TestFixture { TEST_CASE(checkRedundantCopy); TEST_CASE(checkNegativeShift); + TEST_CASE(checkNegativeShiftErrorPath); TEST_CASE(incompleteArrayFill); @@ -366,8 +372,8 @@ class TestOther : public TestFixture { SimpleTokenizer tokenizer(*settings, *this, opt.cpp); ASSERT_LOC(tokenizer.tokenize(code), file, line); - // Check.. - runChecks(tokenizer, this); + CheckOther check; + runChecks(check, tokenizer, *this); } struct CheckPOptions @@ -383,8 +389,8 @@ class TestOther : public TestFixture { // Tokenizer.. ASSERT_LOC(tokenizer.simplifyTokens1(""), file, line); - // Check.. - runChecks(tokenizer, this); + CheckOther check; + runChecks(check, tokenizer, *this); } template @@ -396,7 +402,7 @@ class TestOther : public TestFixture { void emptyBrackets() { check("{\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -404,22 +410,22 @@ class TestOther : public TestFixture { void zeroDiv1() { // floating point division by zero => no error check("void foo() {\n" " cout << 1. / 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" " cout << 42 / (double)0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" " cout << 42 / (float)0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" " cout << 42 / (int)0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:16]: (error) Division by zero. [zerodiv]\n", errout_str()); } @@ -432,34 +438,34 @@ class TestOther : public TestFixture { " sum += i;\n" " }\n" " cout<(fStepMain / fStepHelp);\n" " }\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -590,7 +596,7 @@ class TestOther : public TestFixture { " << boost::format(\" %d :: %s <> %s\") % 0 % \"a\" % \"b\"\n" " << std::endl;\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -598,13 +604,13 @@ class TestOther : public TestFixture { check("void f(int a) {\n" " int res = (a+2)/0;\n" " int res = (a*2)/0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:18]: (error) Division by zero. [zerodiv]\n" "[test.cpp:3:18]: (error) Division by zero. [zerodiv]\n", errout_str()); check("void f() {\n" " int res = (a+2)/0;\n" " int res = (a*2)/0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -612,7 +618,7 @@ class TestOther : public TestFixture { // #8141 check("intmax_t f() {\n" " return 1 / imaxabs(0);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:12]: (error) Division by zero. [zerodiv]\n", errout_str()); } void zeroDiv13() { @@ -707,7 +713,7 @@ class TestOther : public TestFixture { "{\n" " uint16_t x = 0xFFFFU;\n" // UINT16_MAX=0xFFFF " return 42/(++x);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:14]: (error) Division by zero. [zerodiv]\n", errout_str()); } @@ -734,25 +740,25 @@ class TestOther : public TestFixture { check("void f(unsigned int x) {\n" " int y = 17 / x;\n" " if (x > 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:9] -> [test.cpp:2:14]: (warning) Either the condition 'x>0' is redundant or there is division by zero at line 2. [zerodivcond]\n", errout_str()); check("void f(unsigned int x) {\n" " int y = 17 / x;\n" " if (x >= 1) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:9] -> [test.cpp:2:14]: (warning) Either the condition 'x>=1' is redundant or there is division by zero at line 2. [zerodivcond]\n", errout_str()); check("void f(int x) {\n" " int y = 17 / x;\n" " if (x == 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:9] -> [test.cpp:2:14]: (warning) Either the condition 'x==0' is redundant or there is division by zero at line 2. [zerodivcond]\n", errout_str()); check("void f(unsigned int x) {\n" " int y = 17 / x;\n" " if (x != 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:9] -> [test.cpp:2:14]: (warning) Either the condition 'x!=0' is redundant or there is division by zero at line 2. [zerodivcond]\n", errout_str()); // function call @@ -760,7 +766,7 @@ class TestOther : public TestFixture { "void f2(unsigned int y) {\n" " f1(123,y);\n" " if (y>0){}\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:4:10] -> [test.cpp:1:28]: (warning) Either the condition 'y>0' is redundant or there is division by zero at line 1. [zerodivcond]\n", errout_str()); @@ -771,7 +777,7 @@ class TestOther : public TestFixture { " int y = 17 / x;\n" " x = some+calculation;\n" " if (x != 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); { @@ -782,7 +788,7 @@ class TestOther : public TestFixture { " int y = 17 / x;\n" " do_something();\n" " if (x != 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // function is called. but don't care, variable is local @@ -792,7 +798,7 @@ class TestOther : public TestFixture { " int y = 17 / x;\n" " do_something();\n" " if (x != 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:9] -> [test.cpp:4:14]: (warning) Either the condition 'x!=0' is redundant or there is division by zero at line 4. [zerodivcond]\n", errout_str()); } @@ -800,14 +806,14 @@ class TestOther : public TestFixture { "void f(int x) {\n" " int y = 17 / x;\n" " do_something(x);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int x;\n" "void f() {\n" " int y = 17 / x;\n" " while (y || x == 0) { x--; }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // ticket 5033 segmentation fault (valid code) in CheckOther::checkZeroDivisionOrUselessCondition @@ -816,33 +822,33 @@ class TestOther : public TestFixture { "double* p2= new double[1];\n" "double* p3= new double[1];\n" "double* pp[3] = {p1,p2,p3};\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #5105 - FP check("int f(int a, int b) {\n" " int r = a / b;\n" " if (func(b)) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Unknown types for b and c --> do not warn check("int f(int d) {\n" " int r = (a?b:c) / d;\n" " if (d == 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int f(int a) {\n" " int r = a ? 1 / a : 0;\n" " if (a == 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int f(int a) {\n" " int r = (a == 0) ? 0 : 1 / a;\n" " if (a == 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int g();\n" @@ -852,7 +858,7 @@ class TestOther : public TestFixture { " else if (x > 0) {}\n" " else\n" " a = b / -x;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct A {\n" @@ -894,40 +900,73 @@ class TestOther : public TestFixture { ASSERT_EQUALS("", errout_str()); } + void zeroDivErrorPath() { + setMultiline(); + Settings s = settings0; + s.templateLocation = "{file}:{line}:note:{info}\n"; + + check("int f1(int i, bool b) {\n" + " int j = b ? i : 0;\n" + " return 1 / j;\n" + "}\n" + "int f2(int i, bool b) {\n" + " int j = b ? 0 : i;\n" + " return 1 / j;\n" + "}\n" + "int f3(int i, bool b) {\n" + " int j = 1;\n" + " if (b)\n" + " j = 0;\n" + " return 1 / j;\n" + "}\n", dinit(CheckOptions, $.settings = &s)); + ASSERT_EQUALS("[test.cpp:3:14]: warning: Division by zero. [zerodivcond]\n" + "[test.cpp:2:13]: note: Assuming condition 'b' is false\n" + "[test.cpp:2:15]: note: Assignment 'j=b?i:0', assigned value is 0\n" + "[test.cpp:3:14]: note: Division by zero\n" + "[test.cpp:7:14]: warning: Division by zero. [zerodivcond]\n" + "[test.cpp:6:13]: note: Assuming condition 'b' is true\n" + "[test.cpp:6:15]: note: Assignment 'j=b?0:i', assigned value is 0\n" + "[test.cpp:7:14]: note: Division by zero\n" + "[test.cpp:13:14]: warning: Division by zero. [zerodivcond]\n" + "[test.cpp:12:13]: note: Assignment 'j=0', assigned value is 0\n" + "[test.cpp:11:9]: note: Assuming condition is true\n" + "[test.cpp:13:14]: note: Division by zero\n", errout_str()); + } + void nanInArithmeticExpression() { check("void f()\n" "{\n" " double x = 3.0 / 0.0 + 1.0;\n" " printf(\"%f\", x);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:19]: (style) Using NaN/Inf in a computation. [nanInArithmeticExpression]\n", errout_str()); check("void f()\n" "{\n" " double x = 3.0 / 0.0 - 1.0;\n" " printf(\"%f\", x);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:19]: (style) Using NaN/Inf in a computation. [nanInArithmeticExpression]\n", errout_str()); check("void f()\n" "{\n" " double x = 1.0 + 3.0 / 0.0;\n" " printf(\"%f\", x);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:25]: (style) Using NaN/Inf in a computation. [nanInArithmeticExpression]\n", errout_str()); check("void f()\n" "{\n" " double x = 1.0 - 3.0 / 0.0;\n" " printf(\"%f\", x);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:25]: (style) Using NaN/Inf in a computation. [nanInArithmeticExpression]\n", errout_str()); check("void f()\n" "{\n" " double x = 3.0 / 0.0;\n" " printf(\"%f\", x);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -958,7 +997,7 @@ class TestOther : public TestFixture { " }\n" "\n" " return 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -968,7 +1007,7 @@ class TestOther : public TestFixture { " Error e;\n" " e.SetValue(12);\n" " throw e;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -982,7 +1021,7 @@ class TestOther : public TestFixture { " p = &i;\n" " }\n" " *p = 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -990,7 +1029,7 @@ class TestOther : public TestFixture { check("void foo()\n" "{\n" " int i;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1001,7 +1040,7 @@ class TestOther : public TestFixture { " if (x) {\n" " for ( ; i < 10; ++i) ;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:9]: (style) The scope of the variable 'i' can be reduced. [variableScope]\n", errout_str()); check("void f(int x) {\n" @@ -1009,7 +1048,7 @@ class TestOther : public TestFixture { " if (x) {\n" " for ( ; i < 10; ++i) ;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x)\n" @@ -1019,7 +1058,7 @@ class TestOther : public TestFixture { " else {\n" " for ( ; i < 10; ++i) ;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:9]: (style) The scope of the variable 'i' can be reduced. [variableScope]\n", errout_str()); } @@ -1033,7 +1072,7 @@ class TestOther : public TestFixture { " if (b) {\n" " c(i);\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" // #5398 @@ -1042,14 +1081,14 @@ class TestOther : public TestFixture { " if (success) {\n" " foo(notReducable);\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(Test &test) {\n" " int& x = test.getData();\n" " if (test.process())\n" " x = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f()\n" @@ -1064,7 +1103,7 @@ class TestOther : public TestFixture { " return 0;\n" " }\n" "}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int &x)\n" @@ -1075,7 +1114,7 @@ class TestOther : public TestFixture { " ++n;\n" " ++x;\n" " } while (x);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1087,7 +1126,7 @@ class TestOther : public TestFixture { " if (x) {\n" " y++;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1098,7 +1137,7 @@ class TestOther : public TestFixture { " BOOST_FOREACH(int edge, edges) {\n" " edgeResistance = (edge+1) / 2.0;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11]: (style) The scope of the variable 'edgeResistance' can be reduced. [variableScope]\n", errout_str()); } @@ -1114,7 +1153,7 @@ class TestOther : public TestFixture { " if (a == 2) {\n" " f.x();\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("class fred {\n" // #2062 @@ -1126,7 +1165,7 @@ class TestOther : public TestFixture { " if (a == 2) {\n" " f.x();\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:10]: (style) The scope of the variable 'f' can be reduced. [variableScope]\n", errout_str()); check("struct S { int a, b; };\n" @@ -1147,7 +1186,7 @@ class TestOther : public TestFixture { " FOR {\n" " foo(x++);\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1155,21 +1194,21 @@ class TestOther : public TestFixture { check("int f() {\n" " int x = 0;\n" " AB ab = { x, 0 };\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int f() {\n" " int x = 0;\n" " if (a == 0) { ++x; }\n" " AB ab = { x, 0 };\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int f() {\n" " int x = 0;\n" " if (a == 0) { ++x; }\n" " if (a == 1) { AB ab = { x, 0 }; }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1180,7 +1219,7 @@ class TestOther : public TestFixture { " if (x)\n" " foo(i);\n" " foo(j);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9]: (style) The scope of the variable 'i' can be reduced. [variableScope]\n", errout_str()); check("void f(int x) {\n" @@ -1189,7 +1228,7 @@ class TestOther : public TestFixture { " if (x)\n" " j = i;\n" " foo(j);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" @@ -1197,7 +1236,7 @@ class TestOther : public TestFixture { " x++;\n" " if (x == 5)\n" " foo(b);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" @@ -1205,7 +1244,7 @@ class TestOther : public TestFixture { " x++;\n" " if (x == 5)\n" " foo(b);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1216,7 +1255,7 @@ class TestOther : public TestFixture { " forever {\n" " if (i++ == 42) { break; }\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1227,7 +1266,7 @@ class TestOther : public TestFixture { " if(a) {\n" " for ( ; i < 10; ++i) ;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1239,7 +1278,7 @@ class TestOther : public TestFixture { " else if(b);\n" " else if(c);\n" " else;\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); } @@ -1249,7 +1288,7 @@ class TestOther : public TestFixture { " while((++a) < 56) {\n" " foo();\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -1257,7 +1296,7 @@ class TestOther : public TestFixture { " do {\n" " foo();\n" " } while((++a) < 56);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -1266,7 +1305,7 @@ class TestOther : public TestFixture { " a = 64;\n" " foo(a);\n" " } while((++a) < 56);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -1275,7 +1314,7 @@ class TestOther : public TestFixture { " a = 64;\n" " foo(a);\n" " } while(z());\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9]: (style) The scope of the variable 'a' can be reduced. [variableScope]\n", errout_str()); } @@ -1286,7 +1325,7 @@ class TestOther : public TestFixture { " x = stuff(x);\n" " morestuff(x);\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9]: (style) The scope of the variable 'x' can be reduced. [variableScope]\n", errout_str()); check("void f() {\n" @@ -1296,7 +1335,7 @@ class TestOther : public TestFixture { " morestuff(x);\n" " }\n" " if (b) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9]: (style) The scope of the variable 'x' can be reduced. [variableScope]\n", errout_str()); } @@ -1316,7 +1355,7 @@ class TestOther : public TestFixture { " x = foo();\n" " do_something(x);\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11]: (style) The scope of the variable 'x' can be reduced. [variableScope]\n", errout_str()); check("void f() {\n" @@ -1335,7 +1374,7 @@ class TestOther : public TestFixture { " x = foo();\n" " do_something(x);\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -1350,7 +1389,7 @@ class TestOther : public TestFixture { " default:\n" " break;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11]: (style) The scope of the variable 'x' can be reduced. [variableScope]\n", errout_str()); check("void f() {\n" @@ -1367,7 +1406,7 @@ class TestOther : public TestFixture { " do_something(x);\n" " break;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1377,7 +1416,7 @@ class TestOther : public TestFixture { " int b = a;\n" " if (b > 32) b = x;\n" " return b;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1386,7 +1425,7 @@ class TestOther : public TestFixture { " int test_value = 3;\n" " int test_array[1][1] = { { test_value } };\n" " return sizeof(test_array);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1399,7 +1438,7 @@ class TestOther : public TestFixture { " ++p;\n" " ++i;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // try to avoid an obvious false negative after applying the fix for the example above: check("void foo() {\n" @@ -1412,7 +1451,7 @@ class TestOther : public TestFixture { " ++p;\n" " ++i;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9]: (style) The scope of the variable 'p' can be reduced. [variableScope]\n", errout_str()); } @@ -1422,7 +1461,7 @@ class TestOther : public TestFixture { " Test myTest([&](size_t aX){\n" " std::cout << myCounter += aX << std::endl;\n" " });\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1432,7 +1471,7 @@ class TestOther : public TestFixture { " if (cond) {\n" " r.dostuff();\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9]: (style) The scope of the variable 'r' can be reduced. [variableScope]\n", errout_str()); check("void f(Foo x) {\n" @@ -1440,7 +1479,7 @@ class TestOther : public TestFixture { " if (cond) {\n" " foo.dostuff();\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1451,7 +1490,7 @@ class TestOther : public TestFixture { " currtime = time(&dummy);\n" " if (currtime > t) {}\n" " }\n" - "}", dinit(CheckOptions, $.cpp = false)); + "}\n", dinit(CheckOptions, $.cpp = false)); ASSERT_EQUALS("[test.c:2:12]: (style) The scope of the variable 'currtime' can be reduced. [variableScope]\n", errout_str()); } @@ -1463,7 +1502,7 @@ class TestOther : public TestFixture { " if (cond2) { dostuff(key); }\n" " }\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1473,7 +1512,7 @@ class TestOther : public TestFixture { "#ifdef X\n" "#endif\n" " if (id == ABC) { return x; }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); checkP("void f() {\n" @@ -1481,7 +1520,7 @@ class TestOther : public TestFixture { "#endif\n" " int x = 0;\n" " if (id == ABC) { return x; }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:7]: (style) The scope of the variable 'x' can be reduced. [variableScope]\n", errout_str()); } @@ -1515,7 +1554,7 @@ class TestOther : public TestFixture { " s = \"abc\";\n" " g(s);\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:17]: (style) The scope of the variable 's' can be reduced. [variableScope]\n", errout_str()); check("auto foo(std::vector& vec, bool flag) {\n" @@ -1528,7 +1567,7 @@ class TestOther : public TestFixture { " iter = dummy.begin();\n" " }\n" " return *iter;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:1:28]: (style) Parameter 'vec' can be declared as reference to const [constParameterReference]\n", errout_str()); check("auto& foo(std::vector& vec, bool flag) {\n" @@ -1541,7 +1580,7 @@ class TestOther : public TestFixture { " iter = dummy.begin();\n" " }\n" " return *iter;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -1794,7 +1833,7 @@ class TestOther : public TestFixture { void varScope39() { check("struct S {\n" // #12405 - " void f(const std::string&) const;\n" + " void f(const std::string& s) const;\n" " const int* g(std::string&) const;\n" "};\n" "void h(int);\n" @@ -2054,7 +2093,7 @@ class TestOther : public TestFixture { SimpleTokenizer tokenizerCpp(settings, *this); ASSERT_LOC(tokenizerCpp.tokenize(code), file, line); - CheckOther checkOtherCpp(&tokenizerCpp, &settings, this); + CheckOtherImpl checkOtherCpp(&tokenizerCpp, settings, *this); checkOtherCpp.warningOldStylePointerCast(); checkOtherCpp.warningDangerousTypeCast(); } @@ -2065,7 +2104,7 @@ class TestOther : public TestFixture { "void foo(Base* base)\n" "{\n" " Derived * d = (Derived *) base;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:19]: (warning) Potentially invalid type conversion in old-style C cast, clarify/fix with C++ cast [dangerousTypeCast]\n", errout_str()); checkOldStylePointerCast("class Base{};\n" @@ -2073,13 +2112,13 @@ class TestOther : public TestFixture { "void foo(Derived* derived)\n" "{\n" " Base * b = (Base *) derived;\n" // <- cast from derived to base is safe => cstyleCast - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:16]: (style) C-style pointer casting [cstyleCast]\n", errout_str()); checkOldStylePointerCast("void foo(Base* base)\n" "{\n" " Derived * d = (Derived *) base;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:19]: (warning) Potentially invalid type conversion in old-style C cast, clarify/fix with C++ cast [dangerousTypeCast]\n", errout_str()); checkOldStylePointerCast("class Base{};\n" @@ -2087,7 +2126,7 @@ class TestOther : public TestFixture { "void foo(Base* base)\n" "{\n" " Derived * d = (const Derived *) base;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:19]: (warning) Potentially invalid type conversion in old-style C cast, clarify/fix with C++ cast [dangerousTypeCast]\n", errout_str()); checkOldStylePointerCast("class Base{};\n" @@ -2095,7 +2134,7 @@ class TestOther : public TestFixture { "void foo()\n" "{\n" " Derived * d = (const Derived *) ( new Base() );\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:19]: (warning) Potentially invalid type conversion in old-style C cast, clarify/fix with C++ cast [dangerousTypeCast]\n", errout_str()); checkOldStylePointerCast("class Base{};\n" @@ -2103,102 +2142,102 @@ class TestOther : public TestFixture { "void foo()\n" "{\n" " Derived * d = (const Derived *) new Base();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:19]: (warning) Potentially invalid type conversion in old-style C cast, clarify/fix with C++ cast [dangerousTypeCast]\n", errout_str()); checkOldStylePointerCast("class Base{};\n" "void foo()\n" "{\n" " Base * b = (Base *) new short[10];\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:16]: (warning) Potentially invalid type conversion in old-style C cast, clarify/fix with C++ cast [dangerousTypeCast]\n", errout_str()); checkOldStylePointerCast("class Base;\n" "void foo()\n" "{\n" " Base * b = (volatile Base *) derived;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:16]: (style) C-style pointer casting [cstyleCast]\n", errout_str()); checkOldStylePointerCast("class Base;\n" "void foo()\n" "{\n" " Base * b = (volatile Base * const) derived;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:16]: (style) C-style pointer casting [cstyleCast]\n", errout_str()); checkOldStylePointerCast("class Base;\n" "void foo()\n" "{\n" " Base * b = (const volatile Base *) derived;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:16]: (style) C-style pointer casting [cstyleCast]\n", errout_str()); checkOldStylePointerCast("class Base;\n" "void foo()\n" "{\n" " Base * b = (const volatile Base * const) derived;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:16]: (style) C-style pointer casting [cstyleCast]\n", errout_str()); checkOldStylePointerCast("class Base;\n" "void foo()\n" "{\n" " Base * b = (const Base *) ( new Derived() );\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:16]: (style) C-style pointer casting [cstyleCast]\n", errout_str()); checkOldStylePointerCast("class Base;\n" "void foo()\n" "{\n" " Base * b = (const Base *) new Derived();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:16]: (style) C-style pointer casting [cstyleCast]\n", errout_str()); checkOldStylePointerCast("class Base;\n" "void foo()\n" "{\n" " Base * b = (const Base *) new short[10];\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:16]: (warning) Potentially invalid type conversion in old-style C cast, clarify/fix with C++ cast [dangerousTypeCast]\n", errout_str()); checkOldStylePointerCast("class B;\n" "class A\n" "{\n" " virtual void abc(B *) const = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); checkOldStylePointerCast("class B;\n" "class A\n" "{\n" " virtual void abc(const B *) const = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #3630 checkOldStylePointerCast("class SomeType{};\n" "class X : public Base {\n" " X() : Base((SomeType*)7) {}\n" // <- intToPointerCast - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkOldStylePointerCast("class SomeType{};\n" "class X : public Base {\n" " X() : Base((SomeType*)0x7000) {}\n" // <- it's common in embedded code to cast address - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); checkOldStylePointerCast("class SomeType;\n" "class X : public Base {\n" " X() : Base((SomeType*)var) {}\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:3:16]: (style) C-style pointer casting [cstyleCast]\n", errout_str()); checkOldStylePointerCast("class SomeType;\n" "class X : public Base {\n" " X() : Base((SomeType*)0) {}\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); // #5560 @@ -2208,7 +2247,7 @@ class TestOther : public TestFixture { "{ virtual G* createGui(S*, C*) const = 0; };\n" "\n" "class MS : public M\n" - "{ virtual void addController(C*) override {} };", Standards::CPP03); + "{ virtual void addController(C*) override {} };\n", Standards::CPP03); ASSERT_EQUALS("", errout_str()); // #6164 @@ -2217,7 +2256,7 @@ class TestOther : public TestFixture { "void testCC() {\n" " std::vector v;\n" " v.push_back((Base*)new Derived);\n" - "}"); + "}\n"); // FIXME write a dangerousTypeCast warning instead ASSERT_EQUALS("[test.cpp:5:15]: (style) C-style pointer casting [cstyleCast]\n", errout_str()); @@ -2328,6 +2367,11 @@ class TestOther : public TestFixture { " return N::f(float_ptr(b.data()));\n" // <- the cast is safe "}\n"); ASSERT_EQUALS("[test.cpp:9:17]: (style) C-style pointer casting [cstyleCast]\n", errout_str()); + + checkOldStylePointerCast("void f(const T& x) {\n" // #13766 + " const U& y = (typename const U&)(x);\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:2:18]: (warning) Potentially invalid type conversion in old-style C cast, clarify/fix with C++ cast [dangerousTypeCast]\n", errout_str()); } #define checkIntToPointerCast(...) checkIntToPointerCast_(__FILE__, __LINE__, __VA_ARGS__) @@ -2340,27 +2384,30 @@ class TestOther : public TestFixture { SimpleTokenizer tokenizerCpp(settings, *this); ASSERT_LOC(tokenizerCpp.tokenize(code), file, line); - CheckOther checkOtherCpp(&tokenizerCpp, &settings, this); + CheckOtherImpl checkOtherCpp(&tokenizerCpp, settings, *this); checkOtherCpp.warningIntToPointerCast(); } void intToPointerCast() { // #3630 - checkIntToPointerCast("uint8_t* ptr = (uint8_t*)7;"); + checkIntToPointerCast("uint8_t* ptr = (uint8_t*)7;\n"); ASSERT_EQUALS("[test.cpp:1:16]: (portability) Casting non-zero decimal integer literal to pointer. [intToPointerCast]\n", errout_str()); - checkIntToPointerCast("void* ptr = (void*)7;"); + checkIntToPointerCast("void* ptr = (void*)7;\n"); ASSERT_EQUALS("[test.cpp:1:13]: (portability) Casting non-zero decimal integer literal to pointer. [intToPointerCast]\n", errout_str()); - checkIntToPointerCast("uint8_t* ptr = (uint8_t*)0;"); + checkIntToPointerCast("uint8_t* ptr = (uint8_t*)0;\n"); ASSERT_EQUALS("", errout_str()); - checkIntToPointerCast("uint8_t* ptr = (uint8_t*)0x7000;"); // <- it's common in embedded code to cast address + checkIntToPointerCast("uint8_t* ptr = (uint8_t*)0x7000;\n"); // <- it's common in embedded code to cast address ASSERT_EQUALS("", errout_str()); checkIntToPointerCast("struct S { int i; };\n" // #13886, don't crash - "int f() { return sizeof(((struct S*)0)->i); }"); + "int f() { return sizeof(((struct S*)0)->i); }\n"); ASSERT_EQUALS("", errout_str()); + + checkIntToPointerCast("auto p = (int*)0b10;\n"); // #14180 + ASSERT_EQUALS("[test.cpp:1:10]: (portability) Casting non-zero binary integer literal to pointer. [intToPointerCast]\n", errout_str()); } struct CheckInvalidPointerCastOptions @@ -2379,7 +2426,7 @@ class TestOther : public TestFixture { SimpleTokenizer tokenizer(settings, *this); ASSERT_LOC(tokenizer.tokenize(code), file, line); - CheckOther checkOtherCpp(&tokenizer, &settings, this); + CheckOtherImpl checkOtherCpp(&tokenizer, settings, *this); checkOtherCpp.invalidPointerCast(); } @@ -2389,19 +2436,19 @@ class TestOther : public TestFixture { " float *f = new float[10];\n" " delete [] (double*)f;\n" " delete [] (long double const*)(new float[10]);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:15]: (portability) Casting between float * and double * which have an incompatible binary data representation. [invalidPointerCast]\n" "[test.cpp:4:15]: (portability) Casting between float * and const long double * which have an incompatible binary data representation. [invalidPointerCast]\n", errout_str()); checkInvalidPointerCast("void test(const float* f) {\n" " double *d = (double*)f;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:17]: (portability) Casting between const float * and double * which have an incompatible binary data representation. [invalidPointerCast]\n", errout_str()); checkInvalidPointerCast("void test(double* d1) {\n" " long double *ld = (long double*)d1;\n" " double *d2 = (double*)ld;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:23]: (portability) Casting between double * and long double * which have an incompatible binary data representation. [invalidPointerCast]\n" "[test.cpp:3:18]: (portability) Casting between long double * and double * which have an incompatible binary data representation. [invalidPointerCast]\n", errout_str()); @@ -2409,148 +2456,148 @@ class TestOther : public TestFixture { " long double *d = (long double*)(i);\n" " double *d = (double*)(i);\n" " float *f = reinterpret_cast(i);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:22]: (portability) Casting between signed int * and long double * which have an incompatible binary data representation. [invalidPointerCast]\n" "[test.cpp:3:17]: (portability) Casting between signed int * and double * which have an incompatible binary data representation. [invalidPointerCast]\n" "[test.cpp:4:16]: (portability) Casting between signed int * and float * which have an incompatible binary data representation. [invalidPointerCast]\n", errout_str()); checkInvalidPointerCast("float* test(unsigned int* i) {\n" " return (float*)i;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:12]: (portability) Casting between unsigned int * and float * which have an incompatible binary data representation. [invalidPointerCast]\n", errout_str()); checkInvalidPointerCast("float* test(unsigned int* i) {\n" " return (float*)i[0];\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); checkInvalidPointerCast("float* test(double& d) {\n" " return (float*)&d;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:12]: (portability) Casting between double * and float * which have an incompatible binary data representation. [invalidPointerCast]\n", errout_str()); checkInvalidPointerCast("void test(float* data) {\n" " f.write((char*)data,sizeof(float));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); checkInvalidPointerCast("void test(float* data) {\n" " f.write((char*)data,sizeof(float));\n" - "}", dinit(CheckInvalidPointerCastOptions, $.inconclusive = true)); // #3639 + "}\n", dinit(CheckInvalidPointerCastOptions, $.inconclusive = true)); // #3639 ASSERT_EQUALS("[test.cpp:2:13]: (portability, inconclusive) Casting from float * to char * is not portable due to different binary data representations on different platforms. [invalidPointerCast]\n", errout_str()); checkInvalidPointerCast("long long* test(float* f) {\n" " return (long long*)f;\n" - "}", dinit(CheckInvalidPointerCastOptions, $.portability = false)); + "}\n", dinit(CheckInvalidPointerCastOptions, $.portability = false)); ASSERT_EQUALS("", errout_str()); checkInvalidPointerCast("long long* test(float* f, char* c) {\n" " foo((long long*)f);\n" " return reinterpret_cast(c);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9]: (portability) Casting from float * to signed long long * is not portable due to different binary data representations on different platforms. [invalidPointerCast]\n", errout_str()); - checkInvalidPointerCast("Q_DECLARE_METATYPE(int*)"); // #4135 - don't crash + checkInvalidPointerCast("Q_DECLARE_METATYPE(int*)\n"); // #4135 - don't crash } void passedByValue() { - check("void f(const std::string str) {}"); + check("void f(const std::string str) {}\n"); ASSERT_EQUALS("[test.cpp:1:26]: (performance) Function parameter 'str' should be passed by const reference. [passedByValue]\n", errout_str()); - check("void f(std::unique_ptr ptr) {}"); + check("void f(std::unique_ptr ptr) {}\n"); ASSERT_EQUALS("", errout_str()); - check("void f(const std::shared_ptr ptr) {}"); + check("void f(const std::shared_ptr ptr) {}\n"); ASSERT_EQUALS("", errout_str()); - check("void f(const std::function ptr) {}"); + check("void f(const std::function ptr) {}\n"); ASSERT_EQUALS("", errout_str()); { - check("void f(const std::pair x) {}"); + check("void f(const std::pair x) {}\n"); ASSERT_EQUALS("", errout_str()); - check("void f(const std::pair x) {}"); + check("void f(const std::pair x) {}\n"); TODO_ASSERT_EQUALS("error", "", errout_str()); } - check("void f(const std::string::size_type x) {}"); + check("void f(const std::string::size_type x) {}\n"); ASSERT_EQUALS("", errout_str()); - check("class Foo;\nvoid f(const Foo foo) {}"); // Unknown class + check("class Foo;\nvoid f(const Foo foo) {}\n"); // Unknown class ASSERT_EQUALS("[test.cpp:2:18]: (performance, inconclusive) Function parameter 'foo' should be passed by const reference. [passedByValue]\n", errout_str()); - check("class Foo { std::vector v; };\nvoid f(const Foo foo) {}"); // Large class (STL member) + check("class Foo { std::vector v; };\nvoid f(const Foo foo) {}\n"); // Large class (STL member) ASSERT_EQUALS("[test.cpp:2:18]: (performance) Function parameter 'foo' should be passed by const reference. [passedByValue]\n", errout_str()); - check("class Foo { int i; };\nvoid f(const Foo foo) {}"); // Small class + check("class Foo { int i; };\nvoid f(const Foo foo) {}\n"); // Small class ASSERT_EQUALS("", errout_str()); - check("class Foo { int i[6]; };\nvoid f(const Foo foo) {}"); // Large class (array) + check("class Foo { int i[6]; };\nvoid f(const Foo foo) {}\n"); // Large class (array) ASSERT_EQUALS("[test.cpp:2:18]: (performance) Function parameter 'foo' should be passed by const reference. [passedByValue]\n", errout_str()); - check("class Foo { std::string* s; };\nvoid f(const Foo foo) {}"); // Small class (pointer) + check("class Foo { std::string* s; };\nvoid f(const Foo foo) {}\n"); // Small class (pointer) ASSERT_EQUALS("", errout_str()); - check("class Foo { static std::string s; };\nvoid f(const Foo foo) {}"); // Small class (static member) + check("class Foo { static std::string s; };\nvoid f(const Foo foo) {}\n"); // Small class (static member) ASSERT_EQUALS("", errout_str()); - check("class X { std::string s; }; class Foo : X { };\nvoid f(const Foo foo) {}"); // Large class (inherited) + check("class X { std::string s; }; class Foo : X { };\nvoid f(const Foo foo) {}\n"); // Large class (inherited) ASSERT_EQUALS("[test.cpp:2:18]: (performance) Function parameter 'foo' should be passed by const reference. [passedByValue]\n", errout_str()); - check("class X { std::string s; }; class Foo { X x; };\nvoid f(const Foo foo) {}"); // Large class (inherited) + check("class X { std::string s; }; class Foo { X x; };\nvoid f(const Foo foo) {}\n"); // Large class (inherited) ASSERT_EQUALS("[test.cpp:2:18]: (performance) Function parameter 'foo' should be passed by const reference. [passedByValue]\n", errout_str()); - check("void f(const std::string &str) {}"); + check("void f(const std::string &str) {}\n"); ASSERT_EQUALS("", errout_str()); // The idiomatic way of passing a std::string_view is by value - check("void f(const std::string_view str) {}"); + check("void f(const std::string_view str) {}\n"); ASSERT_EQUALS("", errout_str()); - check("void f(std::string_view str) {}"); + check("void f(std::string_view str) {}\n"); ASSERT_EQUALS("", errout_str()); - check("void f(const std::string_view &str) {}"); + check("void f(const std::string_view &str) {}\n"); ASSERT_EQUALS("", errout_str()); - check("void f(const std::vector v) {}"); + check("void f(const std::vector v) {}\n"); ASSERT_EQUALS("[test.cpp:1:31]: (performance) Function parameter 'v' should be passed by const reference. [passedByValue]\n", errout_str()); - check("void f(const std::vector v) {}"); + check("void f(const std::vector v) {}\n"); ASSERT_EQUALS("[test.cpp:1:39]: (performance) Function parameter 'v' should be passed by const reference. [passedByValue]\n", errout_str()); - check("void f(const std::vector::size_type s) {}"); + check("void f(const std::vector::size_type s) {}\n"); ASSERT_EQUALS("", errout_str()); - check("void f(const std::vector &v) {}"); + check("void f(const std::vector &v) {}\n"); ASSERT_EQUALS("", errout_str()); - check("void f(const std::map &v) {}"); + check("void f(const std::map &v) {}\n"); ASSERT_EQUALS("", errout_str()); - check("void f(const std::map v) {}"); + check("void f(const std::map v) {}\n"); ASSERT_EQUALS("[test.cpp:1:32]: (performance) Function parameter 'v' should be passed by const reference. [passedByValue]\n", errout_str()); - check("void f(const std::map v) {}"); + check("void f(const std::map v) {}\n"); ASSERT_EQUALS("[test.cpp:1:48]: (performance) Function parameter 'v' should be passed by const reference. [passedByValue]\n", errout_str()); - check("void f(const std::map v) {}"); + check("void f(const std::map v) {}\n"); ASSERT_EQUALS("[test.cpp:1:40]: (performance) Function parameter 'v' should be passed by const reference. [passedByValue]\n", errout_str()); - check("void f(const std::map v) {}"); + check("void f(const std::map v) {}\n"); ASSERT_EQUALS("[test.cpp:1:40]: (performance) Function parameter 'v' should be passed by const reference. [passedByValue]\n", errout_str()); - check("void f(const std::streamoff pos) {}"); + check("void f(const std::streamoff pos) {}\n"); ASSERT_EQUALS("", errout_str()); - check("void f(std::initializer_list i) {}"); + check("void f(std::initializer_list i) {}\n"); ASSERT_EQUALS("", errout_str()); // #5824 - check("void log(const std::string& file, int line, const std::string& function, const std::string str, ...) {}"); + check("void log(const std::string& file, int line, const std::string& function, const std::string str, ...) {}\n"); ASSERT_EQUALS("", errout_str()); // #5534 @@ -2559,12 +2606,12 @@ class TestOther : public TestFixture { "class Plane {\n" " vec Refract(vec &vec) const;\n" " bool IntersectLinePlane(const vec &planeNormal);\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("class X {\n" " virtual void func(const std::string str) {}\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:2:41]: (performance) Function parameter 'str' should be passed by const reference. [passedByValue]\n", errout_str()); check("enum X;\n" @@ -2581,7 +2628,7 @@ class TestOther : public TestFixture { ASSERT_EQUALS("", errout_str()); check("enum X;\n" - "enum X { a, b, c };" + "enum X { a, b, c };\n" "void foo(X x4){}\n"); ASSERT_EQUALS("", errout_str()); @@ -2733,6 +2780,18 @@ class TestOther : public TestFixture { "void S::func(S::F f) {}\n"); ASSERT_EQUALS("", errout_str()); + check("bool f(std::vector> v) {\n" // #12136 + " if (v.size() != 10U)\n" + " return false;\n" + " for (auto it = v.begin(); it != v.end(); ++it) {\n" + " if (it->size() < 9U)\n" + " return false;\n" + " it->erase(it->begin(), it->begin() + 9U);\n" + " }\n" + " return true;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + /*const*/ Settings settingsWin64 = settingsBuilder().platform(Platform::Type::Win64).build(); check("using ui64 = unsigned __int64;\n" "ui64 Test(ui64 one, ui64 two) { return one + two; }\n", @@ -2741,87 +2800,87 @@ class TestOther : public TestFixture { } void passedByValue_nonConst() { - check("void f(std::string str) {}"); + check("void f(std::string str) {}\n"); ASSERT_EQUALS("[test.cpp:1:20]: (performance) Function parameter 'str' should be passed by const reference. [passedByValue]\n", errout_str()); check("void f(std::string str) {\n" " return str + x;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:1:20]: (performance) Function parameter 'str' should be passed by const reference. [passedByValue]\n", errout_str()); check("void f(std::string str) {\n" " std::cout << str;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:1:20]: (performance) Function parameter 'str' should be passed by const reference. [passedByValue]\n", errout_str()); check("void f(std::string str) {\n" " std::cin >> str;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(std::string str) {\n" " std::string s2 = str;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:17]: (performance, inconclusive) Use const reference for 's2' to avoid unnecessary data copying. [redundantCopyLocalConst]\n" "[test.cpp:1:20]: (performance) Function parameter 'str' should be passed by const reference. [passedByValue]\n", errout_str()); check("void f(std::string str) {\n" " std::string& s2 = str;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:1:20]: (performance) Function parameter 'str' should be passed by const reference. [passedByValue]\n" "[test.cpp:2:18]: (style) Variable 's2' can be declared as reference to const [constVariableReference]\n", errout_str()); check("void f(std::string str) {\n" " const std::string& s2 = str;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:1:20]: (performance) Function parameter 'str' should be passed by const reference. [passedByValue]\n", errout_str()); check("void f(std::string str) {\n" " str = \"\";\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(std::string str) {\n" " foo(str);\n" // It could be that foo takes str as non-const-reference - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(const std::string& str);\n" "void f(std::string str) {\n" " foo(str);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:20]: (performance) Function parameter 'str' should be passed by const reference. [passedByValue]\n", errout_str()); check("void foo(std::string str);\n" "void f(std::string str) {\n" " foo(str);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:20]: (performance) Function parameter 'str' should be passed by const reference. [passedByValue]\n", errout_str()); check("void foo(std::string& str);\n" "void f(std::string str) {\n" " foo(str);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(std::string* str);\n" "void f(std::string str) {\n" " foo(&str);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int& i1, const std::string& str, int& i2);\n" "void f(std::string str) {\n" " foo((a+b)*c, str, x);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:20]: (performance) Function parameter 'str' should be passed by const reference. [passedByValue]\n", errout_str()); check("std::string f(std::string str) {\n" " str += x;\n" " return str;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("class X {\n" @@ -2830,7 +2889,7 @@ class TestOther : public TestFixture { "};\n" "Y f(X x) {\n" " x.func();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:7]: (performance) Function parameter 'x' should be passed by const reference. [passedByValue]\n", errout_str()); check("class X {\n" @@ -2838,17 +2897,17 @@ class TestOther : public TestFixture { "};\n" "Y f(X x) {\n" " x.func();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("class X {\n" " void func(std::string str) {}\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:2:27]: (performance) Function parameter 'str' should be passed by const reference. [passedByValue]\n", errout_str()); check("class X {\n" " virtual void func(std::string str) {}\n" // Do not warn about virtual functions, if 'str' is not declared as const - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("class X {\n" @@ -2858,7 +2917,7 @@ class TestOther : public TestFixture { " char b;\n" "};\n" "void f(Y y) {\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:10]: (performance) Function parameter 'y' should be passed by const reference. [passedByValue]\n", errout_str()); check("class X {\n" @@ -2871,7 +2930,7 @@ class TestOther : public TestFixture { " char c;\n" "};\n" "void f(X x, Y y) {\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:10:15]: (performance) Function parameter 'y' should be passed by const reference. [passedByValue]\n", errout_str()); { @@ -2880,7 +2939,7 @@ class TestOther : public TestFixture { " uint64_t a;\n" " uint64_t b;\n" "};\n" - "void f(X x) {}"; + "void f(X x) {}\n"; /*const*/ Settings s32 = settingsBuilder(settings1).platform(Platform::Type::Unix32).build(); check(code, dinit(CheckOptions, $.settings = &s32)); @@ -2895,27 +2954,30 @@ class TestOther : public TestFixture { "\n" "void foo(Buffer& buffer) {\n" " getWriter()->operator<<(buffer);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } void passedByValue_externC() { - check("struct X { int a[5]; }; void f(X v) { }"); + check("struct X { int a[5]; }; void f(X v) { }\n"); ASSERT_EQUALS("[test.cpp:1:34]: (performance) Function parameter 'v' should be passed by const reference. [passedByValue]\n", errout_str()); - check("extern \"C\" { struct X { int a[5]; }; void f(X v) { } }"); + check("extern \"C\" { struct X { int a[5]; }; void f(X v) { } }\n"); + ASSERT_EQUALS("", errout_str()); + + check("struct X { int a[5]; }; extern \"C\" void f(X v) { }\n"); ASSERT_EQUALS("", errout_str()); - check("struct X { int a[5]; }; extern \"C\" void f(X v) { }"); + check("struct X { int a[5]; }; void f(const X v);\n"); ASSERT_EQUALS("", errout_str()); - check("struct X { int a[5]; }; void f(const X v);"); + check("struct X { int a[5]; }; void f(const X v) { (void) v; }\n"); ASSERT_EQUALS("[test.cpp:1:40]: (performance) Function parameter 'v' should be passed by const reference. [passedByValue]\n", errout_str()); - check("extern \"C\" { struct X { int a[5]; }; void f(const X v); }"); + check("extern \"C\" { struct X { int a[5]; }; void f(const X v); }\n"); ASSERT_EQUALS("", errout_str()); - check("struct X { int a[5]; }; extern \"C\" void f(const X v) { }"); + check("struct X { int a[5]; }; extern \"C\" void f(const X v) { }\n"); ASSERT_EQUALS("", errout_str()); } @@ -2923,140 +2985,140 @@ class TestOther : public TestFixture { check("int f(std::vector x) {\n" " int& i = x[0];\n" " return i;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:1:24]: (performance) Function parameter 'x' should be passed by const reference. [passedByValue]\n" "[test.cpp:2:10]: (style) Variable 'i' can be declared as reference to const [constVariableReference]\n", errout_str()); check("int f(std::vector& x) {\n" " return x[0];\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:1:25]: (style) Parameter 'x' can be declared as reference to const [constParameterReference]\n", errout_str()); check("int f(std::vector x) {\n" " const int& i = x[0];\n" " return i;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:1:24]: (performance) Function parameter 'x' should be passed by const reference. [passedByValue]\n", errout_str()); check("int f(std::vector x) {\n" " static int& i = x[0];\n" " return i;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:1:24]: (performance) Function parameter 'x' should be passed by const reference. [passedByValue]\n", errout_str()); check("int f(std::vector x) {\n" " int& i = x[0];\n" " i++;\n" " return i;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int& f(std::vector& x) {\n" " x.push_back(1);\n" " int& i = x[0];\n" " return i;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int f(const std::vector& x) {\n" " return x[0];\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int& f(std::vector& x) {\n" " return x[0];\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("const int& f(std::vector& x) {\n" " return x[0];\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:1:32]: (style) Parameter 'x' can be declared as reference to const [constParameterReference]\n", errout_str()); check("int f(std::vector& x) {\n" " x[0]++;\n" " return x[0];\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct A { int a; };\n" "A f(std::vector& x) {\n" " x[0].a = 1;\n" " return x[0];\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct A { int a(); };\n" "A f(std::vector& x) {\n" " x[0].a();\n" " return x[0];\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int g(int& x);\n" "int f(std::vector& x) {\n" " g(x[0]);\n" " return x[0];\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("template\n" "T f(T& x) {\n" " return x[0];\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("template\n" "T f(T&& x) {\n" " return x[0];\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("template\n" "T f(T& x) {\n" " return x[0];\n" "}\n" - "void h() { std::vector v; h(v); }"); + "void h() { std::vector v; h(v); }\n"); ASSERT_EQUALS("", errout_str()); check("int f(int& x) {\n" " return std::move(x);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(std::ostream& os) {\n" " os << \"Hello\";\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void g(int*);\n" "void f(int& x) {\n" " g(&x);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct A { A(int*); };\n" "A f(int& x) {\n" " return A(&x);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct A { A(int*); };\n" "A f(int& x) {\n" " return A{&x};\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int& x, int& y) {\n" " y++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:1:13]: (style) Parameter 'x' can be declared as reference to const [constParameterReference]\n", errout_str()); check("struct A {\n" " explicit A(int& y) : x(&y) {}\n" " int * x = nullptr;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct A {\n" @@ -3064,7 +3126,7 @@ class TestOther : public TestFixture { " void swap(A& a) {\n" " v.swap(a.v);\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct A {\n" @@ -3075,24 +3137,24 @@ class TestOther : public TestFixture { "};\n" "void g(A& a) {\n" " a.f();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(std::vector& v) {\n" " for(auto&& x:v)\n" " x = 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(std::vector& v) {\n" " for(auto x:v)\n" " x = 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:1:26]: (style) Parameter 'v' can be declared as reference to const [constParameterReference]\n", errout_str()); check("void f(std::vector& v) {\n" " for(auto& x:v) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:15]: (style) Variable 'x' can be declared as reference to const [constVariableReference]\n", errout_str()); @@ -3113,32 +3175,32 @@ class TestOther : public TestFixture { check("void f(std::vector& v) {\n" " for(const auto& x:v) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:1:26]: (style) Parameter 'v' can be declared as reference to const [constParameterReference]\n", errout_str()); check("void f(int& i) {\n" " int& j = i;\n" " j++;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(std::vector& v) {\n" " int& i = v[0];\n" " i++;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(std::map >& m, unsigned int i) {\n" " std::map& members = m[i];\n" " members.clear();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct A {\n" " int& x;\n" " A(int& y) : x(y)\n" " {}\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct A {\n" @@ -3147,14 +3209,14 @@ class TestOther : public TestFixture { "struct B : A {\n" " B(int& x) : A(x)\n" " {}\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("void f(bool b, int& x, int& y) {\n" " auto& z = x;\n" " auto& w = b ? y : z;\n" " w = 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct S {\n" @@ -3162,7 +3224,7 @@ class TestOther : public TestFixture { "};\n" "int& f(S& s) {\n" " return s.i;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int* f(std::list& x, unsigned int y) {\n" @@ -3171,7 +3233,7 @@ class TestOther : public TestFixture { " return &m;\n" " }\n" " return nullptr;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int& f(std::list& x, int& y) {\n" @@ -3180,7 +3242,7 @@ class TestOther : public TestFixture { " return m;\n" " }\n" " return y;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("bool from_string(int& t, const std::string& s) {\n" @@ -3224,7 +3286,7 @@ class TestOther : public TestFixture { check("struct T { void dostuff() const {}};\n" "void a(T& x) {\n" " x.dostuff();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11]: (style) Parameter 'x' can be declared as reference to const [constParameterReference]\n", errout_str()); check("struct T : public U { void dostuff() const {}};\n" "void a(T& x) {\n" @@ -3232,86 +3294,86 @@ class TestOther : public TestFixture { " const T& z = x;\n" // Make sure we find all assignments " T& y = x;\n" " y.mutate();\n" // to avoid warnings that y can be const - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct T : public U { void dostuff() const {}};\n" "void a(T& x) {\n" " x.dostuff();\n" " const U& y = x\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11]: (style) Parameter 'x' can be declared as reference to const [constParameterReference]\n", errout_str()); check("struct T : public U { void dostuff() const {}};\n" "void a(T& x) {\n" " x.dostuff();\n" " U& y = x;\n" " y.mutate();\n" // to avoid warnings that y can be const - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct T : public U { void dostuff() const {}};\n" "void a(T& x) {\n" " x.dostuff();\n" " my::type& y = x;\n" // we don't know if y is const or not " y.mutate();\n" // to avoid warnings that y can be const - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct T : public U { void dostuff() const {}};\n" "void a(T& x) {\n" " x.dostuff();\n" " const U& y = static_cast(x);\n" " y.mutate();\n" // to avoid warnings that y can be const - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11]: (style) Parameter 'x' can be declared as reference to const [constParameterReference]\n", errout_str()); check("struct T : public U { void dostuff() const {}};\n" "void a(T& x) {\n" " x.dostuff();\n" " U& y = static_cast(x);\n" " y.mutate();\n" // to avoid warnings that y can be const - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct T : public U { void dostuff() const {}};\n" "void a(T& x) {\n" " x.dostuff();\n" " const U& y = dynamic_cast(x)\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11]: (style) Parameter 'x' can be declared as reference to const [constParameterReference]\n", errout_str()); check("struct T : public U { void dostuff() const {}};\n" "void a(T& x) {\n" " x.dostuff();\n" " const U& y = dynamic_cast(x);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11]: (style) Parameter 'x' can be declared as reference to const [constParameterReference]\n", errout_str()); check("struct T : public U { void dostuff() const {}};\n" "void a(T& x) {\n" " x.dostuff();\n" " const U& y = dynamic_cast(x);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11]: (style) Parameter 'x' can be declared as reference to const [constParameterReference]\n", errout_str()); check("struct T : public U { void dostuff() const {}};\n" "void a(T& x) {\n" " x.dostuff();\n" " U& y = dynamic_cast(x);\n" " y.mutate();\n" // to avoid warnings that y can be const - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct T : public U { void dostuff() const {}};\n" "void a(T& x) {\n" " x.dostuff();\n" " const U& y = dynamic_cast(x);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11]: (style) Parameter 'x' can be declared as reference to const [constParameterReference]\n", errout_str()); check("struct T : public U { void dostuff() const {}};\n" "void a(T& x) {\n" " x.dostuff();\n" " U& y = dynamic_cast(x);\n" " y.mutate();\n" // to avoid warnings that y can be const - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct T : public U { void dostuff() const {}};\n" "void a(T& x) {\n" " x.dostuff();\n" " U* y = dynamic_cast(&x);\n" " y->mutate();\n" // to avoid warnings that y can be const - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct T : public U { void dostuff() const {}};\n" @@ -3319,49 +3381,49 @@ class TestOther : public TestFixture { " x.dostuff();\n" " const U * y = dynamic_cast(&x);\n" " y->mutate();\n" // to avoid warnings that y can be const - "}"); + "}\n"); TODO_ASSERT_EQUALS("can be const", errout_str(), ""); //Currently taking the address is treated as a non-const operation when it should depend on what we do with it check("struct T : public U { void dostuff() const {}};\n" "void a(T& x) {\n" " x.dostuff();\n" " U const * y = dynamic_cast(&x);\n" " y->mutate();\n" // to avoid warnings that y can be const - "}"); + "}\n"); TODO_ASSERT_EQUALS("can be const", errout_str(), ""); //Currently taking the address is treated as a non-const operation when it should depend on what we do with it check("struct T : public U { void dostuff() const {}};\n" "void a(T& x) {\n" " x.dostuff();\n" " const U const * const * const * const y = dynamic_cast(&x);\n" " y->mutate();\n" // to avoid warnings that y can be const - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct T : public U { void dostuff() const {}};\n" "void a(T& x) {\n" " x.dostuff();\n" " const U const * const * const * const y = dynamic_cast(&x);\n" " y->mutate();\n" // to avoid warnings that y can be const - "}"); + "}\n"); TODO_ASSERT_EQUALS("can be const", errout_str(), ""); //Currently taking the address is treated as a non-const operation when it should depend on what we do with it check("struct T : public U { void dostuff() const {}};\n" "void a(T& x) {\n" " x.dostuff();\n" " const U const * const * * const y = dynamic_cast(&x);\n" " y->mutate();\n" // to avoid warnings that y can be const - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct T : public U { void dostuff() const {}};\n" "void a(T& x) {\n" " x.dostuff();\n" " my::fancy const * const * const y = dynamic_cast const * const * const>(&x);\n" " y->mutate();\n" // to avoid warnings that y can be const - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct T : public U { void dostuff() const {}};\n" "void a(T& x) {\n" " x.dostuff();\n" " const U& y = (const U&)(x);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:18]: (warning) Potentially invalid type conversion in old-style C cast, clarify/fix with C++ cast [dangerousTypeCast]\n" "[test.cpp:2:11]: (style) Parameter 'x' can be declared as reference to const [constParameterReference]\n", errout_str()); @@ -3370,13 +3432,13 @@ class TestOther : public TestFixture { " x.dostuff();\n" " U& y = (U&)(x);\n" " y.mutate();\n" // to avoid warnings that y can be const - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:12]: (warning) Potentially invalid type conversion in old-style C cast, clarify/fix with C++ cast [dangerousTypeCast]\n", errout_str()); check("struct T : public U { void dostuff() const {}};\n" "void a(T& x) {\n" " x.dostuff();\n" " const U& y = (typename const U&)(x);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:18]: (warning) Potentially invalid type conversion in old-style C cast, clarify/fix with C++ cast [dangerousTypeCast]\n" "[test.cpp:2:11]: (style) Parameter 'x' can be declared as reference to const [constParameterReference]\n", errout_str()); @@ -3385,14 +3447,14 @@ class TestOther : public TestFixture { " x.dostuff();\n" " U& y = (typename U&)(x);\n" " y.mutate();\n" // to avoid warnings that y can be const - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:12]: (warning) Potentially invalid type conversion in old-style C cast, clarify/fix with C++ cast [dangerousTypeCast]\n", errout_str()); check("struct T : public U { void dostuff() const {}};\n" "void a(T& x) {\n" " x.dostuff();\n" " U* y = (U*)(&x);\n" " y->mutate();\n" // to avoid warnings that y can be const - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:12]: (warning) Potentially invalid type conversion in old-style C cast, clarify/fix with C++ cast [dangerousTypeCast]\n", errout_str()); check("struct C { void f() const; };\n" // #9875 - crash @@ -3400,7 +3462,7 @@ class TestOther : public TestFixture { "void foo(C& x) {\n" " x.f();\n" " foo( static_cast(0) );\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:13]: (style) Parameter 'x' can be declared as reference to const [constParameterReference]\n", errout_str()); check("class a {\n" @@ -3608,7 +3670,7 @@ class TestOther : public TestFixture { "void bh(const std::list &ak, const std::list &al);\n" "void ah();\n" "void an();\n" - "void h();"); + "void h();\n"); ASSERT_EQUALS("[test.cpp:131:12]: (style) Variable 'tm' can be declared as pointer to const [constVariablePointer]\n" "[test.cpp:136:19]: (style) Variable 'af' can be declared as pointer to const [constVariablePointer]\n" "[test.cpp:137:12]: (style) Variable 'ag' can be declared as pointer to const [constVariablePointer]\n", @@ -3623,7 +3685,7 @@ class TestOther : public TestFixture { "class D\n" "{\n" "public:\n" - " explicit D(int&);\n" + " explicit D(int& i);\n" "\n" "private:\n" " C c;\n" @@ -3632,7 +3694,7 @@ class TestOther : public TestFixture { "D::D(int& i)\n" " : c(i)\n" "{\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("class C\n" @@ -3644,7 +3706,7 @@ class TestOther : public TestFixture { "class D\n" "{\n" "public:\n" - " explicit D(int&) noexcept;\n" + " explicit D(int& i) noexcept;\n" "\n" "private:\n" " C c;\n" @@ -3652,7 +3714,7 @@ class TestOther : public TestFixture { "\n" "D::D(int& i) noexcept\n" " : c(i)\n" - "{}"); + "{}\n"); ASSERT_EQUALS("", errout_str()); check("class C\n" @@ -3664,7 +3726,7 @@ class TestOther : public TestFixture { "class D\n" "{\n" "public:\n" - " explicit D(int&);\n" + " explicit D(int& i);\n" "\n" "private:\n" " C c;\n" @@ -3673,7 +3735,7 @@ class TestOther : public TestFixture { "D::D(int& i)\n" " : c(i)\n" "{\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:16]: (style) Parameter 'i' can be declared as reference to const\n", "", errout_str()); check("class C\n" @@ -3685,7 +3747,7 @@ class TestOther : public TestFixture { "class D\n" "{\n" "public:\n" - " explicit D(int&);\n" + " explicit D(int& i);\n" "\n" "private:\n" " C c;\n" @@ -3694,7 +3756,7 @@ class TestOther : public TestFixture { "D::D(int& i)\n" " : c(i)\n" "{\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:16]: (style) Parameter 'i' can be declared as reference to const\n", "", errout_str()); check("class C\n" @@ -3706,7 +3768,7 @@ class TestOther : public TestFixture { "class D\n" "{\n" "public:\n" - " explicit D(int&);\n" + " explicit D(int& i);\n" "\n" "private:\n" " C c;\n" @@ -3715,13 +3777,13 @@ class TestOther : public TestFixture { "D::D(int& i)\n" " : c(0, i)\n" "{\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:16]: (style) Parameter 'i' can be declared as reference to const\n", "", errout_str()); check("void f(std::map> &map) {\n" // #10266 " for (auto &[slave, panels] : map)\n" " panels.erase(it);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct S { void f(); int i; };\n" @@ -3907,7 +3969,9 @@ class TestOther : public TestFixture { " (void)(true);\n" " if (r) {}\n" "}\n"); - ASSERT_EQUALS("[test.cpp:1:13]: (style) Parameter 'r' can be declared as reference to const [constParameterReference]\n", errout_str()); + ASSERT_EQUALS("[test.cpp:2:5]: (warning) Redundant code: Found unused cast in expression '(void)(true)'. [constStatement]\n" + "[test.cpp:1:13]: (style) Parameter 'r' can be declared as reference to const [constParameterReference]\n", + errout_str()); check("struct S { void f(int&); };\n" // #12216 "void g(S& s, int& r, void (S::* p2m)(int&)) {\n" @@ -4070,7 +4134,7 @@ class TestOther : public TestFixture { "[test.cpp:10:25]: (style) Parameter 'v' can be declared as reference to const [constParameterReference]\n", errout_str()); - check("void push(V& v) { v.push_back({ .x = 1 }); }"); // #14010 + check("void push(V& v) { v.push_back({ .x = 1 }); }\n"); // #14010 ASSERT_EQUALS("", errout_str()); check("size_t* f(std::array& a) { return reinterpret_cast(a.data()); }\n"); // #14074 @@ -4108,35 +4172,125 @@ class TestOther : public TestFixture { " if (*pp) {}\n" "}\n"); ASSERT_EQUALS("", errout_str()); - } - - void constParameterCallback() { - check("int callback(std::vector& x) { return x[0]; }\n" - "void f() { dostuff(callback); }"); - ASSERT_EQUALS("[test.cpp:2:20] -> [test.cpp:1:32]: (style) Parameter 'x' can be declared as reference to const. However it seems that 'callback' is a callback function, if 'x' is declared with const you might also need to cast function pointer(s). [constParameterCallback]\n", errout_str()); - // #9906 - check("class EventEngine : public IEventEngine {\n" + check("class C {\n" "public:\n" - " EventEngine();\n" - "\n" + " explicit C(const std::string s);\n" "private:\n" - " void signalEvent(ev::sig& signal, int revents);\n" - "};\n" - "\n" - "EventEngine::EventEngine() {\n" - " mSigWatcher.set(this);\n" + " std::string _s;\n" + "};\n"); + ASSERT_EQUALS("", errout_str()); + + check("void f(int& r) {\n" // #9761 + " o1 = r;\n" "}\n" - "\n" - "void EventEngine::signalEvent(ev::sig& signal, int revents) {\n" - " switch (signal.signum) {}\n" - "}"); - ASSERT_EQUALS("[test.cpp:10:48] -> [test.cpp:13:40]: (style) Parameter 'signal' can be declared as reference to const. However it seems that 'signalEvent' is a callback function, if 'signal' is declared with const you might also need to cast function pointer(s). [constParameterCallback]\n", errout_str()); + "boost::optional o2;\n" + "void g(int& r) {\n" + " o2 = r;\n" + "}\n" + "struct T {\n" + " int* p;\n" + " T& operator=(int& rhs) { p = &rhs; return *this; }\n" + "};\n" + "void h(T& t, int& r) {\n" + " t = r;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); - check("void f(int* p) {}\n" // 12843 - "void g(std::map&m) {\n" + check("void f(std::optional& o) {\n" + " *o = 1;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("int f() {\n" + " int x = 0;\n" + " int& r(x);\n" + " r = x;\n" + " return r;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("struct Item { int state; };\n" + "void foo(std::vector &items) {\n" + " for (auto &item : items) {\n" + " switch (auto &s = item.state) {\n" + " case 0: s = 1; break;\n" + " default: break;\n" + " }\n" + " }\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("struct DS;\n" // #15104 + "void select(int a, int b);\n" + "class Query {\n" + "public:\n" + " template \n" + " int select(DS &ds, Callback cb);\n" + " template \n" + " int select(DS *ds, Callback cb);\n" + " int select(DS &ds) { return select(ds, [](int) {}); }\n" + "};\n"); + ASSERT_EQUALS("", errout_str()); + + check("int& g();\n" // reference to function + "int k();\n" + "int f1() { int& (&r)() = g; return r(); }\n" + "int f2() { int (&r)() = k; return r(); }\n" + "int& f3(int& (&f)()) { return f(); }\n" + "int f4(int&& (&f)()) { return f(); }\n" + "int f5(int (&f)()) { return f(); }\n" + "using FR = int&(&)();\n" + "void f6(FR f) { f(); }\n"); + ASSERT_EQUALS("", errout_str()); + + check("struct S {\n" // #15087 + " int a, b;\n" + "};\n" + "int& f(S& s, bool b) {\n" + " return b ? s.a : s.b;\n" + "}\n" + "int* g(U& u) {\n" + " return &(u.i);\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("typedef int I;\n" // #15167 + "I f() {\n" + " I a[1] = { 0 };\n" + " return a[0];\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:3:7]: (style) Variable 'a' can be declared as const array [constVariable]\n", + errout_str()); + } + + void constParameterCallback() { + check("int callback(std::vector& x) { return x[0]; }\n" + "void f() { dostuff(callback); }\n"); + ASSERT_EQUALS("[test.cpp:2:20] -> [test.cpp:1:32]: (style) Parameter 'x' can be declared as reference to const. However it seems that 'callback' is a callback function, if 'x' is declared with const you might also need to cast function pointer(s). [constParameterCallback]\n", errout_str()); + + // #9906 + check("class EventEngine : public IEventEngine {\n" + "public:\n" + " EventEngine();\n" + "\n" + "private:\n" + " void signalEvent(ev::sig& signal, int revents);\n" + "};\n" + "\n" + "EventEngine::EventEngine() {\n" + " mSigWatcher.set(this);\n" + "}\n" + "\n" + "void EventEngine::signalEvent(ev::sig& signal, int revents) {\n" + " switch (signal.signum) {}\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:10:48] -> [test.cpp:13:40]: (style) Parameter 'signal' can be declared as reference to const. However it seems that 'signalEvent' is a callback function, if 'signal' is declared with const you might also need to cast function pointer(s). [constParameterCallback]\n", errout_str()); + + check("void f(int* p) {}\n" // 12843 + "void g(std::map&m) {\n" " m[&f] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:8] -> [test.cpp:1:13]: (style) Parameter 'p' can be declared as pointer to const. " "However it seems that 'f' is a callback function, if 'p' is declared with const you might also need to cast function pointer(s). [constParameterCallback]\n", errout_str()); @@ -4149,52 +4303,62 @@ class TestOther : public TestFixture { " S s2{ cb };\n" "}\n"); ASSERT_EQUALS("[test.cpp:6:11] -> [test.cpp:2:21]: (performance) Function parameter 's' should be passed by const reference. However it seems that 'cb' is a callback function. [passedByValueCallback]\n", errout_str()); + + check("struct S {\n" // #14696 + " explicit S(std::string s = {}) {}\n" + "};\n" + "struct T {\n" + " explicit T(std::string* s = {}) {}\n" + "};\n"); + ASSERT_EQUALS("[test.cpp:2:28]: (performance) Function parameter 's' should be passed by const reference. [passedByValue]\n" + "[test.cpp:5:29]: (style) Parameter 's' can be declared as pointer to const [constParameterPointer]\n", + errout_str()); } void constPointer() { - check("void foo(int *p) { return *p; }"); + check("void foo(int *p) { return *p; }\n"); ASSERT_EQUALS("[test.cpp:1:15]: (style) Parameter 'p' can be declared as pointer to const [constParameterPointer]\n", errout_str()); - check("void foo(int *p) { x = *p; }"); + check("void foo(int *p) { x = *p; }\n"); ASSERT_EQUALS("[test.cpp:1:15]: (style) Parameter 'p' can be declared as pointer to const [constParameterPointer]\n", errout_str()); - check("void foo(int *p) { int &ref = *p; ref = 12; }"); + check("void foo(int *p) { int &ref = *p; ref = 12; }\n"); ASSERT_EQUALS("", errout_str()); - check("void foo(int *p) { x = *p + 10; }"); + check("void foo(int *p) { x = *p + 10; }\n"); ASSERT_EQUALS("[test.cpp:1:15]: (style) Parameter 'p' can be declared as pointer to const [constParameterPointer]\n", errout_str()); - check("void foo(int *p) { return p[10]; }"); + check("void foo(int *p) { return p[10]; }\n"); ASSERT_EQUALS("[test.cpp:1:15]: (style) Parameter 'p' can be declared as pointer to const [constParameterPointer]\n", errout_str()); - check("void foo(int *p) { int &ref = p[0]; ref = 12; }"); + check("void foo(int *p) { int &ref = p[0]; ref = 12; }\n"); ASSERT_EQUALS("", errout_str()); - check("void foo(int *p) { x[*p] = 12; }"); + check("void foo(int *p) { x[*p] = 12; }\n"); ASSERT_EQUALS("[test.cpp:1:15]: (style) Parameter 'p' can be declared as pointer to const [constParameterPointer]\n", errout_str()); - check("void foo(int *p) { if (p) {} }"); + check("void foo(int *p) { if (p) {} }\n"); ASSERT_EQUALS("[test.cpp:1:15]: (style) Parameter 'p' can be declared as pointer to const [constParameterPointer]\n", errout_str()); - check("void foo(int *p) { if (p || x) {} }"); + check("void foo(int *p) { if (p || x) {} }\n"); ASSERT_EQUALS("[test.cpp:1:15]: (style) Parameter 'p' can be declared as pointer to const [constParameterPointer]\n", errout_str()); - check("void foo(int *p) { if (p == 0) {} }"); + check("void foo(int *p) { if (p == 0) {} }\n"); ASSERT_EQUALS("[test.cpp:1:15]: (style) Parameter 'p' can be declared as pointer to const [constParameterPointer]\n", errout_str()); - check("void foo(int *p) { if (!p) {} }"); + check("void foo(int *p) { if (!p) {} }\n"); ASSERT_EQUALS("[test.cpp:1:15]: (style) Parameter 'p' can be declared as pointer to const [constParameterPointer]\n", errout_str()); - check("void foo(int *p) { if (*p > 123) {} }"); + check("void foo(int *p) { if (*p > 123) {} }\n"); ASSERT_EQUALS("[test.cpp:1:15]: (style) Parameter 'p' can be declared as pointer to const [constParameterPointer]\n", errout_str()); - check("void foo(int *p) { return *p + 1; }"); + check("void foo(int *p) { return *p + 1; }\n"); ASSERT_EQUALS("[test.cpp:1:15]: (style) Parameter 'p' can be declared as pointer to const [constParameterPointer]\n", errout_str()); - check("void foo(int *p) { return *p > 1; }"); + check("void foo(int *p) { return *p > 1; }\n"); ASSERT_EQUALS("[test.cpp:1:15]: (style) Parameter 'p' can be declared as pointer to const [constParameterPointer]\n", errout_str()); - check("void foo(const int* c) { if (c == 0) {}; }"); + check("void foo(const int* c) { if (c == 0) {}; }\n"); ASSERT_EQUALS("", errout_str()); check("struct a { void b(); };\n" @@ -4227,59 +4391,59 @@ class TestOther : public TestFixture { " int& i = (*x)[0];\n" " i++;\n" " return i;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct A { int a; };\n" "A f(std::vector* x) {\n" " x->front().a = 1;\n" " return x->front();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(std::vector* v) {\n" " for(auto&& x:*v)\n" " x = 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct A {\n" " int* x;\n" " A(int* y) : x(y)\n" " {}\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("void f(bool b, int* x, int* y) {\n" " int* z = x;\n" " int* w = b ? y : z;\n" " *w = 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(bool b, int* x, int* y) {\n" " int& z = *x;\n" " int& w = b ? *y : z;\n" " w = 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("class Base { virtual void dostuff(int *p) = 0; };\n" // #10397 - "class Derived: public Base { int x; void dostuff(int *p) override { x = *p; } };"); + "class Derived: public Base { int x; void dostuff(int *p) override { x = *p; } };\n"); ASSERT_EQUALS("", errout_str()); check("struct Data { char buf[128]; };\n" // #10483 "void encrypt(Data& data) {\n" " const char a[] = \"asfasd\";\n" " memcpy(data.buf, &a, sizeof(a));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #10547 check("void foo(std::istream &istr) {\n" " unsigned char x[2];\n" " istr >> x[0];\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #10744 @@ -4789,6 +4953,102 @@ class TestOther : public TestFixture { " return h(s ? s->gnc() : 1);\n" "}\n"); ASSERT_EQUALS("[test.cpp:5:11]: (style) Parameter 's' can be declared as pointer to const [constParameterPointer]\n", errout_str()); + + check("using IntPtr = int *;\n" + "int* foo(IntPtr bar) {\n" + " return bar = 0;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("struct S { int x; };\n" // #14700 + "int f(S* s) {\n" + " return s->x ? 1 : 0;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:2:10]: (style) Parameter 's' can be declared as pointer to const [constParameterPointer]\n", errout_str()); + + check("struct S { int a[1][1]; };\n" // #14714 + "int f(S* s) {\n" + " return s->a[0][0] ? 1 : 0;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:2:10]: (style) Parameter 's' can be declared as pointer to const [constParameterPointer]\n", errout_str()); + + check("int f(int *p, int *q) {\n" // #14748 + " return p ? *p : *q;\n" + "}\n" + "void g(int *p, int *q) {\n" + " int& r = p ? *p : *q;\n" + " r = 0;\n" + "}\n" + "void h(int *p, int *q) {\n" + " i(p ? *p : *q);\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:1:12]: (style) Parameter 'p' can be declared as pointer to const [constParameterPointer]\n" + "[test.cpp:1:20]: (style) Parameter 'q' can be declared as pointer to const [constParameterPointer]\n", + errout_str()); + + check("int f(std::vector* p) {\n" // #14810 + " return *p->cbegin();\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:1:25]: (style) Parameter 'p' can be declared as pointer to const [constParameterPointer]\n", errout_str()); + + check("struct S {\n" // #14817 + " explicit S(int *a) : m{ a[0], a[1] } {}\n" + " int m[2];\n" + "}\n" + "struct T {\n" + " explicit T(int *a) : m{ &a[0], &a[1] } {}\n" + " int* m[2];\n" + "};\n"); + ASSERT_EQUALS("[test.cpp:2:21]: (style) Parameter 'a' can be declared as pointer to const [constParameterPointer]\n", + errout_str()); + + check("class A {\n" // #11471 + "public:\n" + " A(const int& i, int) : m_i(&i) {}\n" + " const int* m_i;\n" + "};\n" + "A f(int& s) {\n" + " return A(s, 0);\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:6:10]: (style) Parameter 's' can be declared as reference to const [constParameterReference]\n", + errout_str()); + + check("struct S { std::string a; };\n" // #13678 + "struct T { S s; };\n" + "bool f(S* s) {\n" + " return s->a.empty();\n" + "}\n" + "bool g(T* t) {\n" + " return t->s.a.empty();\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:3:11]: (style) Parameter 's' can be declared as pointer to const [constParameterPointer]\n" + "[test.cpp:6:11]: (style) Parameter 't' can be declared as pointer to const [constParameterPointer]\n", + errout_str()); + + check("struct S { int i; };\n" // #13099 + "double f(S * s, int n, int a, int b, double* p) {\n" + " return (s + (n * (a + 1) + b))->i / *(p + b);\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:2:14]: (style) Parameter 's' can be declared as pointer to const [constParameterPointer]\n" + "[test.cpp:2:46]: (style) Parameter 'p' can be declared as pointer to const [constParameterPointer]\n", + errout_str()); + + check("struct S : U {\n" // #13944 + " void f(int* p) const {\n" + " if (m == p) {}\n" + " }\n" + " void g(int* p) final {\n" + " if (m == p) {}\n" + " }\n" + " int* m;\n" + "};\n"); + ASSERT_EQUALS("[test.cpp:2:17]: (style) Either there is a missing override/final keyword, or the parameter 'p' can be declared as pointer to const [constParameterPointer]\n", + errout_str()); + + check("void f(int *p __attribute__((unused))) {}\n" + "void g(__attribute__((unused)) int *p) {}\n" + "void h(int *p) {}\n"); + ASSERT_EQUALS("[test.cpp:3:13]: (style) Parameter 'p' can be declared as pointer to const [constParameterPointer]\n", errout_str()); } void constArray() { @@ -4860,7 +5120,7 @@ class TestOther : public TestFixture { " y = 3;\n" " }\n" " bar(y);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:11] -> [test.cpp:9:11]: (style) Variable 'y' is reassigned a value before the old one has been used. 'break;' missing? [redundantAssignInSwitch]\n", errout_str()); check("void foo()\n" @@ -4876,7 +5136,7 @@ class TestOther : public TestFixture { " y = 3;\n" " }\n" " bar(y);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:11] -> [test.cpp:11:11]: (style) Variable 'y' is reassigned a value before the old one has been used. 'break;' missing? [redundantAssignInSwitch]\n", errout_str()); check("void foo()\n" @@ -4893,7 +5153,7 @@ class TestOther : public TestFixture { " }\n" " }\n" " bar(y);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo()\n" @@ -4911,7 +5171,7 @@ class TestOther : public TestFixture { " y = 3;\n" " }\n" " bar(y);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo()\n" @@ -4926,7 +5186,7 @@ class TestOther : public TestFixture { " y = 3;\n" " }\n" " bar(y);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo()\n" @@ -4941,7 +5201,7 @@ class TestOther : public TestFixture { " y = 3;\n" " }\n" " bar(y);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo()\n" @@ -4958,7 +5218,7 @@ class TestOther : public TestFixture { " }\n" " bar(y);\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo()\n" @@ -4975,7 +5235,7 @@ class TestOther : public TestFixture { " }\n" " bar(y);\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo()\n" @@ -4990,7 +5250,7 @@ class TestOther : public TestFixture { " y = 3;\n" " }\n" " bar(y);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo()\n" @@ -5005,7 +5265,7 @@ class TestOther : public TestFixture { " y = 3;\n" " }\n" " bar(y);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:11] -> [test.cpp:10:11]: (style) Variable 'y' is reassigned a value before the old one has been used. 'break;' missing? [redundantAssignInSwitch]\n", errout_str()); check("void bar() {}\n" // bar isn't noreturn @@ -5021,7 +5281,7 @@ class TestOther : public TestFixture { " y = 3;\n" " }\n" " bar(y);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:11] -> [test.cpp:11:11]: (style) Variable 'y' is reassigned a value before the old one has been used. 'break;' missing? [redundantAssignInSwitch]\n", errout_str()); check("void foo(int a) {\n" @@ -5033,7 +5293,7 @@ class TestOther : public TestFixture { " case 3:\n" " strcpy(str, \"b'\");\n" " }\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); TODO_ASSERT_EQUALS("[test.cpp:6] -> [test.cpp:8]: (style) Buffer 'str' is being written before its old content has been used. 'break;' missing?\n", "", errout_str()); @@ -5047,7 +5307,7 @@ class TestOther : public TestFixture { " case 3:\n" " strncpy(str, \"b'\");\n" " }\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:6] -> [test.cpp:8]: (style) Buffer 'str' is being written before its old content has been used. 'break;' missing?\n", "", errout_str()); @@ -5064,7 +5324,7 @@ class TestOther : public TestFixture { " strcpy(str, \"b'\");\n" " z++;\n" " }\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); TODO_ASSERT_EQUALS("[test.cpp:7] -> [test.cpp:10]: (style) Buffer 'str' is being written before its old content has been used. 'break;' missing?\n", "", errout_str()); @@ -5080,7 +5340,7 @@ class TestOther : public TestFixture { " strcpy(str, \"b'\");\n" " break;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int a) {\n" @@ -5093,7 +5353,7 @@ class TestOther : public TestFixture { " case 3:\n" " strcpy(str, \"b'\");\n" " }\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); // Ticket #5158 "segmentation fault (valid code)" @@ -5126,7 +5386,7 @@ class TestOther : public TestFixture { " case 1: x = 3; goto a;\n" " case 1: x = 6; goto a;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -5142,7 +5402,7 @@ class TestOther : public TestFixture { " y = 3;\n" " }\n" " bar(y);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:9] -> [test.cpp:9:11]: (style) Variable 'y' is reassigned a value before the old one has been used. 'break;' missing? [redundantAssignInSwitch]\n", errout_str()); check("void foo()\n" "{\n" @@ -5157,7 +5417,7 @@ class TestOther : public TestFixture { " y = 3;\n" " }\n" " bar(y);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:9] -> [test.cpp:11:11]: (style) Variable 'y' is reassigned a value before the old one has been used. 'break;' missing? [redundantAssignInSwitch]\n", errout_str()); check("void foo()\n" "{\n" @@ -5170,7 +5430,7 @@ class TestOther : public TestFixture { " ++y;\n" " }\n" " bar(y);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo()\n" "{\n" @@ -5183,7 +5443,7 @@ class TestOther : public TestFixture { " ++y;\n" " }\n" " bar(y);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo()\n" "{\n" @@ -5196,7 +5456,7 @@ class TestOther : public TestFixture { " y = 3;\n" " }\n" " bar(y);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:9] -> [test.cpp:9:11]: (style) Variable 'y' is reassigned a value before the old one has been used. 'break;' missing? [redundantAssignInSwitch]\n", errout_str()); check("void foo()\n" "{\n" @@ -5211,7 +5471,7 @@ class TestOther : public TestFixture { " y = 3;\n" " }\n" " bar(y);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:9] -> [test.cpp:11:11]: (style) Variable 'y' is reassigned a value before the old one has been used. 'break;' missing? [redundantAssignInSwitch]\n", errout_str()); check("void foo()\n" "{\n" @@ -5224,7 +5484,7 @@ class TestOther : public TestFixture { " --y;\n" " }\n" " bar(y);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo()\n" "{\n" @@ -5237,7 +5497,7 @@ class TestOther : public TestFixture { " --y;\n" " }\n" " bar(y);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo()\n" "{\n" @@ -5250,7 +5510,7 @@ class TestOther : public TestFixture { " y = 3;\n" " }\n" " bar(y);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:10] -> [test.cpp:9:11]: (style) Variable 'y' is reassigned a value before the old one has been used. 'break;' missing? [redundantAssignInSwitch]\n", errout_str()); check("void foo()\n" "{\n" @@ -5265,7 +5525,7 @@ class TestOther : public TestFixture { " y = 3;\n" " }\n" " bar(y);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:10] -> [test.cpp:11:11]: (style) Variable 'y' is reassigned a value before the old one has been used. 'break;' missing? [redundantAssignInSwitch]\n", errout_str()); check("void foo()\n" "{\n" @@ -5278,7 +5538,7 @@ class TestOther : public TestFixture { " y++;\n" " }\n" " bar(y);\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); check("void foo()\n" "{\n" @@ -5291,7 +5551,7 @@ class TestOther : public TestFixture { " y++;\n" " }\n" " bar(y);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo()\n" "{\n" @@ -5304,7 +5564,7 @@ class TestOther : public TestFixture { " y = 3;\n" " }\n" " bar(y);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:10] -> [test.cpp:9:11]: (style) Variable 'y' is reassigned a value before the old one has been used. 'break;' missing? [redundantAssignInSwitch]\n", errout_str()); check("void foo()\n" "{\n" @@ -5319,7 +5579,7 @@ class TestOther : public TestFixture { " y = 3;\n" " }\n" " bar(y);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:10] -> [test.cpp:11:11]: (style) Variable 'y' is reassigned a value before the old one has been used. 'break;' missing? [redundantAssignInSwitch]\n", errout_str()); check("void foo()\n" "{\n" @@ -5332,7 +5592,7 @@ class TestOther : public TestFixture { " y--;\n" " }\n" " bar(y);\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); check("void foo()\n" "{\n" @@ -5345,7 +5605,7 @@ class TestOther : public TestFixture { " y--;\n" " }\n" " bar(y);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo()\n" "{\n" @@ -5361,7 +5621,7 @@ class TestOther : public TestFixture { " }\n" " }\n" " bar(y);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo()\n" "{\n" @@ -5378,7 +5638,7 @@ class TestOther : public TestFixture { " y = 3;\n" " }\n" " bar(y);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo()\n" "{\n" @@ -5392,7 +5652,7 @@ class TestOther : public TestFixture { " y++;\n" " }\n" " bar(y);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo()\n" "{\n" @@ -5406,7 +5666,7 @@ class TestOther : public TestFixture { " y = 3;\n" " }\n" " bar(y);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo()\n" "{\n" @@ -5422,7 +5682,7 @@ class TestOther : public TestFixture { " }\n" " bar(y);\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo()\n" "{\n" @@ -5438,7 +5698,7 @@ class TestOther : public TestFixture { " }\n" " bar(y);\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo()\n" "{\n" @@ -5452,7 +5712,7 @@ class TestOther : public TestFixture { " y = 3;\n" " }\n" " bar(y);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo()\n" "{\n" @@ -5466,7 +5726,7 @@ class TestOther : public TestFixture { " y = 3;\n" " }\n" " bar(y);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:10] -> [test.cpp:10:11]: (style) Variable 'y' is reassigned a value before the old one has been used. 'break;' missing? [redundantAssignInSwitch]\n", errout_str()); check("bool f() {\n" @@ -5484,7 +5744,7 @@ class TestOther : public TestFixture { " };\n" " ret = true;\n" " return ret;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:13] -> [test.cpp:14:9]: (style) Variable 'ret' is reassigned a value before the old one has been used. [redundantAssignment]\n" "[test.cpp:8:13] -> [test.cpp:14:9]: (style) Variable 'ret' is reassigned a value before the old one has been used. [redundantAssignment]\n" "[test.cpp:11:13] -> [test.cpp:14:9]: (style) Variable 'ret' is reassigned a value before the old one has been used. [redundantAssignment]\n", @@ -5503,7 +5763,7 @@ class TestOther : public TestFixture { " y |= 3;\n" " break;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:9]: (style) Redundant bitwise operation on 'y' in 'switch' statement. 'break;' missing? [redundantBitwiseOperationInSwitch]\n", errout_str()); check("void foo(int a)\n" @@ -5517,7 +5777,7 @@ class TestOther : public TestFixture { " y = y | 3;\n" " break;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:9]: (style) Redundant bitwise operation on 'y' in 'switch' statement. 'break;' missing? [redundantBitwiseOperationInSwitch]\n", errout_str()); check("void foo(int a)\n" @@ -5531,7 +5791,7 @@ class TestOther : public TestFixture { " y |= 3;\n" " break;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:9]: (style) Redundant bitwise operation on 'y' in 'switch' statement. 'break;' missing? [redundantBitwiseOperationInSwitch]\n", errout_str()); check("void foo(int a)\n" @@ -5546,7 +5806,7 @@ class TestOther : public TestFixture { " y |= 3;\n" " break;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int a)\n" @@ -5561,7 +5821,7 @@ class TestOther : public TestFixture { " y |= z;\n" " break;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int a)\n" @@ -5576,7 +5836,7 @@ class TestOther : public TestFixture { " y |= 3;\n" " break;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int a)\n" @@ -5591,7 +5851,7 @@ class TestOther : public TestFixture { " y |= 3;\n" " break;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:11] -> [test.cpp:8:11]: (style) Variable 'y' is reassigned a value before the old one has been used. [redundantAssignment]\n", errout_str()); check("void foo(int a)\n" @@ -5605,7 +5865,7 @@ class TestOther : public TestFixture { " y &= 3;\n" " break;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:9]: (style) Redundant bitwise operation on 'y' in 'switch' statement. 'break;' missing? [redundantBitwiseOperationInSwitch]\n", errout_str()); check("void foo(int a)\n" @@ -5620,7 +5880,7 @@ class TestOther : public TestFixture { " y |= 3;\n" " break;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int a)\n" @@ -5634,7 +5894,7 @@ class TestOther : public TestFixture { " y ^= 3;\n" " break;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int a)\n" @@ -5648,7 +5908,7 @@ class TestOther : public TestFixture { " y |= 3;\n" " break;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int a)\n" @@ -5662,7 +5922,7 @@ class TestOther : public TestFixture { " y &= 3;\n" " break;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int a)\n" @@ -5676,7 +5936,7 @@ class TestOther : public TestFixture { " y &= 2;\n" " break;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -5688,20 +5948,20 @@ class TestOther : public TestFixture { " continue;\n" " }\n" " }\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("[test.cpp:5:13]: (style) Consecutive return, break, continue, goto or throw statements are unnecessary. [duplicateBreak]\n", errout_str()); check("int foo(int a) {\n" " return 0;\n" " return(a-1);\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("[test.cpp:3:5]: (style) Consecutive return, break, continue, goto or throw statements are unnecessary. [duplicateBreak]\n", errout_str()); check("int foo(int a) {\n" " A:" " return(0);\n" " goto A;\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("[test.cpp:3:5]: (style) Consecutive return, break, continue, goto or throw statements are unnecessary. [duplicateBreak]\n", errout_str()); constexpr char xmldata[] = "\n" @@ -5716,7 +5976,7 @@ class TestOther : public TestFixture { check("void foo() {\n" " exit(0);\n" " break;\n" - "}", dinit(CheckOptions, $.inconclusive = false, $.settings = &settings)); + "}\n", dinit(CheckOptions, $.inconclusive = false, $.settings = &settings)); ASSERT_EQUALS("[test.cpp:3:5]: (style) Consecutive return, break, continue, goto or throw statements are unnecessary. [duplicateBreak]\n", errout_str()); check("class NeonSession {\n" @@ -5725,16 +5985,16 @@ class TestOther : public TestFixture { "void NeonSession::exit()\n" "{\n" " SAL_INFO(\"ucb.ucp.webdav\", \"neon commands cannot be aborted\");\n" - "}", dinit(CheckOptions, $.inconclusive = false, $.settings = &settings)); + "}\n", dinit(CheckOptions, $.inconclusive = false, $.settings = &settings)); ASSERT_EQUALS("", errout_str()); check("void NeonSession::exit()\n" "{\n" " SAL_INFO(\"ucb.ucp.webdav\", \"neon commands cannot be aborted\");\n" - "}", dinit(CheckOptions, $.inconclusive = false, $.settings = &settings)); + "}\n", dinit(CheckOptions, $.inconclusive = false, $.settings = &settings)); ASSERT_EQUALS("", errout_str()); - check("void foo() { xResAccess->exit(); }", dinit(CheckOptions, $.inconclusive = false, $.settings = &settings)); + check("void foo() { xResAccess->exit(); }\n", dinit(CheckOptions, $.inconclusive = false, $.settings = &settings)); ASSERT_EQUALS("", errout_str()); check("void foo(int a)\n" @@ -5748,7 +6008,7 @@ class TestOther : public TestFixture { " c++;\n" " break;\n" " }\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("[test.cpp:7:13]: (style) Consecutive return, break, continue, goto or throw statements are unnecessary. [duplicateBreak]\n", errout_str()); check("void foo(int a)\n" @@ -5761,7 +6021,7 @@ class TestOther : public TestFixture { " c++;\n" " break;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int a)\n" @@ -5772,7 +6032,7 @@ class TestOther : public TestFixture { " break;\n" " }\n" " }\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("[test.cpp:6:13]: (style) Consecutive return, break, continue, goto or throw statements are unnecessary. [duplicateBreak]\n", errout_str()); check("void foo(int a)\n" @@ -5784,7 +6044,7 @@ class TestOther : public TestFixture { " }\n" " a+=2;\n" " }\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("[test.cpp:6:13]: (style) Consecutive return, break, continue, goto or throw statements are unnecessary. [duplicateBreak]\n", errout_str()); check("void foo(int a)\n" @@ -5795,50 +6055,50 @@ class TestOther : public TestFixture { " }\n" " a+=2;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int foo() {\n" " throw 0;\n" " return 1;\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("[test.cpp:3:5]: (style) Consecutive return, break, continue, goto or throw statements are unnecessary. [duplicateBreak]\n", errout_str()); check("void foo() {\n" " throw 0;\n" " return;\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("[test.cpp:3:5]: (style) Consecutive return, break, continue, goto or throw statements are unnecessary. [duplicateBreak]\n", errout_str()); check("int foo() {\n" " throw = 0;\n" " return 1;\n" - "}", dinit(CheckOptions, $.cpp = false, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.cpp = false, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); check("int foo() {\n" " return 0;\n" " return 1;\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("[test.cpp:3:5]: (style) Consecutive return, break, continue, goto or throw statements are unnecessary. [duplicateBreak]\n", errout_str()); check("int foo() {\n" " return 0;\n" " foo();\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("[test.cpp:3:5]: (style) Statements following 'return' will never be executed. [unreachableCode]\n", errout_str()); check("int foo(int unused) {\n" " return 0;\n" " (void)unused;\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); check("int foo(int unused1, int unused2) {\n" " return 0;\n" " (void)unused1;\n" " (void)unused2;\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); check("int foo(int unused1, int unused2) {\n" @@ -5846,14 +6106,14 @@ class TestOther : public TestFixture { " (void)unused1;\n" " (void)unused2;\n" " foo();\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("[test.cpp:5:5]: (style) Statements following 'return' will never be executed. [unreachableCode]\n", errout_str()); check("int foo() {\n" " if(bar)\n" " return 0;\n" " return 124;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int foo() {\n" @@ -5864,7 +6124,7 @@ class TestOther : public TestFixture { " return 0;\n" " }\n" " return 124;\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("[test.cpp:4:9]: (style) Consecutive return, break, continue, goto or throw statements are unnecessary. [duplicateBreak]\n", errout_str()); check("void foo() {\n" @@ -5872,7 +6132,7 @@ class TestOther : public TestFixture { " return;\n" " break;\n" " }\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("[test.cpp:4:9]: (style) Consecutive return, break, continue, goto or throw statements are unnecessary. [duplicateBreak]\n", errout_str()); // #5707 @@ -5883,20 +6143,20 @@ class TestOther : public TestFixture { " }\n" " return 0;\n" " j=2;\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("[test.cpp:7:5]: (style) Statements following 'return' will never be executed. [unreachableCode]\n", errout_str()); check("int foo() {\n" " return 0;\n" " label:\n" " throw 0;\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("[test.cpp:3:3]: (style) Label 'label' is not used. [unusedLabel]\n", errout_str()); check("struct A {\n" " virtual void foo (P & Val) throw ();\n" " virtual void foo1 (P & Val) throw ();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int foo() {\n" @@ -5905,7 +6165,7 @@ class TestOther : public TestFixture { " bar();\n" " label:\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #3457 check("int foo() {\n" @@ -5914,7 +6174,7 @@ class TestOther : public TestFixture { " bar();\n" " label:\n" " } while (true);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #3457 check("int foo() {\n" @@ -5923,7 +6183,7 @@ class TestOther : public TestFixture { " bar();\n" " label:\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #3457 // #3383. TODO: Use preprocessor @@ -5932,20 +6192,20 @@ class TestOther : public TestFixture { " return 0;\n" "\n" // #endif " return 1;\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); check("int foo() {\n" "\n" // #ifdef A " return 0;\n" "\n" // #endif " return 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:5]: (style, inconclusive) Consecutive return, break, continue, goto or throw statements are unnecessary. [duplicateBreak]\n", errout_str()); // #4711 lambda functions check("int f() {\n" " return g([](int x){(void)x+1; return x;});\n" - "}", + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); @@ -5960,7 +6220,7 @@ class TestOther : public TestFixture { " __asm__ (\"rorw $8, %w0\" : \"=r\" (__v) : \"0\" (__x) : \"cc\");\n" " (void)__v;\n" " }));\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); // #6008 @@ -5969,7 +6229,7 @@ class TestOther : public TestFixture { " int sum = a_ + b_;\n" " return sum;\n" " };\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); // #5789 @@ -5977,20 +6237,20 @@ class TestOther : public TestFixture { " uint64_t enter, exit;\n" " uint64_t events;\n" " per_state_info() : enter(0), exit(0), events(0) {}\n" - "};", dinit(CheckOptions, $.inconclusive = false)); + "};\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); // #6664 check("void foo() {\n" " (beat < 100) ? (void)0 : exit(0);\n" " bar();\n" - "}", dinit(CheckOptions, $.inconclusive = false, $.settings = &settings)); + "}\n", dinit(CheckOptions, $.inconclusive = false, $.settings = &settings)); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" " (beat < 100) ? exit(0) : (void)0;\n" " bar();\n" - "}", dinit(CheckOptions, $.inconclusive = false, $.settings = &settings)); + "}\n", dinit(CheckOptions, $.inconclusive = false, $.settings = &settings)); ASSERT_EQUALS("", errout_str()); // #8261 @@ -5998,13 +6258,13 @@ class TestOther : public TestFixture { TODO_ASSERT_THROW(check("void foo() {\n" " (beat < 100) ? (void)0 : throw(0);\n" " bar();\n" - "}", dinit(CheckOptions, $.inconclusive = false, $.settings = &settings)), InternalError); + "}\n", dinit(CheckOptions, $.inconclusive = false, $.settings = &settings)), InternalError); //ASSERT_EQUALS("", errout_str()); check("int foo() {\n" " exit(0);\n" " return 1;\n" // <- clarify for tools that function does not continue.. - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -6189,6 +6449,61 @@ class TestOther : public TestFixture { ASSERT_EQUALS("", errout_str()); } + void unreachableSwitchCase() { + check("enum T { A, B};\n" + "void f(const T &t) {\n" + " if (t == A) {\n" + " switch (t) {\n" + " case A:\n" + " break;\n" + " case B:\n" + " break;\n" + " }\n" + " }\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:7:9]: (style) Switch case 'B' can never be selected because the switch condition is known to be 0. [unreachableSwitchCase]\n", errout_str()); + + check("void f(int t) {\n" + " if (t == 0) {\n" + " switch (t) {\n" + " case 0:\n" + " break;\n" + " case 1:\n" + " break;\n" + " }\n" + " }\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:6:9]: (style) Switch case '1' can never be selected because the switch condition is known to be 0. [unreachableSwitchCase]\n", errout_str()); + + check("void f(int t) {\n" + " switch (t) {\n" + " case 0:\n" + " break;\n" + " case 1:\n" + " break;\n" + " }\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("void f(int x, int y) {\n" + " if (x == 0) {\n" + " switch (x) {\n" + " case 0:\n" + " switch (y) {\n" + " case 1:\n" + " break;\n" + " case 2:\n" + " break;\n" + " }\n" + " break;\n" + " case 1:\n" + " break;\n" + " }\n" + " }\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:12:9]: (style) Switch case '1' can never be selected because the switch condition is known to be 0. [unreachableSwitchCase]\n", errout_str()); + } + void redundantContinue() { check("void f() {\n" // #11195 " for (int i = 0; i < 10; ++i) {\n" @@ -6247,7 +6562,7 @@ class TestOther : public TestFixture { " case A||B:\n" " foo();\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:15]: (warning, inconclusive) Found suspicious case label in switch(). Operator '&&' probably doesn't work as intended. [suspiciousCase]\n" "[test.cpp:5:16]: (warning, inconclusive) Found suspicious case label in switch(). Operator '||' probably doesn't work as intended. [suspiciousCase]\n" "[test.cpp:7:15]: (warning, inconclusive) Found suspicious case label in switch(). Operator '||' probably doesn't work as intended. [suspiciousCase]\n", errout_str()); @@ -6257,7 +6572,7 @@ class TestOther : public TestFixture { " case 1:\n" " a=A&&B;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // TODO Do not throw AST validation exception @@ -6266,19 +6581,19 @@ class TestOther : public TestFixture { " case A&&B?B:A:\n" " foo();\n" " }\n" - "}"), InternalError); + "}\n"), InternalError); //ASSERT_EQUALS("", errout_str()); } void suspiciousEqualityComparison() { check("void foo(int c) {\n" " if (x) c == 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:14]: (warning, inconclusive) Found suspicious equality comparison. Did you intend to assign a value instead? [constStatement]\n", errout_str()); check("void foo(const int* c) {\n" " if (x) *c == 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:15]: (warning, inconclusive) Found suspicious equality comparison. Did you intend to assign a value instead? [constStatement]\n", errout_str()); @@ -6286,90 +6601,90 @@ class TestOther : public TestFixture { " if (c == 1) {\n" " c = 0;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int c) {\n" " c == 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:7]: (warning, inconclusive) Found suspicious equality comparison. Did you intend to assign a value instead? [constStatement]\n", errout_str()); check("void foo(int c) {\n" " for (int i = 0; i == 10; i ++) {\n" " a ++;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int c) {\n" " for (i == 0; i < 10; i ++) {\n" " c ++;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:12]: (warning, inconclusive) Found suspicious equality comparison. Did you intend to assign a value instead? [constStatement]\n", errout_str()); check("void foo(int c) {\n" " for (i == 1; i < 10; i ++) {\n" " c ++;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:12]: (warning, inconclusive) Found suspicious equality comparison. Did you intend to assign a value instead? [constStatement]\n", errout_str()); check("void foo(int c) {\n" " for (i == 2; i < 10; i ++) {\n" " c ++;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:12]: (warning, inconclusive) Found suspicious equality comparison. Did you intend to assign a value instead? [constStatement]\n", errout_str()); check("void foo(int c) {\n" " for (int i = 0; i < 10; i == c) {\n" " c ++;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:31]: (warning, inconclusive) Found suspicious equality comparison. Did you intend to assign a value instead? [constStatement]\n", errout_str()); check("void foo(int c) {\n" " for (; running == 1;) {\n" " c ++;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int c) {\n" " printf(\"%i\", ({x==0;}));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int arg) {\n" " printf(\"%i\", ({int x = do_something(); x == 0;}));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int x) {\n" " printf(\"%i\", ({x == 0; x > 0 ? 10 : 20}));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:22]: (warning, inconclusive) Found suspicious equality comparison. Did you intend to assign a value instead? [constStatement]\n", errout_str()); check("void foo(int x) {\n" " for (const Token* end = tok->link(); tok != end; tok = (tok == end) ? end : tok->next()) {\n" " x++;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int x) {\n" " for (int i = (x == 0) ? 0 : 5; i < 10; i ++) {\n" " x++;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int x) {\n" " for (int i = 0; i < 10; i += (x == 5) ? 1 : 2) {\n" " x++;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -6425,26 +6740,26 @@ class TestOther : public TestFixture { " int x = 1;\n" " x = x;\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:7]: (style) Redundant assignment of 'x' to itself. [selfAssignment]\n", errout_str()); check("void foo()\n" "{\n" " int x = x;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:11]: (style) Redundant assignment of 'x' to itself. [selfAssignment]\n", errout_str()); check("struct A { int b; };\n" "void foo(A* a1, A* a2) {\n" " a1->b = a1->b;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:11]: (style) Redundant assignment of 'a1->b' to itself. [selfAssignment]\n", errout_str()); check("int x;\n" "void f()\n" "{\n" " x = x = 3;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:7]: (style) Redundant assignment of 'x' to itself. [selfAssignment]\n", errout_str()); // #4073 (segmentation fault) @@ -6452,59 +6767,59 @@ class TestOther : public TestFixture { "{\n" " if (a == 42)\n" " a = a;\n" - "}"); + "}\n"); check("void foo()\n" "{\n" " int x = 1;\n" " x = x + 1;\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo()\n" "{\n" " int *x = getx();\n" " *x = x;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" " BAR *x = getx();\n" " x = x;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:7]: (style) Redundant assignment of 'x' to itself. [selfAssignment]\n", errout_str()); // #2502 - non-primitive type -> there might be some side effects check("void foo()\n" "{\n" " Fred fred; fred = fred;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" " x = (x == 0);" " func(x);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int x) {\n" " x = (x != 0);" " func(x);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // ticket #3001 - false positive check("void foo(int x) {\n" " x = x ? x : 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #3800 - false negative when variable is extern check("extern int i;\n" "void f() {\n" " i = i;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:7]: (style) Redundant assignment of 'i' to itself. [selfAssignment]\n", errout_str()); // #4291 - id for variables accessed through 'this' @@ -6514,16 +6829,16 @@ class TestOther : public TestFixture { "};\n" "void Foo::func() {\n" " this->var = var;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:15]: (style) Redundant assignment of 'this->var' to itself. [selfAssignment]\n", errout_str()); check("class Foo {\n" " int var;\n" - " void func(int var);\n" + " Foo(int var);\n" "};\n" - "void Foo::func(int var) {\n" + "Foo::Foo(int var) {\n" " this->var = var;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #6406 - designated initializer doing bogus self assignment @@ -6533,7 +6848,7 @@ class TestOther : public TestFixture { "void something(void) {}\n" "void f() {\n" " struct callbacks ops = { .s = ops.s };\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:6]: (style) Redundant assignment of 'something' to itself.\n", "", errout_str()); check("class V\n" @@ -6548,12 +6863,12 @@ class TestOther : public TestFixture { " x = x; y = y; z = z;\n" " }\n" " double x, y, z;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:10:11]: (style) Redundant assignment of 'x' to itself. [selfAssignment]\n" "[test.cpp:10:18]: (style) Redundant assignment of 'y' to itself. [selfAssignment]\n" "[test.cpp:10:25]: (style) Redundant assignment of 'z' to itself. [selfAssignment]\n", errout_str()); - check("void f(int i) { i = !!i; }"); + check("void f(int i) { i = !!i; }\n"); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" @@ -6623,7 +6938,7 @@ class TestOther : public TestFixture { " Lock(123);\n" " std::cout << \"hello\" << std::endl;\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:15:5]: (style) Instance of 'Lock' object is destroyed immediately. [unusedScopedObject]\n", errout_str()); } @@ -6642,7 +6957,7 @@ class TestOther : public TestFixture { "{\n" " CouldBeFunction ( 123 ) ;\n" " return 0 ;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -6659,7 +6974,7 @@ class TestOther : public TestFixture { " error();\n" " do_something();\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); } @@ -6669,7 +6984,7 @@ class TestOther : public TestFixture { "{\n" " NotAFunction ( 123 );\n" " return 0 ;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:5]: (style) Instance of 'NotAFunction' object is destroyed immediately. [unusedScopedObject]\n", errout_str()); } @@ -6679,7 +6994,7 @@ class TestOther : public TestFixture { "{\n" " NotAClass ( 123 ) ;\n" " return true ;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:5]: (style) Instance of 'NotAClass' object is destroyed immediately. [unusedScopedObject]\n", errout_str()); } @@ -6688,7 +7003,7 @@ class TestOther : public TestFixture { "{\n" " if ( a > b ) return c == a ;\n" " return b == a ;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -6698,7 +7013,7 @@ class TestOther : public TestFixture { "public:\n" "~Something ( ) ;\n" "Something ( ) ;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -6717,7 +7032,7 @@ class TestOther : public TestFixture { " int a = 1;\n" " IncrementFunctor()(a);\n" " return a;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -6730,7 +7045,7 @@ class TestOther : public TestFixture { " };\n" " Foo();\n" " do_something();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:5]: (style) Instance of 'Foo' object is destroyed immediately. [unusedScopedObject]\n", errout_str()); } @@ -6742,7 +7057,7 @@ class TestOther : public TestFixture { "\n" "void fn() {\n" " Foo().bar();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -6771,14 +7086,14 @@ class TestOther : public TestFixture { "void foo() {\n" " stat(\"file.txt\", &st);\n" " do_something();\n" - "}"); + "}\n"); ASSERT_EQUALS("",errout_str()); check("struct AMethodObject {\n" // #4336 " AMethodObject(double, double, double);\n" "};\n" "struct S {\n" - " static void A(double, double, double);\n" + " static void A(double a1, double a2, double a3);\n" "};\n" "void S::A(double const a1, double const a2, double const a3) {\n" " AMethodObject(a1, a2, a3);\n" @@ -6875,7 +7190,8 @@ class TestOther : public TestFixture { " std::pair(1, 2);\n" " (void)0;\n" "}\n"); - ASSERT_EQUALS("[test.cpp:2:10]: (style) Instance of 'std::string' object is destroyed immediately. [unusedScopedObject]\n" + ASSERT_EQUALS("[test.cpp:5:5]: (warning) Redundant code: Found unused cast in expression '(void)0'. [constStatement]\n" + "[test.cpp:2:10]: (style) Instance of 'std::string' object is destroyed immediately. [unusedScopedObject]\n" "[test.cpp:3:10]: (style) Instance of 'std::string' object is destroyed immediately. [unusedScopedObject]\n" "[test.cpp:4:10]: (style) Instance of 'std::pair' object is destroyed immediately. [unusedScopedObject]\n", errout_str()); @@ -6924,7 +7240,7 @@ class TestOther : public TestFixture { "\n" " { sigaction(SIGHUP, &sa, 0); };\n" " { sigaction(SIGINT, &sa, 0); };\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -6935,76 +7251,76 @@ class TestOther : public TestFixture { " };\n" "\n" " const AB ab[3] = { AB(0), AB(1), AB(2) };\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } void clarifyCalculation() { check("int f(char c) {\n" " return 10 * (c == 0) ? 1 : 2;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:26]: (style) Clarify calculation precedence for '*' and '?'. [clarifyCalculation]\n", errout_str()); check("void f(char c) {\n" " printf(\"%i\", 10 * (c == 0) ? 1 : 2);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:32]: (style) Clarify calculation precedence for '*' and '?'. [clarifyCalculation]\n", errout_str()); check("void f() {\n" " return (2*a)?b:c;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(char c) {\n" " printf(\"%i\", a + b ? 1 : 2);\n" - "}",dinit(CheckOptions, $.inconclusive = false)); + "}\n",dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("[test.cpp:2:24]: (style) Clarify calculation precedence for '+' and '?'. [clarifyCalculation]\n", errout_str()); check("void f() {\n" " std::cout << x << y ? 2 : 3;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:25]: (style) Clarify calculation precedence for '<<' and '?'. [clarifyCalculation]\n", errout_str()); check("void f() {\n" " int ab = a - b ? 2 : 3;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:20]: (style) Clarify calculation precedence for '-' and '?'. [clarifyCalculation]\n", errout_str()); check("void f() {\n" " int ab = a | b ? 2 : 3;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:20]: (style) Clarify calculation precedence for '|' and '?'. [clarifyCalculation]\n", errout_str()); // ticket #195 check("int f(int x, int y) {\n" " return x >> ! y ? 8 : 2;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:21]: (style) Clarify calculation precedence for '>>' and '?'. [clarifyCalculation]\n", errout_str()); check("int f() {\n" " return shift < sizeof(int64_t)*8 ? 1 : 2;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); - check("void f() { a = *p ? 1 : 2; }"); + check("void f() { a = *p ? 1 : 2; }\n"); ASSERT_EQUALS("", errout_str()); - check("void f(int x) { const char *p = x & 1 ? \"1\" : \"0\"; }"); + check("void f(int x) { const char *p = x & 1 ? \"1\" : \"0\"; }\n"); ASSERT_EQUALS("", errout_str()); - check("void foo() { x = a % b ? \"1\" : \"0\"; }"); + check("void foo() { x = a % b ? \"1\" : \"0\"; }\n"); ASSERT_EQUALS("", errout_str()); - check("void f(int x) { return x & 1 ? '1' : '0'; }"); + check("void f(int x) { return x & 1 ? '1' : '0'; }\n"); ASSERT_EQUALS("", errout_str()); - check("void f(int x) { return x & 16 ? 1 : 0; }"); + check("void f(int x) { return x & 16 ? 1 : 0; }\n"); ASSERT_EQUALS("", errout_str()); - check("void f(int x) { return x % 16 ? 1 : 0; }"); + check("void f(int x) { return x % 16 ? 1 : 0; }\n"); ASSERT_EQUALS("", errout_str()); - check("enum {X,Y}; void f(int x) { return x & Y ? 1 : 0; }"); + check("enum {X,Y}; void f(int x) { return x & Y ? 1 : 0; }\n"); ASSERT_EQUALS("", errout_str()); } @@ -7012,7 +7328,7 @@ class TestOther : public TestFixture { check("char* f(char* c) {\n" " *c++;\n" " return c;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:2:5]: (warning, inconclusive) Found suspicious operator '*', result is not used. [constStatement]\n" "[test.cpp:2:7]: (warning) In expression like '*A++' the result of '*' is unused. Did you intend to write '(*A)++;'? [clarifyStatement]\n", @@ -7021,7 +7337,7 @@ class TestOther : public TestFixture { check("char* f(char** c) {\n" " *c[5]--;\n" " return *c;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:2:5]: (warning, inconclusive) Found suspicious operator '*', result is not used. [constStatement]\n" "[test.cpp:2:10]: (warning) In expression like '*A++' the result of '*' is unused. Did you intend to write '(*A)++;'? [clarifyStatement]\n", @@ -7029,7 +7345,7 @@ class TestOther : public TestFixture { check("void f(Foo f) {\n" " *f.a++;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:2:5]: (warning, inconclusive) Found suspicious operator '*', result is not used. [constStatement]\n" "[test.cpp:2:9]: (warning) In expression like '*A++' the result of '*' is unused. Did you intend to write '(*A)++;'? [clarifyStatement]\n", @@ -7037,7 +7353,7 @@ class TestOther : public TestFixture { check("void f(Foo f) {\n" " *f.a[5].v[3]++;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:2:5]: (warning, inconclusive) Found suspicious operator '*', result is not used. [constStatement]\n" "[test.cpp:2:17]: (warning) In expression like '*A++' the result of '*' is unused. Did you intend to write '(*A)++;'? [clarifyStatement]\n", @@ -7045,7 +7361,7 @@ class TestOther : public TestFixture { check("void f(Foo f) {\n" " *f.a(1, 5).v[x + y]++;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:2:5]: (warning, inconclusive) Found suspicious operator '*', result is not used. [constStatement]\n" "[test.cpp:2:24]: (warning) In expression like '*A++' the result of '*' is unused. Did you intend to write '(*A)++;'? [clarifyStatement]\n", @@ -7054,18 +7370,18 @@ class TestOther : public TestFixture { check("char* f(char* c) {\n" " (*c)++;\n" " return c;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(char* c) {\n" " bar(*c++);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("char*** f(char*** c) {\n" " ***c++;\n" " return c;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:2:5]: (warning, inconclusive) Found suspicious operator '*', result is not used. [constStatement]\n" "[test.cpp:2:9]: (warning) In expression like '*A++' the result of '*' is unused. Did you intend to write '(*A)++;'? [clarifyStatement]\n", @@ -7074,7 +7390,7 @@ class TestOther : public TestFixture { check("char** f(char*** c) {\n" " **c[5]--;\n" " return **c;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:2:5]: (warning, inconclusive) Found suspicious operator '*', result is not used. [constStatement]\n" "[test.cpp:2:11]: (warning) In expression like '*A++' the result of '*' is unused. Did you intend to write '(*A)++;'? [clarifyStatement]\n", @@ -7083,7 +7399,7 @@ class TestOther : public TestFixture { check("char*** f(char*** c) {\n" " (***c)++;\n" " return c;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(const int*** p) {\n" // #10923 @@ -7093,12 +7409,12 @@ class TestOther : public TestFixture { check("void *f(char** c) {\n" " bar(**c++);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void *f(char* p) {\n" " for (p = path; *p++;) ;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -7118,7 +7434,7 @@ class TestOther : public TestFixture { " b = 1;\n" " else\n" " b = 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:5] -> [test.cpp:2:5]: (style, inconclusive) Found duplicate branches for 'if' and 'else'. [duplicateBranch]\n", errout_str()); check("void f(int a, int &b) {\n" @@ -7129,7 +7445,7 @@ class TestOther : public TestFixture { " b = 2;\n" " } else\n" " b = 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:9] -> [test.cpp:3:9]: (style, inconclusive) Found duplicate branches for 'if' and 'else'. [duplicateBranch]\n", errout_str()); check("void f(int a, int &b) {\n" @@ -7141,7 +7457,7 @@ class TestOther : public TestFixture { " else\n" " b = 2;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:9] -> [test.cpp:5:9]: (style, inconclusive) Found duplicate branches for 'if' and 'else'. [duplicateBranch]\n", errout_str()); check("int f(int signed, unsigned char value) {\n" @@ -7151,7 +7467,7 @@ class TestOther : public TestFixture { " else\n" " ret = (unsigned char)value;\n" " return ret;\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -7159,7 +7475,7 @@ class TestOther : public TestFixture { " __asm__(\"mov ax, bx\");\n" " else\n" " __asm__(\"mov bx, bx\");\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #3407 check("void f() {\n" @@ -7167,7 +7483,7 @@ class TestOther : public TestFixture { " __asm__(\"mov ax, bx\");\n" " else\n" " __asm__(\"mov ax, bx\");\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:5] -> [test.cpp:2:5]: (style, inconclusive) Found duplicate branches for 'if' and 'else'. [duplicateBranch]\n", errout_str()); } @@ -7181,7 +7497,7 @@ class TestOther : public TestFixture { " frac = front/(front-back);\n" " else\n" " frac = front/(front-back);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:3] -> [test.cpp:3:3]: (style, inconclusive) Found duplicate branches for 'if' and 'else'. [duplicateBranch]\n", errout_str()); check("void f()\n" @@ -7190,7 +7506,7 @@ class TestOther : public TestFixture { " { frac = front/(front-back);}\n" " else\n" " frac = front/((front-back));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:3] -> [test.cpp:3:3]: (style, inconclusive) Found duplicate branches for 'if' and 'else'. [duplicateBranch]\n", errout_str()); // No message about empty branches (#5354) @@ -7200,7 +7516,7 @@ class TestOther : public TestFixture { " {}\n" " else\n" " {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -7212,7 +7528,7 @@ class TestOther : public TestFixture { " DOSTUFF1\n" " else\n" " DOSTUFF2\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -7224,7 +7540,7 @@ class TestOther : public TestFixture { " } else {\n" " x = j;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:13] -> [test.cpp:5:7] -> [test.cpp:3:5]: (style, inconclusive) Found duplicate branches for 'if' and 'else'. [duplicateBranch]\n" "[test.cpp:2:9]: (style) The scope of the variable 'j' can be reduced. [variableScope]\n", errout_str()); @@ -7237,7 +7553,7 @@ class TestOther : public TestFixture { " } else {\n" " x = j;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -7248,7 +7564,7 @@ class TestOther : public TestFixture { " } else {\n" " return new A::Z(true);\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -7262,7 +7578,7 @@ class TestOther : public TestFixture { " unsigned int i = 0;\n" " j = i;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:7] -> [test.cpp:3:5]: (style, inconclusive) Found duplicate branches for 'if' and 'else'. [duplicateBranch]\n", errout_str()); check("void f(bool b) {\n" @@ -7274,7 +7590,7 @@ class TestOther : public TestFixture { " unsigned int i = 0;\n" " j = 1;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(bool b) {\n" @@ -7284,7 +7600,7 @@ class TestOther : public TestFixture { " } else {\n" " int i = 0;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(bool b) {\n" @@ -7296,7 +7612,16 @@ class TestOther : public TestFixture { " int i = 0;\n" " j = i;\n" " }\n" - "}"); + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("void f(int i) {\n" + " if (1 == i) {\n" + " ;\n" + " } else {\n" + " ;\n" + " }\n" + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -7306,7 +7631,7 @@ class TestOther : public TestFixture { " } else {\n" " int i = 0;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(bool b) {\n" @@ -7314,14 +7639,14 @@ class TestOther : public TestFixture { " int i = 0;\n" " } else {\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } void duplicateExpression1() { check("void foo(int a) {\n" " if (a == a) { }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11]: (style) Same expression on both sides of '=='. [duplicateExpression]\n", errout_str()); check("void fun(int b) {\n" @@ -7330,7 +7655,7 @@ class TestOther : public TestFixture { " d > d &&\n" " e < e &&\n" " f ;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:15]: (style) Same expression on both sides of '&&'. [duplicateExpression]\n" "[test.cpp:3:15]: (style) Same expression on both sides of '=='. [duplicateExpression]\n" "[test.cpp:4:15]: (style) Same expression on both sides of '>'. [duplicateExpression]\n" @@ -7338,82 +7663,82 @@ class TestOther : public TestFixture { check("void foo() {\n" " return a && a;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:14]: (style) Same expression on both sides of '&&'. [duplicateExpression]\n", errout_str()); check("void foo() {\n" " a = b && b;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11]: (style) Same expression on both sides of '&&'. [duplicateExpression]\n", errout_str()); check("void foo(int b) {\n" " f(a,b == b);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11]: (style) Same expression on both sides of '=='. [duplicateExpression]\n", errout_str()); check("void foo(int b) {\n" " f(b == b, a);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9]: (style) Same expression on both sides of '=='. [duplicateExpression]\n", errout_str()); check("void foo() {\n" " if (x!=2 || x!=2) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:14]: (style) Same expression on both sides of '||'. [duplicateExpression]\n", errout_str()); check("void foo(int a, int b) {\n" " if ((a < b) && (b > a)) { }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:17]: (style) Same expression on both sides of '&&' because 'aa' represent the same value. [knownConditionTrueFalse]\n", errout_str()); check("void foo(int a, int b) {\n" " if ((a <= b) && (b >= a)) { }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:18]: (style) Same expression on both sides of '&&' because 'a<=b' and 'b>=a' represent the same value. [knownConditionTrueFalse]\n", errout_str()); check("void foo() {\n" " if (x!=2 || y!=3 || x!=2) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:22]: (style) Same expression 'x!=2' found multiple times in chain of '||' operators. [duplicateExpression]\n", errout_str()); check("void foo() {\n" " if (x!=2 && (x=y) && x!=2) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" " if (a && b || a && b) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:16]: (style) Same expression on both sides of '||'. [duplicateExpression]\n", errout_str()); check("void foo() {\n" " if (a && b || b && c) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" " if (a && b | b && c) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:16]: (style) Same expression on both sides of '|'. [duplicateExpression]\n", errout_str()); check("void foo() {\n" " if ((a + b) | (a + b)) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:17]: (style) Same expression on both sides of '|'. [duplicateExpression]\n", errout_str()); check("void foo() {\n" " if ((a | b) & (a | b)) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:17]: (style) Same expression on both sides of '&'. [duplicateExpression]\n", errout_str()); check("void foo(int a, int b) {\n" " if ((a | b) == (a | b)) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:17]: (style) Same expression on both sides of '=='. [duplicateExpression]\n", errout_str()); check("void foo() {\n" " if (a1[a2[c & 0xff] & 0xff]) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void d(const char f, int o, int v)\n" @@ -7421,127 +7746,133 @@ class TestOther : public TestFixture { " if (((f=='R') && (o == 1) && ((v < 2) || (v > 99))) ||\n" " ((f=='R') && (o == 2) && ((v < 2) || (v > 99))) ||\n" " ((f=='T') && (o == 2) && ((v < 200) || (v > 9999)))) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); - check("int f(int x) { return x+x; }"); + check("int f(int x) { return x+x; }\n"); ASSERT_EQUALS("", errout_str()); - check("void f(int x) { while (x+=x) ; }"); + check("void f(int x) { while (x+=x) ; }\n"); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" " if (a && b && b) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:16]: (style) Same expression on both sides of '&&'. [duplicateExpression]\n", errout_str()); check("void foo() {\n" " if (a || b || b) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:16]: (style) Same expression on both sides of '||'. [duplicateExpression]\n", errout_str()); check("void foo() {\n" " if (a / 1000 / 1000) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int foo(int i) {\n" " return i/i;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:13]: (style) Same expression on both sides of '/'. [duplicateExpression]\n", errout_str()); check("void foo() {\n" " if (a << 1 << 1) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); - check("int f() { return !!y; }"); // No FP + check("int f() { return !!y; }\n"); // No FP ASSERT_EQUALS("", errout_str()); // make sure there are not "same expression" fp when there are different casts - check("void f(long x) { if ((int32_t)x == (int64_t)x) {} }", + check("void f(long x) { if ((int32_t)x == (int64_t)x) {} }\n", dinit(CheckOptions, $.inconclusive = false) ); ASSERT_EQUALS("", errout_str()); // make sure there are not "same expression" fp when there are different ({}) expressions - check("void f(long x) { if (({ 1+2; }) == ({3+4;})) {} }"); + check("void f(long x) { if (({ 1+2; }) == ({3+4;})) {} }\n"); ASSERT_EQUALS("", errout_str()); // #5535: Reference named like its type - check("void foo() { UMSConfig& UMSConfig = GetUMSConfiguration(); }"); + check("void foo() { UMSConfig& UMSConfig = GetUMSConfiguration(); }\n"); ASSERT_EQUALS("[test.cpp:1:25]: (style) Variable 'UMSConfig' can be declared as reference to const [constVariableReference]\n", errout_str()); // #3868 - false positive (same expression on both sides of |) check("void f(int x) {\n" " a = x ? A | B | C\n" " : A | B;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(const Bar &bar) {\n" " bool a = bar.isSet() && bar->isSet();\n" " bool b = bar.isSet() && bar.isSet();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:26]: (style) Same expression on both sides of '&&'. [duplicateExpression]\n", errout_str()); check("void foo(int a, int b) {\n" " if ((b + a) | (a + b)) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:17]: (style) Same expression on both sides of '|' because 'b+a' and 'a+b' represent the same value. [duplicateExpression]\n", errout_str()); check("void foo(const std::string& a, const std::string& b) {\n" " return a.find(b+\"&\") || a.find(\"&\"+b);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int a, int b) {\n" " if ((b > a) | (a > b)) {}\n" // > is not commutative - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(double a, double b) {\n" " if ((b + a) > (a + b)) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:17]: (style) The comparison 'b+a > a+b' is always false because 'b+a' and 'a+b' represent the same value. [knownConditionTrueFalse]\n", errout_str()); check("void f(int x) {\n" " if ((x == 1) && (x == 0x00000001))\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:18]: (style) Same expression on both sides of '&&' because 'x==1' and 'x==0x00000001' represent the same value. [knownConditionTrueFalse]\n", errout_str()); check("void f() {\n" " enum { Four = 4 };\n" - " if (Four == 4) {}" - "}"); + " if (Four == 4) {}\n" + "}\n"); ASSERT_EQUALS("[test.cpp:3:14]: (style) The comparison 'Four == 4' is always true. [knownConditionTrueFalse]\n", errout_str()); check("void f() {\n" " enum { Four = 4 };\n" " static_assert(Four == 4, \"\");\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " enum { Four = 4 };\n" " _Static_assert(Four == 4, \"\");\n" - "}", dinit(CheckOptions, $.cpp = false)); + "}\n", dinit(CheckOptions, $.cpp = false)); + ASSERT_EQUALS("", errout_str()); + + check("void f() {\n" // #15020 + " enum { Four = 4 };\n" + " static_assert(Four == 4, \"\");\n" + "}\n", dinit(CheckOptions, $.cpp = false)); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " enum { Four = 4 };\n" " static_assert(4 == Four, \"\");\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " enum { FourInEnumOne = 4 };\n" " enum { FourInEnumTwo = 4 };\n" " if (FourInEnumOne == FourInEnumTwo) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:23]: (style) The comparison 'FourInEnumOne == FourInEnumTwo' is always true because 'FourInEnumOne' and 'FourInEnumTwo' represent the same value. [knownConditionTrueFalse]\n", errout_str()); @@ -7549,13 +7880,13 @@ class TestOther : public TestFixture { " enum { FourInEnumOne = 4 };\n" " enum { FourInEnumTwo = 4 };\n" " static_assert(FourInEnumOne == FourInEnumTwo, \"\");\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int a, int b) {\n" " if (sizeof(a) == sizeof(a)) { }\n" " if (sizeof(a) == sizeof(b)) { }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:19]: (style) Same expression on both sides of '=='. [duplicateExpression]\n", errout_str()); check("float bar(int) __attribute__((pure));\n" @@ -7564,7 +7895,7 @@ class TestOther : public TestFixture { " if (bar(a) == bar(a)) { }\n" " if (unknown(a) == unknown(a)) { }\n" " if (foo(a) == foo(a)) { }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:16]: (style) Same expression on both sides of '=='. [duplicateExpression]\n", errout_str()); } @@ -7574,25 +7905,25 @@ class TestOther : public TestFixture { " if (!(dbl == dbl)) have_nan = 1;\n" " if (flt != flt) have_nan = 1;\n" " return have_nan;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); - check("float f(float x) { return x-x; }"); // ticket #4485 (Inf) + check("float f(float x) { return x-x; }\n"); // ticket #4485 (Inf) ASSERT_EQUALS("", errout_str()); - check("float f(float x) { return (X double)x == (X double)x; }", dinit(CheckOptions, $.inconclusive = false)); + check("float f(float x) { return (X double)x == (X double)x; }\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); check("struct X { float f; };\n" - "float f(struct X x) { return x.f == x.f; }"); + "float f(struct X x) { return x.f == x.f; }\n"); ASSERT_EQUALS("", errout_str()); check("struct X { int i; };\n" - "int f(struct X x) { return x.i == x.i; }"); + "int f(struct X x) { return x.i == x.i; }\n"); ASSERT_EQUALS("[test.cpp:2:32]: (style) Same expression on both sides of '=='. [duplicateExpression]\n", errout_str()); // #5284 - when type is unknown, assume it's float - check("int f() { return x==x; }"); + check("int f() { return x==x; }\n"); ASSERT_EQUALS("", errout_str()); } @@ -7609,7 +7940,7 @@ class TestOther : public TestFixture { check("void foo() {\n" " if (x() || x()) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct A {\n" @@ -7618,7 +7949,7 @@ class TestOther : public TestFixture { "};\n" "void A::foo() const {\n" " if (bar() && bar()) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:15]: (style) Same expression on both sides of '&&'. [duplicateExpression]\n", errout_str()); check("struct A {\n" @@ -7628,7 +7959,7 @@ class TestOther : public TestFixture { "};\n" "void A::foo() {\n" " if (bar() && bar()) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("class B {\n" @@ -7642,55 +7973,55 @@ class TestOther : public TestFixture { " A a;\n" " if (b.bar(1) && b.bar(1)) {}\n" " if (a.bar(1) && a.bar(1)) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:11:18]: (style) Same expression on both sides of '&&'. [duplicateExpression]\n", errout_str()); check("class D { void strcmp(); };\n" "void foo() {\n" " D d;\n" " if (d.strcmp() && d.strcmp()) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" " if ((mystrcmp(a, b) == 0) || (mystrcmp(a, b) == 0)) {}\n" - "}", dinit(CheckOptions, $.inconclusive = false, $.settings = &settings)); + "}\n", dinit(CheckOptions, $.inconclusive = false, $.settings = &settings)); ASSERT_EQUALS("[test.cpp:2:31]: (style) Same expression on both sides of '||'. [duplicateExpression]\n", errout_str()); check("void GetValue() { return rand(); }\n" "void foo() {\n" " if ((GetValue() == 0) || (GetValue() == 0)) { dostuff(); }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void __attribute__((const)) GetValue() { return X; }\n" "void foo() {\n" " if ((GetValue() == 0) || (GetValue() == 0)) { dostuff(); }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:27]: (style) Same expression on both sides of '||'. [duplicateExpression]\n", errout_str()); check("void GetValue() __attribute__((const));\n" "void GetValue() { return X; }\n" "void foo() {\n" " if ((GetValue() == 0) || (GetValue() == 0)) { dostuff(); }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:27]: (style) Same expression on both sides of '||'. [duplicateExpression]\n", errout_str()); check("void foo() {\n" " if (str == \"(\" || str == \"(\") {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:20]: (style) Same expression on both sides of '||'. [duplicateExpression]\n", errout_str()); check("void foo() {\n" " if (bar(a) && !strcmp(a, b) && bar(a) && !strcmp(a, b)) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #5334 check("void f(C *src) {\n" " if (x(src) || x(src))\n" " a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(A *src) {\n" @@ -7701,12 +8032,12 @@ class TestOther : public TestFixture { // #5819 check("Vector func(Vector vec1) {\n" " return fabs(vec1 & vec1 & vec1);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("Vector func(int vec1) {\n" " return fabs(vec1 & vec1 & vec1);\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:2:22]: (style) Same expression on both sides of '&'. [duplicateExpression]\n" "[test.cpp:2:29]: (style) Same expression on both sides of '&'. [duplicateExpression]\n", @@ -7717,25 +8048,25 @@ class TestOther : public TestFixture { void duplicateExpression4() { check("void foo() {\n" " if (*a++ != b || *a++ != b) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" " if (*a-- != b || *a-- != b) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // assignment check("void f() {\n" " while (*(a+=2)==*(b+=2) && *(a+=2)==*(b+=2)) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } void duplicateExpression5() { // #3749 - macros with same values check("void f() {\n" " if ($a == $a) { }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); checkP("#define X 1\n" @@ -7757,7 +8088,7 @@ class TestOther : public TestFixture { void duplicateExpression6() { // #4639 check("float IsNan(float value) { return !(value == value); }\n" "double IsNan(double value) { return !(value == value); }\n" - "long double IsNan(long double value) { return !(value == value); }"); + "long double IsNan(long double value) { return !(value == value); }\n"); ASSERT_EQUALS("", errout_str()); } @@ -7765,36 +8096,36 @@ class TestOther : public TestFixture { check("void f() {\n" " const int i = sizeof(int);\n" " if ( i != sizeof (int)){}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:25] -> [test.cpp:3:12]: (style) The comparison 'i != sizeof(int)' is always false because 'i' and 'sizeof(int)' represent the same value. [knownConditionTrueFalse]\n", errout_str()); check("void f() {\n" " const int i = sizeof(int);\n" " if ( sizeof (int) != i){}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:25] -> [test.cpp:3:23]: (style) The comparison 'sizeof(int) != i' is always false because 'sizeof(int)' and 'i' represent the same value. [knownConditionTrueFalse]\n", errout_str()); - check("void f(int a = 1) { if ( a != 1){}}"); + check("void f(int a = 1) { if ( a != 1){}}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " int a = 1;\n" " if ( a != 1){}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:13] -> [test.cpp:3:12]: (style) The comparison 'a != 1' is always false. [knownConditionTrueFalse]\n", errout_str()); check("void f() {\n" " int a = 1;\n" " int b = 1;\n" " if ( a != b){}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:13] -> [test.cpp:3:13] -> [test.cpp:4:12]: (style) The comparison 'a != b' is always false because 'a' and 'b' represent the same value. [knownConditionTrueFalse]\n", errout_str()); check("void f() {\n" " int a = 1;\n" " int b = a;\n" " if ( a != b){}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:13] -> [test.cpp:4:12]: (style) The comparison 'a != b' is always false because 'a' and 'b' represent the same value. [knownConditionTrueFalse]\n", errout_str()); check("void use(int);\n" @@ -7803,7 +8134,7 @@ class TestOther : public TestFixture { " int b = 1;\n" " use(b);\n" " if ( a != 1){}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:13] -> [test.cpp:6:12]: (style) The comparison 'a != 1' is always false. [knownConditionTrueFalse]\n", errout_str()); check("void use(int);\n" @@ -7812,7 +8143,7 @@ class TestOther : public TestFixture { " use(a);\n" " a = 2;\n" " if ( a != 1){}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void use(int);\n" @@ -7821,38 +8152,38 @@ class TestOther : public TestFixture { " use(a);\n" " a = 1;\n" " if ( a != 1){}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("const int a = 1;\n" "void f() {\n" " if ( a != 1){}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:1:15] -> [test.cpp:3:12]: (style) The comparison 'a != 1' is always false. [knownConditionTrueFalse]\n", errout_str()); check("int a = 1;\n" " void f() {\n" " if ( a != 1){}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " static const int a = 1;\n" " if ( a != 1){}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:26] -> [test.cpp:3:12]: (style) The comparison 'a != 1' is always false. [knownConditionTrueFalse]\n", errout_str()); check("void f() {\n" " static int a = 1;\n" " if ( a != 1){}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " int a = 1;\n" " if ( a != 1){\n" " a++;\n" - " }}"); + " }}\n"); ASSERT_EQUALS("[test.cpp:2:13] -> [test.cpp:3:12]: (style) The comparison 'a != 1' is always false. [knownConditionTrueFalse]\n", errout_str()); check("void f(int b) {\n" @@ -7861,13 +8192,13 @@ class TestOther : public TestFixture { " if ( a != 1){}\n" " a++;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("bool f(bool a, bool b) {\n" " const bool c = a;\n" " return a && b && c;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:20] -> [test.cpp:3:19]: (style) Same expression 'a' found multiple times in chain of '&&' operators because 'a' and 'c' represent the same value. [knownConditionTrueFalse]\n", errout_str()); @@ -7875,13 +8206,13 @@ class TestOther : public TestFixture { check("void f(const bool b) {\n" " const bool b1 = !b;\n" " if(!b && b1){}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:20] -> [test.cpp:3:10]: (style) Same expression on both sides of '&&' because '!b' and 'b1' represent the same value. [knownConditionTrueFalse]\n", errout_str()); // 7284 check("void f(void) {\n" " if (a || !!a) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:10]: (style) Same expression on both sides of '||' because 'a' and '!!a' represent the same value. [knownConditionTrueFalse]\n", errout_str()); // 8205 @@ -7892,7 +8223,7 @@ class TestOther : public TestFixture { " if (Diag==0) {}\n" " break;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:15] -> [test.cpp:5:16]: (style) The comparison 'Diag == 0' is always true. [knownConditionTrueFalse]\n", errout_str()); // #9744 @@ -7920,13 +8251,13 @@ class TestOther : public TestFixture { " int b = a;\n" " a = 2;\n" " if ( b != a){}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); - check("void f(int * a, int i) { int b = a[i]; a[i] = 2; if ( b != a[i]){}}"); + check("void f(int * a, int i) { int b = a[i]; a[i] = 2; if ( b != a[i]){}}\n"); ASSERT_EQUALS("", errout_str()); - check("void f(int * a, int i) { int b = *a; *a = 2; if ( b != *a){}}"); + check("void f(int * a, int i) { int b = *a; *a = 2; if ( b != *a){}}\n"); ASSERT_EQUALS("", errout_str()); check("struct A { int f() const; };\n" @@ -7937,7 +8268,7 @@ class TestOther : public TestFixture { " x = g();\n" " if (x.f() == a) break;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int f(int i);\n" @@ -7950,14 +8281,14 @@ class TestOther : public TestFixture { " const bool x = a.f(A::B);\n" " const bool y = a.f(A::C);\n" " if(!x && !y) return;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" " const bool x = a.f(A::B);\n" " const bool y = a.f(A::C);\n" " if (!x && !y) return;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(bool * const b);\n" @@ -7966,17 +8297,17 @@ class TestOther : public TestFixture { " bool y = true;\n" " f(&x);\n" " if (!x && !y) return;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " const int a = {};\n" " if(a == 1) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("volatile const int var = 42;\n" - "void f() { if(var == 42) {} }"); + "void f() { if(var == 42) {} }\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -7985,7 +8316,7 @@ class TestOther : public TestFixture { " c.a = &a;\n" " g(&c);\n" " if (a == 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -7995,7 +8326,7 @@ class TestOther : public TestFixture { " uint16_t x = 1000;\n" " uint8_t y = x;\n" " if (x != y) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -8006,7 +8337,7 @@ class TestOther : public TestFixture { " const int b = a-1;\n" " const int c = a+1;\n" " return c;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -8015,7 +8346,7 @@ class TestOther : public TestFixture { "public:\n" " double getScale() const { return m_range * m_zoom; }\n" " void setZoom(double z) { m_zoom = z; }\n" - " void dostuff(int);\n" + " void dostuff(int x);\n" "private:\n" " double m_zoom;\n" " double m_range;\n" @@ -8027,7 +8358,7 @@ class TestOther : public TestFixture { " setZoom(m_zoom + 1);\n" " double scale_ratio = getScale() / old_scale;\n" // <- FP " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -8036,14 +8367,14 @@ class TestOther : public TestFixture { "{\n" " int var = buffer[index - 1];\n" " return buffer[index - 1] - var;\n" // << - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:25] -> [test.cpp:4:34]: (style) Same expression on both sides of '-' because 'buffer[index-1]' and 'var' represent the same value. [duplicateExpression]\n", errout_str()); } void duplicateExpression13() { //#7899 check("void f() {\n" " if (sizeof(long) == sizeof(long long)) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -8176,7 +8507,76 @@ class TestOther : public TestFixture { void duplicateExpression20() { check("enum { N = 1 };\n" // #14202 - "int f() { return N - 1; }"); + "int f() { return N - 1; }\n"); + ASSERT_EQUALS("", errout_str()); + } + + void duplicateExpression21() { + check("struct S { int i; };\n" // #12795 + "struct T {\n" + " std::map m;\n" + " S* get(const std::string& s) { return m[s]; }\n" + " void modify() { for (const auto& e : m) e.second->i = 0; }\n" + "};\n" + "void f(T& t) {\n" + " const S* p = t.get(\"abc\");\n" + " const int o = p->i;\n" + " t.modify();\n" + " if (p->i == o) {}\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("struct S { int i; };\n" + " struct T {\n" + " std::vector m;\n" + " void modify() { for (auto e : m) e->i = 0; }\n" + "};\n" + "void f(T& t) {\n" + " const S* p = t.m[0];\n" + " const int o = p->i;\n" + " t.modify();\n" + " if (p->i == o) {}\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + } + + void duplicateExpression23() { // #12282 + // long double literals out of the range of double are different values + check("long double f() {\n" + " return 1e400L / 1e500L;\n" + "}\n" + "int g(long double x) {\n" + " return x == 1e400L || x == 1e500L;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + } + + void duplicateExpression22() { + check("int f() {\n" // #14913 + " return 0x1 | (0x2 | 0x4) | 0x1;\n" + "}\n" + "int g() {\n" + " return 0x1 | (0x2 | 0x1);\n" + "}\n" + "int h() {\n" + " return 0x1 | (0x1 | 0x2);\n" + "}\n" + "int i() {\n" + " return 0x2 | (0x4 | 0x1) | 0x1;\n" + "}\n" + "int j() {\n" + " return 0x2 | (0x1 | 0x4) | 0x1;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:2:30]: (style) Same expression '0x1' found multiple times in chain of '|' operators. [duplicateExpression]\n" + "[test.cpp:5:23]: (style) Same expression '0x1' found multiple times in chain of '|' operators. [duplicateExpression]\n" + "[test.cpp:8:23]: (style) Same expression '0x1' found multiple times in chain of '|' operators. [duplicateExpression]\n" + "[test.cpp:11:23]: (style) Same expression '0x1' found multiple times in chain of '|' operators. [duplicateExpression]\n" + "[test.cpp:14:23]: (style) Same expression '0x1' found multiple times in chain of '|' operators. [duplicateExpression]\n", + errout_str()); + + check("bool f(const int** a, const int** b) {\n" + " return (a[0] != nullptr) != (b[0] != nullptr);\n" + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -8184,23 +8584,23 @@ class TestOther : public TestFixture { check("void f() {\n" " int a = 1;\n" " while ( a != 1){}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:13] -> [test.cpp:3:15]: (style) The comparison 'a != 1' is always false. [knownConditionTrueFalse]\n", errout_str()); - check("void f() { int a = 1; while ( a != 1){ a++; }}"); + check("void f() { int a = 1; while ( a != 1){ a++; }}\n"); ASSERT_EQUALS("", errout_str()); - check("void f() { int a = 1; for ( int i=0; i < 3 && a != 1; i++){ a++; }}"); + check("void f() { int a = 1; for ( int i=0; i < 3 && a != 1; i++){ a++; }}\n"); ASSERT_EQUALS("", errout_str()); - check("void f(int b) { int a = 1; while (b) { if ( a != 1){} b++; } a++; }"); + check("void f(int b) { int a = 1; while (b) { if ( a != 1){} b++; } a++; }\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " for(int i = 0; i < 10;) {\n" " if( i != 0 ) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:17] -> [test.cpp:3:15]: (style) The comparison 'i != 0' is always false. [knownConditionTrueFalse]\n", errout_str()); check("void f() {\n" @@ -8208,7 +8608,7 @@ class TestOther : public TestFixture { " if( i != 0 ) {}\n" " i++;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -8216,14 +8616,14 @@ class TestOther : public TestFixture { " if( i != 0 ) { i++; }\n" " i++;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " for(int i = 0; i < 10;) {\n" " if( i != 0 ) { i++; }\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -8232,7 +8632,7 @@ class TestOther : public TestFixture { " if( i != 0 ) {}\n" " i++;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int b) {\n" @@ -8241,7 +8641,7 @@ class TestOther : public TestFixture { " if ( a != 1){}\n" " b++;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:17] -> [test.cpp:4:16]: (style) The comparison 'a != 1' is always false. [knownConditionTrueFalse]\n", errout_str()); check("struct T {\n" // #11083 @@ -8284,35 +8684,35 @@ class TestOther : public TestFixture { void duplicateExpressionTernary() { // #6391 check("void f() {\n" " return A ? x : x;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:18]: (style) Same expression in both branches of ternary operator. [duplicateExpressionTernary]\n", errout_str()); check("int f(bool b, int a) {\n" " const int c = a;\n" " return b ? a : c;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:19] -> [test.cpp:3:18]: (style) Same expression in both branches of ternary operator. [duplicateExpressionTernary]\n", errout_str()); check("void f() {\n" " return A ? x : z;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(unsigned char c) {\n" " x = y ? (signed char)c : (unsigned char)c;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("std::string stringMerge(std::string const& x, std::string const& y) {\n" // #7938 " return ((x > y) ? (y + x) : (x + y));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #6426 { const char code[] = "void foo(bool flag) {\n" " bar( (flag) ? ~0u : ~0ul);\n" - "}"; + "}\n"; /*const*/ Settings settings = settings1; settings.platform.sizeof_int = 4; settings.platform.int_bit = 32; @@ -8332,31 +8732,31 @@ class TestOther : public TestFixture { void duplicateValueTernary() { check("void f() {\n" " if( a ? (b ? false:false): false ) ;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:23]: (style) Same expression in both branches of ternary operator. [duplicateExpressionTernary]\n", errout_str()); - check("int f1(int a) {return (a == 1) ? (int)1 : 1; }"); + check("int f1(int a) {return (a == 1) ? (int)1 : 1; }\n"); ASSERT_EQUALS("[test.cpp:1:41]: (style) Same value in both branches of ternary operator. [duplicateValueTernary]\n", errout_str()); - check("int f2(int a) {return (a == 1) ? (int)1 : (int)1; }"); + check("int f2(int a) {return (a == 1) ? (int)1 : (int)1; }\n"); ASSERT_EQUALS("[test.cpp:1:41]: (style) Same expression in both branches of ternary operator. [duplicateExpressionTernary]\n", errout_str()); - check("int f3(int a) {return (a == 1) ? 1 : (int)1; }"); + check("int f3(int a) {return (a == 1) ? 1 : (int)1; }\n"); ASSERT_EQUALS("[test.cpp:1:36]: (style) Same value in both branches of ternary operator. [duplicateValueTernary]\n", errout_str()); - check("int f4(int a) {return (a == 1) ? 1 : 1; }"); + check("int f4(int a) {return (a == 1) ? 1 : 1; }\n"); ASSERT_EQUALS("[test.cpp:1:36]: (style) Same expression in both branches of ternary operator. [duplicateExpressionTernary]\n", errout_str()); - check("int f5(int a) {return (a == (int)1) ? (int)1 : 1; }"); + check("int f5(int a) {return (a == (int)1) ? (int)1 : 1; }\n"); ASSERT_EQUALS("[test.cpp:1:46]: (style) Same value in both branches of ternary operator. [duplicateValueTernary]\n", errout_str()); - check("int f6(int a) {return (a == (int)1) ? (int)1 : (int)1; }"); + check("int f6(int a) {return (a == (int)1) ? (int)1 : (int)1; }\n"); ASSERT_EQUALS("[test.cpp:1:46]: (style) Same expression in both branches of ternary operator. [duplicateExpressionTernary]\n", errout_str()); - check("int f7(int a) {return (a == (int)1) ? 1 : (int)1; }"); + check("int f7(int a) {return (a == (int)1) ? 1 : (int)1; }\n"); ASSERT_EQUALS("[test.cpp:1:41]: (style) Same value in both branches of ternary operator. [duplicateValueTernary]\n", errout_str()); - check("int f8(int a) {return (a == (int)1) ? 1 : 1; }"); + check("int f8(int a) {return (a == (int)1) ? 1 : 1; }\n"); ASSERT_EQUALS("[test.cpp:1:41]: (style) Same expression in both branches of ternary operator. [duplicateExpressionTernary]\n", errout_str()); check("struct Foo {\n" @@ -8410,10 +8810,10 @@ class TestOther : public TestFixture { } void duplicateValueTernarySizeof() { // #13773 - check("int f() { return x ? sizeof(uint32_t) : sizeof(unsigned int); }"); + check("int f() { return x ? sizeof(uint32_t) : sizeof(unsigned int); }\n"); ASSERT_EQUALS("", errout_str()); - check("int f() { return x ? sizeof(uint32_t) : sizeof(uint32_t); }"); + check("int f() { return x ? sizeof(uint32_t) : sizeof(uint32_t); }\n"); ASSERT_EQUALS("[test.cpp:1:39]: (style) Same expression in both branches of ternary operator. [duplicateExpressionTernary]\n", errout_str()); } @@ -8422,7 +8822,7 @@ class TestOther : public TestFixture { " if (I >= 0 && I < 3) {}\n" "}\n" "\n" - "static auto a = f<0>();"); + "static auto a = f<0>();\n"); ASSERT_EQUALS("", errout_str()); check("template\n" // #7754 @@ -8508,35 +8908,35 @@ class TestOther : public TestFixture { } void oppositeExpression() { - check("void f(bool a) { if(a && !a) {} }"); + check("void f(bool a) { if(a && !a) {} }\n"); ASSERT_EQUALS("[test.cpp:1:23]: (style) Opposite expression on both sides of '&&'. [oppositeExpression]\n", errout_str()); - check("void f(bool a) { if(a != !a) {} }"); + check("void f(bool a) { if(a != !a) {} }\n"); ASSERT_EQUALS("[test.cpp:1:23]: (style) Opposite expression on both sides of '!='. [oppositeExpression]\n", errout_str()); - check("void f(bool a) { if( a == !(a) ) {}}"); + check("void f(bool a) { if( a == !(a) ) {}}\n"); ASSERT_EQUALS("[test.cpp:1:24]: (style) Opposite expression on both sides of '=='. [oppositeExpression]\n", errout_str()); - check("void f(bool a) { if( a != !(a) ) {}}"); + check("void f(bool a) { if( a != !(a) ) {}}\n"); ASSERT_EQUALS("[test.cpp:1:24]: (style) Opposite expression on both sides of '!='. [oppositeExpression]\n", errout_str()); - check("void f(bool a) { if( !(a) == a ) {}}"); + check("void f(bool a) { if( !(a) == a ) {}}\n"); ASSERT_EQUALS("[test.cpp:1:27]: (style) Opposite expression on both sides of '=='. [oppositeExpression]\n", errout_str()); - check("void f(bool a) { if( !(a) != a ) {}}"); + check("void f(bool a) { if( !(a) != a ) {}}\n"); ASSERT_EQUALS("[test.cpp:1:27]: (style) Opposite expression on both sides of '!='. [oppositeExpression]\n", errout_str()); - check("void f(bool a) { if( !(!a) == !(a) ) {}}"); + check("void f(bool a) { if( !(!a) == !(a) ) {}}\n"); ASSERT_EQUALS("[test.cpp:1:28]: (style) Opposite expression on both sides of '=='. [oppositeExpression]\n", errout_str()); - check("void f(bool a) { if( !(!a) != !(a) ) {}}"); + check("void f(bool a) { if( !(!a) != !(a) ) {}}\n"); ASSERT_EQUALS("[test.cpp:1:28]: (style) Opposite expression on both sides of '!='. [oppositeExpression]\n", errout_str()); check("void f1(bool a) {\n" " const bool b = a;\n" " if( a == !(b) ) {}\n" " if( b == !(a) ) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:20] -> [test.cpp:3:11]: (style) Opposite expression on both sides of '=='. [oppositeExpression]\n" "[test.cpp:2:20] -> [test.cpp:4:11]: (style) Opposite expression on both sides of '=='. [oppositeExpression]\n", errout_str()); @@ -8544,26 +8944,26 @@ class TestOther : public TestFixture { " const bool b = *a;\n" " if( *a == !(b) ) {}\n" " if( b == !(*a) ) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:20] -> [test.cpp:3:12]: (style) Opposite expression on both sides of '=='. [oppositeExpression]\n" "[test.cpp:2:20] -> [test.cpp:4:11]: (style) Opposite expression on both sides of '=='. [oppositeExpression]\n", errout_str()); - check("void f(bool a) { a = !a; }"); + check("void f(bool a) { a = !a; }\n"); ASSERT_EQUALS("", errout_str()); - check("void f(int a) { if( a < -a ) {}}"); + check("void f(int a) { if( a < -a ) {}}\n"); ASSERT_EQUALS("[test.cpp:1:23]: (style) Opposite expression on both sides of '<'. [oppositeExpression]\n", errout_str()); - check("void f(int a) { a -= -a; }"); + check("void f(int a) { a -= -a; }\n"); ASSERT_EQUALS("", errout_str()); - check("void f(int a) { a = a / (-a); }"); + check("void f(int a) { a = a / (-a); }\n"); ASSERT_EQUALS("", errout_str()); - check("bool f(int i){ return !((i - 1) & i); }"); + check("bool f(int i){ return !((i - 1) & i); }\n"); ASSERT_EQUALS("", errout_str()); - check("bool f(unsigned i){ return (x > 0) && (x & (x-1)) == 0; }"); + check("bool f(unsigned i){ return (x > 0) && (x & (x-1)) == 0; }\n"); ASSERT_EQUALS("", errout_str()); check("void A::f(bool a, bool c)\n" @@ -8571,27 +8971,27 @@ class TestOther : public TestFixture { " const bool b = a;\n" " if(c) { a = false; }\n" " if(b && !a) { }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(bool c) {\n" " const bool b = a;\n" " if(c) { a = false; }\n" " if(b && !a) { }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " bool x = a;\n" " dostuff();\n" " if (x && a) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " const bool b = g();\n" " if (!b && g()) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(const bool *a) {\n" @@ -8634,7 +9034,7 @@ class TestOther : public TestFixture { "void test() {\n" " int i = f();\n" " int j = f();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:9] -> [test.cpp:4:9]: (style) Same expression used in consecutive assignments of 'i' and 'j'. [duplicateAssignExpression]\n", errout_str()); check("struct Foo { int f() const; int g() const; };\n" @@ -8642,7 +9042,7 @@ class TestOther : public TestFixture { " Foo f = Foo{};\n" " int i = f.f();\n" " int j = f.f();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:9] -> [test.cpp:4:9]: (style) Same expression used in consecutive assignments of 'i' and 'j'. [duplicateAssignExpression]\n", errout_str()); check("struct Foo { int f() const; int g() const; };\n" @@ -8651,7 +9051,7 @@ class TestOther : public TestFixture { " Foo f2 = Foo{};\n" " int i = f.f();\n" " int j = f.f();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:9] -> [test.cpp:5:9]: (style) Same expression used in consecutive assignments of 'i' and 'j'. [duplicateAssignExpression]\n", errout_str()); check("int f() __attribute__((pure));\n" @@ -8659,7 +9059,7 @@ class TestOther : public TestFixture { "void test() {\n" " int i = 1 + f();\n" " int j = 1 + f();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:9] -> [test.cpp:4:9]: (style) Same expression used in consecutive assignments of 'i' and 'j'. [duplicateAssignExpression]\n", errout_str()); check("int f() __attribute__((pure));\n" @@ -8667,7 +9067,7 @@ class TestOther : public TestFixture { "void test() {\n" " int i = f() + 1;\n" " int j = 1 + f();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int f() __attribute__((pure));\n" @@ -8676,7 +9076,7 @@ class TestOther : public TestFixture { " int x = f();\n" " int i = x + 1;\n" " int j = f() + 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int f() __attribute__((pure));\n" @@ -8684,7 +9084,7 @@ class TestOther : public TestFixture { "void test() {\n" " int i = f() + f();\n" " int j = f() + f();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:9] -> [test.cpp:4:9]: (style) Same expression used in consecutive assignments of 'i' and 'j'. [duplicateAssignExpression]\n", errout_str()); check("int f(int) __attribute__((pure));\n" @@ -8692,7 +9092,7 @@ class TestOther : public TestFixture { "void test() {\n" " int i = f(0);\n" " int j = f(0);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:9] -> [test.cpp:4:9]: (style) Same expression used in consecutive assignments of 'i' and 'j'. [duplicateAssignExpression]\n", errout_str()); check("int f(int) __attribute__((pure));\n" @@ -8701,39 +9101,39 @@ class TestOther : public TestFixture { " const int x = 0;\n" " int i = f(0);\n" " int j = f(x);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void test(const int * p, const int * q) {\n" " int i = *p;\n" " int j = *p;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:9] -> [test.cpp:2:9]: (style) Same expression used in consecutive assignments of 'i' and 'j'. [duplicateAssignExpression]\n", errout_str()); check("struct A { int x; int y; };" "void test(A a) {\n" " int i = a.x;\n" " int j = a.x;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:9] -> [test.cpp:2:9]: (style) Same expression used in consecutive assignments of 'i' and 'j'. [duplicateAssignExpression]\n", errout_str()); check("void test() {\n" " int i = 0;\n" " int j = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void test() {\n" " int i = -1;\n" " int j = -1;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int f(int);\n" "void test() {\n" " int i = f(0);\n" " int j = f(1);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int f();\n" @@ -8741,21 +9141,21 @@ class TestOther : public TestFixture { "void test() {\n" " int i = f() || f();\n" " int j = f() && f();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct Foo {};\n" "void test() {\n" " Foo i = Foo();\n" " Foo j = Foo();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct Foo {};\n" "void test() {\n" " Foo i = Foo{};\n" " Foo j = Foo{};\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct Foo { int f() const; float g() const; };\n" @@ -8763,7 +9163,7 @@ class TestOther : public TestFixture { " Foo f = Foo{};\n" " int i = f.f();\n" " int j = f.f();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:9] -> [test.cpp:4:9]: (style, inconclusive) Same expression used in consecutive assignments of 'i' and 'j'. [duplicateAssignExpression]\n", errout_str()); check("struct Foo { int f(); int g(); };\n" @@ -8771,43 +9171,43 @@ class TestOther : public TestFixture { " Foo f = Foo{};\n" " int i = f.f();\n" " int j = f.f();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void test() {\n" " int i = f();\n" " int j = f();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void test(int x) {\n" " int i = ++x;\n" " int j = ++x;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void test(int x) {\n" " int i = x++;\n" " int j = x++;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void test(int x) {\n" " int i = --x;\n" " int j = --x;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void test(int x) {\n" " int i = x--;\n" " int j = x--;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void test(int x) {\n" " int i = x + 1;\n" " int j = 1 + x;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -8816,7 +9216,7 @@ class TestOther : public TestFixture { "void foo(SW* x) {\n" " int start = x->first;\n" " int end = x->first;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:9] -> [test.cpp:3:9]: (style, inconclusive) Same expression used in consecutive assignments of 'start' and 'end'. [duplicateAssignExpression]\n" "[test.cpp:2:14]: (style) Parameter 'x' can be declared as pointer to const [constParameterPointer]\n", errout_str()); @@ -8825,7 +9225,7 @@ class TestOther : public TestFixture { "void foo(SW* x, int i, int j) {\n" " int start = x->first;\n" " int end = x->first;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:9] -> [test.cpp:3:9]: (style, inconclusive) Same expression used in consecutive assignments of 'start' and 'end'. [duplicateAssignExpression]\n" "[test.cpp:2:14]: (style) Parameter 'x' can be declared as pointer to const [constParameterPointer]\n", errout_str()); @@ -8835,13 +9235,13 @@ class TestOther : public TestFixture { " Foo f = Foo{};\n" " int i = f.f();\n" " int j = f.f();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:9] -> [test.cpp:4:9]: (style, inconclusive) Same expression used in consecutive assignments of 'i' and 'j'. [duplicateAssignExpression]\n", errout_str()); check("void test(const int * p) {\n" " int i = *p;\n" " int j = *p;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:9] -> [test.cpp:2:9]: (style, inconclusive) Same expression used in consecutive assignments of 'i' and 'j'. [duplicateAssignExpression]\n", errout_str()); check("struct Foo { int f() const; int g(int) const; };\n" @@ -8849,7 +9249,7 @@ class TestOther : public TestFixture { " Foo f = Foo{};\n" " int i = f.f();\n" " int j = f.f();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:9] -> [test.cpp:4:9]: (style, inconclusive) Same expression used in consecutive assignments of 'i' and 'j'. [duplicateAssignExpression]\n", errout_str()); check("struct Foo { int f() const; };\n" @@ -8857,7 +9257,7 @@ class TestOther : public TestFixture { " Foo f = Foo{};\n" " int i = f.f();\n" " int j = f.f();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:9] -> [test.cpp:4:9]: (style, inconclusive) Same expression used in consecutive assignments of 'i' and 'j'. [duplicateAssignExpression]\n", errout_str()); } @@ -8869,7 +9269,7 @@ class TestOther : public TestFixture { " int j = a.x;\n" " use(i);\n" " i = j;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:9] -> [test.cpp:3:9]: (style, inconclusive) Same expression used in consecutive assignments of 'i' and 'j'. [duplicateAssignExpression]\n", errout_str()); check("struct A { int x; int y; };" @@ -8879,7 +9279,7 @@ class TestOther : public TestFixture { " int j = a.x;\n" " use(j);\n" " j = i;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:9] -> [test.cpp:3:9]: (style, inconclusive) Same expression used in consecutive assignments of 'i' and 'j'. [duplicateAssignExpression]\n", errout_str()); check("struct A { int x; int y; };" @@ -8889,10 +9289,9 @@ class TestOther : public TestFixture { " int j = a.x;\n" " use(j);\n" " if (i == j) {}\n" - "}"); + "}\n"); ASSERT_EQUALS( - "[test.cpp:4:9] -> [test.cpp:3:9]: (style, inconclusive) Same expression used in consecutive assignments of 'i' and 'j'. [duplicateAssignExpression]\n" - "[test.cpp:3:14] -> [test.cpp:4:14] -> [test.cpp:6:11]: (style) The comparison 'i == j' is always true because 'i' and 'j' represent the same value. [knownConditionTrueFalse]\n", + "[test.cpp:4:9] -> [test.cpp:3:9]: (style, inconclusive) Same expression used in consecutive assignments of 'i' and 'j'. [duplicateAssignExpression]\n", errout_str()); check("struct A { int x; int y; };" @@ -8902,10 +9301,9 @@ class TestOther : public TestFixture { " int j = a.x;\n" " use(j);\n" " if (i == a.x) {}\n" - "}"); + "}\n"); ASSERT_EQUALS( - "[test.cpp:4:9] -> [test.cpp:3:9]: (style, inconclusive) Same expression used in consecutive assignments of 'i' and 'j'. [duplicateAssignExpression]\n" - "[test.cpp:3:14] -> [test.cpp:6:11]: (style) The comparison 'i == a.x' is always true because 'i' and 'a.x' represent the same value. [knownConditionTrueFalse]\n", + "[test.cpp:4:9] -> [test.cpp:3:9]: (style, inconclusive) Same expression used in consecutive assignments of 'i' and 'j'. [duplicateAssignExpression]\n", errout_str()); check("struct A { int x; int y; };" @@ -8915,10 +9313,9 @@ class TestOther : public TestFixture { " int j = a.x;\n" " use(i);\n" " if (j == a.x) {}\n" - "}"); + "}\n"); ASSERT_EQUALS( - "[test.cpp:4:9] -> [test.cpp:3:9]: (style, inconclusive) Same expression used in consecutive assignments of 'i' and 'j'. [duplicateAssignExpression]\n" - "[test.cpp:4:14] -> [test.cpp:6:11]: (style) The comparison 'j == a.x' is always true because 'j' and 'a.x' represent the same value. [knownConditionTrueFalse]\n", + "[test.cpp:4:9] -> [test.cpp:3:9]: (style, inconclusive) Same expression used in consecutive assignments of 'i' and 'j'. [duplicateAssignExpression]\n", errout_str()); // Issue #8612 @@ -8947,7 +9344,7 @@ class TestOther : public TestFixture { " }\n" " previous = current;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:16:7] -> [test.cpp:15:7]: (style, inconclusive) Same expression used in consecutive assignments of 'current' and 'previous'. [duplicateAssignExpression]\n", errout_str()); } @@ -8963,21 +9360,21 @@ class TestOther : public TestFixture { " int b = x.f();\n" " (void)a;\n" " (void)b;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:13] -> [test.cpp:7:13]: (style, inconclusive) Same expression used in consecutive assignments of 'a' and 'b'. [duplicateAssignExpression]\n", errout_str()); // Issue #8712 check("void f() {\n" " unsigned char d;\n" " d = d % 5;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("template \n" "T f() {\n" " T x = T();\n" "}\n" - "int &a = f();"); + "int &a = f();\n"); ASSERT_EQUALS("", errout_str()); // Issue #8713 @@ -8989,7 +9386,7 @@ class TestOther : public TestFixture { " uint32_t a = 42;\n" " uint32_t b = uint32_t(A ::B / 1024);\n" " int32_t c = int32_t(a / b);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Issue #8709 @@ -8998,7 +9395,7 @@ class TestOther : public TestFixture { " switch (d) { case b:; }\n" " double e(b);\n" " if(e <= 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #10718 @@ -9019,7 +9416,7 @@ class TestOther : public TestFixture { " int val = 0;\n" " if (val < 0) continue;\n" " if ((val > 0)) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:13] -> [test.cpp:3:11]: (style) The comparison 'val < 0' is always false. [knownConditionTrueFalse]\n" "[test.cpp:2:13] -> [test.cpp:4:12]: (style) The comparison 'val > 0' is always false. [knownConditionTrueFalse]\n", errout_str()); @@ -9050,7 +9447,7 @@ class TestOther : public TestFixture { " if (val < 0) {\n" " if ((val > 0)) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:13] -> [test.cpp:3:11]: (style) The comparison 'val < 0' is always false. [knownConditionTrueFalse]\n" "[test.cpp:2:13] -> [test.cpp:4:14]: (style) The comparison 'val > 0' is always false. [knownConditionTrueFalse]\n", errout_str()); @@ -9059,7 +9456,7 @@ class TestOther : public TestFixture { " if (val < 0) {\n" " if ((val < 0)) {}\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:13] -> [test.cpp:3:11]: (style) The comparison 'val < 0' is always false. [knownConditionTrueFalse]\n" "[test.cpp:2:13] -> [test.cpp:4:14]: (style) The comparison 'val < 0' is always false. [knownConditionTrueFalse]\n", errout_str()); @@ -9068,25 +9465,25 @@ class TestOther : public TestFixture { " int foo = 0;\n" " if (activate) {}\n" " else if (foo) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } void checkSignOfUnsignedVariable() { check("void foo() {\n" " for(unsigned char i = 10; i >= 0; i--) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:31]: (style) Unsigned expression 'i' can't be negative so it is unnecessary to test it. [unsignedPositive]\n", errout_str()); check("void foo(bool b) {\n" " for(unsigned int i = 10; b || i >= 0; i--) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:35]: (style) Unsigned expression 'i' can't be negative so it is unnecessary to test it. [unsignedPositive]\n", errout_str()); { const char code[] = "void foo(unsigned int x) {\n" " if (x < 0) {}\n" - "}"; + "}\n"; check(code, dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("[test.cpp:2:9]: (style) Checking if unsigned expression 'x' is less than zero. [unsignedLessThanZero]\n", errout_str()); check(code, dinit(CheckOptions, $.inconclusive = false, $.verbose = true)); @@ -9095,19 +9492,19 @@ class TestOther : public TestFixture { check("void foo(unsigned int x) {\n" " if (x < 0u) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9]: (style) Checking if unsigned expression 'x' is less than zero. [unsignedLessThanZero]\n", errout_str()); check("void foo(int x) {\n" " if (x < 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); { const char code[] = "void foo(unsigned x) {\n" " int y = 0;\n" " if (x < y) {}\n" - "}"; + "}\n"; check(code, dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("[test.cpp:3:9]: (style) Checking if unsigned expression 'x' is less than zero. [unsignedLessThanZero]\n", errout_str()); check(code, dinit(CheckOptions, $.inconclusive = false, $.verbose = true)); @@ -9118,149 +9515,149 @@ class TestOther : public TestFixture { " if (b)\n" " y = 1;\n" " if (x < y) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(unsigned int x) {\n" " if (0 > x) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9]: (style) Checking if unsigned expression 'x' is less than zero. [unsignedLessThanZero]\n", errout_str()); check("void foo(unsigned int x) {\n" " if (0UL > x) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11]: (style) Checking if unsigned expression 'x' is less than zero. [unsignedLessThanZero]\n", errout_str()); check("void foo(int x) {\n" " if (0 > x) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(unsigned int x) {\n" " if (x >= 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9]: (style) Unsigned expression 'x' can't be negative so it is unnecessary to test it. [unsignedPositive]\n", errout_str()); check("void foo(unsigned int x, unsigned y) {\n" " if (x - y >= 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:13]: (style) Unsigned expression 'x-y' can't be negative so it is unnecessary to test it. [unsignedPositive]\n", errout_str()); check("void foo(unsigned int x) {\n" " if (x >= 0ull) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9]: (style) Unsigned expression 'x' can't be negative so it is unnecessary to test it. [unsignedPositive]\n", errout_str()); check("void foo(int x) {\n" " if (x >= 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(unsigned int x) {\n" " if (0 <= x) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9]: (style) Unsigned expression 'x' can't be negative so it is unnecessary to test it. [unsignedPositive]\n", errout_str()); check("void foo(unsigned int x) {\n" " if (0ll <= x) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11]: (style) Unsigned expression 'x' can't be negative so it is unnecessary to test it. [unsignedPositive]\n", errout_str()); check("void foo(int x) {\n" " if (0 <= x) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(unsigned int x, bool y) {\n" " if (x < 0 && y) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9]: (style) Checking if unsigned expression 'x' is less than zero. [unsignedLessThanZero]\n", errout_str()); check("void foo(int x, bool y) {\n" " if (x < 0 && y) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(unsigned int x, bool y) {\n" " if (0 > x && y) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9]: (style) Checking if unsigned expression 'x' is less than zero. [unsignedLessThanZero]\n", errout_str()); check("void foo(int x, bool y) {\n" " if (0 > x && y) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(unsigned int x, bool y) {\n" " if (x >= 0 && y) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9]: (style) Unsigned expression 'x' can't be negative so it is unnecessary to test it. [unsignedPositive]\n", errout_str()); check("void foo(int x, bool y) {\n" " if (x >= 0 && y) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(unsigned int x, bool y) {\n" " if (y && x < 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:14]: (style) Checking if unsigned expression 'x' is less than zero. [unsignedLessThanZero]\n", errout_str()); check("void foo(int x, bool y) {\n" " if (y && x < 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(unsigned int x, bool y) {\n" " if (y && 0 > x) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:14]: (style) Checking if unsigned expression 'x' is less than zero. [unsignedLessThanZero]\n", errout_str()); check("void foo(int x, bool y) {\n" " if (y && 0 > x) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(unsigned int x, bool y) {\n" " if (y && x >= 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:14]: (style) Unsigned expression 'x' can't be negative so it is unnecessary to test it. [unsignedPositive]\n", errout_str()); check("void foo(int x, bool y) {\n" " if (y && x >= 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(unsigned int x, bool y) {\n" " if (x < 0 || y) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9]: (style) Checking if unsigned expression 'x' is less than zero. [unsignedLessThanZero]\n", errout_str()); check("void foo(int x, bool y) {\n" " if (x < 0 || y) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(unsigned int x, bool y) {\n" " if (0 > x || y) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9]: (style) Checking if unsigned expression 'x' is less than zero. [unsignedLessThanZero]\n", errout_str()); check("void foo(int x, bool y) {\n" " if (0 > x || y) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(unsigned int x, bool y) {\n" " if (x >= 0 || y) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9]: (style) Unsigned expression 'x' can't be negative so it is unnecessary to test it. [unsignedPositive]\n", errout_str()); check("void foo(int x, bool y) {\n" " if (x >= 0 || y) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #3233 - FP when template is used (template parameter is numeric constant) @@ -9268,7 +9665,7 @@ class TestOther : public TestFixture { const char code[] = "template void foo(unsigned int x) {\n" " if (x <= n);\n" "}\n" - "foo<0>();"; + "foo<0>();\n"; check(code, dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); check(code); @@ -9278,7 +9675,7 @@ class TestOther : public TestFixture { { check("template void foo(unsigned int x) {\n" "if (x <= 0);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:7]: (style) Checking if unsigned expression 'x' is less than zero. [unsignedLessThanZero]\n", errout_str()); } @@ -9289,7 +9686,7 @@ class TestOther : public TestFixture { " {\n" " value = 0u;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:13]: (style) Checking if unsigned expression 'value' is less than zero. [unsignedLessThanZero]\n", errout_str()); // #9040 @@ -9306,7 +9703,7 @@ class TestOther : public TestFixture { " const uint32_t x = 0;\n" " constexpr const auto y = 0xFFFFU;\n" " if (y < x) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:10]: (style) Checking if unsigned expression 'y' is less than zero. [unsignedLessThanZero]\n", errout_str()); // #12387 @@ -9318,14 +9715,14 @@ class TestOther : public TestFixture { "}\n" "void g() {\n" " f(0);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #13734 check("void f() {\n" " uint8_t a[N + 1];\n" " for (unsigned p = 0; p < (sizeof(a) / sizeof((a)[0])); ++p) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(const unsigned char u) {\n" @@ -9337,7 +9734,7 @@ class TestOther : public TestFixture { " if (0 > u) {}\n" " if (0 <= u) {}\n" " if (0 >= u) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:11]: (style) Checking if unsigned expression 'u' is less than zero. [unsignedLessThanZero]\n" "[test.cpp:4:11]: (style) Unsigned expression 'u' can't be negative so it is unnecessary to test it. [unsignedPositive]\n" "[test.cpp:5:11]: (style) Checking if unsigned expression 'u' is less than zero. [unsignedLessThanZero]\n" @@ -9350,14 +9747,14 @@ class TestOther : public TestFixture { void checkSignOfPointer() { check("void foo(const int* x) {\n" " if (x >= 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9]: (style) A pointer can not be negative so it is either pointless or an error to check if it is not. [pointerPositive]\n", errout_str()); { const char code[] = "void foo(const int* x) {\n" " int y = 0;\n" " if (x >= y) {}\n" - "}"; + "}\n"; check(code, dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("[test.cpp:3:9]: (style) A pointer can not be negative so it is either pointless or an error to check if it is not. [pointerPositive]\n", errout_str()); check(code, dinit(CheckOptions, $.inconclusive = false, $.verbose = true)); @@ -9365,19 +9762,19 @@ class TestOther : public TestFixture { } check("void foo(const int* x) {\n" " if (*x >= 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(const int* x) {\n" " if (x < 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9]: (style) A pointer can not be negative so it is either pointless or an error to check if it is. [pointerLessThanZero]\n", errout_str()); { const char code[] = "void foo(const int* x) {\n" " unsigned y = 0u;\n" " if (x < y) {}\n" - "}"; + "}\n"; check(code, dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("[test.cpp:3:9]: (style) A pointer can not be negative so it is either pointless or an error to check if it is. [pointerLessThanZero]\n", errout_str()); @@ -9387,32 +9784,32 @@ class TestOther : public TestFixture { check("void foo(const int* x) {\n" " if (*x < 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(const int* x, const int* y) {\n" " if (x - y < 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(const int* x, const int* y) {\n" " if (x - y <= 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(const int* x, const int* y) {\n" " if (x - y > 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(const int* x, const int* y) {\n" " if (x - y >= 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(const Bar* x) {\n" " if (0 <= x) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9]: (style) A pointer can not be negative so it is either pointless or an error to check if it is not. [pointerPositive]\n", errout_str()); check("struct S {\n" @@ -9420,7 +9817,7 @@ class TestOther : public TestFixture { "};\n" "void foo(S* first) {\n" " if (first.ptr >= 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:17]: (style) A pointer can not be negative so it is either pointless or an error to check if it is not. [pointerPositive]\n" "[test.cpp:4:13]: (style) Parameter 'first' can be declared as pointer to const [constParameterPointer]\n", errout_str()); @@ -9430,7 +9827,7 @@ class TestOther : public TestFixture { "};\n" "void foo(S* first, S* second) {\n" " if((first.ptr - second.ptr) >= 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:13]: (style) Parameter 'first' can be declared as pointer to const [constParameterPointer]\n" "[test.cpp:4:23]: (style) Parameter 'second' can be declared as pointer to const [constParameterPointer]\n", errout_str()); @@ -9440,7 +9837,7 @@ class TestOther : public TestFixture { "};\n" "void foo(S* first) {\n" " if((first.ptr) >= 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:18]: (style) A pointer can not be negative so it is either pointless or an error to check if it is not. [pointerPositive]\n" "[test.cpp:4:13]: (style) Parameter 'first' can be declared as pointer to const [constParameterPointer]\n", errout_str()); @@ -9450,7 +9847,7 @@ class TestOther : public TestFixture { "};\n" "void foo(S* first, S* second) {\n" " if(0 <= first.ptr - second.ptr) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:13]: (style) Parameter 'first' can be declared as pointer to const [constParameterPointer]\n" "[test.cpp:4:23]: (style) Parameter 'second' can be declared as pointer to const [constParameterPointer]\n", errout_str()); @@ -9460,7 +9857,7 @@ class TestOther : public TestFixture { "};\n" "void foo(S* first, S* second) {\n" " if(0 <= (first.ptr - second.ptr)) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:13]: (style) Parameter 'first' can be declared as pointer to const [constParameterPointer]\n" "[test.cpp:4:23]: (style) Parameter 'second' can be declared as pointer to const [constParameterPointer]\n", errout_str()); @@ -9470,7 +9867,7 @@ class TestOther : public TestFixture { "};\n" "void foo(S* first, S* second) {\n" " if(first.ptr - second.ptr < 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:13]: (style) Parameter 'first' can be declared as pointer to const [constParameterPointer]\n" "[test.cpp:4:23]: (style) Parameter 'second' can be declared as pointer to const [constParameterPointer]\n", errout_str()); @@ -9480,7 +9877,7 @@ class TestOther : public TestFixture { "};\n" "void foo(S* first, S* second) {\n" " if((first.ptr - second.ptr) < 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:13]: (style) Parameter 'first' can be declared as pointer to const [constParameterPointer]\n" "[test.cpp:4:23]: (style) Parameter 'second' can be declared as pointer to const [constParameterPointer]\n", errout_str()); @@ -9490,7 +9887,7 @@ class TestOther : public TestFixture { "};\n" "void foo(S* first, S* second) {\n" " if(0 > first.ptr - second.ptr) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:13]: (style) Parameter 'first' can be declared as pointer to const [constParameterPointer]\n" "[test.cpp:4:23]: (style) Parameter 'second' can be declared as pointer to const [constParameterPointer]\n", errout_str()); @@ -9500,69 +9897,69 @@ class TestOther : public TestFixture { "};\n" "void foo(S* first, S* second) {\n" " if(0 > (first.ptr - second.ptr)) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:13]: (style) Parameter 'first' can be declared as pointer to const [constParameterPointer]\n" "[test.cpp:4:23]: (style) Parameter 'second' can be declared as pointer to const [constParameterPointer]\n", errout_str()); check("void foo(const int* x) {\n" " if (0 <= x[0]) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(Bar* x) {\n" " if (0 <= x.y) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:1:15]: (style) Parameter 'x' can be declared as pointer to const [constParameterPointer]\n", errout_str()); check("void foo(Bar* x) {\n" " if (0 <= x->y) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:1:15]: (style) Parameter 'x' can be declared as pointer to const [constParameterPointer]\n", errout_str()); check("void foo(Bar* x, Bar* y) {\n" " if (0 <= x->y - y->y ) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:1:15]: (style) Parameter 'x' can be declared as pointer to const [constParameterPointer]\n" "[test.cpp:1:23]: (style) Parameter 'y' can be declared as pointer to const [constParameterPointer]\n", errout_str()); check("void foo(const Bar* x) {\n" " if (0 > x) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:9]: (style) A pointer can not be negative so it is either pointless or an error to check if it is. [pointerLessThanZero]\n", errout_str()); check("void foo(const int* x) {\n" " if (0 > x[0]) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(Bar* x) {\n" " if (0 > x.y) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:1:15]: (style) Parameter 'x' can be declared as pointer to const [constParameterPointer]\n", errout_str()); check("void foo(Bar* x) {\n" " if (0 > x->y) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:1:15]: (style) Parameter 'x' can be declared as pointer to const [constParameterPointer]\n", errout_str()); check("void foo() {\n" " int (*t)(void *a, void *b);\n" " if (t(a, b) < 0) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" " int (*t)(void *a, void *b);\n" " if (0 > t(a, b)) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct object_info { int *typep; };\n" "void packed_object_info(struct object_info *oi) {\n" " if (oi->typep < 0);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:17]: (style) A pointer can not be negative so it is either pointless or an error to check if it is. [pointerLessThanZero]\n" "[test.cpp:2:45]: (style) Parameter 'oi' can be declared as pointer to const [constParameterPointer]\n", errout_str()); @@ -9570,7 +9967,7 @@ class TestOther : public TestFixture { check("struct object_info { int typep[10]; };\n" "void packed_object_info(struct object_info *oi) {\n" " if (oi->typep < 0);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:17]: (style) A pointer can not be negative so it is either pointless or an error to check if it is. [pointerLessThanZero]\n" "[test.cpp:2:45]: (style) Parameter 'oi' can be declared as pointer to const [constParameterPointer]\n", errout_str()); @@ -9578,28 +9975,28 @@ class TestOther : public TestFixture { check("struct object_info { int *typep; };\n" "void packed_object_info(struct object_info *oi) {\n" " if (*oi->typep < 0);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:45]: (style) Parameter 'oi' can be declared as pointer to const [constParameterPointer]\n", errout_str()); } void checkSuspiciousSemicolon1() { check("void foo() {\n" " for(int i = 0; i < 10; ++i);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Empty block check("void foo() {\n" " for(int i = 0; i < 10; ++i); {\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:3]: (warning) Suspicious use of ; at the end of 'for' statement. [suspiciousSemicolon]\n", errout_str()); check("void foo() {\n" " while (!quit); {\n" " do_something();\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:3]: (warning) Suspicious use of ; at the end of 'while' statement. [suspiciousSemicolon]\n", errout_str()); } @@ -9608,21 +10005,21 @@ class TestOther : public TestFixture { " if (i == 1); {\n" " do_something();\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:3]: (warning) Suspicious use of ; at the end of 'if' statement. [suspiciousSemicolon]\n", errout_str()); // Seen this in the wild check("void foo() {\n" " if (Match());\n" " do_something();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" " if (Match());\n" " else\n" " do_something();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" @@ -9631,7 +10028,7 @@ class TestOther : public TestFixture { " {\n" " do_something();\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" @@ -9640,7 +10037,7 @@ class TestOther : public TestFixture { " {\n" " do_something();\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -9649,20 +10046,20 @@ class TestOther : public TestFixture { "void foo() {\n" " if (x == 123);\n" " REQUIRE(y=z);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } void checkSuspiciousComparison() { checkP("void f(int a, int b) {\n" " a > b;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:5]: (warning, inconclusive) Found suspicious operator '>', result is not used. [constStatement]\n", errout_str()); checkP("void f() {\n" // #10607 " for (auto p : m)\n" " std::vector> k;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -9670,25 +10067,25 @@ class TestOther : public TestFixture { check("void foo(char *p) {\n" " char *a; a = malloc(1024);\n" " free(a + 10);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:3]: (error) Mismatching address is freed. The address you get from malloc() must be freed without offset. [invalidFree]\n", errout_str()); check("void foo(char *p) {\n" " char *a; a = malloc(1024);\n" " free(a - 10);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:3]: (error) Mismatching address is freed. The address you get from malloc() must be freed without offset. [invalidFree]\n", errout_str()); check("void foo(char *p) {\n" " char *a; a = malloc(1024);\n" " free(10 + a);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:3]: (error) Mismatching address is freed. The address you get from malloc() must be freed without offset. [invalidFree]\n", errout_str()); check("void foo(char *p) {\n" " char *a; a = new char[1024];\n" " delete[] (a + 10);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:1:16]: (style) Parameter 'p' can be declared as pointer to const [constParameterPointer]\n" "[test.cpp:3:3]: (error) Mismatching address is deleted. The address you get from new must be deleted without offset. [invalidFree]\n", errout_str()); @@ -9696,7 +10093,7 @@ class TestOther : public TestFixture { check("void foo(char *p) {\n" " char *a; a = new char;\n" " delete a + 10;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:1:16]: (style) Parameter 'p' can be declared as pointer to const [constParameterPointer]\n" "[test.cpp:3:3]: (error) Mismatching address is deleted. The address you get from new must be deleted without offset. [invalidFree]\n", errout_str()); @@ -9705,7 +10102,7 @@ class TestOther : public TestFixture { " char *a; a = new char;\n" " bar(a);\n" " delete a + 10;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(char *p) {\n" @@ -9714,7 +10111,7 @@ class TestOther : public TestFixture { " bar(a);\n" " delete a + 10;\n" " delete b + 10;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:3]: (error) Mismatching address is deleted. The address you get from new must be deleted without offset. [invalidFree]\n", errout_str()); check("void foo(char *p) {\n" @@ -9723,14 +10120,14 @@ class TestOther : public TestFixture { " bar(a, b);\n" " delete a + 10;\n" " delete b + 10;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(char *p) {\n" " char *a; a = new char;\n" " bar()\n" " delete a + 10;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:1:16]: (style) Parameter 'p' can be declared as pointer to const [constParameterPointer]\n" "[test.cpp:4:3]: (error) Mismatching address is deleted. The address you get from new must be deleted without offset. [invalidFree]\n", errout_str()); @@ -9739,7 +10136,7 @@ class TestOther : public TestFixture { " char *ptr; ptr = malloc(42);\n" " ptr += xx;\n" " free(ptr + 1 - xx);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:3]: (error) Mismatching address is freed. The address you get from malloc() must be freed without offset. [invalidFree]\n", errout_str()); check("void foo(size_t xx) {\n" @@ -9747,7 +10144,7 @@ class TestOther : public TestFixture { " std::cout << ptr;\n" " ptr = otherPtr;\n" " free(otherPtr - xx - 1);\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:2:9]: (style) Variable 'ptr' can be declared as pointer to const [constVariablePointer]\n", errout_str()); @@ -9757,7 +10154,7 @@ class TestOther : public TestFixture { check("const std::string& getA(){static std::string a;return a;}\n" "void foo() {\n" " const std::string a = getA();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:23]: (performance, inconclusive) Use const reference for 'a' to avoid unnecessary data copying. [redundantCopyLocalConst]\n", errout_str()); check("class A { public: A() {} char x[100]; };\n" @@ -9766,7 +10163,7 @@ class TestOther : public TestFixture { "{\n" " const A a = getA();\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:13]: (performance, inconclusive) Use const reference for 'a' to avoid unnecessary data copying. [redundantCopyLocalConst]\n", errout_str()); check("const int& getA(){static int a;return a;}\n" @@ -9774,7 +10171,7 @@ class TestOther : public TestFixture { "{\n" " const int a = getA();\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("const int& getA(){static int a;return a;}\n" @@ -9783,7 +10180,7 @@ class TestOther : public TestFixture { " int getA = 0;\n" " const int a = getA + 3;\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:1:12] -> [test.cpp:4:9]: (style) Local variable 'getA' shadows outer function [shadowFunction]\n", errout_str()); check("class A { public: A() {} char x[100]; };\n" @@ -9792,7 +10189,7 @@ class TestOther : public TestFixture { "{\n" " const A a(getA());\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:13]: (performance, inconclusive) Use const reference for 'a' to avoid unnecessary data copying. [redundantCopyLocalConst]\n", errout_str()); check("const int& getA(){static int a;return a;}\n" @@ -9800,7 +10197,7 @@ class TestOther : public TestFixture { "{\n" " const int a(getA());\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("class A{\n" @@ -9812,7 +10209,7 @@ class TestOther : public TestFixture { "{\n" " const A a = getA() + 1;\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("class A{\n" @@ -9824,7 +10221,7 @@ class TestOther : public TestFixture { "{\n" " const A a(getA()+1);\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #5190 - FP when creating object with constructor that takes a reference @@ -9833,7 +10230,7 @@ class TestOther : public TestFixture { "const A &getA();\n" "void f() {\n" " const B b(getA());\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("class A {};\n" @@ -9841,7 +10238,7 @@ class TestOther : public TestFixture { "const A& getA();\n" "void f() {\n" " const B b{ getA() };\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #5618 @@ -9854,7 +10251,7 @@ class TestOther : public TestFixture { " const std::string temp = tok->str();\n" " tok->str(tok->strAt(2));\n" " }\n" - "}"; + "}\n"; check(code5618); ASSERT_EQUALS("", errout_str()); check(code5618, dinit(CheckOptions, $.inconclusive = false)); @@ -9865,7 +10262,7 @@ class TestOther : public TestFixture { "X f(const X &in) {\n" " const X s = f(in);\n" " return f(s);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #7981 - False positive redundantCopyLocalConst - const ref argument to ctor @@ -9877,7 +10274,7 @@ class TestOther : public TestFixture { " \n" "void foo() {\n" " const CD cd(CD::getOne());\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct S {\n" // #10545 @@ -9890,7 +10287,7 @@ class TestOther : public TestFixture { " if (i != 0)\n" " return old;\n" " return {};\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct X { int x; };\n" // #10191 @@ -9986,9 +10383,7 @@ class TestOther : public TestFixture { " u.g();\n" " if (c == m->get()) {}\n" "}\n"); - TODO_ASSERT_EQUALS("", - "[test.cpp:16:33] -> [test.cpp:18:11]: (style) The comparison 'c == m->get()' is always true because 'c' and 'm->get()' represent the same value. [knownConditionTrueFalse]\n", - errout_str()); + ASSERT_EQUALS("", errout_str()); check("struct S {\n" // #12925 " const std::string & f() const { return str; }\n" @@ -10074,62 +10469,62 @@ class TestOther : public TestFixture { "{\n" " int a; a = 123;\n" " (void)(a << -1);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:13]: (error) Shifting by a negative value is undefined behaviour [shiftNegative]\n", errout_str()); check("void foo()\n" "{\n" " int a; a = 123;\n" " (void)(a >> -1);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:13]: (error) Shifting by a negative value is undefined behaviour [shiftNegative]\n", errout_str()); check("void foo()\n" "{\n" " int a; a = 123;\n" " a <<= -1;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:6]: (error) Shifting by a negative value is undefined behaviour [shiftNegative]\n", errout_str()); check("void foo()\n" "{\n" " int a; a = 123;\n" " a >>= -1;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:6]: (error) Shifting by a negative value is undefined behaviour [shiftNegative]\n", errout_str()); check("void foo()\n" "{\n" " std::cout << -1;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo()\n" "{\n" " std::cout << a << -1 ;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo()\n" "{\n" " std::cout << 3 << -1 ;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" " x = (-10+2) << 3;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:16]: (portability) Shifting a negative value is technically undefined behaviour [shiftNegativeLHS]\n", errout_str()); - check("x = y ? z << $-1 : 0;"); + check("x = y ? z << $-1 : 0;\n"); ASSERT_EQUALS("", errout_str()); // Negative LHS - check("const int x = -1 >> 2;"); + check("const int x = -1 >> 2;\n"); ASSERT_EQUALS("[test.cpp:1:18]: (portability) Shifting a negative value is technically undefined behaviour [shiftNegativeLHS]\n", errout_str()); // #6383 - unsigned type - check("const int x = (unsigned int)(-1) >> 2;"); + check("const int x = (unsigned int)(-1) >> 2;\n"); ASSERT_EQUALS("", errout_str()); // #7814 - UB happening in valueflowcode when it tried to compute shifts. check("int shift1() { return 1 >> -1 ;}\n" "int shift2() { return 1 << -1 ;}\n" "int shift3() { return -1 >> 1 ;}\n" - "int shift4() { return -1 << 1 ;}"); + "int shift4() { return -1 << 1 ;}\n"); ASSERT_EQUALS("[test.cpp:1:25]: (error) Shifting by a negative value is undefined behaviour [shiftNegative]\n" "[test.cpp:2:25]: (error) Shifting by a negative value is undefined behaviour [shiftNegative]\n" "[test.cpp:3:26]: (portability) Shifting a negative value is technically undefined behaviour [shiftNegativeLHS]\n" @@ -10172,13 +10567,31 @@ class TestOther : public TestFixture { ASSERT_EQUALS("", errout_str()); } + void checkNegativeShiftErrorPath() { + setMultiline(); + Settings s = settings1; + s.templateLocation = "{file}:{line}:note:{info}\n"; + + check("int f(int i, bool b) {\n" + " int s = -1;\n" + " if (b)\n" + " return i;\n" + " return i << s;\n" + "}\n", dinit(CheckOptions, $.settings = &s)); + ASSERT_EQUALS("[test.cpp:5:14]: warning: Shifting by a negative value is undefined behaviour [shiftNegative]\n" + "[test.cpp:2:14]: note: Assignment 's=-1', assigned value is -1\n" + "[test.cpp:3:9]: note: Assuming condition is false\n" + "[test.cpp:5:14]: note: Shifting by a negative value is undefined behaviour\n", + errout_str()); + } + void incompleteArrayFill() { check("void f() {\n" " int a[5];\n" " memset(a, 123, 5);\n" " memcpy(a, b, 5);\n" " memmove(a, b, 5);\n" - "}"); + "}\n"); ASSERT_EQUALS(// TODO "[test.cpp:4] -> [test.cpp:5]: (performance) Buffer 'a' is being written before its old content has been used.\n" "[test.cpp:3:5]: (warning, inconclusive) Array 'a' is filled incompletely. Did you forget to multiply the size given to 'memset()' with 'sizeof(*a)'? [incompleteArrayFill]\n" "[test.cpp:4:5]: (warning, inconclusive) Array 'a' is filled incompletely. Did you forget to multiply the size given to 'memcpy()' with 'sizeof(*a)'? [incompleteArrayFill]\n" @@ -10189,7 +10602,7 @@ class TestOther : public TestFixture { "void f() {\n" " memset(::a, 123, 5);\n" " memset(Z::b.a, 123, 5);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:4:5]: (warning, inconclusive) Array '::a' is filled incompletely. Did you forget to multiply the size given to 'memset()' with 'sizeof(*::a)'? [incompleteArrayFill]\n" "[test.cpp:5]: (warning, inconclusive) Array 'Z::b.a' is filled incompletely. Did you forget to multiply the size given to 'memset()' with 'sizeof(*Z::b.a)'?\n", "[test.cpp:4:5]: (warning, inconclusive) Array '::a' is filled incompletely. Did you forget to multiply the size given to 'memset()' with 'sizeof(*::a)'? [incompleteArrayFill]\n", errout_str()); @@ -10197,45 +10610,45 @@ class TestOther : public TestFixture { check("void f() {\n" " Foo* a[5];\n" " memset(a, 'a', 5);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (warning, inconclusive) Array 'a' is filled incompletely. Did you forget to multiply the size given to 'memset()' with 'sizeof(*a)'? [incompleteArrayFill]\n", errout_str()); check("class Foo {int a; int b;};\n" "void f() {\n" " Foo a[5];\n" " memset(a, 'a', 5);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:5]: (warning, inconclusive) Array 'a' is filled incompletely. Did you forget to multiply the size given to 'memset()' with 'sizeof(*a)'? [incompleteArrayFill]\n", errout_str()); check("void f() {\n" " Foo a[5];\n" // Size of foo is unknown " memset(a, 'a', 5);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " char a[5];\n" " memset(a, 'a', 5);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " int a[5];\n" " memset(a+15, 'a', 5);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " bool a[5];\n" " memset(a, false, 5);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (portability, inconclusive) Array 'a' might be filled incompletely. Did you forget to multiply the size given to 'memset()' with 'sizeof(*a)'? [incompleteArrayFill]\n", errout_str()); check("void f() {\n" " const int n = 5;" " int a[n];\n" " memset(a, 0, n);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (warning, inconclusive) Array 'a' is filled incompletely. Did you forget to multiply the size given to 'memset()' with 'sizeof(*a)'? [incompleteArrayFill]\n", errout_str()); } @@ -10246,7 +10659,7 @@ class TestOther : public TestFixture { check("void f(int i) {\n" " i = 1;\n" " i = 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:7]: style: Variable 'i' is reassigned a value before the old one has been used. [redundantAssignment]\n" "[test.cpp:2:7]: note: i is assigned\n" "[test.cpp:3:7]: note: i is overwritten\n", errout_str()); @@ -10256,7 +10669,7 @@ class TestOther : public TestFixture { "void f() {\n" " i = 1;\n" " i = 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:7]: style: Variable 'i' is reassigned a value before the old one has been used. [redundantAssignment]\n" "[test.cpp:3:7]: note: i is assigned\n" "[test.cpp:4:7]: note: i is overwritten\n", errout_str()); @@ -10265,7 +10678,7 @@ class TestOther : public TestFixture { " int i;\n" " i = 1;\n" " i = 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:7]: style: Variable 'i' is reassigned a value before the old one has been used. [redundantAssignment]\n" "[test.cpp:3:7]: note: i is assigned\n" "[test.cpp:4:7]: note: i is overwritten\n", errout_str()); @@ -10274,14 +10687,14 @@ class TestOther : public TestFixture { " static int i;\n" " i = 1;\n" " i = 1;\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("error", "", errout_str()); check("void f() {\n" " int i[10];\n" " i[2] = 1;\n" " i[2] = 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:10]: style: Variable 'i[2]' is reassigned a value before the old one has been used. [redundantAssignment]\n" "[test.cpp:3:10]: note: i[2] is assigned\n" "[test.cpp:4:10]: note: i[2] is overwritten\n", errout_str()); @@ -10291,14 +10704,14 @@ class TestOther : public TestFixture { " i[x] = 1;\n" " x=1;\n" " i[x] = 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(const int x) {\n" " int i[10];\n" " i[x] = 1;\n" " i[x] = 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:10]: style: Variable 'i[x]' is reassigned a value before the old one has been used. [redundantAssignment]\n" "[test.cpp:3:10]: note: i[x] is assigned\n" "[test.cpp:4:10]: note: i[x] is overwritten\n", errout_str()); @@ -10307,14 +10720,14 @@ class TestOther : public TestFixture { check("void f() {\n" " Foo& bar = foo();\n" " bar = x;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " Foo& bar = foo();\n" " bar = x;\n" " bar = y;\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("error", "", errout_str()); check("void f() {\n" @@ -10322,7 +10735,7 @@ class TestOther : public TestFixture { " bar = y();\n" " foo();\n" " bar = y();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Tests with function call between assignment @@ -10330,7 +10743,7 @@ class TestOther : public TestFixture { " i = 1;\n" " bar();\n" " i = 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:7]: style: Variable 'i' is reassigned a value before the old one has been used. [redundantAssignment]\n" "[test.cpp:2:7]: note: i is assigned\n" "[test.cpp:4:7]: note: i is overwritten\n", errout_str()); @@ -10340,7 +10753,7 @@ class TestOther : public TestFixture { " i = 1;\n" " bar();\n" // Global variable might be accessed in bar() " i = 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -10348,7 +10761,7 @@ class TestOther : public TestFixture { " i = 1;\n" " bar();\n" // bar() might call f() recursively. This could be a false positive in more complex examples (when value of i is used somewhere. See #4229) " i = 2;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -10356,7 +10769,7 @@ class TestOther : public TestFixture { " i = 1;\n" " bar();\n" " i = 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:7]: style: Variable 'i' is reassigned a value before the old one has been used. [redundantAssignment]\n" "[test.cpp:3:7]: note: i is assigned\n" "[test.cpp:5:7]: note: i is overwritten\n", errout_str()); @@ -10366,14 +10779,14 @@ class TestOther : public TestFixture { " i = 1;\n" " bar(i);\n" // Passed as argument " i = 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " Foo bar = foo();\n" " bar();\n" // #5568. operator() called " bar = y();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Branch tests @@ -10381,7 +10794,7 @@ class TestOther : public TestFixture { " i = 1;\n" " if(x)\n" " i = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int i) {\n" @@ -10389,7 +10802,7 @@ class TestOther : public TestFixture { " i = 0;\n" " i = 1;\n" " i = 2;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:7]: style: Variable 'i' is reassigned a value before the old one has been used. [redundantAssignment]\n" "[test.cpp:4:7]: note: i is assigned\n" "[test.cpp:5:7]: note: i is overwritten\n", errout_str()); @@ -10402,7 +10815,7 @@ class TestOther : public TestFixture { "void f() {\n" " x = 2;\n" " x = g();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int g() {\n" @@ -10411,7 +10824,7 @@ class TestOther : public TestFixture { "void f(int x) {\n" " x = 2;\n" " x = g();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:7]: style: Variable 'x' is reassigned a value before the old one has been used. [redundantAssignment]\n" "[test.cpp:5:7]: note: x is assigned\n" "[test.cpp:6:7]: note: x is overwritten\n", errout_str()); @@ -10420,7 +10833,7 @@ class TestOther : public TestFixture { " Foo& bar = foo();\n" " bar = x;\n" " bar = y();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("class C {\n" @@ -10432,7 +10845,7 @@ class TestOther : public TestFixture { "void C::f() {\n" " x = 2;\n" " x = g();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("class C {\n" @@ -10444,7 +10857,7 @@ class TestOther : public TestFixture { "void C::f(Foo z) {\n" " x = 2;\n" " x = z.g();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // ({ }) @@ -10452,7 +10865,7 @@ class TestOther : public TestFixture { " int x;\n" " x = 321;\n" " x = ({ asm(123); })\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // from #3103 (avoid a false negative) @@ -10461,7 +10874,7 @@ class TestOther : public TestFixture { " x = 1;\n" " x = 1;\n" " return x + 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:7]: style: Variable 'x' is reassigned a value before the old one has been used. [redundantAssignment]\n" "[test.cpp:3:7]: note: x is assigned\n" "[test.cpp:4:7]: note: x is overwritten\n", errout_str()); @@ -10473,20 +10886,20 @@ class TestOther : public TestFixture { " if (y)\n" // <-- cppcheck does not know anything about 'y' " x = 2;\n" " return x + 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // initialization, assignment with 0 check("void f() {\n" // Ticket #4356 " int x = 0;\n" // <- ignore initialization with 0 " x = 3;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " state_t *x = NULL;\n" " x = dostuff();\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:2:12]: style: Variable 'x' can be declared as pointer to const [constVariablePointer]\n", errout_str()); @@ -10495,7 +10908,7 @@ class TestOther : public TestFixture { " state_t *x;\n" " x = NULL;\n" " x = dostuff();\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:2:12]: style: Variable 'x' can be declared as pointer to const [constVariablePointer]\n", errout_str()); @@ -10505,7 +10918,7 @@ class TestOther : public TestFixture { " bar(++x);\n" " x = 5;\n" " return bar(x);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // struct member.. @@ -10516,7 +10929,7 @@ class TestOther : public TestFixture { " ab.a = 1;\n" " ab.a = 2;\n" " return ab.a;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:10]: style: Variable 'ab.a' is reassigned a value before the old one has been used. [redundantAssignment]\n" "[test.cpp:5:10]: note: ab.a is assigned\n" "[test.cpp:6:10]: note: ab.a is overwritten\n", errout_str()); @@ -10528,7 +10941,7 @@ class TestOther : public TestFixture { " ab.a = 1;\n" " ab = do_something();\n" " return ab.a;\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("error", "", errout_str()); check("struct AB { int a; int b; };\n" @@ -10539,7 +10952,7 @@ class TestOther : public TestFixture { " do_something(&ab);\n" " ab.a = 2;\n" " return ab.a;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct AB { int a; int b; };\n" @@ -10550,7 +10963,7 @@ class TestOther : public TestFixture { " do_something(&ab);\n" " ab.a = 2;\n" " return ab.a;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct AB { int a; int b; };\n" @@ -10561,7 +10974,7 @@ class TestOther : public TestFixture { " ab++;\n" " ab->a = 1;\n" " ab->b = 2;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct AB { int a; int b; };\n" @@ -10572,20 +10985,20 @@ class TestOther : public TestFixture { " ab = x;\n" " ab->a = 1;\n" " ab->b = 2;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(struct AB *ab) {\n" // # " ab->data->x = 1;\n" " ab = &ab1;\n" " ab->data->x = 2;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #5964 check("void func(char *buffer, const char *format, int precision, unsigned value) {\n" " (precision < 0) ? sprintf(buffer, format, value) : sprintf(buffer, format, precision, value);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // don't crash @@ -10598,14 +11011,14 @@ class TestOther : public TestFixture { "void func(struct state *s) {\n" " s->foo[s->x++] = 2;\n" " s->d[1].fc.i++;\n" - "}"); + "}\n"); // #6525 - inline assembly check("void f(int i) {\n" " i = 1;\n" " asm(\"foo\");\n" " i = 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #6555 @@ -10618,7 +11031,7 @@ class TestOther : public TestFixture { " catch (...) {\n" " barney(p);\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo() {\n" @@ -10630,7 +11043,7 @@ class TestOther : public TestFixture { " catch (...) {\n" " barney(x);\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11]: style: The scope of the variable 'p' can be reduced. [variableScope]\n" "[test.cpp:2:11]: style: Variable 'p' can be declared as pointer to const [constVariablePointer]\n", errout_str()); @@ -10646,7 +11059,7 @@ class TestOther : public TestFixture { " catch (...) {\n" " barney(p);\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Member variable pointers @@ -10656,7 +11069,7 @@ class TestOther : public TestFixture { " memptr = &POD::b;\n" " if (memptr)\n" " memptr = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:12]: style: Variable 'memptr' is reassigned a value before the old one has been used. [redundantAssignment]\n" "[test.cpp:3:12]: note: memptr is assigned\n" "[test.cpp:4:12]: note: memptr is overwritten\n", errout_str()); @@ -10666,7 +11079,7 @@ class TestOther : public TestFixture { "{\n" " var[0] = 0.2f;\n" " var[0] = 0.2f;\n" // <-- is initialized twice - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:10]: style: Variable 'var[0]' is reassigned a value before the old one has been used. [redundantAssignment]\n" "[test.cpp:3:10]: note: var[0] is assigned\n" "[test.cpp:4:10]: note: var[0] is overwritten\n", errout_str()); @@ -10675,7 +11088,7 @@ class TestOther : public TestFixture { "{\n" " *var = 0.2f;\n" " *var = 0.2f;\n" // <-- is initialized twice - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:8]: style: Variable '*var' is reassigned a value before the old one has been used. [redundantAssignment]\n" "[test.cpp:3:8]: note: *var is assigned\n" "[test.cpp:4:8]: note: *var is overwritten\n", errout_str()); @@ -10685,7 +11098,7 @@ class TestOther : public TestFixture { " volatile char *reg = (volatile char *)0x12345;\n" " *reg = 12;\n" " *reg = 34;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(std::map& m, int key, int value) {\n" // #6379 @@ -10702,28 +11115,28 @@ class TestOther : public TestFixture { check("void f() {\n" " int a = 0;\n" " a = 4;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " int a;\n" " a = 0;\n" " a = 4;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " unsigned a;\n" " a = 0u;\n" " a = 2u;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " void* a;\n" " a = (void*)0;\n" " a = p;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:2:10]: (style) Variable 'a' can be declared as pointer to const [constVariablePointer]\n", errout_str()); @@ -10732,7 +11145,7 @@ class TestOther : public TestFixture { " void* a;\n" " a = (void*)0U;\n" " a = p;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:2:10]: (style) Variable 'a' can be declared as pointer to const [constVariablePointer]\n", errout_str()); @@ -10746,7 +11159,7 @@ class TestOther : public TestFixture { " const char* p = \"abc\";\n" " p = \"def\";\n" " return p;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:2:19] -> [test.cpp:3:7]: (style) Redundant initialization for 's'. The initialized value is overwritten before it is read. [redundantInitialization]\n" "[test.cpp:7:19] -> [test.cpp:8:7]: (style) Redundant initialization for 'p'. The initialized value is overwritten before it is read. [redundantInitialization]\n", @@ -10762,7 +11175,7 @@ class TestOther : public TestFixture { " struct foo x;\n" " x.a = _mm_set1_ps(1.0);\n" " x.a = _mm_set1_ps(2.0);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:7] -> [test.cpp:8:7]: (style) Variable 'x.a' is reassigned a value before the old one has been used. [redundantAssignment]\n", errout_str()); check("void f() {\n" @@ -10770,13 +11183,13 @@ class TestOther : public TestFixture { " ab.x = 23;\n" " ab.y = 41;\n" " ab.x = 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:8] -> [test.cpp:5:8]: (style) Variable 'ab.x' is reassigned a value before the old one has been used. [redundantAssignment]\n", errout_str()); check("void f() {\n" " struct AB ab = {0};\n" " ab = foo();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -10797,7 +11210,7 @@ class TestOther : public TestFixture { " u.l1 = 1;\n" " lTotal += u.b.b1;\n" " u.l1 = 2;\n" //Should not show RedundantAssignment - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); // Ticket #5115 "redundantAssignment when using a union" @@ -10815,7 +11228,7 @@ class TestOther : public TestFixture { " } u;\n" " u.l1 = 1;\n" " u.l1 = 2;\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("[test.cpp:13:10] -> [test.cpp:14:10]: (style) Variable 'u.l1' is reassigned a value before the old one has been used. [redundantAssignment]\n", errout_str()); // Ticket #10093 "redundantAssignment when using a union" @@ -10839,7 +11252,7 @@ class TestOther : public TestFixture { " m.u16.ab = 47;\n" " m.u16.cd = 0;\n" " m.u16.ab = m.u32.abcd / 53;\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); // Ticket #10093 "redundantAssignment when using a union" @@ -10857,7 +11270,7 @@ class TestOther : public TestFixture { " u.as_int = 42;\n" " fn(&u.as_char[0], 4);\n" " u.as_int = 0;\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); // Ticket #5115 "redundantAssignment when using a union" @@ -10868,7 +11281,7 @@ class TestOther : public TestFixture { " } addr;\n" " addr.s8 = ptr;\n" " addr.u64 += 8;\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); check("struct S {\n" // #12895 @@ -10882,6 +11295,48 @@ class TestOther : public TestFixture { " Dst.s->y = Src.s->y;\n" "}\n"); ASSERT_EQUALS("", errout_str()); + + // Ticket #14371 "redundantAssignment when using a union" + check("union U {\n" + " struct {\n" + " unsigned int abcd;\n" + " } u32;\n" + " struct {\n" + " unsigned short ab;\n" + " unsigned short cd;\n" + " } u16;\n" + "};\n" + "void f1() {\n" + " U m;\n" + " m.u32.abcd = 1234;\n" + " m.u32.abcd = 5 * m.u16.ab;\n" + "}\n" + "void f2(unsigned int a, unsigned short b) {\n" + " U m;\n" + " m.u32.abcd = a;\n" + " m.u32.abcd += 0x8000;\n" + " m.u32.abcd = m.u16.ab * b;\n" + "}\n" + "void f3(unsigned int seed) {\n" + " U m, other;\n" + " other.u32.abcd = seed;\n" + " m.u32.abcd = 1234;\n" + " m.u32.abcd = other.u16.ab * 2;\n" + "}\n" + "void f4(unsigned short x) {\n" + " U m;\n" + " m.u16.ab = x;\n" + " m.u16.cd = 0;\n" + " m.u16.ab = m.u32.abcd / 53;\n" + "}\n" + "void f5(unsigned short x, unsigned int y) {\n" + " U m;\n" + " m.u16.ab = x;\n" + " m.u16.cd = 0;\n" + " m.u16.ab = y;\n" + "}\n", dinit(CheckOptions, $.inconclusive = false)); + ASSERT_EQUALS("[test.cpp:24:16] -> [test.cpp:25:16]: (style) Variable 'm.u32.abcd' is reassigned a value before the old one has been used. [redundantAssignment]\n" + "[test.cpp:35:14] -> [test.cpp:37:14]: (style) Variable 'm.u16.ab' is reassigned a value before the old one has been used. [redundantAssignment]\n", errout_str()); } void redundantVarAssignment_7133() { @@ -10903,7 +11358,7 @@ class TestOther : public TestFixture { " }\n" " catch (const uno::Exception&) {\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void ConvertBitmapData(sal_uInt16 nDestBits) {\n" @@ -10912,7 +11367,7 @@ class TestOther : public TestFixture { " BitmapBuffer aDstBuf;\n" " aSrcBuf.mnBitCount = nDestBits;\n" " bConverted = ::ImplFastBitmapConversion( aDstBuf, aSrcBuf, aTwoRects );\n" - "}", dinit(CheckOptions, $.cpp = false)); + "}\n", dinit(CheckOptions, $.cpp = false)); ASSERT_EQUALS("[test.c:3:24] -> [test.c:5:24]: (style) Variable 'aSrcBuf.mnBitCount' is reassigned a value before the old one has been used. [redundantAssignment]\n", errout_str()); check("void ConvertBitmapData(sal_uInt16 nDestBits) {\n" " BitmapBuffer aSrcBuf;\n" @@ -10920,7 +11375,7 @@ class TestOther : public TestFixture { " BitmapBuffer aDstBuf;\n" " aSrcBuf.mnBitCount = nDestBits;\n" " bConverted = ::ImplFastBitmapConversion( aDstBuf, aSrcBuf, aTwoRects );\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:24] -> [test.cpp:5:24]: (style) Variable 'aSrcBuf.mnBitCount' is reassigned a value before the old one has been used. [redundantAssignment]\n", errout_str()); @@ -10929,7 +11384,7 @@ class TestOther : public TestFixture { " C c;\n" " c = x;\n" " c = x;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:7] -> [test.cpp:5:7]: (style, inconclusive) Variable 'c' is reassigned a value before the old one has been used if variable is no semaphore variable. [redundantAssignment]\n", errout_str()); } @@ -10943,7 +11398,7 @@ class TestOther : public TestFixture { " m->prev->next = m->next;\n" " m->next->prev = m->prev;\n" " return m->next;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -10957,7 +11412,7 @@ class TestOther : public TestFixture { " x = 6;\n" " f();\n" " return y;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #10228 @@ -10977,7 +11432,7 @@ class TestOther : public TestFixture { " for (i = 0; i < 4; i++)\n" " buf[i] = 131;\n" " buf[i] = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void bar() {\n" // #9262 do-while with break @@ -10987,20 +11442,20 @@ class TestOther : public TestFixture { " if (foo()) break;\n" " x = 1;\n" " } while (false);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int num) {\n" // #9420 FP " int a = num;\n" " for (int b = 0; b < num; a = b++)\n" " dostuff(a);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(int num) {\n" // #9420 FN " int a = num;\n" " for (int b = 0; b < num; a = b++);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("error", "", errout_str()); } @@ -11013,7 +11468,7 @@ class TestOther : public TestFixture { " break;\n" " }\n" " ret = 3;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:13] -> [test.cpp:8:9]: (style) Variable 'ret' is reassigned a value before the old one has been used. [redundantAssignment]\n", errout_str()); } @@ -11023,7 +11478,7 @@ class TestOther : public TestFixture { " *x = 23;\n" " foo(ptr);\n" " *x = 32;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #8997 @@ -11033,7 +11488,7 @@ class TestOther : public TestFixture { " *p = 1;\n" " p += 1;\n" " *p = 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -11042,7 +11497,7 @@ class TestOther : public TestFixture { " *p = 1;\n" " if (condition) return;\n" " *p = 2;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -11054,7 +11509,7 @@ class TestOther : public TestFixture { " i += 2;\n" " arr[i] = 3;\n" " dostuff(arr);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -11068,9 +11523,9 @@ class TestOther : public TestFixture { " if (b) break;\n" " ret = 1;\n" " break;\n" - " }" + " }\n" " return ret;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int a, int b) {\n" @@ -11081,8 +11536,8 @@ class TestOther : public TestFixture { " if (b) break;\n" " ret = 1;\n" " break;\n" - " }" - "}"); + " }\n" + "}\n"); ASSERT_EQUALS("[test.cpp:5:13] -> [test.cpp:7:13]: (style) Variable 'ret' is reassigned a value before the old one has been used. [redundantAssignment]\n", errout_str()); } @@ -11092,7 +11547,7 @@ class TestOther : public TestFixture { check("void f() {\n" " int err = -ENOMEM;\n" " err = dostuff();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:9]: style: Redundant initialization for 'err'. The initialized value is overwritten before it is read. [redundantInitialization]\n" "[test.cpp:2:13]: note: err is initialized\n" "[test.cpp:3:9]: note: err is overwritten\n", @@ -11101,7 +11556,7 @@ class TestOther : public TestFixture { check("void f() {\n" " struct S s = {1,2,3};\n" " s = dostuff();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:7]: style: Redundant initialization for 's'. The initialized value is overwritten before it is read. [redundantInitialization]\n" "[test.cpp:2:16]: note: s is initialized\n" "[test.cpp:3:7]: note: s is overwritten\n", @@ -11110,7 +11565,7 @@ class TestOther : public TestFixture { check("void f() {\n" " int *p = NULL;\n" " p = dostuff();\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:2:10]: style: Variable 'p' can be declared as pointer to const [constVariablePointer]\n", errout_str()); @@ -11119,14 +11574,14 @@ class TestOther : public TestFixture { check("void f() {\n" " struct S s = {0};\n" " s = dostuff();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("namespace N { enum E {e0,e1}; }\n" "void f() {\n" " N::E e = N::e0;\n" // #9261 " e = dostuff();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" // #10143 @@ -11142,6 +11597,87 @@ class TestOther : public TestFixture { " return i;\n" "}\n"); ASSERT_EQUALS("", errout_str()); + + check("int f(char c) {\n" // #15037 + " int i = (int)c;\n" + " i = 3;\n" + " return i;\n" + "}\n" + "int g(char c) {\n" + " int i = static_cast(c);\n" + " i = 3;\n" + " return i;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:3:7]: style: Redundant initialization for 'i'. The initialized value is overwritten before it is read. [redundantInitialization]\n" + "[test.cpp:2:11]: note: i is initialized\n" + "[test.cpp:3:7]: note: i is overwritten\n" + "[test.cpp:8:8]: style: Redundant initialization for 'i'. The initialized value is overwritten before it is read. [redundantInitialization]\n" + "[test.cpp:7:12]: note: i is initialized\n" + "[test.cpp:8:8]: note: i is overwritten\n", errout_str()); + + check("int g();\n" // #15023 + "int f1() {\n" + " int i{ g() };\n" + " i = 0;\n" + " return i;\n" + "}\n" + "int f2() {\n" + " int i(g());\n" + " i = 0;\n" + " return i;\n" + "}\n" + "int f3() {\n" + " int i{ 1 };\n" + " i = 0;\n" + " return i;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:4:7]: style: Redundant initialization for 'i'. The initialized value is overwritten before it is read. [redundantInitialization]\n" + "[test.cpp:3:10]: note: i is initialized\n" + "[test.cpp:4:7]: note: i is overwritten\n" + "[test.cpp:9:7]: style: Redundant initialization for 'i'. The initialized value is overwritten before it is read. [redundantInitialization]\n" + "[test.cpp:8:10]: note: i is initialized\n" + "[test.cpp:9:7]: note: i is overwritten\n", + errout_str()); + + check("struct S { int a, b; };\n" // #15021 + "int f(S s) {\n" + " auto [x, y] = s;\n" + " x = 1;\n" + " y = 2;\n" + " return x + y;\n" + "}\n" + "struct T { int c; };\n" + "int g(T t) {\n" + " auto [z] = t;\n" + " z = 0;\n" + " return z;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:4:7]: style: Redundant initialization for 'x'. The initialized value is overwritten before it is read. [redundantInitialization]\n" + "[test.cpp:3:17]: note: x is initialized\n" + "[test.cpp:4:7]: note: x is overwritten\n" + "[test.cpp:5:7]: style: Redundant initialization for 'y'. The initialized value is overwritten before it is read. [redundantInitialization]\n" + "[test.cpp:3:17]: note: y is initialized\n" + "[test.cpp:5:7]: note: y is overwritten\n" + "[test.cpp:11:7]: style: Redundant initialization for 'z'. The initialized value is overwritten before it is read. [redundantInitialization]\n" + "[test.cpp:10:14]: note: z is initialized\n" + "[test.cpp:11:7]: note: z is overwritten\n", + errout_str()); + + check("struct S {\n" + " int a, b;\n" + "};\n" + "void f(S& s) {\n" + " auto& [x, y] = s;\n" + " y = 1;\n" + "}\n" + "void f(S s) {\n" + " auto [x, y] = s;\n" + " y = 1;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:10:7]: style: Redundant initialization for 'y'. The initialized value is overwritten before it is read. [redundantInitialization]\n" + "[test.cpp:9:17]: note: y is initialized\n" + "[test.cpp:10:7]: note: y is overwritten\n", + errout_str()); } // cppcheck-suppress unusedPrivateFunction @@ -11151,21 +11687,21 @@ class TestOther : public TestFixture { " char a[10];\n" " memcpy(a, foo, bar);\n" " memset(a, 0, bar);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3] -> [test.cpp:4]: (performance) Buffer 'a' is being written before its old content has been used.\n", errout_str()); check("void f() {\n" " char a[10];\n" " strcpy(a, foo);\n" " strncpy(a, 0, bar);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3] -> [test.cpp:4]: (performance) Buffer 'a' is being written before its old content has been used.\n", errout_str()); check("void f() {\n" " char a[10];\n" " sprintf(a, \"foo\");\n" " memmove(a, 0, bar);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3] -> [test.cpp:4]: (performance) Buffer 'a' is being written before its old content has been used.\n", errout_str()); check("void f(char *filename) {\n" @@ -11173,14 +11709,14 @@ class TestOther : public TestFixture { " strcpy(p, \"foo\");\n" " dostuff(filename);\n" " strcpy(p, \"foo\");\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Writing to different parts of a buffer check("void f(void* a) {\n" " memcpy(a, foo, bar);\n" " memset(a+5, 0, bar);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Use variable as second argument @@ -11188,7 +11724,7 @@ class TestOther : public TestFixture { " memset(a, 0, 5);\n" " memcpy(b, a, 5);\n" " memset(a, 1, 5);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // strcat is special @@ -11197,7 +11733,7 @@ class TestOther : public TestFixture { " strcpy(a, foo);\n" " strcat(a, bar);\n" // Not redundant " strcpy(a, x);\n" // Redundant - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3] -> [test.cpp:5]: (performance) Buffer 'a' is being written before its old content has been used.\n", errout_str()); // Tests with function call between copy @@ -11206,7 +11742,7 @@ class TestOther : public TestFixture { " snprintf(a, foo, bar);\n" " bar();\n" " memset(a, 0, size);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3] -> [test.cpp:5]: (performance) Buffer 'a' is being written before its old content has been used.\n", errout_str()); check("void* a;\n" @@ -11214,7 +11750,7 @@ class TestOther : public TestFixture { " memset(a, 0, size);\n" " bar();\n" // Global variable might be accessed in bar() " memset(a, 0, size);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -11222,7 +11758,7 @@ class TestOther : public TestFixture { " memset(a, 0, size);\n" " bar();\n" " memset(a, 0, size);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("[test.cpp:3] -> [test.cpp:5]: (performance) Buffer 'a' is being written before its old content has been used.\n", "", errout_str()); check("void bar(void* a) {}\n" @@ -11230,7 +11766,7 @@ class TestOther : public TestFixture { " memset(a, 0, size);\n" " bar(a);\n" // Passed as argument " memset(a, 0, size);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // Branch tests @@ -11238,7 +11774,7 @@ class TestOther : public TestFixture { " memset(a, 0, size);\n" " if(x)\n" " memset(a, 0, size);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #4455 - initialization of local buffer @@ -11246,14 +11782,14 @@ class TestOther : public TestFixture { " char buf[10];\n" " memset(buf, 0, 10);\n" " strcpy(buf, string);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(void) {\n" " char buf[10] = {0};\n" " memset(buf, 0, 10);\n" " strcpy(buf, string);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3] -> [test.cpp:4]: (performance) Buffer 'buf' is being written before its old content has been used.\n", errout_str()); // #5689 - use return value of strcpy @@ -11261,7 +11797,7 @@ class TestOther : public TestFixture { " int i = atoi(strcpy(a, foo));\n" " strncpy(a, 0, bar);\n" " return i;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #7175 - read+write @@ -11270,7 +11806,7 @@ class TestOther : public TestFixture { " strcpy(buf, x);\n" " strcpy(buf, dostuff(buf));\n" // <- read + write " strcpy(buf, x);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -11278,7 +11814,7 @@ class TestOther : public TestFixture { " strcpy(buf, x);\n" " strcpy(buf, dostuff(buf));\n" " strcpy(buf, x);\n" - "}"); + "}\n"); TODO_ASSERT_EQUALS("error", "", errout_str()); } @@ -11341,23 +11877,24 @@ class TestOther : public TestFixture { " x = a + b;\n" " return x;\n" "}\n"); - ASSERT_EQUALS("[test.cpp:2:11] -> [test.cpp:4:9]: (style) Variable 'x' is assigned an expression that holds the same value. [redundantAssignment]\n", - errout_str()); + ASSERT_EQUALS( + "[test.cpp:2:11] -> [test.cpp:3:9] -> [test.cpp:4:9]: (style) Variable 'x' is assigned an expression that holds the same value. [redundantAssignment]\n", + errout_str()); } void varFuncNullUB() { // #4482 check("void a(...);\n" - "void b() { a(NULL); }"); + "void b() { a(NULL); }\n"); ASSERT_EQUALS("[test.cpp:2:13]: (portability) Passing NULL after the last typed argument to a variadic function leads to undefined behaviour. [varFuncNullUB]\n", errout_str()); check("void a(char *p, ...);\n" - "void b() { a(NULL, 2); }"); + "void b() { a(NULL, 2); }\n"); ASSERT_EQUALS("", errout_str()); checkP("extern const int sentinel;\n" "void a(int, ...);\n" "#define b(x, ...) a((x), __VA_ARGS__, &sentinel)\n" - "void c() { b(1, NULL); }"); + "void c() { b(1, NULL); }\n"); ASSERT_EQUALS("", errout_str()); } @@ -11371,7 +11908,7 @@ class TestOther : public TestFixture { " bar(c);\n" " c = getchar();\n" " } ;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:10]: (warning) Storing getchar() return value in char variable and then comparing with EOF. [checkCastIntToCharAndBack]\n", errout_str()); check("void f() {\n" @@ -11380,7 +11917,7 @@ class TestOther : public TestFixture { " {\n" " bar(c);\n" " } ;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:17]: (warning) Storing getchar() return value in char variable and then comparing with EOF. [checkCastIntToCharAndBack]\n", errout_str()); check("void f() {\n" @@ -11390,7 +11927,7 @@ class TestOther : public TestFixture { " bar(c);\n" " c = getchar();\n" " } ;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:17]: (warning) Storing getchar() return value in char variable and then comparing with EOF. [checkCastIntToCharAndBack]\n", errout_str()); check("void f() {\n" @@ -11398,7 +11935,7 @@ class TestOther : public TestFixture { " while( EOF != ( c = getchar() ) )\n" " {\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:19]: (warning) Storing getchar() return value in char variable and then comparing with EOF. [checkCastIntToCharAndBack]\n", errout_str()); check("void f() {\n" @@ -11408,7 +11945,7 @@ class TestOther : public TestFixture { " bar(i);\n" " i = getchar();\n" " } ;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -11418,7 +11955,7 @@ class TestOther : public TestFixture { " bar(i);\n" " i = getchar();\n" " } ;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); @@ -11428,7 +11965,7 @@ class TestOther : public TestFixture { "do {\n" " c = getc (pFile);\n" "} while (c != EOF);" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:10]: (warning) Storing getc() return value in char variable and then comparing with EOF. [checkCastIntToCharAndBack]\n", errout_str()); check("void f (FILE * pFile){\n" @@ -11436,7 +11973,7 @@ class TestOther : public TestFixture { "do {\n" " c = getc (pFile);\n" "} while (EOF != c);" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:17]: (warning) Storing getc() return value in char variable and then comparing with EOF. [checkCastIntToCharAndBack]\n", errout_str()); check("void f (FILE * pFile){\n" @@ -11444,7 +11981,7 @@ class TestOther : public TestFixture { "do {\n" " i = getc (pFile);\n" "} while (i != EOF);" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f (FILE * pFile){\n" @@ -11452,7 +11989,7 @@ class TestOther : public TestFixture { "do {\n" " i = getc (pFile);\n" "} while (EOF != i);" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); @@ -11462,7 +11999,7 @@ class TestOther : public TestFixture { "do {\n" " c = fgetc (pFile);\n" "} while (c != EOF);" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:10]: (warning) Storing fgetc() return value in char variable and then comparing with EOF. [checkCastIntToCharAndBack]\n", errout_str()); check("void f (FILE * pFile){\n" @@ -11470,7 +12007,7 @@ class TestOther : public TestFixture { "do {\n" " c = fgetc (pFile);\n" "} while (EOF != c);" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:17]: (warning) Storing fgetc() return value in char variable and then comparing with EOF. [checkCastIntToCharAndBack]\n", errout_str()); check("void f (FILE * pFile){\n" @@ -11478,7 +12015,7 @@ class TestOther : public TestFixture { "do {\n" " c = fgetc (pFile);\n" "} while (EOF != c);" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f (FILE * pFile){\n" @@ -11486,7 +12023,7 @@ class TestOther : public TestFixture { "do {\n" " i = fgetc (pFile);\n" "} while (i != EOF);" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f (FILE * pFile){\n" @@ -11494,7 +12031,7 @@ class TestOther : public TestFixture { "do {\n" " i = fgetc (pFile);\n" "} while (EOF != i);" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // cin.get() @@ -11504,7 +12041,7 @@ class TestOther : public TestFixture { " std::cout << ch;\n" " ch = std::cin.get();\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:18]: (warning) Storing cin.get() return value in char variable and then comparing with EOF. [checkCastIntToCharAndBack]\n", errout_str()); check("void f(){\n" @@ -11513,7 +12050,7 @@ class TestOther : public TestFixture { " std::cout << ch;\n" " ch = std::cin.get();\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:11]: (warning) Storing cin.get() return value in char variable and then comparing with EOF. [checkCastIntToCharAndBack]\n", errout_str()); check("void f(){\n" @@ -11522,7 +12059,7 @@ class TestOther : public TestFixture { " std::cout << i;\n" " i = std::cin.get();\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(){\n" @@ -11531,7 +12068,7 @@ class TestOther : public TestFixture { " std::cout << i;\n" " i = std::cin.get();\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -11540,32 +12077,32 @@ class TestOther : public TestFixture { " if (a < 0)\n" " return a++,\n" " do_something();\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); TODO_ASSERT_EQUALS("[test.cpp:3]: (style) Comma is used in return statement. The comma can easily be misread as a ';'.\n", "", errout_str()); check("int fun(int a) {\n" " if (a < 0)\n" " return a++, do_something();\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); check("int fun(int a) {\n" " if (a < 0)\n" " return a+5,\n" " do_something();\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); TODO_ASSERT_EQUALS("[test.cpp:3]: (style) Comma is used in return statement. The comma can easily be misread as a ';'.\n", "", errout_str()); check("int fun(int a) {\n" " if (a < 0)\n" " return a+5, do_something();\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); check("int fun(int a) {\n" " if (a < 0)\n" " return c::b;\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); // #4943 take care of C++11 initializer lists @@ -11576,7 +12113,7 @@ class TestOther : public TestFixture { " { \"2\" },\n" " { \"3\" }\n" " };\n" - "}", dinit(CheckOptions, $.inconclusive = false)); + "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); } @@ -11592,7 +12129,7 @@ class TestOther : public TestFixture { " explicit B(A a) : a(std::move(a)) {}\n" " void Init(A _a) { a = std::move(_a); }\n" " A a;" - "};", dinit(CheckOptions, $.inconclusive = false)); + "};\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); check("struct A\n" @@ -11605,7 +12142,7 @@ class TestOther : public TestFixture { " explicit B(A a) : a{std::move(a)} {}\n" " void Init(A _a) { a = std::move(_a); }\n" " A a;" - "};", dinit(CheckOptions, $.inconclusive = false)); + "};\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); check("struct A\n" @@ -11619,7 +12156,7 @@ class TestOther : public TestFixture { " void Init(A _a) { a = std::move(_a); }\n" " A a;" " A a2;" - "};", dinit(CheckOptions, $.inconclusive = false)); + "};\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); check("struct A\n" @@ -11633,7 +12170,7 @@ class TestOther : public TestFixture { " void Init(A _a) { a = std::move(_a); }\n" " A a;" " A a2;" - "};", dinit(CheckOptions, $.inconclusive = false)); + "};\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("[test.cpp:8:14]: (performance) Function parameter 'a2' should be passed by const reference. [passedByValue]\n", errout_str()); check("struct A\n" @@ -11647,11 +12184,11 @@ class TestOther : public TestFixture { " void Init(A _a) { a = std::move(_a); }\n" " A a;" " A a2;" - "};", dinit(CheckOptions, $.inconclusive = false)); + "};\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("[test.cpp:8:14]: (performance) Function parameter 'a2' should be passed by const reference. [passedByValue]\n", errout_str()); check("std::map m;\n" // #10817 - "void f(const decltype(m)::const_iterator i) {}"); + "void f(const decltype(m)::const_iterator i) {}\n"); ASSERT_EQUALS("", errout_str()); check("int (*pf) (std::vector) = nullptr;\n" // #12118 @@ -11694,7 +12231,7 @@ class TestOther : public TestFixture { "};\n" "T f(T t) {\n" " return t;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // don't crash Settings settingsUnix32 = settingsBuilder().platform(Platform::Type::Unix32).build(); @@ -11711,33 +12248,33 @@ class TestOther : public TestFixture { // positive test check("bool f(int x){\n" " return isless(x,x);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11]: (warning) Comparison of two identical variables with isless(x,x) always evaluates to false. [comparisonFunctionIsAlwaysTrueOrFalse]\n", errout_str()); check("bool f(int x){\n" " return isgreater(x,x);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11]: (warning) Comparison of two identical variables with isgreater(x,x) always evaluates to false. [comparisonFunctionIsAlwaysTrueOrFalse]\n", errout_str()); check("bool f(int x){\n" " return islessgreater(x,x);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11]: (warning) Comparison of two identical variables with islessgreater(x,x) always evaluates to false. [comparisonFunctionIsAlwaysTrueOrFalse]\n", errout_str()); check("bool f(int x){\n" " return islessequal(x,x);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11]: (warning) Comparison of two identical variables with islessequal(x,x) always evaluates to true. [comparisonFunctionIsAlwaysTrueOrFalse]\n", errout_str()); check("bool f(int x){\n" " return isgreaterequal(x,x);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:11]: (warning) Comparison of two identical variables with isgreaterequal(x,x) always evaluates to true. [comparisonFunctionIsAlwaysTrueOrFalse]\n", errout_str()); // no warning should be reported for check("bool f(int x, int y){\n" " return isgreaterequal(x,y) && islessequal(x,y) && islessgreater(x,y) && isgreater(x,y) && isless(x,y);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -11745,7 +12282,7 @@ class TestOther : public TestFixture { // no signed integer overflow should happen check("void f(unsigned long long ull) {\n" " if (ull == 0x89504e470d0a1a0a || ull == 0x8a4d4e470d0a1a0a) ;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -11840,7 +12377,7 @@ class TestOther : public TestFixture { void test_isSameExpression() { // see #5738 check("bool isInUnoIncludeFile(StringRef name) {" " return name.startswith(SRCDIR \"/com/\") || name.startswith(SRCDIR \"/uno/\");\n" - "};", dinit(CheckOptions, $.inconclusive = false)); + "};\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); } @@ -11849,7 +12386,7 @@ class TestOther : public TestFixture { " int counter = 0;\n" " InterlockedDecrement(&counter);\n" " whatever();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); checkInterlockedDecrement("void f() {\n" @@ -11858,7 +12395,7 @@ class TestOther : public TestFixture { " if (counter)\n" " return;\n" " destroy();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:9]: (error) Race condition: non-interlocked access after InterlockedDecrement(). Use InterlockedDecrement() return value instead. [raceAfterInterlockedDecrement]\n", errout_str()); checkInterlockedDecrement("void f() {\n" @@ -11866,7 +12403,7 @@ class TestOther : public TestFixture { " InterlockedDecrement(&counter);\n" " if (!counter)\n" " destroy();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:9]: (error) Race condition: non-interlocked access after InterlockedDecrement(). Use InterlockedDecrement() return value instead. [raceAfterInterlockedDecrement]\n", errout_str()); checkInterlockedDecrement("void f() {\n" @@ -11875,7 +12412,7 @@ class TestOther : public TestFixture { " if (counter > 0)\n" " return;\n" " destroy();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:9]: (error) Race condition: non-interlocked access after InterlockedDecrement(). Use InterlockedDecrement() return value instead. [raceAfterInterlockedDecrement]\n", errout_str()); checkInterlockedDecrement("void f() {\n" @@ -11884,7 +12421,7 @@ class TestOther : public TestFixture { " if (0 < counter)\n" " return;\n" " destroy();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:9]: (error) Race condition: non-interlocked access after InterlockedDecrement(). Use InterlockedDecrement() return value instead. [raceAfterInterlockedDecrement]\n", errout_str()); checkInterlockedDecrement("void f() {\n" @@ -11892,7 +12429,7 @@ class TestOther : public TestFixture { " InterlockedDecrement(&counter);\n" " if (counter == 0)\n" " destroy();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:9]: (error) Race condition: non-interlocked access after InterlockedDecrement(). Use InterlockedDecrement() return value instead. [raceAfterInterlockedDecrement]\n", errout_str()); checkInterlockedDecrement("void f() {\n" @@ -11900,7 +12437,7 @@ class TestOther : public TestFixture { " InterlockedDecrement(&counter);\n" " if (0 == counter)\n" " destroy();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:9]: (error) Race condition: non-interlocked access after InterlockedDecrement(). Use InterlockedDecrement() return value instead. [raceAfterInterlockedDecrement]\n", errout_str()); checkInterlockedDecrement("void f() {\n" @@ -11909,7 +12446,7 @@ class TestOther : public TestFixture { " if (0 != counter)\n" " return;\n" " destroy()\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:9]: (error) Race condition: non-interlocked access after InterlockedDecrement(). Use InterlockedDecrement() return value instead. [raceAfterInterlockedDecrement]\n", errout_str()); checkInterlockedDecrement("void f() {\n" @@ -11918,7 +12455,7 @@ class TestOther : public TestFixture { " if (counter != 0)\n" " return;\n" " destroy()\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:9]: (error) Race condition: non-interlocked access after InterlockedDecrement(). Use InterlockedDecrement() return value instead. [raceAfterInterlockedDecrement]\n", errout_str()); checkInterlockedDecrement("void f() {\n" @@ -11926,7 +12463,7 @@ class TestOther : public TestFixture { " InterlockedDecrement(&counter);\n" " if (counter <= 0)\n" " destroy();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:9]: (error) Race condition: non-interlocked access after InterlockedDecrement(). Use InterlockedDecrement() return value instead. [raceAfterInterlockedDecrement]\n", errout_str()); checkInterlockedDecrement("void f() {\n" @@ -11934,7 +12471,7 @@ class TestOther : public TestFixture { " InterlockedDecrement(&counter);\n" " if (0 >= counter)\n" " destroy();\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:9]: (error) Race condition: non-interlocked access after InterlockedDecrement(). Use InterlockedDecrement() return value instead. [raceAfterInterlockedDecrement]\n", errout_str()); checkInterlockedDecrement("void f() {\n" @@ -11943,7 +12480,7 @@ class TestOther : public TestFixture { " if (newCount)\n" " return;\n" " destroy();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); checkInterlockedDecrement("void f() {\n" @@ -11951,7 +12488,7 @@ class TestOther : public TestFixture { " int newCount = InterlockedDecrement(&counter);\n" " if (!newCount)\n" " destroy();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); checkInterlockedDecrement("void f() {\n" @@ -11960,7 +12497,7 @@ class TestOther : public TestFixture { " if (newCount > 0)\n" " return;\n" " destroy();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); checkInterlockedDecrement("void f() {\n" @@ -11969,7 +12506,7 @@ class TestOther : public TestFixture { " if (0 < newCount)\n" " return;\n" " destroy();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); checkInterlockedDecrement("void f() {\n" @@ -11977,7 +12514,7 @@ class TestOther : public TestFixture { " int newCount = InterlockedDecrement(&counter);\n" " if (newCount == 0)\n" " destroy();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); checkInterlockedDecrement("void f() {\n" @@ -11985,7 +12522,7 @@ class TestOther : public TestFixture { " int newCount = InterlockedDecrement(&counter);\n" " if (0 == newCount)\n" " destroy();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); checkInterlockedDecrement("void f() {\n" @@ -11994,7 +12531,7 @@ class TestOther : public TestFixture { " if (0 != newCount)\n" " return;\n" " destroy()\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); checkInterlockedDecrement("void f() {\n" @@ -12003,7 +12540,7 @@ class TestOther : public TestFixture { " if (newCount != 0)\n" " return;\n" " destroy()\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); checkInterlockedDecrement("void f() {\n" @@ -12011,7 +12548,7 @@ class TestOther : public TestFixture { " int newCount = InterlockedDecrement(&counter);\n" " if (newCount <= 0)\n" " destroy();\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); checkInterlockedDecrement("void f() {\n" @@ -12019,7 +12556,7 @@ class TestOther : public TestFixture { " int newCount = InterlockedDecrement(&counter);\n" " if (0 >= newCount)\n" " destroy;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); checkInterlockedDecrement("int f() {\n" @@ -12030,7 +12567,7 @@ class TestOther : public TestFixture { " } else {\n" " return counter;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:16]: (error) Race condition: non-interlocked access after InterlockedDecrement(). Use InterlockedDecrement() return value instead. [raceAfterInterlockedDecrement]\n", errout_str()); checkInterlockedDecrement("int f() {\n" @@ -12041,7 +12578,7 @@ class TestOther : public TestFixture { " } else {\n" " return counter;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:16]: (error) Race condition: non-interlocked access after InterlockedDecrement(). Use InterlockedDecrement() return value instead. [raceAfterInterlockedDecrement]\n", errout_str()); @@ -12052,7 +12589,7 @@ class TestOther : public TestFixture { " return 0;\n" " }\n" " return counter;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:12]: (error) Race condition: non-interlocked access after InterlockedDecrement(). Use InterlockedDecrement() return value instead. [raceAfterInterlockedDecrement]\n", errout_str()); checkInterlockedDecrement("int f() {\n" @@ -12062,7 +12599,7 @@ class TestOther : public TestFixture { " return 0;\n" " }\n" " return counter;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:12]: (error) Race condition: non-interlocked access after InterlockedDecrement(). Use InterlockedDecrement() return value instead. [raceAfterInterlockedDecrement]\n", errout_str()); checkInterlockedDecrement("int f() {\n" @@ -12073,7 +12610,7 @@ class TestOther : public TestFixture { " } else\n" " return counter;\n" " \n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:16]: (error) Race condition: non-interlocked access after InterlockedDecrement(). Use InterlockedDecrement() return value instead. [raceAfterInterlockedDecrement]\n", errout_str()); checkInterlockedDecrement("int f() {\n" @@ -12084,21 +12621,21 @@ class TestOther : public TestFixture { " } else\n" " return counter;\n" " \n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:16]: (error) Race condition: non-interlocked access after InterlockedDecrement(). Use InterlockedDecrement() return value instead. [raceAfterInterlockedDecrement]\n", errout_str()); } void testUnusedLabel() { check("void f() {\n" " label:\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:5]: (style) Label 'label' is not used. [unusedLabel]\n", errout_str()); check("void f() {\n" " label:\n" " foo();\n" " goto label;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" @@ -12108,28 +12645,28 @@ class TestOther : public TestFixture { "}\n" "void g() {\n" " label:\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:5]: (style) Label 'label' is not used. [unusedLabel]\n", errout_str()); check("void f() {\n" " switch(a) {\n" " default:\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " class X {\n" " protected:\n" " };\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " class X {\n" " my_protected:\n" " };\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int test(char art) {\n" @@ -12141,7 +12678,7 @@ class TestOther : public TestFixture { " case 2:\n" " return 2;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (warning) Label 'caseZERO' is not used. Should this be a 'case' of the enclosing switch()? [unusedLabelSwitch]\n" "[test.cpp:5:5]: (warning) Label 'case1' is not used. Should this be a 'case' of the enclosing switch()? [unusedLabelSwitch]\n", errout_str()); @@ -12151,7 +12688,7 @@ class TestOther : public TestFixture { " return 2;\n" " }\n" " label:\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:5]: (style) Label 'label' is not used. [unusedLabel]\n", errout_str()); } @@ -12162,7 +12699,7 @@ class TestOther : public TestFixture { " goto END;\n" "#endif\n" "END:\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:1]: (style) Label 'END' is not used. There is #if in function body so the label might be used in code that is removed by the preprocessor. [unusedLabelConfiguration]\n", errout_str()); } @@ -12180,7 +12717,7 @@ class TestOther : public TestFixture { " END:\n" " return;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:10:5]: (warning) Label 'END' is not used. There is #if in function body so the label might be used in code that is removed by the preprocessor. Should this be a 'case' of the enclosing switch()? [unusedLabelSwitchConfiguration]\n", errout_str()); } @@ -12189,12 +12726,12 @@ class TestOther : public TestFixture { Settings s; check("void f() {\n" " label:\n" - "}", dinit(CheckOptions, $.settings = &s)); + "}\n", dinit(CheckOptions, $.settings = &s)); ASSERT_EQUALS("", errout_str()); - s.premiumArgs = "--premium=misra-c-2012"; // <- activates unusedLabel checking + s.premiumArgs = "--misra-c-2012"; // <- activates unusedLabel checking check("void f() {\n" " label:\n" - "}", dinit(CheckOptions, $.settings = &s)); + "}\n", dinit(CheckOptions, $.settings = &s)); ASSERT_EQUALS("[test.cpp:2:5]: (style) Label 'label' is not used. [unusedLabel]\n", errout_str()); } @@ -12206,32 +12743,32 @@ class TestOther : public TestFixture { SimpleTokenizer tokenizer(settings, *this, cpp); ASSERT_LOC(tokenizer.tokenize(code), file, line); - // Check.. - runChecks(tokenizer, this); + CheckOther check; + runChecks(check, tokenizer, *this); } void testEvaluationOrder() { check("void f() {\n" " int x = dostuff();\n" " return x + x++;\n" - "}", dinit(CheckOptions, $.cpp = false)); + "}\n", dinit(CheckOptions, $.cpp = false)); ASSERT_EQUALS("[test.c:3:12]: (error) Expression 'x+x++' depends on order of evaluation of side effects [unknownEvaluationOrder]\n", errout_str()); // #7226 check("long int f1(const char *exp) {\n" " return strtol(++exp, (char **)&exp, 10);\n" - "}", dinit(CheckOptions, $.cpp = false)); + "}\n", dinit(CheckOptions, $.cpp = false)); ASSERT_EQUALS("", errout_str()); check("long int f1(const char *exp) {\n" " return dostuff(++exp, exp, 10);\n" - "}", dinit(CheckOptions, $.cpp = false)); + "}\n", dinit(CheckOptions, $.cpp = false)); ASSERT_EQUALS("[test.c:2:23]: (error) Expression '++exp,exp' depends on order of evaluation of side effects [unknownEvaluationOrder]\n", errout_str()); check("void f() {\n" " int a;\n" " while (a=x(), a==123) {}\n" - "}", dinit(CheckOptions, $.cpp = false)); + "}\n", dinit(CheckOptions, $.cpp = false)); ASSERT_EQUALS("", errout_str()); // # 8717 @@ -12256,25 +12793,25 @@ class TestOther : public TestFixture { check("void f(int i) {\n" " int n = ++i + i;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:15]: (error) Expression '++i+i' depends on order of evaluation of side effects [unknownEvaluationOrder]\n", errout_str()); check("long int f1(const char *exp) {\n" " return dostuff(++exp, ++exp, 10);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:23]: (portability) Expression '++exp,++exp' depends on order of evaluation of side effects. Behavior is Unspecified according to c++17 [unknownEvaluationOrder]\n" "[test.cpp:2:23]: (portability) Expression '++exp,++exp' depends on order of evaluation of side effects. Behavior is Unspecified according to c++17 [unknownEvaluationOrder]\n", errout_str()); check("void f(int i) {\n" " int n = (~(-(++i)) + i);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:22]: (error) Expression '~(-(++i))+i' depends on order of evaluation of side effects [unknownEvaluationOrder]\n", errout_str()); const Settings settings11 = settingsBuilder(settings0).cpp(Standards::CPP11).certainty(Certainty::inconclusive).build(); checkCustomSettings("void f(int i) {\n" " i = i++ + 2;\n" - "}", settings11); + "}\n", settings11); ASSERT_EQUALS("[test.cpp:2:11]: (error) Expression 'i+++2' depends on order of evaluation of side effects [unknownEvaluationOrder]\n", errout_str()); // #14431 @@ -12288,7 +12825,7 @@ class TestOther : public TestFixture { // self assignment check("void f() {\n" " int x = x = y + 1;\n" - "}", dinit(CheckOptions, $.cpp = false)); + "}\n", dinit(CheckOptions, $.cpp = false)); ASSERT_EQUALS( "[test.c:2:9]: (style) Redundant assignment of 'x' to itself. [selfAssignment]\n" "[test.c:2:9]: (style) Redundant assignment of 'x' to itself. [selfAssignment]\n", // duplicate @@ -12300,7 +12837,7 @@ class TestOther : public TestFixture { checkP("#define X x\n" "void f(int x) {\n" " return x + X++;\n" - "}", dinit(CheckPOptions, $.cpp = false)); + "}\n", dinit(CheckPOptions, $.cpp = false)); ASSERT_EQUALS("[test.c:3:12]: (error) Expression 'x+x++' depends on order of evaluation of side effects [unknownEvaluationOrder]\n", errout_str()); } @@ -12308,13 +12845,13 @@ class TestOther : public TestFixture { // FP check("void f(int id) {\n" " id = dostuff(id += 42);\n" - "}", dinit(CheckOptions, $.cpp = false)); + "}\n", dinit(CheckOptions, $.cpp = false)); ASSERT_EQUALS("", errout_str()); // FN check("void f(int id) {\n" " id = id + dostuff(id += 42);\n" - "}", dinit(CheckOptions, $.cpp = false)); + "}\n", dinit(CheckOptions, $.cpp = false)); TODO_ASSERT_EQUALS("error", "", errout_str()); } @@ -12322,19 +12859,19 @@ class TestOther : public TestFixture { check("int f(void) {\n" " int t;\n" " return (unsigned char)(t=1,t^c);\n" - "}", dinit(CheckOptions, $.cpp = false)); + "}\n", dinit(CheckOptions, $.cpp = false)); ASSERT_EQUALS("", errout_str()); check("void f(void) {\n" " int t;\n" " dostuff(t=1,t^c);\n" - "}", dinit(CheckOptions, $.cpp = false)); + "}\n", dinit(CheckOptions, $.cpp = false)); ASSERT_EQUALS("[test.c:3:14]: (error) Expression 't=1,t^c' depends on order of evaluation of side effects [unknownEvaluationOrder]\n", errout_str()); check("void f(void) {\n" " int t;\n" " dostuff((t=1,t),2);\n" - "}", dinit(CheckOptions, $.cpp = false)); + "}\n", dinit(CheckOptions, $.cpp = false)); ASSERT_EQUALS("", errout_str()); // #8230 @@ -12363,7 +12900,7 @@ class TestOther : public TestFixture { void testEvaluationOrderSizeof() { check("void f(char *buf) {\n" " dostuff(buf++, sizeof(*buf));" - "}", dinit(CheckOptions, $.cpp = false)); + "}\n", dinit(CheckOptions, $.cpp = false)); ASSERT_EQUALS("", errout_str()); } @@ -12382,10 +12919,20 @@ class TestOther : public TestFixture { " if (0 > d.n) {\n" " return;\n" " }\n" - "}", dinit(CheckOptions, $.cpp = false)); + "}\n", dinit(CheckOptions, $.cpp = false)); ASSERT_EQUALS("[test.c:8:11]: (style) Checking if unsigned expression 'd.n' is less than zero. [unsignedLessThanZero]\n" "[test.c:12:9]: (style) Checking if unsigned expression 'd.n' is less than zero. [unsignedLessThanZero]\n", errout_str()); + + check("int ifunc(int x);\n" + "unsigned int ufunc(unsigned int x);\n" + "void f(void)\n" + "{\n" + " unsigned int x = 0;\n" + " if (_Generic(x, int: ifunc, unsigned int: ufunc)(x) < 0) {}\n" + "}\n", dinit(CheckOptions, $.cpp = false)); + ASSERT_EQUALS("[test.c:6:57]: (style) Checking if unsigned expression '_Generic(x,int:ifunc,unsigned int:ufunc)(x)' is less than zero. [unsignedLessThanZero]\n", + errout_str()); } void doubleMove1() { @@ -12394,7 +12941,7 @@ class TestOther : public TestFixture { " A a;\n" " g(std::move(a));\n" " g(std::move(a));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:17]: (warning) Access of moved variable 'a'. [accessMoved]\n", errout_str()); } @@ -12408,7 +12955,7 @@ class TestOther : public TestFixture { " }\n" " B b1;\n" " B b2;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:6:24]: (warning) Access of moved variable 'b'. [accessMoved]\n", errout_str()); } @@ -12421,7 +12968,7 @@ class TestOther : public TestFixture { " {}\n" " B b1;\n" " B b2;\n" - "};"); + "};\n"); ASSERT_EQUALS("[test.cpp:5:19]: (warning) Access of moved variable 'b'. [accessMoved]\n", errout_str()); } @@ -12452,7 +12999,7 @@ class TestOther : public TestFixture { " A a;\n" " a = g(std::move(a));\n" " a = g(std::move(a));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -12462,7 +13009,7 @@ class TestOther : public TestFixture { " A a;\n" " B b = g(std::move(a));\n" " C c = g(std::move(a));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:23]: (warning) Access of moved variable 'a'. [accessMoved]\n", errout_str()); } @@ -12478,7 +13025,7 @@ class TestOther : public TestFixture { " h(a);\n" " a = b;\n" " h(a);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:7]: (warning) Access of moved variable 'a'. [accessMoved]\n" "[test.cpp:8:7]: (warning) Access of moved variable 'a'. [accessMoved]\n", errout_str()); } @@ -12489,7 +13036,7 @@ class TestOther : public TestFixture { " A a;\n" " a.reset(g(std::move(a)));\n" " a.reset(g(std::move(a)));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -12501,7 +13048,7 @@ class TestOther : public TestFixture { " A c;\n" " b.reset(g(std::move(a)));\n" " c.reset(g(std::move(a)));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:25]: (warning) Access of moved variable 'a'. [accessMoved]\n", errout_str()); } @@ -12517,7 +13064,7 @@ class TestOther : public TestFixture { " h(a);\n" " a.reset(b);\n" " h(a);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:7]: (warning) Access of moved variable 'a'. [accessMoved]\n" "[test.cpp:8:7]: (warning) Access of moved variable 'a'. [accessMoved]\n", errout_str()); } @@ -12529,7 +13076,7 @@ class TestOther : public TestFixture { " A b = std::move(a);\n" " g(a);\n" " A c = a;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:7]: (warning) Access of moved variable 'a'. [accessMoved]\n" "[test.cpp:6:11]: (warning) Access of moved variable 'a'. [accessMoved]\n", errout_str()); } @@ -12541,7 +13088,7 @@ class TestOther : public TestFixture { " A b = std::move(a);\n" " g(a);\n" " A c = a;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -12552,7 +13099,7 @@ class TestOther : public TestFixture { " A b = std::move(a);\n" " g(a);\n" " A c = a;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:7]: (warning) Access of moved variable 'a'. [accessMoved]\n" "[test.cpp:6:11]: (warning) Access of moved variable 'a'. [accessMoved]\n", errout_str()); } @@ -12563,7 +13110,7 @@ class TestOther : public TestFixture { " A b = std::move(a);\n" " g(a);\n" " A c = a;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:7]: (warning, inconclusive) Access of moved variable 'a'. [accessMoved]\n" "[test.cpp:5:11]: (warning, inconclusive) Access of moved variable 'a'. [accessMoved]\n", errout_str()); } @@ -12576,7 +13123,7 @@ class TestOther : public TestFixture { " if (i)\n" " return g(std::move(b));\n" " return h(std::move(a),std::move(b));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:7:24]: (warning) Access of moved variable 'a'. [accessMoved]\n", errout_str()); } @@ -12586,7 +13133,7 @@ class TestOther : public TestFixture { " g(std::move(v));\n" " v.clear();\n" " if (v.empty()) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -12596,9 +13143,19 @@ class TestOther : public TestFixture { " g(std::move(p));\n" " x = p->x;\n" " y = p->y;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:9]: (warning) Access of moved variable 'p'. [accessMoved]\n" "[test.cpp:5:9]: (warning) Access of moved variable 'p'. [accessMoved]\n", errout_str()); + + check("void h(std::unique_ptr);\n" // #12436 + "void g(int);\n" + "void f() {\n" + " std::unique_ptr p = std::make_unique(5);\n" + " int* w = p.get();\n" + " h(std::move(p));\n" + " g(*w);\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:10]: (style) Variable 'w' can be declared as pointer to const [constVariablePointer]\n", errout_str()); } void moveAndAddressOf() { @@ -12606,7 +13163,7 @@ class TestOther : public TestFixture { " std::string s1 = x;\n" " std::string s2 = std::move(s1);\n" " p = &s1;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -12615,16 +13172,16 @@ class TestOther : public TestFixture { " A a;\n" " gx(std::move(a).x());\n" " gy(std::move(a).y());\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } void moveAndLambda() { check("void f() {\n" " A a;\n" - " auto h = [a=std::move(a)](){return g(std::move(a));};" + " auto h = [a=std::move(a)](){return g(std::move(a));};\n" " b = a;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -12645,6 +13202,24 @@ class TestOther : public TestFixture { " }\n" "}\n"); ASSERT_EQUALS("[test.cpp:4:36]: (warning) Access of moved variable 'l'. [accessMoved]\n", errout_str()); + + check("struct S {\n" // #13179 + " operator bool() const { return !m.empty(); }\n" + " std::string m;\n" + "};\n" + "S get();\n" + "void set(S);\n" + "void f() {\n" + " while (S s = get()) {\n" + " set(std::move(s));\n" + " }\n" + "}\n" + "void g() {\n" + " while (S s{ get() }) {\n" + " set(std::move(s));\n" + " }\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); } void moveCallback() @@ -12677,7 +13252,7 @@ class TestOther : public TestFixture { "void f(T && t) {\n" " g(std::forward(t));\n" " T s = t;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:11]: (warning) Access of forwarded variable 't'. [accessForwarded]\n", errout_str()); } @@ -12703,6 +13278,13 @@ class TestOther : public TestFixture { " cif::condition mWhere;\n" "};\n"); ASSERT_EQUALS("", errout_str()); + + check("void g(std::string);\n" // #14928 + "void f(std::string s) {\n" + " g(std::move(s));\n" + " for (char c : s) {}\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:4:19]: (warning) Access of moved variable 's'. [accessMoved]\n", errout_str()); } void moveTernary() @@ -12732,6 +13314,18 @@ class TestOther : public TestFixture { " h(b ? h(gA(5, std::move(s))) : h(gB(7, std::move(s))));\n" "}\n"); ASSERT_EQUALS("", errout_str()); + + check("int cb(std::string);\n" // #13628 + "void f(bool b, std::string s1) {\n" + " std::string s2 = b ? cb(std::move(s1)) : s1;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("int cb(std::string);\n" + "void f(bool b, std::string s1) {\n" + " std::string s2 = b ? cb(std::move(s1)) : s1 + s1;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); } void movePointerAlias() @@ -12791,7 +13385,7 @@ class TestOther : public TestFixture { "void Fred::func1(int a, int b, int c) { }\n" "void Fred::func2(int A, int B, int C) { }\n" "void Fred::func3(int a, int b, int c) { }\n" - "void Fred::func4(int A, int B, int C) { }"); + "void Fred::func4(int A, int B, int C) { }\n"); ASSERT_EQUALS("[test.cpp:3:16] -> [test.cpp:4:16]: (style, inconclusive) Function 'func2' argument 1 names different: declaration 'a' definition 'A'. [funcArgNamesDifferent]\n" "[test.cpp:3:23] -> [test.cpp:4:23]: (style, inconclusive) Function 'func2' argument 2 names different: declaration 'b' definition 'B'. [funcArgNamesDifferent]\n" "[test.cpp:3:30] -> [test.cpp:4:30]: (style, inconclusive) Function 'func2' argument 3 names different: declaration 'c' definition 'C'. [funcArgNamesDifferent]\n" @@ -12816,6 +13410,24 @@ class TestOther : public TestFixture { "[test.cpp:7:15] -> [test.cpp:8:15]: (style, inconclusive) Function 'h' argument 1 names different: declaration 'a' definition 'b'. [funcArgNamesDifferent]\n", errout_str()); + check("void f(int a);\n" // #14632 + "void f(int) {}\n" + "void g(int);\n" + "void g(int b) {}\n" + "void h(int);\n" + "void h(int) {}\n"); + ASSERT_EQUALS( + "[test.cpp:1:12]: (style, inconclusive) Function 'f' argument 1 names different: declaration 'a' definition ''. [funcArgNamesDifferentUnnamed]\n" + "[test.cpp:4:12]: (style, inconclusive) Function 'g' argument 1 names different: declaration '' definition 'b'. [funcArgNamesDifferentUnnamed]\n", + errout_str()); + + check("void f(void (*fp)(), int x);\n" // #14847 + "void f(void (*fp)(), int x) {}\n"); + ASSERT_EQUALS("", errout_str()); + + check("void f(void (*fp)(int a, int b), int b);\n" // #14853 + "void f(void (*fp)(int a, int b), int b) {}\n"); + ASSERT_EQUALS("", errout_str()); } void funcArgOrderDifferent() { @@ -12841,54 +13453,58 @@ class TestOther : public TestFixture { "[test.cpp:9:20] -> [test.cpp:14:22]: (warning) Function 'func2' argument order different: declaration 'a, b, c' definition 'c, b, a' [funcArgOrderDifferent]\n" "[test.cpp:10:20] -> [test.cpp:15:22]: (warning) Function 'func3' argument order different: declaration 'a, b, c' definition 'c, b, a' [funcArgOrderDifferent]\n" "[test.cpp:11:16] -> [test.cpp:16:22]: (warning) Function 'func4' argument order different: declaration ', b, c' definition 'c, b, a' [funcArgOrderDifferent]\n", errout_str()); + + check("void f(int N, const int a[N], const int b[N]);\n" // #14710 + "void f(int N, const int a[N], const int b[N]) {}\n"); + ASSERT_EQUALS("", errout_str()); } // #7846 - Syntax error when using C++11 braced-initializer in default argument void cpp11FunctionArgInit() { // syntax error is not expected - ASSERT_NO_THROW(check("\n void foo(int declaration = {}) {" + ASSERT_NO_THROW(check("\n void foo(int declaration = {}) {\n" "\n for (int i = 0; i < 10; i++) {}\n" - "\n }" - "\n ")); + "\n }\n" + "\n")); ASSERT_EQUALS("", errout_str()); } void shadowVariables() { check("int x;\n" - "void f() { int x; }"); + "void f() { int x; }\n"); ASSERT_EQUALS("[test.cpp:1:5] -> [test.cpp:2:16]: (style) Local variable 'x' shadows outer variable [shadowVariable]\n", errout_str()); check("int x();\n" - "void f() { int x; }"); + "void f() { int x; }\n"); ASSERT_EQUALS("[test.cpp:1:5] -> [test.cpp:2:16]: (style) Local variable 'x' shadows outer function [shadowFunction]\n", errout_str()); check("struct C {\n" " C(int x) : x(x) {}\n" // <- we do not want a FP here " int x;\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" " if (cond) {int x;}\n" // <- not a shadow variable " int x;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int size() {\n" " int size;\n" // <- not a shadow variable - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f() {\n" // #8954 - lambda " int x;\n" - " auto f = [](){ int x; }" - "}"); + " auto f = [](){ int x; }\n" + "}\n"); ASSERT_EQUALS("", errout_str()); - check("void f(int x) { int x; }"); + check("void f(int x) { int x; }\n"); ASSERT_EQUALS("[test.cpp:1:12] -> [test.cpp:1:21]: (style) Local variable 'x' shadows outer argument [shadowArgument]\n", errout_str()); - check("class C { C(); void foo() { static int C = 0; } }"); // #9195 - shadow constructor + check("class C { C(); void foo() { static int C = 0; } }\n"); // #9195 - shadow constructor ASSERT_EQUALS("", errout_str()); check("struct C {\n" // #10091 - shadow destructor @@ -12897,7 +13513,7 @@ class TestOther : public TestFixture { " bool C{};\n" " }\n" "};\n" - "C::~C() = default;"); + "C::~C() = default;\n"); ASSERT_EQUALS("", errout_str()); // 10752 - no @@ -12908,21 +13524,21 @@ class TestOther : public TestFixture { " int i = 0;\n" " return i;\n" " }\n" - "};"); + "};\n"); ASSERT_EQUALS("", errout_str()); check("struct S {\n" " int i{};\n" " void f() { int i; }\n" "};\n"); - ASSERT_EQUALS("[test.cpp:2:9] -> [test.cpp:3:20]: (style) Local variable 'i' shadows outer variable [shadowVariable]\n", errout_str()); + ASSERT_EQUALS("[test.cpp:2:9] -> [test.cpp:3:20]: (style) Local variable 'i' shadows outer member [shadowMember]\n", errout_str()); check("struct S {\n" " int i{};\n" " std::vector v;\n" " void f() const { for (const int& i : v) {} }\n" "};\n"); - ASSERT_EQUALS("[test.cpp:2:9] -> [test.cpp:4:38]: (style) Local variable 'i' shadows outer variable [shadowVariable]\n", errout_str()); + ASSERT_EQUALS("[test.cpp:2:9] -> [test.cpp:4:38]: (style) Local variable 'i' shadows outer member [shadowMember]\n", errout_str()); check("struct S {\n" // #10405 " F* f{};\n" @@ -12932,7 +13548,7 @@ class TestOther : public TestFixture { "void S::f() const {\n" " for (const F& f : fl) {}\n" "};\n"); - ASSERT_EQUALS("[test.cpp:2:8] -> [test.cpp:7:19]: (style) Local variable 'f' shadows outer variable [shadowVariable]\n", errout_str()); + ASSERT_EQUALS("[test.cpp:2:8] -> [test.cpp:7:19]: (style) Local variable 'f' shadows outer member [shadowMember]\n", errout_str()); check("extern int a;\n" "int a;\n" @@ -12954,25 +13570,76 @@ class TestOther : public TestFixture { " friend int f() { int i = 5; return i; }\n" "};\n"); ASSERT_EQUALS("", errout_str()); + + check("int x;\n" + "void f(int x) {}\n"); + ASSERT_EQUALS("[test.cpp:1:5] -> [test.cpp:2:12]: (style) Argument 'x' shadows outer variable [shadowVariable]\n", errout_str()); + + check("void x() {}\n" + "void f(int x) {}\n"); + ASSERT_EQUALS("[test.cpp:1:6] -> [test.cpp:2:12]: (style) Argument 'x' shadows outer function [shadowFunction]\n", errout_str()); + + check("struct S { int v; void func(int v); };\n" + "void S::func(int v) { this->v = v; }\n"); + ASSERT_EQUALS("[test.cpp:1:16] -> [test.cpp:2:18]: (style) Argument 'v' shadows outer member [shadowMember]\n", errout_str()); + + check("struct S { int v; void func(void); };\n" + "void S::func() { int v = 0; }\n"); + ASSERT_EQUALS("[test.cpp:1:16] -> [test.cpp:2:22]: (style) Local variable 'v' shadows outer member [shadowMember]\n", errout_str()); + + check("struct S { int v; explicit S(int v); };\n" + "S::S(int v) : v(v) {}\n"); + ASSERT_EQUALS("", errout_str()); + + check("struct S { int v(); explicit S(int v); };\n" + "S::S(int v) : v(v) {}\n"); + ASSERT_EQUALS("", errout_str()); + + check("struct S { int i(); static void f(int i) {} };\n"); + ASSERT_EQUALS("", errout_str()); + + check("struct S { static int i(); static void f(int i) {} };\n"); + ASSERT_EQUALS("[test.cpp:1:23] -> [test.cpp:1:46]: (style) Argument 'i' shadows outer function [shadowFunction]\n", errout_str()); + + check("struct S { void g(float f) {} void f() {} };\n"); + ASSERT_EQUALS("[test.cpp:1:36] -> [test.cpp:1:25]: (style) Argument 'f' shadows outer function [shadowFunction]\n", errout_str()); + + check("int f() {\n" // #15114 + " int i = 0;\n" + " return i;\n" + "}\n" + "int i = 1;\n"); + ASSERT_EQUALS("", errout_str()); + + { + ScopedFile inc("test.h", "int pad1;\nint pad2;\nint pad3;\nint pad4;\nfloat f;\n"); + checkP("void g() {\n" + " #include \"test.h\"\n" + " {\n" + " float f;\n" + " }\n" + "}\n"); + ASSERT_EQUALS("[test.h:5:7] -> [test.cpp:4:15]: (style) Local variable 'f' shadows outer variable [shadowVariable]\n", errout_str()); + } } void knownArgument() { check("void g(int);\n" "void f(int x) {\n" " g((x & 0x01) >> 7);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:17]: (style) Argument '(x&0x01)>>7' to function g is always 0. It does not matter what value 'x' has. [knownArgument]\n", errout_str()); check("void g(int);\n" "void f(int x) {\n" " g((int)((x & 0x01) >> 7));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:6]: (style) Argument '(int)((x&0x01)>>7)' to function g is always 0. It does not matter what value 'x' has. [knownArgument]\n", errout_str()); check("void g(int, int);\n" "void f(int x) {\n" " g(x, (x & 0x01) >> 7);\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:3:20]: (style) Argument '(x&0x01)>>7' to function g is always 0. It does not matter what value 'x' has. [knownArgument]\n", errout_str()); @@ -12980,65 +13647,65 @@ class TestOther : public TestFixture { check("void g(int);\n" "void f(int x) {\n" " g(0);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void g(int);\n" "void h() { return 1; }\n" "void f(int x) {\n" " g(h());\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void g(int);\n" "void f(int x) {\n" " g(std::strlen(\"a\"));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void g(int);\n" "void f(int x) {\n" " g((int)0);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void g(Foo *);\n" "void f() {\n" " g(reinterpret_cast(0));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void g(int);\n" "void f(int x) {\n" " x = 0;\n" " g(x);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void g(int);\n" "void f() {\n" " const int x = 0;\n" " g(x + 1);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void g(int);\n" "void f() {\n" " char i = 1;\n" " g(static_cast(i));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("char *yytext;\n" "void re_init_scanner() {\n" " int size = 256;\n" " yytext = xmalloc(size * sizeof *yytext);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void foo(const char *c) {\n" " if (*c == '+' && (operand || !isalnum(*c))) {}\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #8986 @@ -13046,14 +13713,14 @@ class TestOther : public TestFixture { "void g() {\n" " const int x[] = { 10, 10 };\n" " f(x[0]);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int);\n" "void g() {\n" " int x[] = { 10, 10 };\n" " f(x[0]);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:9]: (style) Variable 'x' can be declared as const array [constVariable]\n", errout_str()); check("struct A { int x; };" @@ -13062,21 +13729,21 @@ class TestOther : public TestFixture { " A y;\n" " y.x = 1;\n" " g(y.x);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // allow known argument value in assert call check("void g(int);\n" "void f(int x) {\n" " ASSERT((int)((x & 0x01) >> 7));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #9905 - expression that does not use integer calculation at all check("void foo() {\n" " const std::string heading = \"Interval\";\n" " std::cout << std::setw(heading.length());\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #9909 - struct member with known value @@ -13087,7 +13754,7 @@ class TestOther : public TestFixture { "void growLongStack(LongStack* self) {\n" " self->maxsize = 32;\n" " dostuff(self->maxsize * sizeof(intptr_t));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #11894 @@ -13156,6 +13823,18 @@ class TestOther : public TestFixture { " return MACRO(true);\n" "}\n"); ASSERT_EQUALS("", errout_str()); + + check("struct S {\n" // #15063 + " int (*f)(int, int);\n" + " int (*a[2])(int, int);\n" + "};\n" + "int g1(struct S* p, int x) {\n" + " return (p->f)(x, 10);\n" + "}\n" + "int g2(struct S s, int x) {\n" + " return s.a[0](x, 10);\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); } void knownArgumentHiddenVariableExpression() { @@ -13182,7 +13861,7 @@ class TestOther : public TestFixture { " bar(P, N); \n" " if (c ? a : b)\n" " baz(P, N); \n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -13195,7 +13874,7 @@ class TestOther : public TestFixture { " diff = 1;\n" " }\n" " return diff;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:2:15] -> [test.cpp:5:8] -> [test.cpp:3:15] -> [test.cpp:5:14] -> [test.cpp:5:12]: (error) Comparing pointers that point to different objects [comparePointers]\n", errout_str()); @@ -13206,7 +13885,7 @@ class TestOther : public TestFixture { " int* xp = &x;\n" " int* yp = &y;\n" " return xp > yp;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:2:9] -> [test.cpp:4:15] -> [test.cpp:3:9] -> [test.cpp:5:15] -> [test.cpp:6:15]: (error) Comparing pointers that point to different objects [comparePointers]\n" "[test.cpp:4:10]: (style) Variable 'xp' can be declared as pointer to const [constVariablePointer]\n" @@ -13217,7 +13896,7 @@ class TestOther : public TestFixture { " int x = 0;\n" " int y = 1;\n" " return &x > &y;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:2:9] -> [test.cpp:4:12] -> [test.cpp:3:9] -> [test.cpp:4:17] -> [test.cpp:4:15]: (error) Comparing pointers that point to different objects [comparePointers]\n", errout_str()); @@ -13229,7 +13908,7 @@ class TestOther : public TestFixture { " int* xp = &x.data;\n" " int* yp = &y.data;\n" " return xp > yp;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:3:7] -> [test.cpp:5:15] -> [test.cpp:4:7] -> [test.cpp:6:15] -> [test.cpp:7:15]: (error) Comparing pointers that point to different objects [comparePointers]\n" "[test.cpp:5:10]: (style) Variable 'xp' can be declared as pointer to const [constVariablePointer]\n" @@ -13243,7 +13922,7 @@ class TestOther : public TestFixture { " int* xp = &x->data;\n" " int* yp = &y->data;\n" " return xp > yp;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:2:10] -> [test.cpp:3:12] -> [test.cpp:5:15] -> [test.cpp:2:16] -> [test.cpp:4:12] -> [test.cpp:6:15] -> [test.cpp:7:15]: (error) Comparing pointers that point to different objects [comparePointers]\n" "[test.cpp:5:10]: (style) Variable 'xp' can be declared as pointer to const [constVariablePointer]\n" @@ -13252,7 +13931,7 @@ class TestOther : public TestFixture { check("bool f(int * xp, int* yp) {\n" " return &xp > &yp;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:1:14] -> [test.cpp:2:12] -> [test.cpp:1:23] -> [test.cpp:2:18] -> [test.cpp:2:16]: (error) Comparing pointers that point to different objects [comparePointers]\n", errout_str()); @@ -13261,7 +13940,7 @@ class TestOther : public TestFixture { " int x = 0;\n" " int y = 1;\n" " return &x - &y;\n" - "}"); + "}\n"); ASSERT_EQUALS( "[test.cpp:2:9] -> [test.cpp:4:12] -> [test.cpp:3:9] -> [test.cpp:4:17] -> [test.cpp:4:15]: (error) Subtracting pointers that point to different objects [subtractPointers]\n", errout_str()); @@ -13271,19 +13950,19 @@ class TestOther : public TestFixture { " int* xp = &x[0];\n" " int* yp = &x[1];\n" " return xp > yp;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:10]: (style) Variable 'xp' can be declared as pointer to const [constVariablePointer]\n" "[test.cpp:4:10]: (style) Variable 'yp' can be declared as pointer to const [constVariablePointer]\n", errout_str()); check("bool f(const int * xp, const int* yp) {\n" " return xp > yp;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("bool f(const int & x, const int& y) {\n" " return &x > &y;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int& g();\n" @@ -13293,7 +13972,7 @@ class TestOther : public TestFixture { " const int* xp = &x;\n" " const int* yp = &y;\n" " return xp > yp;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("struct A {int data;};\n" @@ -13303,7 +13982,7 @@ class TestOther : public TestFixture { " int* xp = &x->data;\n" " int* yp = &y->data;\n" " return xp > yp;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:10]: (style) Variable 'xp' can be declared as pointer to const [constVariablePointer]\n" "[test.cpp:6:10]: (style) Variable 'yp' can be declared as pointer to const [constVariablePointer]\n", errout_str()); @@ -13354,21 +14033,21 @@ class TestOther : public TestFixture { " return A::Hash{}(a);\n" " }\n" " };\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } void moduloOfOne() { check("void f(unsigned int x) {\n" " int y = x % 1;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:13]: (style) Modulo of one is always equal to zero [moduloofone]\n", errout_str()); check("void f() {\n" " for (int x = 1; x < 10; x++) {\n" " int y = 100 % x;\n" " }\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(int i, int j) {\n" // #11191 @@ -13401,44 +14080,44 @@ class TestOther : public TestFixture { " union { int i; float f; } u;\n" " u.i = 0;\n" " u.i = u.f;\n" // <- error - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:9]: (error) Overlapping read/write of union is undefined behavior [overlappingWriteUnion]\n", errout_str()); check("void foo() {\n" // #11013 " union { struct { uint8_t a; uint8_t b; }; uint16_t c; } u;\n" " u.a = u.b = 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // memcpy check("void foo() {\n" " char a[10];\n" " memcpy(&a[5], &a[4], 2u);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (error) Overlapping read/write in memcpy() is undefined behavior [overlappingWriteFunction]\n", errout_str()); check("void foo() {\n" " char a[10];\n" " memcpy(a+5, a+4, 2u);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (error) Overlapping read/write in memcpy() is undefined behavior [overlappingWriteFunction]\n", errout_str()); check("void foo() {\n" " char a[10];\n" " memcpy(a, a+1, 2u);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (error) Overlapping read/write in memcpy() is undefined behavior [overlappingWriteFunction]\n", errout_str()); check("void foo() {\n" " char a[8];\n" " memcpy(&a[0], &a[4], 4u);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("_Bool a[10];\n" // #10350 "void foo() {\n" " memcpy(&a[5], &a[4], 2u * sizeof(a[0]));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (error) Overlapping read/write in memcpy() is undefined behavior [overlappingWriteFunction]\n", errout_str()); check("int K[2];\n" // #12638 @@ -13447,7 +14126,7 @@ class TestOther : public TestFixture { " memcpy(&K[1], &K[0], sizeof(K[0]));\n" " memcpy(p, p + 1, sizeof(*p));\n" " memcpy(p + 1, p, sizeof(*p));\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("int K[2];\n" @@ -13456,7 +14135,7 @@ class TestOther : public TestFixture { " memcpy(&K[1], &K[0], 2 *sizeof(K[0]));\n" " memcpy(p, p + 1, 2 * sizeof(*p));\n" " memcpy(p + 1, p, 2 * sizeof(*p));\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (error) Overlapping read/write in memcpy() is undefined behavior [overlappingWriteFunction]\n" "[test.cpp:4:5]: (error) Overlapping read/write in memcpy() is undefined behavior [overlappingWriteFunction]\n" "[test.cpp:5:5]: (error) Overlapping read/write in memcpy() is undefined behavior [overlappingWriteFunction]\n" @@ -13467,25 +14146,25 @@ class TestOther : public TestFixture { check("void foo() {\n" " wchar_t a[10];\n" " wmemcpy(&a[5], &a[4], 2u);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (error) Overlapping read/write in wmemcpy() is undefined behavior [overlappingWriteFunction]\n", errout_str()); check("void foo() {\n" " wchar_t a[10];\n" " wmemcpy(a+5, a+4, 2u);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (error) Overlapping read/write in wmemcpy() is undefined behavior [overlappingWriteFunction]\n", errout_str()); check("void foo() {\n" " wchar_t a[10];\n" " wmemcpy(a, a+1, 2u);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (error) Overlapping read/write in wmemcpy() is undefined behavior [overlappingWriteFunction]\n", errout_str()); // strcpy check("void foo(char *ptr) {\n" " strcpy(ptr, ptr);\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:2:5]: (error) Overlapping read/write in strcpy() is undefined behavior [overlappingWriteFunction]\n", errout_str()); } @@ -13542,7 +14221,7 @@ class TestOther : public TestFixture { "}\n"); ASSERT_EQUALS("[test.cpp:4:17]: (style) Pointer expression 'a.x' converted to bool is always true. [knownPointerToBool]\n", errout_str()); - check("void f(bool* b) { if (b) *b = true; }"); + check("void f(bool* b) { if (b) *b = true; }\n"); ASSERT_EQUALS("", errout_str()); check("bool f() {\n" @@ -13590,8 +14269,14 @@ class TestOther : public TestFixture { "void f() {\n" " bool b[2] = {};\n" " g(b);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); + + check("void g(bool);\n" // #14303 + "void f() {\n" + " g(g);\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:3:7]: (style) Pointer expression 'g' converted to bool is always true. [knownPointerToBool]\n", errout_str()); } void iterateByValue() { @@ -13789,7 +14474,7 @@ class TestOther : public TestFixture { " union bad_union_0 bad1 = {0};\n" " e(&bad1);\n" " bad_union_1 bad2 = {0};\n" - "}"); + "}\n"); const std::string exp = unionZeroInitMessage(20, 28, "bad0", "i") + unionZeroInitMessage(21, 21, "bad1", "i64") + unionZeroInitMessage(23, 15, "bad2", "i"); @@ -13800,7 +14485,7 @@ class TestOther : public TestFixture { check( "void foo(void) {\n" " union { int c; char s8[2]; } u = {0};\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -13816,7 +14501,7 @@ class TestOther : public TestFixture { " } s1;\n" " } s0;\n" " } u = {0};\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -13824,7 +14509,7 @@ class TestOther : public TestFixture { check( "union u {\n" " Unknown x;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -13844,7 +14529,7 @@ class TestOther : public TestFixture { "\n" "void foo(void) {\n" " Evex evex = {0};\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } }; diff --git a/test/testpath.cpp b/test/testpath.cpp index 69ee91fa391..0ebe343685f 100644 --- a/test/testpath.cpp +++ b/test/testpath.cpp @@ -516,7 +516,8 @@ class TestPath : public TestFixture { ASSERT_EQUALS(expected, Path::getAbsoluteFilePath(Path::join(cwd, "testabspath.txt"))); std::string cwd_up = Path::getPathFromFilename(cwd); - cwd_up.pop_back(); // remove trailing slash + if (cwd_up != "/") + cwd_up.pop_back(); // remove trailing slash ASSERT_EQUALS(cwd_up, Path::getAbsoluteFilePath(Path::join(cwd, ".."))); ASSERT_EQUALS(cwd_up, Path::getAbsoluteFilePath(Path::join(cwd, "../"))); ASSERT_EQUALS(cwd_up, Path::getAbsoluteFilePath(Path::join(cwd, "..\\"))); diff --git a/test/testpostfixoperator.cpp b/test/testpostfixoperator.cpp index 9e95fabc949..85568c974ac 100644 --- a/test/testpostfixoperator.cpp +++ b/test/testpostfixoperator.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -38,8 +38,7 @@ class TestPostfixOperator : public TestFixture { SimpleTokenizer tokenizer(settings, *this); ASSERT_LOC(tokenizer.tokenize(code), file, line); - // Check for postfix operators.. - CheckPostfixOperator checkPostfixOperator(&tokenizer, &settings, this); + CheckPostfixOperatorImpl checkPostfixOperator(&tokenizer, settings, *this); checkPostfixOperator.postfixOperator(); } @@ -72,7 +71,7 @@ class TestPostfixOperator : public TestFixture { " k--;\n" " std::cout << k << std::endl;\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("class K {};" @@ -83,7 +82,7 @@ class TestPostfixOperator : public TestFixture { " k++;\n" " std::cout << k << std::endl;\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:5]: (performance) Prefer prefix ++/-- operators for non-primitive types. [postfixOperator]\n", errout_str()); check("struct K {};" @@ -91,14 +90,14 @@ class TestPostfixOperator : public TestFixture { "{\n" " K k(0);\n" " k++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:5]: (performance) Prefer prefix ++/-- operators for non-primitive types. [postfixOperator]\n", errout_str()); check("struct K {};\n" "void foo(K& k)\n" "{\n" " k++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:5]: (performance) Prefer prefix ++/-- operators for non-primitive types. [postfixOperator]\n", errout_str()); check("union K {};" @@ -106,7 +105,7 @@ class TestPostfixOperator : public TestFixture { "{\n" " K k(0);\n" " k++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:5]: (performance) Prefer prefix ++/-- operators for non-primitive types. [postfixOperator]\n", errout_str()); check("class K {};" @@ -119,7 +118,7 @@ class TestPostfixOperator : public TestFixture { " }\n" " std::cout << k << std::endl;\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:9]: (performance) Prefer prefix ++/-- operators for non-primitive types. [postfixOperator]\n", errout_str()); check("class K {};" @@ -133,7 +132,7 @@ class TestPostfixOperator : public TestFixture { " k++;\n" " std::cout << k << std::endl;\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:5]: (performance) Prefer prefix ++/-- operators for non-primitive types. [postfixOperator]\n", errout_str()); @@ -145,7 +144,7 @@ class TestPostfixOperator : public TestFixture { " k--;\n" " std::cout << k << std::endl;\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:5:5]: (performance) Prefer prefix ++/-- operators for non-primitive types. [postfixOperator]\n", errout_str()); check("class K {};" @@ -156,7 +155,7 @@ class TestPostfixOperator : public TestFixture { " ++k;\n" " std::cout << k << std::endl;\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("class K {};" @@ -167,7 +166,7 @@ class TestPostfixOperator : public TestFixture { " --k;\n" " std::cout << k << std::endl;\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); // #9042 @@ -178,7 +177,7 @@ class TestPostfixOperator : public TestFixture { "};\n" "template \n" "class s {};\n" - "using BOOL = char;"); + "using BOOL = char;\n"); ASSERT_EQUALS("", errout_str()); } @@ -189,7 +188,7 @@ class TestPostfixOperator : public TestFixture { " std::cout << i << std::endl;\n" " }\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("class K {};\n" @@ -199,7 +198,7 @@ class TestPostfixOperator : public TestFixture { " std::cout << i << std::endl;\n" " }\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:28]: (performance) Prefer prefix ++/-- operators for non-primitive types. [postfixOperator]\n", errout_str()); check("class K {};\n" @@ -209,7 +208,7 @@ class TestPostfixOperator : public TestFixture { " std::cout << i << std::endl;\n" " }\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("class K {};\n" @@ -219,7 +218,7 @@ class TestPostfixOperator : public TestFixture { " std::cout << i << std::endl;\n" " }\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:27]: (performance) Prefer prefix ++/-- operators for non-primitive types. [postfixOperator]\n", errout_str()); check("class K {};\n" @@ -229,7 +228,7 @@ class TestPostfixOperator : public TestFixture { " std::cout << i << std::endl;\n" " }\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); check("void f(const std::string &s) {\n" // #7731 @@ -247,7 +246,7 @@ class TestPostfixOperator : public TestFixture { " k++;\n" " std::cout << k << std::endl;\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:5]: (performance) Prefer prefix ++/-- operators for non-primitive types. [postfixOperator]\n", errout_str()); } @@ -262,7 +261,7 @@ class TestPostfixOperator : public TestFixture { " }\n" " v.clear();\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:6:64]: (performance) Prefer prefix ++/-- operators for non-primitive types. [postfixOperator]\n", errout_str()); check("int main() {\n" @@ -276,7 +275,7 @@ class TestPostfixOperator : public TestFixture { " it++;\n" " }\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:9:8]: (performance) Prefer prefix ++/-- operators for non-primitive types. [postfixOperator]\n", errout_str()); check("int main() {\n" @@ -289,7 +288,7 @@ class TestPostfixOperator : public TestFixture { " it++;\n" " }\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:8:8]: (performance) Prefer prefix ++/-- operators for non-primitive types. [postfixOperator]\n", errout_str()); check("int main() {\n" @@ -303,14 +302,14 @@ class TestPostfixOperator : public TestFixture { " rit--;\n" " }\n" " return 0;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:9:8]: (performance) Prefer prefix ++/-- operators for non-primitive types. [postfixOperator]\n", errout_str()); } void test2168() { ASSERT_THROW_INTERNAL(check("--> declare allocator lock here\n" - "int main(){}"), + "int main(){}\n"), AST); } @@ -318,7 +317,7 @@ class TestPostfixOperator : public TestFixture { check("void f(int* p){\n" " p++;\n" " std::cout << *p;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -328,7 +327,7 @@ class TestPostfixOperator : public TestFixture { "\n" "void f() {\n" " p++;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -336,7 +335,7 @@ class TestPostfixOperator : public TestFixture { check("bool foo() {\n" " std::vector::iterator aIter(aImport.begin());\n" " aIter++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:3:5]: (performance) Prefer prefix ++/-- operators for non-primitive types. [postfixOperator]\n", errout_str()); } @@ -345,14 +344,14 @@ class TestPostfixOperator : public TestFixture { " class A {}; class B {A a;};\n" " B b;\n" " b.a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:7]: (performance) Prefer prefix ++/-- operators for non-primitive types. [postfixOperator]\n", errout_str()); check("bool foo() {\n" " class A {}; class B {A a;};\n" " B b;\n" " foo(b.a++);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -361,7 +360,7 @@ class TestPostfixOperator : public TestFixture { " class A {};\n" " A a;\n" " i++, a++;\n" - "}"); + "}\n"); ASSERT_EQUALS("[test.cpp:4:10]: (performance) Prefer prefix ++/-- operators for non-primitive types. [postfixOperator]\n", errout_str()); check("bool foo(int i) {\n" @@ -369,7 +368,7 @@ class TestPostfixOperator : public TestFixture { " A a;\n" " foo(i, a++);\n" " foo(a++, i);\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } @@ -381,7 +380,7 @@ class TestPostfixOperator : public TestFixture { " a += i;\n" " }\n" " return a;\n" - "}"); + "}\n"); ASSERT_EQUALS("", errout_str()); } }; diff --git a/test/testpreprocessor.cpp b/test/testpreprocessor.cpp index d072ebbf2e3..faa1d9ce01b 100644 --- a/test/testpreprocessor.cpp +++ b/test/testpreprocessor.cpp @@ -32,7 +32,9 @@ #include "fixture.h" #include "helpers.h" +#include #include +#include #include #include #include @@ -50,13 +52,14 @@ class TestPreprocessor : public TestFixture { TestPreprocessor() : TestFixture("TestPreprocessor") {} private: + #define expandMacros(...) expandMacros_(__FILE__, __LINE__, __VA_ARGS__) template - std::string expandMacros(const char (&code)[size], ErrorLogger &errorLogger) const { + std::string expandMacros_(const char* file, int line, const char (&code)[size], ErrorLogger &errorLogger) const { simplecpp::OutputList outputList; std::vector files; - simplecpp::TokenList tokens1 = simplecpp::TokenList(code, files, "file.cpp", &outputList); + simplecpp::TokenList tokens1 = simplecpp::TokenList(code, files, "file.cpp", {}, &outputList); Preprocessor p(tokens1, settingsDefault, errorLogger, Path::identify(tokens1.getFiles()[0], false)); - ASSERT(p.loadFiles(files)); + ASSERT_LOC(p.loadAllIncludes(files), file, line); simplecpp::TokenList tokens2 = p.preprocess("", files, outputList); (void)p.reportOutput(outputList, true); return tokens2.stringify(); @@ -72,7 +75,7 @@ class TestPreprocessor : public TestFixture { throw std::runtime_error("token list not empty"); simplecpp::OutputList outputList; - const simplecpp::TokenList tokens1(code, files, file0, &outputList); + const simplecpp::TokenList tokens1(code, files, file0, dui, &outputList); // Preprocess.. simplecpp::TokenList tokens2(files); @@ -121,7 +124,7 @@ class TestPreprocessor : public TestFixture { simplecpp::OutputList outputList; std::vector files; - simplecpp::TokenList tokens({code, size}, files, Path::simplifyPath(filename), &outputList); + simplecpp::TokenList tokens({code, size}, files, Path::simplifyPath(filename), {}, &outputList); // TODO: we should be using the actual Preprocessor implementation Preprocessor preprocessor(tokens, settings, errorlogger, Path::identify(tokens.getFiles()[0], false)); @@ -136,12 +139,15 @@ class TestPreprocessor : public TestFixture { preprocessor.simplifyPragmaAsm(); std::map cfgcode; - if (cfgs.empty()) - cfgs = preprocessor.getConfigs(); + if (cfgs.empty()) { + cfgs.insert(""); + std::set configDefines = { "__cplusplus" }; + preprocessor.getConfigs(configDefines, cfgs); + } for (const std::string & config : cfgs) { try { - const bool writeLocations = (strstr(code, "#file") != nullptr) || (strstr(code, "#include") != nullptr); - cfgcode[config] = preprocessor.getcode(config, files, writeLocations); + const bool writeLocs = (strstr(code, "#include") != nullptr); + cfgcode[config] = preprocessor.getcode(config, files, writeLocs); } catch (const simplecpp::Output &) { cfgcode[config] = ""; } @@ -343,6 +349,8 @@ class TestPreprocessor : public TestFixture { TEST_CASE(getConfigsAndCodeIssue14317); TEST_CASE(getConfigsMostGeneralConfigIssue14317); + TEST_CASE(getConfigsInvalid); // #14732 + TEST_CASE(if_sizeof); TEST_CASE(invalid_ifs); // #5909 @@ -365,17 +373,24 @@ class TestPreprocessor : public TestFixture { TEST_CASE(testMissingIncludeMixed); TEST_CASE(testMissingIncludeCheckConfig); + TEST_CASE(testLazyInclude); + TEST_CASE(testKeepComments); + TEST_CASE(testKeepCommentsMultiline); + TEST_CASE(hasInclude); TEST_CASE(limitsDefines); TEST_CASE(hashCalculation); + TEST_CASE(hashCalculationIncludeOrder); TEST_CASE(standard); TEST_CASE(writeLocations); TEST_CASE(pragmaAsm); + + TEST_CASE(dumpPreprocessor); } template @@ -389,12 +404,27 @@ class TestPreprocessor : public TestFixture { ASSERT(settings.library.load("", library, false).errorcode == Library::ErrorCode::OK); std::vector files; simplecpp::OutputList outputList; - simplecpp::TokenList tokens(code,files,"test.c",&outputList); - Preprocessor preprocessor(tokens, settings, *this, Standards::Language::C); // TODO: do we need to consider #file? - ASSERT(preprocessor.loadFiles(files)); - ASSERT(!preprocessor.reportOutput(outputList, true)); + simplecpp::TokenList tokens(code, files, "test.c", {}, &outputList); + Preprocessor preprocessor(tokens, settings, *this, Standards::Language::C); + std::set configs = { "" }; + std::set configDefines = { "__cplusplus" }; + const auto getDefineName = [](const std::string &defineString) { + return defineString.substr(0, defineString.find_first_of("( ")); + }; + std::transform(settings.library.defines().begin(), + settings.library.defines().end(), + std::inserter(configDefines, configDefines.end()), + getDefineName); + preprocessor.setLoadCallback([&](simplecpp::FileData &data, bool loaded) { + if (loaded) { + Preprocessor::removeComments(data.tokens); + preprocessor.getConfigs(data.filename, data.tokens, configDefines, configs); + } + }); preprocessor.removeComments(); - const std::set configs = preprocessor.getConfigs(); + preprocessor.getConfigs(configDefines, configs); + ASSERT(preprocessor.loadAllIncludes(files)); + ASSERT(!preprocessor.reportOutput(outputList, true)); std::string ret; for (const std::string & config : configs) ret += config + '\n'; @@ -405,9 +435,14 @@ class TestPreprocessor : public TestFixture { std::size_t getHash(const char (&code)[size]) { std::vector files; simplecpp::TokenList tokens(code,files,"test.c"); - Preprocessor preprocessor(tokens, settingsDefault, *this, Standards::Language::C); // TODO: do we need to consider #file? - ASSERT(preprocessor.loadFiles(files)); + Preprocessor preprocessor(tokens, settingsDefault, *this, Standards::Language::C); + preprocessor.setLoadCallback([](simplecpp::FileData &data, bool loaded) { + if (loaded) { + Preprocessor::removeComments(data.tokens); + } + }); preprocessor.removeComments(); + ASSERT(preprocessor.loadAllIncludes(files)); return preprocessor.calculateHash(""); } @@ -416,7 +451,7 @@ class TestPreprocessor : public TestFixture { "cpp\n" "#else\n" "c\n" - "#endif"; + "#endif\n"; { // Preprocess => actual result.. @@ -470,16 +505,19 @@ class TestPreprocessor : public TestFixture { void error4() { // In included file { + ScopedFile header("ab.h", "#error hello world!\n"); const auto settings = dinit(Settings, $.userDefines = "TEST"); - const char code[] = "#file \"ab.h\"\n#error hello world!\n#endfile"; + const char code[] = "#include \"ab.h\"\n"; (void)getcodeforcfg(settings, *this, code, "TEST", "test.c"); ASSERT_EQUALS("[ab.h:1:2]: (error) #error hello world! [preprocessorErrorDirective]\n", errout_str()); } // After including a file { + ScopedFile header("ab.h", ""); const auto settings = dinit(Settings, $.userDefines = "TEST"); - const char code[] = "#file \"ab.h\"\n\n#endfile\n#error aaa"; + const char code[] = "#include \"ab.h\"\n" + "#error aaa\n"; (void)getcodeforcfg(settings, *this, code, "TEST", "test.c"); ASSERT_EQUALS("[test.c:2:2]: (error) #error aaa [preprocessorErrorDirective]\n", errout_str()); } @@ -548,7 +586,7 @@ class TestPreprocessor : public TestFixture { "\n" "#ifndef C\n" "#error aa\n" - "#endif"; + "#endif\n"; ASSERT_EQUALS("A=A;B=B;C\nA=A;C\nC\n", getConfigsStr(filedata)); } @@ -582,35 +620,35 @@ class TestPreprocessor : public TestFixture { } void includeguard1() { + ScopedFile header("abc.h", + "#ifndef abcH\n" + "#define abcH\n" + "#endif\n"); // Handling include guards.. - const char filedata[] = "#file \"abc.h\"\n" - "#ifndef abcH\n" - "#define abcH\n" - "#endif\n" - "#endfile\n" + const char filedata[] = "#include \"abc.h\"\n" "#ifdef ABC\n" - "#endif"; + "#endif\n"; ASSERT_EQUALS("\nABC=ABC\n", getConfigsStr(filedata)); } void includeguard2() { + ScopedFile header("abc.h", + "foo\n" + "#ifdef ABC\n" + "\n" + "#endif\n"); // Handling include guards.. - const char filedata[] = "#file \"abc.h\"\n" - "foo\n" - "#ifdef ABC\n" - "\n" - "#endif\n" - "#endfile\n"; + const char filedata[] = "#include \"abc.h\"\n"; ASSERT_EQUALS("\nABC=ABC\n", getConfigsStr(filedata)); } void ifdefwithfile() { + ScopedFile header("abc.h", "class A{};/*\n\n\n\n\n\n\n*/\n"); + // Handling include guards.. const char filedata[] = "#ifdef ABC\n" - "#file \"abc.h\"\n" - "class A{};/*\n\n\n\n\n\n\n*/\n" - "#endfile\n" + "#include \"abc.h\"\n" "#endif\n" "int main() {}\n"; @@ -925,7 +963,7 @@ class TestPreprocessor : public TestFixture { void ticket_4922() { // #4922 const char code[] = "__asm__ \n" "{ int extern __value) 0; (double return (\"\" } extern\n" - "__typeof __finite (__finite) __finite __inline \"__GI___finite\");"; + "__typeof __finite (__finite) __finite __inline \"__GI___finite\");\n"; (void)getcode(settings0, *this, code); } @@ -1036,7 +1074,7 @@ class TestPreprocessor : public TestFixture { void macro_simple16() { // # 4703 const char filedata[] = "#define MACRO( A, B, C ) class A##B##C##Creator {};\n" - "MACRO( B\t, U , G )"; + "MACRO( B\t, U , G )\n"; ASSERT_EQUALS("\nclass BUGCreator { } ;", expandMacros(filedata, *this)); } @@ -1044,41 +1082,41 @@ class TestPreprocessor : public TestFixture { // It would probably be OK if the generated code was // "\n123+$123" since the first 123 comes from the source code const char filedata[] = "#define MACRO(A) A+123\n" - "MACRO(123)"; + "MACRO(123)\n"; ASSERT_EQUALS("\n123 + 123", expandMacros(filedata, *this)); } void macro_simple18() { // (1e-7) const char filedata1[] = "#define A (1e-7)\n" - "a=A;"; + "a=A;\n"; ASSERT_EQUALS("\na = ( 1e-7 ) ;", expandMacros(filedata1, *this)); const char filedata2[] = "#define A (1E-7)\n" - "a=A;"; + "a=A;\n"; ASSERT_EQUALS("\na = ( 1E-7 ) ;", expandMacros(filedata2, *this)); const char filedata3[] = "#define A (1e+7)\n" - "a=A;"; + "a=A;\n"; ASSERT_EQUALS("\na = ( 1e+7 ) ;", expandMacros(filedata3, *this)); const char filedata4[] = "#define A (1.e+7)\n" - "a=A;"; + "a=A;\n"; ASSERT_EQUALS("\na = ( 1.e+7 ) ;", expandMacros(filedata4, *this)); const char filedata5[] = "#define A (1.7f)\n" - "a=A;"; + "a=A;\n"; ASSERT_EQUALS("\na = ( 1.7f ) ;", expandMacros(filedata5, *this)); const char filedata6[] = "#define A (.1)\n" - "a=A;"; + "a=A;\n"; ASSERT_EQUALS("\na = ( .1 ) ;", expandMacros(filedata6, *this)); const char filedata7[] = "#define A (1.)\n" - "a=A;"; + "a=A;\n"; ASSERT_EQUALS("\na = ( 1. ) ;", expandMacros(filedata7, *this)); const char filedata8[] = "#define A (8.0E+007)\n" - "a=A;"; + "a=A;\n"; ASSERT_EQUALS("\na = ( 8.0E+007 ) ;", expandMacros(filedata8, *this)); } @@ -1250,8 +1288,8 @@ class TestPreprocessor : public TestFixture { void macro_NULL() { // See ticket #4482 - UB when passing NULL to variadic function - ASSERT_EQUALS("\n0", expandMacros("#define null 0\nnull", *this)); - TODO_ASSERT_EQUALS("\nNULL", "\n0", expandMacros("#define NULL 0\nNULL", *this)); // TODO: Let the tokenizer handle NULL? + ASSERT_EQUALS("\n0", expandMacros("#define null 0\nnull\n", *this)); + TODO_ASSERT_EQUALS("\nNULL\n", "\n0", expandMacros("#define NULL 0\nNULL\n", *this)); // TODO: Let the tokenizer handle NULL? } void string1() { @@ -1501,7 +1539,7 @@ class TestPreprocessor : public TestFixture { "#pragma asm foo\n" " mov r1, 11\n" "#pragma endasm bar\n" - "bbb"; + "bbb\n"; // Preprocess => actual result.. const std::map actual = getcode(settings0, *this, filedata); @@ -1515,7 +1553,7 @@ class TestPreprocessor : public TestFixture { const char filedata[] = "#pragma asm\n" " mov @w1, 11\n" "#pragma endasm ( temp=@w1 )\n" - "bbb"; + "bbb\n"; // Preprocess => actual result.. const std::map actual = getcode(settings0, *this, filedata); @@ -1574,22 +1612,9 @@ class TestPreprocessor : public TestFixture { } { - const char filedata[] = "#file \"abc.h\"\n" - "#define a\n" - "\"\n" - "#endfile\n"; - - // expand macros.. - const std::string actual(expandMacros(filedata, *this)); - - ASSERT_EQUALS("", actual); - ASSERT_EQUALS("[abc.h:2:1]: (error) No pair for character (\"). Can't process file. File is either invalid or unicode, which is currently not supported. [syntaxError]\n", errout_str()); - } - - { - const char filedata[] = "#file \"abc.h\"\n" - "#define a\n" - "#endfile\n" + ScopedFile header("abc.h", + "#define a\n"); + const char filedata[] = "#include \"abc.h\"\n" "\"\n"; // expand macros.. @@ -1650,7 +1675,7 @@ class TestPreprocessor : public TestFixture { "#else\n" "#define N 20\n" "#endif\n" - "N"; + "N\n"; // Preprocess => actual result.. const std::map actual = getcode(settings0, *this, filedata); @@ -1718,14 +1743,14 @@ class TestPreprocessor : public TestFixture { const char filedata[] = "#define A 0\n" "#if A\n" "FOO\n" - "#endif"; + "#endif\n"; ASSERT_EQUALS("", getcodeforcfg(settings0, *this, filedata,"","test.c")); } { const char filedata[] = "#define A 1\n" "#if A==1\n" "FOO\n" - "#endif"; + "#endif\n"; ASSERT_EQUALS("\n\nFOO", getcodeforcfg(settings0, *this, filedata,"","test.c")); } } @@ -1735,7 +1760,7 @@ class TestPreprocessor : public TestFixture { "#define B A\n" "#if (B==A) || (B==C)\n" "FOO\n" - "#endif"; + "#endif\n"; ASSERT_EQUALS("\n\n\nFOO", getcodeforcfg(settings0, *this, filedata,"","test.c")); } @@ -1743,7 +1768,7 @@ class TestPreprocessor : public TestFixture { const char filedata[] = "#define A 0\n" "#if (A==0)\n" "FOO\n" - "#endif"; + "#endif\n"; ASSERT_EQUALS("\n\nFOO", getcodeforcfg(settings0, *this, filedata,"","test.c")); } @@ -1751,7 +1776,7 @@ class TestPreprocessor : public TestFixture { const char filedata[] = "#define X +123\n" "#if X==123\n" "FOO\n" - "#endif"; + "#endif\n"; ASSERT_EQUALS("\n\nFOO", getcodeforcfg(settings0, *this, filedata,"","test.c")); } @@ -1761,7 +1786,7 @@ class TestPreprocessor : public TestFixture { "#define B (A & 0x00f0)\n" "#if B==0x0010\n" "FOO\n" - "#endif"; + "#endif\n"; ASSERT_EQUALS("\n\n\nFOO", getcodeforcfg(settings0, *this, filedata,"","test.c")); } { @@ -1770,14 +1795,14 @@ class TestPreprocessor : public TestFixture { "#define C (B & A)\n" "#if C==0x0010\n" "FOO\n" - "#endif"; + "#endif\n"; ASSERT_EQUALS("\n\n\n\nFOO", getcodeforcfg(settings0, *this, filedata,"","test.c")); } { const char filedata[] = "#define A (1+A)\n" // don't hang for recursive macros "#if A==1\n" "FOO\n" - "#endif"; + "#endif\n"; ASSERT_EQUALS("\n\nFOO", getcodeforcfg(settings0, *this, filedata,"","test.c")); } } @@ -1909,7 +1934,7 @@ class TestPreprocessor : public TestFixture { const char filedata[] = "#ifndef A\n" "#define A(x) x\n" "#endif\n" - "A(123);"; + "A(123);\n"; // Preprocess => actual result.. const std::map actual = getcode(settings0, *this, filedata); @@ -1994,12 +2019,12 @@ class TestPreprocessor : public TestFixture { void invalid_define_1() { (void)getcode(settings0, *this, "#define =\n"); - ASSERT_EQUALS("[file.c:1:2]: (error) Failed to parse #define [syntaxError]\n", errout_str()); + ASSERT_EQUALS("[file.c:1:2]: (error) Failed to parse #define, bad macro syntax [syntaxError]\n", errout_str()); } void invalid_define_2() { // #4036 (void)getcode(settings0, *this, "#define () {(int f(x) }\n"); - ASSERT_EQUALS("[file.c:1:2]: (error) Failed to parse #define [syntaxError]\n", errout_str()); + ASSERT_EQUALS("[file.c:1:2]: (error) Failed to parse #define, bad macro syntax [syntaxError]\n", errout_str()); } void inline_suppressions() { @@ -2095,7 +2120,7 @@ class TestPreprocessor : public TestFixture { } void remarkComment4() { - const char code[] = "//REMARK /"; + const char code[] = "//REMARK /\n"; const auto remarkComments = getRemarkComments(code, *this); ASSERT_EQUALS(0, remarkComments.size()); } @@ -2144,19 +2169,19 @@ class TestPreprocessor : public TestFixture { void predefine5() { // #3737, #5119 - automatically define __cplusplus // #3737... - const char code[] = "#ifdef __cplusplus\n123\n#endif"; + const char code[] = "#ifdef __cplusplus\n123\n#endif\n"; ASSERT_EQUALS("", getcodeforcfg(settings0, *this, code, "", "test.c")); ASSERT_EQUALS("\n123", getcodeforcfg(settings0, *this, code, "", "test.cpp")); } void predefine6() { // automatically define __STDC_VERSION__ - const char code[] = "#ifdef __STDC_VERSION__\n123\n#endif"; + const char code[] = "#ifdef __STDC_VERSION__\n123\n#endif\n"; ASSERT_EQUALS("\n123", getcodeforcfg(settings0, *this, code, "", "test.c")); ASSERT_EQUALS("", getcodeforcfg(settings0, *this, code, "", "test.cpp")); } void strictAnsi() { - const char code[] = "#ifdef __STRICT_ANSI__\n123\n#endif"; + const char code[] = "#ifdef __STRICT_ANSI__\n123\n#endif\n"; Settings settings; settings.standards.setStd("gnu99"); @@ -2284,14 +2309,14 @@ class TestPreprocessor : public TestFixture { } void getConfigs7e() { + ScopedFile header("test.h", + "#ifndef test_h\n" + "#define test_h\n" + "#ifdef ABC\n" + "#endif\n" + "#endif\n"); const char filedata[] = "#ifdef ABC\n" - "#file \"test.h\"\n" - "#ifndef test_h\n" - "#define test_h\n" - "#ifdef ABC\n" - "#endif\n" - "#endif\n" - "#endfile\n" + "#include \"test.h\"\n" "#endif\n"; ASSERT_EQUALS("\nABC=ABC\n", getConfigsStr(filedata)); } @@ -2308,17 +2333,17 @@ class TestPreprocessor : public TestFixture { "#define bar foo\n" "#define baz bar+0\n" "#if 0\n" - "#endif"; + "#endif\n"; ASSERT_EQUALS("\n", getConfigsStr(filedata)); } void getConfigs11() { // #9832 - include guards - const char filedata[] = "#file \"test.h\"\n" - "#if !defined(test_h)\n" - "#define test_h\n" - "123\n" - "#endif\n" - "#endfile\n"; + ScopedFile header("test.h", + "#if !defined(test_h)\n" + "#define test_h\n" + "123\n" + "#endif\n"); + const char filedata[] = "#include \"test.h\"\n"; ASSERT_EQUALS("\n", getConfigsStr(filedata)); } @@ -2715,13 +2740,40 @@ class TestPreprocessor : public TestFixture { ASSERT_EQUALS("\nX\nY=Y\nZ\n", getConfigsStr(filedata)); } + void getConfigsInvalid() { // #14732 + { + const char filedata[] = "#if<\n"; + ASSERT_EQUALS("\n", getConfigsStr(filedata)); + } + { + const char filedata[] = "#if>\n"; + ASSERT_EQUALS("\n", getConfigsStr(filedata)); + } + { + const char filedata[] = "#if==\n"; + ASSERT_EQUALS("\n", getConfigsStr(filedata)); + } + { + const char filedata[] = "#if<=\n"; + ASSERT_EQUALS("\n", getConfigsStr(filedata)); + } + { + const char filedata[] = "#if>=\n"; + ASSERT_EQUALS("\n", getConfigsStr(filedata)); + } + { + const char filedata[] = "#if!\n"; + ASSERT_EQUALS("\n", getConfigsStr(filedata)); + } + } + void if_sizeof() { // #4071 const char code[] = "#if sizeof(unsigned short) == 2\n" "Fred & Wilma\n" "#elif sizeof(unsigned short) == 4\n" "Fred & Wilma\n" "#else\n" - "#endif"; + "#endif\n"; const std::map actual = getcode(settings0, *this, code); ASSERT_EQUALS("\nFred & Wilma", actual.at("")); @@ -2749,7 +2801,7 @@ class TestPreprocessor : public TestFixture { void garbage() { const char filedata[] = "V\n" "#define X b #endif #line 0 \"x\" ;\n" - "#if ! defined ( Y ) #endif"; + "#if ! defined ( Y ) #endif\n"; // Preprocess => don't crash.. (void)getcode(settings0, *this, filedata); @@ -2773,7 +2825,7 @@ class TestPreprocessor : public TestFixture { ScopedFile header("header.h", ""); - const char code[] = "#include \"header.h\""; + const char code[] = "#include \"header.h\"\n"; (void)getcodeforcfg(settings, *this, code, "", "test.c"); ASSERT_EQUALS("", errout_str()); @@ -2788,7 +2840,7 @@ class TestPreprocessor : public TestFixture { ); setTemplateFormat("simple"); - const char code[] = "#include \"header.h\""; + const char code[] = "#include \"header.h\"\n"; (void)getcodeforcfg(settings, *this, code, "", "test.c"); ASSERT_EQUALS("test.c:1:2: information: Include file: \"header.h\" not found. [missingInclude]\n", errout_str()); @@ -2805,7 +2857,7 @@ class TestPreprocessor : public TestFixture { ScopedFile header("header.h", "", "inc"); - const char code[] = "#include \"header.h\""; + const char code[] = "#include \"header.h\"\n"; (void)getcodeforcfg(settings, *this, code, "", "test.c"); ASSERT_EQUALS("test.c:1:2: information: Include file: \"header.h\" not found. [missingInclude]\n", errout_str()); @@ -2823,7 +2875,7 @@ class TestPreprocessor : public TestFixture { ScopedFile header("header.h", "", "inc"); - const char code[] = "#include \"inc/header.h\""; + const char code[] = "#include \"inc/header.h\"\n"; (void)getcodeforcfg(settings, *this, code, "", "test.c"); ASSERT_EQUALS("", errout_str()); @@ -2841,7 +2893,7 @@ class TestPreprocessor : public TestFixture { ScopedFile header("header.h", "", Path::getCurrentPath()); - std::string code("#include \"" + header.path() + "\""); + std::string code("#include \"" + header.path() + "\"\n"); (void)getcodeforcfg(settings, *this, code.data(), code.size(), "", "test.c"); ASSERT_EQUALS("", errout_str()); @@ -2858,7 +2910,7 @@ class TestPreprocessor : public TestFixture { const std::string header = Path::join(Path::getCurrentPath(), "header.h"); - std::string code("#include \"" + header + "\""); + std::string code("#include \"" + header + "\"\n"); (void)getcodeforcfg(settings, *this, code.data(), code.size(), "", "test.c"); ASSERT_EQUALS("test.c:1:2: information: Include file: \"" + header + "\" not found. [missingInclude]\n", errout_str()); @@ -2875,7 +2927,7 @@ class TestPreprocessor : public TestFixture { ScopedFile header("header.h", ""); - const char code[] = "#include "; + const char code[] = "#include \n"; (void)getcodeforcfg(settings, *this, code, "", "test.c"); ASSERT_EQUALS("test.c:1:2: information: Include file: not found. Please note: Standard library headers do not need to be provided to get proper results. [missingIncludeSystem]\n", errout_str()); @@ -2890,7 +2942,7 @@ class TestPreprocessor : public TestFixture { ); setTemplateFormat("simple"); - const char code[] = "#include "; + const char code[] = "#include \n"; (void)getcodeforcfg(settings, *this, code, "", "test.c"); ASSERT_EQUALS("test.c:1:2: information: Include file: not found. Please note: Standard library headers do not need to be provided to get proper results. [missingIncludeSystem]\n", errout_str()); @@ -2908,7 +2960,7 @@ class TestPreprocessor : public TestFixture { ScopedFile header("header.h", "", "system"); - const char code[] = "#include "; + const char code[] = "#include \n"; (void)getcodeforcfg(settings, *this, code, "", "test.c"); ASSERT_EQUALS("", errout_str()); @@ -2926,7 +2978,7 @@ class TestPreprocessor : public TestFixture { ScopedFile header("header.h", "", Path::getCurrentPath()); - std::string code("#include <" + header.path() + ">"); + std::string code("#include <" + header.path() + ">\n"); (void)getcodeforcfg(settings, *this, code.data(), code.size(), "", "test.c"); ASSERT_EQUALS("", errout_str()); @@ -2943,7 +2995,7 @@ class TestPreprocessor : public TestFixture { const std::string header = Path::join(Path::getCurrentPath(), "header.h"); - std::string code("#include <" + header + ">"); + std::string code("#include <" + header + ">\n"); (void)getcodeforcfg(settings, *this, code.data(), code.size(), "", "test.c"); ASSERT_EQUALS("test.c:1:2: information: Include file: <" + header + "> not found. Please note: Standard library headers do not need to be provided to get proper results. [missingIncludeSystem]\n", errout_str()); @@ -2965,7 +3017,7 @@ class TestPreprocessor : public TestFixture { const char code[] = "#include \"missing.h\"\n" "#include \n" "#include \n" - "#include \"header2.h\""; + "#include \"header2.h\"\n"; (void)getcodeforcfg(settings, *this, code, "", "test.c"); ASSERT_EQUALS("test.c:1:2: information: Include file: \"missing.h\" not found. [missingInclude]\n" @@ -3013,8 +3065,84 @@ class TestPreprocessor : public TestFixture { "test.c:11:2: information: Include file: <" + missing4 + "> not found. Please note: Standard library headers do not need to be provided to get proper results. [missingIncludeSystem]\n", errout_str()); } + void testLazyInclude() { + const char *code = "#ifdef CONFIG1\n" + "#include \"header1.h\"\n" + "#include \"missing1.h\"\n" + "#else\n" + "#include \"header2.h\"\n" + "#include \"missing2.h\"\n" + "#endif\n"; + + std::vector files; + simplecpp::TokenList tokens(code, files, "test.c"); + + ScopedFile header1("header1.h", "1\n"); + ScopedFile header2("header2.h", "2\n"); + + Settings settings; + Preprocessor preprocessor(tokens, settings, *this, Standards::Language::CPP); + + simplecpp::OutputList outputList; + simplecpp::TokenList tokens2 = preprocessor.preprocess("CONFIG1", files, outputList); + std::string out = tokens2.stringify(); + + const simplecpp::FileDataCache &cache = preprocessor.mFileCache; + + ASSERT_EQUALS("\n#line 1 \"header1.h\"\n1", out); + ASSERT_EQUALS(1, outputList.size()); + ASSERT_EQUALS("Header not found: \"missing1.h\"", outputList.begin()->msg); + ASSERT_EQUALS(1, cache.size()); + } + + void testKeepComments() { + const char *code = "#include \"header.h\"\n" + "// source file comment\n" + "/* source file comment */\n"; + std::vector files; + simplecpp::TokenList tokens(code, files, "test.c"); + + ScopedFile header("header.h", + "// header comment\n" + "/* header comment */\n"); + + Settings settings; + settings.keepComments = true; + Preprocessor preprocessor(tokens, settings, *this, Standards::Language::CPP); + + simplecpp::OutputList outputList; + simplecpp::TokenList tokens2 = preprocessor.preprocess("", files, outputList); + std::string out = tokens2.stringify(); + + const char *expected = "\n" + "#line 1 \"header.h\"\n" + "// header comment\n" + "/* header comment */\n" + "#line 2 \"test.c\"\n" + "// source file comment\n" + "/* source file comment */"; + ASSERT_EQUALS(expected, out); + } + + void testKeepCommentsMultiline() { + const char *code = "/* multi...\n" + " ...line */\n" + "int x;\n"; + std::vector files; + simplecpp::TokenList tokens(code, files, "test.c"); + + Settings settings; + settings.keepComments = true; + Preprocessor preprocessor(tokens, settings, *this, Standards::Language::C); + + const char *expected = "/* multi...\n" + " ...line */\n" + "int x ;"; + ASSERT_EQUALS(expected, preprocessor.getcode("", files, false)); + } + void hasInclude() { - const char code[] = "#if __has_include()\n123\n#endif"; + const char code[] = "#if __has_include()\n123\n#endif\n"; Settings settings; settings.standards.setStd("c++11"); @@ -3034,7 +3162,7 @@ class TestPreprocessor : public TestFixture { // #11928 / #10045 const char code[] = "void f(long l) {\n" " if (l > INT_MAX) {}\n" - "}"; + "}\n"; const std::string actual = getcodeforcfg(settings0, *this, code, "", "test.c"); ASSERT_EQUALS("void f ( long l ) {\n" "if ( l > $2147483647 ) { }\n" @@ -3043,9 +3171,9 @@ class TestPreprocessor : public TestFixture { void hashCalculation() { // #12383 - const char code[] = "int a;"; - const char code2[] = "int a;"; // extra space - const char code3[] = "\n\nint a;"; // extra new line + const char code[] = "int a;\n"; + const char code2[] = "int a;\n"; // extra space + const char code3[] = "\n\nint a;\n"; // extra new line ASSERT_EQUALS(getHash(code), getHash(code)); ASSERT_EQUALS(getHash(code2), getHash(code2)); @@ -3055,9 +3183,28 @@ class TestPreprocessor : public TestFixture { ASSERT(getHash(code2) != getHash(code3)); } + void hashCalculationIncludeOrder() { + // #15132 - the load order of the includes must not change the hash + const ScopedFile headerA("hash_a.h", "int a;\n"); + const ScopedFile headerZ("hash_z.h", "int z;\n"); + const char code[] = "#include \"hash_z.h\"\n" + "#include \"hash_a.h\"\n"; + + const auto getHashLoadOrder = [&](const std::vector &loadOrder) { + std::vector files; + simplecpp::TokenList tokens(code, files, "test.c"); + Preprocessor preprocessor(tokens, settingsDefault, *this, Standards::Language::C); + for (const std::string &header : loadOrder) + ASSERT(preprocessor.loadFile(files, header)); + return preprocessor.calculateHash(""); + }; + + ASSERT_EQUALS(getHashLoadOrder({"hash_z.h", "hash_a.h"}), getHashLoadOrder({"hash_a.h", "hash_z.h"})); + } + void standard() { - const char code[] = "int a;"; + const char code[] = "int a;\n"; // TODO: this bypasses the standard determined from the settings - the parameter should not be exposed simplecpp::DUI dui; @@ -3098,7 +3245,7 @@ class TestPreprocessor : public TestFixture { std::vector files; TokenList tokenlist{settingsDefault, Standards::Language::CPP}; // TODO: can this happen from application code? if yes we need to turn it into a proper error - ASSERT_THROW_EQUALS(preprocess(code, files, "test.cpp", tokenlist, dui), std::runtime_error, "unexpected simplecpp::Output type 9"); + ASSERT_THROW_EQUALS(preprocess(code, files, "test.cpp", tokenlist, dui), std::runtime_error, "unexpected simplecpp::Output type 12"); ASSERT(!tokenlist.front()); // nothing is tokenized when an unknown standard is provided } } @@ -3108,8 +3255,8 @@ class TestPreprocessor : public TestFixture { const char inc[] = "class A {\n" "public:\n" " void f() {}\n" - "};"; - const char code[] = R"(#include "test.h")"; + "};\n"; + const char code[] = "#include \"test.h\"\n"; ScopedFile header("test.h", inc); const std::string processed = getcodeforcfg(settingsDefault, *this, code, "", "test.cpp"); ASSERT_EQUALS( @@ -3124,9 +3271,33 @@ class TestPreprocessor : public TestFixture { void pragmaAsm() { - const char code[] = "#pragma asm"; + const char code[] = "#pragma asm\n"; ASSERT_THROW_INTERNAL(getcodeforcfg(settingsDefault, *this, code, "", "test.cpp"), InternalError::SYNTAX); } + + void dumpPreprocessor() { + const char code[] = "#if M == 0\n" + "#endif\n;\n"; + std::vector files; + simplecpp::OutputList outputList; + + Settings settings; + settings.relativePaths = true; + settings.basePaths.emplace_back("/some/path"); + + simplecpp::TokenList tokens1(code, files, "/some/path/test.cpp", {}, &outputList); + Preprocessor preprocessor(tokens1, settings, *this, Standards::Language::CPP); + (void)preprocessor.preprocess("", files, outputList); + (void)preprocessor.reportOutput(outputList, true); + + std::ostringstream ostr; + preprocessor.dump(ostr); + ASSERT_EQUALS( + " \n" + " \n" + " \n", + ostr.str()); + } }; REGISTER_TEST(TestPreprocessor) diff --git a/test/testprocessexecutor.cpp b/test/testprocessexecutor.cpp index 10562088f56..87b03d915f8 100644 --- a/test/testprocessexecutor.cpp +++ b/test/testprocessexecutor.cpp @@ -64,7 +64,7 @@ class TestProcessExecutorBase : public TestFixture { struct CheckOptions { bool quiet = true; - ShowTime showtime = ShowTime::NONE; + Settings::ShowTime showtime = Settings::ShowTime::NONE; const char* plistOutput = nullptr; std::vector filesList; }; @@ -104,7 +104,9 @@ class TestProcessExecutorBase : public TestFixture { s.plistOutput = opt.plistOutput; s.templateFormat = "{callstack}: ({severity}) {inconclusive:inconclusive: }{message}"; Suppressions supprs; - TimerResults timerResults; + std::unique_ptr timerResults; + if (s.showtime != Settings::ShowTime::NONE) + timerResults.reset(new TimerResults); // NOLINTNEXTLINE(performance-unnecessary-value-param) auto executeFn = [](std::string,std::vector,std::string,std::string&){ @@ -120,7 +122,7 @@ class TestProcessExecutorBase : public TestFixture { if (useFS) filelist.clear(); - ProcessExecutor executor(filelist, fileSettings, s, supprs, *this, &timerResults, executeFn); + ProcessExecutor executor(filelist, fileSettings, s, supprs, *this, timerResults.get(), executeFn); ASSERT_EQUALS(result, executor.check()); } #endif // HAS_THREADING_MODEL_FORK @@ -177,7 +179,7 @@ class TestProcessExecutorBase : public TestFixture { "void f()\n" "{\n" " (void)(*((int*)0));\n" - "}", dinit(CheckOptions, $.showtime = ShowTime::SUMMARY)); + "}", dinit(CheckOptions, $.showtime = Settings::ShowTime::SUMMARY)); // we are not interested in the results - so just consume them ignore_errout(); } @@ -246,7 +248,7 @@ class TestProcessExecutorBase : public TestFixture { check(2, 2, 0, "int main() {}", dinit(CheckOptions, - $.showtime = ShowTime::TOP5_FILE)); + $.showtime = Settings::ShowTime::TOP5_FILE)); const std::string output_s = GET_REDIRECT_OUTPUT; // for each file: top5 results + check time TODO_ASSERT_EQUALS(static_cast(5 + 1) * 2, 0, cppcheck::count_all_of(output_s, '\n')); @@ -257,7 +259,7 @@ class TestProcessExecutorBase : public TestFixture { check(2, 2, 0, "int main() {}", dinit(CheckOptions, - $.showtime = ShowTime::FILE)); + $.showtime = Settings::ShowTime::FILE)); const std::string output_s = GET_REDIRECT_OUTPUT; TODO_ASSERT_EQUALS(2, 0, cppcheck::count_all_of(output_s, "Overall time:")); } @@ -267,7 +269,7 @@ class TestProcessExecutorBase : public TestFixture { check(2, 2, 0, "int main() {}", dinit(CheckOptions, - $.showtime = ShowTime::FILE_TOTAL)); + $.showtime = Settings::ShowTime::FILE_TOTAL)); const std::string output_s = GET_REDIRECT_OUTPUT; TODO_ASSERT(output_s.find("Check time: " + fprefix() + "_1.c: ") != std::string::npos); TODO_ASSERT(output_s.find("Check time: " + fprefix() + "_2.c: ") != std::string::npos); diff --git a/test/testprogrammemory.cpp b/test/testprogrammemory.cpp index 688efa0075b..c37431de608 100644 --- a/test/testprogrammemory.cpp +++ b/test/testprogrammemory.cpp @@ -39,7 +39,7 @@ class TestProgramMemory : public TestFixture { } void copyOnWrite() const { - SimpleTokenList tokenlist("1+1;"); + SimpleTokenList tokenlist("1+1;\n"); Token* tok = tokenlist.front(); const nonneg int id = 123; tok->exprId(id); diff --git a/test/testrefthunk.cpp b/test/testrefthunk.cpp new file mode 100644 index 00000000000..f9ae525295a --- /dev/null +++ b/test/testrefthunk.cpp @@ -0,0 +1,134 @@ +/* + * Cppcheck - A tool for static C/C++ code analysis + * Copyright (C) 2007-2026 Cppcheck team. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "fixture.h" +#include "refthunk.h" + +#include +#include + +class TestRefThunk : public TestFixture { +public: + TestRefThunk() : TestFixture("TestRefThunk") {} + +private: + void run() override { + TEST_CASE(callAccess); + TEST_CASE(getReference); + TEST_CASE(referenceConversion); + TEST_CASE(memberAccess); + TEST_CASE(mutation); + TEST_CASE(copyConstruct); + TEST_CASE(copyAssign); + TEST_CASE(rebind); + TEST_CASE(convertToConst); + TEST_CASE(functionArguments); + } + + void callAccess() const { + int x = 42; + const RefThunk p(x); + ASSERT_EQUALS(42, p()); + ASSERT(&p() == &x); + } + + void getReference() const { + int x = 1; + const RefThunk p(x); + ASSERT(&p.get() == &x); + p.get() = 2; + ASSERT_EQUALS(2, x); + } + + void referenceConversion() const { + int x = 1; + const RefThunk p(x); + int& ref = p; + ASSERT(&ref == &x); + ref = 2; + ASSERT_EQUALS(2, x); + } + + void memberAccess() const { + const std::string s = "abc"; + const RefThunk p(s); + ASSERT_EQUALS(3, p().size()); + ASSERT_EQUALS("abc", p()); + } + + void mutation() const { + std::string s = "abc"; + const RefThunk p(s); + p() += "d"; + p().push_back('e'); + ASSERT_EQUALS("abcde", s); + } + + void copyConstruct() const { + int x = 1; + const RefThunk p(x); + const RefThunk q(p); + ASSERT(&q() == &x); + } + + void copyAssign() const { + int x = 1; + int y = 2; + RefThunk p(x); + const RefThunk q(y); + ASSERT(&p() == &x); + p = q; + ASSERT(&p() == &y); + ASSERT_EQUALS(2, p()); + } + + void rebind() const { + int x = 1; + int y = 2; + RefThunk p(x); + ASSERT(&p() == &x); + // assignment rebinds, like std::reference_wrapper - it does not write through + p = y; + ASSERT(&p() == &y); + ASSERT_EQUALS(1, x); + } + + void convertToConst() const { + int x = 1; + const RefThunk p(x); + const RefThunk cp(p); + ASSERT(&cp() == &x); + const int& ref = cp; + ASSERT(&ref == &x); + } + + static std::size_t refArg(const std::string& s) { + return s.size(); + } + + void functionArguments() const { + std::string s = "abcd"; + const RefThunk p(s); + // implicit conversion to reference, like std::reference_wrapper + ASSERT_EQUALS(4, refArg(p)); + // explicit access through operator() + ASSERT_EQUALS(4, refArg(p())); + } +}; + +REGISTER_TEST(TestRefThunk) diff --git a/test/testregex.cpp b/test/testregex.cpp index 4b6b2781cc8..f26a9fee4fa 100644 --- a/test/testregex.cpp +++ b/test/testregex.cpp @@ -1,6 +1,6 @@ /* * Cppcheck - A tool for static C/C++ code analysis - * Copyright (C) 2007-2025 Cppcheck team. + * Copyright (C) 2007-2026 Cppcheck team. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -47,8 +47,10 @@ class TestRegExBase : public TestFixture { std::shared_ptr assertRegex_(const char* file, int line, std::string pattern, const std::string& exp_err = "") const { std::string regex_err; auto r = Regex::create(std::move(pattern), mEngine, regex_err); - if (exp_err.empty()) + if (exp_err.empty()) { ASSERT_LOC(!!r.get(), file, line); + ASSERT_EQUALS_ENUM_LOC(mEngine, r->engine(), file, line); + } else ASSERT_LOC(!r.get(), file, line); // only not set if we encountered an error ASSERT_EQUALS_LOC(exp_err, regex_err, file, line); diff --git a/test/testrunner.vcxproj b/test/testrunner.vcxproj index 1c5230c5535..2ce6a97d9c4 100755 --- a/test/testrunner.vcxproj +++ b/test/testrunner.vcxproj @@ -87,6 +87,7 @@ + diff --git a/test/testsettings.cpp b/test/testsettings.cpp index ddd11de3273..8235e75f8c7 100644 --- a/test/testsettings.cpp +++ b/test/testsettings.cpp @@ -42,6 +42,9 @@ class TestSettings : public TestFixture { TEST_CASE(getMaxConfigsDefines); TEST_CASE(getMaxConfigsDefinesAndOpt); TEST_CASE(getMaxConfigsOptAndProject); + + TEST_CASE(isPremiumEnabledCert); + TEST_CASE(isPremiumEnabledMisra); } void simpleEnableGroup() const { @@ -356,6 +359,38 @@ class TestSettings : public TestFixture { s.maxConfigsProject = 10; ASSERT_EQUALS(3, s.getMaxConfigs()); } + + void isPremiumEnabledCert() const { + Settings s; + s.premiumArgs = "--cert-c-2016"; + ASSERT(s.isPremiumEnabled("uninitvar")); + s.premiumArgs = "--cert-c"; + ASSERT(s.isPremiumEnabled("uninitvar")); + s.premiumArgs = "--cert-c:all"; + ASSERT(s.isPremiumEnabled("uninitvar")); + s.premiumArgs = "--cert-c++"; + ASSERT(s.isPremiumEnabled("uninitvar")); + s.premiumArgs = "--cert-cpp"; + ASSERT(s.isPremiumEnabled("uninitvar")); + } + + void isPremiumEnabledMisra() const { + Settings s; + s.premiumArgs = "--misra-c-2012"; + ASSERT(s.isPremiumEnabled("uninitvar")); + s.premiumArgs = "--misra-c-2023"; + ASSERT(s.isPremiumEnabled("uninitvar")); + s.premiumArgs = "--misra-c-2025"; + ASSERT(s.isPremiumEnabled("uninitvar")); + s.premiumArgs = "--misra-cpp-2008"; + ASSERT(s.isPremiumEnabled("uninitvar")); + s.premiumArgs = "--misra-c++-2008"; + ASSERT(s.isPremiumEnabled("uninitvar")); + s.premiumArgs = "--misra-cpp-2023"; + ASSERT(s.isPremiumEnabled("uninitvar")); + s.premiumArgs = "--misra-c++-2023"; + ASSERT(s.isPremiumEnabled("uninitvar")); + } }; REGISTER_TEST(TestSettings) diff --git a/test/testsimplifytemplate.cpp b/test/testsimplifytemplate.cpp index b28bc503869..407649e0c7f 100644 --- a/test/testsimplifytemplate.cpp +++ b/test/testsimplifytemplate.cpp @@ -314,12 +314,15 @@ class TestSimplifyTemplate : public TestFixture { TEST_CASE(requires3); TEST_CASE(requires4); TEST_CASE(requires5); + TEST_CASE(requires6); TEST_CASE(explicitBool1); TEST_CASE(explicitBool2); TEST_CASE(templateArgPreserveType); // #13882 - type of template argument + TEST_CASE(template_shift_negative); // shift folding with a negative operand + TEST_CASE(dumpTemplateArgFrom); } @@ -369,7 +372,7 @@ class TestSimplifyTemplate : public TestFixture { void template1() { const char code[] = "template T f(T val) { T a; }\n" - "f(10);"; + "f(10);\n"; const char expected[] = "int f ( int val ) ; " "f ( 10 ) ; " @@ -380,7 +383,7 @@ class TestSimplifyTemplate : public TestFixture { void template2() { const char code[] = "template class Fred { T a; };\n" - "Fred fred;"; + "Fred fred;\n"; const char expected[] = "class Fred ; " "Fred fred ; " @@ -391,7 +394,7 @@ class TestSimplifyTemplate : public TestFixture { void template3() { const char code[] = "template class Fred { T data[sz]; };\n" - "Fred fred;"; + "Fred fred;\n"; const char expected[] = "class Fred ; " "Fred fred ; " @@ -402,7 +405,7 @@ class TestSimplifyTemplate : public TestFixture { void template4() { const char code[] = "template class Fred { Fred(); };\n" - "Fred fred;"; + "Fred fred;\n"; const char expected[] = "class Fred ; " "Fred fred ; " @@ -414,7 +417,7 @@ class TestSimplifyTemplate : public TestFixture { void template5() { const char code[] = "template class Fred { };\n" "template Fred::Fred() { }\n" - "Fred fred;"; + "Fred fred;\n"; const char expected[] = "class Fred ; " "Fred fred ; " @@ -427,7 +430,7 @@ class TestSimplifyTemplate : public TestFixture { void template6() { const char code[] = "template class Fred { };\n" "Fred fred1;\n" - "Fred fred2;"; + "Fred fred2;\n"; const char expected[] = "class Fred ; " "Fred fred1 ; " @@ -528,7 +531,7 @@ class TestSimplifyTemplate : public TestFixture { "template < typename T > class A { void f ( ) { B < T > a ; a = B < T > :: g ( ) ; T b ; b = 0 ; if ( b ) { b = 0 ; } } } ; " "template < typename T > B < T > h ( ) { return B < T > ( ) ; }", tok(code)); - ASSERT_EQUALS("class A { template < typename T > int foo ( T d ) ; } ;", tok("class A{ template int foo(T d);};")); + ASSERT_EQUALS("class A { template < typename T > int foo ( T d ) ; } ;", tok("class A{ template int foo(T d);};\n")); } void template9() { @@ -633,7 +636,7 @@ class TestSimplifyTemplate : public TestFixture { " y(AA::create(new CC(AA(), 0)))\n" " {}\n" "\n" - "int yy[AA::size()];"; + "int yy[AA::size()];\n"; const char expected[] = "class BB { } ; " "class AA ; " "class AA ; " @@ -709,7 +712,7 @@ class TestSimplifyTemplate : public TestFixture { " vec(const vec& v) {}\n" // <- never used don't instantiate "};\n" "\n" - "vec<4> v;"; + "vec<4> v;\n"; const char expected2[] = "struct vec<4> ; " "vec<4> v ; " "struct vec<4> { " @@ -767,7 +770,7 @@ class TestSimplifyTemplate : public TestFixture { void template18() { const char code[] = "template class foo { T a; };\n" - "foo *f;"; + "foo *f;\n"; const char expected[] = "class foo ; " "foo * f ; " @@ -812,7 +815,7 @@ class TestSimplifyTemplate : public TestFixture { void template21() { { const char code[] = "template struct Fred { T a; };\n" - "Fred fred;"; + "Fred fred;\n"; const char expected[] = "struct Fred ; " "Fred fred ; " @@ -823,7 +826,7 @@ class TestSimplifyTemplate : public TestFixture { { const char code[] = "template struct Fred { T data[sz]; };\n" - "Fred fred;"; + "Fred fred;\n"; const char expected[] = "struct Fred ; " "Fred fred ; " @@ -834,7 +837,7 @@ class TestSimplifyTemplate : public TestFixture { { const char code[] = "template struct Fred { Fred(); };\n" - "Fred fred;"; + "Fred fred;\n"; const char expected[] = "struct Fred ; " "Fred fred ; " @@ -846,7 +849,7 @@ class TestSimplifyTemplate : public TestFixture { { const char code[] = "template struct Fred { };\n" "Fred fred1;\n" - "Fred fred2;"; + "Fred fred2;\n"; const char expected[] = "struct Fred ; " "Fred fred1 ; " @@ -859,7 +862,7 @@ class TestSimplifyTemplate : public TestFixture { void template22() { const char code[] = "template struct Fred { T a; };\n" - "Fred fred;"; + "Fred fred;\n"; const char expected[] = "struct Fred ; " "Fred fred ; " @@ -872,7 +875,7 @@ class TestSimplifyTemplate : public TestFixture { const char code[] = "template void foo() { }\n" "void bar() {\n" " std::cout << (foo());\n" - "}"; + "}\n"; const char expected[] = "void foo ( ) ; " "void bar ( ) {" @@ -893,7 +896,7 @@ class TestSimplifyTemplate : public TestFixture { "template class bitset: B\n" "{};\n" "\n" - "bitset<1> z;"; + "bitset<1> z;\n"; const char expected[] = "struct B<4> ; " "class bitset<1> ; " "bitset<1> z ; " @@ -911,7 +914,7 @@ class TestSimplifyTemplate : public TestFixture { "template class bitset: B<((sizeof(int)) ? : 1)>\n" "{};\n" "\n" - "bitset<1> z;"; + "bitset<1> z;\n"; const char expected[] = "struct B<4> ; " "class bitset<1> ; " "bitset<1> z ; " @@ -934,7 +937,7 @@ class TestSimplifyTemplate : public TestFixture { void template27() { // #3350 - template inside macro call - const char code[] = "X(template class Fred);"; + const char code[] = "X(template class Fred);\n"; ASSERT_THROW_INTERNAL(tok(code), SYNTAX); } @@ -951,19 +954,19 @@ class TestSimplifyTemplate : public TestFixture { void template30() { // #3529 - template < template < .. - const char code[] = "template class A, class B> void f(){}"; + const char code[] = "template class A, class B> void f(){}\n"; ASSERT_EQUALS("template < template < class > class A , class B > void f ( ) { }", tok(code)); } void template31() { // #4010 - template reference type - const char code[] = "template struct A{}; A a;"; + const char code[] = "template struct A{}; A a;\n"; ASSERT_EQUALS("struct A ; " "A a ; " "struct A { } ;", tok(code)); // #7409 - rvalue - const char code2[] = "template struct A{}; A a;"; + const char code2[] = "template struct A{}; A a;\n"; ASSERT_EQUALS("struct A ; " "A a ; " "struct A { } ;", tok(code2)); @@ -993,7 +996,7 @@ class TestSimplifyTemplate : public TestFixture { const char code[] = "template struct A { };\n" "template struct B { };\n" "template struct C { A > > ab; };\n" - "C c;"; + "C c;\n"; ASSERT_EQUALS("struct A>> ; " "struct B> ; " "struct C ; " @@ -1008,7 +1011,7 @@ class TestSimplifyTemplate : public TestFixture { const char code[] = "struct A { };\n" "template struct B { };\n" "template struct C { };\n" - "C< B > c;"; + "C< B > c;\n"; ASSERT_EQUALS("struct A { } ; " "template < class T > struct B { } ; " // <- redundant.. but nevermind "struct C> ; " @@ -1023,13 +1026,13 @@ class TestSimplifyTemplate : public TestFixture { const char code[] = "namespace abc {\n" "template struct X { void f(X &x) {} };\n" "}\n" - "template <> int X::Y(0);"; + "template <> int X::Y(0);\n"; (void)tok(code); } void template35() { // #4074 - "A<'x'> a;" is not recognized as template instantiation const char code[] = "template class A {};\n" - "A <'x'> a;"; + "A <'x'> a;\n"; ASSERT_EQUALS("class A<'x'> ; " "A<'x'> a ; " "class A<'x'> { } ;", tok(code)); @@ -1038,7 +1041,7 @@ class TestSimplifyTemplate : public TestFixture { void template36() { // #4310 - Passing unknown template instantiation as template argument const char code[] = "template struct X { T t; };\n" "template struct Y { Foo < X< Bar > > _foo; };\n" // <- Bar is unknown - "Y bar;"; + "Y bar;\n"; ASSERT_EQUALS("struct X> ; " "struct Y ; " "Y bar ; " @@ -1052,7 +1055,7 @@ class TestSimplifyTemplate : public TestFixture { const char code[] = "class A { };\n" "template class B {};\n" "B b1;\n" - "B b2;"; + "B b2;\n"; ASSERT_EQUALS("class A { } ; class B ; B b1 ; B b2 ; class B { } ;", tok(code)); } @@ -1060,7 +1063,7 @@ class TestSimplifyTemplate : public TestFixture { const char code[] = "struct A { };\n" "template class B {};\n" "B b1;\n" - "B b2;"; + "B b2;\n"; ASSERT_EQUALS("struct A { } ; class B ; B b1 ; B b2 ; class B { } ;", tok(code)); } @@ -1068,7 +1071,7 @@ class TestSimplifyTemplate : public TestFixture { const char code[] = "enum A { };\n" "template class B {};\n" "B b1;\n" - "B b2;"; + "B b2;\n"; ASSERT_EQUALS("enum A { } ; class B ; B b1 ; B b2 ; class B { } ;", tok(code)); } @@ -1076,7 +1079,7 @@ class TestSimplifyTemplate : public TestFixture { void template_unhandled() { // An unhandled template usage should not be simplified.. - ASSERT_EQUALS("x < int > ( ) ;", tok("x();")); + ASSERT_EQUALS("x < int > ( ) ;", tok("x();\n")); } void template38() { // #4832 - Crash on C++11 right angle brackets @@ -1110,21 +1113,21 @@ class TestSimplifyTemplate : public TestFixture { } void template39() { // #4742 - Used to freeze in 1.60 - const char code[] = "template struct vector {" - " operator T() const;" - "};" - "void f() {" - " vector> v;" - " const vector vi = static_cast>(v);" - "}"; + const char code[] = "template struct vector {\n" + " operator T() const;\n" + "};\n" + "void f() {\n" + " vector> v;\n" + " const vector vi = static_cast>(v);\n" + "}\n"; (void)tok(code); } void template40() { // #5055 - false negatives when there is template specialization outside struct - const char code[] = "struct A {" - " template struct X { T t; };" - "};" - "template<> struct A::X { int *t; };"; + const char code[] = "struct A {\n" + " template struct X { T t; };\n" + "};\n" + "template<> struct A::X { int *t; };\n"; const char expected[] = "struct A { " "struct X ; " "template < typename T > struct X { T t ; } ; " @@ -1135,13 +1138,13 @@ class TestSimplifyTemplate : public TestFixture { void template41() { // #4710 - const in template instantiation not handled perfectly const char code1[] = "template struct X { };\n" - "void f(const X x) { }"; + "void f(const X x) { }\n"; ASSERT_EQUALS("struct X ; " "void f ( const X x ) { } " "struct X { } ;", tok(code1)); const char code2[] = "template T f(T t) { return t; }\n" - "int x() { return f(123); }"; + "int x() { return f(123); }\n"; ASSERT_EQUALS("int f ( int t ) ; " "int x ( ) { return f ( 123 ) ; } " "int f ( int t ) { return t ; }", tok(code2)); @@ -1155,7 +1158,7 @@ class TestSimplifyTemplate : public TestFixture { " }() + ^ {\n" " return sizeof...(args);\n" " }();\n" - "}"; + "}\n"; ASSERT_THROW_INTERNAL(tok(code), SYNTAX); } @@ -1173,7 +1176,7 @@ class TestSimplifyTemplate : public TestFixture { "int main(void) {\n" " C ca;\n" " return 0;\n" - "}"; + "}\n"; const char expected[] = "struct E ; " "struct C> ; " "struct C ; " @@ -1212,14 +1215,14 @@ class TestSimplifyTemplate : public TestFixture { } void template44() { // #5297 - const char code[] = "template struct StackContainer {" - " void foo(int i) {" - " if (0 >= 1 && i<0) {}" - " }" - "};" - "template class ZContainer : public StackContainer {};" - "struct FGSTensor {};" - "class FoldedZContainer : public ZContainer {};"; + const char code[] = "template struct StackContainer {\n" + " void foo(int i) {\n" + " if (0 >= 1 && i<0) {}\n" + " }\n" + "};\n" + "template class ZContainer : public StackContainer {};\n" + "struct FGSTensor {};\n" + "class FoldedZContainer : public ZContainer {};\n"; const char expected[] = "struct StackContainer ; " "class ZContainer ; " "struct FGSTensor { } ; " @@ -1234,14 +1237,14 @@ class TestSimplifyTemplate : public TestFixture { } void template45() { // #5814 - const char code[] = "namespace Constants { const int fourtytwo = 42; } " - "template struct TypeMath { " - " static const int mult = sizeof(T) * U; " - "}; " - "template struct FOO { " - " enum { value = TypeMath::mult }; " - "}; " - "FOO foo;"; + const char code[] = "namespace Constants { const int fourtytwo = 42; }\n" + "template struct TypeMath {\n" + " static const int mult = sizeof(T) * U;\n" + "};\n" + "template struct FOO {\n" + " enum { value = TypeMath::mult };\n" + "};\n" + "FOO foo;\n"; const char expected[] = "namespace Constants { const int fourtytwo = 42 ; } " "struct TypeMath ; " "struct FOO ; " @@ -1257,32 +1260,32 @@ class TestSimplifyTemplate : public TestFixture { } void template46() { // #5816 - ASSERT_NO_THROW(tok("template struct A { static const int value = 0; }; " - "template struct B { " - " enum { value = A::value }; " - "};")); + ASSERT_NO_THROW(tok("template struct A { static const int value = 0; };\n" + "template struct B {\n" + " enum { value = A::value };\n" + "};\n")); ASSERT_EQUALS("", errout_str()); - ASSERT_NO_THROW(tok("template struct A {}; " - "enum { e = sizeof(A) }; " - "template struct B {};")); + ASSERT_NO_THROW(tok("template struct A {};\n" + "enum { e = sizeof(A) };\n" + "template struct B {};\n")); ASSERT_EQUALS("", errout_str()); - ASSERT_NO_THROW(tok("template struct A { static const int value = 0; }; " - "template struct B { typedef int type; }; " - "template struct C { " - " enum { value = A::type, int>::value }; " - "};")); + ASSERT_NO_THROW(tok("template struct A { static const int value = 0; };\n" + "template struct B { typedef int type; };\n" + "template struct C {\n" + " enum { value = A::type, int>::value };\n" + "};\n")); ASSERT_EQUALS("", errout_str()); } void template47() { // #6023 - ASSERT_NO_THROW(tok("template > class C1 {}; " - "class C2 : public C1 {};")); + ASSERT_NO_THROW(tok("template > class C1 {};\n" + "class C2 : public C1 {};\n")); ASSERT_EQUALS("", errout_str()); } void template48() { // #6134 (hang) - (void)tok("template int f( { } ); " - "int foo = f<1>(0);"); + (void)tok("template int f( { } );\n" + "int foo = f<1>(0);\n"); ASSERT_EQUALS("", errout_str()); } @@ -1324,17 +1327,17 @@ class TestSimplifyTemplate : public TestFixture { } void template52() { // #6437 - const char code[] = "template int sum() { " - " return value + sum(); " - "} " - "template int calculate_value() { " - " if (x != y) { " - " return sum(); " - " } else { " - " return 0; " - " } " - "} " - "int value = calculate_value<1,1>();"; + const char code[] = "template int sum() {\n" + " return value + sum();\n" + "}\n" + "template int calculate_value() {\n" + " if (x != y) {\n" + " return sum();\n" + " } else {\n" + " return 0;\n" + " }\n" + "}\n" + "int value = calculate_value<1,1>();\n"; const char expected[] = "int sum<0> ( ) ; " "int calculate_value<1,1> ( ) ; " "int value ; value = calculate_value<1,1> ( ) ; " @@ -1352,13 +1355,13 @@ class TestSimplifyTemplate : public TestFixture { } void template53() { // #4335 - const char code[] = "template struct Factorial { " - " enum { value = N * Factorial::value }; " - "};" - "template <> struct Factorial<0> { " - " enum { value = 1 }; " - "};" - "const int x = Factorial<4>::value;"; + const char code[] = "template struct Factorial {\n" + " enum { value = N * Factorial::value };\n" + "};\n" + "template <> struct Factorial<0> {\n" + " enum { value = 1 };\n" + "};\n" + "const int x = Factorial<4>::value;\n"; const char expected[] = "struct Factorial<0> ; " "struct Factorial<4> ; " "struct Factorial<3> ; " @@ -1385,12 +1388,12 @@ class TestSimplifyTemplate : public TestFixture { } void template54() { // #6587 (use-after-free) - (void)tok("template _Tp* fn(); " - "template struct A { " - " template ())> " - " struct B { }; " - "}; " - "A a;"); + (void)tok("template _Tp* fn();\n" + "template struct A {\n" + " template ())>\n" + " struct B { };\n" + "};\n" + "A a;\n"); } void template55() { // #6604 @@ -1404,7 +1407,7 @@ class TestSimplifyTemplate : public TestFixture { "{\n" " friend struct ConstCastHelper, T>;\n" " AtSmartPtr(const AtSmartPtr& r);\n" - "};")); + "};\n")); // Similar problem can also happen with ... ASSERT_EQUALS( @@ -1424,16 +1427,16 @@ class TestSimplifyTemplate : public TestFixture { " (A*)(p);\n" " }\n" "};\n" - "A a(0);")); + "A a(0);\n")); } void template56() { // #7117 - const char code[] = "template struct Foo { " - " std::array mfoo; " - "}; " - "void foo() { " - " Foo myFoo; " - "}"; + const char code[] = "template struct Foo {\n" + " std::array mfoo;\n" + "};\n" + "void foo() {\n" + " Foo myFoo;\n" + "}\n"; const char expected[] = "struct Foo ; " "void foo ( ) { " "Foo myFoo ; " @@ -1446,7 +1449,7 @@ class TestSimplifyTemplate : public TestFixture { void template57() { // #7891 const char code[] = "template struct Test { Test(T); };\n" - "Test test( 0 );"; + "Test test( 0 );\n"; const char exp[] = "struct Test ; " "Test test ( 0 ) ; " "struct Test { Test ( unsigned long ) ; } ;"; @@ -1460,7 +1463,7 @@ class TestSimplifyTemplate : public TestFixture { "}\n" "void foo() {\n" " TestArithmetic();\n" - "}"; + "}\n"; const char exp[] = "void TestArithmetic ( ) ; " "void foo ( ) {" " TestArithmetic ( ) ; " @@ -1486,7 +1489,7 @@ class TestSimplifyTemplate : public TestFixture { "}\n" "int main () {\n" " return diagonalGroupTest<4>();\n" - "}"; + "}\n"; const char exp[] = "struct Factorial<0> ; " "struct Factorial<4> ; " "struct Factorial<3> ; " @@ -1508,7 +1511,7 @@ class TestSimplifyTemplate : public TestFixture { "template void f() {}\n" "template void h() { f::type(0)>(); }\n" "\n" - "void j() { h(); }"; + "void j() { h(); }\n"; const char exp[] = "struct S ; " "void f::type(0)> ( ) ; " "void h ( ) ; " @@ -1531,7 +1534,7 @@ class TestSimplifyTemplate : public TestFixture { " void f1(Bar x) {}\n" " Foo> f2() { }\n" "};\n" - "Bar c;"; + "Bar c;\n"; const char exp[] = "struct Foo> ; " "struct Bar ; " "Bar c ; " @@ -1548,7 +1551,7 @@ class TestSimplifyTemplate : public TestFixture { "template void f() { x = y ? C1::allocate(1) : 0; }\n" "template class C3 {};\n" "template C3::C3(const C3 &v) { C1 c1; }\n" - "C3 c3;"; + "C3 c3;\n"; const char exp[] = "struct C1 ; " "template < class T > void f ( ) { x = y ? ( C1 < int > :: allocate ( 1 ) ) : 0 ; } " "class C3 ; " @@ -1560,8 +1563,8 @@ class TestSimplifyTemplate : public TestFixture { } void template63() { // #8576 - const char code[] = "template struct TestClass { T m_hi; };" - "TestClass> objTest3;"; + const char code[] = "template struct TestClass { T m_hi; };\n" + "TestClass> objTest3;\n"; const char exp[] = "struct TestClass> ; " "TestClass> objTest3 ; " "struct TestClass> { std :: auto_ptr < v > m_hi ; } ;"; @@ -1582,7 +1585,7 @@ class TestSimplifyTemplate : public TestFixture { " t_func<0>();\n" " t_func<1>();\n" "}\n" - "};"; + "};\n"; const char exp[] = "bool foo ( ) ; " "struct A { " "void t_func<0> ( ) ; " @@ -1620,7 +1623,7 @@ class TestSimplifyTemplate : public TestFixture { "int main() {\n" " AssociationDAGlobalGraphObserver grObs;\n" " return 1;\n" - "}"; + "}\n"; const char exp[] = "namespace bpp " "{ " "class AssociationDAGraphImplObserver ; " @@ -1640,7 +1643,7 @@ class TestSimplifyTemplate : public TestFixture { " const int ** foo();\n" "};\n" "template const int ** Fred::foo() { return nullptr; }\n" - "Fred fred;"; + "Fred fred;\n"; const char exp[] = "struct Fred ; " "Fred fred ; " "struct Fred { " @@ -1663,7 +1666,7 @@ class TestSimplifyTemplate : public TestFixture { "template Container::Container(const Container & x) { nElements = x.nElements; c = x.c; }\n" "template Container & Container::operator = (const Container & x) { mElements = x.mElements; c = x.c; return *this; }\n" "template Container::~Container() {}\n" - "Container intContainer;"; + "Container intContainer;\n"; const char expected[] = "struct Container ; " "Container intContainer ; " @@ -1688,7 +1691,7 @@ class TestSimplifyTemplate : public TestFixture { " char dummy[sizeof(T)];\n" " T value;\n" "};\n" - "Fred fred;"; + "Fred fred;\n"; const char exp[] = "union Fred ; " "Fred fred ; " "union Fred { " @@ -1703,7 +1706,7 @@ class TestSimplifyTemplate : public TestFixture { " int test;\n" " template T lookup() { return test; }\n" " int Fun() { return lookup(); }\n" - "};"; + "};\n"; const char exp[] = "class Test { " "int test ; " "int lookup ( ) ; " @@ -1721,7 +1724,7 @@ class TestSimplifyTemplate : public TestFixture { "template\n" "class Bar : private Bar {\n" " void foo() { }\n" - "};"; + "};\n"; const char exp[] = "template < typename T , typename V , int KeySize = 0 > class Bar ; " "class Bar ; " "class Bar { " @@ -1753,7 +1756,7 @@ class TestSimplifyTemplate : public TestFixture { " return f1(pInterface, interface_type::static_type());\n" "}\n" "\n" - "Reference< class XPropertyList > dostuff();"; + "Reference< class XPropertyList > dostuff();\n"; const char exp[] = "int f1 ( int * pInterface , int x ) { return 0 ; } " "class Reference ; " "Reference dostuff ( ) ; " @@ -1766,7 +1769,7 @@ class TestSimplifyTemplate : public TestFixture { const char code[] = "template class Tokenizer;\n" "const Tokenizer *tokenizer() const;\n" "template \n" - "Tokenizer::Tokenizer() { }"; + "Tokenizer::Tokenizer() { }\n"; const char exp[] = "template < typename N , typename P > class Tokenizer ; " "const Tokenizer < Node , Path > * tokenizer ( ) const ; " "template < typename N , typename P > " @@ -1778,7 +1781,7 @@ class TestSimplifyTemplate : public TestFixture { const char code[] = "template\n" "void keep_range(T& value, const T mini, const T maxi){}\n" "template void keep_range(float& v, const float l, const float u);\n" - "template void keep_range(int& v, const int l, const int u);"; + "template void keep_range(int& v, const int l, const int u);\n"; const char exp[] = "void keep_range ( float & value , const float mini , const float maxi ) ; " "void keep_range ( int & value , const int mini , const int maxi ) ; " "void keep_range ( float & value , const float mini , const float maxi ) { } " @@ -1791,7 +1794,7 @@ class TestSimplifyTemplate : public TestFixture { "class PushBackStreamBuf {\n" "public:\n" " void pushBack(const BTlist &vec);\n" - "};"; + "};\n"; const char exp[] = "class BTlist ; " "class PushBackStreamBuf { " "public: " @@ -1804,7 +1807,7 @@ class TestSimplifyTemplate : public TestFixture { void template75() { const char code[] = "template\n" "T foo(T& value){ return value; }\n" - "template std::vector> foo>>(std::vector>& v);"; + "template std::vector> foo>>(std::vector>& v);\n"; const char exp[] = "std :: vector < std :: vector < int > > foo>> ( std :: vector < std :: vector < int > > & value ) ; " "std :: vector < std :: vector < int > > foo>> ( std :: vector < std :: vector < int > > & value ) { return value ; }"; ASSERT_EQUALS(exp, tok(code)); @@ -1834,7 +1837,7 @@ class TestSimplifyTemplate : public TestFixture { "int main() {\n" " std::cout << is_void::value << std::endl;\n" " std::cout << is_void::value << std::endl;\n" - "}"; + "}\n"; const char exp[] = "struct is_void ; " "struct is_void ; " "struct is_void : std :: true_type { } ; " @@ -1849,7 +1852,7 @@ class TestSimplifyTemplate : public TestFixture { void template78() { const char code[] = "template \n" "struct Base { };\n" - "struct S : Base ::Type { };"; + "struct S : Base ::Type { };\n"; const char exp[] = "struct Base ; " "struct S : Base :: Type { } ; " "struct Base { } ;"; @@ -1867,7 +1870,7 @@ class TestSimplifyTemplate : public TestFixture { "void some_func() {\n" " Foo x;\n" " x.foo();\n" - "}"; + "}\n"; const char exp[] = "class Foo { " "public: " "void foo ( ) ; " @@ -1888,7 +1891,7 @@ class TestSimplifyTemplate : public TestFixture { const char code[] = "class Fred {\n" " template T foo(T t) const { return t; }\n" "};\n" - "const void * p = Fred::foo(nullptr);"; + "const void * p = Fred::foo(nullptr);\n"; const char exp[] = "class Fred { " "const void * foo ( const void * t ) const ; " "} ; " @@ -1906,7 +1909,7 @@ class TestSimplifyTemplate : public TestFixture { "SortWith::SortWith(Type) {}\n" "int main() {\n" " SortWith(0);\n" - "}"; + "}\n"; const char exp[] = "template < typename Type > " "struct SortWith { " "SortWith ( Type ) ; " @@ -1936,7 +1939,7 @@ class TestSimplifyTemplate : public TestFixture { " swizzle<1>(tt2);\n" " tvec3 tt3;\n" " swizzle<2,3>(tt3);\n" - "}"; + "}\n"; const char exp[] = "const int f16 = 16 ; " "class tvec2 ; " "class tvec3 ; " @@ -1967,7 +1970,7 @@ class TestSimplifyTemplate : public TestFixture { "MultiConsumer::MultiConsumer() : sizeBuffer(0) {}\n" "MultiReads::MultiReads() {\n" " mc = new MultiConsumer();\n" - "}"; + "}\n"; const char exp[] = "template < typename Task > " // TODO: this should be expanded "class MultiConsumer { " "MultiConsumer ( ) ; " @@ -1985,7 +1988,7 @@ class TestSimplifyTemplate : public TestFixture { const char code[] = "template \n" "auto d() -> typename a::e {\n" " d();\n" - "}"; + "}\n"; const char exp[] = "template < class b , int c , class > " "auto d ( ) . a < decltype ( b { } ) > :: e { " "d < int , c , int > ( ) ; " @@ -1996,8 +1999,8 @@ class TestSimplifyTemplate : public TestFixture { const char code[] = "template \n" "auto d() -> typename a::e {\n" " d();\n" - "}" - "void foo() { d(); }"; + "}\n" + "void foo() { d(); }\n"; const char exp[] = "auto d ( ) . a < char > :: e ; " "auto d ( ) . a < int > :: e ; " "void foo ( ) { d ( ) ; } " @@ -2021,7 +2024,7 @@ class TestSimplifyTemplate : public TestFixture { "extern template void C::foo();\n" "template\n" "template::value)>::type>\n" - "void C::foo() {}"; + "void C::foo() {}\n"; // @todo the output is very wrong but we are only worried about the crash for now (void)tok(code); } @@ -2037,7 +2040,7 @@ class TestSimplifyTemplate : public TestFixture { "template \n" "S U::u;\n" "template S U::u;\n" - "S &i = U::u;"; + "S &i = U::u;\n"; (void)tok(code); } @@ -2045,7 +2048,7 @@ class TestSimplifyTemplate : public TestFixture { const char code[] = "template\n" "T f1(T t) { return t; }\n" "template const char * f1(const char *);\n" - "template const char & f1(const char &);"; + "template const char & f1(const char &);\n"; const char exp[] = "const char * f1 ( const char * t ) ; " "const char & f1 ( const char & t ) ; " "const char * f1 ( const char * t ) { return t ; } " @@ -2072,7 +2075,7 @@ class TestSimplifyTemplate : public TestFixture { "int main() {\n" " CTest::Greeting(true);\n" " return 0;\n" - "}"; + "}\n"; const char exp[] = "class CTest { " "public: " "static void Greeting ( bool ) ; " @@ -2102,7 +2105,7 @@ class TestSimplifyTemplate : public TestFixture { "template void Fred::foo();\n" "template void Fred::foo();\n" "template <> void Fred::foo() { }\n" - "template <> void Fred::foo() { }"; + "template <> void Fred::foo() { }\n"; const char exp[] = "struct Fred { " "static void foo ( ) ; " "static void foo ( ) ; " @@ -2120,7 +2123,7 @@ class TestSimplifyTemplate : public TestFixture { const char code[] = "template struct S1 {};\n" "void f(S1) {}\n" "template \n" - "decltype(S1().~S1()) fun1() {};"; + "decltype(S1().~S1()) fun1() {};\n"; const char exp[] = "struct S1 ; " "void f ( S1 ) { } " "template < typename T > " @@ -2135,7 +2138,7 @@ class TestSimplifyTemplate : public TestFixture { "template<> char foo(char a) { return a; }\n" "template<> int foo(int a) { return a; }\n" "template float foo(float);\n" - "template double foo(double);"; + "template double foo(double);\n"; const char exp[] = "int foo ( int a ) ; " "char foo ( char a ) ; " "float foo ( float t ) ; " @@ -2153,7 +2156,7 @@ class TestSimplifyTemplate : public TestFixture { " template<> int foo(int a) { return a; }\n" "};\n" "template float Fred::foo(float);\n" - "template double Fred::foo(double);"; + "template double Fred::foo(double);\n"; const char exp[] = "struct Fred { " "int foo ( int a ) ; " "char foo ( char a ) ; " @@ -2178,7 +2181,7 @@ class TestSimplifyTemplate : public TestFixture { " template float NS2::foo(float);\n" " template bool NS1::NS2::foo(bool);\n" "}\n" - "template double NS1::NS2::foo(double);"; + "template double NS1::NS2::foo(double);\n"; const char exp[] = "namespace NS1 { " "namespace NS2 { " "int foo ( int a ) ; " @@ -2211,7 +2214,7 @@ class TestSimplifyTemplate : public TestFixture { " template float NS::foo(float);\n" " template bool NS1::NS::foo(bool);\n" "}\n" - "template double NS1::NS::foo(double);"; + "template double NS1::NS::foo(double);\n"; const char exp[] = "namespace NS1 { " "namespace NS { " "int foo ( int a ) ; " @@ -2242,7 +2245,7 @@ class TestSimplifyTemplate : public TestFixture { " foo(2);\n" " foo(3.14);\n" " foo(3.14f);\n" - "}"; + "}\n"; const char exp[] = "void foo ( const double & d ) ; " "void foo ( const float & t ) ; " "void foo ( const int & t ) ; " @@ -2271,7 +2274,7 @@ class TestSimplifyTemplate : public TestFixture { "void Vector2::process() {\n" " ForEach();\n" "}\n" - "Vector2 c;"; + "Vector2 c;\n"; const char exp[] = "void ForEach ( ) ; " "class Vector2 ; " "Vector2 c ; " @@ -2301,7 +2304,7 @@ class TestSimplifyTemplate : public TestFixture { "Array matmul() {\n" " return foo( );\n" "}\n" - "template Array> matmul>();"; + "template Array> matmul>();\n"; const char exp[] = "class Array ; " "class Array> ; " "class Array ; " @@ -2329,7 +2332,7 @@ class TestSimplifyTemplate : public TestFixture { "template<>\n" "int Value = 456;\n" "float f = Value;\n" - "int i = Value;"; + "int i = Value;\n"; const char exp[] = "float Value ; Value = 123 ; " "int Value ; Value = 456 ; " "float f ; f = Value ; " @@ -2348,7 +2351,7 @@ class TestSimplifyTemplate : public TestFixture { "long f0 = fib<0>;\n" "long f1 = fib<1>;\n" "long f2 = fib<2>;\n" - "long f3 = fib<3>;"; + "long f3 = fib<3>;\n"; const char exp[] = "constexpr long fib<2> = fib<1> + fib<0> ; " "constexpr long fib<3> = fib<2> + fib<1> ; " "constexpr long fib<0> = 0 ; " @@ -2408,7 +2411,7 @@ class TestSimplifyTemplate : public TestFixture { "}\n" "using namespace NS1::NS2::NS3::NS4;\n" "Fred fred_bool1;\n" - "NS1::NS2::NS3::NS4::Fred fred_int1;"; + "NS1::NS2::NS3::NS4::Fred fred_int1;\n"; const char exp[] = "namespace NS1 { " "namespace NS2 { " "namespace NS3 { " @@ -2486,7 +2489,7 @@ class TestSimplifyTemplate : public TestFixture { " unique_ptr_with_deleter tmp(new A(), [](A* a) {\n" " delete a;\n" " });\n" - "}"; + "}\n"; const char exp[] = "class A { } ; " "static void func ( ) { " "std :: unique_ptr < A , std :: function < void ( A * ) > > tmp ( new A ( ) , [ ] ( A * a ) { " @@ -2534,7 +2537,7 @@ class TestSimplifyTemplate : public TestFixture { " using ArrayType = std::vector;\n" " void func(typename ArrayType::size_type i) {\n" " }\n" - "};"; + "};\n"; const char exp[] = "class A { " "public: " @@ -2555,7 +2558,7 @@ class TestSimplifyTemplate : public TestFixture { "}\n" "void f() {\n" " if(std::is_floating_point::value) {}\n" - "}"; + "}\n"; const char exp[] = "namespace ns { " "template < class T > " "struct is_floating_point " @@ -2580,7 +2583,7 @@ class TestSimplifyTemplate : public TestFixture { " return t;\n" " }\n" "}\n" - "sample::Sample s1;"; + "sample::Sample s1;\n"; const char exp[] = "namespace sample { " "class Sample ; " "} " @@ -2606,7 +2609,7 @@ class TestSimplifyTemplate : public TestFixture { " BOOST_HANA_CONSTANT_CHECK ( hana :: equal (\n" " hana :: at_key ( hana :: make_map ( p < 0 , 0 > ( ) ) , key < 0 > ( ) ) ,\n" " val < 0 > ( ) ) ) ;\n" - "}"; + "}\n"; const char exp[] = "auto key<0> ( ) ; " "auto val<0> ( ) ; " "auto p<0,0> ( ) ; " @@ -2626,7 +2629,7 @@ class TestSimplifyTemplate : public TestFixture { "template