Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
891fa97
chore(build): register uninitialized variable analysis sources
SizzleUnrlsd Feb 9, 2026
72d1151
docs(cli): document STL filtering behavior and analysis options
SizzleUnrlsd Feb 9, 2026
234a26a
feat(reporting): extend diagnostic metadata for uninitialized checks
SizzleUnrlsd Feb 9, 2026
07e6f33
feat(cli): add --STL flag to include system and STL functions
SizzleUnrlsd Feb 9, 2026
a44a953
test(cli): add coverage for STL flag and uninitialized scenarios
SizzleUnrlsd Feb 9, 2026
53cf436
feat(diagnostics): add call-based uninitialized read diagnostic messa…
SizzleUnrlsd Feb 9, 2026
2c67585
feat(filter): default to user-code analysis and gate STL via option
SizzleUnrlsd Feb 9, 2026
bace875
feat(reporting): serialize uninitialized rule metadata with confidenc…
SizzleUnrlsd Feb 9, 2026
27dee24
feat(uninitialized): add call-aware issue kind and callee context
SizzleUnrlsd Feb 9, 2026
745dc51
feat(uninitialized): implement byte-range lattice with interprocedura…
SizzleUnrlsd Feb 9, 2026
aadf00b
test(uninitialized): add never-used local variable case
SizzleUnrlsd Feb 9, 2026
b2f11c9
test(uninitialized): add C argument path with conditional initialization
SizzleUnrlsd Feb 9, 2026
42dde51
test(uninitialized): add C++ argument handling without false positive…
SizzleUnrlsd Feb 9, 2026
e862076
test(uninitialized): add fully initialized array no-warning case
SizzleUnrlsd Feb 9, 2026
9068b9f
test(uninitialized): add partially initialized array warning case
SizzleUnrlsd Feb 9, 2026
6cfc38a
test(uninitialized): add basic uninitialized array read case
SizzleUnrlsd Feb 9, 2026
2f1f28a
test(uninitialized): add baseline uninitialized scalar read case
SizzleUnrlsd Feb 9, 2026
800e43c
test(uninitialized): add branch-dependent initialization case
SizzleUnrlsd Feb 9, 2026
a7a51de
test(uninitialized): add goto-based control flow initialization case
SizzleUnrlsd Feb 9, 2026
1ebd167
test(uninitialized): add initialized local no-warning case
SizzleUnrlsd Feb 9, 2026
717e3ce
test(uninitialized): add interprocedural call-chain read-before-write…
SizzleUnrlsd Feb 9, 2026
24363a1
test(uninitialized): add interprocedural read-before-write case
SizzleUnrlsd Feb 9, 2026
e1603da
test(uninitialized): add interprocedural partial struct initializatio…
SizzleUnrlsd Feb 9, 2026
5a711cd
test(uninitialized): add interprocedural write call-chain no-warning …
SizzleUnrlsd Feb 9, 2026
49c5dcc
test(uninitialized): add interprocedural write-before-read no-warning…
SizzleUnrlsd Feb 9, 2026
1adb6a7
test(uninitialized): add nested loop partial initialization case
SizzleUnrlsd Feb 9, 2026
c000c27
test(uninitialized): add uninitialized pointer dereference case
SizzleUnrlsd Feb 9, 2026
ce44309
test(uninitialized): add C++ reference propagation case
SizzleUnrlsd Feb 9, 2026
425eed8
test(uninitialized): add fully initialized struct no-warning case
SizzleUnrlsd Feb 9, 2026
2611acd
test(uninitialized): add partially initialized struct warning case
SizzleUnrlsd Feb 9, 2026
49aa131
test(uninitialized): add basic uninitialized struct field read case
SizzleUnrlsd Feb 9, 2026
75ee66e
test(uninitialized): add switch-dependent initialization case
SizzleUnrlsd Feb 9, 2026
5603a9c
test(uninitialized): add unused uninitialized local declaration case
SizzleUnrlsd Feb 9, 2026
de7191a
fix(test): add cstdint include for uninitialized local argument fixture
SizzleUnrlsd Feb 9, 2026
4dd6175
fix(uninitialized): optimize range updates and harden summary/read tr…
SizzleUnrlsd Feb 9, 2026
59c4b73
fix(filter): make path normalization deterministic for relative paths
SizzleUnrlsd Feb 9, 2026
a915ea4
fix(report): sort SARIF rules deterministically and format confidence…
SizzleUnrlsd Feb 9, 2026
9801e8b
test(uninitialized): add regression for memcpy reading uninitialized …
SizzleUnrlsd Feb 9, 2026
31074b3
test(uninitialized): add negative memcpy case with initialized source
SizzleUnrlsd Feb 9, 2026
4413236
test(uninitialized): add pointer redirection case to prevent false po…
SizzleUnrlsd Feb 9, 2026
d34af67
chore(style): format code with clang-format
SizzleUnrlsd Feb 9, 2026
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
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ set(STACK_ANALYZER_SOURCES
src/analysis/StackBufferAnalysis.cpp
src/analysis/StackComputation.cpp
src/analysis/StackPointerEscape.cpp
src/analysis/UninitializedVarAnalysis.cpp
src/report/ReportSerialization.cpp
src/mangle.cpp
src/passes/ModulePasses.cpp
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ LLVM_DIR=/opt/llvm/lib/cmake/llvm Clang_DIR=/opt/llvm/lib/cmake/clang ./build.sh
--only-dir=<path> or --only-dir <path> filters by directory
--only-function=<name> or --only-function <name> filters by function
--only-func=<name> alias for --only-function
--STL includes STL/system library functions (default excludes them)
--dump-filter prints filter decisions (stderr)
```

Expand Down
11 changes: 8 additions & 3 deletions include/StackUsageAnalyzer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ namespace ctrace::stack
std::vector<std::string> onlyFiles;
std::vector<std::string> onlyDirs;
std::vector<std::string> onlyFunctions;
bool includeSTL = false;
bool dumpFilter = false;
std::string dumpIRPath;
bool dumpIRIsDir = false;
Expand Down Expand Up @@ -119,12 +120,13 @@ namespace ctrace::stack
InvalidBaseReconstruction = 10,
ConstParameterNotModified = 11,
SizeMinusOneWrite = 12,
DuplicateIfCondition = 13
DuplicateIfCondition = 13,
UninitializedLocalRead = 14
};

template <> struct EnumTraits<DescriptiveErrorCode>
{
static constexpr std::array<std::string_view, 14> names = {"None",
static constexpr std::array<std::string_view, 15> names = {"None",
"StackBufferOverflow",
"NegativeStackIndex",
"VLAUsage",
Expand All @@ -137,7 +139,8 @@ namespace ctrace::stack
"InvalidBaseReconstruction",
"ConstParameterNotModified",
"SizeMinusOneWrite",
"DuplicateIfCondition"};
"DuplicateIfCondition",
"UninitializedLocalRead"};
};

/*
Expand All @@ -160,6 +163,8 @@ namespace ctrace::stack
DiagnosticSeverity severity = DiagnosticSeverity::Warning;
DescriptiveErrorCode errCode = DescriptiveErrorCode::None;
std::string ruleId;
double confidence = -1.0; // [0,1], negative means unset
std::string cweId;
std::vector<std::string> variableAliasingVec;
std::string message;
};
Expand Down
37 changes: 37 additions & 0 deletions include/analysis/UninitializedVarAnalysis.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#pragma once

#include <functional>
#include <string>
#include <vector>

namespace llvm
{
class Function;
class Instruction;
class Module;
} // namespace llvm

namespace ctrace::stack::analysis
{
enum class UninitializedLocalIssueKind
{
ReadBeforeDefiniteInit,
ReadBeforeDefiniteInitViaCall,
NeverInitialized
};

struct UninitializedLocalReadIssue
{
std::string funcName;
std::string varName;
const llvm::Instruction* inst = nullptr;
unsigned line = 0;
unsigned column = 0;
std::string calleeName;
UninitializedLocalIssueKind kind = UninitializedLocalIssueKind::ReadBeforeDefiniteInit;
};

std::vector<UninitializedLocalReadIssue>
analyzeUninitializedLocalReads(llvm::Module& mod,
const std::function<bool(const llvm::Function&)>& shouldAnalyze);
} // namespace ctrace::stack::analysis
6 changes: 6 additions & 0 deletions main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ static void printHelp()
<< " --only-file=<path> Only report functions from this source file\n"
<< " --only-dir=<path> Only report functions under this directory\n"
<< " --only-func=<name> Only report functions with this name (comma-separated)\n"
<< " --STL Include STL/system library functions in analysis\n"
<< " --stack-limit=<value> Override stack size limit (bytes, or KiB/MiB/GiB)\n"
<< " --dump-filter Print filter decisions to stderr\n"
<< " --dump-ir=<path> Write LLVM IR to file (or directory for multiple inputs)\n"
Expand Down Expand Up @@ -419,6 +420,11 @@ int main(int argc, char** argv)
cfg.quiet = true;
continue;
}
if (argStr == "--STL" || argStr == "--stl")
{
cfg.includeSTL = true;
continue;
}
if (argStr == "--only-file")
{
if (i + 1 >= argc)
Expand Down
12 changes: 12 additions & 0 deletions run_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,18 @@ def check_cli_parsing_and_filters() -> bool:
else:
print(f" ✅ macro case OK: {' '.join(opt)}")

# STL toggle
for opt in [["--STL"], ["--stl"]]:
cmd = [str(ANALYZER), str(sample)] + opt + ["--only-function=transition"]
result = subprocess.run(cmd, capture_output=True, text=True)
output = (result.stdout or "") + (result.stderr or "")
if result.returncode != 0 or "Function:" not in output:
print(f" ❌ STL flag case failed: {' '.join(opt)}")
print(output)
ok = False
else:
print(f" ✅ STL flag case OK: {' '.join(opt)}")

print()
return ok

Expand Down
77 changes: 77 additions & 0 deletions src/StackUsageAnalyzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "analysis/StackBufferAnalysis.hpp"
#include "analysis/StackComputation.hpp"
#include "analysis/StackPointerEscape.hpp"
#include "analysis/UninitializedVarAnalysis.hpp"
#include "passes/ModulePasses.hpp"

namespace ctrace::stack
Expand Down Expand Up @@ -922,6 +923,75 @@ namespace ctrace::stack
}
}

static void appendUninitializedLocalReadDiagnostics(
AnalysisResult& result,
const std::vector<analysis::UninitializedLocalReadIssue>& issues)
{
for (const auto& issue : issues)
{
unsigned line = issue.line;
unsigned column = issue.column;
bool haveLoc = (line != 0);
if (issue.inst)
{
llvm::DebugLoc DL = issue.inst->getDebugLoc();
if (DL)
{
line = DL.getLine();
column = DL.getCol();
haveLoc = true;
}
}

std::ostringstream body;
if (issue.kind == analysis::UninitializedLocalIssueKind::ReadBeforeDefiniteInit)
{
body << " [!!] potential read of uninitialized local variable '"
<< issue.varName << "'\n";
body << " this load may execute before any definite initialization on "
"all control-flow paths\n";
}
else if (issue.kind ==
analysis::UninitializedLocalIssueKind::ReadBeforeDefiniteInitViaCall)
{
body << " [!!] potential read of uninitialized local variable '"
<< issue.varName << "'\n";
body
<< " this call may read the value before any definite initialization";
if (!issue.calleeName.empty())
{
body << " in '" << issue.calleeName << "'";
}
body << "\n";
}
else
{
body << " [!] local variable '" << issue.varName << "' is never initialized\n";
body << " declared without initializer and no definite write was found "
"in this function\n";
}

Diagnostic diag;
diag.funcName = issue.funcName;
diag.line = haveLoc ? line : 0;
diag.column = haveLoc ? column : 0;
diag.severity = DiagnosticSeverity::Warning;
diag.errCode = DescriptiveErrorCode::UninitializedLocalRead;
diag.ruleId =
(issue.kind == analysis::UninitializedLocalIssueKind::ReadBeforeDefiniteInit ||
issue.kind ==
analysis::UninitializedLocalIssueKind::ReadBeforeDefiniteInitViaCall)
? "UninitializedLocalRead"
: "UninitializedLocalVariable";
diag.confidence =
(issue.kind == analysis::UninitializedLocalIssueKind::NeverInitialized) ? 0.75
: 0.90;
diag.cweId = "CWE-457";
diag.message = body.str();
result.diagnostics.push_back(std::move(diag));
}
}

static void appendInvalidBaseReconstructionDiagnostics(
AnalysisResult& result,
const std::vector<analysis::InvalidBaseReconstructionIssue>& issues)
Expand Down Expand Up @@ -1268,6 +1338,13 @@ namespace ctrace::stack
appendDuplicateIfConditionDiagnostics(result, duplicateIfIssues);
logDuration("Multiple stores", t0);

// 12c) Detect potential reads from uninitialized local stack variables
t0 = Clock::now();
std::vector<analysis::UninitializedLocalReadIssue> uninitializedReadIssues =
analysis::analyzeUninitializedLocalReads(mod, shouldAnalyzeFunction);
appendUninitializedLocalReadDiagnostics(result, uninitializedReadIssues);
logDuration("Uninitialized local reads", t0);

// 13) Detect invalid base pointer reconstructions (offsetof/container_of)
t0 = Clock::now();
std::vector<analysis::InvalidBaseReconstructionIssue> baseReconIssues =
Expand Down
Loading
Loading