Skip to content

Commit e10a5d3

Browse files
committed
Add support for running all available SASTs
1 parent 63722b9 commit e10a5d3

8 files changed

Lines changed: 628 additions & 4 deletions

File tree

codesectools/cli.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from codesectools.datasets import DATASETS_ALL
1818
from codesectools.datasets.core.dataset import Dataset
1919
from codesectools.sasts import SASTS_ALL
20+
from codesectools.sasts.all.cli import cli as all_sast_cli
2021
from codesectools.sasts.core.sast.requirements import DownloadableRequirement
2122

2223
cli = typer.Typer(name="cstools", no_args_is_help=True)
@@ -56,7 +57,7 @@ def status(
5657
bool, typer.Option("--datasets", help="Show datasets only")
5758
] = False,
5859
) -> None:
59-
"""Display the availability status of SASTs and the cache status of datasets."""
60+
"""Display the availability of SASTs and datasets."""
6061
if sasts or (not sasts and not datasets):
6162
table = Table(show_lines=True)
6263
table.add_column("SAST", justify="center", no_wrap=True)
@@ -159,5 +160,7 @@ def download(
159160
downloadable.download_dataset()
160161

161162

163+
cli.add_typer(all_sast_cli)
164+
162165
for _, sast_data in SASTS_ALL.items():
163166
cli.add_typer(sast_data["cli_factory"].build_cli())

codesectools/sasts/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
SASTS_ALL = {}
2525
for child in SASTS_DIR.iterdir():
2626
if child.is_dir():
27-
if list(child.glob("sast.py")) and child.name != "core":
27+
if list(child.glob("sast.py")) and child.name not in ["all", "core"]:
2828
sast_name = child.name
2929

3030
sast_module = importlib.import_module(

codesectools/sasts/all/cli.py

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
"""Defines the command-line interface for running all available SASTs."""
2+
3+
import shutil
4+
from pathlib import Path
5+
6+
import typer
7+
from click import Choice
8+
from rich import print
9+
from rich.table import Table
10+
from typing_extensions import Annotated
11+
12+
from codesectools.datasets import DATASETS_ALL
13+
from codesectools.datasets.core.dataset import FileDataset, GitRepoDataset
14+
from codesectools.sasts import SASTS_ALL
15+
from codesectools.sasts.all.graphics import ProjectGraphics
16+
from codesectools.sasts.all.sast import AllSAST
17+
18+
cli = typer.Typer(name="allsast", no_args_is_help=True)
19+
all_sast = AllSAST()
20+
21+
22+
@cli.callback()
23+
def main() -> None:
24+
"""Run all available SASTs together."""
25+
pass
26+
27+
28+
@cli.command(help="List used SASTs.")
29+
def info() -> None:
30+
"""Display the status of all SASTs and their inclusion in AllSAST."""
31+
table = Table(show_lines=True)
32+
table.add_column("SAST", justify="center", no_wrap=True)
33+
table.add_column("Status", justify="center", no_wrap=True)
34+
table.add_column("Note", justify="center")
35+
for sast_name, sast_data in SASTS_ALL.items():
36+
if sast_data["status"] == "full":
37+
table.add_row(
38+
sast_name,
39+
"Full",
40+
"[b]Included ✅[/b] in AllSAST",
41+
)
42+
elif sast_data["status"] == "partial":
43+
table.add_row(
44+
sast_name,
45+
"Partial",
46+
f"[b]Not included ❌[/b] is available\nMissing: [red]{sast_data['missing']}[/red]",
47+
)
48+
else:
49+
table.add_row(
50+
sast_name,
51+
"None",
52+
f"[b]Not included ❌[/b] is available\nMissing: [red]{sast_data['missing']}[/red]",
53+
)
54+
print(table)
55+
56+
57+
@cli.command(help="Analyze a project using all availbale SASTs.")
58+
def analyze(
59+
lang: Annotated[
60+
str,
61+
typer.Argument(
62+
click_type=Choice(all_sast.supported_languages),
63+
help="Source code language (only one at the time)",
64+
metavar="LANG",
65+
),
66+
],
67+
overwrite: Annotated[
68+
bool,
69+
typer.Option(
70+
"--overwrite",
71+
help="Overwrite existing analysis results for current project",
72+
),
73+
] = False,
74+
) -> None:
75+
"""Run analysis on the current project with all available SASTs."""
76+
for sast in all_sast.sasts:
77+
output_dir = sast.output_dir / Path.cwd().name
78+
if output_dir.is_dir():
79+
if overwrite:
80+
shutil.rmtree(output_dir)
81+
sast.run_analysis(lang, Path.cwd(), output_dir)
82+
else:
83+
print(f"Found existing analysis result at {output_dir}")
84+
print("Use --overwrite to overwrite it")
85+
else:
86+
sast.run_analysis(lang, Path.cwd(), output_dir)
87+
88+
89+
@cli.command(help="Benchmark a dataset using all SASTs.")
90+
def benchmark(
91+
dataset: Annotated[
92+
str,
93+
typer.Argument(
94+
click_type=Choice(all_sast.supported_dataset_full_names),
95+
metavar="DATASET",
96+
),
97+
],
98+
overwrite: Annotated[
99+
bool,
100+
typer.Option(
101+
"--overwrite",
102+
help="Overwrite existing results (not applicable on CVEfixes)",
103+
),
104+
] = False,
105+
testing: Annotated[
106+
bool,
107+
typer.Option(
108+
"--testing",
109+
help="Run benchmark over a single dataset unit for testing",
110+
),
111+
] = False,
112+
) -> None:
113+
"""Run a benchmark on a dataset using all available SASTs."""
114+
dataset_name, lang = dataset.split("_")
115+
for sast in all_sast.sasts:
116+
dataset = DATASETS_ALL[dataset_name](lang)
117+
if isinstance(dataset, FileDataset):
118+
sast.analyze_files(dataset, overwrite, testing)
119+
elif isinstance(dataset, GitRepoDataset):
120+
sast.analyze_repos(dataset, overwrite, testing)
121+
122+
123+
@cli.command(help="List existing analysis results.")
124+
def list() -> None:
125+
"""List existing analysis results for projects and datasets."""
126+
table = Table(show_lines=True)
127+
table.add_column("Name", justify="center", no_wrap=True)
128+
table.add_column("Type", justify="center", no_wrap=True)
129+
table.add_column("Analyzed with", justify="center", no_wrap=True)
130+
131+
for dataset_full_name in all_sast.list_results(dataset=True):
132+
table.add_row(
133+
dataset_full_name,
134+
"Dataset",
135+
", ".join(f"[b]{sast.name}[/b]" for sast in all_sast.sasts),
136+
)
137+
for project in all_sast.list_results(project=True):
138+
table.add_row(
139+
project,
140+
"Project",
141+
", ".join(f"[b]{sast.name}[/b]" for sast in all_sast.sasts),
142+
)
143+
144+
print(table)
145+
146+
147+
@cli.command(
148+
help="Generate plot for results visualization (datasets are not supported)."
149+
)
150+
def plot(
151+
result: Annotated[
152+
str,
153+
typer.Argument(
154+
click_type=Choice(all_sast.list_results(project=True)),
155+
metavar="RESULT",
156+
),
157+
],
158+
) -> None:
159+
"""Generate and display plots for a project's aggregated analysis results."""
160+
if result in all_sast.list_results(project=True):
161+
project = result
162+
project_graphics = ProjectGraphics(project_name=project)
163+
project_graphics.show()

0 commit comments

Comments
 (0)