Skip to content
Merged
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
13 changes: 12 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,18 @@ jobs:
python3 -u run_test.py --jobs="${TEST_JOBS}"

# Self-analysis (Linux only)

# Resource summary cache — stale entries are harmless (cache key includes
# schema + model + IR hash, so mismatches simply trigger a rebuild).
- name: Restore resource summary cache
if: runner.os == 'Linux'
uses: actions/cache@v4
with:
path: .cache/resource-lifetime
key: resource-cache-${{ runner.os }}-${{ hashFiles('models/resource-lifetime/**') }}
restore-keys: |
resource-cache-${{ runner.os }}-

- name: Self-analysis (analyze own source code)
if: runner.os == 'Linux'
run: |
Expand All @@ -130,7 +142,6 @@ jobs:
--json-out artifacts/self-analysis.json \
--fail-on error \
--analyzer-arg=--analysis-profile=fast \
--analyzer-arg=--resource-summary-cache-memory-only \
--analyzer-arg=--resource-model=models/resource-lifetime/generic.txt

- name: Upload SARIF to Code Scanning
Expand Down
44 changes: 19 additions & 25 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,42 +21,36 @@ set(LLVM_LINK_LLVM_DYLIB ON)

find_package(LLVM REQUIRED CONFIG)

include(FetchContent)
message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")

# Optional ASAN enablement at the top-level to match the cc dependency.
option(ENABLE_DEBUG_ASAN "Enable debug symbols and AddressSanitizer" OFF)
if(DEFINED DEBUG_ASAN)
set(ENABLE_DEBUG_ASAN ${DEBUG_ASAN} CACHE BOOL
"Enable debug symbols and AddressSanitizer" FORCE)
endif()
# ===========================
# INCLUDE .CMAKE
# ===========================

FetchContent_Declare(
cc
GIT_REPOSITORY https://github.com/CoreTrace/coretrace-compiler.git
GIT_TAG main
)
FetchContent_MakeAvailable(cc)

set(CORETRACE_LOGGER_BUILD_EXAMPLES OFF CACHE BOOL "Disable logger examples" FORCE)
set(CORETRACE_LOGGER_BUILD_TESTS OFF CACHE BOOL "Disable logger tests" FORCE)
include(FetchContent)
FetchContent_Declare(coretrace-logger
GIT_REPOSITORY https://github.com/CoreTrace/coretrace-log.git
GIT_TAG main
)
FetchContent_MakeAvailable(coretrace-logger)
include(${CMAKE_SOURCE_DIR}/cmake/compiler/coretrace-compiler.cmake)

message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")
include(${CMAKE_SOURCE_DIR}/cmake/logger/coretraceLog.cmake)

# ===========================
# Build Options
# ===========================

# Options de build
option(BUILD_CLI "Build stack_usage_analyzer CLI tool" ON)
option(BUILD_SHARED_LIB "Build shared library variant" ON)
option(ENABLE_STACK_USAGE "Emit per-function stack usage (.su) files" ON)
option(ENABLE_WARN_PADDED "Enable -Wpadded warnings" ON)
option(ENABLE_WARN_REORDER_INIT_LIST "Enable -Wreorder-init-list when supported" ON)
option(ENABLE_Z3_BACKEND "Enable optional Z3 SMT backend if Z3 is available" ON)

message("Building CLI Tool = ${BUILD_CLI}")
message("Building Shared Library = ${BUILD_SHARED_LIB}")
message("Emitting Stack Usage (.su) Files = ${ENABLE_STACK_USAGE}")
message("Enabling -Wpadded Warnings = ${ENABLE_WARN_PADDED}")
message("Enabling -Wreorder-init-list = ${ENABLE_WARN_REORDER_INIT_LIST}")
message("Enabling Z3 SMT Backend = ${ENABLE_Z3_BACKEND}")


if(ENABLE_WARN_REORDER_INIT_LIST)
check_cxx_compiler_flag("-Wreorder-init-list" CTRACE_STACK_HAS_WREORDER_INIT_LIST)
endif()
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,10 @@ Ready-to-adapt workflow examples:
--dump-filter prints filter decisions (stderr)
```

JSON reports include a root-level `diagnosticsSummary` object:
`{"info": <count>, "warning": <count>, "error": <count>}`.
These counters are computed from emitted diagnostics (post-filter), matching human summary totals.

To generate `compile_commands.json` with CMake, configure with
`-DCMAKE_EXPORT_COMPILE_COMMANDS=ON` and point to the resulting file
(often under `build/`).
Expand Down
15 changes: 15 additions & 0 deletions cmake/compiler/coretrace-compiler.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
include(FetchContent)

# Optional ASAN enablement at the top-level to match the cc dependency.
option(ENABLE_DEBUG_ASAN "Enable debug symbols and AddressSanitizer" OFF)
if(DEFINED DEBUG_ASAN)
set(ENABLE_DEBUG_ASAN ${DEBUG_ASAN} CACHE BOOL
"Enable debug symbols and AddressSanitizer" FORCE)
endif()

FetchContent_Declare(
cc
GIT_REPOSITORY https://github.com/CoreTrace/coretrace-compiler.git
GIT_TAG main
)
FetchContent_MakeAvailable(cc)
10 changes: 10 additions & 0 deletions cmake/logger/coretraceLog.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
set(CORETRACE_LOGGER_BUILD_EXAMPLES OFF CACHE BOOL "Disable logger examples" OFF)
set(CORETRACE_LOGGER_BUILD_TESTS OFF CACHE BOOL "Disable logger tests" OFF)

include(FetchContent)

FetchContent_Declare(coretrace-logger
GIT_REPOSITORY https://github.com/CoreTrace/coretrace-log.git
GIT_TAG main
)
FetchContent_MakeAvailable(coretrace-logger)
38 changes: 38 additions & 0 deletions include/StackUsageAnalyzer.hpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// StackUsageAnalyzer.hpp
#pragma once

#include <cstddef>
#include <cstdint>
#include <memory>
#include <span>
#include <string>
#include <vector>

Expand Down Expand Up @@ -239,6 +241,35 @@ namespace ctrace::stack
std::string message;
};

struct DiagnosticSummary
{
std::size_t info = 0;
std::size_t warning = 0;
std::size_t error = 0;
};

[[nodiscard]] constexpr DiagnosticSummary
summarizeDiagnostics(std::span<const Diagnostic> diagnostics) noexcept
{
DiagnosticSummary summary;
for (const Diagnostic& diagnostic : diagnostics)
{
switch (diagnostic.severity)
{
case DiagnosticSeverity::Info:
++summary.info;
break;
case DiagnosticSeverity::Warning:
++summary.warning;
break;
case DiagnosticSeverity::Error:
++summary.error;
break;
}
}
return summary;
}

// Global result for a module
struct AnalysisResult
{
Expand All @@ -250,6 +281,13 @@ namespace ctrace::stack
std::vector<Diagnostic> diagnostics;
};

[[nodiscard]] constexpr DiagnosticSummary
summarizeDiagnostics(const AnalysisResult& result) noexcept
{
return summarizeDiagnostics(
std::span<const Diagnostic>(result.diagnostics.data(), result.diagnostics.size()));
}

// Serialize an AnalysisResult to a simple JSON format (for CI / GitHub Actions).
// `inputFile`: path of the analyzed file (the one you pass to analyzeFile).
std::string toJson(const AnalysisResult& result, const std::string& inputFile);
Expand Down
1 change: 1 addition & 0 deletions include/cli/ArgParser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ namespace ctrace::stack::cli
std::vector<std::string> inputFilenames;

std::string sarifBaseDir;
std::string sarifOutPath;
std::string configPath;
std::string compileCommandsPath;

Expand Down
52 changes: 20 additions & 32 deletions scripts/ci/run_code_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,12 +300,15 @@ def analyzer_cmd(
compdb_path: Path | None,
base_dir: str | None,
extra_args: list[str],
sarif_out: str | None = None,
) -> list[str]:
cmd = [str(analyzer), *[str(x) for x in inputs], f"--format={fmt}"]
if compdb_path:
cmd.append(f"--compdb={compdb_path}")
if base_dir:
cmd.append(f"--base-dir={base_dir}")
if sarif_out:
cmd.append(f"--sarif-out={sarif_out}")
cmd.extend(extra_args)
return cmd

Expand Down Expand Up @@ -371,33 +374,39 @@ def main() -> int:
print("No input files selected.", file=sys.stderr)
return 2

print(f"Running analyzer (JSON) on {len(selected_inputs)} file(s).")
json_cmd = analyzer_cmd(
sarif_out_path: str | None = None
if args.sarif_out:
ensure_parent(Path(args.sarif_out))
sarif_out_path = str(Path(args.sarif_out).resolve())

print(f"Running analyzer on {len(selected_inputs)} file(s).")
cmd = analyzer_cmd(
analyzer=analyzer,
inputs=selected_inputs,
fmt="json",
compdb_path=compdb_path,
base_dir=args.base_dir,
extra_args=args.analyzer_arg,
sarif_out=sarif_out_path,
)
json_run = subprocess.run(json_cmd, check=False, capture_output=True, text=True)
if json_run.returncode != 0:
if json_run.stdout:
sys.stdout.write(json_run.stdout)
if json_run.stderr:
sys.stderr.write(json_run.stderr)
return json_run.returncode
run = subprocess.run(cmd, check=False, capture_output=True, text=True)
if run.returncode != 0:
if run.stdout:
sys.stdout.write(run.stdout)
if run.stderr:
sys.stderr.write(run.stderr)
return run.returncode

try:
payload = json.loads(json_run.stdout)
payload = json.loads(run.stdout)
except json.JSONDecodeError as exc:
print(f"Analyzer returned invalid JSON: {exc}", file=sys.stderr)
return 2

if args.json_out:
json_output_path = Path(args.json_out)
ensure_parent(json_output_path)
json_output_path.write_text(json_run.stdout, encoding="utf-8")
json_output_path.write_text(run.stdout, encoding="utf-8")

diags = payload.get("diagnostics", [])
if not isinstance(diags, list):
Expand All @@ -410,27 +419,6 @@ def main() -> int:

print_diags(diags, args.print_diagnostics)

if args.sarif_out:
print("Running analyzer (SARIF export).")
sarif_cmd = analyzer_cmd(
analyzer=analyzer,
inputs=selected_inputs,
fmt="sarif",
compdb_path=compdb_path,
base_dir=args.base_dir,
extra_args=args.analyzer_arg,
)
sarif_run = subprocess.run(sarif_cmd, check=False, capture_output=True, text=True)
if sarif_run.returncode != 0:
if sarif_run.stdout:
sys.stdout.write(sarif_run.stdout)
if sarif_run.stderr:
sys.stderr.write(sarif_run.stderr)
return sarif_run.returncode
sarif_output_path = Path(args.sarif_out)
ensure_parent(sarif_output_path)
sarif_output_path.write_text(sarif_run.stdout, encoding="utf-8")

failed = (args.fail_on == "error" and errors > 0) or (
args.fail_on == "warning" and (errors > 0 or warnings > 0)
)
Expand Down
Loading
Loading