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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ endif()
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

set(LLVM_LINK_LLVM_DYLIB ON)

Expand Down Expand Up @@ -50,6 +51,7 @@ set(STACK_ANALYZER_SOURCES
src/StackUsageAnalyzer.cpp
src/analysis/AllocaUsage.cpp
src/analysis/AnalyzerUtils.cpp
src/analysis/CompileCommands.cpp
src/analysis/ConstParamAnalysis.cpp
src/analysis/DynamicAlloca.cpp
src/analysis/FunctionFilter.cpp
Expand Down Expand Up @@ -150,8 +152,8 @@ if(BUILD_CLI)
target_link_libraries(stack_usage_analyzer
PRIVATE
stack_usage_analyzer_lib
# pas besoin de relinker cc::compilerlib_static ici,
# il est déjà dans la lib
# No need to relink cc::compilerlib_static here,
# it is already linked into the library.
)

if(ENABLE_DEBUG_ASAN)
Expand Down
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
./stack_usage_analyzer --mode=[abi/ir] test.[ll/c/cpp] other.[ll/c/cpp]
./stack_usage_analyzer main.cpp -I./include
./stack_usage_analyzer main.cpp -I./include --compile-arg=-I/opt/homebrew/opt/llvm@20/include
./stack_usage_analyzer main.cpp --compile-commands=build/compile_commands.json
./stack_usage_analyzer main.cpp -I./include --only-file=./main.cpp --only-function=main
./stack_usage_analyzer main.cpp --dump-ir=./debug/main.ll
./stack_usage_analyzer a.c b.c --dump-ir=./debug
```

```
Expand All @@ -29,6 +32,11 @@
--warnings-only keeps only important diagnostics
--stack-limit=<value> overrides stack limit (bytes, or KiB/MiB/GiB)
--compile-arg=<arg> passes an extra argument to the compiler
--compile-commands=<path> uses compile_commands.json (file or directory)
--compdb=<path> alias for --compile-commands
--compdb-fast drops heavy build flags for faster analysis
--timing prints compile/analysis timings to stderr
--dump-ir=<path> writes LLVM IR to a file (or directory for multiple inputs)
-I<dir> or -I <dir> adds an include directory
-D<name>[=value] or -D <name>[=value] defines a macro
--only-file=<path> or --only-file <path> filters by file
Expand All @@ -38,6 +46,13 @@
--dump-filter prints filter decisions (stderr)
```

To generate `compile_commands.json` with CMake, configure with
`-DCMAKE_EXPORT_COMPILE_COMMANDS=ON` and point to the resulting file
(often under `build/`).

If analysis feels slow, `--compdb-fast` disables heavy flags (optimizations,
sanitizers, profiling) while keeping include paths and macros.

### Example

Given this code:
Expand Down Expand Up @@ -142,7 +157,7 @@ Function: main
Examples:
```c
char buf[10];
return buf; // renvoi pointeur vers stack use-after-return
return buf; // returns pointer to stack -> use-after-return
```

Or storing:
Expand Down
2 changes: 1 addition & 1 deletion extern-project/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ int main(int argc, char **argv)

auto res = ctrace::stack::analyzeFile(filename, cfg, ctx, diag);

// Exemple : output SARIF sur stdout
// Example: SARIF output to stdout
std::cout << ctrace::stack::toSarif(res, filename) << std::endl;

return 0;
Expand Down
47 changes: 29 additions & 18 deletions include/StackUsageAnalyzer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ namespace llvm
class SMDiagnostic;
} // namespace llvm

namespace ctrace::stack::analysis
{
class CompilationDatabase;
} // namespace ctrace::stack::analysis

namespace ctrace::stack
{

Expand All @@ -26,33 +31,39 @@ namespace ctrace::stack
ABI
};

// Configuration de l'analyse (mode + limite de stack)
// Analysis configuration (mode + stack limit).
struct AnalysisConfig
{
AnalysisMode mode = AnalysisMode::IR;
StackSize stackLimit = 8ull * 1024ull * 1024ull; // 8 MiB par défaut
StackSize stackLimit = 8ull * 1024ull * 1024ull; // 8 MiB default
bool quiet = false;
bool warningsOnly = false;
std::vector<std::string> extraCompileArgs;
std::shared_ptr<const analysis::CompilationDatabase> compilationDatabase;
bool requireCompilationDatabase = false;
bool compdbFast = false;
bool timing = false;
std::vector<std::string> onlyFiles;
std::vector<std::string> onlyDirs;
std::vector<std::string> onlyFunctions;
bool dumpFilter = false;
std::string dumpIRPath;
bool dumpIRIsDir = false;
};

// Résultat par fonction
// Per-function result
struct FunctionResult
{
std::string filePath;
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
StackSize localStack = 0; // local frame size (depends on mode)
StackSize maxStack = 0; // max stack including callees
bool localStackUnknown = false; // unknown local size (dynamic alloca)
bool maxStackUnknown = false; // unknown max stack (propagated via calls)
bool hasDynamicAlloca = false; // dynamic alloca detected in the function

bool isRecursive = false; // part of a cycle F <-> G ...
bool hasInfiniteSelfRecursion = false; // DominatorTree heuristic
bool exceedsLimit = false; // maxStack > config.stackLimit
};

Expand Down Expand Up @@ -151,7 +162,7 @@ namespace ctrace::stack
std::string message;
};

// Résultat global pour un module
// Global result for a module
struct AnalysisResult
{
AnalysisConfig config;
Expand All @@ -162,22 +173,22 @@ namespace ctrace::stack
std::vector<Diagnostic> diagnostics;
};

// Serialize an AnalysisResult to a simple JSON format (pour CI / GitHub Actions).
// `inputFile` : chemin du fichier analysé (celui que tu passes à analyzeFile).
// Serialize an AnalysisResult to a simple JSON format (for CI / GitHub Actions).
// `inputFile`: path of the analyzed file (the one you pass to analyzeFile).
std::string toJson(const AnalysisResult& result, const std::string& inputFile);
std::string toJson(const AnalysisResult& result, const std::vector<std::string>& inputFiles);

// 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.
// `inputFile`: path of the analyzed file.
// `toolName` / `toolVersion`: tool metadata in 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)
// Analyze an already loaded module (can be reused by other tools).
AnalysisResult analyzeModule(llvm::Module& mod, const AnalysisConfig& config);

// Helper pratique : charge un .ll et appelle analyzeModule()
// Convenience helper: load a .ll and call analyzeModule()
AnalysisResult analyzeFile(const std::string& filename, const AnalysisConfig& config,
llvm::LLVMContext& ctx, llvm::SMDiagnostic& err);

Expand Down
33 changes: 33 additions & 0 deletions include/analysis/CompileCommands.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#pragma once

#include <memory>
#include <string>
#include <unordered_map>
#include <vector>

namespace ctrace::stack::analysis
{
struct CompileCommand
{
std::string directory;
std::vector<std::string> arguments;
};

class CompilationDatabase
{
public:
static std::shared_ptr<CompilationDatabase> loadFromFile(const std::string& path,
std::string& error);

const CompileCommand* findCommandForFile(const std::string& filePath) const;

const std::string& sourcePath() const
{
return sourcePath_;
}

private:
std::string sourcePath_;
std::unordered_map<std::string, CompileCommand> commands_;
};
} // namespace ctrace::stack::analysis
10 changes: 5 additions & 5 deletions include/analysis/InvalidBaseReconstruction.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ namespace ctrace::stack::analysis
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
std::string varName; // alloca variable name (stack object)
std::string sourceMember; // source member (e.g., "b")
int64_t offsetUsed = 0; // offset used in the calculation (can be negative)
std::string targetType; // target cast type (e.g., "struct A*")
bool isOutOfBounds = false; // true if we can prove it is out of bounds
const llvm::Instruction* inst = nullptr;
};

Expand Down
2 changes: 1 addition & 1 deletion include/analysis/SizeMinusKWrites.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace ctrace::stack::analysis
struct SizeMinusKWriteIssue
{
std::string funcName;
std::string sinkName; // nom de l'appel ou "store"
std::string sinkName; // call name or "store"
bool ptrNonNull = false;
bool sizeAboveK = false;
bool hasPointerDest = true;
Expand Down
10 changes: 5 additions & 5 deletions include/analysis/StackBufferAnalysis.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ namespace ctrace::stack::analysis
std::string funcName;
std::string varName;
StackSize arraySize = 0;
StackSize indexOrUpperBound = 0; // utilisé pour les bornes sup (UB) ou index constant
StackSize indexOrUpperBound = 0; // used for upper bounds (UB) or constant index
bool isWrite = false;
bool indexIsConstant = false;
const llvm::Instruction* inst = nullptr;

// Violation basée sur une borne inférieure (index potentiellement négatif)
// Violation based on a lower bound (index potentially negative)
bool isLowerBoundViolation = false;
long long lowerBound = 0; // borne inférieure déduite (signée)
long long lowerBound = 0; // deduced lower bound (signed)

std::string aliasPath; // ex: "pp -> ptr -> buf"
std::vector<std::string> aliasPathVec; // {"pp", "ptr", "buf"}
Expand All @@ -49,8 +49,8 @@ namespace ctrace::stack::analysis
{
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
std::size_t storeCount = 0; // total number of StoreInsts into this buffer
std::size_t distinctIndexCount = 0; // number of distinct index expressions
const llvm::AllocaInst* allocaInst = nullptr;
};

Expand Down
6 changes: 3 additions & 3 deletions include/analysis/StackComputation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ namespace ctrace::stack::analysis

struct InternalAnalysisState
{
std::map<const llvm::Function*, StackEstimate> TotalStack; // stack max, callees inclus
std::set<const llvm::Function*> RecursiveFuncs; // fonctions dans au moins un cycle
std::set<const llvm::Function*> InfiniteRecursionFuncs; // auto-récursion “infinie”
std::map<const llvm::Function*, StackEstimate> TotalStack; // max stack, including callees
std::set<const llvm::Function*> RecursiveFuncs; // functions in at least one cycle
std::set<const llvm::Function*> InfiniteRecursionFuncs; // “infinite” self-recursion
};

CallGraph buildCallGraph(llvm::Module& M);
Expand Down
2 changes: 1 addition & 1 deletion include/analysis/StackPointerEscape.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace ctrace::stack::analysis
std::string varName;
std::string
escapeKind; // "return", "store_global", "store_unknown", "call_arg", "call_callback"
std::string targetName; // nom du global, si applicable
std::string targetName; // global name, if applicable
const llvm::Instruction* inst = nullptr;
};

Expand Down
3 changes: 1 addition & 2 deletions include/helpers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
namespace ctrace::stack
{

template <typename E>
struct EnumTraits; // pas de définition générique -> erreur si non spécialisé
template <typename E> struct EnumTraits; // no generic definition -> error if not specialized

template <typename E> concept EnumWithTraits = std::is_enum_v<E> && requires
{
Expand Down
Loading
Loading