From 1c512f3dc56d83025dd0ffaae17120739acf6d29 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Tue, 3 Mar 2026 18:09:20 +0100 Subject: [PATCH] Remove cppcheck from CI, add to tools/ We don't enforce cppcheck through the CI, although it's there since years. It runs on Ubuntu 18/20, so it's old. For folks, it's likely not discoverable on how to run it locally. Let's make a fresh start. This removes cppcheck from all CI-related code. Instead, it adds it under tools/cppcheck/, including documentation on how to run it locally, bare-matel or in docker. Signed-off-by: Robert Schmidt --- ci-scripts/Jenkinsfile-GitLab-Container | 22 ---- ci-scripts/cls_oai_html.py | 84 ------------ ci-scripts/cls_static_code_analysis.py | 120 ------------------ ci-scripts/docker/Dockerfile.cppcheck.xenial | 41 ------ ci-scripts/main.py | 3 - ci-scripts/xml_class_list.yml | 1 - ci-scripts/xml_files/cppcheck.xml | 20 --- doc/README.md | 1 + doc/TESTBenches.md | 4 - tools/cppcheck/README.md | 28 ++++ tools/cppcheck/docker-compose.yaml | 14 ++ tools/cppcheck/run-cppcheck.sh | 11 ++ .../cppcheck/suppressions.list | 0 13 files changed, 54 insertions(+), 295 deletions(-) delete mode 100644 ci-scripts/docker/Dockerfile.cppcheck.xenial delete mode 100644 ci-scripts/xml_files/cppcheck.xml create mode 100644 tools/cppcheck/README.md create mode 100644 tools/cppcheck/docker-compose.yaml create mode 100755 tools/cppcheck/run-cppcheck.sh rename ci-scripts/cppcheck_suppressions.list => tools/cppcheck/suppressions.list (100%) diff --git a/ci-scripts/Jenkinsfile-GitLab-Container b/ci-scripts/Jenkinsfile-GitLab-Container index d75b1888d4..721c89b77f 100644 --- a/ci-scripts/Jenkinsfile-GitLab-Container +++ b/ci-scripts/Jenkinsfile-GitLab-Container @@ -219,28 +219,6 @@ pipeline { } } } - stage ("cppcheck") { - steps { - script { - triggerSlaveJob ('RAN-cppcheck', 'cppcheck') - } - } - post { - always { - script { - // Using a unique variable name for each test stage to avoid overwriting on a global variable - // due to parallel-time concurrency - cppcheckStatus = finalizeSlaveJob('RAN-cppcheck') - } - } - failure { - script { - currentBuild.result = 'FAILURE' - failingStages += cppcheckStatus - } - } - } - } stage ("ARM-Cross-Compile") { steps { script { diff --git a/ci-scripts/cls_oai_html.py b/ci-scripts/cls_oai_html.py index 2f59ae1852..66d4b804ae 100644 --- a/ci-scripts/cls_oai_html.py +++ b/ci-scripts/cls_oai_html.py @@ -379,90 +379,6 @@ class HTMLManagement(): self.htmlFile.write(' \n') self.htmlFile.close() - def CreateHtmlTestRowCppCheckResults(self, CCR): - if (self.htmlFooterCreated or (not self.htmlHeaderCreated)): - return - self.htmlFile = open('test_results.html', 'a') - vId = 0 - for version in CCR.versions: - self.htmlFile.write(' \n') - self.htmlFile.write(' Results for cppcheck v ' + version + ' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' NB ERRORS\n') - if CCR.nbErrors[vId] == 0: - myColor = 'lightgreen' - elif CCR.nbErrors[vId] < 20: - myColor = 'orange' - else: - myColor = 'lightcoral' - self.htmlFile.write(' ' + str(CCR.nbErrors[vId]) + '\n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' NB WARNINGS\n') - if CCR.nbWarnings[vId] == 0: - myColor = 'lightgreen' - elif CCR.nbWarnings[vId] < 20: - myColor = 'orange' - else: - myColor = 'lightcoral' - self.htmlFile.write(' ' + str(CCR.nbWarnings[vId]) + '\n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' ----------------- \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' Memory leak\n') - self.htmlFile.write(' ' + str(CCR.nbMemLeaks[vId]) + '\n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' Possible null pointer deference\n') - self.htmlFile.write(' ' + str(CCR.nbNullPtrs[vId]) + '\n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' Uninitialized variable\n') - self.htmlFile.write(' ' + str(CCR.nbUninitVars[vId]) + '\n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' Undefined behaviour shifting\n') - self.htmlFile.write(' ' + str(CCR.nbTooManyBitsShift[vId]) + '\n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' Signed integer overflow\n') - self.htmlFile.write(' ' + str(CCR.nbIntegerOverflow[vId]) + '\n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' Printf formatting issues\n') - self.htmlFile.write(' ' + str(CCR.nbInvalidPrintf[vId]) + '\n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' Modulo result is predetermined\n') - self.htmlFile.write(' ' + str(CCR.nbModuloAlways[vId]) + '\n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' Opposite Condition -> dead code\n') - self.htmlFile.write(' ' + str(CCR.nbOppoInnerCondition[vId]) + '\n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' \n') - self.htmlFile.write(' Wrong Scanf Nb Args\n') - self.htmlFile.write(' ' + str(CCR.nbWrongScanfArg[vId]) + '\n') - self.htmlFile.write(' \n') - vId += 1 - def CreateHtmlTestRowPhySimTestResult(self, testSummary, testResult): if (self.htmlFooterCreated or (not self.htmlHeaderCreated)): return diff --git a/ci-scripts/cls_static_code_analysis.py b/ci-scripts/cls_static_code_analysis.py index 603df581e3..4e388847c7 100644 --- a/ci-scripts/cls_static_code_analysis.py +++ b/ci-scripts/cls_static_code_analysis.py @@ -27,28 +27,6 @@ import constants as CONST import cls_cmd from cls_ci_helper import archiveArtifact -#----------------------------------------------------------- -# Class Declaration -#----------------------------------------------------------- -class CppCheckResults(): - - def __init__(self): - - self.variants = ['bionic', 'focal'] - self.versions = ['',''] - self.nbErrors = [0,0] - self.nbWarnings = [0,0] - self.nbNullPtrs = [0,0] - self.nbMemLeaks = [0,0] - self.nbUninitVars = [0,0] - self.nbInvalidPrintf = [0,0] - self.nbModuloAlways = [0,0] - self.nbTooManyBitsShift = [0,0] - self.nbIntegerOverflow = [0,0] - self.nbWrongScanfArg = [0,0] - self.nbPtrAddNotNull = [0,0] - self.nbOppoInnerCondition = [0,0] - class StaticCodeAnalysis(): def __init__(self): @@ -60,104 +38,6 @@ class StaticCodeAnalysis(): self.ranTargetBranch = '' self.eNBSourceCodePath = '' - def CppCheckAnalysis(self, ctx, node, HTML): - if self.ranRepository == '' or self.ranBranch == '' or self.ranCommitID == '': - HELP.GenericHelp(CONST.Version) - sys.exit('Insufficient Parameter') - lSourcePath = self.eNBSourceCodePath - - if not lSourcePath or not node: - raise ValueError(f"{lSourcePath=} {node=}") - logging.debug('Building on server: ' + node) - cmd = cls_cmd.getConnection(node) - # on RedHat/CentOS .git extension is mandatory - result = re.search(r'([a-zA-Z0-9\:\-\.\/])+\.git', self.ranRepository) - if result is not None: - full_ran_repo_name = self.ranRepository.replace('git/', 'git') - else: - full_ran_repo_name = self.ranRepository + '.git' - - cmd.cd(lSourcePath) - logDir = f'{lSourcePath}/cmake_targets/log' - cmd.run(f'mkdir -p {logDir}') - cmd.run('docker image rm oai-cppcheck:bionic oai-cppcheck:focal') - cmd.run(f'sed -e "s@xenial@bionic@" {lSourcePath}/ci-scripts/docker/Dockerfile.cppcheck.xenial > {lSourcePath}/ci-scripts/docker/Dockerfile.cppcheck.bionic') - cmd.run(f'docker build --tag oai-cppcheck:bionic --file {lSourcePath}/ci-scripts/docker/Dockerfile.cppcheck.bionic . > {logDir}/cppcheck-bionic.txt 2>&1') - cmd.run(f'sed -e "s@xenial@focal@" {lSourcePath}/ci-scripts/docker/Dockerfile.cppcheck.xenial > {lSourcePath}/ci-scripts/docker/Dockerfile.cppcheck.focal') - cmd.run(f'docker build --tag oai-cppcheck:focal --file {lSourcePath}/ci-scripts/docker/Dockerfile.cppcheck.focal . > {logDir}/cppcheck-focal.txt 2>&1') - cmd.run('docker image rm oai-cppcheck:bionic oai-cppcheck:focal') - - bionic = archiveArtifact(cmd, ctx, f'{logDir}/cppcheck-bionic.txt') - focal = archiveArtifact(cmd, ctx, f'{logDir}/cppcheck-focal.txt') - cmd.close() - - CCR = CppCheckResults() - CCR_ref = CppCheckResults() - vId = 0 - for variant in CCR.variants: - filename = ctx.baseFilename() + '-cppcheck-'+ variant + '.txt' - logging.info(f"will check file '{filename}'") - if not os.path.isfile(filename): - raise FileNotFoundError(f"{filename} is not a file") - else: - xmlStart = False - with open(filename, 'r') as logfile: - for line in logfile: - ret = re.search(r'cppcheck version="(?P[0-9\.]+)"', str(line)) - if ret is not None: - CCR.versions[vId] = ret.group('version') - if re.search('RUN cat cmake_targets/log/cppcheck.xml', str(line)) is not None: - xmlStart = True - if xmlStart: - if re.search('severity="error"', str(line)) is not None: - CCR.nbErrors[vId] += 1 - if re.search('severity="warning"', str(line)) is not None: - CCR.nbWarnings[vId] += 1 - if re.search('id="memleak"', str(line)) is not None: - CCR.nbMemLeaks[vId] += 1 - if re.search('id="nullPointer"', str(line)) is not None: - CCR.nbNullPtrs[vId] += 1 - if re.search('id="uninitvar"', str(line)) is not None: - CCR.nbUninitVars[vId] += 1 - if re.search('id="invalidPrintfArgType_sint"|id="invalidPrintfArgType_uint"', str(line)) is not None: - CCR.nbInvalidPrintf[vId] += 1 - if re.search('id="moduloAlwaysTrueFalse"', str(line)) is not None: - CCR.nbModuloAlways[vId] += 1 - if re.search('id="shiftTooManyBitsSigned"', str(line)) is not None: - CCR.nbTooManyBitsShift[vId] += 1 - if re.search('id="integerOverflow"', str(line)) is not None: - CCR.nbIntegerOverflow[vId] += 1 - if re.search('id="wrongPrintfScanfArgNum"|id="invalidScanfArgType_int"', str(line)) is not None: - CCR.nbWrongScanfArg[vId] += 1 - if re.search('id="pointerAdditionResultNotNull"', str(line)) is not None: - CCR.nbPtrAddNotNull[vId] += 1 - if re.search('id="oppositeInnerCondition"', str(line)) is not None: - CCR.nbOppoInnerCondition[vId] += 1 - vMsg = '' - vMsg += '======== Variant ' + variant + ' - ' + CCR.versions[vId] + ' ========\n' - vMsg += ' ' + str(CCR.nbErrors[vId]) + ' errors\n' - vMsg += ' ' + str(CCR.nbWarnings[vId]) + ' warnings\n' - vMsg += ' -- Details --\n' - vMsg += ' Memory leak: ' + str(CCR.nbMemLeaks[vId]) + '\n' - vMsg += ' Possible null pointer deference: ' + str(CCR.nbNullPtrs[vId]) + '\n' - vMsg += ' Uninitialized variable: ' + str(CCR.nbUninitVars[vId]) + '\n' - vMsg += ' Undefined behaviour shifting: ' + str(CCR.nbTooManyBitsShift[vId]) + '\n' - vMsg += ' Signed integer overflow: ' + str(CCR.nbIntegerOverflow[vId]) + '\n' - vMsg += '\n' - vMsg += ' Printf formatting issue: ' + str(CCR.nbInvalidPrintf[vId]) + '\n' - vMsg += ' Modulo result is predetermined: ' + str(CCR.nbModuloAlways[vId]) + '\n' - vMsg += ' Opposite Condition -> dead code: ' + str(CCR.nbOppoInnerCondition[vId]) + '\n' - vMsg += ' Wrong Scanf Nb Args: ' + str(CCR.nbWrongScanfArg[vId]) + '\n' - for vLine in vMsg.split('\n'): - logging.debug(vLine) - vId += 1 - - HTML.CreateHtmlTestRow('N/A', 'OK', CONST.ALL_PROCESSES_OK) - HTML.CreateHtmlTestRowCppCheckResults(CCR) - logging.info('\u001B[1m Static Code Analysis Pass\u001B[0m') - - return True - def LicenceAndFormattingCheck(self, ctx, node, HTML): # Workspace is no longer recreated from scratch. # It implies that this method shall be called last within a build pipeline diff --git a/ci-scripts/docker/Dockerfile.cppcheck.xenial b/ci-scripts/docker/Dockerfile.cppcheck.xenial deleted file mode 100644 index fcb7f814ae..0000000000 --- a/ci-scripts/docker/Dockerfile.cppcheck.xenial +++ /dev/null @@ -1,41 +0,0 @@ -# SPDX-License-Identifier: LicenseRef-CSSL-1.0 - -#--------------------------------------------------------------------- -# -# Dockerfile for the Open-Air-Interface BUILD service -# Valid for Ubuntu 22.04 -# -#--------------------------------------------------------------------- -FROM ubuntu:xenial AS oai-cppcheck - - -ENV DEBIAN_FRONTEND=noninteractive - -RUN apt-get update && \ - apt-get upgrade --yes && \ - apt-get install --yes \ - build-essential \ - vim \ - cppcheck - -WORKDIR /oai-ran -COPY . . - -WORKDIR /oai-ran/common/utils/T -RUN make - -WORKDIR /oai-ran -RUN mkdir -p cmake_targets/log && \ - cppcheck --enable=warning --force --xml --xml-version=2 \ - --inline-suppr \ - -i openair1/PHY/CODING/nrLDPC_decoder/nrLDPC_decoder.c \ - --suppressions-list=ci-scripts/cppcheck_suppressions.list \ - -I common/utils \ - -I openair3/NAS/COMMON/UTIL \ - -j`nproc` . 2> cmake_targets/log/cppcheck.xml 1> cmake_targets/log/cppcheck_build.txt - -RUN grep -E -c 'severity="error' cmake_targets/log/cppcheck.xml - -RUN grep -E -c 'severity="warning' cmake_targets/log/cppcheck.xml - -RUN cat cmake_targets/log/cppcheck.xml diff --git a/ci-scripts/main.py b/ci-scripts/main.py index 27cf36bcf8..906f73f433 100644 --- a/ci-scripts/main.py +++ b/ci-scripts/main.py @@ -198,9 +198,6 @@ def ExecuteActionWithParam(action, ctx, node): elif action == 'LicenceAndFormattingCheck': success = SCA.LicenceAndFormattingCheck(ctx, node, HTML) - elif action == 'Cppcheck_Analysis': - success = SCA.CppCheckAnalysis(ctx, node, HTML) - elif action == 'Push_Local_Registry': tag_prefix = test.findtext('tag_prefix') or "" success = CONTAINERS.Push_Image_to_Local_Registry(node, HTML, tag_prefix) diff --git a/ci-scripts/xml_class_list.yml b/ci-scripts/xml_class_list.yml index 52b44e0e96..d762c4b316 100755 --- a/ci-scripts/xml_class_list.yml +++ b/ci-scripts/xml_class_list.yml @@ -23,7 +23,6 @@ - Deploy_Object - Stop_Object - Undeploy_Object - - Cppcheck_Analysis - Deploy_Run_OC_PhySim - Build_Deploy_PhySim - LicenceAndFormattingCheck diff --git a/ci-scripts/xml_files/cppcheck.xml b/ci-scripts/xml_files/cppcheck.xml deleted file mode 100644 index 061c39b5b8..0000000000 --- a/ci-scripts/xml_files/cppcheck.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - build-tab - CPPCHECK Analysis - wrench - - - Create_Workspace - Create new Workspace - carabe - - - - Cppcheck_Analysis - Static Code Analysis with cppcheck - carabe - - - diff --git a/doc/README.md b/doc/README.md index 37fe2a980d..d1dfc2dc4d 100644 --- a/doc/README.md +++ b/doc/README.md @@ -135,6 +135,7 @@ The other SDRs (AW2S, LimeSDR, ...) have no READMEs. - [clang-format.md](./clang-format.md): how to format the code. See also the next entry for an error detection tool. - [formatting](../tools/formatting/README.md) is a clang-format error detection tool +- [cppcheck](../tools/cppcheck/README.md) for static code analysis - [sanitizers.md](./dev_tools/sanitizers.md): how to run with ASan/UBSan/MemSAN/TSan - [iwyu](../tools/iwyu/README.md) is a tool to detect `#include` errors - [docker-dev-env](../tools/docker-dev-env/README.md) is a ubuntu24 docker development environment diff --git a/doc/TESTBenches.md b/doc/TESTBenches.md index d348ffe355..361d774215 100644 --- a/doc/TESTBenches.md +++ b/doc/TESTBenches.md @@ -96,10 +96,6 @@ information on how the images are built. - orion: Cross-compilation from Intel to ARM - base image from `Dockerfile.base.ubuntu.cross-arm64` - build image from `Dockerfile.build.ubuntu.cross-arm64` (no target images) -- [RAN-cppcheck](https://jenkins-oai.eurecom.fr/job/RAN-cppcheck/) - ~BUILD-ONLY ~4G-LTE ~5G-NR ~nrUE - - bellatrix - - performs static code analysis, currently not actively enforced - [RAN-RHEL8-Cluster-Image-Builder](https://jenkins-oai.eurecom.fr/job/RAN-RHEL8-Cluster-Image-Builder/) ~BUILD-ONLY ~4G-LTE ~5G-NR ~nrUE - cluster (`Asterix-OC-oaicicd-session` resource): RHEL image build using the OpenShift Cluster (using gcc/clang) diff --git a/tools/cppcheck/README.md b/tools/cppcheck/README.md new file mode 100644 index 0000000000..414f35a68a --- /dev/null +++ b/tools/cppcheck/README.md @@ -0,0 +1,28 @@ + + +# Static code analysis + +How to run static code analysis tool(s). + +[[_TOC_]] + +## cppcheck Overview + +This can run a (dockerized) cppcheck static code analysis. The docker-compose +file represents the status as run as in the CI until it's removal (because it +was not enforced). It is now here so that people could use it to find bugs (and +use the tool more easily). + +## cppcheck Usage + +From the openairinterface5g root, simply run + + ./tools/cppcheck/run-cppcheck.sh + +This will run cppcheck with options listed in this file, assuming you have it +installed. Alternatively, you can run the CI version as of Ubuntu 20 with + + cd tools/cppcheck && docker compose up --no-log-prefix + +You can upgrade to a newer version by modifying the `FROM` line in the +docker-compose file. diff --git a/tools/cppcheck/docker-compose.yaml b/tools/cppcheck/docker-compose.yaml new file mode 100644 index 0000000000..8b8c5878d0 --- /dev/null +++ b/tools/cppcheck/docker-compose.yaml @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: MIT + +services: + cppcheck: + image: cppcheck + build: + dockerfile_inline: | + FROM ubuntu:focal + RUN DEBIAN_FRONTEND=noninteractive apt update && apt install -y cppcheck + volumes: + - ../../:/code/ + command: + bash -c "cd /code/ && ./tools/cppcheck/run-cppcheck.sh" + diff --git a/tools/cppcheck/run-cppcheck.sh b/tools/cppcheck/run-cppcheck.sh new file mode 100755 index 0000000000..5a5df0c752 --- /dev/null +++ b/tools/cppcheck/run-cppcheck.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# SPDX-License-Identifier: MIT + +cppcheck --enable=warning --force \ + --inline-suppr \ + --quiet \ + -i openair1/PHY/CODING/nrLDPC_decoder/nrLDPC_decoder.c \ + --suppressions-list=tools/cppcheck/suppressions.list \ + -I common/utils \ + -I openair3/NAS/COMMON/UTIL \ + -j`nproc` . diff --git a/ci-scripts/cppcheck_suppressions.list b/tools/cppcheck/suppressions.list similarity index 100% rename from ci-scripts/cppcheck_suppressions.list rename to tools/cppcheck/suppressions.list