From 95ce21b271097c75b8b34dc998ce9499e3424cf0 Mon Sep 17 00:00:00 2001 From: Hugo Date: Fri, 5 Jun 2026 10:11:33 +0900 Subject: [PATCH 1/2] test(analysis): cover pointer-slot induction false positive --- ...pointer_slot_induction_no_invalid_base.cpp | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 test/offset_of-container_of/range_for_pointer_slot_induction_no_invalid_base.cpp diff --git a/test/offset_of-container_of/range_for_pointer_slot_induction_no_invalid_base.cpp b/test/offset_of-container_of/range_for_pointer_slot_induction_no_invalid_base.cpp new file mode 100644 index 0000000..14f0355 --- /dev/null +++ b/test/offset_of-container_of/range_for_pointer_slot_induction_no_invalid_base.cpp @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +// strict-diagnostic-count: false + +struct ViewLike +{ + const char* data; + unsigned long size; + + constexpr bool ends_with(ViewLike suffix) const + { + return size >= suffix.size && suffix.data != nullptr; + } +}; + +int range_for_pointer_slot_induction_should_not_warn(ViewLike filename) +{ + constexpr ViewLike cExtensions[] = {{".c", 2}, {".h", 2}}; + + for (const auto& ext : cExtensions) + { + if (filename.ends_with(ext)) + return 1; + } + + return 0; +} + +int main(void) +{ + return range_for_pointer_slot_induction_should_not_warn({"main.c", 6}); +} + +// not contains: potential UB: invalid base reconstruction via offsetof/container_of +// not contains: derived pointer points OUTSIDE the valid object range From da0820870fffb8e08082367d8234ac20b670918d Mon Sep 17 00:00:00 2001 From: Hugo Date: Fri, 5 Jun 2026 10:11:52 +0900 Subject: [PATCH 2/2] fix(analysis): bound pointer-slot induction in origin tracking --- src/analysis/InvalidBaseReconstruction.cpp | 106 ++++++++++++--------- 1 file changed, 62 insertions(+), 44 deletions(-) diff --git a/src/analysis/InvalidBaseReconstruction.cpp b/src/analysis/InvalidBaseReconstruction.cpp index d1e74a0..225c2a1 100644 --- a/src/analysis/InvalidBaseReconstruction.cpp +++ b/src/analysis/InvalidBaseReconstruction.cpp @@ -426,6 +426,57 @@ namespace ctrace::stack::analysis int64_t offset = 0; }; + static bool matchPointerSlotInductionStore(const llvm::Value* storedValue, + const llvm::AllocaInst* pointerSlot, + const llvm::DataLayout& DL) + { + using namespace llvm; + + const Value* base = nullptr; + int64_t offset = 0; + if (!getGEPConstantOffsetAndBase(storedValue, DL, offset, base)) + return false; + if (offset == 0) + return false; + + const Value* strippedBase = base ? base->stripPointerCasts() : nullptr; + if (!isLoadFromAlloca(strippedBase, pointerSlot)) + return false; + + return true; + } + + static std::string formatSourceOffsets(const std::set& offsets) + { + constexpr std::size_t kMaxDisplayedOffsets = 12; + + auto formatOffset = [](int64_t offset) + { return offset != 0 ? "+" + std::to_string(offset) : std::string("base"); }; + + if (offsets.size() == 1) + { + const int64_t offset = *offsets.begin(); + return offset != 0 ? "offset +" + std::to_string(offset) : std::string("base"); + } + + std::ostringstream out; + out << "offsets "; + std::size_t printed = 0; + for (int64_t offset : offsets) + { + if (printed >= kMaxDisplayedOffsets) + { + out << ", ... (+" << (offsets.size() - printed) << " more)"; + break; + } + if (printed != 0) + out << ", "; + out << formatOffset(offset); + ++printed; + } + return out.str(); + } + static void collectPointerOrigins(const llvm::Value* V, const llvm::DataLayout& DL, llvm::SmallVectorImpl& out, WorkBudget& budget) { @@ -450,6 +501,7 @@ namespace ctrace::stack::analysis Type* allocaTy = AI->getAllocatedType(); if (allocaTy->isPointerTy()) { + SmallVector seedStores; for (const User* Usr : AI->users()) { if (auto* SI = dyn_cast(Usr)) @@ -457,12 +509,16 @@ namespace ctrace::stack::analysis if (SI->getPointerOperand() != AI) continue; const Value* StoredVal = SI->getValueOperand(); - if (recordVisitedOffset(visited, StoredVal, currentOffset)) - { - worklist.push_back({StoredVal, currentOffset}); - } + if (matchPointerSlotInductionStore(StoredVal, AI, DL)) + continue; + seedStores.push_back(StoredVal); } } + for (const Value* StoredVal : seedStores) + { + if (recordVisitedOffset(visited, StoredVal, currentOffset)) + worklist.push_back({StoredVal, currentOffset}); + } continue; } @@ -1049,29 +1105,10 @@ namespace ctrace::stack::analysis if (!entry.anyOutOfBounds && !entry.anyNonZeroResult) continue; - std::ostringstream memberStr; - if (entry.memberOffsets.size() == 1) - { - int64_t mo = *entry.memberOffsets.begin(); - memberStr << (mo != 0 ? "offset +" + std::to_string(mo) : "base"); - } - else - { - memberStr << "offsets "; - bool first = true; - for (int64_t mo : entry.memberOffsets) - { - if (!first) - memberStr << ", "; - memberStr << (mo != 0 ? "+" + std::to_string(mo) : "base"); - first = false; - } - } - InvalidBaseReconstructionIssue issue; issue.funcName = F.getName().str(); issue.varName = entry.varName; - issue.sourceMember = memberStr.str(); + issue.sourceMember = formatSourceOffsets(entry.memberOffsets); issue.offsetUsed = kv.first.second; issue.targetType = entry.targetType; issue.isOutOfBounds = entry.anyOutOfBounds; @@ -1157,29 +1194,10 @@ namespace ctrace::stack::analysis if (!entry.anyOutOfBounds && !entry.anyNonZeroResult) continue; - std::ostringstream memberStr; - if (entry.memberOffsets.size() == 1) - { - int64_t mo = *entry.memberOffsets.begin(); - memberStr << (mo != 0 ? "offset +" + std::to_string(mo) : "base"); - } - else - { - memberStr << "offsets "; - bool first = true; - for (int64_t mo : entry.memberOffsets) - { - if (!first) - memberStr << ", "; - memberStr << (mo != 0 ? "+" + std::to_string(mo) : "base"); - first = false; - } - } - InvalidBaseReconstructionIssue issue; issue.funcName = F.getName().str(); issue.varName = entry.varName; - issue.sourceMember = memberStr.str(); + issue.sourceMember = formatSourceOffsets(entry.memberOffsets); issue.offsetUsed = gepOffset; issue.targetType = entry.targetType; issue.isOutOfBounds = entry.anyOutOfBounds;