Skip to content

Commit df1a69e

Browse files
authored
Merge pull request #52 from driftregion/static_analysis
add static analysis
2 parents 4dab62e + 2052d4d commit df1a69e

33 files changed

+868
-427
lines changed

.CodeChecker/cppcheckargs.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
--suppressions-list=.CodeChecker/suppressions.txt

.CodeChecker/post_to_github.py

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
#!/usr/bin/env python3
2+
import os
3+
import sys
4+
import json
5+
import requests
6+
7+
def chunked(lst, size=50):
8+
"""Yield successive `size`-sized chunks from the list."""
9+
for i in range(0, len(lst), size):
10+
yield lst[i:i + size]
11+
12+
def main(report_path):
13+
# 1. Read environment variables needed for GitHub API
14+
github_token = os.environ.get("GITHUB_TOKEN")
15+
if not github_token:
16+
print("Error: GITHUB_TOKEN not found in environment.")
17+
sys.exit(1)
18+
19+
repository = os.environ.get("GITHUB_REPOSITORY")
20+
if not repository:
21+
print("Error: GITHUB_REPOSITORY not found in environment.")
22+
sys.exit(1)
23+
24+
head_sha = os.environ.get("GITHUB_HEAD_SHA")
25+
if not head_sha:
26+
print("Error: GITHUB_HEAD_SHA not found in environment.")
27+
sys.exit(1)
28+
29+
owner, repo = repository.split("/")
30+
headers = {
31+
"Authorization": f"Bearer {github_token}",
32+
"Accept": "application/vnd.github+json"
33+
}
34+
35+
# 2. Read CodeChecker JSON report
36+
with open(report_path, "r") as f:
37+
data = json.load(f)
38+
39+
reports = data.get("reports", [])
40+
if not reports:
41+
print("No CodeChecker reports found. Exiting without annotations.")
42+
# Optionally, you might create a check run that says "No issues found".
43+
sys.exit(0)
44+
45+
# 3. Build annotations from each CodeChecker issue
46+
annotations = []
47+
highest_severity = "LOW" # track highest severity seen
48+
49+
for r in reports:
50+
file_path = r["file"]["path"]
51+
line = r.get("line", 1)
52+
col = r.get("column", 1)
53+
message = r.get("message", "No message")
54+
severity = r.get("severity", "LOW")
55+
checker = r.get("checker_name", "UnknownChecker")
56+
57+
# Convert absolute path to relative if needed:
58+
# e.g. if your build paths differ from your repo structure,
59+
# you might strip out something like GITHUB_WORKSPACE.
60+
# For example:
61+
workspace = os.environ.get("GITHUB_WORKSPACE", "")
62+
if file_path.startswith(workspace):
63+
file_path = file_path[len(workspace)+1:]
64+
65+
# Convert severity to annotation_level
66+
# GitHub recognizes "notice", "warning", or "failure" for annotation_level.
67+
# We'll treat HIGH as 'failure', otherwise 'warning' or 'notice'.
68+
if severity.upper() == "HIGH":
69+
annotation_level = "failure"
70+
highest_severity = "HIGH"
71+
else:
72+
annotation_level = "warning"
73+
74+
annotations.append({
75+
"path": file_path,
76+
"start_line": line,
77+
"end_line": line,
78+
# if you want column coverage:
79+
"start_column": col,
80+
"end_column": col,
81+
"annotation_level": annotation_level,
82+
"message": message,
83+
"title": checker
84+
})
85+
86+
# Decide final conclusion:
87+
# e.g., if we found any 'HIGH' severity issues, mark "failure".
88+
# else "success", or "neutral", depending on your preference.
89+
conclusion = "success"
90+
if highest_severity == "HIGH":
91+
conclusion = "failure"
92+
93+
# 4. Create a check run (status in_progress) to get an ID
94+
create_url = f"https://api.github.com/repos/{owner}/{repo}/check-runs"
95+
create_payload = {
96+
"name": "CodeChecker Analysis",
97+
"head_sha": head_sha,
98+
"status": "in_progress"
99+
}
100+
101+
resp = requests.post(create_url, headers=headers, json=create_payload)
102+
if resp.status_code not in (200, 201):
103+
print("Error creating check run:", resp.text)
104+
sys.exit(1)
105+
106+
check_run_id = resp.json()["id"]
107+
print(f"Created check run with ID={check_run_id}")
108+
109+
# 5. Update the check run in multiple batches if needed (50 annotations per request limit).
110+
update_url = f"https://api.github.com/repos/{owner}/{repo}/check-runs/{check_run_id}"
111+
112+
all_annotations = len(annotations)
113+
index = 0
114+
115+
for chunk in chunked(annotations, 50):
116+
index += len(chunk)
117+
118+
# Only on the *final chunk* do we set status "completed" with conclusion
119+
is_last_chunk = (index == all_annotations)
120+
121+
update_payload = {
122+
"name": "CodeChecker Analysis",
123+
"head_sha": head_sha,
124+
# partial updates can keep "status" as "in_progress" until last chunk
125+
"status": "in_progress",
126+
"output": {
127+
"title": "CodeChecker Results",
128+
"summary": f"Found {all_annotations} issue(s).",
129+
"annotations": chunk
130+
}
131+
}
132+
133+
if is_last_chunk:
134+
update_payload["conclusion"] = conclusion
135+
update_payload["status"] = "completed"
136+
137+
resp = requests.patch(update_url, headers=headers, json=update_payload)
138+
if resp.status_code not in (200, 201):
139+
print("Error updating check run:", resp.text)
140+
sys.exit(1)
141+
142+
print(f"Updated check run with {len(chunk)} annotations (total {index}/{all_annotations}). Last chunk: {is_last_chunk}")
143+
144+
print("All annotations posted successfully.")
145+
146+
if __name__ == "__main__":
147+
if len(sys.argv) < 2:
148+
print("Usage: python post_to_github.py <report.json>")
149+
sys.exit(1)
150+
main(sys.argv[1])

.CodeChecker/run.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/bin/bash
2+
3+
CodeChecker analyze compile_commands.json \
4+
--ignore .CodeChecker/skipfile_bazel.txt \
5+
-o reports
6+
7+
# MISRA checking via CodeChecker isn't working properly. Use ./.cppcheck/run.sh instead.
8+
# CodeChecker analyze compile_commands.json \
9+
# --ignore .CodeChecker/skipfile_bazel.txt \
10+
# --ignore .CodeChecker/skipfile_thirdparty.txt \
11+
# --cppcheckargs .CodeChecker/cppcheckargs.txt \
12+
# --analyzer-config cppcheck:addons=.cppcheck/misra.json \
13+
# --analyzer-config cppcheck:platform=unix64 \
14+
# -o misra_reports

.CodeChecker/skipfile_bazel.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-*/launcher_maker.cc

.CodeChecker/skipfile_thirdparty.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-*src/tp/isotp-c/*

.CodeChecker/suppressions.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
misra-c2012-15.5 // Rule 15.5 Advisory A function should have a single point of exit at the end
2+
// A project should not contain unused macro definitions
3+
misra-c2012-2.5:src/uds.h // this file declares macros intended for external use, not all of which are used internally
4+
5+
// Don't warn on system header contents
6+
*:*/usr/include/*

.cppcheck/misra.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"script": "misra.py",
3+
"args": [
4+
"--rule-texts=.cppcheck/misra_c_2023__headlines_for_cppcheck.txt"
5+
]
6+
}

.cppcheck/run.sh

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/bin/bash
2+
mkdir -p ./cppcheck_reports ./codechecker_cppcheck_reports
3+
4+
5+
cppcheck \
6+
--project=compile_commands.json \
7+
--platform=unix64 \
8+
--enable=all \
9+
--addon=.cppcheck/misra.json \
10+
--suppressions-list=.CodeChecker/suppressions.txt \
11+
--plist-output=cppcheck_reports \
12+
-DUDS_SYS=UDS_SYS_UNIX \
13+
--checkers-report=cppcheck_reports/checkers.txt \
14+
--library=posix \
15+
--output-file=cppcheck_reports/cppcheck.txt \
16+
--file-filter='src/*' \
17+
18+
19+
# report-converter -c -t cppcheck -o ./codechecker_cppcheck_reports ./cppcheck_reports
20+
# CodeChecker parse codechecker_cppcheck_reports --trim-path-prefix '*/cppcheck_reports' > report.txt

.github/workflows/static_analysis.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: Static Analysis
2+
3+
on:
4+
pull_request:
5+
branches: [ "main" ]
6+
7+
8+
jobs:
9+
build:
10+
runs-on: ubuntu-latest
11+
12+
steps:
13+
- name: checkout
14+
uses: actions/checkout@v3
15+
16+
- name: Install dependencies
17+
run: |
18+
sudo apt-get update
19+
sudo apt-get install -y build-essential cmake python3-pip jq cppcheck
20+
pip3 install codechecker requests
21+
22+
CodeChecker analyzers --details
23+
24+
- name: Analyze with CodeChecker
25+
run: |
26+
make static_analysis
27+
28+
- name: Print report summary
29+
run: |
30+
CodeChecker parse reports || true
31+
32+
- name: Generate json report
33+
run: |
34+
CodeChecker parse reports --export json -o report.json || true
35+
36+
- name: Create Check Run with Annotations
37+
env:
38+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
39+
GITHUB_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
40+
GITHUB_REPOSITORY: ${{ github.repository }}
41+
run: |
42+
python3 .CodeChecker/post_to_github.py report.json

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,10 @@ compile_commands.json
2727
MODULE.bazel.lock
2828
iso14229.c
2929
iso14229.h
30+
html_report
31+
misra_reports
32+
**/*.ctu-info
33+
**/*.dump
34+
report.txt
35+
json_report.json
36+
cppcheck_reports

0 commit comments

Comments
 (0)