Skip to content

Commit 789b0b8

Browse files
authored
Merge pull request #40 from OPPIDA/fix/cppcheck-version
2 parents 136e72a + faeca08 commit 789b0b8

File tree

8 files changed

+74
-11
lines changed

8 files changed

+74
-11
lines changed

Dockerfile

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ RUN apt update -qq && \
2828
cloc \
2929
openjdk-17-jdk-headless maven \
3030
build-essential bear \
31-
-y -qq --no-install-recommends && \
32-
rm -rf /var/lib/apt/lists/*
31+
-y -qq --no-install-recommends
3332

3433
RUN groupadd -g $GID codesectools && \
3534
useradd -l -u $UID -g codesectools -m codesectools -s /bin/bash && \
@@ -60,9 +59,11 @@ RUN curl -sL https://github.com/spotbugs/spotbugs/releases/download/4.9.8/spotbu
6059
ENV PATH="/home/codesectools/sasts/spotbugs/bin:$PATH"
6160

6261
# Cppcheck
63-
RUN sudo apt update -qq && \
64-
DEBIAN_FRONTEND=noninteractive sudo apt install cppcheck -y -qq --no-install-recommends && \
65-
sudo rm -rf /var/lib/apt/lists/*
62+
RUN sudo apt install -y -qq --no-install-recommends libpcre3-dev && \
63+
curl -sL https://github.com/danmar/cppcheck/archive/refs/tags/2.19.0.tar.gz | tar -xzvf - && \
64+
mv cppcheck-* /home/codesectools/sasts/cppcheck && \
65+
(cd /home/codesectools/sasts/cppcheck && make -j$(nproc) MATCHCOMPILER=yes HAVE_RULES=yes CXXOPTS="-O2" CPPOPTS="-DNDEBUG")
66+
ENV PATH="/home/codesectools/sasts/cppcheck:$PATH"
6667

6768
# =========================== CodeSecTools ===========================
6869
COPY --from=builder --chown=codesectools:codesectools /app /app

codesectools/sasts/core/sast/requirements.py

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@
22

33
from __future__ import annotations
44

5+
import re
56
import shutil
67
from abc import ABC, abstractmethod
78
from typing import TYPE_CHECKING, Any, Literal
89

910
import typer
11+
from packaging import version
1012
from rich import print
1113

12-
from codesectools.utils import USER_CACHE_DIR, USER_CONFIG_DIR
14+
from codesectools.utils import USER_CACHE_DIR, USER_CONFIG_DIR, run_command
1315

1416
if TYPE_CHECKING:
1517
from pathlib import Path
@@ -125,13 +127,47 @@ def is_fulfilled(self, **kwargs: Any) -> bool:
125127
return (USER_CONFIG_DIR / self.sast_name / self.name).is_file()
126128

127129

130+
class BinaryVersion:
131+
"""Represent a version requirement for a binary."""
132+
133+
def __init__(self, command_flag: str, pattern: str, expected: str) -> None:
134+
"""Initialize a Version instance.
135+
136+
Args:
137+
command_flag: The command line flag to get the version string (e.g., '--version').
138+
pattern: A regex pattern to extract the version number from the output.
139+
expected: The minimum expected version string.
140+
141+
"""
142+
self.command_flag = command_flag
143+
self.pattern = pattern
144+
self.expected = version.parse(expected)
145+
146+
def check(self, binary: Binary) -> bool:
147+
"""Check if the binary's version meets the requirement.
148+
149+
Args:
150+
binary: The Binary requirement object to check.
151+
152+
Returns:
153+
True if the version is sufficient, False otherwise.
154+
155+
"""
156+
retcode, output = run_command([binary.name, self.command_flag])
157+
if m := re.search(self.pattern, output):
158+
detected_version = version.parse(m.group(1))
159+
return detected_version >= self.expected
160+
return False
161+
162+
128163
class Binary(SASTRequirement):
129164
"""Represent a binary executable requirement for a SAST tool."""
130165

131166
def __init__(
132167
self,
133168
name: str,
134169
depends_on: list[SASTRequirement] | None = None,
170+
version: BinaryVersion | None = None,
135171
instruction: str | None = None,
136172
url: str | None = None,
137173
doc: bool = False,
@@ -141,6 +177,7 @@ def __init__(
141177
Args:
142178
name: The name of the requirement.
143179
depends_on: A list of other requirements that must be fulfilled first.
180+
version: An optional BinaryVersion object to check for a minimum version.
144181
instruction: A short instruction on how to download the requirement.
145182
url: A URL for more detailed instructions.
146183
doc: A flag indicating if the instruction is available in the documentation.
@@ -149,10 +186,23 @@ def __init__(
149186
super().__init__(
150187
name=name, depends_on=depends_on, instruction=instruction, url=url, doc=doc
151188
)
189+
self.version = version
190+
191+
def __repr__(self) -> str:
192+
"""Return a developer-friendly string representation of the requirement."""
193+
if self.version:
194+
return f"{self.__class__.__name__}({self.name}>={self.version.expected})"
195+
else:
196+
return super().__repr__()
152197

153198
def is_fulfilled(self, **kwargs: Any) -> bool:
154199
"""Check if the binary is available in the system's PATH."""
155-
return bool(shutil.which(self.name))
200+
if bool(shutil.which(self.name)):
201+
if self.version:
202+
return self.version.check(self)
203+
return True
204+
else:
205+
return False
156206

157207

158208
class GitRepo(DownloadableRequirement):

codesectools/sasts/tools/Cppcheck/sast.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from codesectools.sasts.core.sast.properties import SASTProperties
1111
from codesectools.sasts.core.sast.requirements import (
1212
Binary,
13+
BinaryVersion,
1314
SASTRequirements,
1415
)
1516
from codesectools.sasts.tools.Cppcheck.parser import CppcheckAnalysisResult
@@ -40,7 +41,11 @@ class CppcheckSAST(PrebuiltBuildlessSAST):
4041
properties = SASTProperties(free=True, offline=True)
4142
requirements = SASTRequirements(
4243
full_reqs=[
43-
Binary("cppcheck", url="https://cppcheck.sourceforge.io/"),
44+
Binary(
45+
"cppcheck",
46+
url="https://cppcheck.sourceforge.io/",
47+
version=BinaryVersion("--version", r"(\d+\.\d+\.\d+)", "2.16.0"),
48+
),
4449
],
4550
partial_reqs=[],
4651
)

codesectools/utils.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ def render_command(command: list, mapping: dict[str, str]) -> list[str]:
104104

105105

106106
def run_command(
107-
command: Sequence[str], cwd: Path, env: dict[str, str] | None = None
107+
command: Sequence[str], cwd: Path | None = None, env: dict[str, str] | None = None
108108
) -> tuple[int | None, str]:
109109
"""Execute a command in a subprocess and capture its output.
110110
@@ -118,6 +118,8 @@ def run_command(
118118
stdout/stderr output as a string.
119119
120120
"""
121+
if cwd is None:
122+
cwd = Path.cwd()
121123
modified_env = {**os.environ, **env} if env else os.environ
122124

123125
process = subprocess.Popen(

docs/sast/profiles/cppcheck.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: Cppcheck
22
description: Cppcheck is a static 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 have very few false positives. Cppcheck is designed to be able to analyze your C/C++ code even if it has non-standard syntax (common in embedded projects).
33
type: Data Flow Analysis (Compiled code)
44
url: https://cppcheck.sourceforge.io/
5-
supported_version: 2.13.0
5+
supported_version: ">=2.16.0"
66
supported_languages:
77
- C/C++
88
legal:

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ dependencies = [
1515
"lxml>=6.0.2",
1616
"matplotlib>=3.10.3",
1717
"numpy>=2.3.1",
18+
"packaging>=25.0",
1819
"python-on-whales>=0.79.0",
1920
"pyyaml>=6.0.2",
2021
"requests>=2.32.4",

requirements.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,9 @@ numpy==2.3.5 \
437437
packaging==25.0 \
438438
--hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \
439439
--hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f
440-
# via matplotlib
440+
# via
441+
# codesectools
442+
# matplotlib
441443
pillow==11.3.0 \
442444
--hash=sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2 \
443445
--hash=sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214 \

uv.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)