From 9ae02fe91d6d5ad7446f52e8b2041f2d77e42bd2 Mon Sep 17 00:00:00 2001 From: Hugo Date: Fri, 30 Jan 2026 04:40:34 +0100 Subject: [PATCH 1/4] chore: add clang-format configuration --- .clang-format | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .clang-format diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..0149e40 --- /dev/null +++ b/.clang-format @@ -0,0 +1,12 @@ +BasedOnStyle: LLVM +Standard: c++17 +IndentWidth: 4 +TabWidth: 4 +UseTab: Never +BreakBeforeBraces: Allman +AllowShortFunctionsOnASingleLine: Empty +ColumnLimit: 100 +PointerAlignment: Left +NamespaceIndentation: All +SortIncludes: Never +ReflowComments: false From 3b708fabf05a569ab6d97d950678e59392593d86 Mon Sep 17 00:00:00 2001 From: Hugo Date: Fri, 30 Jan 2026 04:40:40 +0100 Subject: [PATCH 2/4] chore: add clang-format scripts and CI --- .github/workflows/clang-format.yml | 22 ++++++++++++++++++++ CMakeLists.txt | 13 ++++++++++++ README.md | 8 ++++++++ scripts/format-check.sh | 32 ++++++++++++++++++++++++++++++ scripts/format.sh | 21 ++++++++++++++++++++ 5 files changed, 96 insertions(+) create mode 100644 .github/workflows/clang-format.yml create mode 100755 scripts/format-check.sh create mode 100755 scripts/format.sh diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml new file mode 100644 index 0000000..ea07d8b --- /dev/null +++ b/.github/workflows/clang-format.yml @@ -0,0 +1,22 @@ +name: clang-format + +on: + push: + branches: [main, master] + pull_request: + +jobs: + format-check: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install clang-format 17 + run: | + sudo apt-get update + sudo apt-get install -y clang-format-17 + sudo update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-17 100 + + - name: Run format-check + run: ./scripts/format-check.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index f91cead..9916922 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -126,3 +126,16 @@ if(BUILD_CLI) # il est déjà dans la lib ) endif() + +# ============ +# FORMATTING +# ============ +add_custom_target(format + COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/scripts/format.sh" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + COMMENT "Run clang-format on source files") + +add_custom_target(format-check + COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/scripts/format-check.sh" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + COMMENT "Verify clang-format compliance") diff --git a/README.md b/README.md index 8f8bb99..a085fd4 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,14 @@ ./build.sh ``` +### Code style (clang-format) + +- Version cible : `clang-format` 17 (utilisée dans la CI). +- Formater localement : `./scripts/format.sh` +- Vérifier sans modifier : `./scripts/format-check.sh` +- CMake : `cmake --build build --target format` ou `--target format-check` +- CI : le job GitHub Actions `clang-format` échoue si un fichier n’est pas formaté. + #### CORETRACE-STACK-USAGE CLI ```zsh diff --git a/scripts/format-check.sh b/scripts/format-check.sh new file mode 100755 index 0000000..35ac7f0 --- /dev/null +++ b/scripts/format-check.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)" + +files=() +while IFS= read -r -d '' file; do + files+=("$file") +done < <(find "${REPO_ROOT}" \ + \( -path "${REPO_ROOT}/build" -o -path "${REPO_ROOT}/extern-project" -o -path "${REPO_ROOT}/external" -o -path "${REPO_ROOT}/third_party" -o -path "${REPO_ROOT}/vendor" -o -path "${REPO_ROOT}/.git" \) -prune -o \ + -type f \( -name '*.c' -o -name '*.cc' -o -name '*.cpp' -o -name '*.cxx' -o -name '*.h' -o -name '*.hh' -o -name '*.hpp' -o -name '*.hxx' \) -print0) + +if [ "${#files[@]}" -eq 0 ]; then + echo "No source files to check." + exit 0 +fi + +echo "Checking formatting on ${#files[@]} files..." +failed=0 +for file in "${files[@]}"; do + if ! clang-format --dry-run --Werror "${file}"; then + failed=1 + fi +done + +if [ "${failed}" -ne 0 ]; then + echo "Formatting check failed. Run scripts/format.sh to fix." + exit 1 +fi + +echo "Formatting is clean." diff --git a/scripts/format.sh b/scripts/format.sh new file mode 100755 index 0000000..0da3f69 --- /dev/null +++ b/scripts/format.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)" + +# Collect source files while skipping generated / external content. +files=() +while IFS= read -r -d '' file; do + files+=("$file") +done < <(find "${REPO_ROOT}" \ + \( -path "${REPO_ROOT}/build" -o -path "${REPO_ROOT}/extern-project" -o -path "${REPO_ROOT}/external" -o -path "${REPO_ROOT}/third_party" -o -path "${REPO_ROOT}/vendor" -o -path "${REPO_ROOT}/.git" \) -prune -o \ + -type f \( -name '*.c' -o -name '*.cc' -o -name '*.cpp' -o -name '*.cxx' -o -name '*.h' -o -name '*.hh' -o -name '*.hpp' -o -name '*.hxx' \) -print0) + +if [ "${#files[@]}" -eq 0 ]; then + echo "No source files to format." + exit 0 +fi + +echo "Formatting ${#files[@]} files with clang-format (style from ${REPO_ROOT}/.clang-format)..." +clang-format -i "${files[@]}" From f6915b8a4346a0c08120ee103043870f740c4174 Mon Sep 17 00:00:00 2001 From: Hugo Date: Fri, 30 Jan 2026 04:42:19 +0100 Subject: [PATCH 3/4] chore(style): run clang-format on C/C++ sources --- include/StackUsageAnalyzer.hpp | 283 +- include/helpers.hpp | 33 +- include/mangle.hpp | 23 +- main.cpp | 129 +- src/StackUsageAnalyzer.cpp | 7299 +++++++++-------- src/mangle.cpp | 24 +- test/alloca/oversized-constant.c | 2 +- test/alloca/recursive-controlled-alloca.c | 2 +- test/alloca/recursive-infinite-alloca.c | 2 +- test/alloca/user-controlled.c | 2 +- .../bound-storage-for-statement.c | 13 +- test/bound-storage/bound-storage.c | 4 +- test/bound-storage/deep-alias.c | 11 +- .../indirection-profonde-aliasing.c | 15 +- test/bound-storage/ranges_test.c | 19 +- test/bound-storage/struct_array_overflow.c | 15 +- test/cpy-buffer/bad-usage-memcpy.c | 2 +- test/cpy-buffer/bad-usage-memset.c | 2 +- test/escape-stack/direct-callback.c | 4 +- test/escape-stack/global-buf.c | 4 +- test/escape-stack/global-struct.c | 7 +- test/escape-stack/indirect-callback.c | 4 +- test/escape-stack/out_param.c | 10 +- test/escape-stack/return-buf.c | 6 +- test/escape-stack/stack_escape.c | 25 +- .../container_of_correct_member_offset_ok.c | 9 +- .../container_of_wrong_member_offset_error.c | 6 +- .../container_of_wrong_offset_and_ok.c | 18 +- .../gep_positive_offset_wrong_base_warn.c | 4 +- .../inttoptr_multipath_offset_mismatch_warn.c | 4 +- .../inttoptr_ptrtoint_wrong_offset_error.c | 18 +- .../inttoptr_rhs_sub_ignore.c | 7 +- .../inttoptr_through_stack_slot_error.c | 9 +- ...nttoptr_unused_reconstructed_ptr_no_diag.c | 10 +- .../advanced-cases.c | 38 +- .../advanced-cases.cpp | 18 +- .../const-mixed.c | 23 +- .../const-readonly.c | 28 +- .../readonly-pointer.c | 4 +- .../readonly-reference.cpp | 4 +- test/test.cc | 3 +- test/test.cpp | 3 +- test/vla/deguised-constant.c | 4 +- test/vla/vla-scanf.c | 2 +- 44 files changed, 4385 insertions(+), 3767 deletions(-) diff --git a/include/StackUsageAnalyzer.hpp b/include/StackUsageAnalyzer.hpp index e774b41..6d2c617 100644 --- a/include/StackUsageAnalyzer.hpp +++ b/include/StackUsageAnalyzer.hpp @@ -13,181 +13,162 @@ namespace llvm class Module; class LLVMContext; class SMDiagnostic; -} +} // namespace llvm namespace ctrace::stack { -using StackSize = std::uint64_t; + using StackSize = std::uint64_t; -enum class AnalysisMode -{ - IR, - ABI -}; + enum class AnalysisMode + { + IR, + ABI + }; -// Configuration de l'analyse (mode + limite de stack) -struct AnalysisConfig -{ - AnalysisMode mode = AnalysisMode::IR; - StackSize stackLimit = 8ull * 1024ull * 1024ull; // 8 MiB par défaut - bool quiet = false; - bool warningsOnly = false; -}; - -// Résultat par fonction -struct FunctionResult -{ - std::string name; - StackSize localStack = 0; // taille frame locale (suivant le mode) - StackSize maxStack = 0; // max stack incluant les callees - bool localStackUnknown = false; // taille locale inconnue (alloca dynamique) - bool maxStackUnknown = false; // max stack inconnue (propagée via appels) - bool hasDynamicAlloca = false; // alloca dynamique détectée dans la fonction - - bool isRecursive = false; // dans un cycle F <-> G ... - bool hasInfiniteSelfRecursion = false; // heuristique DominatorTree - bool exceedsLimit = false; // maxStack > config.stackLimit -}; - -/* + // Configuration de l'analyse (mode + limite de stack) + struct AnalysisConfig + { + AnalysisMode mode = AnalysisMode::IR; + StackSize stackLimit = 8ull * 1024ull * 1024ull; // 8 MiB par défaut + bool quiet = false; + bool warningsOnly = false; + }; + + // Résultat par fonction + struct FunctionResult + { + std::string name; + StackSize localStack = 0; // taille frame locale (suivant le mode) + StackSize maxStack = 0; // max stack incluant les callees + bool localStackUnknown = false; // taille locale inconnue (alloca dynamique) + bool maxStackUnknown = false; // max stack inconnue (propagée via appels) + bool hasDynamicAlloca = false; // alloca dynamique détectée dans la fonction + + bool isRecursive = false; // dans un cycle F <-> G ... + bool hasInfiniteSelfRecursion = false; // heuristique DominatorTree + bool exceedsLimit = false; // maxStack > config.stackLimit + }; + + /* DiagnosticSeverity EnumTraits specialization */ -enum class LanguageType -{ - Unknown = 0, - LLVM_IR = 1, - C = 2, - CXX = 3 -}; - -template<> -struct EnumTraits -{ - static constexpr std::array names = { - "UNKNOWN", - "LLVM_IR", - "C", - "CXX" + enum class LanguageType + { + Unknown = 0, + LLVM_IR = 1, + C = 2, + CXX = 3 }; -}; -/* + template <> struct EnumTraits + { + static constexpr std::array names = {"UNKNOWN", "LLVM_IR", "C", "CXX"}; + }; + + /* DiagnosticSeverity EnumTraits specialization */ -enum class DiagnosticSeverity -{ - Info = 0, - Warning = 1, - Error = 2 -}; + enum class DiagnosticSeverity + { + Info = 0, + Warning = 1, + Error = 2 + }; -template<> -struct EnumTraits -{ - static constexpr std::array names = { - "INFO", - "WARNING", - "ERROR" + template <> struct EnumTraits + { + static constexpr std::array names = {"INFO", "WARNING", "ERROR"}; }; -}; -/* + /* DescriptiveErrorCode EnumTraits specialization */ -enum class DescriptiveErrorCode -{ - None = 0, - StackBufferOverflow = 1, - NegativeStackIndex = 2, - VLAUsage = 3, - StackPointerEscape = 4, - MemcpyWithStackDest = 5, - MultipleStoresToStackBuffer = 6, - AllocaUserControlled = 7, - AllocaTooLarge = 8, - AllocaUsageWarning = 9, - InvalidBaseReconstruction = 10, - ConstParameterNotModified = 11 -}; - -template<> -struct EnumTraits -{ - static constexpr std::array names = { - "None", - "StackBufferOverflow", - "NegativeStackIndex", - "VLAUsage", - "StackPointerEscape", - "MemcpyWithStackDest", - "MultipleStoresToStackBuffer", - "AllocaUserControlled", - "AllocaTooLarge", - "AllocaUsageWarning", - "InvalidBaseReconstruction", - "ConstParameterNotModified" + enum class DescriptiveErrorCode + { + None = 0, + StackBufferOverflow = 1, + NegativeStackIndex = 2, + VLAUsage = 3, + StackPointerEscape = 4, + MemcpyWithStackDest = 5, + MultipleStoresToStackBuffer = 6, + AllocaUserControlled = 7, + AllocaTooLarge = 8, + AllocaUsageWarning = 9, + InvalidBaseReconstruction = 10, + ConstParameterNotModified = 11 }; -}; -/* + template <> struct EnumTraits + { + static constexpr std::array names = {"None", + "StackBufferOverflow", + "NegativeStackIndex", + "VLAUsage", + "StackPointerEscape", + "MemcpyWithStackDest", + "MultipleStoresToStackBuffer", + "AllocaUserControlled", + "AllocaTooLarge", + "AllocaUsageWarning", + "InvalidBaseReconstruction", + "ConstParameterNotModified"}; + }; + + /* Diagnostic struct */ -struct Diagnostic -{ - std::string funcName; - unsigned line = 0; - unsigned column = 0; - - // for SARIF / structured reporting - unsigned startLine = 0; - unsigned startColumn = 0; - unsigned endLine = 0; - unsigned endColumn = 0; - - DiagnosticSeverity severity = DiagnosticSeverity::Warning; - DescriptiveErrorCode errCode = DescriptiveErrorCode::None; - std::string ruleId; - std::vector variableAliasingVec; - std::string message; -}; - -// Résultat global pour un module -struct AnalysisResult -{ - AnalysisConfig config; - std::vector functions; - // Human-readable diagnostics (buffer overflows, VLAs, memcpy issues, escapes, etc.) - // All messages are formatted and then printed in main(). - // std::vector diagnostics; - std::vector diagnostics; -}; - -// Serialize an AnalysisResult to a simple JSON format (pour CI / GitHub Actions). -// `inputFile` : chemin du fichier analysé (celui que tu passes à analyzeFile). -std::string toJson(const AnalysisResult &result, - const std::string &inputFile); - -// Serialize an AnalysisResult to SARIF 2.1.0 (compatible GitHub Code Scanning). -// `inputFile` : chemin du fichier analysé. -// `toolName` / `toolVersion` : metadata du tool dans le SARIF. -std::string toSarif(const AnalysisResult &result, - const std::string &inputFile, - const std::string &toolName = "coretrace-stack-analyzer", - const std::string &toolVersion = "0.1.0"); - -// Analyse un module déjà chargé (tu peux réutiliser dans d'autres outils) -AnalysisResult analyzeModule(llvm::Module &mod, - const AnalysisConfig &config); - -// Helper pratique : charge un .ll et appelle analyzeModule() -AnalysisResult analyzeFile(const std::string &filename, - const AnalysisConfig &config, - llvm::LLVMContext &ctx, - llvm::SMDiagnostic &err); + struct Diagnostic + { + std::string funcName; + unsigned line = 0; + unsigned column = 0; + + // for SARIF / structured reporting + unsigned startLine = 0; + unsigned startColumn = 0; + unsigned endLine = 0; + unsigned endColumn = 0; + + DiagnosticSeverity severity = DiagnosticSeverity::Warning; + DescriptiveErrorCode errCode = DescriptiveErrorCode::None; + std::string ruleId; + std::vector variableAliasingVec; + std::string message; + }; + + // Résultat global pour un module + struct AnalysisResult + { + AnalysisConfig config; + std::vector functions; + // Human-readable diagnostics (buffer overflows, VLAs, memcpy issues, escapes, etc.) + // All messages are formatted and then printed in main(). + // std::vector diagnostics; + std::vector diagnostics; + }; + + // Serialize an AnalysisResult to a simple JSON format (pour CI / GitHub Actions). + // `inputFile` : chemin du fichier analysé (celui que tu passes à analyzeFile). + std::string toJson(const AnalysisResult& result, const std::string& inputFile); + + // Serialize an AnalysisResult to SARIF 2.1.0 (compatible GitHub Code Scanning). + // `inputFile` : chemin du fichier analysé. + // `toolName` / `toolVersion` : metadata du tool dans le SARIF. + std::string toSarif(const AnalysisResult& result, const std::string& inputFile, + const std::string& toolName = "coretrace-stack-analyzer", + const std::string& toolVersion = "0.1.0"); + + // Analyse un module déjà chargé (tu peux réutiliser dans d'autres outils) + AnalysisResult analyzeModule(llvm::Module& mod, const AnalysisConfig& config); + + // Helper pratique : charge un .ll et appelle analyzeModule() + AnalysisResult analyzeFile(const std::string& filename, const AnalysisConfig& config, + llvm::LLVMContext& ctx, llvm::SMDiagnostic& err); } // namespace ctrace::stack diff --git a/include/helpers.hpp b/include/helpers.hpp index 1e0c9c2..2351fe4 100644 --- a/include/helpers.hpp +++ b/include/helpers.hpp @@ -7,27 +7,26 @@ namespace ctrace::stack { -template -struct EnumTraits; // pas de définition générique -> erreur si non spécialisé + template + struct EnumTraits; // pas de définition générique -> erreur si non spécialisé -template -concept EnumWithTraits = std::is_enum_v && requires { - EnumTraits::names; -}; + template concept EnumWithTraits = std::is_enum_v && requires + { + EnumTraits::names; + }; -template -constexpr std::string_view enumToString(E e) noexcept -{ - using Traits = EnumTraits; - using U = std::underlying_type_t; + template constexpr std::string_view enumToString(E e) noexcept + { + using Traits = EnumTraits; + using U = std::underlying_type_t; - constexpr auto size = Traits::names.size(); - const auto idx = static_cast(e); + constexpr auto size = Traits::names.size(); + const auto idx = static_cast(e); - if (idx < 0 || static_cast(idx) >= size) - return "Unknown"; + if (idx < 0 || static_cast(idx) >= size) + return "Unknown"; - return Traits::names[static_cast(idx)]; -} + return Traits::names[static_cast(idx)]; + } } // namespace ctrace::stack diff --git a/include/mangle.hpp b/include/mangle.hpp index 93eb6ac..f0f8f2d 100644 --- a/include/mangle.hpp +++ b/include/mangle.hpp @@ -13,8 +13,7 @@ namespace ctrace_tools * The `StringLike` concept ensures that any type passed to functions * requiring it can be implicitly converted to `std::string_view`. */ - template - concept StringLike = std::convertible_to; + template concept StringLike = std::convertible_to; // TODO: add mangling for windows /** @@ -35,7 +34,7 @@ namespace ctrace_tools * should not be ignored. It is also `noexcept`, indicating that it * does not throw exceptions. */ - [[nodiscard]]bool isMangled(StringLike auto name) noexcept + [[nodiscard]] bool isMangled(StringLike auto name) noexcept { int status = 0; std::string_view sv{name}; @@ -45,10 +44,8 @@ namespace ctrace_tools return false; } - std::unique_ptr demangled( - abi::__cxa_demangle(sv.data(), nullptr, nullptr, &status), - std::free - ); + std::unique_ptr demangled( + abi::__cxa_demangle(sv.data(), nullptr, nullptr, &status), std::free); return status == 0; } @@ -65,11 +62,9 @@ namespace ctrace_tools * * @note The implementation of this function is not provided in the current file. */ - [[nodiscard]] std::string mangleFunction( - const std::string& namespaceName, - const std::string& functionName, - const std::vector& paramTypes - ); + [[nodiscard]] std::string mangleFunction(const std::string& namespaceName, + const std::string& functionName, + const std::vector& paramTypes); - [[nodiscard]] std::string demangle(const char *name); -}; + [[nodiscard]] std::string demangle(const char* name); +}; // namespace ctrace_tools diff --git a/main.cpp b/main.cpp index 682ee5c..bcd6327 100644 --- a/main.cpp +++ b/main.cpp @@ -1,6 +1,6 @@ #include "StackUsageAnalyzer.hpp" -#include // strncmp, strcmp +#include // strncmp, strcmp #include #include #include @@ -18,29 +18,28 @@ enum class OutputFormat static void printHelp() { - llvm::outs() - << "Stack Usage Analyzer - static stack usage analysis for LLVM IR/bitcode\n\n" - << "Usage:\n" - << " stack_usage_analyzer [options]\n\n" - << "Options:\n" - << " --mode=ir|abi Analysis mode (default: ir)\n" - << " --format=json Output JSON report\n" - << " --format=sarif Output SARIF report\n" - << " --quiet Suppress per-function diagnostics\n" - << " --warnings-only Show warnings and errors only\n" - << " -h, --help Show this help message and exit\n\n" - << "Examples:\n" - << " stack_usage_analyzer input.ll\n" - << " stack_usage_analyzer input.ll --mode=abi --format=json\n" - << " stack_usage_analyzer input.ll --warnings-only\n"; + llvm::outs() << "Stack Usage Analyzer - static stack usage analysis for LLVM IR/bitcode\n\n" + << "Usage:\n" + << " stack_usage_analyzer [options]\n\n" + << "Options:\n" + << " --mode=ir|abi Analysis mode (default: ir)\n" + << " --format=json Output JSON report\n" + << " --format=sarif Output SARIF report\n" + << " --quiet Suppress per-function diagnostics\n" + << " --warnings-only Show warnings and errors only\n" + << " -h, --help Show this help message and exit\n\n" + << "Examples:\n" + << " stack_usage_analyzer input.ll\n" + << " stack_usage_analyzer input.ll --mode=abi --format=json\n" + << " stack_usage_analyzer input.ll --warnings-only\n"; } -int main(int argc, char **argv) +int main(int argc, char** argv) { - llvm::LLVMContext context; + llvm::LLVMContext context; llvm::SMDiagnostic err; - const char *inputFilename = nullptr; + const char* inputFilename = nullptr; OutputFormat outputFormat = OutputFormat::Human; AnalysisConfig cfg; // mode = IR, stackLimit = 8MiB par défaut @@ -60,7 +59,7 @@ int main(int argc, char **argv) for (int i = 1; i < argc; ++i) { - const char *arg = argv[i]; + const char* arg = argv[i]; std::string argStr{arg}; if (argStr == "--quiet") { @@ -89,34 +88,42 @@ int main(int argc, char **argv) } if (std::strncmp(arg, "--mode=", 7) == 0) { - const char *modeStr = arg + 7; + const char* modeStr = arg + 7; if (std::strcmp(modeStr, "ir") == 0) { cfg.mode = AnalysisMode::IR; - } else if (std::strcmp(modeStr, "abi") == 0) + } + else if (std::strcmp(modeStr, "abi") == 0) { cfg.mode = AnalysisMode::ABI; - } else { - llvm::errs() << "Unknown mode: " << modeStr - << " (expected 'ir' or 'abi')\n"; + } + else + { + llvm::errs() << "Unknown mode: " << modeStr << " (expected 'ir' or 'abi')\n"; return 1; } - } else if (!inputFilename) { + } + else if (!inputFilename) + { inputFilename = arg; - } else { + } + else + { llvm::errs() << "Unexpected argument: " << arg << "\n"; return 1; } } - if (!inputFilename) { + if (!inputFilename) + { llvm::errs() << "Usage: stack_usage_analyzer [options]\n" << "Try --help for more information.\n"; return 1; } auto result = analyzeFile(inputFilename, cfg, context, err); - if (result.functions.empty()) { + if (result.functions.empty()) + { err.print("stack_usage_analyzer", llvm::errs()); return 1; } @@ -129,71 +136,85 @@ int main(int argc, char **argv) if (outputFormat == OutputFormat::Sarif) { - llvm::outs() << ctrace::stack::toSarif(result, inputFilename, - "coretrace-stack-analyzer", "0.1.0"); + llvm::outs() << ctrace::stack::toSarif(result, inputFilename, "coretrace-stack-analyzer", + "0.1.0"); return 0; } - llvm::outs() << "Mode: " - << (result.config.mode == AnalysisMode::IR ? "IR" : "ABI") - << "\n\n"; + llvm::outs() << "Mode: " << (result.config.mode == AnalysisMode::IR ? "IR" : "ABI") << "\n\n"; - for (const auto &f : result.functions) { + for (const auto& f : result.functions) + { std::vector param_types; // param_types.reserve(issue.inst->getFunction()->arg_size()); - param_types.push_back("void"); // dummy to avoid empty vector issue // refaire avec les paramèters réels - - llvm::outs() << "Function: " << f.name << " " << ((ctrace_tools::isMangled(f.name)) ? ctrace_tools::demangle(f.name.c_str()) : "") << "\n"; - if (f.localStackUnknown) { + param_types.push_back( + "void"); // dummy to avoid empty vector issue // refaire avec les paramèters réels + + llvm::outs() << "Function: " << f.name << " " + << ((ctrace_tools::isMangled(f.name)) ? ctrace_tools::demangle(f.name.c_str()) + : "") + << "\n"; + if (f.localStackUnknown) + { llvm::outs() << " local stack: unknown"; - if (f.localStack > 0) { + if (f.localStack > 0) + { llvm::outs() << " (>= " << f.localStack << " bytes)"; } llvm::outs() << "\n"; - } else { + } + else + { llvm::outs() << " local stack: " << f.localStack << " bytes\n"; } - if (f.maxStackUnknown) { + if (f.maxStackUnknown) + { llvm::outs() << " max stack (including callees): unknown"; - if (f.maxStack > 0) { + if (f.maxStack > 0) + { llvm::outs() << " (>= " << f.maxStack << " bytes)"; } llvm::outs() << "\n"; - } else { + } + else + { llvm::outs() << " max stack (including callees): " << f.maxStack << " bytes\n"; } - if (f.isRecursive) { + if (f.isRecursive) + { llvm::outs() << " [!] recursive or mutually recursive function detected\n"; } - if (f.hasInfiniteSelfRecursion) { + if (f.hasInfiniteSelfRecursion) + { llvm::outs() << " [!!!] unconditional self recursion detected (no base case)\n"; llvm::outs() << " this will eventually overflow the stack at runtime\n"; } - if (f.exceedsLimit) { + if (f.exceedsLimit) + { llvm::outs() << " [!] potential stack overflow: exceeds limit of " << result.config.stackLimit << " bytes\n"; } - if (!result.config.quiet) { - for (const auto &d : result.diagnostics) + if (!result.config.quiet) + { + for (const auto& d : result.diagnostics) { if (d.funcName != f.name) - continue; + continue; // Si warningsOnly est actif, on ignore les diagnostics Info - if (result.config.warningsOnly && - d.severity == DiagnosticSeverity::Info) { + if (result.config.warningsOnly && d.severity == DiagnosticSeverity::Info) + { continue; } if (d.line != 0) { - llvm::outs() << " at line " << d.line - << ", column " << d.column << "\n"; + llvm::outs() << " at line " << d.line << ", column " << d.column << "\n"; } llvm::outs() << d.message << "\n"; } diff --git a/src/StackUsageAnalyzer.cpp b/src/StackUsageAnalyzer.cpp index aa17183..1601728 100644 --- a/src/StackUsageAnalyzer.cpp +++ b/src/StackUsageAnalyzer.cpp @@ -8,7 +8,7 @@ #include #include #include -#include // std::snprintf +#include // std::snprintf #include #include @@ -39,354 +39,385 @@ #include "compilerlib/compiler.h" #include "mangle.hpp" -namespace ctrace::stack { - -// ============================================================================ -// Types internes -// ============================================================================ - -using CallGraph = std::map>; - -enum VisitState { NotVisited = 0, Visiting = 1, Visited = 2 }; - -struct StackEstimate { - StackSize bytes = 0; - bool unknown = false; -}; - -struct LocalStackInfo { - StackSize bytes = 0; - bool unknown = false; - bool hasDynamicAlloca = false; -}; - -// État interne pour la propagation -struct InternalAnalysisState { - std::map TotalStack; // stack max, callees inclus - std::set RecursiveFuncs; // fonctions dans au moins un cycle - std::set InfiniteRecursionFuncs; // auto-récursion “infinie” -}; - -// Rapport interne pour les dépassements de buffer sur la stack -struct StackBufferOverflow { - std::string funcName; - std::string varName; - StackSize arraySize = 0; - StackSize indexOrUpperBound = 0; // utilisé pour les bornes sup (UB) ou index constant - bool isWrite = false; - bool indexIsConstant = false; - const llvm::Instruction *inst = nullptr; - - // Nouveau : violation basée sur une borne inférieure (index potentiellement négatif) - bool isLowerBoundViolation = false; - long long lowerBound = 0; // borne inférieure déduite (signée) - - std::string aliasPath; // ex: "pp -> ptr -> buf" - std::vector aliasPathVec; // {"pp", "ptr", "buf"} - // Optional : helper for sync string <- vector - void rebuildAliasPathString(const std::string& sep = " -> ") +namespace ctrace::stack +{ + + // ============================================================================ + // Types internes + // ============================================================================ + + using CallGraph = std::map>; + + enum VisitState + { + NotVisited = 0, + Visiting = 1, + Visited = 2 + }; + + struct StackEstimate + { + StackSize bytes = 0; + bool unknown = false; + }; + + struct LocalStackInfo { - aliasPath.clear(); - for (size_t i = 0; i < aliasPathVec.size(); ++i) + StackSize bytes = 0; + bool unknown = false; + bool hasDynamicAlloca = false; + }; + + // État interne pour la propagation + struct InternalAnalysisState + { + std::map TotalStack; // stack max, callees inclus + std::set RecursiveFuncs; // fonctions dans au moins un cycle + std::set InfiniteRecursionFuncs; // auto-récursion “infinie” + }; + + // Rapport interne pour les dépassements de buffer sur la stack + struct StackBufferOverflow + { + std::string funcName; + std::string varName; + StackSize arraySize = 0; + StackSize indexOrUpperBound = 0; // utilisé pour les bornes sup (UB) ou index constant + bool isWrite = false; + bool indexIsConstant = false; + const llvm::Instruction* inst = nullptr; + + // Nouveau : violation basée sur une borne inférieure (index potentiellement négatif) + bool isLowerBoundViolation = false; + long long lowerBound = 0; // borne inférieure déduite (signée) + + std::string aliasPath; // ex: "pp -> ptr -> buf" + std::vector aliasPathVec; // {"pp", "ptr", "buf"} + // Optional : helper for sync string <- vector + void rebuildAliasPathString(const std::string& sep = " -> ") { - aliasPath += aliasPathVec[i]; - if (i + 1 < aliasPathVec.size()) - aliasPath += sep; + aliasPath.clear(); + for (size_t i = 0; i < aliasPathVec.size(); ++i) + { + aliasPath += aliasPathVec[i]; + if (i + 1 < aliasPathVec.size()) + aliasPath += sep; + } } - } -}; - -// Intervalle d'entier pour une valeur : borne inférieure / supérieure (signées) -struct IntRange { - bool hasLower = false; - long long lower = 0; - bool hasUpper = false; - long long upper = 0; -}; - -// Rapport interne pour les allocations dynamiques sur la stack (VLA / alloca variable) -struct DynamicAllocaIssue { - std::string funcName; - std::string varName; - std::string typeName; - const llvm::AllocaInst *allocaInst = nullptr; -}; - -// Internal report for alloca/VLA usages with potentially risky sizes -struct AllocaUsageIssue { - std::string funcName; - std::string varName; - const llvm::AllocaInst *allocaInst = nullptr; - - bool userControlled = false; // size derived from argument / non-local value - bool sizeIsConst = false; // size known exactly - bool hasUpperBound = false; // bounded size (from ICmp-derived range) - bool isRecursive = false; // function participates in a recursion cycle - bool isInfiniteRecursive = false; // unconditional self recursion - - StackSize sizeBytes = 0; // exact size in bytes (if sizeIsConst) - StackSize upperBoundBytes = 0; // upper bound in bytes (if hasUpperBound) -}; - -// Rapport interne pour les usages dangereux de memcpy/memset sur la stack -struct MemIntrinsicIssue { - std::string funcName; - std::string varName; - std::string intrinsicName; // "memcpy" / "memset" / "memmove" - StackSize destSizeBytes = 0; - StackSize lengthBytes = 0; - const llvm::Instruction *inst = nullptr; -}; - -// Rapport interne pour plusieurs stores dans un même buffer de stack -struct MultipleStoreIssue { - std::string funcName; - std::string varName; - std::size_t storeCount = 0; // nombre total de StoreInst vers ce buffer - std::size_t distinctIndexCount = 0; // nombre d'expressions d'index distinctes - const llvm::AllocaInst *allocaInst = nullptr; -}; - -// Rapport interne pour les fuites de pointeurs vers la stack -struct StackPointerEscapeIssue { - std::string funcName; - std::string varName; - std::string escapeKind; // "return", "store_global", "store_unknown", "call_arg", "call_callback" - std::string targetName; // nom du global, si applicable - const llvm::Instruction *inst = nullptr; -}; - -// Rapport interne pour les reconstructions invalides de pointeur de base (offsetof/container_of) -struct InvalidBaseReconstructionIssue { - std::string funcName; - std::string varName; // nom de la variable alloca (stack object) - std::string sourceMember; // membre source (ex: "b") - int64_t offsetUsed = 0; // offset utilisé dans le calcul (peut être négatif) - std::string targetType; // type vers lequel on cast (ex: "struct A*") - bool isOutOfBounds = false; // true si on peut prouver que c'est hors bornes - const llvm::Instruction *inst = nullptr; -}; - -// Rapport interne pour les paramètres qui peuvent être rendus const (pointee/referent) -struct ConstParamIssue { - std::string funcName; - std::string paramName; - std::string currentType; - std::string suggestedType; - std::string suggestedTypeAlt; - bool pointerConstOnly = false; // ex: T * const param - bool isReference = false; - bool isRvalueRef = false; - unsigned line = 0; - unsigned column = 0; -}; -// Analyse intra-fonction pour détecter les "fuites" de pointeurs de stack : -// - retour d'une adresse de variable locale (return buf;) -// - stockage de l'adresse d'une variable locale dans un global (global = buf;) -// -// Heuristique : pour chaque AllocaInst, on remonte son graphe d'utilisation -// en suivant les bitcast, GEP, PHI, select de type pointeur, et on marque -// comme "escape" : -// - tout return qui renvoie une valeur dérivée de cette alloca -// - tout store qui écrit cette valeur dans une GlobalVariable. -static void analyzeStackPointerEscapesInFunction( - llvm::Function &F, - std::vector &out) -{ - using namespace llvm; + }; - if (F.isDeclaration()) - return; + // Intervalle d'entier pour une valeur : borne inférieure / supérieure (signées) + struct IntRange + { + bool hasLower = false; + long long lower = 0; + bool hasUpper = false; + long long upper = 0; + }; - for (BasicBlock &BB : F) { - for (Instruction &I : BB) { - auto *AI = dyn_cast(&I); - if (!AI) - continue; + // Rapport interne pour les allocations dynamiques sur la stack (VLA / alloca variable) + struct DynamicAllocaIssue + { + std::string funcName; + std::string varName; + std::string typeName; + const llvm::AllocaInst* allocaInst = nullptr; + }; + + // Internal report for alloca/VLA usages with potentially risky sizes + struct AllocaUsageIssue + { + std::string funcName; + std::string varName; + const llvm::AllocaInst* allocaInst = nullptr; + + bool userControlled = false; // size derived from argument / non-local value + bool sizeIsConst = false; // size known exactly + bool hasUpperBound = false; // bounded size (from ICmp-derived range) + bool isRecursive = false; // function participates in a recursion cycle + bool isInfiniteRecursive = false; // unconditional self recursion + + StackSize sizeBytes = 0; // exact size in bytes (if sizeIsConst) + StackSize upperBoundBytes = 0; // upper bound in bytes (if hasUpperBound) + }; + + // Rapport interne pour les usages dangereux de memcpy/memset sur la stack + struct MemIntrinsicIssue + { + std::string funcName; + std::string varName; + std::string intrinsicName; // "memcpy" / "memset" / "memmove" + StackSize destSizeBytes = 0; + StackSize lengthBytes = 0; + const llvm::Instruction* inst = nullptr; + }; + + // Rapport interne pour plusieurs stores dans un même buffer de stack + struct MultipleStoreIssue + { + std::string funcName; + std::string varName; + std::size_t storeCount = 0; // nombre total de StoreInst vers ce buffer + std::size_t distinctIndexCount = 0; // nombre d'expressions d'index distinctes + const llvm::AllocaInst* allocaInst = nullptr; + }; - // On limite l'analyse aux slots "classiques" de stack (tout alloca) - SmallPtrSet visited; - SmallVector worklist; - worklist.push_back(AI); + // Rapport interne pour les fuites de pointeurs vers la stack + struct StackPointerEscapeIssue + { + std::string funcName; + std::string varName; + std::string + escapeKind; // "return", "store_global", "store_unknown", "call_arg", "call_callback" + std::string targetName; // nom du global, si applicable + const llvm::Instruction* inst = nullptr; + }; + + // Rapport interne pour les reconstructions invalides de pointeur de base (offsetof/container_of) + struct InvalidBaseReconstructionIssue + { + std::string funcName; + std::string varName; // nom de la variable alloca (stack object) + std::string sourceMember; // membre source (ex: "b") + int64_t offsetUsed = 0; // offset utilisé dans le calcul (peut être négatif) + std::string targetType; // type vers lequel on cast (ex: "struct A*") + bool isOutOfBounds = false; // true si on peut prouver que c'est hors bornes + const llvm::Instruction* inst = nullptr; + }; + + // Rapport interne pour les paramètres qui peuvent être rendus const (pointee/referent) + struct ConstParamIssue + { + std::string funcName; + std::string paramName; + std::string currentType; + std::string suggestedType; + std::string suggestedTypeAlt; + bool pointerConstOnly = false; // ex: T * const param + bool isReference = false; + bool isRvalueRef = false; + unsigned line = 0; + unsigned column = 0; + }; + // Analyse intra-fonction pour détecter les "fuites" de pointeurs de stack : + // - retour d'une adresse de variable locale (return buf;) + // - stockage de l'adresse d'une variable locale dans un global (global = buf;) + // + // Heuristique : pour chaque AllocaInst, on remonte son graphe d'utilisation + // en suivant les bitcast, GEP, PHI, select de type pointeur, et on marque + // comme "escape" : + // - tout return qui renvoie une valeur dérivée de cette alloca + // - tout store qui écrit cette valeur dans une GlobalVariable. + static void analyzeStackPointerEscapesInFunction(llvm::Function& F, + std::vector& out) + { + using namespace llvm; - while (!worklist.empty()) { - const Value *V = worklist.back(); - worklist.pop_back(); - if (visited.contains(V)) + if (F.isDeclaration()) + return; + + for (BasicBlock& BB : F) + { + for (Instruction& I : BB) + { + auto* AI = dyn_cast(&I); + if (!AI) continue; - visited.insert(V); - - for (const Use &U : V->uses()) { - const User *Usr = U.getUser(); - - // 1) Retour direct ou via chaîne d'alias : return - if (auto *RI = dyn_cast(Usr)) { - StackPointerEscapeIssue issue; - issue.funcName = F.getName().str(); - issue.varName = AI->hasName() ? AI->getName().str() : std::string(""); - issue.escapeKind = "return"; - issue.targetName = {}; - issue.inst = RI; - out.push_back(std::move(issue)); + + // On limite l'analyse aux slots "classiques" de stack (tout alloca) + SmallPtrSet visited; + SmallVector worklist; + worklist.push_back(AI); + + while (!worklist.empty()) + { + const Value* V = worklist.back(); + worklist.pop_back(); + if (visited.contains(V)) continue; - } + visited.insert(V); - // 2) Stockage de l'adresse : global = ; ou *out = ; - if (auto *SI = dyn_cast(Usr)) { - // Si la valeur stockée est notre pointeur (ou un alias de celui-ci) - if (SI->getValueOperand() == V) { - const Value *dstRaw = SI->getPointerOperand(); - const Value *dst = dstRaw->stripPointerCasts(); + for (const Use& U : V->uses()) + { + const User* Usr = U.getUser(); - // 2.a) Stockage direct dans une variable globale : fuite évidente - if (auto *GV = dyn_cast(dst)) { - StackPointerEscapeIssue issue; - issue.funcName = F.getName().str(); - issue.varName = AI->hasName() ? AI->getName().str() : std::string(""); - issue.escapeKind = "store_global"; - issue.targetName = GV->hasName() ? GV->getName().str() : std::string{}; - issue.inst = SI; - out.push_back(std::move(issue)); - continue; - } + // 1) Retour direct ou via chaîne d'alias : return + if (auto* RI = dyn_cast(Usr)) + { + StackPointerEscapeIssue issue; + issue.funcName = F.getName().str(); + issue.varName = + AI->hasName() ? AI->getName().str() : std::string(""); + issue.escapeKind = "return"; + issue.targetName = {}; + issue.inst = RI; + out.push_back(std::move(issue)); + continue; + } - // 2.b) Stockage via un pointeur non local (ex: *out = buf;) - // On ne connaît pas la durée de vie de la mémoire pointée par dst, - // mais si ce n'est pas une alloca de cette fonction, on considère - // que le pointeur de stack peut s'échapper (paramètre, heap, etc.). - if (!isa(dst)) { - StackPointerEscapeIssue issue; - issue.funcName = F.getName().str(); - issue.varName = AI->hasName() ? AI->getName().str() : std::string(""); - issue.escapeKind = "store_unknown"; - issue.targetName = dst->hasName() ? dst->getName().str() : std::string{}; - issue.inst = SI; - out.push_back(std::move(issue)); - continue; - } + // 2) Stockage de l'adresse : global = ; ou *out = ; + if (auto* SI = dyn_cast(Usr)) + { + // Si la valeur stockée est notre pointeur (ou un alias de celui-ci) + if (SI->getValueOperand() == V) + { + const Value* dstRaw = SI->getPointerOperand(); + const Value* dst = dstRaw->stripPointerCasts(); + + // 2.a) Stockage direct dans une variable globale : fuite évidente + if (auto* GV = dyn_cast(dst)) + { + StackPointerEscapeIssue issue; + issue.funcName = F.getName().str(); + issue.varName = AI->hasName() ? AI->getName().str() + : std::string(""); + issue.escapeKind = "store_global"; + issue.targetName = + GV->hasName() ? GV->getName().str() : std::string{}; + issue.inst = SI; + out.push_back(std::move(issue)); + continue; + } - // 2.c) Stockage dans une alloca locale : on laisse l'alias - // continuer à être exploré via la boucle de travail. On ne - // considère pas cela comme une fuite immédiate. - const AllocaInst *dstAI = cast(dst); - worklist.push_back(dstAI); + // 2.b) Stockage via un pointeur non local (ex: *out = buf;) + // On ne connaît pas la durée de vie de la mémoire pointée par dst, + // mais si ce n'est pas une alloca de cette fonction, on considère + // que le pointeur de stack peut s'échapper (paramètre, heap, etc.). + if (!isa(dst)) + { + StackPointerEscapeIssue issue; + issue.funcName = F.getName().str(); + issue.varName = AI->hasName() ? AI->getName().str() + : std::string(""); + issue.escapeKind = "store_unknown"; + issue.targetName = + dst->hasName() ? dst->getName().str() : std::string{}; + issue.inst = SI; + out.push_back(std::move(issue)); + continue; + } + + // 2.c) Stockage dans une alloca locale : on laisse l'alias + // continuer à être exploré via la boucle de travail. On ne + // considère pas cela comme une fuite immédiate. + const AllocaInst* dstAI = cast(dst); + worklist.push_back(dstAI); + } + // Sinon, c'est un store vers la stack ou un autre emplacement local + // qui ne contient pas directement notre pointeur, pas une fuite en soi. + continue; } - // Sinon, c'est un store vers la stack ou un autre emplacement local - // qui ne contient pas directement notre pointeur, pas une fuite en soi. - continue; - } - // 3) Passage de l'adresse à un appel de fonction : cb(buf); ou f(buf); - if (auto *CB = dyn_cast(Usr)) { - // On inspecte tous les arguments; si l'un d'eux est V (ou un alias direct), - // on considère que l'adresse de la variable locale est transmise. - for (unsigned argIndex = 0; argIndex < CB->arg_size(); ++argIndex) { - if (CB->getArgOperand(argIndex) != V) - continue; + // 3) Passage de l'adresse à un appel de fonction : cb(buf); ou f(buf); + if (auto* CB = dyn_cast(Usr)) + { + // On inspecte tous les arguments; si l'un d'eux est V (ou un alias direct), + // on considère que l'adresse de la variable locale est transmise. + for (unsigned argIndex = 0; argIndex < CB->arg_size(); ++argIndex) + { + if (CB->getArgOperand(argIndex) != V) + continue; + + const Value* calledVal = CB->getCalledOperand(); + const Value* calledStripped = + calledVal ? calledVal->stripPointerCasts() : nullptr; + const Function* directCallee = + calledStripped ? dyn_cast(calledStripped) : nullptr; - const Value *calledVal = CB->getCalledOperand(); - const Value *calledStripped = calledVal ? calledVal->stripPointerCasts() : nullptr; - const Function *directCallee = - calledStripped ? dyn_cast(calledStripped) : nullptr; + StackPointerEscapeIssue issue; + issue.funcName = F.getName().str(); + issue.varName = + AI->hasName() ? AI->getName().str() : std::string(""); + issue.inst = cast(CB); + + if (!directCallee) + { + // Appel indirect via pointeur de fonction : callback typique. + issue.escapeKind = "call_callback"; + issue.targetName.clear(); + } + else + { + // Appel direct : on n'a pas de connaissance précise de la sémantique + // de la fonction appelée; on marque ça comme une fuite potentielle + // plus permissive. + issue.escapeKind = "call_arg"; + issue.targetName = directCallee->hasName() + ? directCallee->getName().str() + : std::string{}; + } - StackPointerEscapeIssue issue; - issue.funcName = F.getName().str(); - issue.varName = AI->hasName() ? AI->getName().str() : std::string(""); - issue.inst = cast(CB); - - if (!directCallee) { - // Appel indirect via pointeur de fonction : callback typique. - issue.escapeKind = "call_callback"; - issue.targetName.clear(); - } else { - // Appel direct : on n'a pas de connaissance précise de la sémantique - // de la fonction appelée; on marque ça comme une fuite potentielle - // plus permissive. - issue.escapeKind = "call_arg"; - issue.targetName = directCallee->hasName() - ? directCallee->getName().str() - : std::string{}; + out.push_back(std::move(issue)); } - out.push_back(std::move(issue)); + // On ne propage pas l'alias via l'appel, mais on considère que + // l'adresse peut être capturée par la fonction appelée. + continue; } - // On ne propage pas l'alias via l'appel, mais on considère que - // l'adresse peut être capturée par la fonction appelée. - continue; - } + // 4) Propagation des alias de pointeurs : + if (auto* BC = dyn_cast(Usr)) + { + if (BC->getType()->isPointerTy()) + worklist.push_back(BC); + continue; + } + if (auto* GEP = dyn_cast(Usr)) + { + worklist.push_back(GEP); + continue; + } + if (auto* PN = dyn_cast(Usr)) + { + if (PN->getType()->isPointerTy()) + worklist.push_back(PN); + continue; + } + if (auto* Sel = dyn_cast(Usr)) + { + if (Sel->getType()->isPointerTy()) + worklist.push_back(Sel); + continue; + } - // 4) Propagation des alias de pointeurs : - if (auto *BC = dyn_cast(Usr)) { - if (BC->getType()->isPointerTy()) - worklist.push_back(BC); - continue; - } - if (auto *GEP = dyn_cast(Usr)) { - worklist.push_back(GEP); - continue; + // Autres usages (load, comparaison, etc.) : pas une fuite, + // et on ne propage pas davantage. } - if (auto *PN = dyn_cast(Usr)) { - if (PN->getType()->isPointerTy()) - worklist.push_back(PN); - continue; - } - if (auto *Sel = dyn_cast(Usr)) { - if (Sel->getType()->isPointerTy()) - worklist.push_back(Sel); - continue; - } - - // Autres usages (load, comparaison, etc.) : pas une fuite, - // et on ne propage pas davantage. } } } } -} - -// -------------------------------------------------------------------------- -// Détection des reconstructions invalides de pointeur de base (offsetof/container_of) -// -------------------------------------------------------------------------- - -// Forward declaration -static std::optional -getAllocaTotalSizeBytes(const llvm::AllocaInst *AI, const llvm::DataLayout &DL); -static const llvm::Value *getPtrToIntOperand(const llvm::Value *V); - -// Pointer origin (base alloca + offset from base) -struct PtrOrigin { - const llvm::AllocaInst *alloca = nullptr; - int64_t offset = 0; -}; - -static bool recordVisitedOffset(std::map> &visited, - const llvm::Value *V, - int64_t offset) -{ - auto &setRef = visited[V]; - return setRef.insert(offset).second; -} - -static bool getGEPConstantOffsetAndBase(const llvm::Value *V, - const llvm::DataLayout &DL, - int64_t &outOffset, - const llvm::Value *&outBase) -{ - using namespace llvm; - if (auto *GEP = dyn_cast(V)) { - APInt offset(64, 0); - if (!GEP->accumulateConstantOffset(DL, offset)) - return false; - outOffset = offset.getSExtValue(); - outBase = GEP->getPointerOperand(); - return true; + // -------------------------------------------------------------------------- + // Détection des reconstructions invalides de pointeur de base (offsetof/container_of) + // -------------------------------------------------------------------------- + + // Forward declaration + static std::optional getAllocaTotalSizeBytes(const llvm::AllocaInst* AI, + const llvm::DataLayout& DL); + static const llvm::Value* getPtrToIntOperand(const llvm::Value* V); + + // Pointer origin (base alloca + offset from base) + struct PtrOrigin + { + const llvm::AllocaInst* alloca = nullptr; + int64_t offset = 0; + }; + + static bool recordVisitedOffset(std::map>& visited, + const llvm::Value* V, int64_t offset) + { + auto& setRef = visited[V]; + return setRef.insert(offset).second; } - if (auto *CE = dyn_cast(V)) { - if (CE->getOpcode() == Instruction::GetElementPtr) { - auto *GEP = cast(CE); + static bool getGEPConstantOffsetAndBase(const llvm::Value* V, const llvm::DataLayout& DL, + int64_t& outOffset, const llvm::Value*& outBase) + { + using namespace llvm; + + if (auto* GEP = dyn_cast(V)) + { APInt offset(64, 0); if (!GEP->accumulateConstantOffset(DL, offset)) return false; @@ -394,1920 +425,2304 @@ static bool getGEPConstantOffsetAndBase(const llvm::Value *V, outBase = GEP->getPointerOperand(); return true; } + + if (auto* CE = dyn_cast(V)) + { + if (CE->getOpcode() == Instruction::GetElementPtr) + { + auto* GEP = cast(CE); + APInt offset(64, 0); + if (!GEP->accumulateConstantOffset(DL, offset)) + return false; + outOffset = offset.getSExtValue(); + outBase = GEP->getPointerOperand(); + return true; + } + } + + return false; } - return false; -} + static const llvm::Value* stripIntCasts(const llvm::Value* V) + { + using namespace llvm; -static const llvm::Value *stripIntCasts(const llvm::Value *V) -{ - using namespace llvm; - - const Value *Cur = V; - while (Cur) { - if (auto *CI = dyn_cast(Cur)) { - const Value *Op = CI->getOperand(0); - if (CI->getType()->isIntegerTy() && Op->getType()->isIntegerTy()) { - Cur = Op; - continue; - } - } else if (auto *CE = dyn_cast(Cur)) { - if (CE->isCast()) { - const Value *Op = CE->getOperand(0); - if (CE->getType()->isIntegerTy() && Op->getType()->isIntegerTy()) { + const Value* Cur = V; + while (Cur) + { + if (auto* CI = dyn_cast(Cur)) + { + const Value* Op = CI->getOperand(0); + if (CI->getType()->isIntegerTy() && Op->getType()->isIntegerTy()) + { Cur = Op; continue; } } + else if (auto* CE = dyn_cast(Cur)) + { + if (CE->isCast()) + { + const Value* Op = CE->getOperand(0); + if (CE->getType()->isIntegerTy() && Op->getType()->isIntegerTy()) + { + Cur = Op; + continue; + } + } + } + break; } - break; + return Cur; } - return Cur; -} -static bool isLoadFromAlloca(const llvm::Value *V, const llvm::AllocaInst *AI) -{ - using namespace llvm; - if (auto *LI = dyn_cast(V)) { - const Value *PtrOp = LI->getPointerOperand()->stripPointerCasts(); - return PtrOp == AI; + static bool isLoadFromAlloca(const llvm::Value* V, const llvm::AllocaInst* AI) + { + using namespace llvm; + if (auto* LI = dyn_cast(V)) + { + const Value* PtrOp = LI->getPointerOperand()->stripPointerCasts(); + return PtrOp == AI; + } + return false; } - return false; -} -static bool valueDependsOnAlloca(const llvm::Value *V, - const llvm::AllocaInst *AI, - llvm::SmallPtrSet &visited) -{ - using namespace llvm; + static bool valueDependsOnAlloca(const llvm::Value* V, const llvm::AllocaInst* AI, + llvm::SmallPtrSet& visited) + { + using namespace llvm; - if (!V) - return false; - if (!visited.insert(V).second) - return false; + if (!V) + return false; + if (!visited.insert(V).second) + return false; - if (isLoadFromAlloca(V, AI)) - return true; + if (isLoadFromAlloca(V, AI)) + return true; - if (auto *I = dyn_cast(V)) { - for (const Value *Op : I->operands()) { - if (valueDependsOnAlloca(Op, AI, visited)) - return true; + if (auto* I = dyn_cast(V)) + { + for (const Value* Op : I->operands()) + { + if (valueDependsOnAlloca(Op, AI, visited)) + return true; + } } - } else if (auto *CE = dyn_cast(V)) { - for (const Value *Op : CE->operands()) { - if (valueDependsOnAlloca(Op, AI, visited)) - return true; + else if (auto* CE = dyn_cast(V)) + { + for (const Value* Op : CE->operands()) + { + if (valueDependsOnAlloca(Op, AI, visited)) + return true; + } } - } - - return false; -} -static bool matchAllocaLoadAddSub(const llvm::Value *V, - const llvm::AllocaInst *AI, - int64_t &deltaOut) -{ - using namespace llvm; - - const Value *lhs = nullptr; - const Value *rhs = nullptr; - unsigned opcode = 0; - - if (auto *BO = dyn_cast(V)) { - opcode = BO->getOpcode(); - lhs = BO->getOperand(0); - rhs = BO->getOperand(1); - } else if (auto *CE = dyn_cast(V)) { - opcode = CE->getOpcode(); - lhs = CE->getOperand(0); - rhs = CE->getOperand(1); - } else { return false; } - if (opcode != Instruction::Add && opcode != Instruction::Sub) - return false; + static bool matchAllocaLoadAddSub(const llvm::Value* V, const llvm::AllocaInst* AI, + int64_t& deltaOut) + { + using namespace llvm; - const auto *lhsC = dyn_cast(lhs); - const auto *rhsC = dyn_cast(rhs); - bool lhsIsLoad = isLoadFromAlloca(lhs, AI); - bool rhsIsLoad = isLoadFromAlloca(rhs, AI); + const Value* lhs = nullptr; + const Value* rhs = nullptr; + unsigned opcode = 0; - if (opcode == Instruction::Add) { - if (lhsIsLoad && rhsC) { - deltaOut = rhsC->getSExtValue(); - return true; + if (auto* BO = dyn_cast(V)) + { + opcode = BO->getOpcode(); + lhs = BO->getOperand(0); + rhs = BO->getOperand(1); } - if (rhsIsLoad && lhsC) { - deltaOut = lhsC->getSExtValue(); - return true; + else if (auto* CE = dyn_cast(V)) + { + opcode = CE->getOpcode(); + lhs = CE->getOperand(0); + rhs = CE->getOperand(1); } - } else if (opcode == Instruction::Sub) { - if (lhsIsLoad && rhsC) { - deltaOut = -rhsC->getSExtValue(); - return true; + else + { + return false; } - // Do not accept C - load - } - return false; -} + if (opcode != Instruction::Add && opcode != Instruction::Sub) + return false; -struct PtrIntMatch { - const llvm::Value *ptrOperand = nullptr; - int64_t offset = 0; - bool sawOffset = false; -}; + const auto* lhsC = dyn_cast(lhs); + const auto* rhsC = dyn_cast(rhs); + bool lhsIsLoad = isLoadFromAlloca(lhs, AI); + bool rhsIsLoad = isLoadFromAlloca(rhs, AI); -static void collectPtrToIntMatches(const llvm::Value *V, - llvm::SmallVectorImpl &out) -{ - using namespace llvm; + if (opcode == Instruction::Add) + { + if (lhsIsLoad && rhsC) + { + deltaOut = rhsC->getSExtValue(); + return true; + } + if (rhsIsLoad && lhsC) + { + deltaOut = lhsC->getSExtValue(); + return true; + } + } + else if (opcode == Instruction::Sub) + { + if (lhsIsLoad && rhsC) + { + deltaOut = -rhsC->getSExtValue(); + return true; + } + // Do not accept C - load + } - struct IntWorkItem { - const Value *val = nullptr; + return false; + } + + struct PtrIntMatch + { + const llvm::Value* ptrOperand = nullptr; int64_t offset = 0; bool sawOffset = false; }; - SmallVector worklist; - std::map> visited; + static void collectPtrToIntMatches(const llvm::Value* V, + llvm::SmallVectorImpl& out) + { + using namespace llvm; - auto recordVisited = [&](const Value *Val, int64_t offset, bool sawOffset) { - unsigned bit = sawOffset ? 2u : 1u; - unsigned &flags = visited[Val][offset]; - if (flags & bit) - return false; - flags |= bit; - return true; - }; + struct IntWorkItem + { + const Value* val = nullptr; + int64_t offset = 0; + bool sawOffset = false; + }; - worklist.push_back({V, 0, false}); - recordVisited(V, 0, false); + SmallVector worklist; + std::map> visited; - while (!worklist.empty()) { - const Value *Cur = stripIntCasts(worklist.back().val); - int64_t curOffset = worklist.back().offset; - bool curSawOffset = worklist.back().sawOffset; - worklist.pop_back(); + auto recordVisited = [&](const Value* Val, int64_t offset, bool sawOffset) + { + unsigned bit = sawOffset ? 2u : 1u; + unsigned& flags = visited[Val][offset]; + if (flags & bit) + return false; + flags |= bit; + return true; + }; - if (const Value *PtrOp = getPtrToIntOperand(Cur)) { - out.push_back({PtrOp, curOffset, curSawOffset}); - continue; - } + worklist.push_back({V, 0, false}); + recordVisited(V, 0, false); - const Value *lhs = nullptr; - const Value *rhs = nullptr; - unsigned opcode = 0; + while (!worklist.empty()) + { + const Value* Cur = stripIntCasts(worklist.back().val); + int64_t curOffset = worklist.back().offset; + bool curSawOffset = worklist.back().sawOffset; + worklist.pop_back(); - if (auto *BO = dyn_cast(Cur)) { - opcode = BO->getOpcode(); - lhs = BO->getOperand(0); - rhs = BO->getOperand(1); - } else if (auto *CE = dyn_cast(Cur)) { - opcode = CE->getOpcode(); - lhs = CE->getOperand(0); - rhs = CE->getOperand(1); - } + if (const Value* PtrOp = getPtrToIntOperand(Cur)) + { + out.push_back({PtrOp, curOffset, curSawOffset}); + continue; + } - if (opcode == Instruction::Add || opcode == Instruction::Sub) { - const auto *lhsC = dyn_cast(lhs); - const auto *rhsC = dyn_cast(rhs); - if (rhsC) { - int64_t delta = rhsC->getSExtValue(); - if (opcode == Instruction::Sub) - delta = -delta; - int64_t newOffset = curOffset + delta; - if (recordVisited(lhs, newOffset, true)) - worklist.push_back({lhs, newOffset, true}); + const Value* lhs = nullptr; + const Value* rhs = nullptr; + unsigned opcode = 0; + + if (auto* BO = dyn_cast(Cur)) + { + opcode = BO->getOpcode(); + lhs = BO->getOperand(0); + rhs = BO->getOperand(1); + } + else if (auto* CE = dyn_cast(Cur)) + { + opcode = CE->getOpcode(); + lhs = CE->getOperand(0); + rhs = CE->getOperand(1); + } + + if (opcode == Instruction::Add || opcode == Instruction::Sub) + { + const auto* lhsC = dyn_cast(lhs); + const auto* rhsC = dyn_cast(rhs); + if (rhsC) + { + int64_t delta = rhsC->getSExtValue(); + if (opcode == Instruction::Sub) + delta = -delta; + int64_t newOffset = curOffset + delta; + if (recordVisited(lhs, newOffset, true)) + worklist.push_back({lhs, newOffset, true}); + continue; + } + if (lhsC && opcode == Instruction::Add) + { + int64_t delta = lhsC->getSExtValue(); + int64_t newOffset = curOffset + delta; + if (recordVisited(rhs, newOffset, true)) + worklist.push_back({rhs, newOffset, true}); + continue; + } + // sub with constant on LHS is not a valid reconstruction + } + + if (auto* PN = dyn_cast(Cur)) + { + for (const Value* In : PN->incoming_values()) + { + if (recordVisited(In, curOffset, curSawOffset)) + worklist.push_back({In, curOffset, curSawOffset}); + } continue; } - if (lhsC && opcode == Instruction::Add) { - int64_t delta = lhsC->getSExtValue(); - int64_t newOffset = curOffset + delta; - if (recordVisited(rhs, newOffset, true)) - worklist.push_back({rhs, newOffset, true}); + if (auto* Sel = dyn_cast(Cur)) + { + const Value* T = Sel->getTrueValue(); + const Value* F = Sel->getFalseValue(); + if (recordVisited(T, curOffset, curSawOffset)) + worklist.push_back({T, curOffset, curSawOffset}); + if (recordVisited(F, curOffset, curSawOffset)) + worklist.push_back({F, curOffset, curSawOffset}); continue; } - // sub with constant on LHS is not a valid reconstruction - } - if (auto *PN = dyn_cast(Cur)) { - for (const Value *In : PN->incoming_values()) { - if (recordVisited(In, curOffset, curSawOffset)) - worklist.push_back({In, curOffset, curSawOffset}); - } - continue; - } - if (auto *Sel = dyn_cast(Cur)) { - const Value *T = Sel->getTrueValue(); - const Value *F = Sel->getFalseValue(); - if (recordVisited(T, curOffset, curSawOffset)) - worklist.push_back({T, curOffset, curSawOffset}); - if (recordVisited(F, curOffset, curSawOffset)) - worklist.push_back({F, curOffset, curSawOffset}); - continue; - } + if (auto* LI = dyn_cast(Cur)) + { + const Value* PtrOp = LI->getPointerOperand()->stripPointerCasts(); + if (auto* AI = dyn_cast(PtrOp)) + { + Type* allocTy = AI->getAllocatedType(); + if (allocTy && allocTy->isIntegerTy()) + { + SmallVector seeds; + SmallVector deltas; - if (auto *LI = dyn_cast(Cur)) { - const Value *PtrOp = LI->getPointerOperand()->stripPointerCasts(); - if (auto *AI = dyn_cast(PtrOp)) { - Type *allocTy = AI->getAllocatedType(); - if (allocTy && allocTy->isIntegerTy()) { - SmallVector seeds; - SmallVector deltas; + for (const User* Usr : AI->users()) + { + auto* SI = dyn_cast(Usr); + if (!SI) + continue; + if (SI->getPointerOperand()->stripPointerCasts() != AI) + continue; + const Value* StoredVal = SI->getValueOperand(); - for (const User *Usr : AI->users()) { - auto *SI = dyn_cast(Usr); - if (!SI) - continue; - if (SI->getPointerOperand()->stripPointerCasts() != AI) - continue; - const Value *StoredVal = SI->getValueOperand(); + int64_t delta = 0; + if (matchAllocaLoadAddSub(StoredVal, AI, delta)) + { + deltas.push_back(delta); + continue; + } - int64_t delta = 0; - if (matchAllocaLoadAddSub(StoredVal, AI, delta)) { - deltas.push_back(delta); - continue; + llvm::SmallPtrSet depVisited; + if (!valueDependsOnAlloca(StoredVal, AI, depVisited)) + { + seeds.push_back(StoredVal); + } } - llvm::SmallPtrSet depVisited; - if (!valueDependsOnAlloca(StoredVal, AI, depVisited)) { - seeds.push_back(StoredVal); + if (!seeds.empty()) + { + for (const Value* Seed : seeds) + { + if (recordVisited(Seed, curOffset, curSawOffset)) + worklist.push_back({Seed, curOffset, curSawOffset}); + for (int64_t delta : deltas) + { + int64_t newOffset = curOffset + delta; + if (recordVisited(Seed, newOffset, true)) + worklist.push_back({Seed, newOffset, true}); + } + } } - } - - if (!seeds.empty()) { - for (const Value *Seed : seeds) { - if (recordVisited(Seed, curOffset, curSawOffset)) - worklist.push_back({Seed, curOffset, curSawOffset}); - for (int64_t delta : deltas) { - int64_t newOffset = curOffset + delta; - if (recordVisited(Seed, newOffset, true)) - worklist.push_back({Seed, newOffset, true}); + else + { + // Fallback: explore stored values directly (may be imprecise). + for (const User* Usr : AI->users()) + { + auto* SI = dyn_cast(Usr); + if (!SI) + continue; + if (SI->getPointerOperand()->stripPointerCasts() != AI) + continue; + const Value* StoredVal = SI->getValueOperand(); + if (recordVisited(StoredVal, curOffset, curSawOffset)) + worklist.push_back({StoredVal, curOffset, curSawOffset}); } } - } else { - // Fallback: explore stored values directly (may be imprecise). - for (const User *Usr : AI->users()) { - auto *SI = dyn_cast(Usr); - if (!SI) - continue; - if (SI->getPointerOperand()->stripPointerCasts() != AI) - continue; - const Value *StoredVal = SI->getValueOperand(); - if (recordVisited(StoredVal, curOffset, curSawOffset)) - worklist.push_back({StoredVal, curOffset, curSawOffset}); - } + continue; } - continue; } } } } -} -static void collectPointerOrigins(const llvm::Value *V, - const llvm::DataLayout &DL, - llvm::SmallVectorImpl &out) -{ - using namespace llvm; + static void collectPointerOrigins(const llvm::Value* V, const llvm::DataLayout& DL, + llvm::SmallVectorImpl& out) + { + using namespace llvm; - SmallVector, 16> worklist; - std::map> visited; + SmallVector, 16> worklist; + std::map> visited; - worklist.push_back({V, 0}); - recordVisitedOffset(visited, V, 0); + worklist.push_back({V, 0}); + recordVisitedOffset(visited, V, 0); - while (!worklist.empty()) { - const Value *Cur = worklist.back().first; - int64_t currentOffset = worklist.back().second; - worklist.pop_back(); + while (!worklist.empty()) + { + const Value* Cur = worklist.back().first; + int64_t currentOffset = worklist.back().second; + worklist.pop_back(); - if (auto *AI = dyn_cast(Cur)) { - Type *allocaTy = AI->getAllocatedType(); - if (allocaTy->isPointerTy()) { - // Pointer slot: follow what gets stored there. - for (const User *Usr : AI->users()) { - if (auto *SI = dyn_cast(Usr)) { - if (SI->getPointerOperand() != AI) - continue; - const Value *StoredVal = SI->getValueOperand(); - if (recordVisitedOffset(visited, StoredVal, currentOffset)) { - worklist.push_back({StoredVal, currentOffset}); + if (auto* AI = dyn_cast(Cur)) + { + Type* allocaTy = AI->getAllocatedType(); + if (allocaTy->isPointerTy()) + { + // Pointer slot: follow what gets stored there. + for (const User* Usr : AI->users()) + { + if (auto* SI = dyn_cast(Usr)) + { + if (SI->getPointerOperand() != AI) + continue; + const Value* StoredVal = SI->getValueOperand(); + if (recordVisitedOffset(visited, StoredVal, currentOffset)) + { + worklist.push_back({StoredVal, currentOffset}); + } } } + continue; } + + out.push_back({AI, currentOffset}); continue; } - out.push_back({AI, currentOffset}); - continue; - } - - if (auto *BC = dyn_cast(Cur)) { - const Value *Src = BC->getOperand(0); - if (recordVisitedOffset(visited, Src, currentOffset)) - worklist.push_back({Src, currentOffset}); - continue; - } + if (auto* BC = dyn_cast(Cur)) + { + const Value* Src = BC->getOperand(0); + if (recordVisitedOffset(visited, Src, currentOffset)) + worklist.push_back({Src, currentOffset}); + continue; + } - if (auto *ASC = dyn_cast(Cur)) { - const Value *Src = ASC->getOperand(0); - if (recordVisitedOffset(visited, Src, currentOffset)) - worklist.push_back({Src, currentOffset}); - continue; - } + if (auto* ASC = dyn_cast(Cur)) + { + const Value* Src = ASC->getOperand(0); + if (recordVisitedOffset(visited, Src, currentOffset)) + worklist.push_back({Src, currentOffset}); + continue; + } - int64_t gepOffset = 0; - const Value *gepBase = nullptr; - if (getGEPConstantOffsetAndBase(Cur, DL, gepOffset, gepBase)) { - int64_t newOffset = currentOffset + gepOffset; - if (recordVisitedOffset(visited, gepBase, newOffset)) - worklist.push_back({gepBase, newOffset}); - continue; - } + int64_t gepOffset = 0; + const Value* gepBase = nullptr; + if (getGEPConstantOffsetAndBase(Cur, DL, gepOffset, gepBase)) + { + int64_t newOffset = currentOffset + gepOffset; + if (recordVisitedOffset(visited, gepBase, newOffset)) + worklist.push_back({gepBase, newOffset}); + continue; + } - if (auto *LI = dyn_cast(Cur)) { - const Value *PtrOp = LI->getPointerOperand(); - if (recordVisitedOffset(visited, PtrOp, currentOffset)) - worklist.push_back({PtrOp, currentOffset}); - continue; - } + if (auto* LI = dyn_cast(Cur)) + { + const Value* PtrOp = LI->getPointerOperand(); + if (recordVisitedOffset(visited, PtrOp, currentOffset)) + worklist.push_back({PtrOp, currentOffset}); + continue; + } - if (auto *PN = dyn_cast(Cur)) { - for (const Value *In : PN->incoming_values()) { - if (recordVisitedOffset(visited, In, currentOffset)) - worklist.push_back({In, currentOffset}); + if (auto* PN = dyn_cast(Cur)) + { + for (const Value* In : PN->incoming_values()) + { + if (recordVisitedOffset(visited, In, currentOffset)) + worklist.push_back({In, currentOffset}); + } + continue; } - continue; - } - if (auto *Sel = dyn_cast(Cur)) { - const Value *T = Sel->getTrueValue(); - const Value *F = Sel->getFalseValue(); - if (recordVisitedOffset(visited, T, currentOffset)) - worklist.push_back({T, currentOffset}); - if (recordVisitedOffset(visited, F, currentOffset)) - worklist.push_back({F, currentOffset}); - continue; - } + if (auto* Sel = dyn_cast(Cur)) + { + const Value* T = Sel->getTrueValue(); + const Value* F = Sel->getFalseValue(); + if (recordVisitedOffset(visited, T, currentOffset)) + worklist.push_back({T, currentOffset}); + if (recordVisitedOffset(visited, F, currentOffset)) + worklist.push_back({F, currentOffset}); + continue; + } - if (auto *CE = dyn_cast(Cur)) { - if (CE->getOpcode() == Instruction::BitCast || - CE->getOpcode() == Instruction::AddrSpaceCast) { - const Value *Src = CE->getOperand(0); - if (recordVisitedOffset(visited, Src, currentOffset)) - worklist.push_back({Src, currentOffset}); + if (auto* CE = dyn_cast(Cur)) + { + if (CE->getOpcode() == Instruction::BitCast || + CE->getOpcode() == Instruction::AddrSpaceCast) + { + const Value* Src = CE->getOperand(0); + if (recordVisitedOffset(visited, Src, currentOffset)) + worklist.push_back({Src, currentOffset}); + } } } } -} -static bool isPointerDereferencedOrUsed(const llvm::Value *V) -{ - using namespace llvm; + static bool isPointerDereferencedOrUsed(const llvm::Value* V) + { + using namespace llvm; - SmallVector worklist; - SmallPtrSet visited; - worklist.push_back(V); + SmallVector worklist; + SmallPtrSet visited; + worklist.push_back(V); - while (!worklist.empty()) { - const Value *Cur = worklist.back(); - worklist.pop_back(); - if (!visited.insert(Cur).second) - continue; + while (!worklist.empty()) + { + const Value* Cur = worklist.back(); + worklist.pop_back(); + if (!visited.insert(Cur).second) + continue; - for (const Use &U : Cur->uses()) { - const User *Usr = U.getUser(); + for (const Use& U : Cur->uses()) + { + const User* Usr = U.getUser(); - if (auto *LI = dyn_cast(Usr)) { - if (LI->getPointerOperand() == Cur) - return true; - continue; - } - if (auto *SI = dyn_cast(Usr)) { - if (SI->getPointerOperand() == Cur) - return true; - if (SI->getValueOperand() == Cur) { - const Value *dst = SI->getPointerOperand()->stripPointerCasts(); - if (auto *AI = dyn_cast(dst)) { - Type *allocTy = AI->getAllocatedType(); - if (allocTy && allocTy->isPointerTy()) { - for (const User *AUser : AI->users()) { - if (auto *LI = dyn_cast(AUser)) { - if (LI->getPointerOperand()->stripPointerCasts() == AI) { - worklist.push_back(LI); + if (auto* LI = dyn_cast(Usr)) + { + if (LI->getPointerOperand() == Cur) + return true; + continue; + } + if (auto* SI = dyn_cast(Usr)) + { + if (SI->getPointerOperand() == Cur) + return true; + if (SI->getValueOperand() == Cur) + { + const Value* dst = SI->getPointerOperand()->stripPointerCasts(); + if (auto* AI = dyn_cast(dst)) + { + Type* allocTy = AI->getAllocatedType(); + if (allocTy && allocTy->isPointerTy()) + { + for (const User* AUser : AI->users()) + { + if (auto* LI = dyn_cast(AUser)) + { + if (LI->getPointerOperand()->stripPointerCasts() == AI) + { + worklist.push_back(LI); + } } } } } } + continue; } - continue; - } - if (auto *RMW = dyn_cast(Usr)) { - if (RMW->getPointerOperand() == Cur) - return true; - continue; - } - if (auto *CX = dyn_cast(Usr)) { - if (CX->getPointerOperand() == Cur) - return true; - continue; - } - if (auto *MI = dyn_cast(Usr)) { - if (MI->getRawDest() == Cur) - return true; - if (auto *MTI = dyn_cast(MI)) { - if (MTI->getRawSource() == Cur) + if (auto* RMW = dyn_cast(Usr)) + { + if (RMW->getPointerOperand() == Cur) return true; + continue; + } + if (auto* CX = dyn_cast(Usr)) + { + if (CX->getPointerOperand() == Cur) + return true; + continue; + } + if (auto* MI = dyn_cast(Usr)) + { + if (MI->getRawDest() == Cur) + return true; + if (auto* MTI = dyn_cast(MI)) + { + if (MTI->getRawSource() == Cur) + return true; + } + continue; } - continue; - } - if (auto *BC = dyn_cast(Usr)) { - worklist.push_back(BC); - continue; - } - if (auto *ASC = dyn_cast(Usr)) { - worklist.push_back(ASC); - continue; - } - if (auto *GEP = dyn_cast(Usr)) { - worklist.push_back(GEP); - continue; - } - if (auto *PN = dyn_cast(Usr)) { - worklist.push_back(PN); - continue; - } - if (auto *Sel = dyn_cast(Usr)) { - worklist.push_back(Sel); - continue; - } - if (auto *CE = dyn_cast(Usr)) { - worklist.push_back(CE); - continue; + if (auto* BC = dyn_cast(Usr)) + { + worklist.push_back(BC); + continue; + } + if (auto* ASC = dyn_cast(Usr)) + { + worklist.push_back(ASC); + continue; + } + if (auto* GEP = dyn_cast(Usr)) + { + worklist.push_back(GEP); + continue; + } + if (auto* PN = dyn_cast(Usr)) + { + worklist.push_back(PN); + continue; + } + if (auto* Sel = dyn_cast(Usr)) + { + worklist.push_back(Sel); + continue; + } + if (auto* CE = dyn_cast(Usr)) + { + worklist.push_back(CE); + continue; + } } } - } - - return false; -} - -static const llvm::Value *getPtrToIntOperand(const llvm::Value *V) -{ - using namespace llvm; - - if (auto *PTI = dyn_cast(V)) - return PTI->getOperand(0); - if (auto *CE = dyn_cast(V)) { - if (CE->getOpcode() == Instruction::PtrToInt) - return CE->getOperand(0); - } - return nullptr; -} -static bool matchPtrToIntAddSub(const llvm::Value *V, - const llvm::Value *&outPtrOperand, - int64_t &outOffset) -{ - using namespace llvm; - - const Value *lhs = nullptr; - const Value *rhs = nullptr; - unsigned opcode = 0; - - if (auto *BO = dyn_cast(V)) { - opcode = BO->getOpcode(); - lhs = BO->getOperand(0); - rhs = BO->getOperand(1); - } else if (auto *CE = dyn_cast(V)) { - opcode = CE->getOpcode(); - lhs = CE->getOperand(0); - rhs = CE->getOperand(1); - } else { return false; } - if (opcode != Instruction::Add && opcode != Instruction::Sub) - return false; + static const llvm::Value* getPtrToIntOperand(const llvm::Value* V) + { + using namespace llvm; - const Value *lhsPtr = getPtrToIntOperand(lhs); - const Value *rhsPtr = getPtrToIntOperand(rhs); + if (auto* PTI = dyn_cast(V)) + return PTI->getOperand(0); + if (auto* CE = dyn_cast(V)) + { + if (CE->getOpcode() == Instruction::PtrToInt) + return CE->getOperand(0); + } + return nullptr; + } - const ConstantInt *lhsC = dyn_cast(lhs); - const ConstantInt *rhsC = dyn_cast(rhs); + static bool matchPtrToIntAddSub(const llvm::Value* V, const llvm::Value*& outPtrOperand, + int64_t& outOffset) + { + using namespace llvm; - if (lhsPtr && rhsC) { - outPtrOperand = lhsPtr; - outOffset = rhsC->getSExtValue(); - if (opcode == Instruction::Sub) - outOffset = -outOffset; - return true; - } + const Value* lhs = nullptr; + const Value* rhs = nullptr; + unsigned opcode = 0; - if (rhsPtr && lhsC) { - if (opcode == Instruction::Sub) { - // Pattern is C - ptrtoint(P): not a base reconstruction. + if (auto* BO = dyn_cast(V)) + { + opcode = BO->getOpcode(); + lhs = BO->getOperand(0); + rhs = BO->getOperand(1); + } + else if (auto* CE = dyn_cast(V)) + { + opcode = CE->getOpcode(); + lhs = CE->getOperand(0); + rhs = CE->getOperand(1); + } + else + { return false; } - outPtrOperand = rhsPtr; - outOffset = lhsC->getSExtValue(); - return true; - } - return false; -} - -// Détecte les patterns de type: -// inttoptr(ptrtoint(P) +/- C) -// ou (char*)P +/- C -// où P est un membre d'une structure sur la stack et C est un offset constant, -// et le résultat est utilisé comme pointeur vers la structure de base. -// -// Ce pattern est typiquement utilisé dans container_of() / offsetof() mais peut -// être incorrect si l'offset ne correspond pas au membre réel. -static void analyzeInvalidBaseReconstructionsInFunction( - llvm::Function &F, - const llvm::DataLayout &DL, - std::vector &out) -{ - using namespace llvm; - - if (F.isDeclaration()) - return; - - // Recherche des allocas (objets stack) - std::map> allocaInfo; - - for (BasicBlock &BB : F) { - for (Instruction &I : BB) { - auto *AI = dyn_cast(&I); - if (!AI) - continue; + if (opcode != Instruction::Add && opcode != Instruction::Sub) + return false; - // Calcul de la taille de l'objet alloué - std::optional sizeOpt = getAllocaTotalSizeBytes(AI, DL); - if (!sizeOpt.has_value()) - continue; // Taille dynamique, on ne peut pas analyser + const Value* lhsPtr = getPtrToIntOperand(lhs); + const Value* rhsPtr = getPtrToIntOperand(rhs); + + const ConstantInt* lhsC = dyn_cast(lhs); + const ConstantInt* rhsC = dyn_cast(rhs); + + if (lhsPtr && rhsC) + { + outPtrOperand = lhsPtr; + outOffset = rhsC->getSExtValue(); + if (opcode == Instruction::Sub) + outOffset = -outOffset; + return true; + } - std::string varName = AI->hasName() ? AI->getName().str() : std::string(""); - allocaInfo[AI] = {varName, sizeOpt.value()}; + if (rhsPtr && lhsC) + { + if (opcode == Instruction::Sub) + { + // Pattern is C - ptrtoint(P): not a base reconstruction. + return false; + } + outPtrOperand = rhsPtr; + outOffset = lhsC->getSExtValue(); + return true; } + + return false; } - // Maintenant, recherchons les patterns de reconstruction de pointeur de base - for (BasicBlock &BB : F) { - for (Instruction &I : BB) { - - // Pattern 1: inttoptr(ptrtoint(P) +/- C) - if (auto *ITP = dyn_cast(&I)) { - if (!isPointerDereferencedOrUsed(ITP)) - continue; + // Détecte les patterns de type: + // inttoptr(ptrtoint(P) +/- C) + // ou (char*)P +/- C + // où P est un membre d'une structure sur la stack et C est un offset constant, + // et le résultat est utilisé comme pointeur vers la structure de base. + // + // Ce pattern est typiquement utilisé dans container_of() / offsetof() mais peut + // être incorrect si l'offset ne correspond pas au membre réel. + static void + analyzeInvalidBaseReconstructionsInFunction(llvm::Function& F, const llvm::DataLayout& DL, + std::vector& out) + { + using namespace llvm; + + if (F.isDeclaration()) + return; - Value *IntVal = ITP->getOperand(0); + // Recherche des allocas (objets stack) + std::map> allocaInfo; - SmallVector matches; - collectPtrToIntMatches(IntVal, matches); - if (matches.empty()) + for (BasicBlock& BB : F) + { + for (Instruction& I : BB) + { + auto* AI = dyn_cast(&I); + if (!AI) continue; - struct AggEntry { - std::set memberOffsets; - bool anyOutOfBounds = false; - bool anyNonZeroResult = false; - std::string varName; - uint64_t allocaSize = 0; - std::string targetType; - }; + // Calcul de la taille de l'objet alloué + std::optional sizeOpt = getAllocaTotalSizeBytes(AI, DL); + if (!sizeOpt.has_value()) + continue; // Taille dynamique, on ne peut pas analyser + + std::string varName = + AI->hasName() ? AI->getName().str() : std::string(""); + allocaInfo[AI] = {varName, sizeOpt.value()}; + } + } - std::map, AggEntry> agg; + // Maintenant, recherchons les patterns de reconstruction de pointeur de base + for (BasicBlock& BB : F) + { + for (Instruction& I : BB) + { - for (const auto &match : matches) { - if (!match.sawOffset) + // Pattern 1: inttoptr(ptrtoint(P) +/- C) + if (auto* ITP = dyn_cast(&I)) + { + if (!isPointerDereferencedOrUsed(ITP)) continue; - SmallVector origins; - collectPointerOrigins(match.ptrOperand, DL, origins); - if (origins.empty()) + Value* IntVal = ITP->getOperand(0); + + SmallVector matches; + collectPtrToIntMatches(IntVal, matches); + if (matches.empty()) continue; - for (const auto &origin : origins) { - auto it = allocaInfo.find(origin.alloca); - if (it == allocaInfo.end()) + struct AggEntry + { + std::set memberOffsets; + bool anyOutOfBounds = false; + bool anyNonZeroResult = false; + std::string varName; + uint64_t allocaSize = 0; + std::string targetType; + }; + + std::map, AggEntry> agg; + + for (const auto& match : matches) + { + if (!match.sawOffset) continue; - const std::string &varName = it->second.first; - uint64_t allocaSize = it->second.second; + SmallVector origins; + collectPointerOrigins(match.ptrOperand, DL, origins); + if (origins.empty()) + continue; - int64_t resultOffset = origin.offset + match.offset; - bool isOutOfBounds = (resultOffset < 0) || - (static_cast(resultOffset) >= allocaSize); + for (const auto& origin : origins) + { + auto it = allocaInfo.find(origin.alloca); + if (it == allocaInfo.end()) + continue; - std::string targetType; - Type *targetTy = ITP->getType(); - if (auto *PtrTy = dyn_cast(targetTy)) { - raw_string_ostream rso(targetType); - PtrTy->print(rso); + const std::string& varName = it->second.first; + uint64_t allocaSize = it->second.second; + + int64_t resultOffset = origin.offset + match.offset; + bool isOutOfBounds = + (resultOffset < 0) || + (static_cast(resultOffset) >= allocaSize); + + std::string targetType; + Type* targetTy = ITP->getType(); + if (auto* PtrTy = dyn_cast(targetTy)) + { + raw_string_ostream rso(targetType); + PtrTy->print(rso); + } + + auto key = std::make_pair(origin.alloca, match.offset); + auto& entry = agg[key]; + entry.memberOffsets.insert(origin.offset); + entry.anyOutOfBounds |= isOutOfBounds; + if (resultOffset != 0) + entry.anyNonZeroResult = true; + entry.varName = varName; + entry.allocaSize = allocaSize; + entry.targetType = targetType.empty() ? "" : targetType; } + } - auto key = std::make_pair(origin.alloca, match.offset); - auto &entry = agg[key]; - entry.memberOffsets.insert(origin.offset); - entry.anyOutOfBounds |= isOutOfBounds; - if (resultOffset != 0) - entry.anyNonZeroResult = true; - entry.varName = varName; - entry.allocaSize = allocaSize; - entry.targetType = targetType.empty() ? "" : targetType; + for (auto& kv : agg) + { + const auto& entry = kv.second; + if (entry.memberOffsets.empty()) + continue; + if (!entry.anyOutOfBounds && !entry.anyNonZeroResult) + continue; + + std::ostringstream memberStr; + if (entry.memberOffsets.size() == 1) + { + int64_t mo = *entry.memberOffsets.begin(); + memberStr << (mo != 0 ? "offset +" + std::to_string(mo) : "base"); + } + else + { + memberStr << "offsets "; + bool first = true; + for (int64_t mo : entry.memberOffsets) + { + if (!first) + memberStr << ", "; + memberStr << (mo != 0 ? "+" + std::to_string(mo) : "base"); + first = false; + } + } + + InvalidBaseReconstructionIssue issue; + issue.funcName = F.getName().str(); + issue.varName = entry.varName; + issue.sourceMember = memberStr.str(); + issue.offsetUsed = kv.first.second; + issue.targetType = entry.targetType; + issue.isOutOfBounds = entry.anyOutOfBounds; + issue.inst = &I; + + out.push_back(std::move(issue)); } } - for (auto &kv : agg) { - const auto &entry = kv.second; - if (entry.memberOffsets.empty()) - continue; - if (!entry.anyOutOfBounds && !entry.anyNonZeroResult) + // Pattern 2: GEP avec offset constant sur un membre + // (équivalent à (char*)ptr + offset) + if (auto* GEP = dyn_cast(&I)) + { + if (!isPointerDereferencedOrUsed(GEP)) continue; - std::ostringstream memberStr; - if (entry.memberOffsets.size() == 1) { - int64_t mo = *entry.memberOffsets.begin(); - memberStr << (mo != 0 ? "offset +" + std::to_string(mo) : "base"); - } else { - memberStr << "offsets "; - bool first = true; - for (int64_t mo : entry.memberOffsets) { - if (!first) - memberStr << ", "; - memberStr << (mo != 0 ? "+" + std::to_string(mo) : "base"); - first = false; - } - } + int64_t gepOffset = 0; + const Value* PtrOp = nullptr; + if (!getGEPConstantOffsetAndBase(GEP, DL, gepOffset, PtrOp)) + continue; - InvalidBaseReconstructionIssue issue; - issue.funcName = F.getName().str(); - issue.varName = entry.varName; - issue.sourceMember = memberStr.str(); - issue.offsetUsed = kv.first.second; - issue.targetType = entry.targetType; - issue.isOutOfBounds = entry.anyOutOfBounds; - issue.inst = &I; + SmallVector origins; + collectPointerOrigins(PtrOp, DL, origins); + if (origins.empty()) + continue; - out.push_back(std::move(issue)); - } - } - - // Pattern 2: GEP avec offset constant sur un membre - // (équivalent à (char*)ptr + offset) - if (auto *GEP = dyn_cast(&I)) { - if (!isPointerDereferencedOrUsed(GEP)) - continue; + struct AggEntry + { + std::set memberOffsets; + bool anyOutOfBounds = false; + bool anyNonZeroResult = false; + std::string varName; + std::string targetType; + }; - int64_t gepOffset = 0; - const Value *PtrOp = nullptr; - if (!getGEPConstantOffsetAndBase(GEP, DL, gepOffset, PtrOp)) - continue; + std::map agg; - SmallVector origins; - collectPointerOrigins(PtrOp, DL, origins); - if (origins.empty()) - continue; + for (const auto& origin : origins) + { + if (origin.offset == 0 && gepOffset >= 0) + { + // Likely a normal member access from the base object. + continue; + } - struct AggEntry { - std::set memberOffsets; - bool anyOutOfBounds = false; - bool anyNonZeroResult = false; - std::string varName; - std::string targetType; - }; + auto it = allocaInfo.find(origin.alloca); + if (it == allocaInfo.end()) + continue; - std::map agg; + const std::string& varName = it->second.first; + uint64_t allocaSize = it->second.second; - for (const auto &origin : origins) { - if (origin.offset == 0 && gepOffset >= 0) { - // Likely a normal member access from the base object. - continue; - } + int64_t resultOffset = origin.offset + gepOffset; + bool isOutOfBounds = (resultOffset < 0) || + (static_cast(resultOffset) >= allocaSize); - auto it = allocaInfo.find(origin.alloca); - if (it == allocaInfo.end()) - continue; + std::string targetType; + Type* targetTy = GEP->getType(); + raw_string_ostream rso(targetType); + targetTy->print(rso); - const std::string &varName = it->second.first; - uint64_t allocaSize = it->second.second; - - int64_t resultOffset = origin.offset + gepOffset; - bool isOutOfBounds = (resultOffset < 0) || - (static_cast(resultOffset) >= allocaSize); - - std::string targetType; - Type *targetTy = GEP->getType(); - raw_string_ostream rso(targetType); - targetTy->print(rso); - - auto &entry = agg[origin.alloca]; - entry.memberOffsets.insert(origin.offset); - entry.anyOutOfBounds |= isOutOfBounds; - if (resultOffset != 0) - entry.anyNonZeroResult = true; - entry.varName = varName; - entry.targetType = targetType; - } + auto& entry = agg[origin.alloca]; + entry.memberOffsets.insert(origin.offset); + entry.anyOutOfBounds |= isOutOfBounds; + if (resultOffset != 0) + entry.anyNonZeroResult = true; + entry.varName = varName; + entry.targetType = targetType; + } - for (auto &kv : agg) { - const auto &entry = kv.second; - if (entry.memberOffsets.empty()) - continue; - if (!entry.anyOutOfBounds && !entry.anyNonZeroResult) - continue; + for (auto& kv : agg) + { + const auto& entry = kv.second; + if (entry.memberOffsets.empty()) + continue; + if (!entry.anyOutOfBounds && !entry.anyNonZeroResult) + continue; - std::ostringstream memberStr; - if (entry.memberOffsets.size() == 1) { - int64_t mo = *entry.memberOffsets.begin(); - memberStr << (mo != 0 ? "offset +" + std::to_string(mo) : "base"); - } else { - memberStr << "offsets "; - bool first = true; - for (int64_t mo : entry.memberOffsets) { - if (!first) - memberStr << ", "; - memberStr << (mo != 0 ? "+" + std::to_string(mo) : "base"); - first = false; + std::ostringstream memberStr; + if (entry.memberOffsets.size() == 1) + { + int64_t mo = *entry.memberOffsets.begin(); + memberStr << (mo != 0 ? "offset +" + std::to_string(mo) : "base"); + } + else + { + memberStr << "offsets "; + bool first = true; + for (int64_t mo : entry.memberOffsets) + { + if (!first) + memberStr << ", "; + memberStr << (mo != 0 ? "+" + std::to_string(mo) : "base"); + first = false; + } } - } - InvalidBaseReconstructionIssue issue; - issue.funcName = F.getName().str(); - issue.varName = entry.varName; - issue.sourceMember = memberStr.str(); - issue.offsetUsed = gepOffset; - issue.targetType = entry.targetType; - issue.isOutOfBounds = entry.anyOutOfBounds; - issue.inst = &I; + InvalidBaseReconstructionIssue issue; + issue.funcName = F.getName().str(); + issue.varName = entry.varName; + issue.sourceMember = memberStr.str(); + issue.offsetUsed = gepOffset; + issue.targetType = entry.targetType; + issue.isOutOfBounds = entry.anyOutOfBounds; + issue.inst = &I; - out.push_back(std::move(issue)); + out.push_back(std::move(issue)); + } } } } } -} -// -------------------------------------------------------------------------- -// Helpers pour analyser les allocas et les bornes d'index -// -------------------------------------------------------------------------- + // -------------------------------------------------------------------------- + // Helpers pour analyser les allocas et les bornes d'index + // -------------------------------------------------------------------------- -// Taille (en nombre d'éléments) pour une alloca de tableau sur la stack -static std::optional getAllocaElementCount(llvm::AllocaInst *AI) -{ - using namespace llvm; + // Taille (en nombre d'éléments) pour une alloca de tableau sur la stack + static std::optional getAllocaElementCount(llvm::AllocaInst* AI) + { + using namespace llvm; - Type *elemTy = AI->getAllocatedType(); - StackSize count = 1; + Type* elemTy = AI->getAllocatedType(); + StackSize count = 1; - // Cas "char test[10];" => alloca [10 x i8] - if (auto *arrTy = dyn_cast(elemTy)) { - count *= arrTy->getNumElements(); - elemTy = arrTy->getElementType(); - } + // Cas "char test[10];" => alloca [10 x i8] + if (auto* arrTy = dyn_cast(elemTy)) + { + count *= arrTy->getNumElements(); + elemTy = arrTy->getElementType(); + } - // Cas "alloca i8, i64 10" => alloca tableau avec taille constante - if (AI->isArrayAllocation()) { - if (auto *C = dyn_cast(AI->getArraySize())) { - count *= C->getZExtValue(); - } else { - // taille non constante - analyse plus compliquée, on ignore pour l'instant - return std::nullopt; + // Cas "alloca i8, i64 10" => alloca tableau avec taille constante + if (AI->isArrayAllocation()) + { + if (auto* C = dyn_cast(AI->getArraySize())) + { + count *= C->getZExtValue(); + } + else + { + // taille non constante - analyse plus compliquée, on ignore pour l'instant + return std::nullopt; + } } + + return count; } - return count; -} + // Taille totale en octets pour une alloca sur la stack. + // Retourne std::nullopt si la taille dépend d'une valeur non constante (VLA). + static std::optional getAllocaTotalSizeBytes(const llvm::AllocaInst* AI, + const llvm::DataLayout& DL) + { + using namespace llvm; -// Taille totale en octets pour une alloca sur la stack. -// Retourne std::nullopt si la taille dépend d'une valeur non constante (VLA). -static std::optional -getAllocaTotalSizeBytes(const llvm::AllocaInst *AI, const llvm::DataLayout &DL) -{ - using namespace llvm; + Type* allocatedTy = AI->getAllocatedType(); - Type *allocatedTy = AI->getAllocatedType(); + // Cas alloca [N x T] (taille connue dans le type) + if (!AI->isArrayAllocation()) + { + return DL.getTypeAllocSize(allocatedTy); + } - // Cas alloca [N x T] (taille connue dans le type) - if (!AI->isArrayAllocation()) { - return DL.getTypeAllocSize(allocatedTy); - } + // Cas alloca T, i64 (taille passée séparément) + if (auto* C = dyn_cast(AI->getArraySize())) + { + uint64_t count = C->getZExtValue(); + uint64_t elemSize = DL.getTypeAllocSize(allocatedTy); + return count * elemSize; + } - // Cas alloca T, i64 (taille passée séparément) - if (auto *C = dyn_cast(AI->getArraySize())) { - uint64_t count = C->getZExtValue(); - uint64_t elemSize = DL.getTypeAllocSize(allocatedTy); - return count * elemSize; + // Taille dynamique - traitée par l'analyse DynamicAllocaIssue + return std::nullopt; } - // Taille dynamique - traitée par l'analyse DynamicAllocaIssue - return std::nullopt; -} - -// Analyse des comparaisons ICmp pour déduire les intervalles d'entiers (bornes inf/sup) -static std::map -computeIntRangesFromICmps(llvm::Function &F) -{ - using namespace llvm; + // Analyse des comparaisons ICmp pour déduire les intervalles d'entiers (bornes inf/sup) + static std::map computeIntRangesFromICmps(llvm::Function& F) + { + using namespace llvm; - std::map ranges; + std::map ranges; - auto applyConstraint = [&ranges](const Value *V, - bool hasLB, long long newLB, - bool hasUB, long long newUB) { - auto &R = ranges[V]; - if (hasLB) { - if (!R.hasLower || newLB > R.lower) { - R.hasLower = true; - R.lower = newLB; + auto applyConstraint = + [&ranges](const Value* V, bool hasLB, long long newLB, bool hasUB, long long newUB) + { + auto& R = ranges[V]; + if (hasLB) + { + if (!R.hasLower || newLB > R.lower) + { + R.hasLower = true; + R.lower = newLB; + } } - } - if (hasUB) { - if (!R.hasUpper || newUB < R.upper) { - R.hasUpper = true; - R.upper = newUB; + if (hasUB) + { + if (!R.hasUpper || newUB < R.upper) + { + R.hasUpper = true; + R.upper = newUB; + } } - } - }; + }; - for (BasicBlock &BB : F) { - for (Instruction &I : BB) { - auto *icmp = dyn_cast(&I); - if (!icmp) - continue; + for (BasicBlock& BB : F) + { + for (Instruction& I : BB) + { + auto* icmp = dyn_cast(&I); + if (!icmp) + continue; - Value *op0 = icmp->getOperand(0); - Value *op1 = icmp->getOperand(1); + Value* op0 = icmp->getOperand(0); + Value* op1 = icmp->getOperand(1); - ConstantInt *C = nullptr; - Value *V = nullptr; + ConstantInt* C = nullptr; + Value* V = nullptr; - // On cherche un pattern "V ? C" ou "C ? V" - if ((C = dyn_cast(op1)) && !isa(op0)) { - V = op0; - } else if ((C = dyn_cast(op0)) && !isa(op1)) { - V = op1; - } else { - continue; - } + // On cherche un pattern "V ? C" ou "C ? V" + if ((C = dyn_cast(op1)) && !isa(op0)) + { + V = op0; + } + else if ((C = dyn_cast(op0)) && !isa(op1)) + { + V = op1; + } + else + { + continue; + } - auto pred = icmp->getPredicate(); + auto pred = icmp->getPredicate(); - bool hasLB = false, hasUB = false; - long long lb = 0, ub = 0; + bool hasLB = false, hasUB = false; + long long lb = 0, ub = 0; - auto updateForSigned = [&](bool valueIsOp0) { - long long c = C->getSExtValue(); - if (valueIsOp0) { - switch (pred) { + auto updateForSigned = [&](bool valueIsOp0) + { + long long c = C->getSExtValue(); + if (valueIsOp0) + { + switch (pred) + { case ICmpInst::ICMP_SLT: // V < C => V <= C-1 - hasUB = true; ub = c - 1; break; + hasUB = true; + ub = c - 1; + break; case ICmpInst::ICMP_SLE: // V <= C => V <= C - hasUB = true; ub = c; break; + hasUB = true; + ub = c; + break; case ICmpInst::ICMP_SGT: // V > C => V >= C+1 - hasLB = true; lb = c + 1; break; + hasLB = true; + lb = c + 1; + break; case ICmpInst::ICMP_SGE: // V >= C => V >= C - hasLB = true; lb = c; break; - case ICmpInst::ICMP_EQ: // V == C => [C, C] - hasLB = true; lb = c; - hasUB = true; ub = c; + hasLB = true; + lb = c; + break; + case ICmpInst::ICMP_EQ: // V == C => [C, C] + hasLB = true; + lb = c; + hasUB = true; + ub = c; break; case ICmpInst::ICMP_NE: // approximation : V != C => V <= C (très conservateur) - hasUB = true; ub = c; + hasUB = true; + ub = c; break; default: break; + } } - } else { - // C ? V <=> V ? C (inversé) - switch (pred) { + else + { + // C ? V <=> V ? C (inversé) + switch (pred) + { case ICmpInst::ICMP_SGT: // C > V => V < C => V <= C-1 - hasUB = true; ub = c - 1; break; + hasUB = true; + ub = c - 1; + break; case ICmpInst::ICMP_SGE: // C >= V => V <= C - hasUB = true; ub = c; break; + hasUB = true; + ub = c; + break; case ICmpInst::ICMP_SLT: // C < V => V > C => V >= C+1 - hasLB = true; lb = c + 1; break; + hasLB = true; + lb = c + 1; + break; case ICmpInst::ICMP_SLE: // C <= V => V >= C - hasLB = true; lb = c; break; - case ICmpInst::ICMP_EQ: // C == V => [C, C] - hasLB = true; lb = c; - hasUB = true; ub = c; + hasLB = true; + lb = c; + break; + case ICmpInst::ICMP_EQ: // C == V => [C, C] + hasLB = true; + lb = c; + hasUB = true; + ub = c; break; case ICmpInst::ICMP_NE: - hasUB = true; ub = c; + hasUB = true; + ub = c; break; default: break; + } } - } - }; + }; - auto updateForUnsigned = [&](bool valueIsOp0) { - unsigned long long cu = C->getZExtValue(); - long long c = static_cast(cu); - if (valueIsOp0) { - switch (pred) { + auto updateForUnsigned = [&](bool valueIsOp0) + { + unsigned long long cu = C->getZExtValue(); + long long c = static_cast(cu); + if (valueIsOp0) + { + switch (pred) + { case ICmpInst::ICMP_ULT: // V < C => V <= C-1 - hasUB = true; ub = c - 1; break; + hasUB = true; + ub = c - 1; + break; case ICmpInst::ICMP_ULE: // V <= C - hasUB = true; ub = c; break; + hasUB = true; + ub = c; + break; case ICmpInst::ICMP_UGT: // V > C => V >= C+1 - hasLB = true; lb = c + 1; break; + hasLB = true; + lb = c + 1; + break; case ICmpInst::ICMP_UGE: // V >= C - hasLB = true; lb = c; break; + hasLB = true; + lb = c; + break; case ICmpInst::ICMP_EQ: - hasLB = true; lb = c; - hasUB = true; ub = c; + hasLB = true; + lb = c; + hasUB = true; + ub = c; break; case ICmpInst::ICMP_NE: - hasUB = true; ub = c; + hasUB = true; + ub = c; break; default: break; + } } - } else { - switch (pred) { + else + { + switch (pred) + { case ICmpInst::ICMP_UGT: // C > V => V < C - hasUB = true; ub = c - 1; break; + hasUB = true; + ub = c - 1; + break; case ICmpInst::ICMP_UGE: // C >= V => V <= C - hasUB = true; ub = c; break; + hasUB = true; + ub = c; + break; case ICmpInst::ICMP_ULT: // C < V => V > C - hasLB = true; lb = c + 1; break; + hasLB = true; + lb = c + 1; + break; case ICmpInst::ICMP_ULE: // C <= V => V >= C - hasLB = true; lb = c; break; + hasLB = true; + lb = c; + break; case ICmpInst::ICMP_EQ: - hasLB = true; lb = c; - hasUB = true; ub = c; + hasLB = true; + lb = c; + hasUB = true; + ub = c; break; case ICmpInst::ICMP_NE: - hasUB = true; ub = c; + hasUB = true; + ub = c; break; default: break; + } } - } - }; + }; - bool valueIsOp0 = (V == op0); + bool valueIsOp0 = (V == op0); - // On choisit le groupe de prédicats - if (pred == ICmpInst::ICMP_SLT || pred == ICmpInst::ICMP_SLE || - pred == ICmpInst::ICMP_SGT || pred == ICmpInst::ICMP_SGE || - pred == ICmpInst::ICMP_EQ || pred == ICmpInst::ICMP_NE) { - updateForSigned(valueIsOp0); - } else if (pred == ICmpInst::ICMP_ULT || pred == ICmpInst::ICMP_ULE || - pred == ICmpInst::ICMP_UGT || pred == ICmpInst::ICMP_UGE) { - updateForUnsigned(valueIsOp0); - } + // On choisit le groupe de prédicats + if (pred == ICmpInst::ICMP_SLT || pred == ICmpInst::ICMP_SLE || + pred == ICmpInst::ICMP_SGT || pred == ICmpInst::ICMP_SGE || + pred == ICmpInst::ICMP_EQ || pred == ICmpInst::ICMP_NE) + { + updateForSigned(valueIsOp0); + } + else if (pred == ICmpInst::ICMP_ULT || pred == ICmpInst::ICMP_ULE || + pred == ICmpInst::ICMP_UGT || pred == ICmpInst::ICMP_UGE) + { + updateForUnsigned(valueIsOp0); + } - if (!(hasLB || hasUB)) - continue; + if (!(hasLB || hasUB)) + continue; - // Applique la contrainte sur V lui-même - applyConstraint(V, hasLB, lb, hasUB, ub); + // Applique la contrainte sur V lui-même + applyConstraint(V, hasLB, lb, hasUB, ub); - // Et éventuellement sur le pointeur sous-jacent si V est un load - if (auto *LI = dyn_cast(V)) { - const Value *ptr = LI->getPointerOperand(); - applyConstraint(ptr, hasLB, lb, hasUB, ub); + // Et éventuellement sur le pointeur sous-jacent si V est un load + if (auto* LI = dyn_cast(V)) + { + const Value* ptr = LI->getPointerOperand(); + applyConstraint(ptr, hasLB, lb, hasUB, ub); + } } } + + return ranges; } - return ranges; -} + // Heuristic: determine if a Value is user-controlled + // (function argument, load from a non-local pointer, call result, etc.). + static bool isValueUserControlledImpl(const llvm::Value* V, const llvm::Function& F, + llvm::SmallPtrSet& visited, + int depth = 0) + { + using namespace llvm; -// Heuristic: determine if a Value is user-controlled -// (function argument, load from a non-local pointer, call result, etc.). -static bool isValueUserControlledImpl(const llvm::Value *V, - const llvm::Function &F, - llvm::SmallPtrSet &visited, - int depth = 0) -{ - using namespace llvm; + if (!V || depth > 20) + return false; + if (visited.contains(V)) + return false; + visited.insert(V); - if (!V || depth > 20) - return false; - if (visited.contains(V)) - return false; - visited.insert(V); + if (isa(V)) + return true; // function argument -> considered user-provided - if (isa(V)) - return true; // function argument -> considered user-provided + if (isa(V)) + return false; - if (isa(V)) - return false; + if (auto* LI = dyn_cast(V)) + { + const Value* ptr = LI->getPointerOperand()->stripPointerCasts(); + if (isa(ptr)) + return true; // load through pointer passed as argument + if (!isa(ptr)) + { + return true; // load from non-local memory (global / heap / unknown) + } + // If it's a local alloca, inspect what gets stored there. + const AllocaInst* AI = cast(ptr); + for (const Use& U : AI->uses()) + { + if (auto* SI = dyn_cast(U.getUser())) + { + if (SI->getPointerOperand()->stripPointerCasts() != ptr) + continue; + if (isValueUserControlledImpl(SI->getValueOperand(), F, visited, depth + 1)) + return true; + } + } + } + + if (auto* CB = dyn_cast(V)) + { + // Value produced by a call: conservatively treat as external/user input. + (void)F; + (void)CB; + return true; + } - if (auto *LI = dyn_cast(V)) { - const Value *ptr = LI->getPointerOperand()->stripPointerCasts(); - if (isa(ptr)) - return true; // load through pointer passed as argument - if (!isa(ptr)) { - return true; // load from non-local memory (global / heap / unknown) + if (auto* I = dyn_cast(V)) + { + for (const Value* Op : I->operands()) + { + if (isValueUserControlledImpl(Op, F, visited, depth + 1)) + return true; + } } - // If it's a local alloca, inspect what gets stored there. - const AllocaInst *AI = cast(ptr); - for (const Use &U : AI->uses()) { - if (auto *SI = dyn_cast(U.getUser())) { - if (SI->getPointerOperand()->stripPointerCasts() != ptr) - continue; - if (isValueUserControlledImpl(SI->getValueOperand(), F, visited, depth + 1)) + else if (auto* CE = dyn_cast(V)) + { + for (const Value* Op : CE->operands()) + { + if (isValueUserControlledImpl(Op, F, visited, depth + 1)) return true; } } - } - if (auto *CB = dyn_cast(V)) { - // Value produced by a call: conservatively treat as external/user input. - (void)F; - (void)CB; - return true; + return false; } - if (auto *I = dyn_cast(V)) { - for (const Value *Op : I->operands()) { - if (isValueUserControlledImpl(Op, F, visited, depth + 1)) - return true; - } - } else if (auto *CE = dyn_cast(V)) { - for (const Value *Op : CE->operands()) { - if (isValueUserControlledImpl(Op, F, visited, depth + 1)) - return true; - } + static bool isValueUserControlled(const llvm::Value* V, const llvm::Function& F) + { + llvm::SmallPtrSet visited; + return isValueUserControlledImpl(V, F, visited, 0); } - return false; -} - -static bool isValueUserControlled(const llvm::Value *V, - const llvm::Function &F) -{ - llvm::SmallPtrSet visited; - return isValueUserControlledImpl(V, F, visited, 0); -} + // Try to recover a human-friendly name for an alloca even when the instruction + // itself is unnamed (typical IR for "char *buf = alloca(n);"). + static std::string deriveAllocaName(const llvm::AllocaInst* AI) + { + using namespace llvm; -// Try to recover a human-friendly name for an alloca even when the instruction -// itself is unnamed (typical IR for "char *buf = alloca(n);"). -static std::string deriveAllocaName(const llvm::AllocaInst *AI) -{ - using namespace llvm; + if (!AI) + return std::string(""); + if (AI->hasName()) + return AI->getName().str(); - if (!AI) - return std::string(""); - if (AI->hasName()) - return AI->getName().str(); + SmallPtrSet visited; + SmallVector worklist; + worklist.push_back(AI); - SmallPtrSet visited; - SmallVector worklist; - worklist.push_back(AI); + while (!worklist.empty()) + { + const Value* V = worklist.back(); + worklist.pop_back(); + if (!visited.insert(V).second) + continue; - while (!worklist.empty()) { - const Value *V = worklist.back(); - worklist.pop_back(); - if (!visited.insert(V).second) - continue; + for (const Use& U : V->uses()) + { + const User* Usr = U.getUser(); - for (const Use &U : V->uses()) { - const User *Usr = U.getUser(); + if (auto* DVI = dyn_cast(Usr)) + { + if (auto* var = DVI->getVariable()) + { + if (!var->getName().empty()) + return var->getName().str(); + } + continue; + } - if (auto *DVI = dyn_cast(Usr)) { - if (auto *var = DVI->getVariable()) { - if (!var->getName().empty()) - return var->getName().str(); + if (auto* SI = dyn_cast(Usr)) + { + if (SI->getValueOperand() != V) + continue; + const Value* dst = SI->getPointerOperand()->stripPointerCasts(); + if (auto* dstAI = dyn_cast(dst)) + { + if (dstAI->hasName()) + return dstAI->getName().str(); + } + worklist.push_back(dst); + continue; } - continue; - } - if (auto *SI = dyn_cast(Usr)) { - if (SI->getValueOperand() != V) + if (auto* BC = dyn_cast(Usr)) + { + worklist.push_back(BC); + continue; + } + if (auto* GEP = dyn_cast(Usr)) + { + worklist.push_back(GEP); + continue; + } + if (auto* PN = dyn_cast(Usr)) + { + if (PN->getType()->isPointerTy()) + worklist.push_back(PN); + continue; + } + if (auto* Sel = dyn_cast(Usr)) + { + if (Sel->getType()->isPointerTy()) + worklist.push_back(Sel); continue; - const Value *dst = SI->getPointerOperand()->stripPointerCasts(); - if (auto *dstAI = dyn_cast(dst)) { - if (dstAI->hasName()) - return dstAI->getName().str(); } - worklist.push_back(dst); - continue; } + } + + return std::string(""); + } + + static std::string formatFunctionNameForMessage(const std::string& name) + { + if (ctrace_tools::isMangled(name)) + return ctrace_tools::demangle(name.c_str()); + return name; + } + + struct TypeQualifiers + { + bool isConst = false; + bool isVolatile = false; + bool isRestrict = false; + }; + + struct StrippedDIType + { + const llvm::DIType* type = nullptr; + TypeQualifiers quals; + }; - if (auto *BC = dyn_cast(Usr)) { - worklist.push_back(BC); + struct ParamDebugInfo + { + std::string name; + const llvm::DIType* type = nullptr; + unsigned line = 0; + unsigned column = 0; + }; + + struct ParamTypeInfo + { + const llvm::DIType* originalType = nullptr; + const llvm::DIType* pointeeType = nullptr; // unqualified, typedefs stripped + const llvm::DIType* pointeeDisplayType = nullptr; // unqualified, typedefs preserved + bool isPointer = false; + bool isReference = false; + bool isRvalueReference = false; + bool pointerConst = false; + bool pointerVolatile = false; + bool pointerRestrict = false; + bool pointeeConst = false; + bool pointeeVolatile = false; + bool pointeeRestrict = false; + bool isDoublePointer = false; + bool isVoid = false; + bool isFunctionPointer = false; + }; + + static const llvm::DIType* stripTypedefs(const llvm::DIType* type) + { + using namespace llvm; + const DIType* cur = type; + while (cur) + { + auto* DT = dyn_cast(cur); + if (!DT) + break; + auto tag = DT->getTag(); + if (tag == dwarf::DW_TAG_typedef) + { + cur = DT->getBaseType(); continue; } - if (auto *GEP = dyn_cast(Usr)) { - worklist.push_back(GEP); + break; + } + return cur; + } + + static StrippedDIType stripQualifiers(const llvm::DIType* type) + { + using namespace llvm; + StrippedDIType out; + out.type = type; + + while (out.type) + { + auto* DT = dyn_cast(out.type); + if (!DT) + break; + auto tag = DT->getTag(); + if (tag == dwarf::DW_TAG_const_type) + { + out.quals.isConst = true; + out.type = DT->getBaseType(); continue; } - if (auto *PN = dyn_cast(Usr)) { - if (PN->getType()->isPointerTy()) - worklist.push_back(PN); + if (tag == dwarf::DW_TAG_volatile_type) + { + out.quals.isVolatile = true; + out.type = DT->getBaseType(); continue; } - if (auto *Sel = dyn_cast(Usr)) { - if (Sel->getType()->isPointerTy()) - worklist.push_back(Sel); + if (tag == dwarf::DW_TAG_restrict_type) + { + out.quals.isRestrict = true; + out.type = DT->getBaseType(); continue; } + break; } + + return out; } - return std::string(""); -} + static std::string formatDITypeName(const llvm::DIType* type) + { + using namespace llvm; + if (!type) + return std::string(""); -static std::string formatFunctionNameForMessage(const std::string &name) -{ - if (ctrace_tools::isMangled(name)) - return ctrace_tools::demangle(name.c_str()); - return name; -} - -struct TypeQualifiers { - bool isConst = false; - bool isVolatile = false; - bool isRestrict = false; -}; - -struct StrippedDIType { - const llvm::DIType *type = nullptr; - TypeQualifiers quals; -}; - -struct ParamDebugInfo { - std::string name; - const llvm::DIType *type = nullptr; - unsigned line = 0; - unsigned column = 0; -}; - -struct ParamTypeInfo { - const llvm::DIType *originalType = nullptr; - const llvm::DIType *pointeeType = nullptr; // unqualified, typedefs stripped - const llvm::DIType *pointeeDisplayType = nullptr; // unqualified, typedefs preserved - bool isPointer = false; - bool isReference = false; - bool isRvalueReference = false; - bool pointerConst = false; - bool pointerVolatile = false; - bool pointerRestrict = false; - bool pointeeConst = false; - bool pointeeVolatile = false; - bool pointeeRestrict = false; - bool isDoublePointer = false; - bool isVoid = false; - bool isFunctionPointer = false; -}; - -static const llvm::DIType *stripTypedefs(const llvm::DIType *type) -{ - using namespace llvm; - const DIType *cur = type; - while (cur) { - auto *DT = dyn_cast(cur); - if (!DT) - break; - auto tag = DT->getTag(); - if (tag == dwarf::DW_TAG_typedef) { - cur = DT->getBaseType(); - continue; + if (auto* BT = dyn_cast(type)) + { + if (!BT->getName().empty()) + return BT->getName().str(); } - break; - } - return cur; -} - -static StrippedDIType stripQualifiers(const llvm::DIType *type) -{ - using namespace llvm; - StrippedDIType out; - out.type = type; - while (out.type) { - auto *DT = dyn_cast(out.type); - if (!DT) - break; - auto tag = DT->getTag(); - if (tag == dwarf::DW_TAG_const_type) { - out.quals.isConst = true; - out.type = DT->getBaseType(); - continue; + if (auto* CT = dyn_cast(type)) + { + if (!CT->getName().empty()) + return CT->getName().str(); + if (!CT->getIdentifier().empty()) + return CT->getIdentifier().str(); } - if (tag == dwarf::DW_TAG_volatile_type) { - out.quals.isVolatile = true; - out.type = DT->getBaseType(); - continue; + + if (auto* DT = dyn_cast(type)) + { + auto tag = DT->getTag(); + if (tag == dwarf::DW_TAG_typedef && !DT->getName().empty()) + { + return DT->getName().str(); + } + if ((tag == dwarf::DW_TAG_const_type) || (tag == dwarf::DW_TAG_volatile_type) || + (tag == dwarf::DW_TAG_restrict_type)) + { + return formatDITypeName(DT->getBaseType()); + } + if (!DT->getName().empty()) + return DT->getName().str(); } - if (tag == dwarf::DW_TAG_restrict_type) { - out.quals.isRestrict = true; - out.type = DT->getBaseType(); - continue; + + if (auto* ST = dyn_cast(type)) + { + (void)ST; + return std::string(""); } - break; + + return std::string(""); } - return out; -} + static bool buildParamTypeInfo(const llvm::DIType* type, ParamTypeInfo& info) + { + using namespace llvm; + if (!type) + return false; -static std::string formatDITypeName(const llvm::DIType *type) -{ - using namespace llvm; - if (!type) - return std::string(""); + info.originalType = type; - if (auto *BT = dyn_cast(type)) { - if (!BT->getName().empty()) - return BT->getName().str(); - } + StrippedDIType top = stripQualifiers(type); + info.pointerConst = top.quals.isConst; + info.pointerVolatile = top.quals.isVolatile; + info.pointerRestrict = top.quals.isRestrict; - if (auto *CT = dyn_cast(type)) { - if (!CT->getName().empty()) - return CT->getName().str(); - if (!CT->getIdentifier().empty()) - return CT->getIdentifier().str(); - } + const DIType* topType = stripTypedefs(top.type); + auto* derived = dyn_cast(topType); + if (!derived) + return false; - if (auto *DT = dyn_cast(type)) { - auto tag = DT->getTag(); - if (tag == dwarf::DW_TAG_typedef && !DT->getName().empty()) { - return DT->getName().str(); + auto tag = derived->getTag(); + if (tag == dwarf::DW_TAG_pointer_type) + { + info.isPointer = true; } - if ((tag == dwarf::DW_TAG_const_type) || - (tag == dwarf::DW_TAG_volatile_type) || - (tag == dwarf::DW_TAG_restrict_type)) { - return formatDITypeName(DT->getBaseType()); + else if (tag == dwarf::DW_TAG_reference_type) + { + info.isReference = true; + } + else if (tag == dwarf::DW_TAG_rvalue_reference_type) + { + info.isReference = true; + info.isRvalueReference = true; + } + else + { + return false; } - if (!DT->getName().empty()) - return DT->getName().str(); - } - - if (auto *ST = dyn_cast(type)) { - (void)ST; - return std::string(""); - } - - return std::string(""); -} - -static bool buildParamTypeInfo(const llvm::DIType *type, - ParamTypeInfo &info) -{ - using namespace llvm; - if (!type) - return false; - info.originalType = type; + const DIType* baseType = derived->getBaseType(); + StrippedDIType base = stripQualifiers(baseType); + info.pointeeConst = base.quals.isConst; + info.pointeeVolatile = base.quals.isVolatile; + info.pointeeRestrict = base.quals.isRestrict; + info.pointeeDisplayType = base.type ? base.type : baseType; - StrippedDIType top = stripQualifiers(type); - info.pointerConst = top.quals.isConst; - info.pointerVolatile = top.quals.isVolatile; - info.pointerRestrict = top.quals.isRestrict; + const DIType* baseNoTypedef = stripTypedefs(base.type); + info.pointeeType = baseNoTypedef; - const DIType *topType = stripTypedefs(top.type); - auto *derived = dyn_cast(topType); - if (!derived) - return false; + if (!baseNoTypedef) + return true; - auto tag = derived->getTag(); - if (tag == dwarf::DW_TAG_pointer_type) { - info.isPointer = true; - } else if (tag == dwarf::DW_TAG_reference_type) { - info.isReference = true; - } else if (tag == dwarf::DW_TAG_rvalue_reference_type) { - info.isReference = true; - info.isRvalueReference = true; - } else { - return false; - } + if (auto* baseDerived = dyn_cast(baseNoTypedef)) + { + auto baseTag = baseDerived->getTag(); + if (baseTag == dwarf::DW_TAG_pointer_type || baseTag == dwarf::DW_TAG_reference_type || + baseTag == dwarf::DW_TAG_rvalue_reference_type) + { + info.isDoublePointer = true; + } + } - const DIType *baseType = derived->getBaseType(); - StrippedDIType base = stripQualifiers(baseType); - info.pointeeConst = base.quals.isConst; - info.pointeeVolatile = base.quals.isVolatile; - info.pointeeRestrict = base.quals.isRestrict; - info.pointeeDisplayType = base.type ? base.type : baseType; + if (isa(baseNoTypedef)) + info.isFunctionPointer = true; - const DIType *baseNoTypedef = stripTypedefs(base.type); - info.pointeeType = baseNoTypedef; + if (auto* basic = dyn_cast(baseNoTypedef)) + { + if (basic->getName() == "void") + info.isVoid = true; + } - if (!baseNoTypedef) return true; - - if (auto *baseDerived = dyn_cast(baseNoTypedef)) { - auto baseTag = baseDerived->getTag(); - if (baseTag == dwarf::DW_TAG_pointer_type || - baseTag == dwarf::DW_TAG_reference_type || - baseTag == dwarf::DW_TAG_rvalue_reference_type) { - info.isDoublePointer = true; - } } - if (isa(baseNoTypedef)) - info.isFunctionPointer = true; - - if (auto *basic = dyn_cast(baseNoTypedef)) { - if (basic->getName() == "void") - info.isVoid = true; - } + static std::string buildTypeString(const ParamTypeInfo& info, const std::string& baseName, + bool addPointeeConst, bool includePointerConst, + const std::string& paramName) + { + std::string out; + if (info.pointeeConst || addPointeeConst) + out += "const "; + if (info.pointeeVolatile) + out += "volatile "; + out += baseName.empty() ? std::string("") : baseName; + + if (info.isReference) + { + out += info.isRvalueReference ? " &&" : " &"; + if (!paramName.empty()) + { + out += paramName; + } + return out; + } - return true; -} + if (info.isPointer) + { + out += " *"; + if (includePointerConst && info.pointerConst) + out += " const"; + if (info.pointerVolatile) + out += " volatile"; + if (info.pointerRestrict) + out += " restrict"; + } -static std::string buildTypeString(const ParamTypeInfo &info, - const std::string &baseName, - bool addPointeeConst, - bool includePointerConst, - const std::string ¶mName) -{ - std::string out; - if (info.pointeeConst || addPointeeConst) - out += "const "; - if (info.pointeeVolatile) - out += "volatile "; - out += baseName.empty() ? std::string("") : baseName; - - if (info.isReference) { - out += info.isRvalueReference ? " &&" : " &"; - if (!paramName.empty()) { - out += paramName; + if (!paramName.empty()) + { + if (!out.empty() && (out.back() == '*' || out.back() == '&')) + out += paramName; + else + out += " " + paramName; } - return out; - } - if (info.isPointer) { - out += " *"; - if (includePointerConst && info.pointerConst) - out += " const"; - if (info.pointerVolatile) - out += " volatile"; - if (info.pointerRestrict) - out += " restrict"; + return out; } - if (!paramName.empty()) { - if (!out.empty() && (out.back() == '*' || out.back() == '&')) - out += paramName; - else - out += " " + paramName; + static std::string buildPointeeQualPrefix(const ParamTypeInfo& info, bool addConst) + { + std::string out; + if (addConst) + out += "const "; + if (info.pointeeVolatile) + out += "volatile "; + if (info.pointeeRestrict) + out += "restrict "; + return out; } - return out; -} + static ParamDebugInfo getParamDebugInfo(const llvm::Function& F, const llvm::Argument& Arg) + { + using namespace llvm; + ParamDebugInfo info; + info.name = Arg.getName().str(); -static std::string buildPointeeQualPrefix(const ParamTypeInfo &info, - bool addConst) -{ - std::string out; - if (addConst) - out += "const "; - if (info.pointeeVolatile) - out += "volatile "; - if (info.pointeeRestrict) - out += "restrict "; - return out; -} - -static ParamDebugInfo getParamDebugInfo(const llvm::Function &F, - const llvm::Argument &Arg) -{ - using namespace llvm; - ParamDebugInfo info; - info.name = Arg.getName().str(); - - if (auto *SP = F.getSubprogram()) { - for (DINode *node : SP->getRetainedNodes()) { - auto *var = dyn_cast(node); - if (!var || !var->isParameter()) - continue; - if (var->getArg() != Arg.getArgNo() + 1) - continue; - if (!var->getName().empty()) - info.name = var->getName().str(); - info.type = var->getType(); - if (var->getLine() != 0) - info.line = var->getLine(); - break; - } + if (auto* SP = F.getSubprogram()) + { + for (DINode* node : SP->getRetainedNodes()) + { + auto* var = dyn_cast(node); + if (!var || !var->isParameter()) + continue; + if (var->getArg() != Arg.getArgNo() + 1) + continue; + if (!var->getName().empty()) + info.name = var->getName().str(); + info.type = var->getType(); + if (var->getLine() != 0) + info.line = var->getLine(); + break; + } - if (!info.type) { - if (auto *subTy = SP->getType()) { - auto types = subTy->getTypeArray(); - if (types.size() > Arg.getArgNo() + 1) - info.type = types[Arg.getArgNo() + 1]; + if (!info.type) + { + if (auto* subTy = SP->getType()) + { + auto types = subTy->getTypeArray(); + if (types.size() > Arg.getArgNo() + 1) + info.type = types[Arg.getArgNo() + 1]; + } } + + if (info.line == 0) + info.line = SP->getLine(); } - if (info.line == 0) - info.line = SP->getLine(); + return info; } - return info; -} + static bool calleeParamIsReadOnly(const llvm::Function* callee, unsigned argIndex) + { + if (!callee || argIndex >= callee->arg_size()) + return false; -static bool calleeParamIsReadOnly(const llvm::Function *callee, - unsigned argIndex) -{ - if (!callee || argIndex >= callee->arg_size()) - return false; + const llvm::Argument& param = *callee->getArg(argIndex); + ParamDebugInfo dbg = getParamDebugInfo(*callee, param); + if (!dbg.type) + return false; - const llvm::Argument ¶m = *callee->getArg(argIndex); - ParamDebugInfo dbg = getParamDebugInfo(*callee, param); - if (!dbg.type) - return false; + ParamTypeInfo typeInfo; + if (!buildParamTypeInfo(dbg.type, typeInfo)) + return false; - ParamTypeInfo typeInfo; - if (!buildParamTypeInfo(dbg.type, typeInfo)) - return false; + if (typeInfo.isDoublePointer || typeInfo.isVoid || typeInfo.isFunctionPointer) + return false; - if (typeInfo.isDoublePointer || typeInfo.isVoid || typeInfo.isFunctionPointer) - return false; + if (!typeInfo.isPointer && !typeInfo.isReference) + return false; - if (!typeInfo.isPointer && !typeInfo.isReference) - return false; + return typeInfo.pointeeConst; + } - return typeInfo.pointeeConst; -} + static bool callArgMayWriteThrough(const llvm::CallBase& CB, unsigned argIndex) + { + using namespace llvm; -static bool callArgMayWriteThrough(const llvm::CallBase &CB, - unsigned argIndex) -{ - using namespace llvm; - - const Function *callee = CB.getCalledFunction(); - if (!callee) { - const Value *called = CB.getCalledOperand(); - if (called) - called = called->stripPointerCasts(); - callee = dyn_cast(called); - } + const Function* callee = CB.getCalledFunction(); + if (!callee) + { + const Value* called = CB.getCalledOperand(); + if (called) + called = called->stripPointerCasts(); + callee = dyn_cast(called); + } - if (!callee) - return true; + if (!callee) + return true; - if (auto *MI = dyn_cast(&CB)) { - if (isa(MI)) - return argIndex == 0; - if (isa(MI)) - return argIndex == 0; - } + if (auto* MI = dyn_cast(&CB)) + { + if (isa(MI)) + return argIndex == 0; + if (isa(MI)) + return argIndex == 0; + } - if (callee->isIntrinsic()) { - switch (callee->getIntrinsicID()) { - case Intrinsic::dbg_declare: - case Intrinsic::dbg_value: - case Intrinsic::dbg_label: - case Intrinsic::lifetime_start: - case Intrinsic::lifetime_end: - case Intrinsic::invariant_start: - case Intrinsic::invariant_end: - case Intrinsic::assume: - return false; - default: - break; + if (callee->isIntrinsic()) + { + switch (callee->getIntrinsicID()) + { + case Intrinsic::dbg_declare: + case Intrinsic::dbg_value: + case Intrinsic::dbg_label: + case Intrinsic::lifetime_start: + case Intrinsic::lifetime_end: + case Intrinsic::invariant_start: + case Intrinsic::invariant_end: + case Intrinsic::assume: + return false; + default: + break; + } } - } - if (callee->doesNotAccessMemory()) - return false; - if (callee->onlyReadsMemory()) - return false; + if (callee->doesNotAccessMemory()) + return false; + if (callee->onlyReadsMemory()) + return false; - if (argIndex >= callee->arg_size()) - return true; // varargs or unknown + if (argIndex >= callee->arg_size()) + return true; // varargs or unknown - const AttributeList &attrs = callee->getAttributes(); - if (attrs.hasParamAttr(argIndex, Attribute::ReadOnly) || - attrs.hasParamAttr(argIndex, Attribute::ReadNone)) { - return false; - } - if (attrs.hasParamAttr(argIndex, Attribute::WriteOnly)) - return true; + const AttributeList& attrs = callee->getAttributes(); + if (attrs.hasParamAttr(argIndex, Attribute::ReadOnly) || + attrs.hasParamAttr(argIndex, Attribute::ReadNone)) + { + return false; + } + if (attrs.hasParamAttr(argIndex, Attribute::WriteOnly)) + return true; - if (calleeParamIsReadOnly(callee, argIndex)) - return false; + if (calleeParamIsReadOnly(callee, argIndex)) + return false; - return true; -} + return true; + } -static bool valueMayBeWrittenThrough(const llvm::Value *root, - const llvm::Function &F) -{ - using namespace llvm; - (void)F; + static bool valueMayBeWrittenThrough(const llvm::Value* root, const llvm::Function& F) + { + using namespace llvm; + (void)F; - SmallPtrSet visited; - SmallVector worklist; - worklist.push_back(root); + SmallPtrSet visited; + SmallVector worklist; + worklist.push_back(root); - while (!worklist.empty()) { - const Value *V = worklist.pop_back_val(); - if (!visited.insert(V).second) - continue; + while (!worklist.empty()) + { + const Value* V = worklist.pop_back_val(); + if (!visited.insert(V).second) + continue; - for (const Use &U : V->uses()) { - const User *Usr = U.getUser(); + for (const Use& U : V->uses()) + { + const User* Usr = U.getUser(); - if (auto *SI = dyn_cast(Usr)) { - if (SI->getPointerOperand() == V) - return true; - if (SI->getValueOperand() == V) { - const Value *dst = SI->getPointerOperand()->stripPointerCasts(); - if (auto *AI = dyn_cast(dst)) { - for (const Use &AU : AI->uses()) { - if (auto *LI = dyn_cast(AU.getUser())) { - if (LI->getPointerOperand()->stripPointerCasts() == AI) - worklist.push_back(LI); + if (auto* SI = dyn_cast(Usr)) + { + if (SI->getPointerOperand() == V) + return true; + if (SI->getValueOperand() == V) + { + const Value* dst = SI->getPointerOperand()->stripPointerCasts(); + if (auto* AI = dyn_cast(dst)) + { + for (const Use& AU : AI->uses()) + { + if (auto* LI = dyn_cast(AU.getUser())) + { + if (LI->getPointerOperand()->stripPointerCasts() == AI) + worklist.push_back(LI); + } } } - } else { - return true; // pointer escapes to non-local memory + else + { + return true; // pointer escapes to non-local memory + } } + continue; } - continue; - } - if (auto *AI = dyn_cast(Usr)) { - if (AI->getPointerOperand() == V) - return true; - continue; - } + if (auto* AI = dyn_cast(Usr)) + { + if (AI->getPointerOperand() == V) + return true; + continue; + } - if (auto *CX = dyn_cast(Usr)) { - if (CX->getPointerOperand() == V) - return true; - continue; - } + if (auto* CX = dyn_cast(Usr)) + { + if (CX->getPointerOperand() == V) + return true; + continue; + } - if (auto *CB = dyn_cast(Usr)) { - for (unsigned i = 0; i < CB->arg_size(); ++i) { - if (CB->getArgOperand(i) == V) { - if (callArgMayWriteThrough(*CB, i)) - return true; + if (auto* CB = dyn_cast(Usr)) + { + for (unsigned i = 0; i < CB->arg_size(); ++i) + { + if (CB->getArgOperand(i) == V) + { + if (callArgMayWriteThrough(*CB, i)) + return true; + } } + continue; } - continue; - } - if (auto *GEP = dyn_cast(Usr)) { - worklist.push_back(GEP); - continue; - } - if (auto *BC = dyn_cast(Usr)) { - worklist.push_back(BC); - continue; - } - if (auto *ASC = dyn_cast(Usr)) { - worklist.push_back(ASC); - continue; - } - if (auto *PN = dyn_cast(Usr)) { - if (PN->getType()->isPointerTy()) - worklist.push_back(PN); - continue; - } - if (auto *Sel = dyn_cast(Usr)) { - if (Sel->getType()->isPointerTy()) - worklist.push_back(Sel); - continue; - } - if (auto *CI = dyn_cast(Usr)) { - if (CI->getType()->isPointerTy()) - worklist.push_back(CI); - continue; + if (auto* GEP = dyn_cast(Usr)) + { + worklist.push_back(GEP); + continue; + } + if (auto* BC = dyn_cast(Usr)) + { + worklist.push_back(BC); + continue; + } + if (auto* ASC = dyn_cast(Usr)) + { + worklist.push_back(ASC); + continue; + } + if (auto* PN = dyn_cast(Usr)) + { + if (PN->getType()->isPointerTy()) + worklist.push_back(PN); + continue; + } + if (auto* Sel = dyn_cast(Usr)) + { + if (Sel->getType()->isPointerTy()) + worklist.push_back(Sel); + continue; + } + if (auto* CI = dyn_cast(Usr)) + { + if (CI->getType()->isPointerTy()) + worklist.push_back(CI); + continue; + } + if (isa(Usr)) + return true; // unknown aliasing, be conservative } - if (isa(Usr)) - return true; // unknown aliasing, be conservative } + + return false; } - return false; -} + static void analyzeConstParamsInFunction(llvm::Function& F, std::vector& out) + { + using namespace llvm; -static void analyzeConstParamsInFunction(llvm::Function &F, - std::vector &out) -{ - using namespace llvm; + if (F.isDeclaration()) + return; - if (F.isDeclaration()) - return; + for (Argument& Arg : F.args()) + { + ParamDebugInfo dbg = getParamDebugInfo(F, Arg); + if (!dbg.type) + continue; - for (Argument &Arg : F.args()) { - ParamDebugInfo dbg = getParamDebugInfo(F, Arg); - if (!dbg.type) - continue; + ParamTypeInfo typeInfo; + if (!buildParamTypeInfo(dbg.type, typeInfo)) + continue; - ParamTypeInfo typeInfo; - if (!buildParamTypeInfo(dbg.type, typeInfo)) - continue; + if (!typeInfo.isPointer && !typeInfo.isReference) + continue; + if (typeInfo.isDoublePointer || typeInfo.isVoid || typeInfo.isFunctionPointer) + continue; + if (typeInfo.pointeeConst) + continue; - if (!typeInfo.isPointer && !typeInfo.isReference) - continue; - if (typeInfo.isDoublePointer || typeInfo.isVoid || typeInfo.isFunctionPointer) - continue; - if (typeInfo.pointeeConst) - continue; - - if (valueMayBeWrittenThrough(&Arg, F)) - continue; - - ConstParamIssue issue; - issue.funcName = F.getName().str(); - issue.paramName = dbg.name.empty() ? Arg.getName().str() : dbg.name; - issue.line = dbg.line; - issue.column = dbg.column; - issue.pointerConstOnly = typeInfo.isPointer && typeInfo.pointerConst && !typeInfo.pointeeConst; - issue.isReference = typeInfo.isReference; - issue.isRvalueRef = typeInfo.isRvalueReference; - - std::string baseName = formatDITypeName(typeInfo.pointeeDisplayType); - issue.currentType = buildTypeString(typeInfo, baseName, false, true, issue.paramName); - if (typeInfo.isRvalueReference) { - std::string valuePrefix = buildPointeeQualPrefix(typeInfo, false); - std::string constRefPrefix = buildPointeeQualPrefix(typeInfo, true); - issue.suggestedType = valuePrefix + baseName + " " + issue.paramName; - issue.suggestedTypeAlt = constRefPrefix + baseName + " &" + issue.paramName; - } else { - issue.suggestedType = buildTypeString(typeInfo, baseName, true, false, issue.paramName); - } + if (valueMayBeWrittenThrough(&Arg, F)) + continue; - out.push_back(std::move(issue)); + ConstParamIssue issue; + issue.funcName = F.getName().str(); + issue.paramName = dbg.name.empty() ? Arg.getName().str() : dbg.name; + issue.line = dbg.line; + issue.column = dbg.column; + issue.pointerConstOnly = + typeInfo.isPointer && typeInfo.pointerConst && !typeInfo.pointeeConst; + issue.isReference = typeInfo.isReference; + issue.isRvalueRef = typeInfo.isRvalueReference; + + std::string baseName = formatDITypeName(typeInfo.pointeeDisplayType); + issue.currentType = buildTypeString(typeInfo, baseName, false, true, issue.paramName); + if (typeInfo.isRvalueReference) + { + std::string valuePrefix = buildPointeeQualPrefix(typeInfo, false); + std::string constRefPrefix = buildPointeeQualPrefix(typeInfo, true); + issue.suggestedType = valuePrefix + baseName + " " + issue.paramName; + issue.suggestedTypeAlt = constRefPrefix + baseName + " &" + issue.paramName; + } + else + { + issue.suggestedType = + buildTypeString(typeInfo, baseName, true, false, issue.paramName); + } + + out.push_back(std::move(issue)); + } } -} -// Forward declaration : essaie de retrouver une constante derrière une Value -static const llvm::ConstantInt* tryGetConstFromValue(const llvm::Value *V, - const llvm::Function &F); + // Forward declaration : essaie de retrouver une constante derrière une Value + static const llvm::ConstantInt* tryGetConstFromValue(const llvm::Value* V, + const llvm::Function& F); -// Analyse intra-fonction pour détecter les allocations dynamiques sur la stack -// (par exemple : int n = read(); char buf[n];) -static void analyzeDynamicAllocasInFunction( - llvm::Function &F, - std::vector &out) -{ - using namespace llvm; + // Analyse intra-fonction pour détecter les allocations dynamiques sur la stack + // (par exemple : int n = read(); char buf[n];) + static void analyzeDynamicAllocasInFunction(llvm::Function& F, + std::vector& out) + { + using namespace llvm; - if (F.isDeclaration()) - return; + if (F.isDeclaration()) + return; - for (BasicBlock &BB : F) { - for (Instruction &I : BB) { - auto *AI = dyn_cast(&I); - if (!AI) - continue; + for (BasicBlock& BB : F) + { + for (Instruction& I : BB) + { + auto* AI = dyn_cast(&I); + if (!AI) + continue; - // Taille d'allocation : on distingue trois cas : - // - constante immédiate -> pas une VLA - // - dérivée d'une constante simple -> pas une VLA (heuristique) - // - vraiment dépendante d'une valeur -> VLA / alloca variable - Value *arraySizeVal = AI->getArraySize(); - - // 1) Cas taille directement constante dans l'IR - if (llvm::isa(arraySizeVal)) - continue; // taille connue à la compilation, OK - - // 2) Heuristique "smart" : essayer de remonter à une constante - // via les stores dans une variable locale (tryGetConstFromValue). - // Exemple typique : - // int n = 6; - // char buf[n]; // en C : VLA, mais ici n est en fait constant - // - // Dans ce cas, on ne veut pas spammer avec un warning VLA : - // on traite ça comme une taille effectivement constante. - if (tryGetConstFromValue(arraySizeVal, F) != nullptr) - continue; + // Taille d'allocation : on distingue trois cas : + // - constante immédiate -> pas une VLA + // - dérivée d'une constante simple -> pas une VLA (heuristique) + // - vraiment dépendante d'une valeur -> VLA / alloca variable + Value* arraySizeVal = AI->getArraySize(); + + // 1) Cas taille directement constante dans l'IR + if (llvm::isa(arraySizeVal)) + continue; // taille connue à la compilation, OK + + // 2) Heuristique "smart" : essayer de remonter à une constante + // via les stores dans une variable locale (tryGetConstFromValue). + // Exemple typique : + // int n = 6; + // char buf[n]; // en C : VLA, mais ici n est en fait constant + // + // Dans ce cas, on ne veut pas spammer avec un warning VLA : + // on traite ça comme une taille effectivement constante. + if (tryGetConstFromValue(arraySizeVal, F) != nullptr) + continue; - // 3) Ici, on considère que c'est une vraie VLA / alloca dynamique - DynamicAllocaIssue issue; - issue.funcName = F.getName().str(); - issue.varName = deriveAllocaName(AI); - if (AI->getAllocatedType()) { - std::string tyStr; - llvm::raw_string_ostream rso(tyStr); - AI->getAllocatedType()->print(rso); - issue.typeName = rso.str(); - } else { - issue.typeName = ""; + // 3) Ici, on considère que c'est une vraie VLA / alloca dynamique + DynamicAllocaIssue issue; + issue.funcName = F.getName().str(); + issue.varName = deriveAllocaName(AI); + if (AI->getAllocatedType()) + { + std::string tyStr; + llvm::raw_string_ostream rso(tyStr); + AI->getAllocatedType()->print(rso); + issue.typeName = rso.str(); + } + else + { + issue.typeName = ""; + } + issue.allocaInst = AI; + out.push_back(std::move(issue)); } - issue.allocaInst = AI; - out.push_back(std::move(issue)); } } -} -// Heuristic: compute an upper bound (if any) from an IntRange -static std::optional -getAllocaUpperBoundBytes(const llvm::AllocaInst *AI, - const llvm::DataLayout &DL, - const std::map &ranges) -{ - using namespace llvm; + // Heuristic: compute an upper bound (if any) from an IntRange + static std::optional + getAllocaUpperBoundBytes(const llvm::AllocaInst* AI, const llvm::DataLayout& DL, + const std::map& ranges) + { + using namespace llvm; - const Value *sizeVal = AI->getArraySize(); - auto findRange = [&ranges](const Value *V) -> const IntRange* { - auto it = ranges.find(V); - if (it != ranges.end()) - return &it->second; - return nullptr; - }; + const Value* sizeVal = AI->getArraySize(); + auto findRange = [&ranges](const Value* V) -> const IntRange* + { + auto it = ranges.find(V); + if (it != ranges.end()) + return &it->second; + return nullptr; + }; + + const IntRange* r = findRange(sizeVal); + if (!r) + { + if (auto* LI = dyn_cast(sizeVal)) + { + const Value* ptr = LI->getPointerOperand(); + r = findRange(ptr); + } + } - const IntRange *r = findRange(sizeVal); - if (!r) { - if (auto *LI = dyn_cast(sizeVal)) { - const Value *ptr = LI->getPointerOperand(); - r = findRange(ptr); + if (r && r->hasUpper && r->upper > 0) + { + StackSize elemSize = DL.getTypeAllocSize(AI->getAllocatedType()); + return static_cast(r->upper) * elemSize; } - } - if (r && r->hasUpper && r->upper > 0) { - StackSize elemSize = DL.getTypeAllocSize(AI->getAllocatedType()); - return static_cast(r->upper) * elemSize; + return std::nullopt; } - return std::nullopt; -} + // Analyze alloca/VLA uses whose size depends on a runtime value. + static void analyzeAllocaUsageInFunction(llvm::Function& F, const llvm::DataLayout& DL, + bool isRecursive, bool isInfiniteRecursive, + std::vector& out) + { + using namespace llvm; -// Analyze alloca/VLA uses whose size depends on a runtime value. -static void analyzeAllocaUsageInFunction( - llvm::Function &F, - const llvm::DataLayout &DL, - bool isRecursive, - bool isInfiniteRecursive, - std::vector &out) -{ - using namespace llvm; + if (F.isDeclaration()) + return; - if (F.isDeclaration()) - return; + auto ranges = computeIntRangesFromICmps(F); - auto ranges = computeIntRangesFromICmps(F); + for (BasicBlock& BB : F) + { + for (Instruction& I : BB) + { + auto* AI = dyn_cast(&I); + if (!AI) + continue; - for (BasicBlock &BB : F) { - for (Instruction &I : BB) { - auto *AI = dyn_cast(&I); - if (!AI) - continue; + // Only consider dynamic allocas: alloca(T, size) or VLA. + if (!AI->isArrayAllocation()) + continue; - // Only consider dynamic allocas: alloca(T, size) or VLA. - if (!AI->isArrayAllocation()) - continue; + AllocaUsageIssue issue; + issue.funcName = F.getName().str(); + issue.varName = deriveAllocaName(AI); + issue.allocaInst = AI; + issue.userControlled = isValueUserControlled(AI->getArraySize(), F); + issue.isRecursive = isRecursive; + issue.isInfiniteRecursive = isInfiniteRecursive; - AllocaUsageIssue issue; - issue.funcName = F.getName().str(); - issue.varName = deriveAllocaName(AI); - issue.allocaInst = AI; - issue.userControlled = isValueUserControlled(AI->getArraySize(), F); - issue.isRecursive = isRecursive; - issue.isInfiniteRecursive = isInfiniteRecursive; + StackSize elemSize = DL.getTypeAllocSize(AI->getAllocatedType()); + const Value* arraySizeVal = AI->getArraySize(); - StackSize elemSize = DL.getTypeAllocSize(AI->getAllocatedType()); - const Value *arraySizeVal = AI->getArraySize(); + if (auto* C = dyn_cast(arraySizeVal)) + { + issue.sizeIsConst = true; + issue.sizeBytes = C->getZExtValue() * elemSize; + } + else if (auto* C = tryGetConstFromValue(arraySizeVal, F)) + { + issue.sizeIsConst = true; + issue.sizeBytes = C->getZExtValue() * elemSize; + } + else if (auto upper = getAllocaUpperBoundBytes(AI, DL, ranges)) + { + issue.hasUpperBound = true; + issue.upperBoundBytes = *upper; + } - if (auto *C = dyn_cast(arraySizeVal)) { - issue.sizeIsConst = true; - issue.sizeBytes = C->getZExtValue() * elemSize; - } else if (auto *C = tryGetConstFromValue(arraySizeVal, F)) { - issue.sizeIsConst = true; - issue.sizeBytes = C->getZExtValue() * elemSize; - } else if (auto upper = getAllocaUpperBoundBytes(AI, DL, ranges)) { - issue.hasUpperBound = true; - issue.upperBoundBytes = *upper; + out.push_back(std::move(issue)); } + } + } + + // Forward declaration pour la résolution d'alloca de tableau depuis un pointeur + static const llvm::AllocaInst* resolveArrayAllocaFromPointer(const llvm::Value* V, + llvm::Function& F, + std::vector& path); + + // Analyse intra-fonction pour détecter des accès potentiellement hors bornes + // sur des buffers alloués sur la stack (alloca). + static void analyzeStackBufferOverflowsInFunction(llvm::Function& F, + std::vector& out) + { + using namespace llvm; + + auto ranges = computeIntRangesFromICmps(F); + + for (BasicBlock& BB : F) + { + for (Instruction& I : BB) + { + auto* GEP = dyn_cast(&I); + if (!GEP) + continue; + + // 1) Trouver la base du pointeur (test, &test[0], ptr, etc.) + const Value* basePtr = GEP->getPointerOperand(); + std::vector aliasPath; + const AllocaInst* AI = resolveArrayAllocaFromPointer(basePtr, F, aliasPath); + if (!AI) + continue; - out.push_back(std::move(issue)); - } - } -} - -// Forward declaration pour la résolution d'alloca de tableau depuis un pointeur -static const llvm::AllocaInst* resolveArrayAllocaFromPointer(const llvm::Value *V, - llvm::Function &F, - std::vector &path); - -// Analyse intra-fonction pour détecter des accès potentiellement hors bornes -// sur des buffers alloués sur la stack (alloca). -static void analyzeStackBufferOverflowsInFunction( - llvm::Function &F, - std::vector &out) -{ - using namespace llvm; + // 2) Déterminer la taille logique du tableau ciblé et récupérer l'index + // On essaie d'abord de la déduire du type traversé par la GEP + // (cas struct S { char buf[10]; }; s.buf[i]) puis on retombe + // sur la taille de l'alloca pour les cas plus simples (char buf[10]). + StackSize arraySize = 0; + Value* idxVal = nullptr; - auto ranges = computeIntRangesFromICmps(F); + Type* srcElemTy = GEP->getSourceElementType(); - for (BasicBlock &BB : F) { - for (Instruction &I : BB) { - auto *GEP = dyn_cast(&I); - if (!GEP) - continue; + if (auto* arrTy = dyn_cast(srcElemTy)) + { + // Cas direct : alloca [N x T]; GEP indices [0, i] + if (GEP->getNumIndices() < 2) + continue; + auto idxIt = GEP->idx_begin(); + ++idxIt; // saute le premier indice (souvent 0) + idxVal = idxIt->get(); + arraySize = arrTy->getNumElements(); + } + else if (auto* ST = dyn_cast(srcElemTy)) + { + // Cas struct avec champ tableau: + // %ptr = getelementptr inbounds %struct.S, %struct.S* %s, + // i32 0, i32 , i64 %i + // + // On attend donc au moins 3 indices: [0, field, i] + if (GEP->getNumIndices() >= 3) + { + auto idxIt = GEP->idx_begin(); - // 1) Trouver la base du pointeur (test, &test[0], ptr, etc.) - const Value *basePtr = GEP->getPointerOperand(); - std::vector aliasPath; - const AllocaInst *AI = resolveArrayAllocaFromPointer(basePtr, F, aliasPath); - if (!AI) - continue; + // premier indice (souvent 0) + auto* idx0 = dyn_cast(idxIt->get()); + ++idxIt; + // second indice: index de champ dans la struct + auto* fieldIdxC = dyn_cast(idxIt->get()); + ++idxIt; - // 2) Déterminer la taille logique du tableau ciblé et récupérer l'index - // On essaie d'abord de la déduire du type traversé par la GEP - // (cas struct S { char buf[10]; }; s.buf[i]) puis on retombe - // sur la taille de l'alloca pour les cas plus simples (char buf[10]). - StackSize arraySize = 0; - Value *idxVal = nullptr; + if (idx0 && fieldIdxC) + { + unsigned fieldIdx = static_cast(fieldIdxC->getZExtValue()); + if (fieldIdx < ST->getNumElements()) + { + Type* fieldTy = ST->getElementType(fieldIdx); + if (auto* fieldArrTy = dyn_cast(fieldTy)) + { + arraySize = fieldArrTy->getNumElements(); + // Troisième indice = index dans le tableau interne + idxVal = idxIt->get(); + } + } + } + } + } - Type *srcElemTy = GEP->getSourceElementType(); + // Si on n'a pas réussi à déduire une taille via la GEP, + // on retombe sur la taille dérivée de l'alloca (cas char buf[10]; ptr = buf; ptr[i]). + if (arraySize == 0 || !idxVal) + { + auto maybeCount = getAllocaElementCount(const_cast(AI)); + if (!maybeCount) + continue; + arraySize = *maybeCount; + if (arraySize == 0) + continue; - if (auto *arrTy = dyn_cast(srcElemTy)) { - // Cas direct : alloca [N x T]; GEP indices [0, i] - if (GEP->getNumIndices() < 2) - continue; - auto idxIt = GEP->idx_begin(); - ++idxIt; // saute le premier indice (souvent 0) - idxVal = idxIt->get(); - arraySize = arrTy->getNumElements(); - } else if (auto *ST = dyn_cast(srcElemTy)) { - // Cas struct avec champ tableau: - // %ptr = getelementptr inbounds %struct.S, %struct.S* %s, - // i32 0, i32 , i64 %i - // - // On attend donc au moins 3 indices: [0, field, i] - if (GEP->getNumIndices() >= 3) { + // Pour ces cas-là, on considère le premier indice comme l'index logique. + if (GEP->getNumIndices() < 1) + continue; auto idxIt = GEP->idx_begin(); + idxVal = idxIt->get(); + } + + std::string varName = + AI->hasName() ? AI->getName().str() : std::string(""); + + // "baseIdxVal" = variable de boucle "i" sans les casts (sext/zext...) + Value* baseIdxVal = idxVal; + while (auto* cast = dyn_cast(baseIdxVal)) + { + baseIdxVal = cast->getOperand(0); + } + + // 4) Cas index constant : test[11] + if (auto* CIdx = dyn_cast(idxVal)) + { + auto idxValue = CIdx->getSExtValue(); + if (idxValue < 0 || static_cast(idxValue) >= arraySize) + { - // premier indice (souvent 0) - auto *idx0 = dyn_cast(idxIt->get()); - ++idxIt; - // second indice: index de champ dans la struct - auto *fieldIdxC = dyn_cast(idxIt->get()); - ++idxIt; - - if (idx0 && fieldIdxC) { - unsigned fieldIdx = - static_cast(fieldIdxC->getZExtValue()); - if (fieldIdx < ST->getNumElements()) { - Type *fieldTy = ST->getElementType(fieldIdx); - if (auto *fieldArrTy = dyn_cast(fieldTy)) { - arraySize = fieldArrTy->getNumElements(); - // Troisième indice = index dans le tableau interne - idxVal = idxIt->get(); + for (User* GU : GEP->users()) + { + if (auto* S = dyn_cast(GU)) + { + StackBufferOverflow report; + report.funcName = F.getName().str(); + report.varName = varName; + report.arraySize = arraySize; + report.indexOrUpperBound = static_cast(idxValue); + report.isWrite = true; + report.indexIsConstant = true; + report.inst = S; + report.aliasPathVec = aliasPath; + if (!aliasPath.empty()) + { + std::reverse(aliasPath.begin(), aliasPath.end()); + std::string chain; + for (size_t i = 0; i < aliasPath.size(); ++i) + { + chain += aliasPath[i]; + if (i + 1 < aliasPath.size()) + chain += " -> "; + } + report.aliasPath = chain; + } + out.push_back(std::move(report)); + } + else if (auto* L = dyn_cast(GU)) + { + StackBufferOverflow report; + report.funcName = F.getName().str(); + report.varName = varName; + report.arraySize = arraySize; + report.indexOrUpperBound = static_cast(idxValue); + report.isWrite = false; + report.indexIsConstant = true; + report.inst = L; + report.aliasPathVec = aliasPath; + if (!aliasPath.empty()) + { + std::reverse(aliasPath.begin(), aliasPath.end()); + std::string chain; + for (size_t i = 0; i < aliasPath.size(); ++i) + { + chain += aliasPath[i]; + if (i + 1 < aliasPath.size()) + chain += " -> "; + } + report.aliasPath = chain; + } + out.push_back(std::move(report)); } } } + continue; } - } - // Si on n'a pas réussi à déduire une taille via la GEP, - // on retombe sur la taille dérivée de l'alloca (cas char buf[10]; ptr = buf; ptr[i]). - if (arraySize == 0 || !idxVal) { - auto maybeCount = getAllocaElementCount(const_cast(AI)); - if (!maybeCount) - continue; - arraySize = *maybeCount; - if (arraySize == 0) - continue; + // 5) Cas index variable : test[i] / ptr[i] + // On regarde si on a un intervalle pour la valeur de base (i, pas le cast) + const Value* key = baseIdxVal; + + // Si l'index vient d'un load (pattern -O0 : load i, icmp, load i, gep), + // on utilise le pointeur sous-jacent comme clé (l'alloca de i). + if (auto* LI = dyn_cast(baseIdxVal)) + { + key = LI->getPointerOperand(); + } - // Pour ces cas-là, on considère le premier indice comme l'index logique. - if (GEP->getNumIndices() < 1) + auto itRange = ranges.find(key); + if (itRange == ranges.end()) + { + // pas de borne connue => on ne dit rien ici continue; - auto idxIt = GEP->idx_begin(); - idxVal = idxIt->get(); - } + } - std::string varName = AI->hasName() ? AI->getName().str() - : std::string(""); + const IntRange& R = itRange->second; - // "baseIdxVal" = variable de boucle "i" sans les casts (sext/zext...) - Value *baseIdxVal = idxVal; - while (auto *cast = dyn_cast(baseIdxVal)) { - baseIdxVal = cast->getOperand(0); - } + // 5.a) Borne supérieure hors bornes: UB >= arraySize + if (R.hasUpper && R.upper >= 0 && static_cast(R.upper) >= arraySize) + { - // 4) Cas index constant : test[11] - if (auto *CIdx = dyn_cast(idxVal)) { - auto idxValue = CIdx->getSExtValue(); - if (idxValue < 0 || - static_cast(idxValue) >= arraySize) { + StackSize ub = static_cast(R.upper); - for (User *GU : GEP->users()) { - if (auto *S = dyn_cast(GU)) { + for (User* GU : GEP->users()) + { + if (auto* S = dyn_cast(GU)) + { StackBufferOverflow report; - report.funcName = F.getName().str(); - report.varName = varName; - report.arraySize = arraySize; - report.indexOrUpperBound = static_cast(idxValue); - report.isWrite = true; - report.indexIsConstant = true; - report.inst = S; - report.aliasPathVec = aliasPath; - if (!aliasPath.empty()) { + report.funcName = F.getName().str(); + report.varName = varName; + report.arraySize = arraySize; + report.indexOrUpperBound = ub; + report.isWrite = true; + report.indexIsConstant = false; + report.inst = S; + report.aliasPathVec = aliasPath; + if (!aliasPath.empty()) + { std::reverse(aliasPath.begin(), aliasPath.end()); std::string chain; - for (size_t i = 0; i < aliasPath.size(); ++i) { + for (size_t i = 0; i < aliasPath.size(); ++i) + { chain += aliasPath[i]; if (i + 1 < aliasPath.size()) chain += " -> "; @@ -2315,20 +2730,24 @@ static void analyzeStackBufferOverflowsInFunction( report.aliasPath = chain; } out.push_back(std::move(report)); - } else if (auto *L = dyn_cast(GU)) { + } + else if (auto* L = dyn_cast(GU)) + { StackBufferOverflow report; - report.funcName = F.getName().str(); - report.varName = varName; - report.arraySize = arraySize; - report.indexOrUpperBound = static_cast(idxValue); - report.isWrite = false; - report.indexIsConstant = true; - report.inst = L; - report.aliasPathVec = aliasPath; - if (!aliasPath.empty()) { + report.funcName = F.getName().str(); + report.varName = varName; + report.arraySize = arraySize; + report.indexOrUpperBound = ub; + report.isWrite = false; + report.indexIsConstant = false; + report.inst = L; + report.aliasPathVec = aliasPath; + if (!aliasPath.empty()) + { std::reverse(aliasPath.begin(), aliasPath.end()); std::string chain; - for (size_t i = 0; i < aliasPath.size(); ++i) { + for (size_t i = 0; i < aliasPath.size(); ++i) + { chain += aliasPath[i]; if (i + 1 < aliasPath.size()) chain += " -> "; @@ -2339,984 +2758,1018 @@ static void analyzeStackBufferOverflowsInFunction( } } } - continue; - } - - // 5) Cas index variable : test[i] / ptr[i] - // On regarde si on a un intervalle pour la valeur de base (i, pas le cast) - const Value *key = baseIdxVal; - - // Si l'index vient d'un load (pattern -O0 : load i, icmp, load i, gep), - // on utilise le pointeur sous-jacent comme clé (l'alloca de i). - if (auto *LI = dyn_cast(baseIdxVal)) { - key = LI->getPointerOperand(); - } - - auto itRange = ranges.find(key); - if (itRange == ranges.end()) { - // pas de borne connue => on ne dit rien ici - continue; - } - - const IntRange &R = itRange->second; - - // 5.a) Borne supérieure hors bornes: UB >= arraySize - if (R.hasUpper && R.upper >= 0 && - static_cast(R.upper) >= arraySize) { - - StackSize ub = static_cast(R.upper); - - for (User *GU : GEP->users()) { - if (auto *S = dyn_cast(GU)) { - StackBufferOverflow report; - report.funcName = F.getName().str(); - report.varName = varName; - report.arraySize = arraySize; - report.indexOrUpperBound = ub; - report.isWrite = true; - report.indexIsConstant = false; - report.inst = S; - report.aliasPathVec = aliasPath; - if (!aliasPath.empty()) { - std::reverse(aliasPath.begin(), aliasPath.end()); - std::string chain; - for (size_t i = 0; i < aliasPath.size(); ++i) { - chain += aliasPath[i]; - if (i + 1 < aliasPath.size()) - chain += " -> "; - } - report.aliasPath = chain; - } - out.push_back(std::move(report)); - } else if (auto *L = dyn_cast(GU)) { - StackBufferOverflow report; - report.funcName = F.getName().str(); - report.varName = varName; - report.arraySize = arraySize; - report.indexOrUpperBound = ub; - report.isWrite = false; - report.indexIsConstant = false; - report.inst = L; - report.aliasPathVec = aliasPath; - if (!aliasPath.empty()) { - std::reverse(aliasPath.begin(), aliasPath.end()); - std::string chain; - for (size_t i = 0; i < aliasPath.size(); ++i) { - chain += aliasPath[i]; - if (i + 1 < aliasPath.size()) - chain += " -> "; - } - report.aliasPath = chain; - } - out.push_back(std::move(report)); - } - } - } - // 5.b) Borne inférieure négative: LB < 0 => index potentiellement négatif - if (R.hasLower && R.lower < 0) { - for (User *GU : GEP->users()) { - if (auto *S = dyn_cast(GU)) { - StackBufferOverflow report; - report.funcName = F.getName().str(); - report.varName = varName; - report.arraySize = arraySize; - report.isWrite = true; - report.indexIsConstant = false; - report.inst = S; - report.isLowerBoundViolation = true; - report.lowerBound = R.lower; - report.aliasPathVec = aliasPath; - if (!aliasPath.empty()) { - std::reverse(aliasPath.begin(), aliasPath.end()); - std::string chain; - for (size_t i = 0; i < aliasPath.size(); ++i) { - chain += aliasPath[i]; - if (i + 1 < aliasPath.size()) - chain += " -> "; + // 5.b) Borne inférieure négative: LB < 0 => index potentiellement négatif + if (R.hasLower && R.lower < 0) + { + for (User* GU : GEP->users()) + { + if (auto* S = dyn_cast(GU)) + { + StackBufferOverflow report; + report.funcName = F.getName().str(); + report.varName = varName; + report.arraySize = arraySize; + report.isWrite = true; + report.indexIsConstant = false; + report.inst = S; + report.isLowerBoundViolation = true; + report.lowerBound = R.lower; + report.aliasPathVec = aliasPath; + if (!aliasPath.empty()) + { + std::reverse(aliasPath.begin(), aliasPath.end()); + std::string chain; + for (size_t i = 0; i < aliasPath.size(); ++i) + { + chain += aliasPath[i]; + if (i + 1 < aliasPath.size()) + chain += " -> "; + } + report.aliasPath = chain; } - report.aliasPath = chain; + out.push_back(std::move(report)); } - out.push_back(std::move(report)); - } else if (auto *L = dyn_cast(GU)) { - StackBufferOverflow report; - report.funcName = F.getName().str(); - report.varName = varName; - report.arraySize = arraySize; - report.isWrite = false; - report.indexIsConstant = false; - report.inst = L; - report.isLowerBoundViolation = true; - report.lowerBound = R.lower; - report.aliasPathVec = aliasPath; - if (!aliasPath.empty()) { - std::reverse(aliasPath.begin(), aliasPath.end()); - std::string chain; - for (size_t i = 0; i < aliasPath.size(); ++i) { - chain += aliasPath[i]; - if (i + 1 < aliasPath.size()) - chain += " -> "; + else if (auto* L = dyn_cast(GU)) + { + StackBufferOverflow report; + report.funcName = F.getName().str(); + report.varName = varName; + report.arraySize = arraySize; + report.isWrite = false; + report.indexIsConstant = false; + report.inst = L; + report.isLowerBoundViolation = true; + report.lowerBound = R.lower; + report.aliasPathVec = aliasPath; + if (!aliasPath.empty()) + { + std::reverse(aliasPath.begin(), aliasPath.end()); + std::string chain; + for (size_t i = 0; i < aliasPath.size(); ++i) + { + chain += aliasPath[i]; + if (i + 1 < aliasPath.size()) + chain += " -> "; + } + report.aliasPath = chain; } - report.aliasPath = chain; + out.push_back(std::move(report)); } - out.push_back(std::move(report)); } } + // Si R.hasUpper && R.upper < arraySize et (pas de LB problématique), + // on considère l'accès comme probablement sûr. } - // Si R.hasUpper && R.upper < arraySize et (pas de LB problématique), - // on considère l'accès comme probablement sûr. } } -} -// ============================================================================ -// Helpers -// ============================================================================ + // ============================================================================ + // Helpers + // ============================================================================ -static void analyzeMemIntrinsicOverflowsInFunction( - llvm::Function &F, - const llvm::DataLayout &DL, - std::vector &out) -{ - using namespace llvm; + static void analyzeMemIntrinsicOverflowsInFunction(llvm::Function& F, + const llvm::DataLayout& DL, + std::vector& out) + { + using namespace llvm; - if (F.isDeclaration()) - return; + if (F.isDeclaration()) + return; - for (BasicBlock &BB : F) { - for (Instruction &I : BB) { + for (BasicBlock& BB : F) + { + for (Instruction& I : BB) + { - // On s'intéresse uniquement aux appels (intrinsics ou libc) - auto *CB = dyn_cast(&I); - if (!CB) - continue; + // On s'intéresse uniquement aux appels (intrinsics ou libc) + auto* CB = dyn_cast(&I); + if (!CB) + continue; - Function *callee = CB->getCalledFunction(); - if (!callee) - continue; + Function* callee = CB->getCalledFunction(); + if (!callee) + continue; - StringRef name = callee->getName(); + StringRef name = callee->getName(); - enum class MemKind { None, MemCpy, MemSet, MemMove }; - MemKind kind = MemKind::None; + enum class MemKind + { + None, + MemCpy, + MemSet, + MemMove + }; + MemKind kind = MemKind::None; - // 1) Cas intrinsics LLVM: llvm.memcpy.*, llvm.memset.*, llvm.memmove.* - if (auto *II = dyn_cast(CB)) { - switch (II->getIntrinsicID()) { - case Intrinsic::memcpy: kind = MemKind::MemCpy; break; - case Intrinsic::memset: kind = MemKind::MemSet; break; - case Intrinsic::memmove: kind = MemKind::MemMove; break; - default: break; + // 1) Cas intrinsics LLVM: llvm.memcpy.*, llvm.memset.*, llvm.memmove.* + if (auto* II = dyn_cast(CB)) + { + switch (II->getIntrinsicID()) + { + case Intrinsic::memcpy: + kind = MemKind::MemCpy; + break; + case Intrinsic::memset: + kind = MemKind::MemSet; + break; + case Intrinsic::memmove: + kind = MemKind::MemMove; + break; + default: + break; + } } - } - // 2) Cas appels libc classiques ou symboles similaires - if (kind == MemKind::None) { - if (name == "memcpy" || name.contains("memcpy")) - kind = MemKind::MemCpy; - else if (name == "memset" || name.contains("memset")) - kind = MemKind::MemSet; - else if (name == "memmove" || name.contains("memmove")) - kind = MemKind::MemMove; - } + // 2) Cas appels libc classiques ou symboles similaires + if (kind == MemKind::None) + { + if (name == "memcpy" || name.contains("memcpy")) + kind = MemKind::MemCpy; + else if (name == "memset" || name.contains("memset")) + kind = MemKind::MemSet; + else if (name == "memmove" || name.contains("memmove")) + kind = MemKind::MemMove; + } - if (kind == MemKind::None) - continue; + if (kind == MemKind::None) + continue; - // On attend au moins 3 arguments: dest, src/val, len - if (CB->arg_size() < 3) - continue; + // On attend au moins 3 arguments: dest, src/val, len + if (CB->arg_size() < 3) + continue; - Value *dest = CB->getArgOperand(0); + Value* dest = CB->getArgOperand(0); - // Résolution heuristique : on enlève les casts/GEPI de surface - // et on remonte jusqu'à une alloca éventuelle. - const Value *cur = dest->stripPointerCasts(); - if (auto *GEP = dyn_cast(cur)) { - cur = GEP->getPointerOperand(); - } - const AllocaInst *AI = dyn_cast(cur); - if (!AI) - continue; + // Résolution heuristique : on enlève les casts/GEPI de surface + // et on remonte jusqu'à une alloca éventuelle. + const Value* cur = dest->stripPointerCasts(); + if (auto* GEP = dyn_cast(cur)) + { + cur = GEP->getPointerOperand(); + } + const AllocaInst* AI = dyn_cast(cur); + if (!AI) + continue; - auto maybeSize = getAllocaTotalSizeBytes(AI, DL); - if (!maybeSize) - continue; - StackSize destBytes = *maybeSize; + auto maybeSize = getAllocaTotalSizeBytes(AI, DL); + if (!maybeSize) + continue; + StackSize destBytes = *maybeSize; - Value *lenV = CB->getArgOperand(2); - auto *lenC = dyn_cast(lenV); - if (!lenC) - continue; // pour l'instant, on ne traite que les tailles constantes + Value* lenV = CB->getArgOperand(2); + auto* lenC = dyn_cast(lenV); + if (!lenC) + continue; // pour l'instant, on ne traite que les tailles constantes - uint64_t len = lenC->getZExtValue(); - if (len <= destBytes) - continue; // pas de débordement évident + uint64_t len = lenC->getZExtValue(); + if (len <= destBytes) + continue; // pas de débordement évident - MemIntrinsicIssue issue; - issue.funcName = F.getName().str(); - issue.varName = AI->hasName() ? AI->getName().str() : std::string(""); - issue.destSizeBytes = destBytes; - issue.lengthBytes = len; - issue.inst = &I; + MemIntrinsicIssue issue; + issue.funcName = F.getName().str(); + issue.varName = AI->hasName() ? AI->getName().str() : std::string(""); + issue.destSizeBytes = destBytes; + issue.lengthBytes = len; + issue.inst = &I; - switch (kind) { - case MemKind::MemCpy: issue.intrinsicName = "memcpy"; break; - case MemKind::MemSet: issue.intrinsicName = "memset"; break; - case MemKind::MemMove: issue.intrinsicName = "memmove"; break; - default: break; - } + switch (kind) + { + case MemKind::MemCpy: + issue.intrinsicName = "memcpy"; + break; + case MemKind::MemSet: + issue.intrinsicName = "memset"; + break; + case MemKind::MemMove: + issue.intrinsicName = "memmove"; + break; + default: + break; + } - out.push_back(std::move(issue)); + out.push_back(std::move(issue)); + } } } -} -// Appelle-t-on une autre fonction que soi-même ? -static bool hasNonSelfCall(const llvm::Function &F) -{ - const llvm::Function *Self = &F; + // Appelle-t-on une autre fonction que soi-même ? + static bool hasNonSelfCall(const llvm::Function& F) + { + const llvm::Function* Self = &F; - for (const llvm::BasicBlock &BB : F) { - for (const llvm::Instruction &I : BB) { + for (const llvm::BasicBlock& BB : F) + { + for (const llvm::Instruction& I : BB) + { - const llvm::Function *Callee = nullptr; + const llvm::Function* Callee = nullptr; - if (auto *CI = llvm::dyn_cast(&I)) { - Callee = CI->getCalledFunction(); - } else if (auto *II = llvm::dyn_cast(&I)) { - Callee = II->getCalledFunction(); - } + if (auto* CI = llvm::dyn_cast(&I)) + { + Callee = CI->getCalledFunction(); + } + else if (auto* II = llvm::dyn_cast(&I)) + { + Callee = II->getCalledFunction(); + } - if (Callee && !Callee->isDeclaration() && Callee != Self) { - return true; // appel vers une autre fonction + if (Callee && !Callee->isDeclaration() && Callee != Self) + { + return true; // appel vers une autre fonction + } } } + return false; } - return false; -} -// ============================================================================ -// Analyse locale de la stack (deux variantes) -// ============================================================================ + // ============================================================================ + // Analyse locale de la stack (deux variantes) + // ============================================================================ -static LocalStackInfo computeLocalStackBase(llvm::Function &F, const llvm::DataLayout &DL) -{ - LocalStackInfo info; + static LocalStackInfo computeLocalStackBase(llvm::Function& F, const llvm::DataLayout& DL) + { + LocalStackInfo info; - for (llvm::BasicBlock &BB : F) { - for (llvm::Instruction &I : BB) { - auto *alloca = llvm::dyn_cast(&I); - if (!alloca) - continue; + for (llvm::BasicBlock& BB : F) + { + for (llvm::Instruction& I : BB) + { + auto* alloca = llvm::dyn_cast(&I); + if (!alloca) + continue; - llvm::Type *ty = alloca->getAllocatedType(); - StackSize count = 1; + llvm::Type* ty = alloca->getAllocatedType(); + StackSize count = 1; - if (auto *CI = llvm::dyn_cast(alloca->getArraySize())) { - count = CI->getZExtValue(); - } else if (auto *C = tryGetConstFromValue(alloca->getArraySize(), F)) { - count = C->getZExtValue(); - } else { - info.hasDynamicAlloca = true; - info.unknown = true; - continue; - } + if (auto* CI = llvm::dyn_cast(alloca->getArraySize())) + { + count = CI->getZExtValue(); + } + else if (auto* C = tryGetConstFromValue(alloca->getArraySize(), F)) + { + count = C->getZExtValue(); + } + else + { + info.hasDynamicAlloca = true; + info.unknown = true; + continue; + } - StackSize size = DL.getTypeAllocSize(ty) * count; - info.bytes += size; + StackSize size = DL.getTypeAllocSize(ty) * count; + info.bytes += size; + } } + + return info; } - return info; -} + // Mode IR pur : somme des allocas, alignée + static LocalStackInfo computeLocalStackIR(llvm::Function& F, const llvm::DataLayout& DL) + { + LocalStackInfo info = computeLocalStackBase(F, DL); -// Mode IR pur : somme des allocas, alignée -static LocalStackInfo computeLocalStackIR(llvm::Function &F, const llvm::DataLayout &DL) -{ - LocalStackInfo info = computeLocalStackBase(F, DL); + if (info.bytes == 0) + return info; - if (info.bytes == 0) - return info; + llvm::MaybeAlign MA = DL.getStackAlignment(); + unsigned stackAlign = MA ? MA->value() : 1u; - llvm::MaybeAlign MA = DL.getStackAlignment(); - unsigned stackAlign = MA ? MA->value() : 1u; + if (stackAlign > 1) + info.bytes = llvm::alignTo(info.bytes, stackAlign); - if (stackAlign > 1) - info.bytes = llvm::alignTo(info.bytes, stackAlign); + return info; + } - return info; -} + // Mode ABI heuristique : frame minimale + overhead sur calls + static LocalStackInfo computeLocalStackABI(llvm::Function& F, const llvm::DataLayout& DL) + { + LocalStackInfo info = computeLocalStackBase(F, DL); -// Mode ABI heuristique : frame minimale + overhead sur calls -static LocalStackInfo computeLocalStackABI(llvm::Function &F, const llvm::DataLayout &DL) -{ - LocalStackInfo info = computeLocalStackBase(F, DL); + llvm::MaybeAlign MA = DL.getStackAlignment(); + unsigned stackAlign = MA ? MA->value() : 1u; // 16 sur beaucoup de cibles - llvm::MaybeAlign MA = DL.getStackAlignment(); - unsigned stackAlign = MA ? MA->value() : 1u; // 16 sur beaucoup de cibles + StackSize frameSize = info.bytes; - StackSize frameSize = info.bytes; + if (stackAlign > 1) + frameSize = llvm::alignTo(frameSize, stackAlign); - if (stackAlign > 1) - frameSize = llvm::alignTo(frameSize, stackAlign); + if (!F.isDeclaration() && stackAlign > 1 && frameSize < stackAlign) + { + frameSize = stackAlign; + } - if (!F.isDeclaration() && stackAlign > 1 && frameSize < stackAlign) { - frameSize = stackAlign; - } + if (stackAlign > 1 && hasNonSelfCall(F)) + { + frameSize = llvm::alignTo(frameSize + stackAlign, stackAlign); + } - if (stackAlign > 1 && hasNonSelfCall(F)) { - frameSize = llvm::alignTo(frameSize + stackAlign, stackAlign); + info.bytes = frameSize; + return info; } - info.bytes = frameSize; - return info; -} - -// Wrapper qui sélectionne le mode -static LocalStackInfo computeLocalStack(llvm::Function &F, - const llvm::DataLayout &DL, - AnalysisMode mode) -{ - switch (mode) + // Wrapper qui sélectionne le mode + static LocalStackInfo computeLocalStack(llvm::Function& F, const llvm::DataLayout& DL, + AnalysisMode mode) { + switch (mode) + { case AnalysisMode::IR: return computeLocalStackIR(F, DL); case AnalysisMode::ABI: return computeLocalStackABI(F, DL); + } + return {}; } - return {}; -} -// Threshold heuristic: consider an alloca "too large" if it consumes at least -// 1/8 of the configured stack budget (8 MiB default), with a 64 KiB floor for -// small limits. -static StackSize computeAllocaLargeThreshold(const AnalysisConfig &config) -{ - const StackSize defaultStack = 8ull * 1024ull * 1024ull; - const StackSize minThreshold = 64ull * 1024ull; // 64 KiB - - StackSize base = config.stackLimit ? config.stackLimit : defaultStack; - StackSize derived = base / 8; + // Threshold heuristic: consider an alloca "too large" if it consumes at least + // 1/8 of the configured stack budget (8 MiB default), with a 64 KiB floor for + // small limits. + static StackSize computeAllocaLargeThreshold(const AnalysisConfig& config) + { + const StackSize defaultStack = 8ull * 1024ull * 1024ull; + const StackSize minThreshold = 64ull * 1024ull; // 64 KiB - if (derived < minThreshold) - derived = minThreshold; + StackSize base = config.stackLimit ? config.stackLimit : defaultStack; + StackSize derived = base / 8; - return derived; -} + if (derived < minThreshold) + derived = minThreshold; -// ============================================================================ -// Construction du graphe d'appels (CallInst / InvokeInst) -// ============================================================================ + return derived; + } -static CallGraph buildCallGraph(llvm::Module &M) -{ - CallGraph CG; + // ============================================================================ + // Construction du graphe d'appels (CallInst / InvokeInst) + // ============================================================================ - for (llvm::Function &F : M) + static CallGraph buildCallGraph(llvm::Module& M) { - if (F.isDeclaration()) - continue; + CallGraph CG; - auto &vec = CG[&F]; - - for (llvm::BasicBlock &BB : F) + for (llvm::Function& F : M) { - for (llvm::Instruction &I : BB) - { + if (F.isDeclaration()) + continue; - const llvm::Function *Callee = nullptr; + auto& vec = CG[&F]; - if (auto *CI = llvm::dyn_cast(&I)) - { - Callee = CI->getCalledFunction(); - } - else if (auto *II = llvm::dyn_cast(&I)) + for (llvm::BasicBlock& BB : F) + { + for (llvm::Instruction& I : BB) { - Callee = II->getCalledFunction(); - } - if (Callee && !Callee->isDeclaration()) - { - vec.push_back(Callee); + const llvm::Function* Callee = nullptr; + + if (auto* CI = llvm::dyn_cast(&I)) + { + Callee = CI->getCalledFunction(); + } + else if (auto* II = llvm::dyn_cast(&I)) + { + Callee = II->getCalledFunction(); + } + + if (Callee && !Callee->isDeclaration()) + { + vec.push_back(Callee); + } } } } - } - return CG; -} + return CG; + } -// ============================================================================ -// Propagation de la stack + détection de cycles -// ============================================================================ + // ============================================================================ + // Propagation de la stack + détection de cycles + // ============================================================================ -static StackEstimate dfsComputeStack( - const llvm::Function *F, - const CallGraph &CG, - const std::map &LocalStack, - std::map &State, - InternalAnalysisState &Res -) -{ - auto itState = State.find(F); - if (itState != State.end()) { - if (itState->second == Visiting) { - // Cycle détecté : on marque tous les noeuds actuellement en "Visiting" - for (auto &p : State) { - if (p.second == Visiting) { - Res.RecursiveFuncs.insert(p.first); + static StackEstimate + dfsComputeStack(const llvm::Function* F, const CallGraph& CG, + const std::map& LocalStack, + std::map& State, InternalAnalysisState& Res) + { + auto itState = State.find(F); + if (itState != State.end()) + { + if (itState->second == Visiting) + { + // Cycle détecté : on marque tous les noeuds actuellement en "Visiting" + for (auto& p : State) + { + if (p.second == Visiting) + { + Res.RecursiveFuncs.insert(p.first); + } + } + auto itLocal = LocalStack.find(F); + if (itLocal != LocalStack.end()) + { + return StackEstimate{itLocal->second.bytes, itLocal->second.unknown}; } + return {}; } - auto itLocal = LocalStack.find(F); - if (itLocal != LocalStack.end()) { - return StackEstimate{itLocal->second.bytes, itLocal->second.unknown}; + else if (itState->second == Visited) + { + auto itTotal = Res.TotalStack.find(F); + return (itTotal != Res.TotalStack.end()) ? itTotal->second : StackEstimate{}; } - return {}; - } else if (itState->second == Visited) { - auto itTotal = Res.TotalStack.find(F); - return (itTotal != Res.TotalStack.end()) ? itTotal->second : StackEstimate{}; } - } - State[F] = Visiting; + State[F] = Visiting; - auto itLocal = LocalStack.find(F); - StackEstimate local = {}; - if (itLocal != LocalStack.end()) { - local.bytes = itLocal->second.bytes; - local.unknown = itLocal->second.unknown; - } - StackEstimate maxCallee = {}; - - auto itCG = CG.find(F); - if (itCG != CG.end()) { - for (const llvm::Function *Callee : itCG->second) { - StackEstimate calleeStack = dfsComputeStack(Callee, CG, LocalStack, State, Res); - if (calleeStack.bytes > maxCallee.bytes) - maxCallee.bytes = calleeStack.bytes; - if (calleeStack.unknown) - maxCallee.unknown = true; + auto itLocal = LocalStack.find(F); + StackEstimate local = {}; + if (itLocal != LocalStack.end()) + { + local.bytes = itLocal->second.bytes; + local.unknown = itLocal->second.unknown; } - } + StackEstimate maxCallee = {}; - StackEstimate total; - total.bytes = local.bytes + maxCallee.bytes; - total.unknown = local.unknown || maxCallee.unknown; - Res.TotalStack[F] = total; - State[F] = Visited; - return total; -} - -static InternalAnalysisState computeGlobalStackUsage( - const CallGraph &CG, - const std::map &LocalStack) -{ - InternalAnalysisState Res; - std::map State; + auto itCG = CG.find(F); + if (itCG != CG.end()) + { + for (const llvm::Function* Callee : itCG->second) + { + StackEstimate calleeStack = dfsComputeStack(Callee, CG, LocalStack, State, Res); + if (calleeStack.bytes > maxCallee.bytes) + maxCallee.bytes = calleeStack.bytes; + if (calleeStack.unknown) + maxCallee.unknown = true; + } + } - for (auto &p : LocalStack) { - State[p.first] = NotVisited; + StackEstimate total; + total.bytes = local.bytes + maxCallee.bytes; + total.unknown = local.unknown || maxCallee.unknown; + Res.TotalStack[F] = total; + State[F] = Visited; + return total; } - for (auto &p : LocalStack) { - const llvm::Function *F = p.first; - if (State[F] == NotVisited) { - dfsComputeStack(F, CG, LocalStack, State, Res); + static InternalAnalysisState + computeGlobalStackUsage(const CallGraph& CG, + const std::map& LocalStack) + { + InternalAnalysisState Res; + std::map State; + + for (auto& p : LocalStack) + { + State[p.first] = NotVisited; + } + + for (auto& p : LocalStack) + { + const llvm::Function* F = p.first; + if (State[F] == NotVisited) + { + dfsComputeStack(F, CG, LocalStack, State, Res); + } } + + return Res; } - return Res; -} + // ============================================================================ + // Détection d’auto-récursion “infinie” (heuristique DominatorTree) + // ============================================================================ + + static bool detectInfiniteSelfRecursion(llvm::Function& F) + { + if (F.isDeclaration()) + return false; + + const llvm::Function* Self = &F; + + std::vector SelfCallBlocks; + + for (llvm::BasicBlock& BB : F) + { + for (llvm::Instruction& I : BB) + { + const llvm::Function* Callee = nullptr; + + if (auto* CI = llvm::dyn_cast(&I)) + { + Callee = CI->getCalledFunction(); + } + else if (auto* II = llvm::dyn_cast(&I)) + { + Callee = II->getCalledFunction(); + } + + if (Callee == Self) + { + SelfCallBlocks.push_back(&BB); + break; + } + } + } -// ============================================================================ -// Détection d’auto-récursion “infinie” (heuristique DominatorTree) -// ============================================================================ + if (SelfCallBlocks.empty()) + return false; -static bool detectInfiniteSelfRecursion(llvm::Function &F) -{ - if (F.isDeclaration()) - return false; + llvm::DominatorTree DT(F); - const llvm::Function *Self = &F; + bool hasReturn = false; - std::vector SelfCallBlocks; + for (llvm::BasicBlock& BB : F) + { + for (llvm::Instruction& I : BB) + { + if (llvm::isa(&I)) + { + hasReturn = true; - for (llvm::BasicBlock &BB : F) { - for (llvm::Instruction &I : BB) { - const llvm::Function *Callee = nullptr; + bool dominatedBySelfCall = false; + for (llvm::BasicBlock* SCB : SelfCallBlocks) + { + if (DT.dominates(SCB, &BB)) + { + dominatedBySelfCall = true; + break; + } + } - if (auto *CI = llvm::dyn_cast(&I)) { - Callee = CI->getCalledFunction(); - } else if (auto *II = llvm::dyn_cast(&I)) { - Callee = II->getCalledFunction(); + if (!dominatedBySelfCall) + return false; + } } + } - if (Callee == Self) { - SelfCallBlocks.push_back(&BB); - break; - } + if (!hasReturn) + { + return true; } + + return true; } - if (SelfCallBlocks.empty()) - return false; + // HELPERS + // Essaie de retrouver une alloca de tableau à partir d'un pointeur, + // en suivant les bitcast, GEP(0,0), et un pattern simple de pointeur local : + // char test[10]; + // char *ptr = test; + // ... load ptr ... ; gep -> ptr[i] + static const llvm::AllocaInst* resolveArrayAllocaFromPointer(const llvm::Value* V, + llvm::Function& F, + std::vector& path) + { + using namespace llvm; - llvm::DominatorTree DT(F); + auto isArrayAlloca = [](const AllocaInst* AI) -> bool + { + Type* T = AI->getAllocatedType(); + // On considère comme "buffer de stack" : + // - les vrais tableaux, + // - les allocas de type tableau (VLA côté IR), + // - les structs qui contiennent au moins un champ tableau. + if (T->isArrayTy() || AI->isArrayAllocation()) + return true; - bool hasReturn = false; + if (auto* ST = llvm::dyn_cast(T)) + { + for (unsigned i = 0; i < ST->getNumElements(); ++i) + { + if (ST->getElementType(i)->isArrayTy()) + return true; + } + } + return false; + }; - for (llvm::BasicBlock &BB : F) { - for (llvm::Instruction &I : BB) { - if (llvm::isa(&I)) { - hasReturn = true; + // Pour éviter les boucles d'aliasing bizarres + SmallPtrSet visited; + const Value* cur = V; - bool dominatedBySelfCall = false; - for (llvm::BasicBlock *SCB : SelfCallBlocks) { - if (DT.dominates(SCB, &BB)) { - dominatedBySelfCall = true; - break; - } + while (cur && !visited.contains(cur)) + { + visited.insert(cur); + if (cur->hasName()) + path.push_back(cur->getName().str()); + + // Cas 1 : on tombe sur une alloca. + if (auto* AI = dyn_cast(cur)) + { + if (isArrayAlloca(AI)) + { + // Alloca d'un buffer de stack (tableau) : cible finale. + return AI; } - if (!dominatedBySelfCall) - return false; - } - } - } + // Sinon, c'est très probablement une variable locale de type pointeur + // (char *ptr; char **pp; etc.). On parcourt les stores vers cette + // variable pour voir quelles valeurs lui sont assignées, et on + // tente de remonter jusqu'à une vraie alloca de tableau. + const AllocaInst* foundAI = nullptr; - if (!hasReturn) { - return true; - } + for (BasicBlock& BB : F) + { + for (Instruction& I : BB) + { + auto* SI = dyn_cast(&I); + if (!SI) + continue; + if (SI->getPointerOperand() != AI) + continue; - return true; -} - -// HELPERS -// Essaie de retrouver une alloca de tableau à partir d'un pointeur, -// en suivant les bitcast, GEP(0,0), et un pattern simple de pointeur local : -// char test[10]; -// char *ptr = test; -// ... load ptr ... ; gep -> ptr[i] -static const llvm::AllocaInst* -resolveArrayAllocaFromPointer(const llvm::Value *V, llvm::Function &F, std::vector &path) -{ - using namespace llvm; - - auto isArrayAlloca = [](const AllocaInst *AI) -> bool { - Type *T = AI->getAllocatedType(); - // On considère comme "buffer de stack" : - // - les vrais tableaux, - // - les allocas de type tableau (VLA côté IR), - // - les structs qui contiennent au moins un champ tableau. - if (T->isArrayTy() || AI->isArrayAllocation()) - return true; + const Value* storedPtr = SI->getValueOperand(); + std::vector subPath; + const AllocaInst* cand = + resolveArrayAllocaFromPointer(storedPtr, F, subPath); + if (!cand) + continue; - if (auto *ST = llvm::dyn_cast(T)) { - for (unsigned i = 0; i < ST->getNumElements(); ++i) { - if (ST->getElementType(i)->isArrayTy()) - return true; + if (!foundAI) + { + foundAI = cand; + // Append subPath to path + path.insert(path.end(), subPath.begin(), subPath.end()); + } + else if (foundAI != cand) + { + // Plusieurs bases différentes : aliasing ambigu, + // on préfère abandonner plutôt que de se tromper. + return nullptr; + } + } + } + return foundAI; } - } - return false; - }; - // Pour éviter les boucles d'aliasing bizarres - SmallPtrSet visited; - const Value *cur = V; - - while (cur && !visited.contains(cur)) { - visited.insert(cur); - if (cur->hasName()) - path.push_back(cur->getName().str()); - - // Cas 1 : on tombe sur une alloca. - if (auto *AI = dyn_cast(cur)) { - if (isArrayAlloca(AI)) { - // Alloca d'un buffer de stack (tableau) : cible finale. - return AI; + // Cas 2 : bitcast -> on remonte l'opérande. + if (auto* BC = dyn_cast(cur)) + { + cur = BC->getOperand(0); + continue; } - // Sinon, c'est très probablement une variable locale de type pointeur - // (char *ptr; char **pp; etc.). On parcourt les stores vers cette - // variable pour voir quelles valeurs lui sont assignées, et on - // tente de remonter jusqu'à une vraie alloca de tableau. - const AllocaInst *foundAI = nullptr; + // Cas 3 : GEP -> on remonte sur le pointeur de base. + if (auto* GEP = dyn_cast(cur)) + { + cur = GEP->getPointerOperand(); + continue; + } - for (BasicBlock &BB : F) { - for (Instruction &I : BB) { - auto *SI = dyn_cast(&I); - if (!SI) - continue; - if (SI->getPointerOperand() != AI) - continue; + // Cas 4 : load d'un pointeur. Exemple typique : + // char *ptr = test; + // char *p2 = ptr; + // char **pp = &ptr; + // (*pp)[i] = ... + // + // On remonte au "container" du pointeur (variable locale, ou autre valeur) + // en suivant l'opérande du load. + if (auto* LI = dyn_cast(cur)) + { + cur = LI->getPointerOperand(); + continue; + } - const Value *storedPtr = SI->getValueOperand(); + // Cas 5 : PHI de pointeurs (fusion de plusieurs alias) : + // on tente de résoudre chaque incoming et on s'assure qu'ils + // pointent tous vers la même alloca de tableau. + if (auto* PN = dyn_cast(cur)) + { + const AllocaInst* foundAI = nullptr; + std::vector phiPath; + for (unsigned i = 0; i < PN->getNumIncomingValues(); ++i) + { + const Value* inV = PN->getIncomingValue(i); std::vector subPath; - const AllocaInst *cand = - resolveArrayAllocaFromPointer(storedPtr, F, subPath); + const AllocaInst* cand = resolveArrayAllocaFromPointer(inV, F, subPath); if (!cand) continue; - - if (!foundAI) { + if (!foundAI) + { foundAI = cand; - // Append subPath to path - path.insert(path.end(), subPath.begin(), subPath.end()); - } else if (foundAI != cand) { - // Plusieurs bases différentes : aliasing ambigu, - // on préfère abandonner plutôt que de se tromper. + phiPath = subPath; + } + else if (foundAI != cand) + { + // PHI mélange plusieurs bases différentes : trop ambigu. return nullptr; } } + path.insert(path.end(), phiPath.begin(), phiPath.end()); + return foundAI; } - return foundAI; - } - - // Cas 2 : bitcast -> on remonte l'opérande. - if (auto *BC = dyn_cast(cur)) { - cur = BC->getOperand(0); - continue; - } - - // Cas 3 : GEP -> on remonte sur le pointeur de base. - if (auto *GEP = dyn_cast(cur)) { - cur = GEP->getPointerOperand(); - continue; - } - - // Cas 4 : load d'un pointeur. Exemple typique : - // char *ptr = test; - // char *p2 = ptr; - // char **pp = &ptr; - // (*pp)[i] = ... - // - // On remonte au "container" du pointeur (variable locale, ou autre valeur) - // en suivant l'opérande du load. - if (auto *LI = dyn_cast(cur)) { - cur = LI->getPointerOperand(); - continue; - } - // Cas 5 : PHI de pointeurs (fusion de plusieurs alias) : - // on tente de résoudre chaque incoming et on s'assure qu'ils - // pointent tous vers la même alloca de tableau. - if (auto *PN = dyn_cast(cur)) { - const AllocaInst *foundAI = nullptr; - std::vector phiPath; - for (unsigned i = 0; i < PN->getNumIncomingValues(); ++i) { - const Value *inV = PN->getIncomingValue(i); - std::vector subPath; - const AllocaInst *cand = - resolveArrayAllocaFromPointer(inV, F, subPath); - if (!cand) - continue; - if (!foundAI) { - foundAI = cand; - phiPath = subPath; - } else if (foundAI != cand) { - // PHI mélange plusieurs bases différentes : trop ambigu. - return nullptr; - } - } - path.insert(path.end(), phiPath.begin(), phiPath.end()); - return foundAI; + // Autres cas (arguments, globales complexes, etc.) : on arrête l'heuristique. + break; } - // Autres cas (arguments, globales complexes, etc.) : on arrête l'heuristique. - break; + return nullptr; } - return nullptr; -} + // Analyse intra-fonction pour détecter plusieurs stores dans un même buffer de stack. + // Heuristique : on compte le nombre de StoreInst qui écrivent dans un GEP basé sur + // une alloca de tableau sur la stack. Si une même alloca reçoit plus d'un store, + // on émet un warning. + static void analyzeMultipleStoresInFunction(llvm::Function& F, + std::vector& out) + { + using namespace llvm; -// Analyse intra-fonction pour détecter plusieurs stores dans un même buffer de stack. -// Heuristique : on compte le nombre de StoreInst qui écrivent dans un GEP basé sur -// une alloca de tableau sur la stack. Si une même alloca reçoit plus d'un store, -// on émet un warning. -static void analyzeMultipleStoresInFunction( - llvm::Function &F, - std::vector &out) -{ - using namespace llvm; + if (F.isDeclaration()) + return; - if (F.isDeclaration()) - return; + struct Info + { + std::size_t storeCount = 0; + llvm::SmallPtrSet indexKeys; + const AllocaInst* AI = nullptr; + }; - struct Info { - std::size_t storeCount = 0; - llvm::SmallPtrSet indexKeys; - const AllocaInst *AI = nullptr; - }; + std::map infoMap; - std::map infoMap; + for (BasicBlock& BB : F) + { + for (Instruction& I : BB) + { + auto* S = dyn_cast(&I); + if (!S) + continue; - for (BasicBlock &BB : F) { - for (Instruction &I : BB) { - auto *S = dyn_cast(&I); - if (!S) - continue; + Value* ptr = S->getPointerOperand(); + auto* GEP = dyn_cast(ptr); + if (!GEP) + continue; - Value *ptr = S->getPointerOperand(); - auto *GEP = dyn_cast(ptr); - if (!GEP) - continue; + // On remonte à la base pour trouver une alloca de tableau sur la stack. + const Value* basePtr = GEP->getPointerOperand(); + std::vector dummyAliasPath; + const AllocaInst* AI = resolveArrayAllocaFromPointer(basePtr, F, dummyAliasPath); + if (!AI) + continue; - // On remonte à la base pour trouver une alloca de tableau sur la stack. - const Value *basePtr = GEP->getPointerOperand(); - std::vector dummyAliasPath; - const AllocaInst *AI = resolveArrayAllocaFromPointer(basePtr, F, dummyAliasPath); - if (!AI) - continue; + // On récupère l'expression d'index utilisée dans le GEP. + Value* idxVal = nullptr; + Type* srcElemTy = GEP->getSourceElementType(); - // On récupère l'expression d'index utilisée dans le GEP. - Value *idxVal = nullptr; - Type *srcElemTy = GEP->getSourceElementType(); + if (auto* arrTy = dyn_cast(srcElemTy)) + { + // Pattern [N x T]* -> indices [0, i] + if (GEP->getNumIndices() < 2) + continue; + auto idxIt = GEP->idx_begin(); + ++idxIt; // saute le premier indice (souvent 0) + idxVal = idxIt->get(); + } + else + { + // Pattern T* -> indice unique [i] (cas char *ptr = test; ptr[i]) + if (GEP->getNumIndices() < 1) + continue; + auto idxIt = GEP->idx_begin(); + idxVal = idxIt->get(); + } - if (auto *arrTy = dyn_cast(srcElemTy)) { - // Pattern [N x T]* -> indices [0, i] - if (GEP->getNumIndices() < 2) - continue; - auto idxIt = GEP->idx_begin(); - ++idxIt; // saute le premier indice (souvent 0) - idxVal = idxIt->get(); - } else { - // Pattern T* -> indice unique [i] (cas char *ptr = test; ptr[i]) - if (GEP->getNumIndices() < 1) + if (!idxVal) continue; - auto idxIt = GEP->idx_begin(); - idxVal = idxIt->get(); - } - if (!idxVal) - continue; + // On normalise un peu la clé d'index en enlevant les casts SSA. + const Value* idxKey = idxVal; + while (auto* cast = dyn_cast(const_cast(idxKey))) + { + idxKey = cast->getOperand(0); + } - // On normalise un peu la clé d'index en enlevant les casts SSA. - const Value *idxKey = idxVal; - while (auto *cast = dyn_cast(const_cast(idxKey))) { - idxKey = cast->getOperand(0); + auto& info = infoMap[AI]; + info.AI = AI; + info.storeCount++; + info.indexKeys.insert(idxKey); } - - auto &info = infoMap[AI]; - info.AI = AI; - info.storeCount++; - info.indexKeys.insert(idxKey); } - } - // Construction des warnings pour chaque buffer qui reçoit plusieurs stores. - for (auto &entry : infoMap) { - const AllocaInst *AI = entry.first; - const Info &info = entry.second; + // Construction des warnings pour chaque buffer qui reçoit plusieurs stores. + for (auto& entry : infoMap) + { + const AllocaInst* AI = entry.first; + const Info& info = entry.second; - if (info.storeCount <= 1) - continue; // un seul store -> pas de warning + if (info.storeCount <= 1) + continue; // un seul store -> pas de warning - MultipleStoreIssue issue; - issue.funcName = F.getName().str(); - issue.varName = AI->hasName() ? AI->getName().str() : std::string(""); - issue.storeCount = info.storeCount; - issue.distinctIndexCount = info.indexKeys.size(); - issue.allocaInst = AI; + MultipleStoreIssue issue; + issue.funcName = F.getName().str(); + issue.varName = AI->hasName() ? AI->getName().str() : std::string(""); + issue.storeCount = info.storeCount; + issue.distinctIndexCount = info.indexKeys.size(); + issue.allocaInst = AI; - out.push_back(std::move(issue)); + out.push_back(std::move(issue)); + } } -} -// HELPERS -static const llvm::ConstantInt* tryGetConstFromValue(const llvm::Value *V, - const llvm::Function &F) -{ - using namespace llvm; - - // On enlève d'abord les cast (sext/zext/trunc, etc.) pour arriver - // à la vraie valeur “de base”. - const Value *cur = V; - while (auto *cast = dyn_cast(cur)) + // HELPERS + static const llvm::ConstantInt* tryGetConstFromValue(const llvm::Value* V, + const llvm::Function& F) { - cur = cast->getOperand(0); - } + using namespace llvm; + + // On enlève d'abord les cast (sext/zext/trunc, etc.) pour arriver + // à la vraie valeur “de base”. + const Value* cur = V; + while (auto* cast = dyn_cast(cur)) + { + cur = cast->getOperand(0); + } - // Cas trivial : c'est déjà une constante entière. - if (auto *C = dyn_cast(cur)) - return C; + // Cas trivial : c'est déjà une constante entière. + if (auto* C = dyn_cast(cur)) + return C; - // Cas -O0 typique : on compare un load d'une variable locale. - auto *LI = dyn_cast(cur); - if (!LI) - return nullptr; + // Cas -O0 typique : on compare un load d'une variable locale. + auto* LI = dyn_cast(cur); + if (!LI) + return nullptr; - const Value *ptr = LI->getPointerOperand(); - const ConstantInt *found = nullptr; + const Value* ptr = LI->getPointerOperand(); + const ConstantInt* found = nullptr; - // Version ultra-simple : on cherche un store de constante dans la fonction. - for (const BasicBlock &BB : F) { - for (const Instruction &I : BB) { - auto *SI = dyn_cast(&I); - if (!SI) - continue; - if (SI->getPointerOperand() != ptr) - continue; - if (auto *C = dyn_cast(SI->getValueOperand())) { - // On garde la dernière constante trouvée (si plusieurs stores, c'est naïf). - found = C; + // Version ultra-simple : on cherche un store de constante dans la fonction. + for (const BasicBlock& BB : F) + { + for (const Instruction& I : BB) + { + auto* SI = dyn_cast(&I); + if (!SI) + continue; + if (SI->getPointerOperand() != ptr) + continue; + if (auto* C = dyn_cast(SI->getValueOperand())) + { + // On garde la dernière constante trouvée (si plusieurs stores, c'est naïf). + found = C; + } } } + return found; } - return found; -} -// ============================================================================ -// API publique : analyzeModule / analyzeFile -// ============================================================================ + // ============================================================================ + // API publique : analyzeModule / analyzeFile + // ============================================================================ -AnalysisResult analyzeModule(llvm::Module &mod, - const AnalysisConfig &config) -{ - const llvm::DataLayout &DL = mod.getDataLayout(); + AnalysisResult analyzeModule(llvm::Module& mod, const AnalysisConfig& config) + { + const llvm::DataLayout& DL = mod.getDataLayout(); - // 1) Stack locale par fonction - std::map LocalStack; - for (llvm::Function &F : mod) { - if (F.isDeclaration()) - continue; - LocalStackInfo info = computeLocalStack(F, DL, config.mode); - LocalStack[&F] = info; - } + // 1) Stack locale par fonction + std::map LocalStack; + for (llvm::Function& F : mod) + { + if (F.isDeclaration()) + continue; + LocalStackInfo info = computeLocalStack(F, DL, config.mode); + LocalStack[&F] = info; + } - // 2) Graphe d'appels - CallGraph CG = buildCallGraph(mod); + // 2) Graphe d'appels + CallGraph CG = buildCallGraph(mod); - // 3) Propagation + détection de récursivité - InternalAnalysisState state = computeGlobalStackUsage(CG, LocalStack); + // 3) Propagation + détection de récursivité + InternalAnalysisState state = computeGlobalStackUsage(CG, LocalStack); - // 4) Détection d’auto-récursion “infinie” pour les fonctions récursives - for (llvm::Function &F : mod) { - if (F.isDeclaration()) - continue; - const llvm::Function *Fn = &F; - if (!state.RecursiveFuncs.count(Fn)) - continue; + // 4) Détection d’auto-récursion “infinie” pour les fonctions récursives + for (llvm::Function& F : mod) + { + if (F.isDeclaration()) + continue; + const llvm::Function* Fn = &F; + if (!state.RecursiveFuncs.count(Fn)) + continue; - if (detectInfiniteSelfRecursion(F)) { - state.InfiniteRecursionFuncs.insert(Fn); + if (detectInfiniteSelfRecursion(F)) + { + state.InfiniteRecursionFuncs.insert(Fn); + } } - } - // 5) Construction du résultat public - AnalysisResult result; - result.config = config; - StackSize allocaLargeThreshold = computeAllocaLargeThreshold(config); + // 5) Construction du résultat public + AnalysisResult result; + result.config = config; + StackSize allocaLargeThreshold = computeAllocaLargeThreshold(config); - for (llvm::Function &F : mod) { - if (F.isDeclaration()) - continue; + for (llvm::Function& F : mod) + { + if (F.isDeclaration()) + continue; - const llvm::Function *Fn = &F; + const llvm::Function* Fn = &F; - LocalStackInfo localInfo; - StackEstimate totalInfo; + LocalStackInfo localInfo; + StackEstimate totalInfo; - auto itLocal = LocalStack.find(Fn); - if (itLocal != LocalStack.end()) - localInfo = itLocal->second; - - auto itTotal = state.TotalStack.find(Fn); - if (itTotal != state.TotalStack.end()) - totalInfo = itTotal->second; - - FunctionResult fr; - fr.name = F.getName().str(); - fr.localStack = localInfo.bytes; - fr.localStackUnknown = localInfo.unknown; - fr.maxStack = totalInfo.bytes; - fr.maxStackUnknown = totalInfo.unknown; - fr.hasDynamicAlloca = localInfo.hasDynamicAlloca; - fr.isRecursive = state.RecursiveFuncs.count(Fn) != 0; - fr.hasInfiniteSelfRecursion = state.InfiniteRecursionFuncs.count(Fn) != 0; - fr.exceedsLimit = (!fr.maxStackUnknown && totalInfo.bytes > config.stackLimit); - - result.functions.push_back(std::move(fr)); - } + auto itLocal = LocalStack.find(Fn); + if (itLocal != LocalStack.end()) + localInfo = itLocal->second; - // 6) Détection des dépassements de buffer sur la stack (analyse intra-fonction) - std::vector bufferIssues; - for (llvm::Function &F : mod) { - if (F.isDeclaration()) - continue; - analyzeStackBufferOverflowsInFunction(F, bufferIssues); - } + auto itTotal = state.TotalStack.find(Fn); + if (itTotal != state.TotalStack.end()) + totalInfo = itTotal->second; - // 7) Affichage des problèmes détectés (pour l'instant, sortie directe) - for (const auto &issue : bufferIssues) - { - unsigned line = 0; - unsigned column = 0; - unsigned startLine = 0; - unsigned startColumn = 0; - unsigned endLine = 0; - unsigned endColumn = 0; - bool haveLoc = false; + FunctionResult fr; + fr.name = F.getName().str(); + fr.localStack = localInfo.bytes; + fr.localStackUnknown = localInfo.unknown; + fr.maxStack = totalInfo.bytes; + fr.maxStackUnknown = totalInfo.unknown; + fr.hasDynamicAlloca = localInfo.hasDynamicAlloca; + fr.isRecursive = state.RecursiveFuncs.count(Fn) != 0; + fr.hasInfiniteSelfRecursion = state.InfiniteRecursionFuncs.count(Fn) != 0; + fr.exceedsLimit = (!fr.maxStackUnknown && totalInfo.bytes > config.stackLimit); + + result.functions.push_back(std::move(fr)); + } + + // 6) Détection des dépassements de buffer sur la stack (analyse intra-fonction) + std::vector bufferIssues; + for (llvm::Function& F : mod) + { + if (F.isDeclaration()) + continue; + analyzeStackBufferOverflowsInFunction(F, bufferIssues); + } - if (issue.inst) + // 7) Affichage des problèmes détectés (pour l'instant, sortie directe) + for (const auto& issue : bufferIssues) { - llvm::DebugLoc DL = issue.inst->getDebugLoc(); - if (DL) + unsigned line = 0; + unsigned column = 0; + unsigned startLine = 0; + unsigned startColumn = 0; + unsigned endLine = 0; + unsigned endColumn = 0; + bool haveLoc = false; + + if (issue.inst) { - line = DL.getLine(); - startLine = DL.getLine(); + llvm::DebugLoc DL = issue.inst->getDebugLoc(); + if (DL) + { + line = DL.getLine(); + startLine = DL.getLine(); - startColumn = DL.getCol(); - column = DL.getCol(); + startColumn = DL.getCol(); + column = DL.getCol(); - // By default, same as start - endLine = DL.getLine(); - endColumn = DL.getCol(); - haveLoc = true; - if (auto *loc = DL.get()) - { - if (auto *scope = llvm::dyn_cast(loc)) + // By default, same as start + endLine = DL.getLine(); + endColumn = DL.getCol(); + haveLoc = true; + if (auto* loc = DL.get()) { - if (scope->getColumn() != 0) + if (auto* scope = llvm::dyn_cast(loc)) { - endColumn = scope->getColumn() + 1; + if (scope->getColumn() != 0) + { + endColumn = scope->getColumn() + 1; + } } } } } - } - bool isUnreachable = false; - { - using namespace llvm; + bool isUnreachable = false; + { + using namespace llvm; - if (issue.inst) { - auto *BB = issue.inst->getParent(); + if (issue.inst) + { + auto* BB = issue.inst->getParent(); - // Parcourt les prédécesseurs du bloc pour voir si certains - // ont une branche conditionnelle avec une condition constante. - for (auto *Pred : predecessors(BB)) { - auto *BI = dyn_cast(Pred->getTerminator()); - if (!BI || !BI->isConditional()) - continue; + // Parcourt les prédécesseurs du bloc pour voir si certains + // ont une branche conditionnelle avec une condition constante. + for (auto* Pred : predecessors(BB)) + { + auto* BI = dyn_cast(Pred->getTerminator()); + if (!BI || !BI->isConditional()) + continue; - auto *CI = dyn_cast(BI->getCondition()); - if (!CI) - continue; + auto* CI = dyn_cast(BI->getCondition()); + if (!CI) + continue; - const llvm::Function &Func = *issue.inst->getFunction(); + const llvm::Function& Func = *issue.inst->getFunction(); - auto *C0 = tryGetConstFromValue(CI->getOperand(0), Func); - auto *C1 = tryGetConstFromValue(CI->getOperand(1), Func); - if (!C0 || !C1) - continue; + auto* C0 = tryGetConstFromValue(CI->getOperand(0), Func); + auto* C1 = tryGetConstFromValue(CI->getOperand(1), Func); + if (!C0 || !C1) + continue; - // Évalue le résultat de l'ICmp pour ces constantes (implémentation maison). - bool condTrue = false; - auto pred = CI->getPredicate(); - const auto &v0 = C0->getValue(); - const auto &v1 = C1->getValue(); + // Évalue le résultat de l'ICmp pour ces constantes (implémentation maison). + bool condTrue = false; + auto pred = CI->getPredicate(); + const auto& v0 = C0->getValue(); + const auto& v1 = C1->getValue(); - switch (pred) { + switch (pred) + { case ICmpInst::ICMP_EQ: condTrue = (v0 == v1); break; @@ -3350,852 +3803,962 @@ AnalysisResult analyzeModule(llvm::Module &mod, default: // On ne traite pas d'autres prédicats exotiques ici continue; + } + + // Branchement du type: + // br i1 %cond, label %then, label %else + // Successeur 0 pris si condTrue == true + // Successeur 1 pris si condTrue == false + if (BB == BI->getSuccessor(0) && condTrue == false) + { + // Le bloc "then" n'est jamais atteint. + isUnreachable = true; + } + else if (BB == BI->getSuccessor(1) && condTrue == true) + { + // Le bloc "else" n'est jamais atteint. + isUnreachable = true; + } } + } + } + + std::ostringstream body; + Diagnostic diag; + + if (issue.isLowerBoundViolation) + { + diag.errCode = DescriptiveErrorCode::NegativeStackIndex; + body << " [!!] potential negative index on variable '" << issue.varName + << "' (size " << issue.arraySize << ")\n"; + if (!issue.aliasPath.empty()) + { + body << " alias path: " << issue.aliasPath << "\n"; + } + body << " inferred lower bound for index expression: " << issue.lowerBound + << " (index may be < 0)\n"; + } + else + { + diag.errCode = DescriptiveErrorCode::StackBufferOverflow; + body << " [!!] potential stack buffer overflow on variable '" << issue.varName + << "' (size " << issue.arraySize << ")\n"; + if (!issue.aliasPath.empty()) + { + body << " alias path: " << issue.aliasPath << "\n"; + } + if (issue.indexIsConstant) + { + body << " constant index " << issue.indexOrUpperBound + << " is out of bounds (0.." << (issue.arraySize ? issue.arraySize - 1 : 0) + << ")\n"; + } + else + { + body << " index variable may go up to " << issue.indexOrUpperBound + << " (array last valid index: " + << (issue.arraySize ? issue.arraySize - 1 : 0) << ")\n"; + } + } + + if (issue.isWrite) + { + body << " (this is a write access)\n"; + } + else + { + body << " (this is a read access)\n"; + } + if (isUnreachable) + { + body << " [info] this access appears unreachable at runtime " + "(condition is always false for this branch)\n"; + } + + diag.funcName = issue.funcName; + diag.line = haveLoc ? line : 0; + diag.column = haveLoc ? column : 0; + diag.startLine = haveLoc ? startLine : 0; + diag.startColumn = haveLoc ? startColumn : 0; + diag.endLine = haveLoc ? endLine : 0; + diag.endColumn = haveLoc ? endColumn : 0; + diag.severity = DiagnosticSeverity::Warning; + diag.message = body.str(); + diag.variableAliasingVec = issue.aliasPathVec; + result.diagnostics.push_back(std::move(diag)); + } + + // 8) Détection des allocations dynamiques sur la stack (VLA / alloca variable) + std::vector dynAllocaIssues; + for (llvm::Function& F : mod) + { + if (F.isDeclaration()) + continue; + analyzeDynamicAllocasInFunction(F, dynAllocaIssues); + } + + // 9) Affichage des allocations dynamiques détectées + for (const auto& d : dynAllocaIssues) + { + unsigned line = 0; + unsigned column = 0; + bool haveLoc = false; + if (d.allocaInst) + { + llvm::DebugLoc DL = d.allocaInst->getDebugLoc(); + if (DL) + { + line = DL.getLine(); + column = DL.getCol(); + haveLoc = true; + } + } + + std::ostringstream body; + + body << " [!] dynamic stack allocation detected for variable '" << d.varName << "'\n"; + body << " allocated type: " << d.typeName << "\n"; + body << " size of this allocation is not compile-time constant " + "(VLA / variable alloca) and may lead to unbounded stack usage\n"; + + Diagnostic diag; + diag.funcName = d.funcName; + diag.line = haveLoc ? line : 0; + diag.column = haveLoc ? column : 0; + diag.severity = DiagnosticSeverity::Warning; + diag.errCode = DescriptiveErrorCode::VLAUsage; + diag.message = body.str(); + result.diagnostics.push_back(std::move(diag)); + } + + // 10) Analyse des usages d'alloca (tainted / taille excessive) + std::vector allocaUsageIssues; + for (llvm::Function& F : mod) + { + if (F.isDeclaration()) + continue; + bool isRec = state.RecursiveFuncs.count(&F) != 0; + bool isInf = state.InfiniteRecursionFuncs.count(&F) != 0; + analyzeAllocaUsageInFunction(F, DL, isRec, isInf, allocaUsageIssues); + } + + for (const auto& a : allocaUsageIssues) + { + unsigned line = 0; + unsigned column = 0; + bool haveLoc = false; + if (a.allocaInst) + { + llvm::DebugLoc DL = a.allocaInst->getDebugLoc(); + if (DL) + { + line = DL.getLine(); + column = DL.getCol(); + haveLoc = true; + } + } + + bool isOversized = false; + if (a.sizeIsConst && a.sizeBytes >= allocaLargeThreshold) + isOversized = true; + else if (a.hasUpperBound && a.upperBoundBytes >= allocaLargeThreshold) + isOversized = true; + else if (a.sizeIsConst && config.stackLimit != 0 && a.sizeBytes >= config.stackLimit) + isOversized = true; + + std::ostringstream body; + Diagnostic diag; + diag.funcName = a.funcName; + diag.line = haveLoc ? line : 0; + diag.column = haveLoc ? column : 0; + + if (isOversized) + { + diag.severity = DiagnosticSeverity::Error; + diag.errCode = DescriptiveErrorCode::AllocaTooLarge; + body << " [!!] large alloca on the stack for variable '" << a.varName << "'\n"; + } + else if (a.userControlled) + { + diag.severity = DiagnosticSeverity::Warning; + diag.errCode = DescriptiveErrorCode::AllocaUserControlled; + body << " [!!] user-controlled alloca size for variable '" << a.varName << "'\n"; + } + else + { + diag.severity = DiagnosticSeverity::Warning; + diag.errCode = DescriptiveErrorCode::AllocaUsageWarning; + body << " [!] dynamic alloca on the stack for variable '" << a.varName << "'\n"; + } + + body << " allocation performed via alloca/VLA; stack usage grows with runtime " + "value\n"; + + if (a.sizeIsConst) + { + body << " requested stack size: " << a.sizeBytes << " bytes\n"; + } + else if (a.hasUpperBound) + { + body << " inferred upper bound for size: " << a.upperBoundBytes << " bytes\n"; + } + else + { + body << " size is unbounded at compile time\n"; + } - // Branchement du type: - // br i1 %cond, label %then, label %else - // Successeur 0 pris si condTrue == true - // Successeur 1 pris si condTrue == false - if (BB == BI->getSuccessor(0) && condTrue == false) { - // Le bloc "then" n'est jamais atteint. - isUnreachable = true; - } else if (BB == BI->getSuccessor(1) && condTrue == true) { - // Le bloc "else" n'est jamais atteint. - isUnreachable = true; - } + if (a.isInfiniteRecursive) + { + // Any alloca inside infinite recursion will blow the stack. + diag.severity = DiagnosticSeverity::Error; + body << " function is infinitely recursive; this alloca runs at every frame " + "and guarantees stack overflow\n"; + } + else if (a.isRecursive) + { + // Controlled recursion still compounds stack usage across frames. + if (diag.severity != DiagnosticSeverity::Error && (isOversized || a.userControlled)) + { + diag.severity = DiagnosticSeverity::Error; } + body << " function is recursive; this allocation repeats at each recursion " + "depth and can exhaust the stack\n"; } - } - std::ostringstream body; - Diagnostic diag; - - if (issue.isLowerBoundViolation) - { - diag.errCode = DescriptiveErrorCode::NegativeStackIndex; - body << " [!!] potential negative index on variable '" - << issue.varName << "' (size " << issue.arraySize << ")\n"; - if (!issue.aliasPath.empty()) { - body << " alias path: " << issue.aliasPath << "\n"; - } - body << " inferred lower bound for index expression: " - << issue.lowerBound << " (index may be < 0)\n"; - } else { - diag.errCode = DescriptiveErrorCode::StackBufferOverflow; - body << " [!!] potential stack buffer overflow on variable '" - << issue.varName << "' (size " << issue.arraySize << ")\n"; - if (!issue.aliasPath.empty()) { - body << " alias path: " << issue.aliasPath << "\n"; - } - if (issue.indexIsConstant) { - body << " constant index " << issue.indexOrUpperBound - << " is out of bounds (0.." - << (issue.arraySize ? issue.arraySize - 1 : 0) - << ")\n"; - } else { - body << " index variable may go up to " - << issue.indexOrUpperBound - << " (array last valid index: " - << (issue.arraySize ? issue.arraySize - 1 : 0) << ")\n"; + if (isOversized) + { + body << " exceeds safety threshold of " << allocaLargeThreshold << " bytes"; + if (config.stackLimit != 0) + { + body << " (stack limit: " << config.stackLimit << " bytes)"; + } + body << "\n"; + } + else if (a.userControlled) + { + body << " size depends on user-controlled input " + "(function argument or non-local value)\n"; + } + else + { + body << " size does not appear user-controlled but remains " + "runtime-dependent\n"; } - } - if (issue.isWrite) - { - body << " (this is a write access)\n"; - } - else - { - body << " (this is a read access)\n"; + diag.message = body.str(); + result.diagnostics.push_back(std::move(diag)); } - if (isUnreachable) + + // 11) Détection des débordements via memcpy/memset sur des buffers de stack + std::vector memIssues; + for (llvm::Function& F : mod) { - body << " [info] this access appears unreachable at runtime " - "(condition is always false for this branch)\n"; + if (F.isDeclaration()) + continue; + analyzeMemIntrinsicOverflowsInFunction(F, DL, memIssues); } - diag.funcName = issue.funcName; - diag.line = haveLoc ? line : 0; - diag.column = haveLoc ? column : 0; - diag.startLine = haveLoc ? startLine : 0; - diag.startColumn = haveLoc ? startColumn : 0; - diag.endLine = haveLoc ? endLine : 0; - diag.endColumn = haveLoc ? endColumn : 0; - diag.severity = DiagnosticSeverity::Warning; - diag.message = body.str(); - diag.variableAliasingVec = issue.aliasPathVec; - result.diagnostics.push_back(std::move(diag)); - } - - // 8) Détection des allocations dynamiques sur la stack (VLA / alloca variable) - std::vector dynAllocaIssues; - for (llvm::Function &F : mod) { - if (F.isDeclaration()) - continue; - analyzeDynamicAllocasInFunction(F, dynAllocaIssues); - } - - // 9) Affichage des allocations dynamiques détectées - for (const auto &d : dynAllocaIssues) - { - unsigned line = 0; - unsigned column = 0; - bool haveLoc = false; - if (d.allocaInst) { - llvm::DebugLoc DL = d.allocaInst->getDebugLoc(); - if (DL) { - line = DL.getLine(); - column = DL.getCol(); - haveLoc = true; + for (const auto& m : memIssues) + { + unsigned line = 0; + unsigned column = 0; + bool haveLoc = false; + if (m.inst) + { + llvm::DebugLoc DL = m.inst->getDebugLoc(); + if (DL) + { + line = DL.getLine(); + column = DL.getCol(); + haveLoc = true; + } } - } - - std::ostringstream body; - - body << " [!] dynamic stack allocation detected for variable '" - << d.varName << "'\n"; - body << " allocated type: " << d.typeName << "\n"; - body << " size of this allocation is not compile-time constant " - "(VLA / variable alloca) and may lead to unbounded stack usage\n"; - - Diagnostic diag; - diag.funcName = d.funcName; - diag.line = haveLoc ? line : 0; - diag.column = haveLoc ? column : 0; - diag.severity = DiagnosticSeverity::Warning; - diag.errCode = DescriptiveErrorCode::VLAUsage; - diag.message = body.str(); - result.diagnostics.push_back(std::move(diag)); - } - // 10) Analyse des usages d'alloca (tainted / taille excessive) - std::vector allocaUsageIssues; - for (llvm::Function &F : mod) { - if (F.isDeclaration()) - continue; - bool isRec = state.RecursiveFuncs.count(&F) != 0; - bool isInf = state.InfiniteRecursionFuncs.count(&F) != 0; - analyzeAllocaUsageInFunction(F, DL, isRec, isInf, allocaUsageIssues); - } + std::ostringstream body; - for (const auto &a : allocaUsageIssues) - { - unsigned line = 0; - unsigned column = 0; - bool haveLoc = false; - if (a.allocaInst) { - llvm::DebugLoc DL = a.allocaInst->getDebugLoc(); - if (DL) { - line = DL.getLine(); - column = DL.getCol(); - haveLoc = true; + body << "Function: " << m.funcName; + if (haveLoc) + { + body << " (line " << line << ", column " << column << ")"; } - } + body << "\n"; - bool isOversized = false; - if (a.sizeIsConst && a.sizeBytes >= allocaLargeThreshold) - isOversized = true; - else if (a.hasUpperBound && a.upperBoundBytes >= allocaLargeThreshold) - isOversized = true; - else if (a.sizeIsConst && config.stackLimit != 0 && a.sizeBytes >= config.stackLimit) - isOversized = true; - - std::ostringstream body; - Diagnostic diag; - diag.funcName = a.funcName; - diag.line = haveLoc ? line : 0; - diag.column = haveLoc ? column : 0; - - if (isOversized) { - diag.severity = DiagnosticSeverity::Error; - diag.errCode = DescriptiveErrorCode::AllocaTooLarge; - body << " [!!] large alloca on the stack for variable '" - << a.varName << "'\n"; - } else if (a.userControlled) { - diag.severity = DiagnosticSeverity::Warning; - diag.errCode = DescriptiveErrorCode::AllocaUserControlled; - body << " [!!] user-controlled alloca size for variable '" - << a.varName << "'\n"; - } else { + body << " [!!] potential stack buffer overflow in " << m.intrinsicName + << " on variable '" << m.varName << "'\n"; + body << " destination stack buffer size: " << m.destSizeBytes << " bytes\n"; + body << " requested " << m.lengthBytes << " bytes to be copied/initialized\n"; + + Diagnostic diag; + diag.funcName = m.funcName; + diag.line = haveLoc ? line : 0; + diag.column = haveLoc ? column : 0; diag.severity = DiagnosticSeverity::Warning; - diag.errCode = DescriptiveErrorCode::AllocaUsageWarning; - body << " [!] dynamic alloca on the stack for variable '" - << a.varName << "'\n"; + diag.message = body.str(); + result.diagnostics.push_back(std::move(diag)); } - body << " allocation performed via alloca/VLA; stack usage grows with runtime value\n"; - - if (a.sizeIsConst) { - body << " requested stack size: " - << a.sizeBytes << " bytes\n"; - } else if (a.hasUpperBound) { - body << " inferred upper bound for size: " - << a.upperBoundBytes << " bytes\n"; - } else { - body << " size is unbounded at compile time\n"; + // 12) Détection de plusieurs stores dans un même buffer de stack + std::vector multiStoreIssues; + for (llvm::Function& F : mod) + { + if (F.isDeclaration()) + continue; + analyzeMultipleStoresInFunction(F, multiStoreIssues); } - if (a.isInfiniteRecursive) { - // Any alloca inside infinite recursion will blow the stack. - diag.severity = DiagnosticSeverity::Error; - body << " function is infinitely recursive; this alloca runs at every frame and guarantees stack overflow\n"; - } else if (a.isRecursive) { - // Controlled recursion still compounds stack usage across frames. - if (diag.severity != DiagnosticSeverity::Error && (isOversized || a.userControlled)) { - diag.severity = DiagnosticSeverity::Error; + for (const auto& ms : multiStoreIssues) + { + unsigned line = 0; + unsigned column = 0; + bool haveLoc = false; + if (ms.allocaInst) + { + llvm::DebugLoc DL = ms.allocaInst->getDebugLoc(); + if (DL) + { + line = DL.getLine(); + column = DL.getCol(); + haveLoc = true; + } } - body << " function is recursive; this allocation repeats at each recursion depth and can exhaust the stack\n"; - } - if (isOversized) { - body << " exceeds safety threshold of " - << allocaLargeThreshold << " bytes"; - if (config.stackLimit != 0) { - body << " (stack limit: " << config.stackLimit << " bytes)"; + std::ostringstream body; + Diagnostic diag; + + body << " [!Info] multiple stores to stack buffer '" << ms.varName + << "' in this function (" << ms.storeCount << " store instruction(s)"; + diag.errCode = DescriptiveErrorCode::MultipleStoresToStackBuffer; + if (ms.distinctIndexCount > 0) + { + body << ", " << ms.distinctIndexCount << " distinct index expression(s)"; } - body << "\n"; - } else if (a.userControlled) { - body << " size depends on user-controlled input " - "(function argument or non-local value)\n"; - } else { - body << " size does not appear user-controlled but remains runtime-dependent\n"; - } + body << ")\n"; - diag.message = body.str(); - result.diagnostics.push_back(std::move(diag)); - } + if (ms.distinctIndexCount == 1) + { + body << " all stores use the same index expression " + "(possible redundant or unintended overwrite)\n"; + } + else if (ms.distinctIndexCount > 1) + { + body << " stores use different index expressions; " + "verify indices are correct and non-overlapping\n"; + } - // 11) Détection des débordements via memcpy/memset sur des buffers de stack - std::vector memIssues; - for (llvm::Function &F : mod) { - if (F.isDeclaration()) - continue; - analyzeMemIntrinsicOverflowsInFunction(F, DL, memIssues); - } + diag.funcName = ms.funcName; + diag.line = haveLoc ? line : 0; + diag.column = haveLoc ? column : 0; + diag.severity = DiagnosticSeverity::Info; + diag.message = body.str(); + result.diagnostics.push_back(std::move(diag)); + } - for (const auto &m : memIssues) - { - unsigned line = 0; - unsigned column = 0; - bool haveLoc = false; - if (m.inst) { - llvm::DebugLoc DL = m.inst->getDebugLoc(); - if (DL) { - line = DL.getLine(); - column = DL.getCol(); - haveLoc = true; - } + // 13) Détection des reconstructions invalides de pointeur de base (offsetof/container_of) + std::vector baseReconIssues; + for (llvm::Function& F : mod) + { + if (F.isDeclaration()) + continue; + analyzeInvalidBaseReconstructionsInFunction(F, DL, baseReconIssues); } - std::ostringstream body; + for (const auto& br : baseReconIssues) + { + unsigned line = 0; + unsigned column = 0; + unsigned startLine = 0; + unsigned startColumn = 0; + unsigned endLine = 0; + unsigned endColumn = 0; + bool haveLoc = false; + + if (br.inst) + { + llvm::DebugLoc DL = br.inst->getDebugLoc(); + if (DL) + { + line = DL.getLine(); + startLine = DL.getLine(); + startColumn = DL.getCol(); + column = DL.getCol(); + endLine = DL.getLine(); + endColumn = DL.getCol(); + haveLoc = true; + + if (auto* loc = DL.get()) + { + if (auto* scope = llvm::dyn_cast(loc)) + { + if (scope->getColumn() != 0) + { + endColumn = scope->getColumn() + 1; + } + } + } + } + } - body << "Function: " << m.funcName; - if (haveLoc) { - body << " (line " << line << ", column " << column << ")"; - } - body << "\n"; - - body << " [!!] potential stack buffer overflow in " - << m.intrinsicName << " on variable '" - << m.varName << "'\n"; - body << " destination stack buffer size: " - << m.destSizeBytes << " bytes\n"; - body << " requested " << m.lengthBytes - << " bytes to be copied/initialized\n"; - - Diagnostic diag; - diag.funcName = m.funcName; - diag.line = haveLoc ? line : 0; - diag.column = haveLoc ? column : 0; - diag.severity = DiagnosticSeverity::Warning; - diag.message = body.str(); - result.diagnostics.push_back(std::move(diag)); - } + std::ostringstream body; - // 12) Détection de plusieurs stores dans un même buffer de stack - std::vector multiStoreIssues; - for (llvm::Function &F : mod) { - if (F.isDeclaration()) - continue; - analyzeMultipleStoresInFunction(F, multiStoreIssues); - } + body << " [!!] potential UB: invalid base reconstruction via offsetof/container_of\n"; + body << " variable: '" << br.varName << "'\n"; + body << " source member: " << br.sourceMember << "\n"; + body << " offset applied: " << (br.offsetUsed >= 0 ? "+" : "") << br.offsetUsed + << " bytes\n"; + body << " target type: " << br.targetType << "\n"; - for (const auto &ms : multiStoreIssues) - { - unsigned line = 0; - unsigned column = 0; - bool haveLoc = false; - if (ms.allocaInst) { - llvm::DebugLoc DL = ms.allocaInst->getDebugLoc(); - if (DL) { - line = DL.getLine(); - column = DL.getCol(); - haveLoc = true; + if (br.isOutOfBounds) + { + body << " [ERROR] derived pointer points OUTSIDE the valid object range\n"; + body << " (this will cause undefined behavior if dereferenced)\n"; + } + else + { + body << " [WARNING] unable to verify that derived pointer points to a valid " + "object\n"; + body << " (potential undefined behavior if offset is incorrect)\n"; } - } - - std::ostringstream body; - Diagnostic diag; - body << " [!Info] multiple stores to stack buffer '" - << ms.varName << "' in this function (" - << ms.storeCount << " store instruction(s)"; - diag.errCode = DescriptiveErrorCode::MultipleStoresToStackBuffer; - if (ms.distinctIndexCount > 0) - { - body << ", " << ms.distinctIndexCount - << " distinct index expression(s)"; + Diagnostic diag; + diag.funcName = br.funcName; + diag.line = haveLoc ? line : 0; + diag.column = haveLoc ? column : 0; + diag.startLine = haveLoc ? startLine : 0; + diag.startColumn = haveLoc ? startColumn : 0; + diag.endLine = haveLoc ? endLine : 0; + diag.endColumn = haveLoc ? endColumn : 0; + diag.severity = + br.isOutOfBounds ? DiagnosticSeverity::Error : DiagnosticSeverity::Warning; + diag.errCode = DescriptiveErrorCode::InvalidBaseReconstruction; + diag.message = body.str(); + result.diagnostics.push_back(std::move(diag)); } - body << ")\n"; - if (ms.distinctIndexCount == 1) + // 14) Détection de fuite de pointeurs de stack (use-after-return potentiel) + std::vector escapeIssues; + for (llvm::Function& F : mod) { - body << " all stores use the same index expression " - "(possible redundant or unintended overwrite)\n"; + if (F.isDeclaration()) + continue; + analyzeStackPointerEscapesInFunction(F, escapeIssues); } - else if (ms.distinctIndexCount > 1) + + for (const auto& e : escapeIssues) { - body << " stores use different index expressions; " - "verify indices are correct and non-overlapping\n"; - } + unsigned line = 0; + unsigned column = 0; + bool haveLoc = false; + if (e.inst) + { + llvm::DebugLoc DL = e.inst->getDebugLoc(); + if (DL) + { + line = DL.getLine(); + column = DL.getCol(); + haveLoc = true; + } + } - diag.funcName = ms.funcName; - diag.line = haveLoc ? line : 0; - diag.column = haveLoc ? column : 0; - diag.severity = DiagnosticSeverity::Info; - diag.message = body.str(); - result.diagnostics.push_back(std::move(diag)); - } + std::ostringstream body; - // 13) Détection des reconstructions invalides de pointeur de base (offsetof/container_of) - std::vector baseReconIssues; - for (llvm::Function &F : mod) { - if (F.isDeclaration()) - continue; - analyzeInvalidBaseReconstructionsInFunction(F, DL, baseReconIssues); - } + body << " [!!] stack pointer escape: address of variable '" << e.varName + << "' escapes this function\n"; - for (const auto &br : baseReconIssues) - { - unsigned line = 0; - unsigned column = 0; - unsigned startLine = 0; - unsigned startColumn = 0; - unsigned endLine = 0; - unsigned endColumn = 0; - bool haveLoc = false; - - if (br.inst) { - llvm::DebugLoc DL = br.inst->getDebugLoc(); - if (DL) { - line = DL.getLine(); - startLine = DL.getLine(); - startColumn = DL.getCol(); - column = DL.getCol(); - endLine = DL.getLine(); - endColumn = DL.getCol(); - haveLoc = true; - - if (auto *loc = DL.get()) { - if (auto *scope = llvm::dyn_cast(loc)) { - if (scope->getColumn() != 0) { - endColumn = scope->getColumn() + 1; - } - } + if (e.escapeKind == "return") + { + body << " escape via return statement " + "(pointer to stack returned to caller)\n"; + } + else if (e.escapeKind == "store_global") + { + if (!e.targetName.empty()) + { + body << " stored into global variable '" << e.targetName + << "' (pointer may be used after the function returns)\n"; + } + else + { + body << " stored into a global variable " + "(pointer may be used after the function returns)\n"; + } + } + else if (e.escapeKind == "store_unknown") + { + body << " stored through a non-local pointer " + "(e.g. via an out-parameter; pointer may outlive this function)\n"; + if (!e.targetName.empty()) + { + body << " destination pointer/value name: '" << e.targetName << "'\n"; + } + } + else if (e.escapeKind == "call_callback") + { + body << " address passed as argument to an indirect call " + "(callback may capture the pointer beyond this function)\n"; + } + else if (e.escapeKind == "call_arg") + { + if (!e.targetName.empty()) + { + body << " address passed as argument to function '" << e.targetName + << "' (callee may capture the pointer beyond this function)\n"; + } + else + { + body << " address passed as argument to a function " + "(callee may capture the pointer beyond this function)\n"; } } + + Diagnostic diag; + diag.funcName = e.funcName; + diag.line = haveLoc ? line : 0; + diag.column = haveLoc ? column : 0; + diag.severity = DiagnosticSeverity::Warning; + diag.errCode = DescriptiveErrorCode::StackPointerEscape; + diag.message = body.str(); + result.diagnostics.push_back(std::move(diag)); } - std::ostringstream body; - - body << " [!!] potential UB: invalid base reconstruction via offsetof/container_of\n"; - body << " variable: '" << br.varName << "'\n"; - body << " source member: " << br.sourceMember << "\n"; - body << " offset applied: " << (br.offsetUsed >= 0 ? "+" : "") - << br.offsetUsed << " bytes\n"; - body << " target type: " << br.targetType << "\n"; - - if (br.isOutOfBounds) { - body << " [ERROR] derived pointer points OUTSIDE the valid object range\n"; - body << " (this will cause undefined behavior if dereferenced)\n"; - } else { - body << " [WARNING] unable to verify that derived pointer points to a valid object\n"; - body << " (potential undefined behavior if offset is incorrect)\n"; + // 15) Const-correctness: parameters that can be made const + std::vector constParamIssues; + for (llvm::Function& F : mod) + { + if (F.isDeclaration()) + continue; + analyzeConstParamsInFunction(F, constParamIssues); } - Diagnostic diag; - diag.funcName = br.funcName; - diag.line = haveLoc ? line : 0; - diag.column = haveLoc ? column : 0; - diag.startLine = haveLoc ? startLine : 0; - diag.startColumn = haveLoc ? startColumn : 0; - diag.endLine = haveLoc ? endLine : 0; - diag.endColumn = haveLoc ? endColumn : 0; - diag.severity = br.isOutOfBounds ? DiagnosticSeverity::Error : DiagnosticSeverity::Warning; - diag.errCode = DescriptiveErrorCode::InvalidBaseReconstruction; - diag.message = body.str(); - result.diagnostics.push_back(std::move(diag)); - } + for (const auto& cp : constParamIssues) + { + std::ostringstream body; + Diagnostic diag; + std::string displayFuncName = formatFunctionNameForMessage(cp.funcName); - // 14) Détection de fuite de pointeurs de stack (use-after-return potentiel) - std::vector escapeIssues; - for (llvm::Function &F : mod) { - if (F.isDeclaration()) - continue; - analyzeStackPointerEscapesInFunction(F, escapeIssues); - } + diag.severity = DiagnosticSeverity::Info; + diag.errCode = DescriptiveErrorCode::ConstParameterNotModified; - for (const auto &e : escapeIssues) - { - unsigned line = 0; - unsigned column = 0; - bool haveLoc = false; - if (e.inst) { - llvm::DebugLoc DL = e.inst->getDebugLoc(); - if (DL) { - line = DL.getLine(); - column = DL.getCol(); - haveLoc = true; - } - } + const char* prefix = "[!]"; + if (diag.severity == DiagnosticSeverity::Warning) + prefix = "[!!]"; + else if (diag.severity == DiagnosticSeverity::Error) + prefix = "[!!!]"; - std::ostringstream body; - - body << " [!!] stack pointer escape: address of variable '" - << e.varName << "' escapes this function\n"; - - if (e.escapeKind == "return") { - body << " escape via return statement " - "(pointer to stack returned to caller)\n"; - } else if (e.escapeKind == "store_global") { - if (!e.targetName.empty()) { - body << " stored into global variable '" - << e.targetName - << "' (pointer may be used after the function returns)\n"; - } else { - body << " stored into a global variable " - "(pointer may be used after the function returns)\n"; - } - } else if (e.escapeKind == "store_unknown") { - body << " stored through a non-local pointer " - "(e.g. via an out-parameter; pointer may outlive this function)\n"; - if (!e.targetName.empty()) { - body << " destination pointer/value name: '" - << e.targetName << "'\n"; - } - } else if (e.escapeKind == "call_callback") { - body << " address passed as argument to an indirect call " - "(callback may capture the pointer beyond this function)\n"; - } else if (e.escapeKind == "call_arg") { - if (!e.targetName.empty()) { - body << " address passed as argument to function '" - << e.targetName - << "' (callee may capture the pointer beyond this function)\n"; - } else { - body << " address passed as argument to a function " - "(callee may capture the pointer beyond this function)\n"; + const char* subLabel = "Pointer"; + if (cp.pointerConstOnly) + { + subLabel = "PointerConstOnly"; + } + else if (cp.isReference) + { + subLabel = cp.isRvalueRef ? "ReferenceRvaluePreferValue" : "Reference"; } - } - - Diagnostic diag; - diag.funcName = e.funcName; - diag.line = haveLoc ? line : 0; - diag.column = haveLoc ? column : 0; - diag.severity = DiagnosticSeverity::Warning; - diag.errCode = DescriptiveErrorCode::StackPointerEscape; - diag.message = body.str(); - result.diagnostics.push_back(std::move(diag)); - } - - // 15) Const-correctness: parameters that can be made const - std::vector constParamIssues; - for (llvm::Function &F : mod) { - if (F.isDeclaration()) - continue; - analyzeConstParamsInFunction(F, constParamIssues); - } - for (const auto &cp : constParamIssues) - { - std::ostringstream body; - Diagnostic diag; - std::string displayFuncName = formatFunctionNameForMessage(cp.funcName); - - diag.severity = DiagnosticSeverity::Info; - diag.errCode = DescriptiveErrorCode::ConstParameterNotModified; - - const char *prefix = "[!]"; - if (diag.severity == DiagnosticSeverity::Warning) - prefix = "[!!]"; - else if (diag.severity == DiagnosticSeverity::Error) - prefix = "[!!!]"; - - const char *subLabel = "Pointer"; - if (cp.pointerConstOnly) { - subLabel = "PointerConstOnly"; - } else if (cp.isReference) { - subLabel = cp.isRvalueRef ? "ReferenceRvaluePreferValue" : "Reference"; - } + if (cp.isRvalueRef) + { + body + << " " << prefix << "ConstParameterNotModified." << subLabel << ": parameter '" + << cp.paramName << "' in function '" << displayFuncName + << "' is an rvalue reference and is never used to modify the referred object\n"; + body << " consider passing by value (" << cp.suggestedType + << ") or const reference (" << cp.suggestedTypeAlt << ")\n"; + body << " current type: " << cp.currentType << "\n"; + } + else if (cp.pointerConstOnly) + { + body << " " << prefix << "ConstParameterNotModified." << subLabel + << ": parameter '" << cp.paramName << "' in function '" << displayFuncName + << "' is declared '" << cp.currentType + << "' but the pointed object is never modified\n"; + body << " consider '" << cp.suggestedType << "' for API const-correctness\n"; + } + else + { + body << " " << prefix << "ConstParameterNotModified." << subLabel + << ": parameter '" << cp.paramName << "' in function '" << displayFuncName + << "' is never used to modify the " + << (cp.isReference ? "referred" : "pointed") << " object\n"; + } - if (cp.isRvalueRef) { - body << " " << prefix << "ConstParameterNotModified." << subLabel - << ": parameter '" << cp.paramName << "' in function '" << displayFuncName - << "' is an rvalue reference and is never used to modify the referred object\n"; - body << " consider passing by value (" << cp.suggestedType - << ") or const reference (" << cp.suggestedTypeAlt << ")\n"; - body << " current type: " << cp.currentType << "\n"; - } else if (cp.pointerConstOnly) { - body << " " << prefix << "ConstParameterNotModified." << subLabel - << ": parameter '" << cp.paramName << "' in function '" << displayFuncName - << "' is declared '" << cp.currentType - << "' but the pointed object is never modified\n"; - body << " consider '" << cp.suggestedType - << "' for API const-correctness\n"; - } else { - body << " " << prefix << "ConstParameterNotModified." << subLabel - << ": parameter '" << cp.paramName << "' in function '" << displayFuncName - << "' is never used to modify the " - << (cp.isReference ? "referred" : "pointed") - << " object\n"; - } + if (!cp.isRvalueRef) + { + body << " current type: " << cp.currentType << "\n"; + body << " suggested type: " << cp.suggestedType << "\n"; + } - if (!cp.isRvalueRef) { - body << " current type: " << cp.currentType << "\n"; - body << " suggested type: " << cp.suggestedType << "\n"; + diag.funcName = cp.funcName; + diag.line = cp.line; + diag.column = cp.column; + diag.startLine = cp.line; + diag.startColumn = cp.column; + diag.endLine = cp.line; + diag.endColumn = cp.column; + diag.message = body.str(); + diag.ruleId = std::string("ConstParameterNotModified.") + subLabel; + result.diagnostics.push_back(std::move(diag)); } - diag.funcName = cp.funcName; - diag.line = cp.line; - diag.column = cp.column; - diag.startLine = cp.line; - diag.startColumn = cp.column; - diag.endLine = cp.line; - diag.endColumn = cp.column; - diag.message = body.str(); - diag.ruleId = std::string("ConstParameterNotModified.") + subLabel; - result.diagnostics.push_back(std::move(diag)); + return result; } - return result; -} - -static LanguageType detectFromExtension(const std::string &path) -{ - auto pos = path.find_last_of('.'); - if (pos == std::string::npos) - return LanguageType::Unknown; + static LanguageType detectFromExtension(const std::string& path) + { + auto pos = path.find_last_of('.'); + if (pos == std::string::npos) + return LanguageType::Unknown; - std::string ext = path.substr(pos + 1); - std::transform(ext.begin(), ext.end(), ext.begin(), - [](unsigned char c) { return std::tolower(c); }); + std::string ext = path.substr(pos + 1); + std::transform(ext.begin(), ext.end(), ext.begin(), + [](unsigned char c) { return std::tolower(c); }); - if (ext == "ll") - return LanguageType::LLVM_IR; + if (ext == "ll") + return LanguageType::LLVM_IR; - if (ext == "c") - return LanguageType::C; + if (ext == "c") + return LanguageType::C; - if (ext == "cpp" || ext == "cc" || ext == "cxx" || - ext == "c++" || ext == "cp" || ext == "C") - return LanguageType::CXX; + if (ext == "cpp" || ext == "cc" || ext == "cxx" || ext == "c++" || ext == "cp" || + ext == "C") + return LanguageType::CXX; - return LanguageType::Unknown; -} + return LanguageType::Unknown; + } -LanguageType detectLanguageFromFile(const std::string &path, - llvm::LLVMContext &ctx) -{ + LanguageType detectLanguageFromFile(const std::string& path, llvm::LLVMContext& ctx) { - llvm::SMDiagnostic diag; - if (auto mod = llvm::parseIRFile(path, diag, ctx)) { - return LanguageType::LLVM_IR; + llvm::SMDiagnostic diag; + if (auto mod = llvm::parseIRFile(path, diag, ctx)) + { + return LanguageType::LLVM_IR; + } } - } - - return detectFromExtension(path); -} -AnalysisResult analyzeFile(const std::string &filename, - const AnalysisConfig &config, - llvm::LLVMContext &ctx, - llvm::SMDiagnostic &err) -{ + return detectFromExtension(path); + } - LanguageType lang = detectLanguageFromFile(filename, ctx); - std::unique_ptr mod; + AnalysisResult analyzeFile(const std::string& filename, const AnalysisConfig& config, + llvm::LLVMContext& ctx, llvm::SMDiagnostic& err) + { - // if (verboseLevel >= 1) - // std::cout << "Language: " << ctrace::stack::enumToString(lang) << "\n"; + LanguageType lang = detectLanguageFromFile(filename, ctx); + std::unique_ptr mod; - if (lang != LanguageType::LLVM_IR) - { // if (verboseLevel >= 1) - // std::cout << "Compiling source file to LLVM IR...\n"; - std::vector args; - args.push_back("-emit-llvm"); - args.push_back("-S"); - args.push_back("-g"); - if (lang == LanguageType::CXX) { - args.push_back("-x"); - args.push_back("c++"); - args.push_back("-std=gnu++17"); - } - args.push_back("-fno-discard-value-names"); - args.push_back(filename); - compilerlib::OutputMode mode = compilerlib::OutputMode::ToMemory; - auto res = compilerlib::compile(args, mode); + // std::cout << "Language: " << ctrace::stack::enumToString(lang) << "\n"; - if (!res.success) + if (lang != LanguageType::LLVM_IR) { - std::cerr << "Compilation failed:\n" << res.diagnostics << '\n'; - return AnalysisResult{config, {}}; - } + // if (verboseLevel >= 1) + // std::cout << "Compiling source file to LLVM IR...\n"; + std::vector args; + args.push_back("-emit-llvm"); + args.push_back("-S"); + args.push_back("-g"); + if (lang == LanguageType::CXX) + { + args.push_back("-x"); + args.push_back("c++"); + args.push_back("-std=gnu++17"); + } + args.push_back("-fno-discard-value-names"); + args.push_back(filename); + compilerlib::OutputMode mode = compilerlib::OutputMode::ToMemory; + auto res = compilerlib::compile(args, mode); - if (res.llvmIR.empty()) - { - std::cerr << "No LLVM IR produced by compilerlib::compile\n"; - return AnalysisResult{config, {}}; - } + if (!res.success) + { + std::cerr << "Compilation failed:\n" << res.diagnostics << '\n'; + return AnalysisResult{config, {}}; + } + + if (res.llvmIR.empty()) + { + std::cerr << "No LLVM IR produced by compilerlib::compile\n"; + return AnalysisResult{config, {}}; + } - auto buffer = llvm::MemoryBuffer::getMemBuffer(res.llvmIR, "in_memory_ll"); + auto buffer = llvm::MemoryBuffer::getMemBuffer(res.llvmIR, "in_memory_ll"); - llvm::SMDiagnostic diag; - mod = llvm::parseIR(buffer->getMemBufferRef(), diag, ctx); + llvm::SMDiagnostic diag; + mod = llvm::parseIR(buffer->getMemBufferRef(), diag, ctx); - if (!mod) - { - std::string msg; - llvm::raw_string_ostream os(msg); - diag.print("in_memory_ll", os); - std::cerr << "Failed to parse in-memory LLVM IR:\n" << os.str(); - return AnalysisResult{config, {}}; + if (!mod) + { + std::string msg; + llvm::raw_string_ostream os(msg); + diag.print("in_memory_ll", os); + std::cerr << "Failed to parse in-memory LLVM IR:\n" << os.str(); + return AnalysisResult{config, {}}; + } } - } - if (lang == LanguageType::LLVM_IR) - { - mod = llvm::parseIRFile(filename, err, ctx); - if (!mod) + if (lang == LanguageType::LLVM_IR) { - // on laisse err.print au caller si besoin - return AnalysisResult{config, {}}; + mod = llvm::parseIRFile(filename, err, ctx); + if (!mod) + { + // on laisse err.print au caller si besoin + return AnalysisResult{config, {}}; + } } + return analyzeModule(*mod, config); } - return analyzeModule(*mod, config); -} -// --------------------------------------------------------------------------- -// JSON / SARIF serialization helpers -// --------------------------------------------------------------------------- + // --------------------------------------------------------------------------- + // JSON / SARIF serialization helpers + // --------------------------------------------------------------------------- -namespace { + namespace + { -// Petit helper pour échapper les chaînes JSON. -static std::string jsonEscape(const std::string &s) -{ - std::string out; - out.reserve(s.size() + 16); - for (char c : s) { - switch (c) { - case '\\': out += "\\\\"; break; - case '\"': out += "\\\""; break; - case '\n': out += "\\n"; break; - case '\r': out += "\\r"; break; - case '\t': out += "\\t"; break; - default: - if (static_cast(c) < 0x20) { - char buf[7]; - std::snprintf(buf, sizeof(buf), "\\u%04x", c & 0xFF); - out += buf; - } else { - out += c; + // Petit helper pour échapper les chaînes JSON. + static std::string jsonEscape(const std::string& s) + { + std::string out; + out.reserve(s.size() + 16); + for (char c : s) + { + switch (c) + { + case '\\': + out += "\\\\"; + break; + case '\"': + out += "\\\""; + break; + case '\n': + out += "\\n"; + break; + case '\r': + out += "\\r"; + break; + case '\t': + out += "\\t"; + break; + default: + if (static_cast(c) < 0x20) + { + char buf[7]; + std::snprintf(buf, sizeof(buf), "\\u%04x", c & 0xFF); + out += buf; + } + else + { + out += c; + } + break; + } } - break; + return out; } - } - return out; -} -// Old helper to convert DiagnosticSeverity to string, don't use it anymore. -static const char *severityToJsonString(DiagnosticSeverity sev) -{ - switch (sev) { - case DiagnosticSeverity::Info: return "info"; - case DiagnosticSeverity::Warning: return "warning"; - case DiagnosticSeverity::Error: return "error"; - } - return "info"; -} + // Old helper to convert DiagnosticSeverity to string, don't use it anymore. + static const char* severityToJsonString(DiagnosticSeverity sev) + { + switch (sev) + { + case DiagnosticSeverity::Info: + return "info"; + case DiagnosticSeverity::Warning: + return "warning"; + case DiagnosticSeverity::Error: + return "error"; + } + return "info"; + } -static const char *severityToSarifLevel(DiagnosticSeverity sev) -{ - // SARIF levels: "none", "note", "warning", "error" - switch (sev) { - case DiagnosticSeverity::Info: return "note"; - case DiagnosticSeverity::Warning: return "warning"; - case DiagnosticSeverity::Error: return "error"; - } - return "note"; -} + static const char* severityToSarifLevel(DiagnosticSeverity sev) + { + // SARIF levels: "none", "note", "warning", "error" + switch (sev) + { + case DiagnosticSeverity::Info: + return "note"; + case DiagnosticSeverity::Warning: + return "warning"; + case DiagnosticSeverity::Error: + return "error"; + } + return "note"; + } -} // anonymous namespace + } // anonymous namespace -std::string toJson(const AnalysisResult &result, - const std::string &inputFile) -{ - std::ostringstream os; - os << "{\n"; - os << " \"meta\": {\n"; - os << " \"tool\": \"" << "ctrace-stack-analyzer" << "\",\n"; - os << " \"inputFile\": \"" << jsonEscape(inputFile) << "\",\n"; - os << " \"mode\": \"" << (result.config.mode == AnalysisMode::IR ? "IR" : "ABI") << "\",\n"; - os << " \"stackLimit\": " << result.config.stackLimit << ",\n"; - os << " \"analysisTimeMs\": " << -1 << "\n"; - os << " },\n"; - - // Fonctions - os << " \"functions\": [\n"; - for (std::size_t i = 0; i < result.functions.size(); ++i) { - const auto &f = result.functions[i]; - os << " {\n"; - os << " \"name\": \"" << jsonEscape(f.name) << "\",\n"; - os << " \"localStack\": "; - if (f.localStackUnknown) { - os << "null"; - } else { - os << f.localStack; - } - os << ",\n"; - os << " \"localStackUnknown\": " << (f.localStackUnknown ? "true" : "false") << ",\n"; - os << " \"maxStack\": "; - if (f.maxStackUnknown) { - os << "null"; - } else { - os << f.maxStack; - } - os << ",\n"; - os << " \"maxStackUnknown\": " << (f.maxStackUnknown ? "true" : "false") << ",\n"; - os << " \"hasDynamicAlloca\": " << (f.hasDynamicAlloca ? "true" : "false") << ",\n"; - os << " \"localStack\": "; - if (f.localStackUnknown) { - os << "null"; - } else { - os << f.localStack; + std::string toJson(const AnalysisResult& result, const std::string& inputFile) + { + std::ostringstream os; + os << "{\n"; + os << " \"meta\": {\n"; + os << " \"tool\": \"" << "ctrace-stack-analyzer" << "\",\n"; + os << " \"inputFile\": \"" << jsonEscape(inputFile) << "\",\n"; + os << " \"mode\": \"" << (result.config.mode == AnalysisMode::IR ? "IR" : "ABI") + << "\",\n"; + os << " \"stackLimit\": " << result.config.stackLimit << ",\n"; + os << " \"analysisTimeMs\": " << -1 << "\n"; + os << " },\n"; + + // Fonctions + os << " \"functions\": [\n"; + for (std::size_t i = 0; i < result.functions.size(); ++i) + { + const auto& f = result.functions[i]; + os << " {\n"; + os << " \"name\": \"" << jsonEscape(f.name) << "\",\n"; + os << " \"localStack\": "; + if (f.localStackUnknown) + { + os << "null"; + } + else + { + os << f.localStack; + } + os << ",\n"; + os << " \"localStackUnknown\": " << (f.localStackUnknown ? "true" : "false") + << ",\n"; + os << " \"maxStack\": "; + if (f.maxStackUnknown) + { + os << "null"; + } + else + { + os << f.maxStack; + } + os << ",\n"; + os << " \"maxStackUnknown\": " << (f.maxStackUnknown ? "true" : "false") << ",\n"; + os << " \"hasDynamicAlloca\": " << (f.hasDynamicAlloca ? "true" : "false") + << ",\n"; + os << " \"localStack\": "; + if (f.localStackUnknown) + { + os << "null"; + } + else + { + os << f.localStack; + } + os << ",\n"; + os << " \"localStackUnknown\": " << (f.localStackUnknown ? "true" : "false") + << ",\n"; + os << " \"maxStack\": "; + if (f.maxStackUnknown) + { + os << "null"; + } + else + { + os << f.maxStack; + } + os << ",\n"; + os << " \"maxStackUnknown\": " << (f.maxStackUnknown ? "true" : "false") << ",\n"; + os << " \"hasDynamicAlloca\": " << (f.hasDynamicAlloca ? "true" : "false") + << ",\n"; + os << " \"isRecursive\": " << (f.isRecursive ? "true" : "false") << ",\n"; + os << " \"hasInfiniteSelfRecursion\": " + << (f.hasInfiniteSelfRecursion ? "true" : "false") << ",\n"; + os << " \"exceedsLimit\": " << (f.exceedsLimit ? "true" : "false") << "\n"; + os << " }"; + if (i + 1 < result.functions.size()) + os << ","; + os << "\n"; } - os << ",\n"; - os << " \"localStackUnknown\": " << (f.localStackUnknown ? "true" : "false") << ",\n"; - os << " \"maxStack\": "; - if (f.maxStackUnknown) { - os << "null"; - } else { - os << f.maxStack; + os << " ],\n"; + + // Diagnostics + os << " \"diagnostics\": [\n"; + for (std::size_t i = 0; i < result.diagnostics.size(); ++i) + { + const auto& d = result.diagnostics[i]; + os << " {\n"; + os << " \"id\": \"diag-" << (i + 1) << "\",\n"; + os << " \"severity\": \"" << ctrace::stack::enumToString(d.severity) << "\",\n"; + const std::string ruleId = + d.ruleId.empty() ? std::string(ctrace::stack::enumToString(d.errCode)) : d.ruleId; + os << " \"ruleId\": \"" << jsonEscape(ruleId) << "\",\n"; + + os << " \"location\": {\n"; + os << " \"function\": \"" << jsonEscape(d.funcName) << "\",\n"; + os << " \"startLine\": " << d.line << ",\n"; + os << " \"startColumn\": " << d.column << ",\n"; + os << " \"endLine\": " << d.endLine << ",\n"; + os << " \"endColumn\": " << d.endColumn << "\n"; + os << " },\n"; + + os << " \"details\": {\n"; + os << " \"message\": \"" << jsonEscape(d.message) << "\",\n"; + os << " \"variableAliasing\": ["; + for (std::size_t j = 0; j < d.variableAliasingVec.size(); ++j) + { + os << "\"" << jsonEscape(d.variableAliasingVec[j]) << "\""; + if (j + 1 < d.variableAliasingVec.size()) + os << ", "; + } + os << "]\n"; + os << " }\n"; // <-- ferme "details" + os << " }"; // <-- ferme le diagnostic + if (i + 1 < result.diagnostics.size()) + os << ","; + os << "\n"; } - os << ",\n"; - os << " \"maxStackUnknown\": " << (f.maxStackUnknown ? "true" : "false") << ",\n"; - os << " \"hasDynamicAlloca\": " << (f.hasDynamicAlloca ? "true" : "false") << ",\n"; - os << " \"isRecursive\": " << (f.isRecursive ? "true" : "false") << ",\n"; - os << " \"hasInfiniteSelfRecursion\": " << (f.hasInfiniteSelfRecursion ? "true" : "false") << ",\n"; - os << " \"exceedsLimit\": " << (f.exceedsLimit ? "true" : "false") << "\n"; - os << " }"; - if (i + 1 < result.functions.size()) - os << ","; - os << "\n"; + os << " ]\n"; + os << "}\n"; + return os.str(); } - os << " ],\n"; - // Diagnostics - os << " \"diagnostics\": [\n"; - for (std::size_t i = 0; i < result.diagnostics.size(); ++i) { - const auto &d = result.diagnostics[i]; + std::string toSarif(const AnalysisResult& result, const std::string& inputFile, + const std::string& toolName, const std::string& toolVersion) + { + std::ostringstream os; + os << "{\n"; + os << " \"version\": \"2.1.0\",\n"; + os << " \"$schema\": " + "\"https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0.json\",\n"; + os << " \"runs\": [\n"; os << " {\n"; - os << " \"id\": \"diag-" << (i + 1) << "\",\n"; - os << " \"severity\": \"" << ctrace::stack::enumToString(d.severity) << "\",\n"; - const std::string ruleId = d.ruleId.empty() - ? std::string(ctrace::stack::enumToString(d.errCode)) - : d.ruleId; - os << " \"ruleId\": \"" << jsonEscape(ruleId) << "\",\n"; - - os << " \"location\": {\n"; - os << " \"function\": \"" << jsonEscape(d.funcName) << "\",\n"; - os << " \"startLine\": " << d.line << ",\n"; - os << " \"startColumn\": " << d.column << ",\n"; - os << " \"endLine\": " << d.endLine << ",\n"; - os << " \"endColumn\": " << d.endColumn << "\n"; + os << " \"tool\": {\n"; + os << " \"driver\": {\n"; + os << " \"name\": \"" << jsonEscape(toolName) << "\",\n"; + os << " \"version\": \"" << jsonEscape(toolVersion) << "\"\n"; + os << " }\n"; os << " },\n"; + os << " \"results\": [\n"; - os << " \"details\": {\n"; - os << " \"message\": \"" << jsonEscape(d.message) << "\",\n"; - os << " \"variableAliasing\": ["; - for (std::size_t j = 0; j < d.variableAliasingVec.size(); ++j) { - os << "\"" << jsonEscape(d.variableAliasingVec[j]) << "\""; - if (j + 1 < d.variableAliasingVec.size()) - os << ", "; + for (std::size_t i = 0; i < result.diagnostics.size(); ++i) + { + const auto& d = result.diagnostics[i]; + os << " {\n"; + // Pour le moment, un seul ruleId générique; tu pourras le spécialiser plus tard. + const std::string ruleId = + d.ruleId.empty() ? std::string(ctrace::stack::enumToString(d.errCode)) : d.ruleId; + os << " \"ruleId\": \"" << jsonEscape(ruleId) << "\",\n"; + os << " \"level\": \"" << severityToSarifLevel(d.severity) << "\",\n"; + os << " \"message\": { \"text\": \"" << jsonEscape(d.message) << "\" },\n"; + os << " \"locations\": [\n"; + os << " {\n"; + os << " \"physicalLocation\": {\n"; + os << " \"artifactLocation\": { \"uri\": \"" << jsonEscape(inputFile) + << "\" },\n"; + os << " \"region\": {\n"; + os << " \"startLine\": " << d.line << ",\n"; + os << " \"startColumn\": " << d.column << "\n"; + os << " }\n"; + os << " }\n"; + os << " }\n"; + os << " ]\n"; + os << " }"; + if (i + 1 < result.diagnostics.size()) + os << ","; + os << "\n"; } - os << "]\n"; - os << " }\n"; // <-- ferme "details" - os << " }"; // <-- ferme le diagnostic - if (i + 1 < result.diagnostics.size()) - os << ","; - os << "\n"; - } - os << " ]\n"; - os << "}\n"; - return os.str(); -} - -std::string toSarif(const AnalysisResult &result, - const std::string &inputFile, - const std::string &toolName, - const std::string &toolVersion) -{ - std::ostringstream os; - os << "{\n"; - os << " \"version\": \"2.1.0\",\n"; - os << " \"$schema\": \"https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0.json\",\n"; - os << " \"runs\": [\n"; - os << " {\n"; - os << " \"tool\": {\n"; - os << " \"driver\": {\n"; - os << " \"name\": \"" << jsonEscape(toolName) << "\",\n"; - os << " \"version\": \"" << jsonEscape(toolVersion) << "\"\n"; - os << " }\n"; - os << " },\n"; - os << " \"results\": [\n"; - - for (std::size_t i = 0; i < result.diagnostics.size(); ++i) { - const auto &d = result.diagnostics[i]; - os << " {\n"; - // Pour le moment, un seul ruleId générique; tu pourras le spécialiser plus tard. - const std::string ruleId = d.ruleId.empty() - ? std::string(ctrace::stack::enumToString(d.errCode)) - : d.ruleId; - os << " \"ruleId\": \"" << jsonEscape(ruleId) << "\",\n"; - os << " \"level\": \"" << severityToSarifLevel(d.severity) << "\",\n"; - os << " \"message\": { \"text\": \"" << jsonEscape(d.message) << "\" },\n"; - os << " \"locations\": [\n"; - os << " {\n"; - os << " \"physicalLocation\": {\n"; - os << " \"artifactLocation\": { \"uri\": \"" << jsonEscape(inputFile) << "\" },\n"; - os << " \"region\": {\n"; - os << " \"startLine\": " << d.line << ",\n"; - os << " \"startColumn\": " << d.column << "\n"; - os << " }\n"; - os << " }\n"; - os << " }\n"; - os << " ]\n"; - os << " }"; - if (i + 1 < result.diagnostics.size()) - os << ","; - os << "\n"; - } - os << " ]\n"; - os << " }\n"; - os << " ]\n"; - os << "}\n"; + os << " ]\n"; + os << " }\n"; + os << " ]\n"; + os << "}\n"; - return os.str(); -} + return os.str(); + } } // namespace ctrace::stack diff --git a/src/mangle.cpp b/src/mangle.cpp index 7918bb4..8c93e90 100644 --- a/src/mangle.cpp +++ b/src/mangle.cpp @@ -1,10 +1,10 @@ #include "mangle.hpp" -namespace ctrace_tools { +namespace ctrace_tools +{ - std::string mangleFunction(const std::string& namespaceName, - const std::string& functionName, - const std::vector& paramTypes) + std::string mangleFunction(const std::string& namespaceName, const std::string& functionName, + const std::vector& paramTypes) { std::stringstream mangled; @@ -22,7 +22,8 @@ namespace ctrace_tools { mangled << functionName.length() << functionName; // Encoder les types de paramètres - for (const std::string& param : paramTypes) { + for (const std::string& param : paramTypes) + { if (param == "int") { mangled << "i"; @@ -51,8 +52,9 @@ namespace ctrace_tools { { mangled << "v"; } - else { - // Pour les types complexes ou non reconnus, encoder avec longueur + nom + else + { + // Pour les types complexes ou non reconnus, encoder avec longueur + nom mangled << param.length() << param; } } @@ -66,17 +68,15 @@ namespace ctrace_tools { return mangled.str(); } - std::string demangle(const char *name) + std::string demangle(const char* name) { int status = 0; char* demangled = abi::__cxa_demangle(name, nullptr, nullptr, &status); - std::string result = (status == 0 && demangled) - ? demangled - : name; + std::string result = (status == 0 && demangled) ? demangled : name; free(demangled); return result; } -}; +}; // namespace ctrace_tools diff --git a/test/alloca/oversized-constant.c b/test/alloca/oversized-constant.c index fa94723..bd4e3cf 100644 --- a/test/alloca/oversized-constant.c +++ b/test/alloca/oversized-constant.c @@ -9,7 +9,7 @@ void big_alloca(void) // allocation performed via alloca/VLA; stack usage grows with runtime value // requested stack size: 2097152 bytes // exceeds safety threshold of 1048576 bytes (stack limit: 8388608 bytes) - char *buf = (char *)alloca(n); + char* buf = (char*)alloca(n); if (buf) buf[0] = 0; } diff --git a/test/alloca/recursive-controlled-alloca.c b/test/alloca/recursive-controlled-alloca.c index 4f9c4ac..421ff05 100644 --- a/test/alloca/recursive-controlled-alloca.c +++ b/test/alloca/recursive-controlled-alloca.c @@ -9,7 +9,7 @@ int rec(size_t n) // size is unbounded at compile time // function is recursive; this allocation repeats at each recursion depth and can exhaust the stack // size depends on user-controlled input (function argument or non-local value) - char *p = (char *)alloca(n); + char* p = (char*)alloca(n); if (n == 0) return 0; return 1 + rec(n - 1); diff --git a/test/alloca/recursive-infinite-alloca.c b/test/alloca/recursive-infinite-alloca.c index 44e612d..c4b73ee 100644 --- a/test/alloca/recursive-infinite-alloca.c +++ b/test/alloca/recursive-infinite-alloca.c @@ -9,7 +9,7 @@ void boom(size_t n) // size is unbounded at compile time // function is infinitely recursive; this alloca runs at every frame and guarantees stack overflow // size depends on user-controlled input (function argument or non-local value) - char *p = (char *)alloca(n); + char* p = (char*)alloca(n); boom(n); } diff --git a/test/alloca/user-controlled.c b/test/alloca/user-controlled.c index 8afd87e..7313083 100644 --- a/test/alloca/user-controlled.c +++ b/test/alloca/user-controlled.c @@ -8,7 +8,7 @@ void foo(size_t n) // allocation performed via alloca/VLA; stack usage grows with runtime value // size is unbounded at compile time // size depends on user-controlled input (function argument or non-local value) - char *buf = (char *)alloca(n); + char* buf = (char*)alloca(n); if (buf) buf[0] = 0; } diff --git a/test/bound-storage/bound-storage-for-statement.c b/test/bound-storage/bound-storage-for-statement.c index c8d0ce3..d98d672 100644 --- a/test/bound-storage/bound-storage-for-statement.c +++ b/test/bound-storage/bound-storage-for-statement.c @@ -1,14 +1,15 @@ int main(void) { char test[10]; - char *ptr = test; + char* ptr = test; // at line 12, column 17 // [!!] potential stack buffer overflow on variable 'test' (size 10) // alias path: test // index variable may go up to 19 (array last valid index: 9) // (this is a write access) - for (int i = 0; i < 20; i++) { + for (int i = 0; i < 20; i++) + { test[i] = 'a'; } @@ -21,7 +22,8 @@ int main(void) test[i] = 'a'; // OK - for (int i = 0; i < 10; i++) { + for (int i = 0; i < 10; i++) + { test[i] = 'b'; } @@ -30,13 +32,14 @@ int main(void) // alias path: test -> arraydecay -> ptr // index variable may go up to 19 (array last valid index: 9) // (this is a write access) - for (int i = 0; i < 20; i++) { + for (int i = 0; i < 20; i++) + { ptr[i] = 'a'; } // [!Info] multiple stores to stack buffer 'test' in this function (4 store instruction(s), 4 distinct index expression(s)) // stores use different index expressions; verify indices are correct and non-overlapping int n = 6; - char buf[n]; // alloca variable + char buf[n]; // alloca variable return 0; } diff --git a/test/bound-storage/bound-storage.c b/test/bound-storage/bound-storage.c index 6a9d1b1..f9d116c 100644 --- a/test/bound-storage/bound-storage.c +++ b/test/bound-storage/bound-storage.c @@ -11,7 +11,7 @@ int main(void) // (this is a write access) test[11] = 'a'; - test[9] = 'b'; // OK + test[9] = 'b'; // OK // at line 21, column 14 // [!!] potential stack buffer overflow on variable 'test' (size 10) @@ -20,7 +20,7 @@ int main(void) // (this is a write access) test[-1] = 'c'; - test[11 - 2] = 'd'; // OK + test[11 - 2] = 'd'; // OK return 0; } diff --git a/test/bound-storage/deep-alias.c b/test/bound-storage/deep-alias.c index e4de467..2ae1e78 100644 --- a/test/bound-storage/deep-alias.c +++ b/test/bound-storage/deep-alias.c @@ -1,16 +1,17 @@ -void deep_alias(char *src) +void deep_alias(char* src) { char buf[10]; - char *p1 = buf; - char *p2 = p1; - char **pp = &p2; + char* p1 = buf; + char* p2 = p1; + char** pp = &p2; // at line 14, column 18 // [!!] potential stack buffer overflow on variable 'buf' (size 10) // alias path: buf -> arraydecay -> p1 -> p2 -> pp // index variable may go up to 19 (array last valid index: 9) // (this is a write access) - for (int i = 0; i < 20; ++i) { + for (int i = 0; i < 20; ++i) + { (*pp)[i] = src[i]; } } diff --git a/test/bound-storage/indirection-profonde-aliasing.c b/test/bound-storage/indirection-profonde-aliasing.c index 3ad1d62..85a18c5 100644 --- a/test/bound-storage/indirection-profonde-aliasing.c +++ b/test/bound-storage/indirection-profonde-aliasing.c @@ -3,8 +3,8 @@ int main(void) { char test[10]; - char *ptr = test; - char **pp = &ptr; + char* ptr = test; + char** pp = &ptr; // at line 13, column 15 // [!!] potential stack buffer overflow on variable 'test' (size 10) // alias path: test -> arraydecay -> ptr @@ -23,7 +23,8 @@ int main(void) // alias path: test // index variable may go up to 19 (array last valid index: 9) // (this is a write access) - for (int i = 0; i < 20; i++) { + for (int i = 0; i < 20; i++) + { test[i] = 'a'; } @@ -36,7 +37,8 @@ int main(void) test[i] = 'a'; // OK - for (int i = 0; i < 10; i++) { + for (int i = 0; i < 10; i++) + { test[i] = 'b'; } @@ -45,11 +47,12 @@ int main(void) // alias path: test -> arraydecay -> ptr // index variable may go up to 19 (array last valid index: 9) // (this is a write access) - for (int i = 0; i < 20; i++) { + for (int i = 0; i < 20; i++) + { ptr[i] = 'a'; } int n = 6; - char buf[n]; // alloca variable + char buf[n]; // alloca variable return 0; } diff --git a/test/bound-storage/ranges_test.c b/test/bound-storage/ranges_test.c index cf9c5f1..0a38fae 100644 --- a/test/bound-storage/ranges_test.c +++ b/test/bound-storage/ranges_test.c @@ -87,8 +87,10 @@ void nested_if_overflow(int i) // alias path: buf // index variable may go up to 10 (array last valid index: 7) // (this is a write access) - if (i <= 10) { - if (i > 5) { + if (i <= 10) + { + if (i > 5) + { buf[i] = 'E'; } } @@ -99,8 +101,10 @@ void nested_if_ok(int i) { char buf[16]; - if (i <= 10) { - if (i > 5) { + if (i <= 10) + { + if (i > 5) + { buf[i] = 'F'; } } @@ -159,7 +163,8 @@ void unreachable_example(void) // constant index 11 is out of bounds (0..9) // (this is a write access) // [info] this access appears unreachable at runtime (condition is always false for this branch) - if (i > 10) { // condition fausse à l’exécution + if (i > 10) + { // condition fausse à l’exécution buf[11] = 'J'; } } @@ -172,7 +177,7 @@ void unreachable_example(void) void alias_lb_ub(int i) { char buf[10]; - char *p = buf; + char* p = buf; // at line 189, column 14 // [!!] potential stack buffer overflow on variable 'buf' (size 10) @@ -193,7 +198,7 @@ void alias_lb_ub(int i) void alias_ok(int i) { char buf[10]; - char *p = buf; + char* p = buf; if (i >= 0 && i < 10) p[i] = 'L'; diff --git a/test/bound-storage/struct_array_overflow.c b/test/bound-storage/struct_array_overflow.c index 03959d7..a74d1c8 100644 --- a/test/bound-storage/struct_array_overflow.c +++ b/test/bound-storage/struct_array_overflow.c @@ -1,15 +1,16 @@ #include -struct S { +struct S +{ char buf[10]; - int x; + int x; }; void ok_direct(void) { struct S s; for (int i = 0; i < 10; ++i) - s.buf[i] = 'A'; // OK + s.buf[i] = 'A'; // OK } void overflow_eq_10(void) @@ -21,7 +22,7 @@ void overflow_eq_10(void) // index variable may go up to 10 (array last valid index: 9) // (this is a write access) for (int i = 0; i <= 10; ++i) - s.buf[i] = 'B'; // i == 10 -> overflow + s.buf[i] = 'B'; // i == 10 -> overflow } void overflow_const_index(void) @@ -32,7 +33,7 @@ void overflow_const_index(void) // alias path: s -> buf // constant index 11 is out of bounds (0..9) // (this is a write access) - s.buf[11] = 'C'; // overflow constant + s.buf[11] = 'C'; // overflow constant } void nested_if_overflow(void) @@ -45,8 +46,8 @@ void nested_if_overflow(void) // alias path: s -> buf // index variable may go up to 15 (array last valid index: 9) // (this is a write access) - if (i > 5 && i <= 15) // UB = 15 - s.buf[i] = 'D'; // overflow + if (i > 5 && i <= 15) // UB = 15 + s.buf[i] = 'D'; // overflow } int main(void) diff --git a/test/cpy-buffer/bad-usage-memcpy.c b/test/cpy-buffer/bad-usage-memcpy.c index fb3b533..1b4e8af 100644 --- a/test/cpy-buffer/bad-usage-memcpy.c +++ b/test/cpy-buffer/bad-usage-memcpy.c @@ -4,7 +4,7 @@ // [!!] potential stack buffer overflow in memcpy on variable '' // destination stack buffer size: 10 bytes // requested 20 bytes to be copied/initialized -void foo(char *src) +void foo(char* src) { char buf[10]; memcpy(buf, src, 20); diff --git a/test/cpy-buffer/bad-usage-memset.c b/test/cpy-buffer/bad-usage-memset.c index 31e07ff..a3e505e 100644 --- a/test/cpy-buffer/bad-usage-memset.c +++ b/test/cpy-buffer/bad-usage-memset.c @@ -4,7 +4,7 @@ // [!!] potential stack buffer overflow in memset on variable '' // destination stack buffer size: 10 bytes // requested 100 bytes to be copied/initialized -void foo(char *src) +void foo(char* src) { char buf[10]; memset(buf, 0, 100); diff --git a/test/escape-stack/direct-callback.c b/test/escape-stack/direct-callback.c index 577805a..ce9cafc 100644 --- a/test/escape-stack/direct-callback.c +++ b/test/escape-stack/direct-callback.c @@ -1,5 +1,5 @@ // case_call_arg.c -void sink(char *p); +void sink(char* p); void pass_to_sink(void) { @@ -7,5 +7,5 @@ void pass_to_sink(void) // at line 10, column 5 // [!!] stack pointer escape: address of variable 'buf' escapes this function // address passed as argument to function 'sink' (callee may capture the pointer beyond this function) - sink(buf); // le callee peut capturer le pointeur + sink(buf); // le callee peut capturer le pointeur } diff --git a/test/escape-stack/global-buf.c b/test/escape-stack/global-buf.c index 6c05cd3..be91b83 100644 --- a/test/escape-stack/global-buf.c +++ b/test/escape-stack/global-buf.c @@ -1,5 +1,5 @@ // tests/stack_escape_global.c -static char *g; +static char* g; void set_global(void) { @@ -7,7 +7,7 @@ void set_global(void) // at line 10, column 7 // [!!] stack pointer escape: address of variable 'buf' escapes this function // stored into global variable 'g' (pointer may be used after the function returns) - g = buf; // warning attendu: store_global + g = buf; // warning attendu: store_global } int main(void) diff --git a/test/escape-stack/global-struct.c b/test/escape-stack/global-struct.c index d5eb36b..dd13162 100644 --- a/test/escape-stack/global-struct.c +++ b/test/escape-stack/global-struct.c @@ -1,5 +1,6 @@ -struct Holder { - char *p; +struct Holder +{ + char* p; }; struct Holder G; @@ -10,5 +11,5 @@ void store_in_global_field(void) // at line 13, column 9 // [!!] stack pointer escape: address of variable 'buf' escapes this function // stored into global variable 'G' (pointer may be used after the function returns) - G.p = buf; // leak : G is global + G.p = buf; // leak : G is global } diff --git a/test/escape-stack/indirect-callback.c b/test/escape-stack/indirect-callback.c index cd7baf5..acff2b6 100644 --- a/test/escape-stack/indirect-callback.c +++ b/test/escape-stack/indirect-callback.c @@ -1,4 +1,4 @@ -typedef void (*cb_t)(char *); +typedef void (*cb_t)(char*); void use_callback(cb_t cb) { @@ -6,5 +6,5 @@ void use_callback(cb_t cb) // at line 9, column 5 // [!!] stack pointer escape: address of variable 'buf' escapes this function // address passed as argument to an indirect call (callback may capture the pointer beyond this function) - cb(buf); // potential leak by callback + cb(buf); // potential leak by callback } diff --git a/test/escape-stack/out_param.c b/test/escape-stack/out_param.c index 5910181..9b46005 100644 --- a/test/escape-stack/out_param.c +++ b/test/escape-stack/out_param.c @@ -1,14 +1,14 @@ -void leak_out_param(char **out) +void leak_out_param(char** out) { char buf[10]; // at line 7, column 10 // [!!] stack pointer escape: address of variable 'buf' escapes this function // stored through a non-local pointer (e.g. via an out-parameter; pointer may outlive this function) - *out = buf; // fuite via paramètre de sortie + *out = buf; // fuite via paramètre de sortie } -void safe_out_param(char **out) +void safe_out_param(char** out) { - char *local = 0; // pointeur, mais pas de stack buffer derrière - *out = local; // pas une adresse de variable de stack + char* local = 0; // pointeur, mais pas de stack buffer derrière + *out = local; // pas une adresse de variable de stack } diff --git a/test/escape-stack/return-buf.c b/test/escape-stack/return-buf.c index bdfbc1d..75b82d4 100644 --- a/test/escape-stack/return-buf.c +++ b/test/escape-stack/return-buf.c @@ -1,15 +1,15 @@ -char *ret_buf(void) +char* ret_buf(void) { char buf[10]; // at line 7, column 5 // [!!] stack pointer escape: address of variable 'buf' escapes this function // escape via return statement (pointer to stack returned to caller) - return buf; // warning attendu: return + return buf; // warning attendu: return } int main(void) { - char *p = ret_buf(); + char* p = ret_buf(); (void)p; return 0; } diff --git a/test/escape-stack/stack_escape.c b/test/escape-stack/stack_escape.c index 727807a..8afa3ce 100644 --- a/test/escape-stack/stack_escape.c +++ b/test/escape-stack/stack_escape.c @@ -1,13 +1,14 @@ -char *g_ptr; +char* g_ptr; -struct Holder { - char *p; +struct Holder +{ + char* p; }; struct Holder G; -typedef void (*cb_t)(char *); +typedef void (*cb_t)(char*); -char *ret_buf(void) +char* ret_buf(void) { char buf[10]; return buf; @@ -25,15 +26,15 @@ void store_in_global_field(void) G.p = buf; } -void leak_out_param(char **out) +void leak_out_param(char** out) { char buf[10]; *out = buf; } -void safe_out_param(char **out) +void safe_out_param(char** out) { - char *local = 0; + char* local = 0; *out = local; } @@ -43,7 +44,7 @@ void use_callback(cb_t cb) cb(buf); } -void sink(char *p); +void sink(char* p); void pass_to_sink(void) { @@ -54,14 +55,14 @@ void pass_to_sink(void) void local_alias_only(void) { char buf[10]; - char *p = buf; - char **pp = &p; + char* p = buf; + char** pp = &p; (void)pp; } int main(void) { - char *p; + char* p; leak_out_param(&p); use_callback((cb_t)0); pass_to_sink(); diff --git a/test/offset_of-container_of/container_of_correct_member_offset_ok.c b/test/offset_of-container_of/container_of_correct_member_offset_ok.c index 09b5d04..21f10d2 100644 --- a/test/offset_of-container_of/container_of_correct_member_offset_ok.c +++ b/test/offset_of-container_of/container_of_correct_member_offset_ok.c @@ -2,7 +2,8 @@ #include #include -struct A { +struct A +{ int32_t a; int32_t b; int32_t c; @@ -11,12 +12,12 @@ struct A { int main(void) { - struct A obj = { .a = 11, .b = 22, .c = 33, .i = 44 }; + struct A obj = {.a = 11, .b = 22, .c = 33, .i = 44}; - int32_t *pb = &obj.b; + int32_t* pb = &obj.b; /* CORRECT: subtract offset of the CORRECT member (b). */ - struct A *good_base = (struct A *)((char *)pb - offsetof(struct A, b)); + struct A* good_base = (struct A*)((char*)pb - offsetof(struct A, b)); /* This should work correctly */ printf("%d\n", good_base->a); diff --git a/test/offset_of-container_of/container_of_wrong_member_offset_error.c b/test/offset_of-container_of/container_of_wrong_member_offset_error.c index 1d78219..3ccaeed 100644 --- a/test/offset_of-container_of/container_of_wrong_member_offset_error.c +++ b/test/offset_of-container_of/container_of_wrong_member_offset_error.c @@ -12,9 +12,9 @@ struct A int main(void) { - struct A obj = { .a = 11, .b = 22, .c = 33, .i = 44 }; + struct A obj = {.a = 11, .b = 22, .c = 33, .i = 44}; - int32_t *pb = &obj.b; + int32_t* pb = &obj.b; /* Bug: subtract offset of the WRONG member (i instead of b). */ // at line 28, column 50 @@ -25,7 +25,7 @@ int main(void) // target type: ptr // [ERROR] derived pointer points OUTSIDE the valid object range // (this will cause undefined behavior if dereferenced) - struct A *bad_base = (struct A *)((char *)pb - offsetof(struct A, i)); + struct A* bad_base = (struct A*)((char*)pb - offsetof(struct A, i)); /* UB: bad_base is not guaranteed to point to a valid struct A object. */ // at line 39, column 30 diff --git a/test/offset_of-container_of/container_of_wrong_offset_and_ok.c b/test/offset_of-container_of/container_of_wrong_offset_and_ok.c index d993855..75afe2e 100644 --- a/test/offset_of-container_of/container_of_wrong_offset_and_ok.c +++ b/test/offset_of-container_of/container_of_wrong_offset_and_ok.c @@ -3,15 +3,15 @@ struct MyStruct { - int32_t field_a; // offset 0 - int32_t field_b; // offset 4 - int32_t field_c; // offset 8 + int32_t field_a; // offset 0 + int32_t field_b; // offset 4 + int32_t field_c; // offset 8 }; void test_invalid_container_of(void) { struct MyStruct obj; - int32_t *ptr_b = &obj.field_b; + int32_t* ptr_b = &obj.field_b; // BUG: using wrong offset (8 instead of 4) // at line 25, column 69 @@ -22,7 +22,7 @@ void test_invalid_container_of(void) // target type: ptr // [ERROR] derived pointer points OUTSIDE the valid object range // (this will cause undefined behavior if dereferenced) - struct MyStruct *wrong_base = (struct MyStruct *)((char *)ptr_b - 8); + struct MyStruct* wrong_base = (struct MyStruct*)((char*)ptr_b - 8); // at line 35, column 17 // [!!] potential UB: invalid base reconstruction via offsetof/container_of @@ -32,18 +32,18 @@ void test_invalid_container_of(void) // target type: ptr // [ERROR] derived pointer points OUTSIDE the valid object range // (this will cause undefined behavior if dereferenced) - wrong_base->field_a = 42; // UB: out of bounds access + wrong_base->field_a = 42; // UB: out of bounds access } void test_valid_container_of(void) { struct MyStruct obj; - int32_t *ptr_b = &obj.field_b; + int32_t* ptr_b = &obj.field_b; // CORRECT: using correct offset (4) - struct MyStruct *correct_base = (struct MyStruct *)((char *)ptr_b - 4); + struct MyStruct* correct_base = (struct MyStruct*)((char*)ptr_b - 4); - correct_base->field_a = 42; // OK + correct_base->field_a = 42; // OK } int main(void) diff --git a/test/offset_of-container_of/gep_positive_offset_wrong_base_warn.c b/test/offset_of-container_of/gep_positive_offset_wrong_base_warn.c index da1646b..ab8081a 100644 --- a/test/offset_of-container_of/gep_positive_offset_wrong_base_warn.c +++ b/test/offset_of-container_of/gep_positive_offset_wrong_base_warn.c @@ -12,7 +12,7 @@ struct A int test_gep_positive_offset_missed(void) { struct A obj = {0}; - int32_t *pb = &obj.b; + int32_t* pb = &obj.b; // Wrong reconstruction with a positive offset (+4). // Correct behavior: diagnostic (base points inside the object, not at the base). @@ -24,7 +24,7 @@ int test_gep_positive_offset_missed(void) // target type: ptr // [WARNING] unable to verify that derived pointer points to a valid object // (potential undefined behavior if offset is incorrect) - struct A *bad_base = (struct A *)((char *)pb + 4); + struct A* bad_base = (struct A*)((char*)pb + 4); // Expected: invalid base reconstruction diagnostic. // at line 38, column 22 diff --git a/test/offset_of-container_of/inttoptr_multipath_offset_mismatch_warn.c b/test/offset_of-container_of/inttoptr_multipath_offset_mismatch_warn.c index 6298f88..41ac55b 100644 --- a/test/offset_of-container_of/inttoptr_multipath_offset_mismatch_warn.c +++ b/test/offset_of-container_of/inttoptr_multipath_offset_mismatch_warn.c @@ -16,7 +16,7 @@ int test_multipath_select(int cond) // Multi-path source: either &obj.b or &obj.c. // Correct behavior: diagnostic because when cond == 0, the base // reconstruction uses the wrong member offset. - int32_t *p = cond ? &obj.b : &obj.c; + int32_t* p = cond ? &obj.b : &obj.c; uintptr_t addr = (uintptr_t)p; addr -= offsetof(struct A, b); // uses offset of b @@ -28,7 +28,7 @@ int test_multipath_select(int cond) // target type: ptr // [WARNING] unable to verify that derived pointer points to a valid object // (potential undefined behavior if offset is incorrect) - struct A *base = (struct A *)addr; + struct A* base = (struct A*)addr; // Expected: invalid base reconstruction diagnostic. return base->a; diff --git a/test/offset_of-container_of/inttoptr_ptrtoint_wrong_offset_error.c b/test/offset_of-container_of/inttoptr_ptrtoint_wrong_offset_error.c index 3dd681c..6d1127d 100644 --- a/test/offset_of-container_of/inttoptr_ptrtoint_wrong_offset_error.c +++ b/test/offset_of-container_of/inttoptr_ptrtoint_wrong_offset_error.c @@ -1,16 +1,17 @@ #include #include -struct Data { - int x; // offset 0 - int y; // offset 4 - int z; // offset 8 +struct Data +{ + int x; // offset 0 + int y; // offset 4 + int z; // offset 8 }; void test_ptrtoint_pattern(void) { struct Data obj; - int *ptr_y = &obj.y; + int* ptr_y = &obj.y; // Pattern: inttoptr(ptrtoint(ptr) - offset) // BUG: using wrong offset (8 instead of 4) @@ -24,12 +25,13 @@ void test_ptrtoint_pattern(void) // target type: ptr // [ERROR] derived pointer points OUTSIDE the valid object range // (this will cause undefined behavior if dereferenced) - struct Data *wrong_base = (struct Data *)addr; + struct Data* wrong_base = (struct Data*)addr; - wrong_base->x = 100; // UB: out of bounds + wrong_base->x = 100; // UB: out of bounds } -int main(void) { +int main(void) +{ test_ptrtoint_pattern(); return 0; } diff --git a/test/offset_of-container_of/inttoptr_rhs_sub_ignore.c b/test/offset_of-container_of/inttoptr_rhs_sub_ignore.c index 27be519..842dfdc 100644 --- a/test/offset_of-container_of/inttoptr_rhs_sub_ignore.c +++ b/test/offset_of-container_of/inttoptr_rhs_sub_ignore.c @@ -1,7 +1,8 @@ #include #include -struct A { +struct A +{ int32_t a; int32_t b; int32_t c; @@ -11,12 +12,12 @@ struct A { int test_ptrtoint_rhs_sub(void) { struct A obj = {0}; - int32_t *pb = &obj.b; + int32_t* pb = &obj.b; // ptrtoint is on the RHS: C - ptrtoint(P). // Correct behavior: ignore this pattern (not a container_of/offsetof reconstruction). uintptr_t addr = 64u - (uintptr_t)pb; - struct A *base = (struct A *)addr; + struct A* base = (struct A*)addr; // Expected: no invalid base reconstruction diagnostic. return base->a; diff --git a/test/offset_of-container_of/inttoptr_through_stack_slot_error.c b/test/offset_of-container_of/inttoptr_through_stack_slot_error.c index 180d033..2ab38f1 100644 --- a/test/offset_of-container_of/inttoptr_through_stack_slot_error.c +++ b/test/offset_of-container_of/inttoptr_through_stack_slot_error.c @@ -2,7 +2,8 @@ #include #include -struct A { +struct A +{ int32_t a; int32_t b; int32_t c; @@ -13,8 +14,8 @@ int test_inttoptr_load_store(void) { struct A obj = {0}; - int32_t *tmp = &obj.b; - int32_t *p = tmp; // forces load/store chain at -O0 + int32_t* tmp = &obj.b; + int32_t* p = tmp; // forces load/store chain at -O0 uintptr_t addr = (uintptr_t)p; addr -= offsetof(struct A, i); // wrong offset (12 instead of 4) @@ -26,7 +27,7 @@ int test_inttoptr_load_store(void) // target type: ptr // [ERROR] derived pointer points OUTSIDE the valid object range // (this will cause undefined behavior if dereferenced) - struct A *bad_base = (struct A *)addr; + struct A* bad_base = (struct A*)addr; return bad_base->a; } diff --git a/test/offset_of-container_of/inttoptr_unused_reconstructed_ptr_no_diag.c b/test/offset_of-container_of/inttoptr_unused_reconstructed_ptr_no_diag.c index cd11c8f..30bfac9 100644 --- a/test/offset_of-container_of/inttoptr_unused_reconstructed_ptr_no_diag.c +++ b/test/offset_of-container_of/inttoptr_unused_reconstructed_ptr_no_diag.c @@ -1,7 +1,8 @@ #include #include -struct A { +struct A +{ int32_t a; int32_t b; int32_t c; @@ -11,15 +12,16 @@ struct A { int test_inttoptr_unused_pointer_noise(void) { struct A obj = {0}; - int32_t *pb = &obj.b; + int32_t* pb = &obj.b; uintptr_t addr = (uintptr_t)pb; addr -= offsetof(struct A, b); // correct offset, should reconstruct obj - struct A *base = (struct A *)addr; + struct A* base = (struct A*)addr; // No dereference or use as struct A*. // Correct behavior: no diagnostic (avoid noise when unused). - if ((uintptr_t)base == 0u) { + if ((uintptr_t)base == 0u) + { return 1; } return 0; diff --git a/test/pointer_reference-const_correctness/advanced-cases.c b/test/pointer_reference-const_correctness/advanced-cases.c index 5c0af6f..65b4d2c 100644 --- a/test/pointer_reference-const_correctness/advanced-cases.c +++ b/test/pointer_reference-const_correctness/advanced-cases.c @@ -2,14 +2,18 @@ #include #include -void consume_const(const int *p) { - if (p) { +void consume_const(const int* p) +{ + if (p) + { (void)*p; } } -void consume_mut(int *p) { - if (p) { +void consume_mut(int* p) +{ + if (p) + { *p = 1; } } @@ -18,11 +22,13 @@ void consume_mut(int *p) { // [!]ConstParameterNotModified.Pointer: parameter 'p' in function 'caller_const' is never used to modify the pointed object // current type: int *p // suggested type: const int *p -void caller_const(int *p) { +void caller_const(int* p) +{ consume_const(p); } -void caller_mut(int *p) { +void caller_mut(int* p) +{ consume_mut(p); } @@ -30,30 +36,36 @@ void caller_mut(int *p) { // [!]ConstParameterNotModified.Pointer: parameter 'p' in function 'read_only' is never used to modify the pointed object // current type: int *p // suggested type: const int *p -void read_only(int *p) { +void read_only(int* p) +{ int v = *p; printf("%d\n", v); } -void variadic_use(int *p) { - printf("%p\n", (void *)p); +void variadic_use(int* p) +{ + printf("%p\n", (void*)p); } // at line 46, column 0 // [!]ConstParameterNotModified.Pointer: parameter 'reg' in function 'read_mmio' is never used to modify the pointed object // current type: volatile int *reg // suggested type: const volatile int *reg -void read_mmio(volatile int *reg) { +void read_mmio(volatile int* reg) +{ int v = *reg; (void)v; } -void takes_double(int **pp) { - if (pp) { +void takes_double(int** pp) +{ + if (pp) + { (void)*pp; } } -void takes_void(void *p) { +void takes_void(void* p) +{ (void)p; } diff --git a/test/pointer_reference-const_correctness/advanced-cases.cpp b/test/pointer_reference-const_correctness/advanced-cases.cpp index 4c7f6f4..c002d99 100644 --- a/test/pointer_reference-const_correctness/advanced-cases.cpp +++ b/test/pointer_reference-const_correctness/advanced-cases.cpp @@ -1,10 +1,12 @@ #include -void consume_ref(const int &v) { +void consume_ref(const int& v) +{ (void)v; } -void consume_ref_mut(int &v) { +void consume_ref_mut(int& v) +{ v += 1; } @@ -12,11 +14,13 @@ void consume_ref_mut(int &v) { // [!]ConstParameterNotModified.Reference: parameter 'v' in function 'caller_ref(int&)' is never used to modify the referred object // current type: int &v // suggested type: const int &v -void caller_ref(int &v) { +void caller_ref(int& v) +{ consume_ref(v); } -void caller_ref_mut(int &v) { +void caller_ref_mut(int& v) +{ consume_ref_mut(v); } @@ -24,7 +28,8 @@ void caller_ref_mut(int &v) { // [!]ConstParameterNotModified.ReferenceRvaluePreferValue: parameter 'v' in function 'rvalue_use(int&&)' is an rvalue reference and is never used to modify the referred object // consider passing by value (int v) or const reference (const int &v) // current type: int &&v -int rvalue_use(int &&v) { +int rvalue_use(int&& v) +{ return v; } @@ -32,7 +37,8 @@ int rvalue_use(int &&v) { // [!]ConstParameterNotModified.Pointer: parameter 'p' in function 'read_only_ptr(int*)' is never used to modify the pointed object // current type: int *p // suggested type: const int *p -void read_only_ptr(int *p) { +void read_only_ptr(int* p) +{ int x = *p; (void)x; } diff --git a/test/pointer_reference-const_correctness/const-mixed.c b/test/pointer_reference-const_correctness/const-mixed.c index b037829..0e3ee20 100644 --- a/test/pointer_reference-const_correctness/const-mixed.c +++ b/test/pointer_reference-const_correctness/const-mixed.c @@ -7,19 +7,23 @@ // [!]ConstParameterNotModified.Pointer: parameter 'values' in function 'print_sum' is never used to modify the pointed object // current type: int *values // suggested type: const int *values -void print_sum(int *values, size_t count) { +void print_sum(int* values, size_t count) +{ int sum = 0; - for (size_t i = 0; i < count; ++i) { + for (size_t i = 0; i < count; ++i) + { sum += values[i]; } printf("%d\n", sum); } -void copy_data(const char *source, char * const dest, size_t len) { +void copy_data(const char* source, char* const dest, size_t len) +{ memcpy(dest, source, len); } -void read_data(char * const buffer) { +void read_data(char* const buffer) +{ printf("%s\n", buffer); } @@ -32,11 +36,15 @@ void read_data(char * const buffer) { // [!]ConstParameterNotModified.Pointer: parameter 'b' in function 'get_max' is never used to modify the pointed object // current type: int *b // suggested type: const int *b -int get_max(int *a, int *b) { +int get_max(int* a, int* b) +{ return (*a > *b) ? *a : *b; } -struct Point { int x, y; }; +struct Point +{ + int x, y; +}; // at line 50, column 0 // [!]ConstParameterNotModified.Pointer: parameter 'p1' in function 'distance' is never used to modify the pointed object @@ -47,6 +55,7 @@ struct Point { int x, y; }; // [!]ConstParameterNotModified.Pointer: parameter 'p2' in function 'distance' is never used to modify the pointed object // current type: Point *p2 // suggested type: const Point *p2 -int distance(struct Point *p1, struct Point *p2) { +int distance(struct Point* p1, struct Point* p2) +{ return abs(p1->x - p2->x) + abs(p1->y - p2->y); } diff --git a/test/pointer_reference-const_correctness/const-readonly.c b/test/pointer_reference-const_correctness/const-readonly.c index 7c95750..2eb45be 100644 --- a/test/pointer_reference-const_correctness/const-readonly.c +++ b/test/pointer_reference-const_correctness/const-readonly.c @@ -1,21 +1,27 @@ #include #include -void increment(int *value) { +void increment(int* value) +{ (*value)++; } -void modify(int *ptr) { +void modify(int* ptr) +{ *ptr = 42; } -void process(int *data) { - modify(data); // assume modify(int *) changes pointee +void process(int* data) +{ + modify(data); // assume modify(int *) changes pointee } -void fill_array(int *arr, size_t n, bool condition) { - for (size_t i = 0; i < n; ++i) { - if (condition) arr[i] = 0; +void fill_array(int* arr, size_t n, bool condition) +{ + for (size_t i = 0; i < n; ++i) + { + if (condition) + arr[i] = 0; } } @@ -23,7 +29,8 @@ void fill_array(int *arr, size_t n, bool condition) { // [!]ConstParameterNotModified.Pointer: parameter 'reg' in function 'read_volatile' is never used to modify the pointed object // current type: volatile int *reg // suggested type: const volatile int *reg -void read_volatile(volatile int *reg) { +void read_volatile(volatile int* reg) +{ int val = *reg; // use val } @@ -32,7 +39,8 @@ void read_volatile(volatile int *reg) { // [!]ConstParameterNotModified.Pointer: parameter 'value' in function 'log' is never used to modify the pointed object // current type: int *value // suggested type: const int *value -void log(int *value) { - printf("%d\n", *value); // read +void log(int* value) +{ + printf("%d\n", *value); // read // but if printf were variadic mutating, conservative } diff --git a/test/pointer_reference-const_correctness/readonly-pointer.c b/test/pointer_reference-const_correctness/readonly-pointer.c index d9b0288..7db656a 100644 --- a/test/pointer_reference-const_correctness/readonly-pointer.c +++ b/test/pointer_reference-const_correctness/readonly-pointer.c @@ -10,12 +10,12 @@ // consider 'const int32_t *param4' for API const-correctness // current type: int32_t * const param4 // suggested type: const int32_t *param4 -void myfunc(int32_t *out, const int32_t *in, int32_t *param3, int32_t * const param4) +void myfunc(int32_t* out, const int32_t* in, int32_t* param3, int32_t* const param4) { *out = *in + *param3 + *param4; } -void set_zero(int *p) +void set_zero(int* p) { *p = 0; } diff --git a/test/pointer_reference-const_correctness/readonly-reference.cpp b/test/pointer_reference-const_correctness/readonly-reference.cpp index 8b950b9..969b306 100644 --- a/test/pointer_reference-const_correctness/readonly-reference.cpp +++ b/test/pointer_reference-const_correctness/readonly-reference.cpp @@ -2,7 +2,7 @@ // [!]ConstParameterNotModified.Reference: parameter 'inc' in function 'increment(int&, int&)' is never used to modify the referred object // current type: int &inc // suggested type: const int &inc -void increment(int &value, int &inc) +void increment(int& value, int& inc) { value += inc; } @@ -11,7 +11,7 @@ void increment(int &value, int &inc) // [!]ConstParameterNotModified.ReferenceRvaluePreferValue: parameter 'value' in function 'read_once(int&&)' is an rvalue reference and is never used to modify the referred object // consider passing by value (int value) or const reference (const int &value) // current type: int &&value -int read_once(int &&value) +int read_once(int&& value) { return value; } diff --git a/test/test.cc b/test/test.cc index 50bf594..a03f8ff 100644 --- a/test/test.cc +++ b/test/test.cc @@ -27,7 +27,8 @@ int main(void) int sum = a + b; const bool is_ok = false; - if (is_ok) { + if (is_ok) + { char test[100]; test[0] = '\0'; // Initialize the string snprintf(test, sizeof(test), "Hello, World! %d %d %d\n", a, b, sum); diff --git a/test/test.cpp b/test/test.cpp index b03b96b..0da6796 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -29,7 +29,8 @@ int main(void) int sum = a + b; const bool is_ok = false; - if (is_ok) { + if (is_ok) + { char test[100]; test[0] = '\0'; // Initialize the string snprintf(test, sizeof(test), "Hello, World! %d %d %d\n", a, b, sum); diff --git a/test/vla/deguised-constant.c b/test/vla/deguised-constant.c index af51a48..3575e03 100644 --- a/test/vla/deguised-constant.c +++ b/test/vla/deguised-constant.c @@ -1,10 +1,10 @@ void foo(void) { int n = 6; - char buf[n]; // techniquement VLA, mais bornée et triviale, patch car faux positif + char buf[n]; // techniquement VLA, mais bornée et triviale, patch car faux positif } -int main(int ac, char **av) +int main(int ac, char** av) { foo(); return 0; diff --git a/test/vla/vla-scanf.c b/test/vla/vla-scanf.c index 6ffedf0..eb5f070 100644 --- a/test/vla/vla-scanf.c +++ b/test/vla/vla-scanf.c @@ -13,7 +13,7 @@ int main(void) // [!] dynamic stack allocation detected for variable 'vla' // allocated type: i8 // size of this allocation is not compile-time constant (VLA / variable alloca) and may lead to unbounded stack usage - char buf[n]; // VLA too + char buf[n]; // VLA too return 0; } From e69fb1345cbfa5933c80f41f895d3182589377a0 Mon Sep 17 00:00:00 2001 From: Hugo Date: Fri, 30 Jan 2026 05:10:31 +0100 Subject: [PATCH 4/4] fix(test): patch bad format --- test/alloca/oversized-constant.c | 2 +- test/alloca/recursive-controlled-alloca.c | 2 +- test/alloca/recursive-infinite-alloca.c | 2 +- test/alloca/user-controlled.c | 2 +- test/bound-storage/bound-storage-for-statement.c | 6 +++--- test/bound-storage/deep-alias.c | 6 +++--- test/bound-storage/indirection-profonde-aliasing.c | 6 +++--- test/bound-storage/ranges_test.c | 14 +++++++------- test/bound-storage/struct_array_overflow.c | 6 +++--- test/escape-stack/global-struct.c | 2 +- .../container_of_wrong_member_offset_error.c | 2 +- .../container_of_wrong_offset_and_ok.c | 2 +- .../gep_positive_offset_wrong_base_warn.c | 2 +- .../inttoptr_ptrtoint_wrong_offset_error.c | 2 +- .../inttoptr_through_stack_slot_error.c | 2 +- .../advanced-cases.c | 6 +++--- .../advanced-cases.cpp | 6 +++--- .../const-mixed.c | 8 ++++---- .../const-readonly.c | 4 ++-- 19 files changed, 41 insertions(+), 41 deletions(-) diff --git a/test/alloca/oversized-constant.c b/test/alloca/oversized-constant.c index bd4e3cf..524bfac 100644 --- a/test/alloca/oversized-constant.c +++ b/test/alloca/oversized-constant.c @@ -4,7 +4,7 @@ void big_alloca(void) { size_t n = 2 * 1024 * 1024; - // at line 12, column 25 + // at line 12, column 24 // [!!] large alloca on the stack for variable 'buf' // allocation performed via alloca/VLA; stack usage grows with runtime value // requested stack size: 2097152 bytes diff --git a/test/alloca/recursive-controlled-alloca.c b/test/alloca/recursive-controlled-alloca.c index 421ff05..0688ad7 100644 --- a/test/alloca/recursive-controlled-alloca.c +++ b/test/alloca/recursive-controlled-alloca.c @@ -3,7 +3,7 @@ int rec(size_t n) { - // at line 12, column 23 + // at line 12, column 22 // [!!] user-controlled alloca size for variable 'p' // allocation performed via alloca/VLA; stack usage grows with runtime value // size is unbounded at compile time diff --git a/test/alloca/recursive-infinite-alloca.c b/test/alloca/recursive-infinite-alloca.c index c4b73ee..997afbb 100644 --- a/test/alloca/recursive-infinite-alloca.c +++ b/test/alloca/recursive-infinite-alloca.c @@ -3,7 +3,7 @@ void boom(size_t n) { - // at line 12, column 23 + // at line 12, column 22 // [!!] user-controlled alloca size for variable 'p' // allocation performed via alloca/VLA; stack usage grows with runtime value // size is unbounded at compile time diff --git a/test/alloca/user-controlled.c b/test/alloca/user-controlled.c index 7313083..9e657ad 100644 --- a/test/alloca/user-controlled.c +++ b/test/alloca/user-controlled.c @@ -3,7 +3,7 @@ void foo(size_t n) { - // at line 11, column 25 + // at line 11, column 24 // [!!] user-controlled alloca size for variable 'buf' // allocation performed via alloca/VLA; stack usage grows with runtime value // size is unbounded at compile time diff --git a/test/bound-storage/bound-storage-for-statement.c b/test/bound-storage/bound-storage-for-statement.c index d98d672..cf2fe66 100644 --- a/test/bound-storage/bound-storage-for-statement.c +++ b/test/bound-storage/bound-storage-for-statement.c @@ -3,7 +3,7 @@ int main(void) char test[10]; char* ptr = test; - // at line 12, column 17 + // at line 13, column 17 // [!!] potential stack buffer overflow on variable 'test' (size 10) // alias path: test // index variable may go up to 19 (array last valid index: 9) @@ -13,7 +13,7 @@ int main(void) test[i] = 'a'; } - // at line 21, column 17 + // at line 22, column 17 // [!!] potential stack buffer overflow on variable 'test' (size 10) // alias path: test // index variable may go up to 11 (array last valid index: 9) @@ -27,7 +27,7 @@ int main(void) test[i] = 'b'; } - // at line 34, column 16 + // at line 37, column 16 // [!!] potential stack buffer overflow on variable 'test' (size 10) // alias path: test -> arraydecay -> ptr // index variable may go up to 19 (array last valid index: 9) diff --git a/test/bound-storage/deep-alias.c b/test/bound-storage/deep-alias.c index 2ae1e78..5064e41 100644 --- a/test/bound-storage/deep-alias.c +++ b/test/bound-storage/deep-alias.c @@ -5,7 +5,7 @@ void deep_alias(char* src) char* p2 = p1; char** pp = &p2; - // at line 14, column 18 + // at line 15, column 18 // [!!] potential stack buffer overflow on variable 'buf' (size 10) // alias path: buf -> arraydecay -> p1 -> p2 -> pp // index variable may go up to 19 (array last valid index: 9) @@ -18,11 +18,11 @@ void deep_alias(char* src) int main(void) { - // at line 23, column 10 + // at line 24, column 10 // [!!] stack pointer escape: address of variable 'src' escapes this function // address passed as argument to function 'llvm.memset.p0.i64' (callee may capture the pointer beyond this function) char src[20] = {0}; - // at line 27, column 5 + // at line 28, column 5 // [!!] stack pointer escape: address of variable 'src' escapes this function // address passed as argument to function 'deep_alias' (callee may capture the pointer beyond this function) deep_alias(src); diff --git a/test/bound-storage/indirection-profonde-aliasing.c b/test/bound-storage/indirection-profonde-aliasing.c index 85a18c5..ee18e7a 100644 --- a/test/bound-storage/indirection-profonde-aliasing.c +++ b/test/bound-storage/indirection-profonde-aliasing.c @@ -18,7 +18,7 @@ int main(void) // (this is a write access) (*pp)[15] = 'a'; - // at line 27, column 17 + // at line 28, column 17 // [!!] potential stack buffer overflow on variable 'test' (size 10) // alias path: test // index variable may go up to 19 (array last valid index: 9) @@ -28,7 +28,7 @@ int main(void) test[i] = 'a'; } - // at line 36, column 17 + // at line 37, column 17 // [!!] potential stack buffer overflow on variable 'test' (size 10) // alias path: test // index variable may go up to 11 (array last valid index: 9) @@ -42,7 +42,7 @@ int main(void) test[i] = 'b'; } - // at line 49, column 16 + // at line 52, column 16 // [!!] potential stack buffer overflow on variable 'test' (size 10) // alias path: test -> arraydecay -> ptr // index variable may go up to 19 (array last valid index: 9) diff --git a/test/bound-storage/ranges_test.c b/test/bound-storage/ranges_test.c index 0a38fae..2448c93 100644 --- a/test/bound-storage/ranges_test.c +++ b/test/bound-storage/ranges_test.c @@ -82,7 +82,7 @@ void nested_if_overflow(int i) { char buf[8]; - // at line 92, column 20 + // at line 94, column 20 // [!!] potential stack buffer overflow on variable 'buf' (size 8) // alias path: buf // index variable may go up to 10 (array last valid index: 7) @@ -128,7 +128,7 @@ void loop_ub_overflow(void) { char buf[10]; - // at line 133, column 16 + // at line 137, column 16 // [!!] potential stack buffer overflow on variable 'buf' (size 10) // alias path: buf // index variable may go up to 10 (array last valid index: 9) @@ -157,7 +157,7 @@ void unreachable_example(void) int i = 1; char buf[10]; - // at line 163, column 17 + // at line 168, column 17 // [!!] potential stack buffer overflow on variable 'buf' (size 10) // alias path: buf // constant index 11 is out of bounds (0..9) @@ -179,13 +179,13 @@ void alias_lb_ub(int i) char buf[10]; char* p = buf; - // at line 189, column 14 + // at line 194, column 14 // [!!] potential stack buffer overflow on variable 'buf' (size 10) // alias path: buf -> arraydecay -> p // index variable may go up to 12 (array last valid index: 9) // (this is a write access) - // at line 189, column 14 + // at line 194, column 14 // [!!] potential negative index on variable 'buf' (size 10) // alias path: p -> arraydecay -> buf // inferred lower bound for index expression: -2 (index may be < 0) @@ -226,13 +226,13 @@ void huge_range(int i) { char buf[10]; - // at line 236, column 16 + // at line 241, column 16 // [!!] potential stack buffer overflow on variable 'buf' (size 10) // alias path: buf // index variable may go up to 100 (array last valid index: 9) // (this is a write access) - // at line 236, column 16 + // at line 241, column 16 // [!!] potential negative index on variable 'buf' (size 10) // alias path: buf // inferred lower bound for index expression: -100 (index may be < 0) diff --git a/test/bound-storage/struct_array_overflow.c b/test/bound-storage/struct_array_overflow.c index a74d1c8..a894052 100644 --- a/test/bound-storage/struct_array_overflow.c +++ b/test/bound-storage/struct_array_overflow.c @@ -16,7 +16,7 @@ void ok_direct(void) void overflow_eq_10(void) { struct S s; - // at line 24, column 18 + // at line 25, column 18 // [!!] potential stack buffer overflow on variable 's' (size 10) // alias path: s -> buf // index variable may go up to 10 (array last valid index: 9) @@ -28,7 +28,7 @@ void overflow_eq_10(void) void overflow_const_index(void) { struct S s; - // at line 35, column 15 + // at line 36, column 15 // [!!] potential stack buffer overflow on variable 's' (size 10) // alias path: s -> buf // constant index 11 is out of bounds (0..9) @@ -41,7 +41,7 @@ void nested_if_overflow(void) struct S s; int i = 15; - // at line 49, column 18 + // at line 50, column 18 // [!!] potential stack buffer overflow on variable 's' (size 10) // alias path: s -> buf // index variable may go up to 15 (array last valid index: 9) diff --git a/test/escape-stack/global-struct.c b/test/escape-stack/global-struct.c index dd13162..b816b34 100644 --- a/test/escape-stack/global-struct.c +++ b/test/escape-stack/global-struct.c @@ -8,7 +8,7 @@ struct Holder G; void store_in_global_field(void) { char buf[10]; - // at line 13, column 9 + // at line 14, column 9 // [!!] stack pointer escape: address of variable 'buf' escapes this function // stored into global variable 'G' (pointer may be used after the function returns) G.p = buf; // leak : G is global diff --git a/test/offset_of-container_of/container_of_wrong_member_offset_error.c b/test/offset_of-container_of/container_of_wrong_member_offset_error.c index 3ccaeed..850f4ad 100644 --- a/test/offset_of-container_of/container_of_wrong_member_offset_error.c +++ b/test/offset_of-container_of/container_of_wrong_member_offset_error.c @@ -17,7 +17,7 @@ int main(void) int32_t* pb = &obj.b; /* Bug: subtract offset of the WRONG member (i instead of b). */ - // at line 28, column 50 + // at line 28, column 48 // [!!] potential UB: invalid base reconstruction via offsetof/container_of // variable: 'obj' // source member: offset +4 diff --git a/test/offset_of-container_of/container_of_wrong_offset_and_ok.c b/test/offset_of-container_of/container_of_wrong_offset_and_ok.c index 75afe2e..bbf5b68 100644 --- a/test/offset_of-container_of/container_of_wrong_offset_and_ok.c +++ b/test/offset_of-container_of/container_of_wrong_offset_and_ok.c @@ -14,7 +14,7 @@ void test_invalid_container_of(void) int32_t* ptr_b = &obj.field_b; // BUG: using wrong offset (8 instead of 4) - // at line 25, column 69 + // at line 25, column 67 // [!!] potential UB: invalid base reconstruction via offsetof/container_of // variable: 'obj' // source member: offset +4 diff --git a/test/offset_of-container_of/gep_positive_offset_wrong_base_warn.c b/test/offset_of-container_of/gep_positive_offset_wrong_base_warn.c index ab8081a..2cc2430 100644 --- a/test/offset_of-container_of/gep_positive_offset_wrong_base_warn.c +++ b/test/offset_of-container_of/gep_positive_offset_wrong_base_warn.c @@ -16,7 +16,7 @@ int test_gep_positive_offset_missed(void) // Wrong reconstruction with a positive offset (+4). // Correct behavior: diagnostic (base points inside the object, not at the base). - // at line 27, column 50 + // at line 27, column 48 // [!!] potential UB: invalid base reconstruction via offsetof/container_of // variable: 'obj' // source member: offset +4 diff --git a/test/offset_of-container_of/inttoptr_ptrtoint_wrong_offset_error.c b/test/offset_of-container_of/inttoptr_ptrtoint_wrong_offset_error.c index 6d1127d..0305bad 100644 --- a/test/offset_of-container_of/inttoptr_ptrtoint_wrong_offset_error.c +++ b/test/offset_of-container_of/inttoptr_ptrtoint_wrong_offset_error.c @@ -17,7 +17,7 @@ void test_ptrtoint_pattern(void) // BUG: using wrong offset (8 instead of 4) uintptr_t addr = (uintptr_t)ptr_y; addr -= 8; - // at line 27, column 31 + // at line 28, column 31 // [!!] potential UB: invalid base reconstruction via offsetof/container_of // variable: 'obj' // source member: offset +4 diff --git a/test/offset_of-container_of/inttoptr_through_stack_slot_error.c b/test/offset_of-container_of/inttoptr_through_stack_slot_error.c index 2ab38f1..504c905 100644 --- a/test/offset_of-container_of/inttoptr_through_stack_slot_error.c +++ b/test/offset_of-container_of/inttoptr_through_stack_slot_error.c @@ -19,7 +19,7 @@ int test_inttoptr_load_store(void) uintptr_t addr = (uintptr_t)p; addr -= offsetof(struct A, i); // wrong offset (12 instead of 4) - // at line 29, column 26 + // at line 30, column 26 // [!!] potential UB: invalid base reconstruction via offsetof/container_of // variable: 'obj' // source member: offset +4 diff --git a/test/pointer_reference-const_correctness/advanced-cases.c b/test/pointer_reference-const_correctness/advanced-cases.c index 65b4d2c..50f1bb8 100644 --- a/test/pointer_reference-const_correctness/advanced-cases.c +++ b/test/pointer_reference-const_correctness/advanced-cases.c @@ -18,7 +18,7 @@ void consume_mut(int* p) } } -// at line 21, column 0 +// at line 25, column 0 // [!]ConstParameterNotModified.Pointer: parameter 'p' in function 'caller_const' is never used to modify the pointed object // current type: int *p // suggested type: const int *p @@ -32,7 +32,7 @@ void caller_mut(int* p) consume_mut(p); } -// at line 33, column 0 +// at line 39, column 0 // [!]ConstParameterNotModified.Pointer: parameter 'p' in function 'read_only' is never used to modify the pointed object // current type: int *p // suggested type: const int *p @@ -47,7 +47,7 @@ void variadic_use(int* p) printf("%p\n", (void*)p); } -// at line 46, column 0 +// at line 54, column 0 // [!]ConstParameterNotModified.Pointer: parameter 'reg' in function 'read_mmio' is never used to modify the pointed object // current type: volatile int *reg // suggested type: const volatile int *reg diff --git a/test/pointer_reference-const_correctness/advanced-cases.cpp b/test/pointer_reference-const_correctness/advanced-cases.cpp index c002d99..64353f7 100644 --- a/test/pointer_reference-const_correctness/advanced-cases.cpp +++ b/test/pointer_reference-const_correctness/advanced-cases.cpp @@ -10,7 +10,7 @@ void consume_ref_mut(int& v) v += 1; } -// at line 15, column 0 +// at line 17, column 0 // [!]ConstParameterNotModified.Reference: parameter 'v' in function 'caller_ref(int&)' is never used to modify the referred object // current type: int &v // suggested type: const int &v @@ -24,7 +24,7 @@ void caller_ref_mut(int& v) consume_ref_mut(v); } -// at line 27, column 0 +// at line 31, column 0 // [!]ConstParameterNotModified.ReferenceRvaluePreferValue: parameter 'v' in function 'rvalue_use(int&&)' is an rvalue reference and is never used to modify the referred object // consider passing by value (int v) or const reference (const int &v) // current type: int &&v @@ -33,7 +33,7 @@ int rvalue_use(int&& v) return v; } -// at line 35, column 0 +// at line 40, column 0 // [!]ConstParameterNotModified.Pointer: parameter 'p' in function 'read_only_ptr(int*)' is never used to modify the pointed object // current type: int *p // suggested type: const int *p diff --git a/test/pointer_reference-const_correctness/const-mixed.c b/test/pointer_reference-const_correctness/const-mixed.c index 0e3ee20..fa17330 100644 --- a/test/pointer_reference-const_correctness/const-mixed.c +++ b/test/pointer_reference-const_correctness/const-mixed.c @@ -27,12 +27,12 @@ void read_data(char* const buffer) printf("%s\n", buffer); } -// at line 35, column 0 +// at line 39, column 0 // [!]ConstParameterNotModified.Pointer: parameter 'a' in function 'get_max' is never used to modify the pointed object // current type: int *a // suggested type: const int *a -// at line 35, column 0 +// at line 39, column 0 // [!]ConstParameterNotModified.Pointer: parameter 'b' in function 'get_max' is never used to modify the pointed object // current type: int *b // suggested type: const int *b @@ -46,12 +46,12 @@ struct Point int x, y; }; -// at line 50, column 0 +// at line 58, column 0 // [!]ConstParameterNotModified.Pointer: parameter 'p1' in function 'distance' is never used to modify the pointed object // current type: Point *p1 // suggested type: const Point *p1 -// at line 50, column 0 +// at line 58, column 0 // [!]ConstParameterNotModified.Pointer: parameter 'p2' in function 'distance' is never used to modify the pointed object // current type: Point *p2 // suggested type: const Point *p2 diff --git a/test/pointer_reference-const_correctness/const-readonly.c b/test/pointer_reference-const_correctness/const-readonly.c index 2eb45be..14458a2 100644 --- a/test/pointer_reference-const_correctness/const-readonly.c +++ b/test/pointer_reference-const_correctness/const-readonly.c @@ -25,7 +25,7 @@ void fill_array(int* arr, size_t n, bool condition) } } -// at line 26, column 0 +// at line 32, column 0 // [!]ConstParameterNotModified.Pointer: parameter 'reg' in function 'read_volatile' is never used to modify the pointed object // current type: volatile int *reg // suggested type: const volatile int *reg @@ -35,7 +35,7 @@ void read_volatile(volatile int* reg) // use val } -// at line 35, column 0 +// at line 42, column 0 // [!]ConstParameterNotModified.Pointer: parameter 'value' in function 'log' is never used to modify the pointed object // current type: int *value // suggested type: const int *value