From 891fa9794de59abb037fd69a8540be5d6d961ff1 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 02:40:57 +0100 Subject: [PATCH 01/41] chore(build): register uninitialized variable analysis sources --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 30995d4..73f0185 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 From 72d1151d6ff49f6ea12ca2e6f4a14feb89514013 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 02:40:57 +0100 Subject: [PATCH 02/41] docs(cli): document STL filtering behavior and analysis options --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f2f10eb..5bb2043 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ LLVM_DIR=/opt/llvm/lib/cmake/llvm Clang_DIR=/opt/llvm/lib/cmake/clang ./build.sh --only-dir= or --only-dir filters by directory --only-function= or --only-function filters by function --only-func= alias for --only-function +--STL includes STL/system library functions (default excludes them) --dump-filter prints filter decisions (stderr) ``` From 234a26a2ce47b421b134a8423b2bc9577aa817c0 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 02:40:58 +0100 Subject: [PATCH 03/41] feat(reporting): extend diagnostic metadata for uninitialized checks --- include/StackUsageAnalyzer.hpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/include/StackUsageAnalyzer.hpp b/include/StackUsageAnalyzer.hpp index 5cdc106..9a0bfce 100644 --- a/include/StackUsageAnalyzer.hpp +++ b/include/StackUsageAnalyzer.hpp @@ -46,6 +46,7 @@ namespace ctrace::stack std::vector onlyFiles; std::vector onlyDirs; std::vector onlyFunctions; + bool includeSTL = false; bool dumpFilter = false; std::string dumpIRPath; bool dumpIRIsDir = false; @@ -119,12 +120,13 @@ namespace ctrace::stack InvalidBaseReconstruction = 10, ConstParameterNotModified = 11, SizeMinusOneWrite = 12, - DuplicateIfCondition = 13 + DuplicateIfCondition = 13, + UninitializedLocalRead = 14 }; template <> struct EnumTraits { - static constexpr std::array names = {"None", + static constexpr std::array names = {"None", "StackBufferOverflow", "NegativeStackIndex", "VLAUsage", @@ -137,7 +139,8 @@ namespace ctrace::stack "InvalidBaseReconstruction", "ConstParameterNotModified", "SizeMinusOneWrite", - "DuplicateIfCondition"}; + "DuplicateIfCondition", + "UninitializedLocalRead"}; }; /* @@ -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 variableAliasingVec; std::string message; }; From 07e6f33a8521355b9541d47a693a8a2ec048ceeb Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 02:40:58 +0100 Subject: [PATCH 04/41] feat(cli): add --STL flag to include system and STL functions --- main.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/main.cpp b/main.cpp index 068438c..2b6543c 100644 --- a/main.cpp +++ b/main.cpp @@ -47,6 +47,7 @@ static void printHelp() << " --only-file= Only report functions from this source file\n" << " --only-dir= Only report functions under this directory\n" << " --only-func= Only report functions with this name (comma-separated)\n" + << " --STL Include STL/system library functions in analysis\n" << " --stack-limit= Override stack size limit (bytes, or KiB/MiB/GiB)\n" << " --dump-filter Print filter decisions to stderr\n" << " --dump-ir= Write LLVM IR to file (or directory for multiple inputs)\n" @@ -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) From a44a9534ce8bc1d28095988f6b4918d38338336e Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 02:40:58 +0100 Subject: [PATCH 05/41] test(cli): add coverage for STL flag and uninitialized scenarios --- run_test.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/run_test.py b/run_test.py index f2fe6de..82b0732 100755 --- a/run_test.py +++ b/run_test.py @@ -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 From 53cf436d27b5e0af9411e2f4cad02e562c80acca Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 02:41:20 +0100 Subject: [PATCH 06/41] feat(diagnostics): add call-based uninitialized read diagnostic messaging --- src/StackUsageAnalyzer.cpp | 77 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/src/StackUsageAnalyzer.cpp b/src/StackUsageAnalyzer.cpp index bc9ca20..a862bcf 100644 --- a/src/StackUsageAnalyzer.cpp +++ b/src/StackUsageAnalyzer.cpp @@ -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 @@ -922,6 +923,75 @@ namespace ctrace::stack } } + static void appendUninitializedLocalReadDiagnostics( + AnalysisResult& result, + const std::vector& 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& issues) @@ -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 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 baseReconIssues = From 2c675852a45ea0b0fae8c71d6c990c31ec6510a6 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 02:41:20 +0100 Subject: [PATCH 07/41] feat(filter): default to user-code analysis and gate STL via option --- src/analysis/FunctionFilter.cpp | 151 +++++++++++++++++++++++++++++++- 1 file changed, 150 insertions(+), 1 deletion(-) diff --git a/src/analysis/FunctionFilter.cpp b/src/analysis/FunctionFilter.cpp index d5ad0f6..6d8d9d0 100644 --- a/src/analysis/FunctionFilter.cpp +++ b/src/analysis/FunctionFilter.cpp @@ -1,5 +1,10 @@ #include "analysis/FunctionFilter.hpp" +#include +#include +#include +#include + #include #include #include @@ -9,12 +14,122 @@ namespace ctrace::stack::analysis { + namespace + { + static std::string normalizePathForMatch(const std::string& input) + { + if (input.empty()) + return {}; + + std::string adjusted = input; + for (char& c : adjusted) + { + if (c == '\\') + c = '/'; + } + + std::filesystem::path path(adjusted); + std::error_code ec; + std::filesystem::path absPath = std::filesystem::absolute(path, ec); + if (ec) + absPath = path; + + std::filesystem::path canonicalPath = std::filesystem::weakly_canonical(absPath, ec); + std::filesystem::path norm = ec ? absPath.lexically_normal() : canonicalPath; + std::string out = norm.generic_string(); + while (out.size() > 1 && out.back() == '/') + out.pop_back(); + return out; + } + + static std::string toLowerCopy(const std::string& input) + { + std::string out; + out.reserve(input.size()); + for (char c : input) + { + out.push_back(static_cast(std::tolower(static_cast(c)))); + } + return out; + } + + static bool pathHasPrefix(const std::string& path, const std::string& prefix) + { + if (prefix.empty()) + return false; + if (path.size() < prefix.size()) + return false; + if (path.compare(0, prefix.size(), prefix) != 0) + return false; + if (path.size() == prefix.size()) + return true; + return path[prefix.size()] == '/'; + } + + static bool pathContainsFragment(const std::string& path, const std::string& fragment) + { + return !fragment.empty() && path.find(fragment) != std::string::npos; + } + + static bool isLikelySystemPath(const std::string& path) + { + if (path.empty()) + return false; + + const std::string normalized = toLowerCopy(normalizePathForMatch(path)); + if (normalized.empty()) + return false; + + static constexpr std::array systemPrefixes = { + "/usr/include", + "/usr/lib", + "/usr/local/include", + "/usr/local/lib", + "/opt/homebrew/include", + "/opt/homebrew/lib", + "/opt/homebrew/cellar", + "/opt/local/include", + "/opt/local/lib", + "/library/developer/commandlinetools/usr/include", + "/library/developer/commandlinetools/usr/lib", + "/applications/xcode.app/contents/developer/toolchains", + "/applications/xcode.app/contents/developer/platforms", + "/nix/store", + "c:/program files"}; + + for (const char* prefix : systemPrefixes) + { + if (pathHasPrefix(normalized, prefix)) + return true; + } + + static constexpr std::array systemFragments = { + "/include/c++/", "/c++/v1/", "/lib/clang/", "/x86_64-linux-gnu/c++/", + "/aarch64-linux-gnu/c++/"}; + + for (const char* fragment : systemFragments) + { + if (pathContainsFragment(normalized, fragment)) + return true; + } + + return false; + } + + static bool isCompilerRuntimeLikeName(llvm::StringRef name) + { + return name.starts_with("llvm.") || name.starts_with("clang.") || + name.starts_with("__asan_") || name.starts_with("__ubsan_") || + name.starts_with("__tsan_") || name.starts_with("__msan_"); + } + } // namespace + FunctionFilter buildFunctionFilter(const llvm::Module& mod, const AnalysisConfig& config) { FunctionFilter filter; filter.hasPathFilter = !config.onlyFiles.empty() || !config.onlyDirs.empty(); filter.hasFuncFilter = !config.onlyFunctions.empty(); - filter.hasFilter = filter.hasPathFilter || filter.hasFuncFilter; + filter.hasFilter = filter.hasPathFilter || filter.hasFuncFilter || !config.includeSTL; filter.moduleSourcePath = mod.getSourceFileName(); filter.config = &config; return filter; @@ -38,7 +153,41 @@ namespace ctrace::stack::analysis return false; } if (!hasPathFilter) + { + if (!cfg.includeSTL && !hasFuncFilter) + { + std::string path = getFunctionSourcePath(F); + std::string usedPath; + bool decision = true; + + if (!path.empty()) + { + usedPath = path; + decision = !isLikelySystemPath(usedPath); + } + else if (!moduleSourcePath.empty()) + { + usedPath = moduleSourcePath; + decision = !isLikelySystemPath(usedPath); + } + else + { + decision = !isCompilerRuntimeLikeName(F.getName()); + } + + if (cfg.dumpFilter) + { + llvm::errs() << "[filter] func=" << F.getName() << " file="; + if (usedPath.empty()) + llvm::errs() << ""; + else + llvm::errs() << usedPath; + llvm::errs() << " keep=" << (decision ? "yes" : "no") << "\n"; + } + return decision; + } return true; + } std::string path = getFunctionSourcePath(F); std::string usedPath; bool decision = false; From bace8756990c68989a6a4c73340e3d3d1bfe22db Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 02:41:20 +0100 Subject: [PATCH 08/41] feat(reporting): serialize uninitialized rule metadata with confidence and CWE --- src/report/ReportSerialization.cpp | 97 ++++++++++++++++++++++++++++-- 1 file changed, 91 insertions(+), 6 deletions(-) diff --git a/src/report/ReportSerialization.cpp b/src/report/ReportSerialization.cpp index fd88046..1dca514 100644 --- a/src/report/ReportSerialization.cpp +++ b/src/report/ReportSerialization.cpp @@ -1,6 +1,7 @@ #include "StackUsageAnalyzer.hpp" #include // std::snprintf +#include #include #include #include @@ -81,6 +82,13 @@ namespace ctrace::stack return "note"; } + static std::string resolveRuleId(const Diagnostic& d) + { + if (!d.ruleId.empty()) + return d.ruleId; + return std::string(ctrace::stack::enumToString(d.errCode)); + } + } // anonymous namespace static std::string toJsonImpl(const AnalysisResult& result, const std::string* inputFile, @@ -190,9 +198,20 @@ namespace ctrace::stack 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; + const std::string ruleId = resolveRuleId(d); os << " \"ruleId\": \"" << jsonEscape(ruleId) << "\",\n"; + os << " \"confidence\": "; + if (d.confidence >= 0.0) + os << d.confidence; + else + os << "null"; + os << ",\n"; + os << " \"cwe\": "; + if (!d.cweId.empty()) + os << "\"" << jsonEscape(d.cweId) << "\""; + else + os << "null"; + os << ",\n"; std::string diagFilePath = d.filePath; if (diagFilePath.empty() && inputFile) @@ -242,6 +261,29 @@ namespace ctrace::stack std::string toSarif(const AnalysisResult& result, const std::string& inputFile, const std::string& toolName, const std::string& toolVersion) { + struct SarifRuleEntry + { + std::string id; + std::string cweId; + }; + + std::vector rules; + std::unordered_map ruleIndices; + for (const auto& d : result.diagnostics) + { + const std::string rid = resolveRuleId(d); + auto it = ruleIndices.find(rid); + if (it == ruleIndices.end()) + { + ruleIndices.emplace(rid, rules.size()); + rules.push_back({rid, d.cweId}); + } + else if (rules[it->second].cweId.empty() && !d.cweId.empty()) + { + rules[it->second].cweId = d.cweId; + } + } + std::ostringstream os; os << "{\n"; os << " \"version\": \"2.1.0\",\n"; @@ -252,7 +294,32 @@ namespace ctrace::stack os << " \"tool\": {\n"; os << " \"driver\": {\n"; os << " \"name\": \"" << jsonEscape(toolName) << "\",\n"; - os << " \"version\": \"" << jsonEscape(toolVersion) << "\"\n"; + os << " \"version\": \"" << jsonEscape(toolVersion) << "\",\n"; + os << " \"rules\": [\n"; + for (std::size_t i = 0; i < rules.size(); ++i) + { + const auto& rule = rules[i]; + os << " {\n"; + os << " \"id\": \"" << jsonEscape(rule.id) << "\",\n"; + os << " \"shortDescription\": { \"text\": \"" << jsonEscape(rule.id) + << "\" }"; + if (!rule.cweId.empty()) + { + os << ",\n"; + os << " \"properties\": {\n"; + os << " \"tags\": [\"" << jsonEscape(rule.cweId) << "\"]\n"; + os << " }\n"; + } + else + { + os << "\n"; + } + os << " }"; + if (i + 1 < rules.size()) + os << ","; + os << "\n"; + } + os << " ]\n"; os << " }\n"; os << " },\n"; os << " \"results\": [\n"; @@ -261,12 +328,30 @@ namespace ctrace::stack { const auto& d = result.diagnostics[i]; os << " {\n"; - // For now, use a single generic ruleId; you can specialize it later. - const std::string ruleId = - d.ruleId.empty() ? std::string(ctrace::stack::enumToString(d.errCode)) : d.ruleId; + const std::string ruleId = resolveRuleId(d); os << " \"ruleId\": \"" << jsonEscape(ruleId) << "\",\n"; os << " \"level\": \"" << severityToSarifLevel(d.severity) << "\",\n"; os << " \"message\": { \"text\": \"" << jsonEscape(d.message) << "\" },\n"; + bool hasConfidence = d.confidence >= 0.0; + bool hasCwe = !d.cweId.empty(); + if (hasConfidence || hasCwe) + { + os << " \"properties\": {\n"; + bool needComma = false; + if (hasConfidence) + { + os << " \"confidence\": " << d.confidence; + needComma = true; + } + if (hasCwe) + { + if (needComma) + os << ",\n"; + os << " \"cwe\": \"" << jsonEscape(d.cweId) << "\""; + } + os << "\n"; + os << " },\n"; + } os << " \"locations\": [\n"; os << " {\n"; os << " \"physicalLocation\": {\n"; From 27dee240966d7efea25a72fe214dd29daf5059a2 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 02:41:20 +0100 Subject: [PATCH 09/41] feat(uninitialized): add call-aware issue kind and callee context --- include/analysis/UninitializedVarAnalysis.hpp | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 include/analysis/UninitializedVarAnalysis.hpp diff --git a/include/analysis/UninitializedVarAnalysis.hpp b/include/analysis/UninitializedVarAnalysis.hpp new file mode 100644 index 0000000..0138bf8 --- /dev/null +++ b/include/analysis/UninitializedVarAnalysis.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include + +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 + analyzeUninitializedLocalReads(llvm::Module& mod, + const std::function& shouldAnalyze); +} // namespace ctrace::stack::analysis From 745dc517c0fb4f9151c10128551ce4be1eb04b23 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 02:41:20 +0100 Subject: [PATCH 10/41] feat(uninitialized): implement byte-range lattice with interprocedural summaries --- src/analysis/UninitializedVarAnalysis.cpp | 1155 +++++++++++++++++++++ 1 file changed, 1155 insertions(+) create mode 100644 src/analysis/UninitializedVarAnalysis.cpp diff --git a/src/analysis/UninitializedVarAnalysis.cpp b/src/analysis/UninitializedVarAnalysis.cpp new file mode 100644 index 0000000..8345f77 --- /dev/null +++ b/src/analysis/UninitializedVarAnalysis.cpp @@ -0,0 +1,1155 @@ +#include "analysis/UninitializedVarAnalysis.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "analysis/IRValueUtils.hpp" + +namespace ctrace::stack::analysis +{ + namespace + { + struct ByteRange + { + std::uint64_t begin = 0; + std::uint64_t end = 0; // [begin, end) + + bool operator==(const ByteRange& other) const + { + return begin == other.begin && end == other.end; + } + }; + + enum class InitLatticeState + { + Uninit, + Partial, + Init + }; + + enum class TrackedObjectKind + { + Alloca, + PointerParam + }; + + struct TrackedMemoryObject + { + TrackedObjectKind kind = TrackedObjectKind::Alloca; + const llvm::AllocaInst* alloca = nullptr; + const llvm::Argument* param = nullptr; + std::uint64_t sizeBytes = 0; // 0 means unknown upper bound. + }; + + struct TrackedObjectContext + { + std::vector objects; + llvm::DenseMap allocaIndex; + llvm::DenseMap paramIndex; + }; + + struct MemoryAccess + { + unsigned objectIdx = 0; + std::uint64_t begin = 0; + std::uint64_t end = 0; + }; + + using RangeSet = std::vector; + using InitRangeState = std::vector; + + struct PointerParamEffectSummary + { + RangeSet readBeforeWriteRanges; + RangeSet writeRanges; + bool hasUnknownReadBeforeWrite = false; + bool hasUnknownWrite = false; + + bool operator==(const PointerParamEffectSummary& other) const + { + return readBeforeWriteRanges == other.readBeforeWriteRanges && + writeRanges == other.writeRanges && + hasUnknownReadBeforeWrite == other.hasUnknownReadBeforeWrite && + hasUnknownWrite == other.hasUnknownWrite; + } + + bool hasAnyEffect() const + { + return hasUnknownReadBeforeWrite || hasUnknownWrite || + !readBeforeWriteRanges.empty() || !writeRanges.empty(); + } + }; + + struct FunctionSummary + { + std::vector paramEffects; + + bool operator==(const FunctionSummary& other) const + { + return paramEffects == other.paramEffects; + } + }; + + using FunctionSummaryMap = llvm::DenseMap; + + static constexpr std::uint64_t kUnknownObjectFullRange = + std::numeric_limits::max() / 4; + + static std::uint64_t saturatingAdd(std::uint64_t lhs, std::uint64_t rhs) + { + constexpr std::uint64_t maxVal = std::numeric_limits::max(); + if (lhs > maxVal - rhs) + return maxVal; + return lhs + rhs; + } + + static bool shouldTrackAlloca(const llvm::AllocaInst& AI) + { + if (!AI.isStaticAlloca()) + return false; + if (AI.getAllocatedType()->isFunctionTy()) + return false; + return true; + } + + static std::uint64_t getAllocaSizeBytes(const llvm::AllocaInst& AI, + const llvm::DataLayout& DL) + { + std::optional allocSize = AI.getAllocationSize(DL); + if (!allocSize || allocSize->isScalable()) + return 0; + return allocSize->getFixedValue(); + } + + static std::uint64_t getTypeStoreSizeBytes(const llvm::Type* ty, const llvm::DataLayout& DL) + { + if (!ty) + return 0; + llvm::TypeSize size = DL.getTypeStoreSize(const_cast(ty)); + if (size.isScalable()) + return 0; + return size.getFixedValue(); + } + + static void normalizeRanges(RangeSet& ranges) + { + if (ranges.empty()) + return; + + std::sort(ranges.begin(), ranges.end(), + [](const ByteRange& lhs, const ByteRange& rhs) + { + if (lhs.begin != rhs.begin) + return lhs.begin < rhs.begin; + return lhs.end < rhs.end; + }); + + std::size_t outIdx = 0; + for (std::size_t i = 0; i < ranges.size(); ++i) + { + ByteRange current = ranges[i]; + if (current.begin >= current.end) + continue; + if (outIdx == 0) + { + ranges[outIdx++] = current; + continue; + } + ByteRange& prev = ranges[outIdx - 1]; + if (current.begin <= prev.end) + { + prev.end = std::max(prev.end, current.end); + } + else + { + ranges[outIdx++] = current; + } + } + ranges.resize(outIdx); + } + + static void addRange(RangeSet& ranges, std::uint64_t begin, std::uint64_t end) + { + if (begin >= end) + return; + ranges.push_back({begin, end}); + normalizeRanges(ranges); + } + + static RangeSet intersectRanges(const RangeSet& lhs, const RangeSet& rhs) + { + RangeSet out; + std::size_t i = 0; + std::size_t j = 0; + while (i < lhs.size() && j < rhs.size()) + { + std::uint64_t begin = std::max(lhs[i].begin, rhs[j].begin); + std::uint64_t end = std::min(lhs[i].end, rhs[j].end); + if (begin < end) + out.push_back({begin, end}); + + if (lhs[i].end < rhs[j].end) + ++i; + else + ++j; + } + return out; + } + + static bool isRangeCovered(const RangeSet& initialized, std::uint64_t begin, + std::uint64_t end) + { + if (begin >= end) + return true; + for (const ByteRange& r : initialized) + { + if (r.begin <= begin && r.end >= end) + return true; + if (r.begin > begin) + return false; + } + return false; + } + + static InitLatticeState classifyInitState(const RangeSet& initialized, + std::uint64_t totalSize) + { + if (totalSize == 0 || initialized.empty()) + return InitLatticeState::Uninit; + if (initialized.size() == 1 && initialized.front().begin == 0 && + initialized.front().end >= totalSize) + { + return InitLatticeState::Init; + } + return InitLatticeState::Partial; + } + + static bool isAllocaObject(const TrackedMemoryObject& obj) + { + return obj.kind == TrackedObjectKind::Alloca; + } + + static bool isParamObject(const TrackedMemoryObject& obj) + { + return obj.kind == TrackedObjectKind::PointerParam; + } + + static std::uint64_t getObjectFullRangeEnd(const TrackedMemoryObject& obj) + { + return obj.sizeBytes == 0 ? kUnknownObjectFullRange : obj.sizeBytes; + } + + static std::string getTrackedObjectName(const TrackedMemoryObject& obj) + { + if (isAllocaObject(obj) && obj.alloca) + return deriveAllocaName(obj.alloca); + if (isParamObject(obj) && obj.param) + { + if (obj.param->hasName()) + return obj.param->getName().str(); + return "arg" + std::to_string(obj.param->getArgNo()); + } + return ""; + } + + static bool clipRangeToObject(const TrackedMemoryObject& obj, std::uint64_t begin, + std::uint64_t end, std::uint64_t& outBegin, + std::uint64_t& outEnd) + { + if (obj.sizeBytes == 0) + { + if (begin >= end) + return false; + outBegin = begin; + outEnd = end; + return true; + } + + if (begin >= obj.sizeBytes) + return false; + outBegin = begin; + outEnd = std::min(end, obj.sizeBytes); + return outBegin < outEnd; + } + + static bool lookupTrackedObjectIndex(const llvm::Value* base, + const TrackedObjectContext& tracked, + unsigned& outIndex) + { + if (auto* AI = llvm::dyn_cast(base)) + { + auto it = tracked.allocaIndex.find(AI); + if (it != tracked.allocaIndex.end()) + { + outIndex = it->second; + return true; + } + } + if (auto* arg = llvm::dyn_cast(base)) + { + auto it = tracked.paramIndex.find(arg); + if (it != tracked.paramIndex.end()) + { + outIndex = it->second; + return true; + } + } + return false; + } + + static const llvm::Value* peelPointerFromSingleStoreSlot(const llvm::Value* ptr) + { + const llvm::Value* current = ptr; + + for (unsigned depth = 0; depth < 4; ++depth) + { + const auto* LI = llvm::dyn_cast(current->stripPointerCasts()); + if (!LI) + break; + + const auto* slot = + llvm::dyn_cast(LI->getPointerOperand()->stripPointerCasts()); + if (!slot || !slot->isStaticAlloca()) + break; + if (!slot->getAllocatedType()->isPointerTy()) + break; + + const llvm::StoreInst* uniqueStore = nullptr; + for (const llvm::Use& U : slot->uses()) + { + const auto* SI = llvm::dyn_cast(U.getUser()); + if (!SI) + continue; + if (SI->getPointerOperand()->stripPointerCasts() != slot) + continue; + if (uniqueStore && uniqueStore != SI) + { + uniqueStore = nullptr; + break; + } + uniqueStore = SI; + } + + if (!uniqueStore) + break; + + const llvm::Value* storedPtr = uniqueStore->getValueOperand()->stripPointerCasts(); + if (!storedPtr->getType()->isPointerTy()) + break; + + current = storedPtr; + } + + return current; + } + + static bool resolveTrackedObjectBase(const llvm::Value* ptr, + const TrackedObjectContext& tracked, + const llvm::DataLayout& DL, unsigned& outObjectIdx, + std::uint64_t& outOffset, bool& outHasConstOffset) + { + if (!ptr || !ptr->getType()->isPointerTy()) + return false; + + const llvm::Value* canonicalPtr = peelPointerFromSingleStoreSlot(ptr); + + int64_t signedOffset = 0; + const llvm::Value* base = llvm::GetPointerBaseWithConstantOffset( + canonicalPtr->stripPointerCasts(), signedOffset, DL, true); + if (base && signedOffset >= 0 && lookupTrackedObjectIndex(base, tracked, outObjectIdx)) + { + outOffset = static_cast(signedOffset); + outHasConstOffset = true; + return true; + } + + const llvm::Value* underlying = + llvm::getUnderlyingObject(canonicalPtr->stripPointerCasts(), 16); + if (!underlying) + return false; + if (!lookupTrackedObjectIndex(underlying, tracked, outObjectIdx)) + return false; + + outOffset = 0; + outHasConstOffset = false; + return true; + } + + static bool resolveAccessFromPointer(const llvm::Value* ptr, std::uint64_t accessSize, + const TrackedObjectContext& tracked, + const llvm::DataLayout& DL, MemoryAccess& out) + { + if (!ptr || !ptr->getType()->isPointerTy() || accessSize == 0) + return false; + + unsigned objectIdx = 0; + std::uint64_t offset = 0; + bool hasConstOffset = false; + if (!resolveTrackedObjectBase(ptr, tracked, DL, objectIdx, offset, hasConstOffset) || + !hasConstOffset) + { + return false; + } + + const TrackedMemoryObject& obj = tracked.objects[objectIdx]; + std::uint64_t begin = offset; + std::uint64_t end = saturatingAdd(offset, accessSize); + std::uint64_t clippedBegin = 0; + std::uint64_t clippedEnd = 0; + if (!clipRangeToObject(obj, begin, end, clippedBegin, clippedEnd)) + return false; + + out.objectIdx = objectIdx; + out.begin = clippedBegin; + out.end = clippedEnd; + return true; + } + + static void collectTrackedObjects(const llvm::Function& F, const llvm::DataLayout& DL, + TrackedObjectContext& tracked) + { + tracked.objects.clear(); + tracked.allocaIndex.clear(); + tracked.paramIndex.clear(); + + for (const llvm::BasicBlock& BB : F) + { + for (const llvm::Instruction& I : BB) + { + auto* AI = llvm::dyn_cast(&I); + if (!AI) + continue; + if (!shouldTrackAlloca(*AI)) + continue; + + std::uint64_t sizeBytes = getAllocaSizeBytes(*AI, DL); + if (sizeBytes == 0) + continue; + + unsigned idx = static_cast(tracked.objects.size()); + tracked.objects.push_back({TrackedObjectKind::Alloca, AI, nullptr, sizeBytes}); + tracked.allocaIndex[AI] = idx; + } + } + + for (const llvm::Argument& arg : F.args()) + { + if (!arg.getType()->isPointerTy()) + continue; + unsigned idx = static_cast(tracked.objects.size()); + tracked.objects.push_back({TrackedObjectKind::PointerParam, nullptr, &arg, 0}); + tracked.paramIndex[&arg] = idx; + } + } + + static InitRangeState makeBottomState(std::size_t trackedCount) + { + return InitRangeState(trackedCount); + } + + static InitRangeState makeTopState(const TrackedObjectContext& tracked) + { + InitRangeState top(tracked.objects.size()); + for (std::size_t i = 0; i < tracked.objects.size(); ++i) + { + addRange(top[i], 0, getObjectFullRangeEnd(tracked.objects[i])); + } + return top; + } + + static void meetMustState(InitRangeState& accum, const InitRangeState& incoming) + { + if (accum.size() != incoming.size()) + return; + for (std::size_t idx = 0; idx < accum.size(); ++idx) + { + accum[idx] = intersectRanges(accum[idx], incoming[idx]); + } + } + + static bool statesEqual(const InitRangeState& lhs, const InitRangeState& rhs) + { + return lhs == rhs; + } + + static void computeReachableBlocks(const llvm::Function& F, + llvm::DenseMap& reachable) + { + reachable.clear(); + if (F.empty()) + return; + + llvm::SmallVector worklist; + worklist.push_back(&F.getEntryBlock()); + reachable[&F.getEntryBlock()] = true; + + while (!worklist.empty()) + { + const llvm::BasicBlock* BB = worklist.pop_back_val(); + for (const llvm::BasicBlock* succ : llvm::successors(BB)) + { + if (!succ) + continue; + auto [it, inserted] = reachable.try_emplace(succ, true); + if (inserted) + { + worklist.push_back(succ); + } + else + { + it->second = true; + } + } + } + } + + static InitRangeState + computeInState(const llvm::BasicBlock& BB, const llvm::BasicBlock* entryBlock, + const llvm::DenseMap& reachable, + const llvm::DenseMap& outState, + const TrackedObjectContext& tracked) + { + InitRangeState in = makeBottomState(tracked.objects.size()); + if (&BB == entryBlock) + return in; + + bool havePred = false; + InitRangeState merged = makeTopState(tracked); + for (const llvm::BasicBlock* pred : llvm::predecessors(&BB)) + { + auto itReach = reachable.find(pred); + if (itReach == reachable.end() || !itReach->second) + continue; + havePred = true; + + auto itOut = outState.find(pred); + if (itOut == outState.end()) + { + merged = makeBottomState(tracked.objects.size()); + break; + } + meetMustState(merged, itOut->second); + } + + if (!havePred) + return in; + return merged; + } + + static const llvm::Instruction* getAllocaDebugAnchor(const llvm::AllocaInst* AI) + { + if (!AI) + return nullptr; + + for (const llvm::Use& U : AI->uses()) + { + auto* dvi = llvm::dyn_cast(U.getUser()); + if (!dvi) + continue; + if (dvi->getDebugLoc()) + return dvi; + } + return AI; + } + + static void getAllocaDeclarationLocation(const llvm::AllocaInst* AI, unsigned& line, + unsigned& column) + { + line = 0; + column = 0; + if (!AI) + return; + + auto* nonConstAI = const_cast(AI); + for (llvm::DbgDeclareInst* ddi : llvm::findDbgDeclares(nonConstAI)) + { + llvm::DebugLoc DL = llvm::getDebugValueLoc(ddi); + if (DL) + { + line = DL.getLine(); + column = DL.getCol(); + return; + } + } + + for (llvm::DbgVariableRecord* dvr : llvm::findDVRDeclares(nonConstAI)) + { + llvm::DebugLoc DL = llvm::getDebugValueLoc(dvr); + if (DL) + { + line = DL.getLine(); + column = DL.getCol(); + return; + } + } + } + + static FunctionSummary makeEmptySummary(const llvm::Function& F) + { + FunctionSummary summary; + summary.paramEffects.resize(F.arg_size()); + return summary; + } + + static void applyCalleeSummaryAtCall( + const llvm::CallBase& CB, const llvm::Function& callee, + const FunctionSummary& calleeSummary, const TrackedObjectContext& tracked, + const llvm::DataLayout& DL, InitRangeState& initialized, llvm::BitVector* writeSeen, + llvm::BitVector* readBeforeInitSeen, FunctionSummary* currentSummary, + std::vector* emittedIssues) + { + const unsigned maxArgs = + std::min(static_cast(CB.arg_size()), + static_cast(calleeSummary.paramEffects.size())); + for (unsigned argIdx = 0; argIdx < maxArgs; ++argIdx) + { + const PointerParamEffectSummary& effect = calleeSummary.paramEffects[argIdx]; + if (!effect.hasAnyEffect()) + continue; + + const llvm::Value* actual = CB.getArgOperand(argIdx); + if (!actual || !actual->getType()->isPointerTy()) + continue; + + unsigned objectIdx = 0; + std::uint64_t baseOffset = 0; + bool hasConstOffset = false; + if (!resolveTrackedObjectBase(actual, tracked, DL, objectIdx, baseOffset, + hasConstOffset)) + { + continue; + } + + const TrackedMemoryObject& obj = tracked.objects[objectIdx]; + + bool hasReadBeforeWrite = false; + bool readWasUnknown = false; + RangeSet uncoveredReadRanges; + + if (hasConstOffset) + { + for (const ByteRange& rr : effect.readBeforeWriteRanges) + { + std::uint64_t mappedBegin = saturatingAdd(baseOffset, rr.begin); + std::uint64_t mappedEnd = saturatingAdd(baseOffset, rr.end); + std::uint64_t clippedBegin = 0; + std::uint64_t clippedEnd = 0; + if (!clipRangeToObject(obj, mappedBegin, mappedEnd, clippedBegin, + clippedEnd)) + { + continue; + } + if (!isRangeCovered(initialized[objectIdx], clippedBegin, clippedEnd)) + { + hasReadBeforeWrite = true; + uncoveredReadRanges.push_back({clippedBegin, clippedEnd}); + } + } + + if (effect.hasUnknownReadBeforeWrite) + { + InitLatticeState objectState = + classifyInitState(initialized[objectIdx], getObjectFullRangeEnd(obj)); + if (objectState != InitLatticeState::Init) + { + hasReadBeforeWrite = true; + readWasUnknown = true; + } + } + } + else + { + if (effect.hasUnknownReadBeforeWrite || !effect.readBeforeWriteRanges.empty()) + { + InitLatticeState objectState = + classifyInitState(initialized[objectIdx], getObjectFullRangeEnd(obj)); + if (objectState != InitLatticeState::Init) + { + hasReadBeforeWrite = true; + readWasUnknown = true; + } + } + } + + if (hasReadBeforeWrite) + { + if (isAllocaObject(obj)) + { + if (readBeforeInitSeen && objectIdx < readBeforeInitSeen->size()) + readBeforeInitSeen->set(objectIdx); + + if (emittedIssues) + { + emittedIssues->push_back( + {CB.getFunction()->getName().str(), getTrackedObjectName(obj), &CB, + 0, 0, callee.getName().str(), + UninitializedLocalIssueKind::ReadBeforeDefiniteInitViaCall}); + } + } + else if (currentSummary && obj.param) + { + PointerParamEffectSummary& current = + currentSummary->paramEffects[obj.param->getArgNo()]; + for (const ByteRange& rr : uncoveredReadRanges) + { + addRange(current.readBeforeWriteRanges, rr.begin, rr.end); + } + if (readWasUnknown || + (!hasConstOffset && (!effect.readBeforeWriteRanges.empty() || + effect.hasUnknownReadBeforeWrite))) + { + current.hasUnknownReadBeforeWrite = true; + } + } + } + + bool wroteSomething = false; + bool writeWasUnknown = false; + if (hasConstOffset) + { + for (const ByteRange& wr : effect.writeRanges) + { + std::uint64_t mappedBegin = saturatingAdd(baseOffset, wr.begin); + std::uint64_t mappedEnd = saturatingAdd(baseOffset, wr.end); + std::uint64_t clippedBegin = 0; + std::uint64_t clippedEnd = 0; + if (!clipRangeToObject(obj, mappedBegin, mappedEnd, clippedBegin, + clippedEnd)) + { + continue; + } + addRange(initialized[objectIdx], clippedBegin, clippedEnd); + wroteSomething = true; + + if (currentSummary && isParamObject(obj) && obj.param) + { + PointerParamEffectSummary& current = + currentSummary->paramEffects[obj.param->getArgNo()]; + addRange(current.writeRanges, clippedBegin, clippedEnd); + } + } + + if (effect.hasUnknownWrite) + { + wroteSomething = true; + writeWasUnknown = true; + } + } + else + { + if (effect.hasUnknownWrite || !effect.writeRanges.empty()) + { + wroteSomething = true; + writeWasUnknown = true; + } + } + + if (writeWasUnknown && currentSummary && isParamObject(obj) && obj.param) + { + currentSummary->paramEffects[obj.param->getArgNo()].hasUnknownWrite = true; + } + + if (wroteSomething && writeSeen && isAllocaObject(obj) && + objectIdx < writeSeen->size()) + { + writeSeen->set(objectIdx); + } + } + } + + static void + transferInstruction(const llvm::Instruction& I, const TrackedObjectContext& tracked, + const llvm::DataLayout& DL, const FunctionSummaryMap& summaries, + InitRangeState& initialized, llvm::BitVector* writeSeen, + llvm::BitVector* readBeforeInitSeen, FunctionSummary* currentSummary, + std::vector* emittedIssues) + { + if (auto* LI = llvm::dyn_cast(&I)) + { + MemoryAccess access; + std::uint64_t loadSize = getTypeStoreSizeBytes(LI->getType(), DL); + if (resolveAccessFromPointer(LI->getPointerOperand(), loadSize, tracked, DL, + access)) + { + const TrackedMemoryObject& obj = tracked.objects[access.objectIdx]; + bool isDefInit = + isRangeCovered(initialized[access.objectIdx], access.begin, access.end); + if (!isDefInit) + { + if (isAllocaObject(obj)) + { + if (emittedIssues) + { + emittedIssues->push_back( + {I.getFunction()->getName().str(), getTrackedObjectName(obj), + LI, 0, 0, "", + UninitializedLocalIssueKind::ReadBeforeDefiniteInit}); + } + if (readBeforeInitSeen && access.objectIdx < readBeforeInitSeen->size()) + readBeforeInitSeen->set(access.objectIdx); + } + else if (currentSummary && obj.param) + { + addRange(currentSummary->paramEffects[obj.param->getArgNo()] + .readBeforeWriteRanges, + access.begin, access.end); + } + } + return; + } + + unsigned objectIdx = 0; + std::uint64_t offset = 0; + bool hasConstOffset = false; + if (!resolveTrackedObjectBase(LI->getPointerOperand(), tracked, DL, objectIdx, + offset, hasConstOffset)) + { + return; + } + + const TrackedMemoryObject& obj = tracked.objects[objectIdx]; + InitLatticeState stateKind = + classifyInitState(initialized[objectIdx], getObjectFullRangeEnd(obj)); + bool isDefInit = (stateKind == InitLatticeState::Init); + if (!isDefInit) + { + if (isAllocaObject(obj)) + { + if (emittedIssues) + { + emittedIssues->push_back( + {I.getFunction()->getName().str(), getTrackedObjectName(obj), LI, 0, + 0, "", UninitializedLocalIssueKind::ReadBeforeDefiniteInit}); + } + if (readBeforeInitSeen && objectIdx < readBeforeInitSeen->size()) + readBeforeInitSeen->set(objectIdx); + } + else if (currentSummary && obj.param) + { + currentSummary->paramEffects[obj.param->getArgNo()] + .hasUnknownReadBeforeWrite = true; + } + } + return; + } + + if (auto* SI = llvm::dyn_cast(&I)) + { + if (llvm::isa(SI->getValueOperand()) || + llvm::isa(SI->getValueOperand())) + { + return; + } + + std::uint64_t storeSize = + getTypeStoreSizeBytes(SI->getValueOperand()->getType(), DL); + MemoryAccess access; + if (resolveAccessFromPointer(SI->getPointerOperand(), storeSize, tracked, DL, + access)) + { + const TrackedMemoryObject& obj = tracked.objects[access.objectIdx]; + addRange(initialized[access.objectIdx], access.begin, access.end); + if (isAllocaObject(obj)) + { + if (writeSeen && access.objectIdx < writeSeen->size()) + writeSeen->set(access.objectIdx); + } + else if (currentSummary && obj.param) + { + addRange(currentSummary->paramEffects[obj.param->getArgNo()].writeRanges, + access.begin, access.end); + } + return; + } + + unsigned objectIdx = 0; + std::uint64_t offset = 0; + bool hasConstOffset = false; + if (!resolveTrackedObjectBase(SI->getPointerOperand(), tracked, DL, objectIdx, + offset, hasConstOffset)) + { + return; + } + + const TrackedMemoryObject& obj = tracked.objects[objectIdx]; + if (isAllocaObject(obj)) + { + if (writeSeen && objectIdx < writeSeen->size()) + writeSeen->set(objectIdx); + } + else if (currentSummary && obj.param) + { + currentSummary->paramEffects[obj.param->getArgNo()].hasUnknownWrite = true; + } + return; + } + + auto* MI = llvm::dyn_cast(&I); + if (MI) + { + auto* len = llvm::dyn_cast(MI->getLength()); + if (len && len->isZero()) + return; + + bool isInitWrite = + llvm::isa(MI) || llvm::isa(MI); + if (!isInitWrite) + return; + + if (len) + { + std::uint64_t writeSize = len->getZExtValue(); + MemoryAccess access; + if (resolveAccessFromPointer(MI->getDest(), writeSize, tracked, DL, access)) + { + const TrackedMemoryObject& obj = tracked.objects[access.objectIdx]; + addRange(initialized[access.objectIdx], access.begin, access.end); + if (isAllocaObject(obj)) + { + if (writeSeen && access.objectIdx < writeSeen->size()) + writeSeen->set(access.objectIdx); + } + else if (currentSummary && obj.param) + { + addRange( + currentSummary->paramEffects[obj.param->getArgNo()].writeRanges, + access.begin, access.end); + } + return; + } + } + + unsigned objectIdx = 0; + std::uint64_t offset = 0; + bool hasConstOffset = false; + if (!resolveTrackedObjectBase(MI->getDest(), tracked, DL, objectIdx, offset, + hasConstOffset)) + { + return; + } + + const TrackedMemoryObject& obj = tracked.objects[objectIdx]; + if (isAllocaObject(obj)) + { + if (writeSeen && objectIdx < writeSeen->size()) + writeSeen->set(objectIdx); + } + else if (currentSummary && obj.param) + { + currentSummary->paramEffects[obj.param->getArgNo()].hasUnknownWrite = true; + } + return; + } + + auto* CB = llvm::dyn_cast(&I); + if (!CB) + return; + + const llvm::Function* callee = CB->getCalledFunction(); + if (!callee || callee->isDeclaration()) + return; + + auto itSummary = summaries.find(callee); + if (itSummary == summaries.end()) + return; + + applyCalleeSummaryAtCall(*CB, *callee, itSummary->second, tracked, DL, initialized, + writeSeen, readBeforeInitSeen, currentSummary, emittedIssues); + } + + static void analyzeFunction(const llvm::Function& F, const llvm::DataLayout& DL, + const FunctionSummaryMap& summaries, + FunctionSummary* outSummary, + std::vector* outIssues) + { + TrackedObjectContext tracked; + collectTrackedObjects(F, DL, tracked); + if (tracked.objects.empty()) + return; + + const unsigned trackedCount = static_cast(tracked.objects.size()); + + llvm::DenseMap reachable; + computeReachableBlocks(F, reachable); + + llvm::DenseMap inState; + llvm::DenseMap outState; + + for (const llvm::BasicBlock& BB : F) + { + if (!reachable.lookup(&BB)) + continue; + const bool isEntry = (&BB == &F.getEntryBlock()); + inState[&BB] = isEntry ? makeBottomState(trackedCount) : makeTopState(tracked); + outState[&BB] = isEntry ? makeBottomState(trackedCount) : makeTopState(tracked); + } + + bool changed = true; + while (changed) + { + changed = false; + + for (const llvm::BasicBlock& BB : F) + { + if (!reachable.lookup(&BB)) + continue; + + InitRangeState newIn = + computeInState(BB, &F.getEntryBlock(), reachable, outState, tracked); + + InitRangeState state = newIn; + for (const llvm::Instruction& I : BB) + { + transferInstruction(I, tracked, DL, summaries, state, nullptr, nullptr, + outSummary, nullptr); + } + + InitRangeState& oldIn = inState[&BB]; + InitRangeState& oldOut = outState[&BB]; + if (!statesEqual(oldIn, newIn)) + { + oldIn = std::move(newIn); + changed = true; + } + if (!statesEqual(oldOut, state)) + { + oldOut = std::move(state); + changed = true; + } + } + } + + if (outSummary) + { + return; + } + + llvm::BitVector writeSeen(trackedCount, false); + llvm::BitVector readBeforeInitSeen(trackedCount, false); + + for (const llvm::BasicBlock& BB : F) + { + if (!reachable.lookup(&BB)) + continue; + + InitRangeState state = inState[&BB]; + for (const llvm::Instruction& I : BB) + { + transferInstruction(I, tracked, DL, summaries, state, &writeSeen, + &readBeforeInitSeen, nullptr, outIssues); + } + } + + for (unsigned idx = 0; idx < trackedCount; ++idx) + { + const TrackedMemoryObject& obj = tracked.objects[idx]; + if (!isAllocaObject(obj)) + continue; + if (writeSeen.test(idx)) + continue; + if (readBeforeInitSeen.test(idx)) + continue; + + const llvm::AllocaInst* AI = obj.alloca; + if (!AI) + continue; + + const std::string varName = deriveAllocaName(AI); + if (varName.empty() || varName == "") + continue; + + unsigned line = 0; + unsigned column = 0; + getAllocaDeclarationLocation(AI, line, column); + if (outIssues) + { + outIssues->push_back({F.getName().str(), varName, getAllocaDebugAnchor(AI), + line, column, "", + UninitializedLocalIssueKind::NeverInitialized}); + } + } + } + + static FunctionSummaryMap + computeFunctionSummaries(llvm::Module& mod, + const std::function& shouldAnalyze) + { + FunctionSummaryMap summaries; + for (const llvm::Function& F : mod) + { + if (F.isDeclaration()) + continue; + if (!shouldAnalyze(F)) + continue; + summaries[&F] = makeEmptySummary(F); + } + + bool changed = true; + unsigned guard = 0; + while (changed && guard < 64) + { + changed = false; + ++guard; + + for (const llvm::Function& F : mod) + { + if (F.isDeclaration()) + continue; + if (!shouldAnalyze(F)) + continue; + + FunctionSummary next = makeEmptySummary(F); + analyzeFunction(F, mod.getDataLayout(), summaries, &next, nullptr); + FunctionSummary& cur = summaries[&F]; + if (!(cur == next)) + { + cur = std::move(next); + changed = true; + } + } + } + + return summaries; + } + } // namespace + + std::vector + analyzeUninitializedLocalReads(llvm::Module& mod, + const std::function& shouldAnalyze) + { + std::vector issues; + + FunctionSummaryMap summaries = computeFunctionSummaries(mod, shouldAnalyze); + + for (const llvm::Function& F : mod) + { + if (F.isDeclaration()) + continue; + if (!shouldAnalyze(F)) + continue; + + analyzeFunction(F, mod.getDataLayout(), summaries, nullptr, &issues); + } + + return issues; + } +} // namespace ctrace::stack::analysis From aadf00b06f73a52e7173ec9d24a776f2eced3335 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 02:41:20 +0100 Subject: [PATCH 11/41] test(uninitialized): add never-used local variable case --- .../uninitialized-local-argument-never-used.c | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 test/uninitialized-variable/uninitialized-local-argument-never-used.c diff --git a/test/uninitialized-variable/uninitialized-local-argument-never-used.c b/test/uninitialized-variable/uninitialized-local-argument-never-used.c new file mode 100644 index 0000000..0ef83bd --- /dev/null +++ b/test/uninitialized-variable/uninitialized-local-argument-never-used.c @@ -0,0 +1,10 @@ +#include + +int main(int argc, char** argv) +{ + int value_int; + bool value_bool; + void* value_ptr; + + return 0; +} From b2f11c94916ca1c3eafcbe87210b8df5031f0ddf Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 02:41:20 +0100 Subject: [PATCH 12/41] test(uninitialized): add C argument path with conditional initialization --- .../uninitialized-local-argument.c | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 test/uninitialized-variable/uninitialized-local-argument.c diff --git a/test/uninitialized-variable/uninitialized-local-argument.c b/test/uninitialized-variable/uninitialized-local-argument.c new file mode 100644 index 0000000..a9044dc --- /dev/null +++ b/test/uninitialized-variable/uninitialized-local-argument.c @@ -0,0 +1,21 @@ +#include + +int main(int argc, char** argv) +{ + int value; + + for (int i = 1; i < argc; ++i) + { + printf("Argument: %s\n", argv[i]); + value = i; // just to use 'value' and avoid unused variable warning + } + printf("%d\n", value); + return 0; +} + +// at line 12, column 20 +// [!!] potential read of uninitialized local variable 'value' +// this load may execute before any definite initialization on all control-flow paths + +// not contains: potential read of uninitialized local variable 'argc.addr' +// not contains: potential read of uninitialized local variable 'argv.addr' From 42dde51123f927756d1b718918b201ae958721aa Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 02:41:20 +0100 Subject: [PATCH 13/41] test(uninitialized): add C++ argument handling without false positives on argc argv --- .../uninitialized-local-argument.cpp | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 test/uninitialized-variable/uninitialized-local-argument.cpp diff --git a/test/uninitialized-variable/uninitialized-local-argument.cpp b/test/uninitialized-variable/uninitialized-local-argument.cpp new file mode 100644 index 0000000..c23bad0 --- /dev/null +++ b/test/uninitialized-variable/uninitialized-local-argument.cpp @@ -0,0 +1,22 @@ +#include +#include + +int main(int argc, char** argv) +{ + std::string value; + uint16_t value16; + + for (int i = 1; i < argc; ++i) + { + const char* arg = argv[i]; + if (std::strcmp(arg, "--help") == 0 || std::strcmp(arg, "-h") == 0) + { + return 1; + } + } + std::cout << value << std::endl; + return 0; +} + +// not contains: potential read of uninitialized local variable 'argc.addr' +// not contains: potential read of uninitialized local variable 'argv.addr' From e862076edaa1a2f9a49334069fdf8f30588c7709 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 02:41:20 +0100 Subject: [PATCH 14/41] test(uninitialized): add fully initialized array no-warning case --- .../uninitialized-local-array-fully-initialized.c | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 test/uninitialized-variable/uninitialized-local-array-fully-initialized.c diff --git a/test/uninitialized-variable/uninitialized-local-array-fully-initialized.c b/test/uninitialized-variable/uninitialized-local-array-fully-initialized.c new file mode 100644 index 0000000..41128f1 --- /dev/null +++ b/test/uninitialized-variable/uninitialized-local-array-fully-initialized.c @@ -0,0 +1,9 @@ +int read_array_fully_initialized(void) +{ + int arr[2]; + arr[0] = 10; + arr[1] = 20; + return arr[1]; +} + +// not contains: potential read of uninitialized local variable 'arr' From 9068b9f0785954853f92067426cef56cab10e2f1 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 02:41:20 +0100 Subject: [PATCH 15/41] test(uninitialized): add partially initialized array warning case --- .../uninitialized-local-array-partial.c | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 test/uninitialized-variable/uninitialized-local-array-partial.c diff --git a/test/uninitialized-variable/uninitialized-local-array-partial.c b/test/uninitialized-variable/uninitialized-local-array-partial.c new file mode 100644 index 0000000..c0dd96d --- /dev/null +++ b/test/uninitialized-variable/uninitialized-local-array-partial.c @@ -0,0 +1,10 @@ +int read_array_partial(void) +{ + int arr[2]; + arr[0] = 42; + return arr[1]; +} + +// at line 5, column 12 +// [!!] potential read of uninitialized local variable 'arr' +// this load may execute before any definite initialization on all control-flow paths From 6cfc38a110decf406b2efc1d85d22716ae02e09b Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 02:41:21 +0100 Subject: [PATCH 16/41] test(uninitialized): add basic uninitialized array read case --- test/uninitialized-variable/uninitialized-local-array.c | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 test/uninitialized-variable/uninitialized-local-array.c diff --git a/test/uninitialized-variable/uninitialized-local-array.c b/test/uninitialized-variable/uninitialized-local-array.c new file mode 100644 index 0000000..cc9f50f --- /dev/null +++ b/test/uninitialized-variable/uninitialized-local-array.c @@ -0,0 +1,9 @@ +int read_array_elem(void) +{ + int arr[4]; + return arr[2]; +} + +// at line 4, column 12 +// [!!] potential read of uninitialized local variable 'arr' +// this load may execute before any definite initialization on all control-flow paths From 2f1f28afb93aae791222e0ef5413c434269bed3a Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 02:41:21 +0100 Subject: [PATCH 17/41] test(uninitialized): add baseline uninitialized scalar read case --- test/uninitialized-variable/uninitialized-local-basic.c | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 test/uninitialized-variable/uninitialized-local-basic.c diff --git a/test/uninitialized-variable/uninitialized-local-basic.c b/test/uninitialized-variable/uninitialized-local-basic.c new file mode 100644 index 0000000..1b0c429 --- /dev/null +++ b/test/uninitialized-variable/uninitialized-local-basic.c @@ -0,0 +1,8 @@ +int read_uninitialized_basic(void) +{ + int value; + // at line 7, column 12 + // [!!] potential read of uninitialized local variable 'value' + // this load may execute before any definite initialization on all control-flow paths + return value; +} From 800e43c6d6419a981961bbb38ea368bacab3aba7 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 02:41:21 +0100 Subject: [PATCH 18/41] test(uninitialized): add branch-dependent initialization case --- .../uninitialized-local-branch.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 test/uninitialized-variable/uninitialized-local-branch.c diff --git a/test/uninitialized-variable/uninitialized-local-branch.c b/test/uninitialized-variable/uninitialized-local-branch.c new file mode 100644 index 0000000..cae1ed8 --- /dev/null +++ b/test/uninitialized-variable/uninitialized-local-branch.c @@ -0,0 +1,13 @@ +int read_uninitialized_branch(int cond) +{ + int x; + + if (cond) + { + x = 42; + } + // at line 12, column 12 + // [!!] potential read of uninitialized local variable 'x' + // this load may execute before any definite initialization on all control-flow paths + return x; +} From a7a51de82dedd5b3ad0dda2235f7ba7f6f8e0784 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 02:41:21 +0100 Subject: [PATCH 19/41] test(uninitialized): add goto-based control flow initialization case --- .../uninitialized-local-goto.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 test/uninitialized-variable/uninitialized-local-goto.c diff --git a/test/uninitialized-variable/uninitialized-local-goto.c b/test/uninitialized-variable/uninitialized-local-goto.c new file mode 100644 index 0000000..4c0c0b4 --- /dev/null +++ b/test/uninitialized-variable/uninitialized-local-goto.c @@ -0,0 +1,13 @@ +int read_goto(int cond) +{ + int value; + if (cond) + goto done; + value = 1; +done: + return value; +} + +// at line 8, column 12 +// [!!] potential read of uninitialized local variable 'value' +// this load may execute before any definite initialization on all control-flow paths From 1ebd16782e1490aef87d99bef044b53ca1ec6c96 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 02:41:21 +0100 Subject: [PATCH 20/41] test(uninitialized): add initialized local no-warning case --- .../uninitialized-local-initialized.c | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 test/uninitialized-variable/uninitialized-local-initialized.c diff --git a/test/uninitialized-variable/uninitialized-local-initialized.c b/test/uninitialized-variable/uninitialized-local-initialized.c new file mode 100644 index 0000000..cef41fb --- /dev/null +++ b/test/uninitialized-variable/uninitialized-local-initialized.c @@ -0,0 +1,11 @@ +int read_initialized_ok(int cond) +{ + int x = 0; + if (cond) + { + x = 7; + } + return x; +} + +// not contains: potential read of uninitialized local variable From 717e3ce43f4100447113b059bc55998eb4541839 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 02:41:21 +0100 Subject: [PATCH 21/41] test(uninitialized): add interprocedural call-chain read-before-write case --- ...-local-interproc-read-before-write-chain.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 test/uninitialized-variable/uninitialized-local-interproc-read-before-write-chain.c diff --git a/test/uninitialized-variable/uninitialized-local-interproc-read-before-write-chain.c b/test/uninitialized-variable/uninitialized-local-interproc-read-before-write-chain.c new file mode 100644 index 0000000..379bdd2 --- /dev/null +++ b/test/uninitialized-variable/uninitialized-local-interproc-read-before-write-chain.c @@ -0,0 +1,19 @@ +static int read_leaf(const int* p) +{ + return *p; +} + +static int read_mid(const int* p) +{ + return read_leaf(p); +} + +int read_via_call_chain_before_init(void) +{ + int value; + return read_mid(&value); +} + +// at line 14, column 12 +// [!!] potential read of uninitialized local variable 'value' +// this call may read the value before any definite initialization in 'read_mid' From 24363a16d2858f4a8d8976609e96e6196ca5e7f7 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 02:41:21 +0100 Subject: [PATCH 22/41] test(uninitialized): add interprocedural read-before-write case --- ...initialized-local-interproc-read-before-write.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 test/uninitialized-variable/uninitialized-local-interproc-read-before-write.c diff --git a/test/uninitialized-variable/uninitialized-local-interproc-read-before-write.c b/test/uninitialized-variable/uninitialized-local-interproc-read-before-write.c new file mode 100644 index 0000000..146059a --- /dev/null +++ b/test/uninitialized-variable/uninitialized-local-interproc-read-before-write.c @@ -0,0 +1,14 @@ +static int read_value(const int* p) +{ + return *p; +} + +int read_via_call_before_init(void) +{ + int value; + return read_value(&value); +} + +// at line 9, column 12 +// [!!] potential read of uninitialized local variable 'value' +// this call may read the value before any definite initialization in 'read_value' From e1603da149d38cb996894fa0ed22c75d62ab27d9 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 02:41:21 +0100 Subject: [PATCH 23/41] test(uninitialized): add interprocedural partial struct initialization case --- ...itialized-local-interproc-struct-partial.c | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 test/uninitialized-variable/uninitialized-local-interproc-struct-partial.c diff --git a/test/uninitialized-variable/uninitialized-local-interproc-struct-partial.c b/test/uninitialized-variable/uninitialized-local-interproc-struct-partial.c new file mode 100644 index 0000000..601d267 --- /dev/null +++ b/test/uninitialized-variable/uninitialized-local-interproc-struct-partial.c @@ -0,0 +1,21 @@ +struct Pair +{ + int x; + int y; +}; + +static void init_x(struct Pair* p) +{ + p->x = 1; +} + +int read_struct_partial_via_call(void) +{ + struct Pair p; + init_x(&p); + return p.y; +} + +// at line 16, column 14 +// [!!] potential read of uninitialized local variable 'p' +// this load may execute before any definite initialization on all control-flow paths From 5a711cddabd3cc06305262d17978c7e9efc0feab Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 02:41:21 +0100 Subject: [PATCH 24/41] test(uninitialized): add interprocedural write call-chain no-warning case --- ...uninitialized-local-interproc-write-chain.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 test/uninitialized-variable/uninitialized-local-interproc-write-chain.c diff --git a/test/uninitialized-variable/uninitialized-local-interproc-write-chain.c b/test/uninitialized-variable/uninitialized-local-interproc-write-chain.c new file mode 100644 index 0000000..e89be88 --- /dev/null +++ b/test/uninitialized-variable/uninitialized-local-interproc-write-chain.c @@ -0,0 +1,18 @@ +static void init_leaf(int* p) +{ + *p = 7; +} + +static void init_mid(int* p) +{ + init_leaf(p); +} + +int read_after_init_call_chain(void) +{ + int value; + init_mid(&value); + return value; +} + +// not contains: potential read of uninitialized local variable 'value' From 49c5dccaedb32c69ed97e1ac1a95e5865c91f762 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 02:41:21 +0100 Subject: [PATCH 25/41] test(uninitialized): add interprocedural write-before-read no-warning case --- .../uninitialized-local-interproc-write.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 test/uninitialized-variable/uninitialized-local-interproc-write.c diff --git a/test/uninitialized-variable/uninitialized-local-interproc-write.c b/test/uninitialized-variable/uninitialized-local-interproc-write.c new file mode 100644 index 0000000..5ece5ae --- /dev/null +++ b/test/uninitialized-variable/uninitialized-local-interproc-write.c @@ -0,0 +1,13 @@ +static void init_value(int* p) +{ + *p = 42; +} + +int read_after_init_call(void) +{ + int value; + init_value(&value); + return value; +} + +// not contains: potential read of uninitialized local variable 'value' From 1adb6a7662465d12088d8a87b0be474371e60499 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 02:41:21 +0100 Subject: [PATCH 26/41] test(uninitialized): add nested loop partial initialization case --- .../uninitialized-local-nested-loops.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 test/uninitialized-variable/uninitialized-local-nested-loops.c diff --git a/test/uninitialized-variable/uninitialized-local-nested-loops.c b/test/uninitialized-variable/uninitialized-local-nested-loops.c new file mode 100644 index 0000000..de514f6 --- /dev/null +++ b/test/uninitialized-variable/uninitialized-local-nested-loops.c @@ -0,0 +1,16 @@ +int read_nested_loops(int n, int m) +{ + int value; + for (int i = 0; i < n; ++i) + { + for (int j = 0; j < m; ++j) + { + value = i + j; + } + } + return value; +} + +// at line 11, column 12 +// [!!] potential read of uninitialized local variable 'value' +// this load may execute before any definite initialization on all control-flow paths From c000c27e705dc4e243491752845374010331d62a Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 02:41:21 +0100 Subject: [PATCH 27/41] test(uninitialized): add uninitialized pointer dereference case --- .../uninitialized-local-pointer.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 test/uninitialized-variable/uninitialized-local-pointer.c diff --git a/test/uninitialized-variable/uninitialized-local-pointer.c b/test/uninitialized-variable/uninitialized-local-pointer.c new file mode 100644 index 0000000..5c41bbd --- /dev/null +++ b/test/uninitialized-variable/uninitialized-local-pointer.c @@ -0,0 +1,12 @@ +int read_pointer_target(int cond) +{ + int fallback = 0; + int* ptr; + if (cond) + ptr = &fallback; + return *ptr; +} + +// at line 7, column 13 +// [!!] potential read of uninitialized local variable 'ptr' +// this load may execute before any definite initialization on all control-flow paths From ce443095e1f26dd38fad00e32d98398e2f6af04c Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 02:41:21 +0100 Subject: [PATCH 28/41] test(uninitialized): add C++ reference propagation case --- .../uninitialized-local-reference.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 test/uninitialized-variable/uninitialized-local-reference.cpp diff --git a/test/uninitialized-variable/uninitialized-local-reference.cpp b/test/uninitialized-variable/uninitialized-local-reference.cpp new file mode 100644 index 0000000..b0b51a6 --- /dev/null +++ b/test/uninitialized-variable/uninitialized-local-reference.cpp @@ -0,0 +1,14 @@ +int read_cpp_ref(int cond, int& out) +{ + int value; + if (cond) + { + value = 42; + } + out = value; + return out; +} + +// at line 8, column 11 +// [!!] potential read of uninitialized local variable 'value' +// this load may execute before any definite initialization on all control-flow paths From 425eed8db6b00f02fd03726aeab68af41721d04b Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 02:41:21 +0100 Subject: [PATCH 29/41] test(uninitialized): add fully initialized struct no-warning case --- ...uninitialized-local-struct-fully-initialized.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 test/uninitialized-variable/uninitialized-local-struct-fully-initialized.c diff --git a/test/uninitialized-variable/uninitialized-local-struct-fully-initialized.c b/test/uninitialized-variable/uninitialized-local-struct-fully-initialized.c new file mode 100644 index 0000000..e6a4eea --- /dev/null +++ b/test/uninitialized-variable/uninitialized-local-struct-fully-initialized.c @@ -0,0 +1,15 @@ +struct Point +{ + int x; + int y; +}; + +int read_struct_fully_initialized(void) +{ + struct Point p; + p.x = 1; + p.y = 2; + return p.y; +} + +// not contains: potential read of uninitialized local variable 'p' From 2611acd9f63383f5c89a7cc29e2aba331e7f6a46 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 02:41:21 +0100 Subject: [PATCH 30/41] test(uninitialized): add partially initialized struct warning case --- .../uninitialized-local-struct-partial.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 test/uninitialized-variable/uninitialized-local-struct-partial.c diff --git a/test/uninitialized-variable/uninitialized-local-struct-partial.c b/test/uninitialized-variable/uninitialized-local-struct-partial.c new file mode 100644 index 0000000..172b660 --- /dev/null +++ b/test/uninitialized-variable/uninitialized-local-struct-partial.c @@ -0,0 +1,16 @@ +struct Pair +{ + int x; + int y; +}; + +int read_struct_partial(void) +{ + struct Pair p; + p.x = 7; + return p.y; +} + +// at line 11, column 14 +// [!!] potential read of uninitialized local variable 'p' +// this load may execute before any definite initialization on all control-flow paths From 49aa131685b438cf265839ebe721ab314feebe5b Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 02:41:21 +0100 Subject: [PATCH 31/41] test(uninitialized): add basic uninitialized struct field read case --- .../uninitialized-local-struct.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 test/uninitialized-variable/uninitialized-local-struct.c diff --git a/test/uninitialized-variable/uninitialized-local-struct.c b/test/uninitialized-variable/uninitialized-local-struct.c new file mode 100644 index 0000000..50deb37 --- /dev/null +++ b/test/uninitialized-variable/uninitialized-local-struct.c @@ -0,0 +1,15 @@ +struct Pair +{ + int x; + int y; +}; + +int read_struct_field(void) +{ + struct Pair p; + return p.x; +} + +// at line 10, column 14 +// [!!] potential read of uninitialized local variable 'p' +// this load may execute before any definite initialization on all control-flow paths From 75ee66e5321788b68c00f36abc8f0a9e042c1f1e Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 02:41:21 +0100 Subject: [PATCH 32/41] test(uninitialized): add switch-dependent initialization case --- .../uninitialized-local-switch.c | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 test/uninitialized-variable/uninitialized-local-switch.c diff --git a/test/uninitialized-variable/uninitialized-local-switch.c b/test/uninitialized-variable/uninitialized-local-switch.c new file mode 100644 index 0000000..d1acbf5 --- /dev/null +++ b/test/uninitialized-variable/uninitialized-local-switch.c @@ -0,0 +1,20 @@ +int read_switch(int tag) +{ + int value; + switch (tag) + { + case 0: + value = 10; + break; + case 1: + break; + default: + value = 20; + break; + } + return value; +} + +// at line 15, column 12 +// [!!] potential read of uninitialized local variable 'value' +// this load may execute before any definite initialization on all control-flow paths From 5603a9c78cfb933a6ac576bddccd23b1662dbe55 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 02:41:21 +0100 Subject: [PATCH 33/41] test(uninitialized): add unused uninitialized local declaration case --- test/uninitialized-variable/uninitialized-local-unused.c | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 test/uninitialized-variable/uninitialized-local-unused.c diff --git a/test/uninitialized-variable/uninitialized-local-unused.c b/test/uninitialized-variable/uninitialized-local-unused.c new file mode 100644 index 0000000..0ff3ec9 --- /dev/null +++ b/test/uninitialized-variable/uninitialized-local-unused.c @@ -0,0 +1,5 @@ +int main(void) +{ + int value; + return 0; +} From de7191a99de63469e2da80f98cbba79be47215c9 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 02:53:40 +0100 Subject: [PATCH 34/41] fix(test): add cstdint include for uninitialized local argument fixture --- test/uninitialized-variable/uninitialized-local-argument.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/uninitialized-variable/uninitialized-local-argument.cpp b/test/uninitialized-variable/uninitialized-local-argument.cpp index c23bad0..d3f5081 100644 --- a/test/uninitialized-variable/uninitialized-local-argument.cpp +++ b/test/uninitialized-variable/uninitialized-local-argument.cpp @@ -1,10 +1,11 @@ +#include #include #include int main(int argc, char** argv) { std::string value; - uint16_t value16; + std::uint16_t value16 = 0; for (int i = 1; i < argc; ++i) { From 4dd6175c53dc8d36e0afc38fcb07681e79bf9c51 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 03:11:35 +0100 Subject: [PATCH 35/41] fix(uninitialized): optimize range updates and harden summary/read tracking logic --- src/analysis/UninitializedVarAnalysis.cpp | 274 +++++++++++++++++----- 1 file changed, 217 insertions(+), 57 deletions(-) diff --git a/src/analysis/UninitializedVarAnalysis.cpp b/src/analysis/UninitializedVarAnalysis.cpp index 8345f77..0f3d911 100644 --- a/src/analysis/UninitializedVarAnalysis.cpp +++ b/src/analysis/UninitializedVarAnalysis.cpp @@ -1,6 +1,7 @@ #include "analysis/UninitializedVarAnalysis.hpp" #include +#include #include #include #include @@ -150,49 +151,40 @@ namespace ctrace::stack::analysis return size.getFixedValue(); } - static void normalizeRanges(RangeSet& ranges) + static void addRange(RangeSet& ranges, std::uint64_t begin, std::uint64_t end) { - if (ranges.empty()) + if (begin >= end) return; - std::sort(ranges.begin(), ranges.end(), - [](const ByteRange& lhs, const ByteRange& rhs) - { - if (lhs.begin != rhs.begin) - return lhs.begin < rhs.begin; - return lhs.end < rhs.end; - }); + auto it = std::lower_bound( + ranges.begin(), ranges.end(), begin, + [](const ByteRange& r, std::uint64_t value) + { + return r.end < value; + }); - std::size_t outIdx = 0; - for (std::size_t i = 0; i < ranges.size(); ++i) + if (it == ranges.end()) { - ByteRange current = ranges[i]; - if (current.begin >= current.end) - continue; - if (outIdx == 0) - { - ranges[outIdx++] = current; - continue; - } - ByteRange& prev = ranges[outIdx - 1]; - if (current.begin <= prev.end) - { - prev.end = std::max(prev.end, current.end); - } - else - { - ranges[outIdx++] = current; - } + ranges.push_back({begin, end}); + return; } - ranges.resize(outIdx); - } - static void addRange(RangeSet& ranges, std::uint64_t begin, std::uint64_t end) - { - if (begin >= end) + if (end < it->begin) + { + ranges.insert(it, {begin, end}); return; - ranges.push_back({begin, end}); - normalizeRanges(ranges); + } + + it->begin = std::min(it->begin, begin); + it->end = std::max(it->end, end); + + auto next = it + 1; + while (next != ranges.end() && next->begin <= it->end) + { + it->end = std::max(it->end, next->end); + ++next; + } + ranges.erase(it + 1, next); } static RangeSet intersectRanges(const RangeSet& lhs, const RangeSet& rhs) @@ -334,22 +326,52 @@ namespace ctrace::stack::analysis break; const llvm::StoreInst* uniqueStore = nullptr; + bool unsafeUse = false; for (const llvm::Use& U : slot->uses()) { - const auto* SI = llvm::dyn_cast(U.getUser()); - if (!SI) + const auto* user = U.getUser(); + if (const auto* SI = llvm::dyn_cast(user)) + { + if (SI->getPointerOperand()->stripPointerCasts() != slot) + { + unsafeUse = true; + break; + } + if (uniqueStore && uniqueStore != SI) + { + uniqueStore = nullptr; + break; + } + uniqueStore = SI; continue; - if (SI->getPointerOperand()->stripPointerCasts() != slot) + } + + if (const auto* LI = llvm::dyn_cast(user)) + { + if (LI->getPointerOperand()->stripPointerCasts() != slot) + { + unsafeUse = true; + break; + } continue; - if (uniqueStore && uniqueStore != SI) + } + + if (const auto* II = llvm::dyn_cast(user)) { - uniqueStore = nullptr; + if (llvm::isa(II) || + llvm::isa(II)) + { + continue; + } + unsafeUse = true; break; } - uniqueStore = SI; + + unsafeUse = true; + break; } - if (!uniqueStore) + if (unsafeUse || !uniqueStore) break; const llvm::Value* storedPtr = uniqueStore->getValueOperand()->stripPointerCasts(); @@ -610,6 +632,15 @@ namespace ctrace::stack::analysis return summary; } + static PointerParamEffectSummary& getParamEffect(FunctionSummary& summary, + const llvm::Argument& arg) + { + const unsigned argNo = arg.getArgNo(); + assert(argNo < summary.paramEffects.size() && + "pointer parameter index must fit in summary vector"); + return summary.paramEffects[argNo]; + } + static void applyCalleeSummaryAtCall( const llvm::CallBase& CB, const llvm::Function& callee, const FunctionSummary& calleeSummary, const TrackedObjectContext& tracked, @@ -708,7 +739,7 @@ namespace ctrace::stack::analysis else if (currentSummary && obj.param) { PointerParamEffectSummary& current = - currentSummary->paramEffects[obj.param->getArgNo()]; + getParamEffect(*currentSummary, *obj.param); for (const ByteRange& rr : uncoveredReadRanges) { addRange(current.readBeforeWriteRanges, rr.begin, rr.end); @@ -743,7 +774,7 @@ namespace ctrace::stack::analysis if (currentSummary && isParamObject(obj) && obj.param) { PointerParamEffectSummary& current = - currentSummary->paramEffects[obj.param->getArgNo()]; + getParamEffect(*currentSummary, *obj.param); addRange(current.writeRanges, clippedBegin, clippedEnd); } } @@ -765,7 +796,7 @@ namespace ctrace::stack::analysis if (writeWasUnknown && currentSummary && isParamObject(obj) && obj.param) { - currentSummary->paramEffects[obj.param->getArgNo()].hasUnknownWrite = true; + getParamEffect(*currentSummary, *obj.param).hasUnknownWrite = true; } if (wroteSomething && writeSeen && isAllocaObject(obj) && @@ -809,8 +840,8 @@ namespace ctrace::stack::analysis } else if (currentSummary && obj.param) { - addRange(currentSummary->paramEffects[obj.param->getArgNo()] - .readBeforeWriteRanges, + addRange( + getParamEffect(*currentSummary, *obj.param).readBeforeWriteRanges, access.begin, access.end); } } @@ -845,8 +876,8 @@ namespace ctrace::stack::analysis } else if (currentSummary && obj.param) { - currentSummary->paramEffects[obj.param->getArgNo()] - .hasUnknownReadBeforeWrite = true; + getParamEffect(*currentSummary, *obj.param).hasUnknownReadBeforeWrite = + true; } } return; @@ -875,7 +906,7 @@ namespace ctrace::stack::analysis } else if (currentSummary && obj.param) { - addRange(currentSummary->paramEffects[obj.param->getArgNo()].writeRanges, + addRange(getParamEffect(*currentSummary, *obj.param).writeRanges, access.begin, access.end); } return; @@ -898,7 +929,7 @@ namespace ctrace::stack::analysis } else if (currentSummary && obj.param) { - currentSummary->paramEffects[obj.param->getArgNo()].hasUnknownWrite = true; + getParamEffect(*currentSummary, *obj.param).hasUnknownWrite = true; } return; } @@ -915,6 +946,120 @@ namespace ctrace::stack::analysis if (!isInitWrite) return; + if (auto* MTI = llvm::dyn_cast(MI)) + { + if (len) + { + std::uint64_t readSize = len->getZExtValue(); + MemoryAccess srcAccess; + if (resolveAccessFromPointer(MTI->getSource(), readSize, tracked, DL, + srcAccess)) + { + const TrackedMemoryObject& srcObj = tracked.objects[srcAccess.objectIdx]; + bool srcDefInit = isRangeCovered(initialized[srcAccess.objectIdx], + srcAccess.begin, srcAccess.end); + if (!srcDefInit) + { + if (isAllocaObject(srcObj)) + { + if (emittedIssues) + { + emittedIssues->push_back( + {I.getFunction()->getName().str(), + getTrackedObjectName(srcObj), MI, 0, 0, "", + UninitializedLocalIssueKind::ReadBeforeDefiniteInit}); + } + if (readBeforeInitSeen && + srcAccess.objectIdx < readBeforeInitSeen->size()) + { + readBeforeInitSeen->set(srcAccess.objectIdx); + } + } + else if (currentSummary && srcObj.param) + { + addRange(getParamEffect(*currentSummary, *srcObj.param) + .readBeforeWriteRanges, + srcAccess.begin, srcAccess.end); + } + } + } + else + { + unsigned srcObjectIdx = 0; + std::uint64_t srcOffset = 0; + bool srcHasConstOffset = false; + if (resolveTrackedObjectBase(MTI->getSource(), tracked, DL, + srcObjectIdx, srcOffset, + srcHasConstOffset)) + { + const TrackedMemoryObject& srcObj = tracked.objects[srcObjectIdx]; + InitLatticeState stateKind = classifyInitState( + initialized[srcObjectIdx], getObjectFullRangeEnd(srcObj)); + if (stateKind != InitLatticeState::Init) + { + if (isAllocaObject(srcObj)) + { + if (emittedIssues) + { + emittedIssues->push_back( + {I.getFunction()->getName().str(), + getTrackedObjectName(srcObj), MI, 0, 0, "", + UninitializedLocalIssueKind:: + ReadBeforeDefiniteInit}); + } + if (readBeforeInitSeen && + srcObjectIdx < readBeforeInitSeen->size()) + { + readBeforeInitSeen->set(srcObjectIdx); + } + } + else if (currentSummary && srcObj.param) + { + getParamEffect(*currentSummary, *srcObj.param) + .hasUnknownReadBeforeWrite = true; + } + } + } + } + } + else + { + unsigned srcObjectIdx = 0; + std::uint64_t srcOffset = 0; + bool srcHasConstOffset = false; + if (resolveTrackedObjectBase(MTI->getSource(), tracked, DL, srcObjectIdx, + srcOffset, srcHasConstOffset)) + { + const TrackedMemoryObject& srcObj = tracked.objects[srcObjectIdx]; + InitLatticeState stateKind = classifyInitState( + initialized[srcObjectIdx], getObjectFullRangeEnd(srcObj)); + if (stateKind != InitLatticeState::Init) + { + if (isAllocaObject(srcObj)) + { + if (emittedIssues) + { + emittedIssues->push_back( + {I.getFunction()->getName().str(), + getTrackedObjectName(srcObj), MI, 0, 0, "", + UninitializedLocalIssueKind::ReadBeforeDefiniteInit}); + } + if (readBeforeInitSeen && + srcObjectIdx < readBeforeInitSeen->size()) + { + readBeforeInitSeen->set(srcObjectIdx); + } + } + else if (currentSummary && srcObj.param) + { + getParamEffect(*currentSummary, *srcObj.param) + .hasUnknownReadBeforeWrite = true; + } + } + } + } + } + if (len) { std::uint64_t writeSize = len->getZExtValue(); @@ -930,9 +1075,8 @@ namespace ctrace::stack::analysis } else if (currentSummary && obj.param) { - addRange( - currentSummary->paramEffects[obj.param->getArgNo()].writeRanges, - access.begin, access.end); + addRange(getParamEffect(*currentSummary, *obj.param).writeRanges, + access.begin, access.end); } return; } @@ -955,7 +1099,7 @@ namespace ctrace::stack::analysis } else if (currentSummary && obj.param) { - currentSummary->paramEffects[obj.param->getArgNo()].hasUnknownWrite = true; + getParamEffect(*currentSummary, *obj.param).hasUnknownWrite = true; } return; } @@ -1003,9 +1147,13 @@ namespace ctrace::stack::analysis outState[&BB] = isEntry ? makeBottomState(trackedCount) : makeTopState(tracked); } + const unsigned reachableBlocks = static_cast(outState.size()); + const unsigned maxIterations = std::max(64u, reachableBlocks * 16u); bool changed = true; - while (changed) + unsigned iteration = 0; + while (changed && iteration < maxIterations) { + ++iteration; changed = false; for (const llvm::BasicBlock& BB : F) @@ -1020,7 +1168,7 @@ namespace ctrace::stack::analysis for (const llvm::Instruction& I : BB) { transferInstruction(I, tracked, DL, summaries, state, nullptr, nullptr, - outSummary, nullptr); + nullptr, nullptr); } InitRangeState& oldIn = inState[&BB]; @@ -1040,6 +1188,18 @@ namespace ctrace::stack::analysis if (outSummary) { + for (const llvm::BasicBlock& BB : F) + { + if (!reachable.lookup(&BB)) + continue; + + InitRangeState state = inState[&BB]; + for (const llvm::Instruction& I : BB) + { + transferInstruction(I, tracked, DL, summaries, state, nullptr, nullptr, + outSummary, nullptr); + } + } return; } From 59c4b7353a2683b7c6811e048b3980c2e2e00110 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 03:11:57 +0100 Subject: [PATCH 36/41] fix(filter): make path normalization deterministic for relative paths --- src/analysis/FunctionFilter.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/analysis/FunctionFilter.cpp b/src/analysis/FunctionFilter.cpp index 6d8d9d0..344487e 100644 --- a/src/analysis/FunctionFilter.cpp +++ b/src/analysis/FunctionFilter.cpp @@ -30,12 +30,13 @@ namespace ctrace::stack::analysis std::filesystem::path path(adjusted); std::error_code ec; - std::filesystem::path absPath = std::filesystem::absolute(path, ec); - if (ec) - absPath = path; - - std::filesystem::path canonicalPath = std::filesystem::weakly_canonical(absPath, ec); - std::filesystem::path norm = ec ? absPath.lexically_normal() : canonicalPath; + std::filesystem::path norm = path.lexically_normal(); + if (norm.is_absolute()) + { + std::filesystem::path canonicalPath = std::filesystem::weakly_canonical(norm, ec); + if (!ec) + norm = canonicalPath; + } std::string out = norm.generic_string(); while (out.size() > 1 && out.back() == '/') out.pop_back(); From a915ea4e6253caf8514973e94817eb54d0fa7638 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 03:12:26 +0100 Subject: [PATCH 37/41] fix(report): sort SARIF rules deterministically and format confidence values --- src/report/ReportSerialization.cpp | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/report/ReportSerialization.cpp b/src/report/ReportSerialization.cpp index 1dca514..fc57eb5 100644 --- a/src/report/ReportSerialization.cpp +++ b/src/report/ReportSerialization.cpp @@ -1,9 +1,11 @@ #include "StackUsageAnalyzer.hpp" +#include #include // std::snprintf -#include +#include #include #include +#include #include namespace ctrace::stack @@ -89,6 +91,13 @@ namespace ctrace::stack return std::string(ctrace::stack::enumToString(d.errCode)); } + static std::string formatConfidence(double confidence) + { + std::ostringstream os; + os << std::fixed << std::setprecision(2) << confidence; + return os.str(); + } + } // anonymous namespace static std::string toJsonImpl(const AnalysisResult& result, const std::string* inputFile, @@ -202,7 +211,7 @@ namespace ctrace::stack os << " \"ruleId\": \"" << jsonEscape(ruleId) << "\",\n"; os << " \"confidence\": "; if (d.confidence >= 0.0) - os << d.confidence; + os << formatConfidence(d.confidence); else os << "null"; os << ",\n"; @@ -283,6 +292,11 @@ namespace ctrace::stack rules[it->second].cweId = d.cweId; } } + std::sort(rules.begin(), rules.end(), + [](const SarifRuleEntry& lhs, const SarifRuleEntry& rhs) + { + return lhs.id < rhs.id; + }); std::ostringstream os; os << "{\n"; @@ -340,7 +354,7 @@ namespace ctrace::stack bool needComma = false; if (hasConfidence) { - os << " \"confidence\": " << d.confidence; + os << " \"confidence\": " << formatConfidence(d.confidence); needComma = true; } if (hasCwe) From 9801e8bd3053fd390bb1e2beac60a71e522d7e29 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 03:13:03 +0100 Subject: [PATCH 38/41] test(uninitialized): add regression for memcpy reading uninitialized source --- .../uninitialized-local-memcpy-read.c | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 test/uninitialized-variable/uninitialized-local-memcpy-read.c diff --git a/test/uninitialized-variable/uninitialized-local-memcpy-read.c b/test/uninitialized-variable/uninitialized-local-memcpy-read.c new file mode 100644 index 0000000..19933f6 --- /dev/null +++ b/test/uninitialized-variable/uninitialized-local-memcpy-read.c @@ -0,0 +1,11 @@ +int memcpy_reads_uninitialized_source(void) +{ + int src; + int dst = 0; + __builtin_memcpy(&dst, &src, sizeof(src)); + return dst; +} + +// at line 5, column 5 +// [!!] potential read of uninitialized local variable 'src' +// this load may execute before any definite initialization on all control-flow paths From 31074b3015bd58324a22ae62efe5ff4beb8fb326 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 03:13:20 +0100 Subject: [PATCH 39/41] test(uninitialized): add negative memcpy case with initialized source --- .../uninitialized-local-memcpy-initialized.c | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 test/uninitialized-variable/uninitialized-local-memcpy-initialized.c diff --git a/test/uninitialized-variable/uninitialized-local-memcpy-initialized.c b/test/uninitialized-variable/uninitialized-local-memcpy-initialized.c new file mode 100644 index 0000000..dff7256 --- /dev/null +++ b/test/uninitialized-variable/uninitialized-local-memcpy-initialized.c @@ -0,0 +1,9 @@ +int memcpy_reads_initialized_source(void) +{ + int src = 7; + int dst = 0; + __builtin_memcpy(&dst, &src, sizeof(src)); + return dst; +} + +// not contains: potential read of uninitialized local variable 'src' From 44132368e32fd9f3b1b9b712f2b6fa0a31ab26e1 Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 03:13:41 +0100 Subject: [PATCH 40/41] test(uninitialized): add pointer redirection case to prevent false positive --- .../uninitialized-local-pointer-redirect.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 test/uninitialized-variable/uninitialized-local-pointer-redirect.c diff --git a/test/uninitialized-variable/uninitialized-local-pointer-redirect.c b/test/uninitialized-variable/uninitialized-local-pointer-redirect.c new file mode 100644 index 0000000..47fc83e --- /dev/null +++ b/test/uninitialized-variable/uninitialized-local-pointer-redirect.c @@ -0,0 +1,15 @@ +static void redirect_ptr(int** out, int* target) +{ + *out = target; +} + +int read_after_pointer_redirect(void) +{ + int uninit; + int init = 42; + int* ptr = &uninit; + redirect_ptr(&ptr, &init); + return *ptr; +} + +// not contains: potential read of uninitialized local variable 'uninit' From d34af672b97d2fb743adec32fc48d0cb65ebe2dd Mon Sep 17 00:00:00 2001 From: Hugo Date: Mon, 9 Feb 2026 03:16:39 +0100 Subject: [PATCH 41/41] chore(style): format code with clang-format --- src/analysis/UninitializedVarAnalysis.cpp | 14 ++++++-------- src/report/ReportSerialization.cpp | 4 +--- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/analysis/UninitializedVarAnalysis.cpp b/src/analysis/UninitializedVarAnalysis.cpp index 0f3d911..b45e5af 100644 --- a/src/analysis/UninitializedVarAnalysis.cpp +++ b/src/analysis/UninitializedVarAnalysis.cpp @@ -156,12 +156,9 @@ namespace ctrace::stack::analysis if (begin >= end) return; - auto it = std::lower_bound( - ranges.begin(), ranges.end(), begin, - [](const ByteRange& r, std::uint64_t value) - { - return r.end < value; - }); + auto it = std::lower_bound(ranges.begin(), ranges.end(), begin, + [](const ByteRange& r, std::uint64_t value) + { return r.end < value; }); if (it == ranges.end()) { @@ -842,7 +839,7 @@ namespace ctrace::stack::analysis { addRange( getParamEffect(*currentSummary, *obj.param).readBeforeWriteRanges, - access.begin, access.end); + access.begin, access.end); } } return; @@ -955,7 +952,8 @@ namespace ctrace::stack::analysis if (resolveAccessFromPointer(MTI->getSource(), readSize, tracked, DL, srcAccess)) { - const TrackedMemoryObject& srcObj = tracked.objects[srcAccess.objectIdx]; + const TrackedMemoryObject& srcObj = + tracked.objects[srcAccess.objectIdx]; bool srcDefInit = isRangeCovered(initialized[srcAccess.objectIdx], srcAccess.begin, srcAccess.end); if (!srcDefInit) diff --git a/src/report/ReportSerialization.cpp b/src/report/ReportSerialization.cpp index fc57eb5..afee8ce 100644 --- a/src/report/ReportSerialization.cpp +++ b/src/report/ReportSerialization.cpp @@ -294,9 +294,7 @@ namespace ctrace::stack } std::sort(rules.begin(), rules.end(), [](const SarifRuleEntry& lhs, const SarifRuleEntry& rhs) - { - return lhs.id < rhs.id; - }); + { return lhs.id < rhs.id; }); std::ostringstream os; os << "{\n";