Skip to content

Commit

Permalink
Merge pull request #52 from driftregion/static_analysis
Browse files Browse the repository at this point in the history
add static analysis
  • Loading branch information
driftregion authored Feb 7, 2025
2 parents 4dab62e + 2052d4d commit df1a69e
Show file tree
Hide file tree
Showing 33 changed files with 868 additions and 427 deletions.
1 change: 1 addition & 0 deletions .CodeChecker/cppcheckargs.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--suppressions-list=.CodeChecker/suppressions.txt
150 changes: 150 additions & 0 deletions .CodeChecker/post_to_github.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#!/usr/bin/env python3
import os
import sys
import json
import requests

def chunked(lst, size=50):
"""Yield successive `size`-sized chunks from the list."""
for i in range(0, len(lst), size):
yield lst[i:i + size]

def main(report_path):
# 1. Read environment variables needed for GitHub API
github_token = os.environ.get("GITHUB_TOKEN")
if not github_token:
print("Error: GITHUB_TOKEN not found in environment.")
sys.exit(1)

repository = os.environ.get("GITHUB_REPOSITORY")
if not repository:
print("Error: GITHUB_REPOSITORY not found in environment.")
sys.exit(1)

head_sha = os.environ.get("GITHUB_HEAD_SHA")
if not head_sha:
print("Error: GITHUB_HEAD_SHA not found in environment.")
sys.exit(1)

owner, repo = repository.split("/")
headers = {
"Authorization": f"Bearer {github_token}",
"Accept": "application/vnd.github+json"
}

# 2. Read CodeChecker JSON report
with open(report_path, "r") as f:
data = json.load(f)

reports = data.get("reports", [])
if not reports:
print("No CodeChecker reports found. Exiting without annotations.")
# Optionally, you might create a check run that says "No issues found".
sys.exit(0)

# 3. Build annotations from each CodeChecker issue
annotations = []
highest_severity = "LOW" # track highest severity seen

for r in reports:
file_path = r["file"]["path"]
line = r.get("line", 1)
col = r.get("column", 1)
message = r.get("message", "No message")
severity = r.get("severity", "LOW")
checker = r.get("checker_name", "UnknownChecker")

# Convert absolute path to relative if needed:
# e.g. if your build paths differ from your repo structure,
# you might strip out something like GITHUB_WORKSPACE.
# For example:
workspace = os.environ.get("GITHUB_WORKSPACE", "")
if file_path.startswith(workspace):
file_path = file_path[len(workspace)+1:]

# Convert severity to annotation_level
# GitHub recognizes "notice", "warning", or "failure" for annotation_level.
# We'll treat HIGH as 'failure', otherwise 'warning' or 'notice'.
if severity.upper() == "HIGH":
annotation_level = "failure"
highest_severity = "HIGH"
else:
annotation_level = "warning"

annotations.append({
"path": file_path,
"start_line": line,
"end_line": line,
# if you want column coverage:
"start_column": col,
"end_column": col,
"annotation_level": annotation_level,
"message": message,
"title": checker
})

# Decide final conclusion:
# e.g., if we found any 'HIGH' severity issues, mark "failure".
# else "success", or "neutral", depending on your preference.
conclusion = "success"
if highest_severity == "HIGH":
conclusion = "failure"

# 4. Create a check run (status in_progress) to get an ID
create_url = f"https://api.github.com/repos/{owner}/{repo}/check-runs"
create_payload = {
"name": "CodeChecker Analysis",
"head_sha": head_sha,
"status": "in_progress"
}

resp = requests.post(create_url, headers=headers, json=create_payload)
if resp.status_code not in (200, 201):
print("Error creating check run:", resp.text)
sys.exit(1)

check_run_id = resp.json()["id"]
print(f"Created check run with ID={check_run_id}")

# 5. Update the check run in multiple batches if needed (50 annotations per request limit).
update_url = f"https://api.github.com/repos/{owner}/{repo}/check-runs/{check_run_id}"

all_annotations = len(annotations)
index = 0

for chunk in chunked(annotations, 50):
index += len(chunk)

# Only on the *final chunk* do we set status "completed" with conclusion
is_last_chunk = (index == all_annotations)

update_payload = {
"name": "CodeChecker Analysis",
"head_sha": head_sha,
# partial updates can keep "status" as "in_progress" until last chunk
"status": "in_progress",
"output": {
"title": "CodeChecker Results",
"summary": f"Found {all_annotations} issue(s).",
"annotations": chunk
}
}

if is_last_chunk:
update_payload["conclusion"] = conclusion
update_payload["status"] = "completed"

resp = requests.patch(update_url, headers=headers, json=update_payload)
if resp.status_code not in (200, 201):
print("Error updating check run:", resp.text)
sys.exit(1)

print(f"Updated check run with {len(chunk)} annotations (total {index}/{all_annotations}). Last chunk: {is_last_chunk}")

print("All annotations posted successfully.")

if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python post_to_github.py <report.json>")
sys.exit(1)
main(sys.argv[1])
14 changes: 14 additions & 0 deletions .CodeChecker/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/bash

CodeChecker analyze compile_commands.json \
--ignore .CodeChecker/skipfile_bazel.txt \
-o reports

# MISRA checking via CodeChecker isn't working properly. Use ./.cppcheck/run.sh instead.
# CodeChecker analyze compile_commands.json \
# --ignore .CodeChecker/skipfile_bazel.txt \
# --ignore .CodeChecker/skipfile_thirdparty.txt \
# --cppcheckargs .CodeChecker/cppcheckargs.txt \
# --analyzer-config cppcheck:addons=.cppcheck/misra.json \
# --analyzer-config cppcheck:platform=unix64 \
# -o misra_reports
1 change: 1 addition & 0 deletions .CodeChecker/skipfile_bazel.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-*/launcher_maker.cc
1 change: 1 addition & 0 deletions .CodeChecker/skipfile_thirdparty.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-*src/tp/isotp-c/*
6 changes: 6 additions & 0 deletions .CodeChecker/suppressions.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
misra-c2012-15.5 // Rule 15.5 Advisory A function should have a single point of exit at the end
// A project should not contain unused macro definitions
misra-c2012-2.5:src/uds.h // this file declares macros intended for external use, not all of which are used internally

// Don't warn on system header contents
*:*/usr/include/*
6 changes: 6 additions & 0 deletions .cppcheck/misra.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"script": "misra.py",
"args": [
"--rule-texts=.cppcheck/misra_c_2023__headlines_for_cppcheck.txt"
]
}
20 changes: 20 additions & 0 deletions .cppcheck/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/bash
mkdir -p ./cppcheck_reports ./codechecker_cppcheck_reports


cppcheck \
--project=compile_commands.json \
--platform=unix64 \
--enable=all \
--addon=.cppcheck/misra.json \
--suppressions-list=.CodeChecker/suppressions.txt \
--plist-output=cppcheck_reports \
-DUDS_SYS=UDS_SYS_UNIX \
--checkers-report=cppcheck_reports/checkers.txt \
--library=posix \
--output-file=cppcheck_reports/cppcheck.txt \
--file-filter='src/*' \


# report-converter -c -t cppcheck -o ./codechecker_cppcheck_reports ./cppcheck_reports
# CodeChecker parse codechecker_cppcheck_reports --trim-path-prefix '*/cppcheck_reports' > report.txt
42 changes: 42 additions & 0 deletions .github/workflows/static_analysis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Static Analysis

on:
pull_request:
branches: [ "main" ]


jobs:
build:
runs-on: ubuntu-latest

steps:
- name: checkout
uses: actions/checkout@v3

- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y build-essential cmake python3-pip jq cppcheck
pip3 install codechecker requests
CodeChecker analyzers --details
- name: Analyze with CodeChecker
run: |
make static_analysis
- name: Print report summary
run: |
CodeChecker parse reports || true
- name: Generate json report
run: |
CodeChecker parse reports --export json -o report.json || true
- name: Create Check Run with Annotations
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
GITHUB_REPOSITORY: ${{ github.repository }}
run: |
python3 .CodeChecker/post_to_github.py report.json
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,10 @@ compile_commands.json
MODULE.bazel.lock
iso14229.c
iso14229.h
html_report
misra_reports
**/*.ctu-info
**/*.dump
report.txt
json_report.json
cppcheck_reports
26 changes: 25 additions & 1 deletion BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,30 @@ refresh_compile_commands(
}
)

refresh_compile_commands(
name = "example_compile_commands",
targets = {
"//examples/linux_rdbi_wdbi:all": "",
}
)

refresh_compile_commands(
name = "lib_compile_commands",
targets = {
"//:iso14229": "",
}
)

cc_library(
name = "iso14229",
srcs=glob(["src/**/*.c", "src/**/*.h"]),
includes=["src"],
copts = [
# gcc adds system headers by default. However, the compile_commands.json used for static analysis needs this include path to be explicit.
"-I/usr/include",
],
)

py_binary(
name="amalgamate",
srcs=["amalgamate.py"],
Expand All @@ -33,4 +57,4 @@ genrule(
],
outs = ["iso14229.zip"],
cmd = "mkdir iso14229 && cp -L $(SRCS) iso14229/ && zip -r $(OUTS) iso14229",
)
)
15 changes: 15 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
all: static_analysis

MISRA_RULES_TXT = .cppcheck/misra_c_2023__headlines_for_cppcheck.txt

$(MISRA_RULES_TXT):
mkdir -p .cppcheck
wget -O $(MISRA_RULES_TXT) https://gitlab.com/MISRA/MISRA-C/MISRA-C-2012/tools/-/raw/main/misra_c_2023__headlines_for_cppcheck.txt

compile_commands.json:
bazel build //:iso14229 && bazel run //:lib_compile_commands

static_analysis: compile_commands.json $(MISRA_RULES_TXT)
.CodeChecker/run.sh

.phony: static_analysis compile_commands.json
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ see `enum UDSEvent` in [src/uds.h](src/uds.h)

```c
typedef struct {
const enum UDSDiagnosticSessionType type; /**< requested session type */
const uint8_t type; /**< requested session type */
uint16_t p2_ms; /**< optional return value: p2 timing override */
uint32_t p2_star_ms; /**< optional return value: p2* timing override */
} UDSDiagSessCtrlArgs_t;
Expand All @@ -114,7 +114,7 @@ typedef struct {

```c
typedef struct {
const enum UDSECUResetType type; /**< reset type requested by client */
const uint8_t type; /**< reset type requested by client */
uint8_t powerDownTime; /**< Optional response: notify client of time until shutdown (0-254) 255
indicates that a time is not available. */
} UDSECUResetArgs_t;
Expand Down Expand Up @@ -189,8 +189,8 @@ typedef struct {

```c
typedef struct {
enum UDSCommunicationControlType ctrlType;
enum UDSCommunicationType commType;
uint8_t ctrlType;
uint8_t commType;
} UDSCommCtrlArgs_t;
```
#### Supported Responses
Expand Down Expand Up @@ -393,6 +393,10 @@ bazel build //:release

# Changelog

## 0.9.0
- breaking API changes:
- converted subfunction enums to #defines with standard-consistent naming

## 0.8.0
- breaking API changes:
- event enum consolidated `UDS_SRV_EVT_...` -> `UDS_EVT`
Expand Down
Loading

0 comments on commit df1a69e

Please sign in to comment.