Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
.PHONY: aarch64 aarch64-debug aarch64-samples aarch64-tests test-aarch64 test-aarch64-vm
.PHONY: vm-x86 vm-aarch64 vm-riscv64 vm-test-all
.PHONY: deb deb-x86
.PHONY: analysis analysis-quick analysis-standard analysis-deep
.PHONY: dev shell check eval

# Default target
Expand Down Expand Up @@ -75,6 +76,12 @@ help:
@echo " make vm-riscv64 Build RISC-V MicroVM -> result-vm-riscv64/"
@echo " make vm-test-all Run full VM lifecycle tests (all architectures)"
@echo ""
@echo "=== Static Analysis ==="
@echo " make analysis Run quick static analysis (alias for analysis-quick)"
@echo " make analysis-quick Run clang-tidy + cppcheck"
@echo " make analysis-standard Run + flawfinder, clang-analyzer, gcc-warnings"
@echo " make analysis-deep Run all 8 tools including gcc-analyzer, semgrep, sanitizers"
@echo ""
@echo "=== Packaging ==="
@echo " make deb Build Debian package -> result-deb/"
@echo ""
Expand Down Expand Up @@ -280,6 +287,33 @@ deb:

deb-x86: deb

# =============================================================================
# Static Analysis
# =============================================================================

# Quick analysis: clang-tidy + cppcheck
analysis: analysis-quick

analysis-quick:
@echo "Running quick static analysis (clang-tidy + cppcheck)..."
nix build .#analysis-quick -o result-analysis-quick
@echo ""
@cat result-analysis-quick/summary.txt

# Standard analysis: + flawfinder, clang-analyzer, gcc-warnings
analysis-standard:
@echo "Running standard static analysis..."
nix build .#analysis-standard -o result-analysis-standard
@echo ""
@cat result-analysis-standard/summary.txt

# Deep analysis: all 8 tools
analysis-deep:
@echo "Running deep static analysis (all tools)..."
nix build .#analysis-deep -o result-analysis-deep
@echo ""
@cat result-analysis-deep/summary.txt

# =============================================================================
# Development
# =============================================================================
Expand Down Expand Up @@ -313,6 +347,10 @@ eval:
nix eval .#xdp2-debug-aarch64 --apply 'x: x.name' 2>/dev/null && echo " xdp2-debug-aarch64: OK" || echo " xdp2-debug-aarch64: FAIL"
nix eval .#prebuilt-samples-aarch64 --apply 'x: x.name' 2>/dev/null && echo " prebuilt-samples-aarch64: OK" || echo " prebuilt-samples-aarch64: FAIL"
nix eval .#aarch64-tests.all --apply 'x: x.name' 2>/dev/null && echo " aarch64-tests.all: OK" || echo " aarch64-tests.all: FAIL"
@echo "Analysis:"
nix eval .#analysis-quick --apply 'x: x.name' 2>/dev/null && echo " analysis-quick: OK" || echo " analysis-quick: FAIL"
nix eval .#analysis-standard --apply 'x: x.name' 2>/dev/null && echo " analysis-standard: OK" || echo " analysis-standard: FAIL"
nix eval .#analysis-deep --apply 'x: x.name' 2>/dev/null && echo " analysis-deep: OK" || echo " analysis-deep: FAIL"
@echo ""
@echo "All evaluations completed."

Expand Down
27 changes: 27 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,15 @@
xdp2 = xdp2-debug; # Tests use debug build with assertions
};

# =====================================================================
# Static Analysis Infrastructure
# Ported from reference implementation, adapted for C/Make build system
# =====================================================================
analysis = import ./nix/analysis {
inherit pkgs lib llvmConfig packagesModule;
src = ./.;
};

# =====================================================================
# Phase 1: Packaging (x86_64 .deb only)
# See: documentation/nix/microvm-implementation-phase1.md
Expand Down Expand Up @@ -172,6 +181,24 @@
# Usage: nix run .#run-sample-tests
inherit run-sample-tests;

# ===================================================================
# Static Analysis
# Usage: nix build .#analysis-quick
# nix build .#analysis-standard
# nix build .#analysis-deep
# ===================================================================
analysis-quick = analysis.quick;
analysis-standard = analysis.standard;
analysis-deep = analysis.deep;
analysis-clang-tidy = analysis.clang-tidy;
analysis-cppcheck = analysis.cppcheck;
analysis-flawfinder = analysis.flawfinder;
analysis-clang-analyzer = analysis.clang-analyzer;
analysis-gcc-warnings = analysis.gcc-warnings;
analysis-gcc-analyzer = analysis.gcc-analyzer;
analysis-semgrep = analysis.semgrep;
analysis-sanitizers = analysis.sanitizers;

# ===================================================================
# Phase 1: Packaging outputs (x86_64 .deb only)
# See: documentation/nix/microvm-implementation-phase1.md
Expand Down
197 changes: 197 additions & 0 deletions nix/analysis/clang-analyzer.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
# nix/analysis/clang-analyzer.nix
#
# Clang Static Analyzer (scan-build) for XDP2's C codebase.
#
# Adapted from the reference C++ implementation:
# - Uses C-specific checkers (core.*, security.*, unix.*, alpha.security.*)
# - No C++ checkers (cplusplus.*, alpha.cplusplus.*)
# - Builds via Make instead of Meson+Ninja
#

{
lib,
pkgs,
src,
llvmConfig,
nativeBuildInputs,
buildInputs,
}:

let
llvmPackages = llvmConfig.llvmPackages;
hostPkgs = pkgs.buildPackages;
hostCC = hostPkgs.stdenv.cc;
hostPython = hostPkgs.python3.withPackages (p: [ p.scapy ]);

host-gcc = hostPkgs.writeShellScript "host-gcc" ''
exec ${hostCC}/bin/gcc \
-I${hostPkgs.boost.dev}/include \
-I${hostPkgs.libpcap}/include \
-L${hostPkgs.boost}/lib \
-L${hostPkgs.libpcap.lib}/lib \
"$@"
'';

host-gxx = hostPkgs.writeShellScript "host-g++" ''
exec ${hostCC}/bin/g++ \
-I${hostPkgs.boost.dev}/include \
-I${hostPkgs.libpcap}/include \
-I${hostPython}/include/python3.13 \
-L${hostPkgs.boost}/lib \
-L${hostPkgs.libpcap.lib}/lib \
-L${hostPython}/lib \
-Wl,-rpath,${hostPython}/lib \
"$@"
'';

scanBuildCheckers = lib.concatStringsSep " " [
"-enable-checker core.NullDereference"
"-enable-checker core.DivideZero"
"-enable-checker core.UndefinedBinaryOperatorResult"
"-enable-checker core.uninitialized.Assign"
"-enable-checker security.FloatLoopCounter"
"-enable-checker security.insecureAPI.getpw"
"-enable-checker security.insecureAPI.gets"
"-enable-checker security.insecureAPI.vfork"
"-enable-checker unix.Malloc"
"-enable-checker unix.MallocSizeof"
"-enable-checker unix.MismatchedDeallocator"
"-enable-checker alpha.security.ArrayBoundV2"
"-enable-checker alpha.unix.SimpleStream"
];

in
pkgs.stdenv.mkDerivation {
pname = "xdp2-analysis-clang-analyzer";
version = "0.1.0";
inherit src;

nativeBuildInputs = nativeBuildInputs ++ [
pkgs.clang-analyzer
];
inherit buildInputs;

hardeningDisable = [ "all" ];
dontFixup = true;
doCheck = false;

HOST_CC = "${hostCC}/bin/gcc";
HOST_CXX = "${hostCC}/bin/g++";
HOST_LLVM_CONFIG = "${llvmConfig.llvm-config-wrapped}/bin/llvm-config";
XDP2_CLANG_VERSION = llvmConfig.version;
XDP2_CLANG_RESOURCE_PATH = llvmConfig.paths.clangResourceDir;

LD_LIBRARY_PATH = lib.makeLibraryPath [
llvmPackages.llvm
llvmPackages.libclang.lib
hostPkgs.boost
];

postPatch = ''
substituteInPlace thirdparty/cppfront/Makefile \
--replace-fail 'include ../../src/config.mk' '# config.mk not needed for standalone build'

sed -i '1i#include <functional>\n#include <unordered_map>\n' thirdparty/cppfront/include/cpp2util.h

substituteInPlace src/configure.sh \
--replace-fail 'CC_GCC="gcc"' 'CC_GCC="''${CC_GCC:-gcc}"' \
--replace-fail 'CC_CXX="g++"' 'CC_CXX="''${CC_CXX:-g++}"'
'';

configurePhase = ''
runHook preConfigure

cd src

export PATH="${hostCC}/bin:${hostPython}/bin:$PATH"
export CC_GCC="${host-gcc}"
export CC_CXX="${host-gxx}"
export CC="${host-gcc}"
export CXX="${host-gxx}"
export PKG_CONFIG_PATH="${hostPython}/lib/pkgconfig:$PKG_CONFIG_PATH"
export HOST_CC="$CC"
export HOST_CXX="$CXX"
export HOST_LLVM_CONFIG="${llvmConfig.llvm-config-wrapped}/bin/llvm-config"
export XDP2_CLANG_VERSION="${llvmConfig.version}"
export XDP2_CLANG_RESOURCE_PATH="${llvmConfig.paths.clangResourceDir}"
export XDP2_C_INCLUDE_PATH="${llvmConfig.paths.clangResourceDir}/include"
export CONFIGURE_DEBUG_LEVEL=7

bash configure.sh --build-opt-parser

if grep -q 'PATH_ARG="--with-path=' config.mk; then
sed -i 's|PATH_ARG="--with-path=.*"|PATH_ARG=""|' config.mk
fi

sed -i 's|^HOST_CC := gcc$|HOST_CC := ${host-gcc}|' config.mk
sed -i 's|^HOST_CXX := g++$|HOST_CXX := ${host-gxx}|' config.mk
echo "HOST_LDFLAGS := -L${hostPkgs.boost}/lib -Wl,-rpath,${hostPkgs.boost}/lib" >> config.mk

cd ..

runHook postConfigure
'';

buildPhase = ''
runHook preBuild

export HOST_CC="${hostCC}/bin/gcc"
export HOST_CXX="${hostCC}/bin/g++"
export HOST_LLVM_CONFIG="${llvmConfig.llvm-config-wrapped}/bin/llvm-config"
export XDP2_CLANG_VERSION="${llvmConfig.version}"
export XDP2_CLANG_RESOURCE_PATH="${llvmConfig.paths.clangResourceDir}"
export XDP2_C_INCLUDE_PATH="${llvmConfig.paths.clangResourceDir}/include"
export XDP2_GLIBC_INCLUDE_PATH="${hostPkgs.stdenv.cc.libc.dev}/include"
export XDP2_LINUX_HEADERS_PATH="${hostPkgs.linuxHeaders}/include"

# Build cppfront first
echo "Building cppfront..."
cd thirdparty/cppfront
$HOST_CXX -std=c++20 source/cppfront.cpp -o cppfront-compiler
cd ../..

# Build xdp2-compiler
echo "Building xdp2-compiler..."
cd src/tools/compiler
make -j''${NIX_BUILD_CORES:-1}
cd ../../..

# Build xdp2 libraries wrapped with scan-build.
# Use full path to clang-analyzer's scan-build (properly wrapped with Nix shebang).
# The one from llvmPackages.clang has a broken /usr/bin/env shebang.
echo "Running scan-build on xdp2..."
cd src
${pkgs.clang-analyzer}/bin/scan-build \
--use-analyzer=${llvmPackages.clang}/bin/clang \
${scanBuildCheckers} \
-o "$NIX_BUILD_TOP/scan-results" \
make -j''${NIX_BUILD_CORES:-1} \
2>&1 | tee "$NIX_BUILD_TOP/scan-build.log" || true
cd ..

runHook postBuild
'';

installPhase = ''
mkdir -p $out

# Copy HTML reports if produced
if [ -d "$NIX_BUILD_TOP/scan-results" ] && [ "$(ls -A "$NIX_BUILD_TOP/scan-results" 2>/dev/null)" ]; then
mkdir -p $out/html-report
cp -r "$NIX_BUILD_TOP/scan-results"/* $out/html-report/ 2>/dev/null || true
fi

# Extract finding count from scan-build output
findings=$(grep -oP '\d+ bugs? found' "$NIX_BUILD_TOP/scan-build.log" | grep -oP '^\d+' || echo "0")
echo "$findings" > $out/count.txt

cp "$NIX_BUILD_TOP/scan-build.log" $out/report.txt

{
echo "=== Clang Static Analyzer (C) ==="
echo ""
echo "Path-sensitive analysis with C-specific checkers."
echo "Findings: $findings"
} > $out/summary.txt
'';
}
48 changes: 48 additions & 0 deletions nix/analysis/clang-tidy.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# nix/analysis/clang-tidy.nix
#
# clang-tidy runner for XDP2's C codebase.
#
# Adapted from the reference C++ implementation:
# - Finds .c and .h files instead of .cc
# - Uses C-appropriate checks (no cppcoreguidelines, modernize)
# - No custom plugin (nixTidyChecks not applicable to XDP2)
#

{
pkgs,
mkCompileDbReport,
}:

let
runner = pkgs.writeShellApplication {
name = "run-clang-tidy-analysis";
runtimeInputs = with pkgs; [
clang-tools
coreutils
findutils
gnugrep
];
text = ''
compile_db="$1"
source_dir="$2"
output_dir="$3"

echo "=== clang-tidy Analysis (C) ==="
echo "Using compilation database: $compile_db"

# Find all .c source files in library and tool directories
find "$source_dir/src" -name '*.c' -not -path '*/test*' -print0 | \
xargs -0 -P "$(nproc)" -I{} \
clang-tidy \
-p "$compile_db" \
--header-filter='src/.*' \
--checks='-*,bugprone-*,cert-*,clang-analyzer-*,misc-*,readability-*' \
{} \
> "$output_dir/report.txt" 2>&1 || true

findings=$(grep -c ': warning:\|: error:' "$output_dir/report.txt" || echo "0")
echo "$findings" > "$output_dir/count.txt"
'';
};
in
mkCompileDbReport "clang-tidy" runner
Loading