From 94c5fe4691fcc194ffb8a5f2eac3830267b753b7 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 8 Feb 2026 01:43:45 +0100 Subject: [PATCH 01/10] chore(style): format code with cmake-format --- CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d4f9f8..cb2115f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,9 +30,9 @@ if(DEFINED DEBUG_ASAN) endif() FetchContent_Declare( - cc - GIT_REPOSITORY https://github.com/CoreTrace/coretrace-compiler.git - GIT_TAG main + cc + GIT_REPOSITORY https://github.com/CoreTrace/coretrace-compiler.git + GIT_TAG main ) FetchContent_MakeAvailable(cc) From 781cd3bd7a103b631577390a62e708e61c124211 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 8 Feb 2026 01:45:30 +0100 Subject: [PATCH 02/10] fix(analysis): handle boolean conditions in duplicate else-if detection --- src/analysis/DuplicateIfCondition.cpp | 28 ++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/analysis/DuplicateIfCondition.cpp b/src/analysis/DuplicateIfCondition.cpp index 350dd1e..d4180a7 100644 --- a/src/analysis/DuplicateIfCondition.cpp +++ b/src/analysis/DuplicateIfCondition.cpp @@ -178,11 +178,20 @@ namespace ctrace::stack::analysis bool precise = false; // true if we can reason about direct stores only }; + enum class ConditionKind + { + Invalid, + ICmp, + BoolValue + }; + struct ConditionKey { + ConditionKind kind = ConditionKind::Invalid; llvm::CmpInst::Predicate pred = llvm::CmpInst::BAD_ICMP_PREDICATE; llvm::Value* lhs = nullptr; llvm::Value* rhs = nullptr; + llvm::Value* boolValue = nullptr; bool valid = false; llvm::SmallVector memoryOperands; }; @@ -242,9 +251,20 @@ namespace ctrace::stack::analysis ConditionKey key; auto* cmp = llvm::dyn_cast(cond); if (!cmp) + { + llvm::Value* raw = stripCasts(cond); + if (raw && raw->getType()->isIntegerTy()) + { + key.valid = true; + key.kind = ConditionKind::BoolValue; + key.boolValue = canonicalizeOperand(raw, key); + dedupeMemoryOperands(key); + } return key; + } key.valid = true; + key.kind = ConditionKind::ICmp; key.pred = cmp->getPredicate(); key.lhs = canonicalizeOperand(cmp->getOperand(0), key); key.rhs = canonicalizeOperand(cmp->getOperand(1), key); @@ -263,7 +283,13 @@ namespace ctrace::stack::analysis { if (!a.valid || !b.valid) return false; - return a.pred == b.pred && a.lhs == b.lhs && a.rhs == b.rhs; + if (a.kind != b.kind) + return false; + if (a.kind == ConditionKind::ICmp) + return a.pred == b.pred && a.lhs == b.lhs && a.rhs == b.rhs; + if (a.kind == ConditionKind::BoolValue) + return a.boolValue == b.boolValue; + return false; } static bool isInterferingWrite(const llvm::Instruction& I, const MemoryOperand& mem) From 20e25a9f02033743fc365cfe38d2cc0dde5a8fb8 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 8 Feb 2026 01:45:44 +0100 Subject: [PATCH 03/10] fix(analysis): surface compiler diagnostics on successful compile --- src/analysis/InputPipeline.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/analysis/InputPipeline.cpp b/src/analysis/InputPipeline.cpp index 9ca49ac..a33e930 100644 --- a/src/analysis/InputPipeline.cpp +++ b/src/analysis/InputPipeline.cpp @@ -323,6 +323,14 @@ namespace ctrace::stack::analysis result.error = "Compilation failed:\n" + res.diagnostics + '\n'; return result; } + if (!res.diagnostics.empty() && !config.quiet) + { + llvm::errs() << res.diagnostics; + if (res.diagnostics.back() != '\n') + { + llvm::errs() << '\n'; + } + } if (res.llvmIR.empty()) { From f566c1233e7eaa8d3e12b89990dc1b7370fa58b0 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 8 Feb 2026 01:46:07 +0100 Subject: [PATCH 04/10] test(diagnostics): add boolean duplicate else-if case --- test/diagnostics/duplicate-else-if-bool.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 test/diagnostics/duplicate-else-if-bool.c diff --git a/test/diagnostics/duplicate-else-if-bool.c b/test/diagnostics/duplicate-else-if-bool.c new file mode 100644 index 0000000..80b3408 --- /dev/null +++ b/test/diagnostics/duplicate-else-if-bool.c @@ -0,0 +1,20 @@ +#include + +int main(int argc, char* argv[]) +{ + bool hasFilter = argc > 1; + + if (hasFilter) + { + return 0; + } + // at line 14, column 14 + // [!] unreachable else-if branch: condition is equivalent to a previous 'if' condition + // else branch implies previous condition is false + else if (hasFilter) + { + return 1; + } + + return 2; +} From e028550fd61cd80f6f0abba8084652bb935ec85c Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 8 Feb 2026 01:46:31 +0100 Subject: [PATCH 05/10] test(diagnostics): add nested-loop boolean duplicate case --- ...plicate-else-if-nested-loop_boolean_expr.c | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 test/diagnostics/duplicate-else-if-nested-loop_boolean_expr.c diff --git a/test/diagnostics/duplicate-else-if-nested-loop_boolean_expr.c b/test/diagnostics/duplicate-else-if-nested-loop_boolean_expr.c new file mode 100644 index 0000000..5772a38 --- /dev/null +++ b/test/diagnostics/duplicate-else-if-nested-loop_boolean_expr.c @@ -0,0 +1,34 @@ +#include +#include + +int main(int argc, char* argv[]) +{ + int num = argc; + bool flag = (argc & 1) != 0; + + for (int i = 0; i < num; ++i) + { + for (int j = 0; j < num; ++j) + { + if (!!num) + { + if (flag) + { + printf("Num is zero\n"); + } + // at line 22, column 26 + // [!] unreachable else-if branch: condition is equivalent to a previous 'if' condition + // else branch implies previous condition is false + else if (flag) + { + printf("No functions matched filters for: %d\n", num); + } + else + { + return 1; + } + } + } + } + return 0; +} From 2755e3e33fec40fd9efbc8f87151a300a471ab8a Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 8 Feb 2026 01:46:59 +0100 Subject: [PATCH 06/10] test(diagnostics): add nested-loop duplicate variant --- .../duplicate-else-if-nested-loop_2.c | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 test/diagnostics/duplicate-else-if-nested-loop_2.c diff --git a/test/diagnostics/duplicate-else-if-nested-loop_2.c b/test/diagnostics/duplicate-else-if-nested-loop_2.c new file mode 100644 index 0000000..8f8c076 --- /dev/null +++ b/test/diagnostics/duplicate-else-if-nested-loop_2.c @@ -0,0 +1,33 @@ +#include +#define IS_ZERO(x) ((x) == 0) + +int main(void) +{ + int num = 13; + + for (int i = 0; i < num; ++i) + { + for (int j = 0; j < num; ++j) + { + if (!!num) + { + if ((num)) + { + printf("Num is zero\n"); + } + // at line 21, column 26 + // [!] unreachable else-if branch: condition is equivalent to a previous 'if' condition + // else branch implies previous condition is false + else if ((num)) + { + printf("No functions matched filters for: %d\n", num); + } + else + { + return 1; + } + } + } + } + return 0; +} From cefd2c4f0e893ec94b80044cc630bf5c327d37c2 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 8 Feb 2026 01:48:04 +0100 Subject: [PATCH 07/10] feat(cli): add default compile args for analysis (-O0, --ct-optnone) --- main.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/main.cpp b/main.cpp index f952b43..068438c 100644 --- a/main.cpp +++ b/main.cpp @@ -402,6 +402,9 @@ int main(int argc, char** argv) // cfg.mode = AnalysisMode::IR; -> already set by default constructor // cfg.stackLimit = 8ull * 1024ull * 1024ull; // 8 MiB -> already set by default constructor but needed to be set with args + cfg.extraCompileArgs.emplace_back("-O0"); + cfg.extraCompileArgs.emplace_back("--ct-optnone"); + for (int i = 1; i < argc; ++i) { const char* arg = argv[i]; From d0d3a47c970fff8b2a9a2671f2b978ed05a62164 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 8 Feb 2026 14:41:25 +0100 Subject: [PATCH 08/10] chore(cmake): add -fstack-usage in cmake --- CMakeLists.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index cb2115f..30995d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,7 @@ message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}") # Options de build option(BUILD_CLI "Build stack_usage_analyzer CLI tool" ON) option(BUILD_SHARED_LIB "Build shared library variant" ON) +option(ENABLE_STACK_USAGE "Emit per-function stack usage (.su) files" OFF) # =========================== # Communs Sources @@ -84,6 +85,10 @@ add_library(stack_usage_analyzer_lib STATIC ${STACK_ANALYZER_SOURCES} ) +if(ENABLE_STACK_USAGE) + target_compile_options(stack_usage_analyzer_lib PRIVATE -fstack-usage) +endif() + if(ENABLE_DEBUG_ASAN) target_compile_options(stack_usage_analyzer_lib PUBLIC -fsanitize=address -fno-omit-frame-pointer -g) target_link_options(stack_usage_analyzer_lib PUBLIC -fsanitize=address) @@ -157,6 +162,10 @@ if(BUILD_CLI) # it is already linked into the library. ) + if(ENABLE_STACK_USAGE) + target_compile_options(stack_usage_analyzer PRIVATE -fstack-usage) + endif() + if(ENABLE_DEBUG_ASAN) target_compile_options(stack_usage_analyzer PRIVATE -fsanitize=address -fno-omit-frame-pointer -g) target_link_options(stack_usage_analyzer PRIVATE -fsanitize=address) From fa793771a2980f2343b4df107a497dd3f4da074c Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 8 Feb 2026 14:42:52 +0100 Subject: [PATCH 09/10] fix: detect duplicate else-if on equivalent pointer expressions --- src/analysis/DuplicateIfCondition.cpp | 88 ++++++++++++++++++++++++--- 1 file changed, 81 insertions(+), 7 deletions(-) diff --git a/src/analysis/DuplicateIfCondition.cpp b/src/analysis/DuplicateIfCondition.cpp index d4180a7..f3bfb23 100644 --- a/src/analysis/DuplicateIfCondition.cpp +++ b/src/analysis/DuplicateIfCondition.cpp @@ -11,10 +11,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include @@ -205,6 +207,68 @@ namespace ctrace::stack::analysis return v; } + static bool valuesEquivalent(const llvm::Value* a, const llvm::Value* b, int depth = 0) + { + if (a == b) + return true; + if (!a || !b) + return false; + if (depth > 6) + return false; + + if (auto* la = llvm::dyn_cast(a)) + { + auto* lb = llvm::dyn_cast(b); + if (!lb) + return false; + return valuesEquivalent(la->getPointerOperand()->stripPointerCasts(), + lb->getPointerOperand()->stripPointerCasts(), depth + 1); + } + + if (auto* ga = llvm::dyn_cast(a)) + { + auto* gb = llvm::dyn_cast(b); + if (!gb) + return false; + if (ga->getNumIndices() != gb->getNumIndices()) + return false; + if (!valuesEquivalent(ga->getPointerOperand()->stripPointerCasts(), + gb->getPointerOperand()->stripPointerCasts(), depth + 1)) + return false; + + auto itA = ga->idx_begin(); + auto itB = gb->idx_begin(); + for (; itA != ga->idx_end(); ++itA, ++itB) + { + auto* ca = llvm::dyn_cast(itA->get()); + auto* cb = llvm::dyn_cast(itB->get()); + if (!ca || !cb) + return false; + if (ca->getValue() != cb->getValue()) + return false; + } + + return true; + } + + if (auto* opA = llvm::dyn_cast(a)) + { + auto* opB = llvm::dyn_cast(b); + if (!opB || opA->getOpcode() != opB->getOpcode()) + return false; + switch (opA->getOpcode()) + { + case llvm::Instruction::BitCast: + case llvm::Instruction::AddrSpaceCast: + return valuesEquivalent(opA->getOperand(0), opB->getOperand(0), depth + 1); + default: + break; + } + } + + return false; + } + static bool isPrecisePointer(const llvm::Value* ptr) { using namespace llvm; @@ -286,9 +350,17 @@ namespace ctrace::stack::analysis if (a.kind != b.kind) return false; if (a.kind == ConditionKind::ICmp) - return a.pred == b.pred && a.lhs == b.lhs && a.rhs == b.rhs; + { + if (a.pred == b.pred && valuesEquivalent(a.lhs, b.lhs) && + valuesEquivalent(a.rhs, b.rhs)) + return true; + if (llvm::CmpInst::getSwappedPredicate(a.pred) == b.pred && + valuesEquivalent(a.lhs, b.rhs) && valuesEquivalent(a.rhs, b.lhs)) + return true; + return false; + } if (a.kind == ConditionKind::BoolValue) - return a.boolValue == b.boolValue; + return valuesEquivalent(a.boolValue, b.boolValue); return false; } @@ -301,25 +373,25 @@ namespace ctrace::stack::analysis if (auto* store = dyn_cast(&I)) { const Value* ptr = store->getPointerOperand()->stripPointerCasts(); - return ptr == mem.ptr; + return valuesEquivalent(ptr, mem.ptr); } if (auto* rmw = dyn_cast(&I)) { const Value* ptr = rmw->getPointerOperand()->stripPointerCasts(); - return ptr == mem.ptr; + return valuesEquivalent(ptr, mem.ptr); } if (auto* cmpxchg = dyn_cast(&I)) { const Value* ptr = cmpxchg->getPointerOperand()->stripPointerCasts(); - return ptr == mem.ptr; + return valuesEquivalent(ptr, mem.ptr); } if (auto* memIntrinsic = dyn_cast(&I)) { const Value* ptr = memIntrinsic->getRawDest()->stripPointerCasts(); - return ptr == mem.ptr; + return valuesEquivalent(ptr, mem.ptr); } if (auto* call = dyn_cast(&I)) @@ -333,7 +405,7 @@ namespace ctrace::stack::analysis const Value* argVal = arg.get(); if (!argVal || !argVal->getType()->isPointerTy()) continue; - if (argVal->stripPointerCasts() == mem.ptr) + if (valuesEquivalent(argVal->stripPointerCasts(), mem.ptr)) return true; } return false; @@ -356,6 +428,8 @@ namespace ctrace::stack::analysis { if (!DT.dominates(pathBlock, &BB)) continue; + if (&BB != atBlock && !DT.dominates(&BB, atBlock)) + continue; for (const llvm::Instruction& I : BB) { From 8bf421a88db959333a06d678816df1bb8c74e9c9 Mon Sep 17 00:00:00 2001 From: Hugo Date: Sun, 8 Feb 2026 15:00:56 +0100 Subject: [PATCH 10/10] chore(style): format code with clang-format --- src/analysis/DuplicateIfCondition.cpp | 10 +++--- .../duplicate-else-if-enum-class.cpp | 35 +++++++++++++++++++ test/diagnostics/duplicate-else-if-enum.c | 35 +++++++++++++++++++ test/diagnostics/duplicate-else-if-enum.cpp | 35 +++++++++++++++++++ test/diagnostics/duplicate-else-if-ptr.c | 34 ++++++++++++++++++ 5 files changed, 144 insertions(+), 5 deletions(-) create mode 100644 test/diagnostics/duplicate-else-if-enum-class.cpp create mode 100644 test/diagnostics/duplicate-else-if-enum.c create mode 100644 test/diagnostics/duplicate-else-if-enum.cpp create mode 100644 test/diagnostics/duplicate-else-if-ptr.c diff --git a/src/analysis/DuplicateIfCondition.cpp b/src/analysis/DuplicateIfCondition.cpp index f3bfb23..e6ba54c 100644 --- a/src/analysis/DuplicateIfCondition.cpp +++ b/src/analysis/DuplicateIfCondition.cpp @@ -258,11 +258,11 @@ namespace ctrace::stack::analysis return false; switch (opA->getOpcode()) { - case llvm::Instruction::BitCast: - case llvm::Instruction::AddrSpaceCast: - return valuesEquivalent(opA->getOperand(0), opB->getOperand(0), depth + 1); - default: - break; + case llvm::Instruction::BitCast: + case llvm::Instruction::AddrSpaceCast: + return valuesEquivalent(opA->getOperand(0), opB->getOperand(0), depth + 1); + default: + break; } } diff --git a/test/diagnostics/duplicate-else-if-enum-class.cpp b/test/diagnostics/duplicate-else-if-enum-class.cpp new file mode 100644 index 0000000..e848a55 --- /dev/null +++ b/test/diagnostics/duplicate-else-if-enum-class.cpp @@ -0,0 +1,35 @@ +enum class DiagnosticSeverity +{ + Info = 0, + Warning = 1, + Error = 2 +}; + +int main(int argc, char** argv) +{ + int iter = 13; + DiagnosticSeverity severity; + + if (argc == 2) + severity = DiagnosticSeverity::Warning; + if (argc == 3) + severity = DiagnosticSeverity::Error; + else + severity = DiagnosticSeverity::Info; + + for (int i = 0; i < iter; ++i) + { + if (severity != DiagnosticSeverity::Info) + { + return 1; + } + // at line 29, column 27 + // [!] unreachable else-if branch: condition is equivalent to a previous 'if' condition + // else branch implies previous condition is false + else if (severity != DiagnosticSeverity::Info) + { + return 1; + } + } + return 0; +} diff --git a/test/diagnostics/duplicate-else-if-enum.c b/test/diagnostics/duplicate-else-if-enum.c new file mode 100644 index 0000000..23179c1 --- /dev/null +++ b/test/diagnostics/duplicate-else-if-enum.c @@ -0,0 +1,35 @@ +typedef enum DiagnosticSeverity +{ + Info = 0, + Warning = 1, + Error = 2 +} DiagnosticSeverity; + +int main(int argc, char** argv) +{ + int iter = 13; + DiagnosticSeverity severity; + + if (argc == 2) + severity = Warning; + if (argc == 3) + severity = Error; + else + severity = Info; + + for (int i = 0; i < iter; ++i) + { + if (severity != Info) + { + return 1; + } + // at line 29, column 27 + // [!] unreachable else-if branch: condition is equivalent to a previous 'if' condition + // else branch implies previous condition is false + else if (severity != Info) + { + return 1; + } + } + return 0; +} diff --git a/test/diagnostics/duplicate-else-if-enum.cpp b/test/diagnostics/duplicate-else-if-enum.cpp new file mode 100644 index 0000000..2d7d7d8 --- /dev/null +++ b/test/diagnostics/duplicate-else-if-enum.cpp @@ -0,0 +1,35 @@ +enum DiagnosticSeverity +{ + Info = 0, + Warning = 1, + Error = 2 +}; + +int main(int argc, char** argv) +{ + int iter = 13; + DiagnosticSeverity severity; + + if (argc == 2) + severity = DiagnosticSeverity::Warning; + if (argc == 3) + severity = DiagnosticSeverity::Error; + else + severity = DiagnosticSeverity::Info; + + for (int i = 0; i < iter; ++i) + { + if (severity != DiagnosticSeverity::Info) + { + return 1; + } + // at line 29, column 27 + // [!] unreachable else-if branch: condition is equivalent to a previous 'if' condition + // else branch implies previous condition is false + else if (severity != DiagnosticSeverity::Info) + { + return 1; + } + } + return 0; +} diff --git a/test/diagnostics/duplicate-else-if-ptr.c b/test/diagnostics/duplicate-else-if-ptr.c new file mode 100644 index 0000000..c2d78a3 --- /dev/null +++ b/test/diagnostics/duplicate-else-if-ptr.c @@ -0,0 +1,34 @@ +#include +#include + +int main(int argc, char* argv[]) +{ + int num = argc; + void* ptr = # + + for (int i = 0; i < num; ++i) + { + for (int j = 0; j < num; ++j) + { + if (!!num) + { + if (ptr) + { + printf("Num is zero\n"); + } + // at line 22, column 26 + // [!] unreachable else-if branch: condition is equivalent to a previous 'if' condition + // else branch implies previous condition is false + else if (ptr) + { + printf("No functions matched filters for: %d\n", num); + } + else + { + return 1; + } + } + } + } + return 0; +}