https://github.com/steakhal updated https://github.com/llvm/llvm-project/pull/178923
From 71d56fa29374ada2403ce872b25ffd24b3689286 Mon Sep 17 00:00:00 2001 From: Balazs Benics <[email protected]> Date: Fri, 30 Jan 2026 16:50:22 +0100 Subject: [PATCH 1/2] [analyzer] Fix crash when copying uninitialized data in function named "swap" So the RegionStore has some assumptions, namely that the core.unitialized.Assign checker is enabled and detects copying Undefined (read of uninitialized data) before the Store is instructed to model this. As it turns out, there is a little hack in the UndefinedAssignmentChecker: ```c++ void UndefinedAssignmentChecker::checkBind(SVal location, SVal val, const Stmt *StoreE, bool AtDeclInit, CheckerContext &C) const { if (!val.isUndef()) return; // Do not report assignments of uninitialized values inside swap functions. // This should allow to swap partially uninitialized structs if (const FunctionDecl *EnclosingFunctionDecl = dyn_cast<FunctionDecl>(C.getStackFrame()->getDecl())) if (C.getCalleeName(EnclosingFunctionDecl) == "swap") return; // ... ``` This meant that no Sink node was inserted by the checker, thus the Store would just go and try to fulfill the bind operation. However, the Store also assumed that it's not going to see Undefined vals, so that case wasn't handled, but simply cast the value to a nonloc::CompoundVal. The checker should have created the Sink node regardless if it wants to emit a report or not. In addition to this, I'm also hardedning the Store to also be able to handle UndefinedVals a bit better. The crash bisects to #118096, but that's only surfaced this issue. Fixes #178797 --- .../Checkers/UndefinedAssignmentChecker.cpp | 7 +++++-- clang/lib/StaticAnalyzer/Core/RegionStore.cpp | 5 +---- clang/test/Analysis/uninit-vals.cpp | 18 ++++++++++++++++++ 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp index 7f8923c7c09fc..a2b00d4261de4 100644 --- a/clang/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp @@ -40,9 +40,12 @@ void UndefinedAssignmentChecker::checkBind(SVal location, SVal val, // Do not report assignments of uninitialized values inside swap functions. // This should allow to swap partially uninitialized structs if (const FunctionDecl *EnclosingFunctionDecl = - dyn_cast<FunctionDecl>(C.getStackFrame()->getDecl())) - if (C.getCalleeName(EnclosingFunctionDecl) == "swap") + dyn_cast<FunctionDecl>(C.getStackFrame()->getDecl())) { + if (C.getCalleeName(EnclosingFunctionDecl) == "swap") { + C.generateSink(C.getState(), C.getPredecessor()); return; + } + } ExplodedNode *N = C.generateErrorNode(); diff --git a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp index 4f4824a3616ce..3bb6247e20612 100644 --- a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp +++ b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp @@ -2659,12 +2659,9 @@ RegionStoreManager::bindArray(LimitedRegionBindingsConstRef B, return bindAggregate(B, R, Init); } - if (isa<nonloc::SymbolVal>(Init)) + if (isa<nonloc::SymbolVal, UnknownVal, UndefinedVal>(Init)) return bindAggregate(B, R, Init); - if (Init.isUnknown()) - return bindAggregate(B, R, UnknownVal()); - // Remaining case: explicit compound values. const nonloc::CompoundVal& CV = Init.castAs<nonloc::CompoundVal>(); nonloc::CompoundVal::iterator VI = CV.begin(), VE = CV.end(); diff --git a/clang/test/Analysis/uninit-vals.cpp b/clang/test/Analysis/uninit-vals.cpp index 6ba56f0c4e78b..7775e6a2125d3 100644 --- a/clang/test/Analysis/uninit-vals.cpp +++ b/clang/test/Analysis/uninit-vals.cpp @@ -33,3 +33,21 @@ void foo() { } } +namespace gh_178797 { +struct SpecialBuffer { + SpecialBuffer() : src(defaultBuffer), dst(defaultBuffer) {} + int* src; + int* dst; + int defaultBuffer[2]; +}; +// Not really a swap, but we need an assignment assigning UndefinedVal +// within a "swap" function to trigger this behavior. +void swap(int& lhs, int& rhs) { + lhs = rhs; // no-crash + // Not reporting copying uninitialized data because that is explicitly suppressed in the checker. +} +void entry_point() { + SpecialBuffer special; + swap(*special.dst, *++special.src); +} +} // namespace gh_178797 From e68c8a2aed12568da1e400dad389276857083008 Mon Sep 17 00:00:00 2001 From: Balazs Benics <[email protected]> Date: Fri, 30 Jan 2026 18:41:42 +0100 Subject: [PATCH 2/2] Revert UndefinedAssignmentChecker back --- .../StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp index a2b00d4261de4..7f8923c7c09fc 100644 --- a/clang/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp @@ -40,12 +40,9 @@ void UndefinedAssignmentChecker::checkBind(SVal location, SVal val, // Do not report assignments of uninitialized values inside swap functions. // This should allow to swap partially uninitialized structs if (const FunctionDecl *EnclosingFunctionDecl = - dyn_cast<FunctionDecl>(C.getStackFrame()->getDecl())) { - if (C.getCalleeName(EnclosingFunctionDecl) == "swap") { - C.generateSink(C.getState(), C.getPredecessor()); + dyn_cast<FunctionDecl>(C.getStackFrame()->getDecl())) + if (C.getCalleeName(EnclosingFunctionDecl) == "swap") return; - } - } ExplodedNode *N = C.generateErrorNode(); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
