diff --git a/tools/static-analysis/clang-tidy-builder.sh b/tools/static-analysis/clang-tidy-builder.sh index 1a5b8a73..6b1b509b 100755 --- a/tools/static-analysis/clang-tidy-builder.sh +++ b/tools/static-analysis/clang-tidy-builder.sh @@ -5,7 +5,7 @@ shopt -s extglob || exit $? retval=0 -if [[ -v CLANG_TIDY ]]; then +if [[ -n "${CLANG_TIDY+x}" ]]; then while :; do @@ -17,27 +17,26 @@ if [[ -v CLANG_TIDY ]]; then done unset arg - if [[ ! -v source_file ]]; then + if [[ -z "${source_file+x}" ]]; then retval=0 break fi - if [[ ! "$source_file" =~ (^|/)src/[^/]+\.c$ ]] && [[ ! "$source_file" =~ (^|/)wolfhsm/[^/]+\.c$ ]]; then - if [[ -v CLANG_OVERRIDE_CFLAGS ]]; then - read -a CLANG_OVERRIDE_CFLAGS_a < <(echo "${CLANG_OVERRIDE_CFLAGS-}") + # Skip wolfssl/ and .github/ directories for wolfHSM project + if [[ "$source_file" =~ wolfssl/ ]] || [[ "$source_file" =~ \.github/ ]]; then + # Just compile without clang-tidy for these directories + if [[ -n "${CLANG_OVERRIDE_CFLAGS+x}" ]]; then + # shellcheck disable=SC2162 # we want backslashes to be interpreted here. + read -a CLANG_OVERRIDE_CFLAGS_a < <(echo "${CLANG_OVERRIDE_CFLAGS}") else CLANG_OVERRIDE_CFLAGS_a=() fi exec "$CLANG" "$@" "${CLANG_OVERRIDE_CFLAGS_a[@]}" fi - if [[ -v CLANG_TIDY_ARGS ]]; then - read -r -a clang_tidy_args_array < <(echo "$CLANG_TIDY_ARGS") || exit $? - else - clang_tidy_args_array=() - fi + read -r -a clang_tidy_args_array < <(echo "$CLANG_TIDY_ARGS") || exit $? - if [[ -v CLANG_TIDY_PER_FILE_CHECKS ]]; then + if [[ -n "${CLANG_TIDY_PER_FILE_CHECKS+x}" ]]; then per_file_checks=() read -r -a clang_tidy_per_file_checks < <(echo "$CLANG_TIDY_PER_FILE_CHECKS") || exit $? for check in "${clang_tidy_per_file_checks[@]}"; do @@ -48,7 +47,7 @@ if [[ -v CLANG_TIDY ]]; then unset check fi - if [[ -v per_file_checks ]]; then + if [[ -n "${per_file_checks+x}" ]]; then declare -i i=0 while [[ $i -lt ${#clang_tidy_args_array[@]} ]]; do if [[ "${clang_tidy_args_array[i]}" =~ ^-checks ]]; then @@ -61,7 +60,7 @@ if [[ -v CLANG_TIDY ]]; then fi : $((++i)) done - if [[ ! -v added_to_existing_checks ]]; then + if [[ -z "${added_to_existing_checks+x}" ]]; then SAVE_IFS="$IFS" IFS=, clang_tidy_args_array+=("-checks=${per_file_checks[*]}") @@ -69,7 +68,7 @@ if [[ -v CLANG_TIDY ]]; then fi fi - if [[ -v CLANG_TIDY_PER_FILE_ARGS ]]; then + if [[ -n "${CLANG_TIDY_PER_FILE_ARGS+x}" ]]; then read -r -a clang_tidy_per_file_args < <(echo "$CLANG_TIDY_PER_FILE_ARGS") || exit $? for arg in "${clang_tidy_per_file_args[@]}"; do if [[ "$source_file" =~ ${arg%:*} ]]; then @@ -79,11 +78,11 @@ if [[ -v CLANG_TIDY ]]; then unset arg fi - if [[ -v CLANG_TIDY_CONFIG ]]; then + if [[ -n "${CLANG_TIDY_CONFIG+x}" ]]; then clang_tidy_args_array+=("-config=${CLANG_TIDY_CONFIG}") fi - if [[ -v CLANG_TIDY_EXTRA_ARGS ]]; then + if [[ -n "${CLANG_TIDY_EXTRA_ARGS+x}" ]]; then read -r -a clang_tidy_extra_args < <(echo "$CLANG_TIDY_EXTRA_ARGS") || exit $? clang_tidy_args_array+=("${clang_tidy_extra_args[@]}") fi @@ -96,7 +95,7 @@ if [[ -v CLANG_TIDY ]]; then done unset arg - if [[ -v use_color ]]; then + if [[ -n "${use_color+x}" ]]; then if text_normal_start="$(tput sgr0)"; then do_style_restore= fi @@ -106,19 +105,19 @@ if [[ -v CLANG_TIDY ]]; then case "$clang_tidy_line" in Use\ -header-filter=.*\ to\ display\ errors\ from\ all\ non-system\ headers.\ Use\ -system-headers\ to\ display\ errors\ from\ system\ headers\ as\ well.) - [[ -v do_style_restore ]] && echo -n "$text_normal_start" >&2 + [[ -n "${do_style_restore+x}" ]] && echo -n "$text_normal_start" >&2 ;; +([0-9])\ warning?(s)\ generated.) - [[ -v do_style_restore ]] && echo -n "$text_normal_start" >&2 + [[ -n "${do_style_restore+x}" ]] && echo -n "$text_normal_start" >&2 ;; Suppressed\ +([0-9])\ warnings\ \(+([0-9])\ NOLINT\).) IFS="[( ]" read -r -a clang_tidy_line_a < <(echo "$clang_tidy_line") if [[ "${clang_tidy_line_a[3]}" == "${clang_tidy_line_a[1]}" ]]; then - [[ -v do_style_restore ]] && echo -n "$text_normal_start" >&2 + [[ -n "${do_style_restore+x}" ]] && echo -n "$text_normal_start" >&2 else echo "$clang_tidy_line" >&2 fi @@ -128,7 +127,7 @@ if [[ -v CLANG_TIDY ]]; then IFS="[( ]" read -r -a clang_tidy_line_a < <(echo "$clang_tidy_line") if [[ $((clang_tidy_line_a[3] + clang_tidy_line_a[7])) == "${clang_tidy_line_a[1]}" ]]; then - [[ -v do_style_restore ]] && echo -n "$text_normal_start" >&2 + [[ -n "${do_style_restore+x}" ]] && echo -n "$text_normal_start" >&2 else echo "$clang_tidy_line" >&2 fi @@ -138,7 +137,7 @@ if [[ -v CLANG_TIDY ]]; then IFS="[( ]" read -r -a clang_tidy_line_a < <(echo "$clang_tidy_line") if [[ "${clang_tidy_line_a[3]}" == "${clang_tidy_line_a[1]}" ]]; then - [[ -v do_style_restore ]] && echo -n "$text_normal_start" >&2 + [[ -n "${do_style_restore+x}" ]] && echo -n "$text_normal_start" >&2 else echo "$clang_tidy_line" >&2 fi @@ -148,7 +147,7 @@ if [[ -v CLANG_TIDY ]]; then IFS="[( ]" read -r -a clang_tidy_line_a < <(echo "$clang_tidy_line") if [[ $((clang_tidy_line_a[3] + clang_tidy_line_a[7])) == "${clang_tidy_line_a[1]}" ]]; then - [[ -v do_style_restore ]] && echo -n "$text_normal_start" >&2 + [[ -n "${do_style_restore+x}" ]] && echo -n "$text_normal_start" >&2 else echo "$clang_tidy_line" >&2 fi @@ -158,7 +157,7 @@ if [[ -v CLANG_TIDY ]]; then IFS="[( ]" read -r -a clang_tidy_line_a < <(echo "$clang_tidy_line") if [[ $((clang_tidy_line_a[3] + clang_tidy_line_a[7] + clang_tidy_line_a[9])) == "${clang_tidy_line_a[1]}" ]]; then - [[ -v do_style_restore ]] && echo -n "$text_normal_start" >&2 + [[ -n "${do_style_restore+x}" ]] && echo -n "$text_normal_start" >&2 else echo "$clang_tidy_line" >&2 fi @@ -168,7 +167,7 @@ if [[ -v CLANG_TIDY ]]; then IFS="[( ]" read -r -a clang_tidy_line_a < <(echo "$clang_tidy_line") if [[ "${clang_tidy_line_a[3]}" == "${clang_tidy_line_a[1]}" ]]; then - [[ -v do_style_restore ]] && echo -n "$text_normal_start" >&2 + [[ -n "${do_style_restore+x}" ]] && echo -n "$text_normal_start" >&2 else echo "$clang_tidy_line" >&2 fi @@ -178,7 +177,7 @@ if [[ -v CLANG_TIDY ]]; then IFS="[( ]" read -r -a clang_tidy_line_a < <(echo "$clang_tidy_line") if [[ $((clang_tidy_line_a[3] + clang_tidy_line_a[7])) == "${clang_tidy_line_a[1]}" ]]; then - [[ -v do_style_restore ]] && echo -n "$text_normal_start" >&2 + [[ -n "${do_style_restore+x}" ]] && echo -n "$text_normal_start" >&2 else echo "$clang_tidy_line" >&2 fi @@ -188,7 +187,7 @@ if [[ -v CLANG_TIDY ]]; then IFS="[( ]" read -r -a clang_tidy_line_a < <(echo "$clang_tidy_line") if [[ $((clang_tidy_line_a[3] + clang_tidy_line_a[7])) == "${clang_tidy_line_a[1]}" ]]; then - [[ -v do_style_restore ]] && echo -n "$text_normal_start" >&2 + [[ -n "${do_style_restore+x}" ]] && echo -n "$text_normal_start" >&2 else echo "$clang_tidy_line" >&2 fi @@ -198,7 +197,7 @@ if [[ -v CLANG_TIDY ]]; then IFS="[( ]" read -r -a clang_tidy_line_a < <(echo "$clang_tidy_line") if [[ $((clang_tidy_line_a[3] + clang_tidy_line_a[7] + clang_tidy_line_a[12])) == "${clang_tidy_line_a[1]}" ]]; then - [[ -v do_style_restore ]] && echo -n "$text_normal_start" >&2 + [[ -n "${do_style_restore+x}" ]] && echo -n "$text_normal_start" >&2 else echo "$clang_tidy_line" >&2 fi @@ -208,7 +207,7 @@ if [[ -v CLANG_TIDY ]]; then IFS="[( ]" read -r -a clang_tidy_line_a < <(echo "$clang_tidy_line") if [[ "${clang_tidy_line_a[1]}" == "${clang_tidy_line_a[3]}" ]]; then - [[ -v do_style_restore ]] && echo -n "$text_normal_start" >&2 + [[ -n "${do_style_restore+x}" ]] && echo -n "$text_normal_start" >&2 else echo "$clang_tidy_line" >&2 fi @@ -217,14 +216,16 @@ if [[ -v CLANG_TIDY ]]; then *) echo "$clang_tidy_line" >&2 - retval=1 + if [[ "$clang_tidy_line" =~ :[[:space:]]error: ]]; then + retval=1 + fi ;; esac done < <("$CLANG_TIDY" "${clang_tidy_args_array[@]}" "$source_file" -- "$@" 2>&1) - if [[ "$retval" != '0' && -v do_style_restore ]]; then + if [[ "$retval" != '0' && -n "${do_style_restore+x}" ]]; then echo -n "$text_normal_start" >&2 fi break @@ -232,9 +233,10 @@ if [[ -v CLANG_TIDY ]]; then fi if [[ "$retval" != '0' ]]; then - if [[ -v CLANG_TIDY_STATUS_FILE ]]; then - # shellcheck disable=SC2320 # noise + if [[ -n "${CLANG_TIDY_STATUS_FILE+x}" ]]; then echo "${source_file} ${retval}" >> "$CLANG_TIDY_STATUS_FILE" || exit $? + # Don't exit with error - let build continue and collect all errors + retval=0 else exit "$retval" fi diff --git a/tools/static-analysis/run_clang_tidy_make.sh b/tools/static-analysis/run_clang_tidy_make.sh index 5f4c5395..349e21d4 100755 --- a/tools/static-analysis/run_clang_tidy_make.sh +++ b/tools/static-analysis/run_clang_tidy_make.sh @@ -1,21 +1,21 @@ #!/bin/bash -# Clang-tidy runner using make integration approach -# Based on wolfSSL's testing/git-hooks/wolfssl-multi-test.sh implementation +# wolfHSM clang-tidy static analysis runner +# Uses wolfSSL's proven clang-tidy configuration for consistent code quality -set -e +set -o noclobber -o nounset -o pipefail || exit $? -# Script directory -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +# Script directory and project root +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" || exit $? +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" || exit $? # Output directory OUTPUT_DIR="$SCRIPT_DIR/reports" -mkdir -p "$OUTPUT_DIR" +mkdir -p "$OUTPUT_DIR" || exit $? # Check if clang-tidy is installed if ! command -v clang-tidy &> /dev/null; then - echo "Error: clang-tidy is not installed or not in PATH" + echo "Error: clang-tidy is not installed or not in PATH" >&2 exit 1 fi @@ -23,7 +23,7 @@ echo "Using clang-tidy version: $(clang-tidy --version | head -n 1)" # Check if clang is installed if ! command -v clang &> /dev/null; then - echo "Error: clang is not installed or not in PATH" + echo "Error: clang is not installed or not in PATH" >&2 exit 1 fi @@ -31,43 +31,131 @@ fi export CLANG_TIDY="$(command -v clang-tidy)" export CLANG="$(command -v clang)" -# Rely on repo-root .clang-tidy for checks/config and header filter. -# No inline -config/-checks here to keep maintenance simple and IDEs consistent. +# wolfHSM clang-tidy configuration using wolfSSL's exact configuration +# Using portable variable checking for compatibility with older bash versions +if [[ -z "${CLANG_TIDY_ARGS+x}" ]]; then + export CLANG_TIDY_ARGS='-allow-enabling-analyzer-alpha-checkers -header-filter=^(?!.*wolfssl).* -checks=readability-*,bugprone-*,misc-no-recursion,misc-misplaced-const,misc-redundant-expression,misc-unused-parameters,misc-unused-using-decls,-clang-diagnostic-language-extension-token,-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,-clang-analyzer-optin.performance.Padding,-readability-braces-around-statements,-readability-function-size,-readability-function-cognitive-complexity,-bugprone-suspicious-include,-bugprone-easily-swappable-parameters,-readability-isolate-declaration,-readability-magic-numbers,-readability-else-after-return,-bugprone-reserved-identifier,-readability-suspicious-call-argument,-bugprone-suspicious-string-compare,-bugprone-branch-clone,-misc-redundant-expression,-readability-non-const-parameter,-readability-redundant-control-flow,-readability-misleading-indentation,-readability-identifier-length,-readability-duplicate-include,-readability-avoid-const-params-in-decls,-readability-avoid-unconditional-preprocessor-if,-readability-use-concise-preprocessor-directives,-bugprone-narrowing-conversions,-bugprone-implicit-widening-of-multiplication-result,-bugprone-assignment-in-if-condition,-clang-analyzer-alpha*,-bugprone-switch-missing-default-case,-bugprone-multi-level-implicit-pointer-conversion,-bugprone-casting-through-void,-readability-avoid-nested-conditional-operator,-readability-redundant-casting,-readability-enum-initial-value,-readability-math-missing-parentheses,-clang-analyzer-security.ArrayBound,-clang-analyzer-osx.SecKeychainAPI' +fi -# Clear output files -> "$OUTPUT_DIR/clang_tidy_output.txt" -> "$OUTPUT_DIR/clang_tidy_summary.txt" +if [[ -z "${CLANG_TIDY_PER_FILE_CHECKS+x}" ]]; then + export CLANG_TIDY_PER_FILE_CHECKS='^(src|benchmark|examples|port|test|wolfhsm|tools)/:concurrency-mt-unsafe ^examples/:-clang-analyzer-unix.Stream,-clang-analyzer-unix.StdCLibraryFunctions ^test/.*\.c:-clang-analyzer-unix.StdCLibraryFunctions ^src/.*_(arm|c|dsp|x86).*\.c:-readability-redundant-preprocessor,-bugprone-signed-char-misuse' +fi -echo "Running clang-tidy analysis using make integration..." +if [[ -z "${CLANG_TIDY_CONFIG+x}" ]]; then + export CLANG_TIDY_CONFIG='{CheckOptions: [{key: concurrency-mt-unsafe.FunctionSet, value: glibc}, {key: bugprone-unused-return-value.AllowCastToVoid, value: true}, {key: bugprone-unused-return-value.CheckedFunctions, value: pthread_cond_wait;pthread_attr_destroy;pthread_attr_init;pthread_attr_setdetachstate;pthread_attr_setinheritsched;pthread_attr_setschedparam;pthread_attr_setschedpolicy;pthread_attr_setscope;pthread_attr_setstack;pthread_cancel;pthread_cond_destroy;pthread_cond_init;pthread_cond_signal;pthread_cond_wait;pthread_create;pthread_detach;pthread_getschedparam;pthread_getspecific;pthread_join;pthread_key_create;pthread_mach_thread_np;pthread_mutex_destroy;pthread_mutex_init;pthread_mutex_lock;pthread_mutex_unlock;pthread_setaffinity_np;pthread_setschedparam;pthread_setspecific;snprintf;vsnprintf;gettimeofday;clock_gettime;bind;closedir;connect;fcntl;fgets;fputc;fread;fseek;fwrite;getaddrinfo;getpeername;gmtime_r;inet_pton;inotify_rm_watch;listen;^read$;realloc;recvfrom;recv;select;send;sendto;setsockopt;^stat$;^write$}, {key: bugprone-sizeof-expression.WarnOnOffsetDividedBySizeOf, value: false} ]}' +fi -# Change to project root -cd "$PROJECT_ROOT" +# Additional configuration variables - match wolfSSL pattern +if [[ -z "${CLANG_TIDY_PER_FILE_ARGS+x}" ]]; then + export CLANG_TIDY_PER_FILE_ARGS="" +fi + +if [[ -z "${CLANG_TIDY_EXTRA_ARGS+x}" ]]; then + export CLANG_TIDY_EXTRA_ARGS="" +fi + +if [[ -z "${CLANG_TIDY_STATUS_FILE+x}" ]]; then + export CLANG_TIDY_STATUS_FILE="$OUTPUT_DIR/clang_tidy_status.txt" + > "$CLANG_TIDY_STATUS_FILE" +fi -# Run make with the wrapper compiler -echo "Building with clang-tidy analysis..." +if [[ -z "${WOLFSSL_CLANG_TIDY+x}" ]]; then + export WOLFSSL_CLANG_TIDY=1 +fi + +# CLANG_OVERRIDE_CFLAGS can be used to pass additional flags to clang +if [[ -z "${CLANG_OVERRIDE_CFLAGS+x}" ]]; then + export CLANG_OVERRIDE_CFLAGS="" +fi + +# Run clang-tidy analysis on wolfHSM source code + +# Clear output files +> "$OUTPUT_DIR/clang_tidy_output.txt" || exit $? +> "$OUTPUT_DIR/clang_tidy_summary.txt" || exit $? -# First clean the build -make -C test clean > /dev/null 2>&1 || true +echo "Running comprehensive clang-tidy analysis on all wolfHSM directories..." +echo "Analyzing: src/, test/, benchmark/, examples/, port/, tools/, wolfhsm/" +echo "Excluding: wolfssl/, .github/" +echo "" + +# Change to project root +cd "$PROJECT_ROOT" || exit $? # Set up the build environment export CC="$SCRIPT_DIR/clang-tidy-builder.sh" export WOLFSSL_DIR="$PROJECT_ROOT/wolfssl" export USER_SETTINGS_DIR="$PROJECT_ROOT/test/config" -# Run the build with clang-tidy wrapper -# Capture both stdout and stderr -set +e # Don't exit on error +# Function to run make and capture output +function run_make_target() { + local target_dir="$1" + local target_name="$2" + + echo "=== Building $target_name in $target_dir ===" -# Run make and capture output -make -C test 2>&1 | tee "$OUTPUT_DIR/clang_tidy_output.txt" + if [[ ! -d "$target_dir" ]]; then + echo "Directory $target_dir does not exist, skipping..." + return 0 + fi -BUILD_RESULT=${PIPESTATUS[0]} -set -e + if [[ ! -f "$target_dir/Makefile" ]]; then + echo "No Makefile in $target_dir, skipping..." + return 0 + fi + + # Clean the build first + make -C "$target_dir" clean > /dev/null 2>&1 || true + + # Run make and append output + if make -C "$target_dir" 2>&1 | tee -a "$OUTPUT_DIR/clang_tidy_output.txt"; then + return 0 + else + return ${PIPESTATUS[0]} + fi +} + +# Run builds for all wolfHSM components +overall_result=0 + +# Build test suite (includes src/ and port/) +if ! run_make_target "test" "wolfHSM test suite"; then + overall_result=2 +fi + +# Build benchmark suite +if ! run_make_target "benchmark" "wolfHSM benchmarks"; then + overall_result=2 +fi + +# Build examples +if ! run_make_target "examples" "wolfHSM examples"; then + overall_result=2 +fi + +# Build tools +if ! run_make_target "tools" "wolfHSM tools"; then + overall_result=2 +fi + +BUILD_RESULT=$overall_result + +# Check if status file has any errors +if [[ -s "$CLANG_TIDY_STATUS_FILE" ]]; then + echo "" + echo "clang-tidy reported errors in the following files:" + cat "$CLANG_TIDY_STATUS_FILE" + TIDY_ERRORS=1 +else + TIDY_ERRORS=0 +fi # Process the output to create a summary +echo "" echo "Processing results..." # Extract errors and warnings (excluding notes) +rm -f "$OUTPUT_DIR/clang_tidy_summary.txt" grep -E "^[^:]+\.(c|h):[0-9]+:[0-9]+: (error|warning):" "$OUTPUT_DIR/clang_tidy_output.txt" > "$OUTPUT_DIR/clang_tidy_summary.txt" 2>/dev/null || true # Count issues @@ -79,22 +167,29 @@ echo "" echo "Static analysis complete!" echo "Full output: $OUTPUT_DIR/clang_tidy_output.txt" echo "Summary: $OUTPUT_DIR/clang_tidy_summary.txt" +if [[ -s "$CLANG_TIDY_STATUS_FILE" ]]; then + echo "Status file: $CLANG_TIDY_STATUS_FILE" +fi echo "" echo "Results:" echo " Errors: $ERROR_COUNT" echo " Warnings: $WARNING_COUNT" echo " Total issues: $TOTAL_ISSUES" -# Exit with error if we have errors or build failed -if [ $BUILD_RESULT -ne 0 ]; then +# Exit with appropriate code +if [[ $BUILD_RESULT -ne 0 ]]; then echo "" echo "❌ clang-tidy build/analysis failed (make exit: $BUILD_RESULT)" exit $BUILD_RESULT -elif [ $ERROR_COUNT -gt 0 ]; then +elif [[ $TIDY_ERRORS -ne 0 ]]; then + echo "" + echo "❌ clang-tidy found errors that must be fixed (see status file)" + exit 1 +elif [[ $ERROR_COUNT -gt 0 ]]; then echo "" echo "❌ clang-tidy found $ERROR_COUNT error(s) that must be fixed" exit 1 -elif [ $WARNING_COUNT -gt 0 ]; then +elif [[ $WARNING_COUNT -gt 0 ]]; then echo "" echo "⚠️ clang-tidy found $WARNING_COUNT warning(s) - consider fixing these" exit 0 # Exit success for now, can change to exit 1 to enforce warnings