Skip to content

Commit

Permalink
Moved to cppcheck-backed static analysis
Browse files Browse the repository at this point in the history
The previous approach used clang static analysis, which is slow and
requires a full build of the ledger code. cppcheck provides similar
results with a more lightweight approach and is much faster.
  • Loading branch information
italo-sampaio committed Dec 14, 2023
1 parent e1dee5d commit 2ce7566
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 71 deletions.
3 changes: 1 addition & 2 deletions docker/ledger/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ WORKDIR /opt
RUN apt-get update && \
apt-get install -y apt-utils && \
apt-get install -y curl && \
apt-get install -y build-essential=12.8ubuntu1 && \
apt-get install -y clang-tools
apt-get install -y build-essential=12.8ubuntu1

RUN curl -L -o gcc-arm-none-eabi-9-2020-q2-update-x86_64-linux.tar.bz2 \
https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2020q2/gcc-arm-none-eabi-9-2020-q2-update-x86_64-linux.tar.bz2 && \
Expand Down
3 changes: 2 additions & 1 deletion docker/mware/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ RUN apt-get update && \
apt-get install -y apt-utils vim && \
apt-get install -y build-essential=12.9 && \
apt-get install -y git && \
apt-get install -y lcov
apt-get install -y lcov && \
apt-get install -y cppcheck

# Python package prerequisites
RUN apt-get install -y \
Expand Down
1 change: 1 addition & 0 deletions ledger/static-analysis/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
output
120 changes: 120 additions & 0 deletions ledger/static-analysis/gen-report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
from argparse import ArgumentParser
import xml.etree.ElementTree as ET
import os


class ErrorRecord:
def __init__(self, id, severity, message, file, line, column):
self.id = id
self.severity = severity
self.message = message
self.file = file
self.line = line
self.column = column


def get_error_list(xml_file):
tree = ET.parse(xml_file)
root = tree.getroot()

error_list = []

for error in root.iter('error'):
id = error.get('id')
severity = error.get('severity')
message = error.get('msg')
location = error.find('location')
file = location.get('file')
line = location.get('line')
column = location.get('col')
error_list.append(ErrorRecord(id, severity, message, file, line, column))

return error_list


def get_report_paths(input_dir):
common_report = os.path.join(input_dir, "common.xml")
signer_report = os.path.join(input_dir, "signer.xml")
ui_report = os.path.join(input_dir, "ui.xml")
tcpsigner_report = os.path.join(input_dir, "tcpsigner.xml")

if not os.path.isfile(common_report):
raise Exception(f"File not found: {common_report}")

if not os.path.isfile(signer_report):
raise Exception(f"File not found: {signer_report}")

if not os.path.isfile(ui_report):
raise Exception(f"File not found: {ui_report}")

if not os.path.isfile(tcpsigner_report):
raise Exception(f"File not found: {tcpsigner_report}")

return [common_report, signer_report, ui_report, tcpsigner_report]


def generate_report(report, cppcheck_report):
error_list = get_error_list(cppcheck_report)
if len(error_list) == 0:
report.write("### No errors found!\n\n")
else:
report.write(f"### {len(error_list)} errors:\n\n")
report.write("| File | Message | Severity |\n")
report.write("|------|---------|----------|\n")
for error in error_list:
report.write(f"| {error.file}:{error.line} |"
f" {error.message} |"
f" {error.severity} |\n")


def main():
parser = ArgumentParser(description="Generates a report from cppcheck XML outputs")
parser.add_argument(
"-d",
"--inputDir",
dest="input_dir",
help="Directory containing cppcheck XML output files.",
required=True,
)
parser.add_argument(
"-o",
"--output",
dest="output_path",
help="Output file path (Markdown report).",
required=True,
)
options = parser.parse_args()

try:
[common_report, signer_report,
ui_report, tcpsigner_report] = get_report_paths(options.input_dir)

# If an old report already exists, remove it
if os.path.exists(options.output_path):
os.remove(options.output_path)

# Create and open report file
with open(options.output_path, "w") as report:
# Write report title
report.write("# rsk-powhsm Static Analysis Report\n\n")
# Write reports for each module
report.write("## Common\n\n")
generate_report(report, common_report)
report.write("****\n")
report.write("## Signer\n\n")
generate_report(report, signer_report)
report.write("****\n")
report.write("## UI\n\n")
generate_report(report, ui_report)
report.write("****\n")
report.write("## TCPSigner\n\n")
generate_report(report, tcpsigner_report)

print(f"Report generated at {options.output_path}")
except Exception as e:
print(f"Error: {e}")
exit(1)


if __name__ == "__main__":
main()
61 changes: 61 additions & 0 deletions ledger/static-analysis/gen-static-analysis
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/bin/bash

if [[ $1 == "exec" ]]; then
BASEDIR=$(realpath $(dirname $0))
SRCDIR=$(realpath $BASEDIR/../src)
REPOROOT=$(realpath $BASEDIR/../..)

# Remove any existing static analysis data
rm -rf $BASEDIR/output && mkdir $BASEDIR/output

CCCHECKER=cppcheck
CCCHECKER_FLAGS="--quiet --enable=warning --inline-suppr --error-exitcode=1 --xml --xml-version=2"

found_errors=0
# Run static analysis on ledger common
COMMON_SRC_DIR=$SRCDIR/common/src
find $COMMON_SRC_DIR -name "*.[ch]" | \
xargs ${CCCHECKER} ${CCCHECKER_FLAGS} 2> $BASEDIR/output/common.xml
if [[ $? -ne 0 ]]; then
found_errors=1
fi

# Run static analysis on ledger signer
SIGNER_SRC_DIR=$SRCDIR/signer/src
find $SIGNER_SRC_DIR -name "*.[ch]" | \
egrep -v "(bigdigits|bigdtypes|keccak256)\.[ch]$" | \
xargs ${CCCHECKER} ${CCCHECKER_FLAGS} 2> $BASEDIR/output/signer.xml
if [[ $? -ne 0 ]]; then
found_errors=1
fi

# Run static analysis on ledger ui
UI_SRC_DIR=$SRCDIR/ui/src
find $UI_SRC_DIR -name "*.[ch]" | \
xargs ${CCCHECKER} ${CCCHECKER_FLAGS} 2> $BASEDIR/output/ui.xml
if [[ $? -ne 0 ]]; then
found_errors=1
fi

# Run static analysis on ledger tcpsigner
TCPSIGNER_SRC_DIR=$SRCDIR/tcpsigner
find $TCPSIGNER_SRC_DIR -name "*.[ch]" | \
egrep -v "(bigdigits|bigdtypes|cJSON)\.[ch]$" | \
xargs ${CCCHECKER} ${CCCHECKER_FLAGS} 2> $BASEDIR/output/tcpsigner.xml
if [[ $? -ne 0 ]]; then
found_errors=1
fi

# If any of the checks above found an error, we want this script to fail to inform the CI.
err_code=$found_errors
else
# Script directory
REPOROOT=$(realpath $(dirname $0)/../..)
SCRIPT=$(realpath $0 --relative-to=$REPOROOT)

# Generate coverage report
$REPOROOT/docker/mware/do-notty-nousb /hsm2 "./$SCRIPT exec"
err_code=$?
fi

exit $err_code
34 changes: 0 additions & 34 deletions static-analysis-signer

This file was deleted.

34 changes: 0 additions & 34 deletions static-analysis-ui

This file was deleted.

0 comments on commit 2ce7566

Please sign in to comment.