[clang] [analyzer] Introduce the invalidation artifact symbol (PR #207155)
Balázs Benics via cfe-commits
cfe-commits at lists.llvm.org
Fri Jul 3 09:58:04 PDT 2026
https://github.com/steakhal updated https://github.com/llvm/llvm-project/pull/207155
>From 63ecb5d84f5016e7ca656834560acf7c5eed4ef1 Mon Sep 17 00:00:00 2001
From: Balazs Benics <benicsbalazs at gmail.com>
Date: Tue, 16 Jun 2026 13:22:37 +0100
Subject: [PATCH 1/6] [analyzer] Introduce SymbolInvalidationArtifact
Implementation of:
https://discourse.llvm.org/t/memory-region-invalidation-tracking-improvements/62432
When a region escapes (opaque call, loop widening, unmodeled atomic,
etc.) the analyzer assigns a fresh SymbolConjured to represent the now
unknown value we have at the location.
In some cases, we want to associate more information about the
invalidation even, like what was the previous value, or what sort of
situation caused the invalidation.
Other situations, like the return value (r-value) of an opaque function
is also represented by Conjured symbols - further complicating the
situation. These are indistinguishable from Conjured symbols caused by
invalidation event.
Carrying invalidation-specific information would allow us creating
better heuristics for suppressing FPs, such as #53338, #53970.
And this is where SymbolInvalidationArtifact comes into play.
Assisted-By: Claude Opus 4.8
---
.../StaticAnalyzer/Checkers/SValExplainer.h | 10 +
.../Core/PathSensitive/CallEvent.h | 9 +
.../Core/PathSensitive/InvalidationCause.h | 206 ++++++++++++++++++
.../Core/PathSensitive/ProgramState.h | 11 +-
.../Core/PathSensitive/SValBuilder.h | 11 +
.../StaticAnalyzer/Core/PathSensitive/Store.h | 8 +-
.../Core/PathSensitive/SymbolManager.h | 113 ++++++++++
.../Core/PathSensitive/Symbols.def | 3 +-
.../Checkers/MismatchedIteratorChecker.cpp | 8 +-
.../Checkers/StackAddrEscapeChecker.cpp | 3 +-
clang/lib/StaticAnalyzer/Core/CMakeLists.txt | 1 +
clang/lib/StaticAnalyzer/Core/CallEvent.cpp | 10 +-
.../StaticAnalyzer/Core/InvalidationCause.cpp | 47 ++++
.../lib/StaticAnalyzer/Core/ProgramState.cpp | 10 +-
clang/lib/StaticAnalyzer/Core/RegionStore.cpp | 80 +++++--
clang/lib/StaticAnalyzer/Core/SValBuilder.cpp | 21 ++
.../lib/StaticAnalyzer/Core/SymbolManager.cpp | 22 ++
clang/test/Analysis/PR57270.cpp | 4 +-
clang/test/Analysis/dtor-array.cpp | 2 +-
clang/test/Analysis/dump_egraph.cpp | 2 +-
clang/test/Analysis/explain-svals.cpp | 4 +-
.../Analysis/invalidation-artifact-dump.c | 82 +++++++
clang/test/Analysis/stream-invalidate.c | 2 +-
clang/test/Analysis/taint-generic.c | 6 +-
24 files changed, 630 insertions(+), 45 deletions(-)
create mode 100644 clang/include/clang/StaticAnalyzer/Core/PathSensitive/InvalidationCause.h
create mode 100644 clang/lib/StaticAnalyzer/Core/InvalidationCause.cpp
create mode 100644 clang/test/Analysis/invalidation-artifact-dump.c
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/SValExplainer.h b/clang/include/clang/StaticAnalyzer/Checkers/SValExplainer.h
index 6c1025ecc7f4d..892dda51e8928 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/SValExplainer.h
+++ b/clang/include/clang/StaticAnalyzer/Checkers/SValExplainer.h
@@ -126,6 +126,16 @@ class SValExplainer : public FullSValVisitor<SValExplainer, std::string> {
printCFGElementRef(S->getCFGElementRef()) + "'";
}
+ std::string
+ VisitSymbolInvalidationArtifact(const SymbolInvalidationArtifact *S) {
+ std::string CauseStr;
+ llvm::raw_string_ostream OS(CauseStr);
+ S->getCause()->dumpToStream(OS);
+ return "the result of an invalidation (" + CauseStr + ") of type '" +
+ S->getType().getAsString() + "' at CFG element '" +
+ printCFGElementRef(S->getCFGElementRef()) + "'";
+ }
+
std::string VisitSymbolDerived(const SymbolDerived *S) {
return "value derived from (" + Visit(S->getParentSymbol()) +
") for " + Visit(S->getRegion());
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h
index 3252421414181..31301dd17b6d7 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h
@@ -263,6 +263,15 @@ class CallEvent {
return Origin.dyn_cast<const Expr *>();
}
+ /// Returns the desired \c UnmodeledCall wrapping this call.
+ template <class CauseT> const CauseT *tryCreateInvalidationCause() const {
+ static_assert(std::is_base_of_v<UnmodeledCall, CauseT>,
+ "forInvalidation<T> requires T : UnmodeledCall");
+ const auto *CE = dyn_cast_or_null<CallExpr>(getOriginExpr());
+ auto &SymMgr = State->getStateManager().getSymbolManager();
+ return SymMgr.acquireCause<CauseT>(CE);
+ }
+
/// Returns the number of arguments (explicit and implicit).
///
/// Note that this may be greater than the number of parameters in the
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/InvalidationCause.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/InvalidationCause.h
new file mode 100644
index 0000000000000..42dc48faa12aa
--- /dev/null
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/InvalidationCause.h
@@ -0,0 +1,206 @@
+//===- InvalidationCause.h - Cause of a region invalidation ------*- C++ -*-==//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines InvalidationCause, a small class hierarchy describing why
+// a memory region was invalidated by ProgramState::invalidateRegions. The
+// cause is carried by SymbolInvalidationArtifact symbols so that downstream
+// machinery (bug-report suppression, diagnostics) can distinguish symbolic
+// values produced by an invalidation event from ordinary conjured symbols.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_INVALIDATIONCAUSE_H
+#define LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_INVALIDATIONCAUSE_H
+
+#include "clang/Basic/LLVM.h"
+#include "llvm/ADT/FoldingSet.h"
+#include "llvm/Support/Compiler.h"
+
+namespace clang {
+
+class CallExpr;
+class Stmt;
+
+namespace ento {
+
+class SymbolManager;
+
+/// Describes why a memory region was invalidated. Instances are uniqued by
+/// SymbolManager::acquireCause<T>(...) and are stable for the analysis
+/// lifetime; callers must not allocate them on the stack.
+class InvalidationCause : public llvm::FoldingSetNode {
+public:
+ virtual ~InvalidationCause() = default;
+
+ enum Kind {
+ // UnmodeledCall range
+ ConservativeEvalCallKind,
+ PartiallyModeledCallKind,
+ BEGIN_UNMODELED_CALL = ConservativeEvalCallKind,
+ END_UNMODELED_CALL = PartiallyModeledCallKind,
+
+ // UnmodeledStmt range
+ UnmodeledExprKind,
+ LoopWideningKind,
+ BEGIN_UNMODELED_STMT = UnmodeledExprKind,
+ END_UNMODELED_STMT = LoopWideningKind,
+ };
+
+ Kind getKind() const { return K; }
+
+ virtual void Profile(llvm::FoldingSetNodeID &ID) const = 0;
+ virtual void dumpToStream(raw_ostream &OS) const = 0;
+
+ LLVM_DUMP_METHOD void dump() const;
+
+protected:
+ explicit InvalidationCause(Kind K) : K(K) {}
+ virtual void anchor();
+
+private:
+ Kind K;
+};
+
+/// Abstract base for invalidations triggered by an unmodeled or partially
+/// modeled call.
+class UnmodeledCall : public InvalidationCause {
+public:
+ /// Returns the expression whose value will be the result of this call.
+ /// Null if and only if 'this' is a CXXDestructorCall.
+ const CallExpr *getCallExpr() const { return CE; }
+
+ static bool classof(const InvalidationCause *C) {
+ return C->getKind() >= BEGIN_UNMODELED_CALL &&
+ C->getKind() <= END_UNMODELED_CALL;
+ }
+
+protected:
+ const CallExpr *CE;
+ UnmodeledCall(Kind K, const CallExpr *CE) : InvalidationCause(K), CE(CE) {}
+ void anchor() override;
+};
+
+/// Conservative evaluation of a call: the call's body wasn't inlined and we
+/// fall back to invalidating its arguments / reachable globals.
+class ConservativeEvalCall final : public UnmodeledCall {
+public:
+ void Profile(llvm::FoldingSetNodeID &ID) const override { Profile(ID, CE); }
+
+ static void Profile(llvm::FoldingSetNodeID &ID, const CallExpr *CE) {
+ ID.AddInteger((unsigned)ConservativeEvalCallKind);
+ ID.AddPointer(CE);
+ }
+
+ void dumpToStream(raw_ostream &OS) const override;
+
+ static bool classof(const InvalidationCause *C) {
+ return C->getKind() == ConservativeEvalCallKind;
+ }
+
+protected:
+ friend class SymbolManager;
+ explicit ConservativeEvalCall(const CallExpr *CE)
+ : UnmodeledCall(ConservativeEvalCallKind, CE) {}
+ void anchor() override;
+};
+
+/// A call that the analyzer models but bails out of for some operands (e.g.
+/// CStringChecker's memcpy fallback, MallocChecker's free invalidation,
+/// SmartPtrModeling's ostream<< handling).
+class PartiallyModeledCall final : public UnmodeledCall {
+public:
+ void Profile(llvm::FoldingSetNodeID &ID) const override { Profile(ID, CE); }
+
+ static void Profile(llvm::FoldingSetNodeID &ID, const CallExpr *CE) {
+ ID.AddInteger((unsigned)PartiallyModeledCallKind);
+ ID.AddPointer(CE);
+ }
+
+ void dumpToStream(raw_ostream &OS) const override;
+
+ static bool classof(const InvalidationCause *C) {
+ return C->getKind() == PartiallyModeledCallKind;
+ }
+
+protected:
+ friend class SymbolManager;
+ explicit PartiallyModeledCall(const CallExpr *CE)
+ : UnmodeledCall(PartiallyModeledCallKind, CE) {}
+ void anchor() override;
+};
+
+/// Abstract base for invalidations triggered by an unmodeled statement
+/// (atomics, inline asm) or a widened loop.
+class UnmodeledStmt : public InvalidationCause {
+public:
+ LLVM_ATTRIBUTE_RETURNS_NONNULL
+ const Stmt *getStmt() const { return S; }
+
+ static bool classof(const InvalidationCause *C) {
+ return C->getKind() >= BEGIN_UNMODELED_STMT &&
+ C->getKind() <= END_UNMODELED_STMT;
+ }
+
+protected:
+ const Stmt *S;
+ UnmodeledStmt(Kind K, const Stmt *S) : InvalidationCause(K), S(S) {
+ assert(S);
+ }
+ void anchor() override;
+};
+
+/// An expression we don't model (e.g. AtomicExpr, GCCAsmStmt).
+class UnmodeledExpr final : public UnmodeledStmt {
+public:
+ void Profile(llvm::FoldingSetNodeID &ID) const override { Profile(ID, S); }
+
+ static void Profile(llvm::FoldingSetNodeID &ID, const Stmt *S) {
+ ID.AddInteger((unsigned)UnmodeledExprKind);
+ ID.AddPointer(S);
+ }
+
+ void dumpToStream(raw_ostream &OS) const override;
+
+ static bool classof(const InvalidationCause *C) {
+ return C->getKind() == UnmodeledExprKind;
+ }
+
+private:
+ friend class SymbolManager;
+ explicit UnmodeledExpr(const Stmt *S) : UnmodeledStmt(UnmodeledExprKind, S) {}
+ void anchor() override;
+};
+
+/// The widened loop's invalidation event.
+class LoopWidening final : public UnmodeledStmt {
+public:
+ void Profile(llvm::FoldingSetNodeID &ID) const override { Profile(ID, S); }
+
+ static void Profile(llvm::FoldingSetNodeID &ID, const Stmt *S) {
+ ID.AddInteger((unsigned)LoopWideningKind);
+ ID.AddPointer(S);
+ }
+
+ void dumpToStream(raw_ostream &OS) const override;
+
+ static bool classof(const InvalidationCause *C) {
+ return C->getKind() == LoopWideningKind;
+ }
+
+protected:
+ friend class SymbolManager;
+ explicit LoopWidening(const Stmt *S) : UnmodeledStmt(LoopWideningKind, S) {}
+ void anchor() override;
+};
+
+raw_ostream &operator<<(raw_ostream &OS, const InvalidationCause &C);
+
+} // namespace ento
+} // namespace clang
+
+#endif // LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_INVALIDATIONCAUSE_H
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h
index 3458fa9fe27a4..15cbcd10f5055 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h
@@ -38,6 +38,7 @@ namespace ento {
class AnalysisManager;
class CallEvent;
class CallEventManager;
+class InvalidationCause;
typedef std::unique_ptr<ConstraintManager>(*ConstraintManagerCreator)(
ProgramStateManager &, ExprEngine *);
@@ -327,18 +328,24 @@ class ProgramState : public llvm::FoldingSetNode {
/// the call and should be considered directly invalidated.
/// \param ITraits information about special handling for particular regions
/// or symbols.
+ /// \param Cause if non-null, identifies the reason for the invalidation;
+ /// regions invalidated under such a cause receive a
+ /// SymbolInvalidationArtifact symbol carrying the cause instead of a
+ /// plain SymbolConjured.
[[nodiscard]] ProgramStateRef invalidateRegions(
ArrayRef<const MemRegion *> Regions, ConstCFGElementRef Elem,
unsigned BlockCount, const StackFrame *SF, bool CausesPointerEscape,
InvalidatedSymbols *IS = nullptr, const CallEvent *Call = nullptr,
- RegionAndSymbolInvalidationTraits *ITraits = nullptr) const;
+ RegionAndSymbolInvalidationTraits *ITraits = nullptr,
+ const InvalidationCause *Cause = nullptr) const;
[[nodiscard]] ProgramStateRef
invalidateRegions(ArrayRef<SVal> Values, ConstCFGElementRef Elem,
unsigned BlockCount, const StackFrame *SF,
bool CausesPointerEscape, InvalidatedSymbols *IS = nullptr,
const CallEvent *Call = nullptr,
- RegionAndSymbolInvalidationTraits *ITraits = nullptr) const;
+ RegionAndSymbolInvalidationTraits *ITraits = nullptr,
+ const InvalidationCause *Cause = nullptr) const;
/// enterStackFrame - Returns the state for entry to the given stack frame,
/// preserving the current state.
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h
index a92acfea8f702..927da06b20755 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h
@@ -48,6 +48,7 @@ namespace ento {
class CallEvent;
class ConditionTruthVal;
+class InvalidationCause;
class ProgramStateManager;
class StoreRef;
class SValBuilder {
@@ -207,6 +208,16 @@ class SValBuilder {
unsigned visitCount,
const void *symbolTag = nullptr);
+ /// Manufacture a value bound to a region during an invalidation event. The
+ /// resulting symbol is a SymbolInvalidationArtifact carrying \p Cause and,
+ /// when \p PreviousSym is non-null, the symbol that was bound to the region
+ /// before the invalidation.
+ DefinedOrUnknownSVal
+ conjureInvalidationArtifactVal(const void *symbolTag, ConstCFGElementRef elem,
+ const StackFrame *SF, QualType type,
+ unsigned count, const InvalidationCause *Cause,
+ SymbolRef PreviousSym = nullptr);
+
/// Conjure a symbol representing heap allocated memory region.
DefinedSVal getConjuredHeapSymbolVal(ConstCFGElementRef elem,
const StackFrame *SF, QualType type,
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/Store.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/Store.h
index cda21138fe5e3..657c8c0772124 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/Store.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/Store.h
@@ -43,6 +43,7 @@ class StackFrame;
namespace ento {
class CallEvent;
+class InvalidationCause;
class ProgramStateManager;
class ScanReachableSymbols;
class SymbolReaper;
@@ -239,11 +240,16 @@ class StoreManager {
/// invalidated. This should include any regions explicitly invalidated
/// even if they do not currently have bindings. Pass \c NULL if this
/// information will not be used.
+ /// \param[in] Cause If non-null, the reason for the invalidation. Conjured
+ /// replacement symbols become SymbolInvalidationArtifact carrying this
+ /// cause; if null, plain SymbolConjured values are produced (legacy
+ /// behavior).
virtual StoreRef invalidateRegions(
Store store, ArrayRef<SVal> Values, ConstCFGElementRef Elem,
unsigned Count, const StackFrame *SF, const CallEvent *Call,
InvalidatedSymbols &IS, RegionAndSymbolInvalidationTraits &ITraits,
- InvalidatedRegions *TopLevelRegions, InvalidatedRegions *Invalidated) = 0;
+ InvalidatedRegions *TopLevelRegions, InvalidatedRegions *Invalidated,
+ const InvalidationCause *Cause = nullptr) = 0;
/// enterStackFrame - Let the StoreManager to do something when execution
/// engine is about to execute into a callee.
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h
index a5d49200a50cb..2313138bf9ef0 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h
@@ -19,6 +19,7 @@
#include "clang/Analysis/AnalysisDeclContext.h"
#include "clang/Basic/LLVM.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/APSIntPtr.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/InvalidationCause.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/StoreRef.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h"
@@ -132,6 +133,84 @@ class SymbolConjured : public SymbolData {
static constexpr bool classof(Kind K) { return K == ClassKind; }
};
+/// A symbol representing a value bound to a memory region as a side effect of
+/// an invalidation event (escape into an opaque function, loop widening,
+/// unmodeled atomic, etc.). Behaves like SymbolConjured but additionally
+/// carries an InvalidationCause describing why the invalidation happened, and
+/// (when known) the symbol that was bound to the region just before the
+/// invalidation. The previous symbol lets bug-report visitors reason about
+/// constraints attached to a value before it was invalidated.
+class SymbolInvalidationArtifact : public SymbolData {
+ ConstCFGElementRef Elem;
+ QualType T;
+ unsigned Count;
+ const StackFrame *SF;
+ const void *SymbolTag;
+ const InvalidationCause *Cause;
+ SymbolRef PreviousSym;
+
+ friend class SymExprAllocator;
+ SymbolInvalidationArtifact(SymbolID sym, ConstCFGElementRef elem,
+ const StackFrame *SF, QualType t, unsigned count,
+ const void *symbolTag,
+ const InvalidationCause *cause,
+ SymbolRef previousSym)
+ : SymbolData(ClassKind, sym), Elem(elem), T(t), Count(count), SF(SF),
+ SymbolTag(symbolTag), Cause(cause), PreviousSym(previousSym) {
+ assert(SF);
+ assert(cause);
+ assert(isValidTypeForSymbol(t));
+ }
+
+public:
+ ConstCFGElementRef getCFGElementRef() const { return Elem; }
+
+ unsigned getCount() const { return Count; }
+
+ /// May be null.
+ const void *getTag() const { return SymbolTag; }
+
+ LLVM_ATTRIBUTE_RETURNS_NONNULL
+ const InvalidationCause *getCause() const { return Cause; }
+
+ LLVM_ATTRIBUTE_RETURNS_NONNULL
+ const StackFrame *getStackFrame() const { return SF; }
+
+ /// The symbol that was bound to the invalidated region immediately before
+ /// the invalidation event, or null if the region had no symbolic binding
+ /// (e.g. a concrete value, or no prior binding at all).
+ SymbolRef getPreviousSymbol() const { return PreviousSym; }
+
+ QualType getType() const override;
+
+ StringRef getKindStr() const override;
+
+ void dumpToStream(raw_ostream &os) const override;
+
+ static void Profile(llvm::FoldingSetNodeID &profile, ConstCFGElementRef Elem,
+ const StackFrame *SF, QualType T, unsigned Count,
+ const void *SymbolTag, const InvalidationCause *Cause,
+ SymbolRef PreviousSym) {
+ profile.AddInteger((unsigned)ClassKind);
+ profile.Add(Elem);
+ profile.AddPointer(SF);
+ profile.Add(T);
+ profile.AddInteger(Count);
+ profile.AddPointer(SymbolTag);
+ profile.AddPointer(Cause);
+ profile.AddPointer(PreviousSym);
+ }
+
+ void Profile(llvm::FoldingSetNodeID &profile) override {
+ Profile(profile, Elem, SF, T, Count, SymbolTag, Cause, PreviousSym);
+ }
+
+ // Implement isa<T> support.
+ static constexpr Kind ClassKind = SymbolInvalidationArtifactKind;
+ static bool classof(const SymExpr *SE) { return classof(SE->getKind()); }
+ static constexpr bool classof(Kind K) { return K == ClassKind; }
+};
+
/// A symbol representing the value of a MemRegion whose parent region has
/// symbolic value.
class SymbolDerived : public SymbolData {
@@ -501,16 +580,20 @@ class SymExprAllocator {
return new (Alloc) SymT(nextID(), std::forward<ArgsT>(Args)...);
}
+ llvm::BumpPtrAllocator &getAllocator() { return Alloc; }
+
private:
SymbolID nextID() { return NextSymbolID++; }
};
class SymbolManager {
using DataSetTy = llvm::FoldingSet<SymExpr>;
+ using CauseSetTy = llvm::FoldingSet<InvalidationCause>;
using SymbolDependTy =
llvm::DenseMap<SymbolRef, std::unique_ptr<SymbolRefSmallVectorTy>>;
DataSetTy DataSet;
+ CauseSetTy CauseSet;
/// Stores the extra dependencies between symbols: the data should be kept
/// alive as long as the key is live.
@@ -533,6 +616,12 @@ class SymbolManager {
template <typename SymExprT, typename... Args>
const SymExprT *acquire(Args &&...args);
+ /// Create or retrieve a uniqued InvalidationCause of type \p CauseT.
+ /// Causes are interned so that they can participate in symbol uniquing
+ /// via stable pointer identity.
+ template <typename CauseT, typename... Args>
+ const CauseT *acquireCause(Args &&...args);
+
const SymbolConjured *conjureSymbol(ConstCFGElementRef Elem,
const StackFrame *SF, QualType T,
unsigned VisitCount,
@@ -541,6 +630,15 @@ class SymbolManager {
return acquire<SymbolConjured>(Elem, SF, T, VisitCount, SymbolTag);
}
+ const SymbolInvalidationArtifact *conjureInvalidationArtifact(
+ ConstCFGElementRef Elem, const StackFrame *SF, QualType T,
+ unsigned VisitCount, const void *SymbolTag,
+ const InvalidationCause *Cause, SymbolRef PreviousSym) {
+ assert(Cause);
+ return acquire<SymbolInvalidationArtifact>(Elem, SF, T, VisitCount,
+ SymbolTag, Cause, PreviousSym);
+ }
+
QualType getType(const SymExpr *SE) const {
return SE->getType();
}
@@ -684,6 +782,21 @@ const T *SymbolManager::acquire(Args &&...args) {
return cast<T>(SD);
}
+template <typename CauseT, typename... Args>
+const CauseT *SymbolManager::acquireCause(Args &&...args) {
+ static_assert(std::is_base_of_v<InvalidationCause, CauseT>,
+ "acquireCause<T> requires T : InvalidationCause");
+ llvm::FoldingSetNodeID profile;
+ CauseT::Profile(profile, args...);
+ void *InsertPos;
+ InvalidationCause *C = CauseSet.FindNodeOrInsertPos(profile, InsertPos);
+ if (!C) {
+ C = new (Alloc.getAllocator()) CauseT(std::forward<Args>(args)...);
+ CauseSet.InsertNode(C, InsertPos);
+ }
+ return cast<CauseT>(C);
+}
+
} // namespace ento
} // namespace clang
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/Symbols.def b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/Symbols.def
index b93f8e2501559..2ee43532d93c3 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/Symbols.def
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/Symbols.def
@@ -49,7 +49,8 @@ ABSTRACT_SYMBOL(SymbolData, SymExpr)
SYMBOL(SymbolExtent, SymbolData)
SYMBOL(SymbolMetadata, SymbolData)
SYMBOL(SymbolRegionValue, SymbolData)
-SYMBOL_RANGE(SYMBOLS, SymbolConjuredKind, SymbolRegionValueKind)
+ SYMBOL(SymbolInvalidationArtifact, SymbolData)
+SYMBOL_RANGE(SYMBOLS, SymbolConjuredKind, SymbolInvalidationArtifactKind)
#undef SYMBOL
#undef ABSTRACT_SYMBOL
diff --git a/clang/lib/StaticAnalyzer/Checkers/MismatchedIteratorChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MismatchedIteratorChecker.cpp
index d7aa42ff34862..34f8ea7a3ff10 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MismatchedIteratorChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MismatchedIteratorChecker.cpp
@@ -204,7 +204,7 @@ void MismatchedIteratorChecker::verifyMatch(CheckerContext &C, SVal Iter,
Cont = Cont->getMostDerivedObjectRegion();
if (const auto *ContSym = Cont->getSymbolicBase()) {
- if (isa<SymbolConjured>(ContSym->getSymbol()))
+ if (isa<SymbolConjured, SymbolInvalidationArtifact>(ContSym->getSymbol()))
return;
}
@@ -220,7 +220,7 @@ void MismatchedIteratorChecker::verifyMatch(CheckerContext &C, SVal Iter,
// the same or a different container but we get different conjured symbols
// for each call. This may cause false positives so omit them from the check.
if (const auto *ContSym = IterCont->getSymbolicBase()) {
- if (isa<SymbolConjured>(ContSym->getSymbol()))
+ if (isa<SymbolConjured, SymbolInvalidationArtifact>(ContSym->getSymbol()))
return;
}
@@ -249,7 +249,7 @@ void MismatchedIteratorChecker::verifyMatch(CheckerContext &C, SVal Iter1,
// the same or a different container but we get different conjured symbols
// for each call. This may cause false positives so omit them from the check.
if (const auto *ContSym = IterCont1->getSymbolicBase()) {
- if (isa<SymbolConjured>(ContSym->getSymbol()))
+ if (isa<SymbolConjured, SymbolInvalidationArtifact>(ContSym->getSymbol()))
return;
}
@@ -259,7 +259,7 @@ void MismatchedIteratorChecker::verifyMatch(CheckerContext &C, SVal Iter1,
const auto *IterCont2 = Pos2->getContainer();
if (const auto *ContSym = IterCont2->getSymbolicBase()) {
- if (isa<SymbolConjured>(ContSym->getSymbol()))
+ if (isa<SymbolConjured, SymbolInvalidationArtifact>(ContSym->getSymbol()))
return;
}
diff --git a/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp
index db307d3ac549f..2a3e824609786 100644
--- a/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp
@@ -447,7 +447,8 @@ static bool isInvalidatedSymbolRegion(const MemRegion *Region) {
SymbolRef Symbol = SymReg->getSymbol();
const auto *DerS = dyn_cast<SymbolDerived>(Symbol);
- return DerS && isa_and_nonnull<SymbolConjured>(DerS->getParentSymbol());
+ return DerS && isa_and_nonnull<SymbolConjured, SymbolInvalidationArtifact>(
+ DerS->getParentSymbol());
}
void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS,
diff --git a/clang/lib/StaticAnalyzer/Core/CMakeLists.txt b/clang/lib/StaticAnalyzer/Core/CMakeLists.txt
index a210f22b055b2..b8116a42939af 100644
--- a/clang/lib/StaticAnalyzer/Core/CMakeLists.txt
+++ b/clang/lib/StaticAnalyzer/Core/CMakeLists.txt
@@ -34,6 +34,7 @@ add_clang_library(clangStaticAnalyzerCore
ExprEngineObjC.cpp
FunctionSummary.cpp
HTMLDiagnostics.cpp
+ InvalidationCause.cpp
LoopUnrolling.cpp
LoopWidening.cpp
MemRegion.cpp
diff --git a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp
index 2338c06d5f992..b2c0825b34dc5 100644
--- a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp
+++ b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp
@@ -40,6 +40,7 @@
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeInfo.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/InvalidationCause.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState_Fwd.h"
@@ -295,10 +296,11 @@ ProgramStateRef CallEvent::invalidateRegions(unsigned BlockCount,
// Invalidate designated regions using the batch invalidation API.
// NOTE: Even if RegionsToInvalidate is empty, we may still invalidate
// global variables.
- return State->invalidateRegions(ValuesToInvalidate, getCFGElementRef(),
- BlockCount, getStackFrame(),
- /*CausedByPointerEscape*/ true,
- /*Symbols=*/nullptr, this, &ETraits);
+ return State->invalidateRegions(
+ ValuesToInvalidate, getCFGElementRef(), BlockCount, getStackFrame(),
+ /*CausedByPointerEscape*/ true,
+ /*Symbols=*/nullptr, this, &ETraits,
+ tryCreateInvalidationCause<ConservativeEvalCall>());
}
ProgramPoint CallEvent::getProgramPoint(bool IsPreVisit,
diff --git a/clang/lib/StaticAnalyzer/Core/InvalidationCause.cpp b/clang/lib/StaticAnalyzer/Core/InvalidationCause.cpp
new file mode 100644
index 0000000000000..5c30cdf934316
--- /dev/null
+++ b/clang/lib/StaticAnalyzer/Core/InvalidationCause.cpp
@@ -0,0 +1,47 @@
+//===- InvalidationCause.cpp - Cause of a region invalidation -------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/StaticAnalyzer/Core/PathSensitive/InvalidationCause.h"
+#include "clang/AST/Stmt.h"
+#include "llvm/Support/raw_ostream.h"
+
+using namespace clang;
+using namespace ento;
+
+void InvalidationCause::anchor() {}
+void UnmodeledCall::anchor() {}
+void ConservativeEvalCall::anchor() {}
+void PartiallyModeledCall::anchor() {}
+void UnmodeledStmt::anchor() {}
+void UnmodeledExpr::anchor() {}
+void LoopWidening::anchor() {}
+
+LLVM_DUMP_METHOD void InvalidationCause::dump() const {
+ dumpToStream(llvm::errs());
+}
+
+raw_ostream &ento::operator<<(raw_ostream &OS, const InvalidationCause &C) {
+ C.dumpToStream(OS);
+ return OS;
+}
+
+void ConservativeEvalCall::dumpToStream(raw_ostream &OS) const {
+ OS << "conservative-call";
+}
+
+void PartiallyModeledCall::dumpToStream(raw_ostream &OS) const {
+ OS << "partial-call";
+}
+
+void UnmodeledExpr::dumpToStream(raw_ostream &OS) const {
+ OS << "unmodeled-expr " << S->getStmtClassName();
+}
+
+void LoopWidening::dumpToStream(raw_ostream &OS) const {
+ OS << "loop-widening";
+}
\ No newline at end of file
diff --git a/clang/lib/StaticAnalyzer/Core/ProgramState.cpp b/clang/lib/StaticAnalyzer/Core/ProgramState.cpp
index da0d00daa3685..2ae2c501c7fb8 100644
--- a/clang/lib/StaticAnalyzer/Core/ProgramState.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ProgramState.cpp
@@ -146,19 +146,21 @@ typedef ArrayRef<SVal> ValueList;
ProgramStateRef ProgramState::invalidateRegions(
RegionList Regions, ConstCFGElementRef Elem, unsigned Count,
const StackFrame *SF, bool CausedByPointerEscape, InvalidatedSymbols *IS,
- const CallEvent *Call, RegionAndSymbolInvalidationTraits *ITraits) const {
+ const CallEvent *Call, RegionAndSymbolInvalidationTraits *ITraits,
+ const InvalidationCause *Cause) const {
SmallVector<SVal, 8> Values;
for (const MemRegion *Reg : Regions)
Values.push_back(loc::MemRegionVal(Reg));
return invalidateRegions(Values, Elem, Count, SF, CausedByPointerEscape, IS,
- Call, ITraits);
+ Call, ITraits, Cause);
}
ProgramStateRef ProgramState::invalidateRegions(
ValueList Values, ConstCFGElementRef Elem, unsigned Count,
const StackFrame *SF, bool CausedByPointerEscape, InvalidatedSymbols *IS,
- const CallEvent *Call, RegionAndSymbolInvalidationTraits *ITraits) const {
+ const CallEvent *Call, RegionAndSymbolInvalidationTraits *ITraits,
+ const InvalidationCause *Cause) const {
ProgramStateManager &Mgr = getStateManager();
ExprEngine &Eng = Mgr.getOwningEngine();
@@ -175,7 +177,7 @@ ProgramStateRef ProgramState::invalidateRegions(
StoreManager::InvalidatedRegions Invalidated;
const StoreRef &NewStore = Mgr.StoreMgr->invalidateRegions(
getStore(), Values, Elem, Count, SF, Call, *IS, *ITraits,
- &TopLevelInvalidated, &Invalidated);
+ &TopLevelInvalidated, &Invalidated, Cause);
ProgramStateRef NewState = makeWithStore(NewStore);
diff --git a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
index 0f7e03ce50858..ae231686c8269 100644
--- a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
+++ b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp
@@ -568,7 +568,8 @@ class RegionStoreManager : public StoreManager {
ConstCFGElementRef Elem,
unsigned Count, const StackFrame *SF,
RegionBindingsRef B,
- InvalidatedRegions *Invalidated);
+ InvalidatedRegions *Invalidated,
+ const InvalidationCause *Cause);
StoreRef invalidateRegions(Store store, ArrayRef<SVal> Values,
ConstCFGElementRef Elem, unsigned Count,
@@ -576,7 +577,8 @@ class RegionStoreManager : public StoreManager {
InvalidatedSymbols &IS,
RegionAndSymbolInvalidationTraits &ITraits,
InvalidatedRegions *Invalidated,
- InvalidatedRegions *InvalidatedTopLevel) override;
+ InvalidatedRegions *InvalidatedTopLevel,
+ const InvalidationCause *Cause) override;
bool scanReachableSymbols(Store S, const MemRegion *R,
ScanReachableSymbols &Callbacks) override;
@@ -1153,6 +1155,8 @@ class InvalidateRegionsWorker : public ClusterAnalysis<InvalidateRegionsWorker>
RegionAndSymbolInvalidationTraits &ITraits;
StoreManager::InvalidatedRegions *Regions;
GlobalsFilterKind GlobalsFilter;
+ const InvalidationCause *Cause;
+
public:
InvalidateRegionsWorker(RegionStoreManager &rm, ProgramStateManager &stateMgr,
RegionBindingsRef b, ConstCFGElementRef elem,
@@ -1160,10 +1164,10 @@ class InvalidateRegionsWorker : public ClusterAnalysis<InvalidateRegionsWorker>
InvalidatedSymbols &is,
RegionAndSymbolInvalidationTraits &ITraitsIn,
StoreManager::InvalidatedRegions *r,
- GlobalsFilterKind GFK)
+ GlobalsFilterKind GFK, const InvalidationCause *Cause)
: ClusterAnalysis<InvalidateRegionsWorker>(rm, stateMgr, b), Elem(elem),
Count(count), SF(SF), IS(is), ITraits(ITraitsIn), Regions(r),
- GlobalsFilter(GFK) {}
+ GlobalsFilter(GFK), Cause(Cause) {}
void VisitCluster(const MemRegion *baseR, const ClusterBindings *C);
void VisitBinding(SVal V);
@@ -1220,6 +1224,25 @@ void InvalidateRegionsWorker::VisitCluster(const MemRegion *baseR,
ITraits.hasTrait(baseR,
RegionAndSymbolInvalidationTraits::TK_PreserveContents);
+ // Capture the symbol that was bound to baseR (if any) before we remove the
+ // cluster, so the SymbolInvalidationArtifact we produce below can record
+ // what the region used to hold. We look at both Default and Direct bindings;
+ // each conjure site below picks the one matching the kind it produces.
+ // Note that we don't have bindings in the store for the initial
+ // SymbolRegionValue, so we need to create that connection ourselves.
+ SymbolRef PrevDefaultSym = nullptr;
+ SymbolRef PrevDirectSym = nullptr;
+ if (Cause) {
+ if (std::optional<SVal> V = B.getDefaultBinding(baseR)) {
+ PrevDefaultSym = V->getAsSymbol();
+ } else if (std::optional<SVal> V = B.getDirectBinding(baseR)) {
+ PrevDirectSym = V->getAsSymbol();
+ } else if (const auto *TypedBaseR = dyn_cast<TypedValueRegion>(baseR)) {
+ PrevDirectSym =
+ svalBuilder.getRegionValueSymbolVal(TypedBaseR).getAsSymbol();
+ }
+ }
+
if (C) {
for (SVal Val : llvm::make_second_range(*C))
VisitBinding(Val);
@@ -1296,7 +1319,9 @@ void InvalidateRegionsWorker::VisitCluster(const MemRegion *baseR,
// Invalidate the region by setting its default value to
// conjured symbol. The type of the symbol is irrelevant.
DefinedOrUnknownSVal V =
- svalBuilder.conjureSymbolVal(baseR, Elem, SF, Ctx.IntTy, Count);
+ Cause ? svalBuilder.conjureInvalidationArtifactVal(
+ baseR, Elem, SF, Ctx.IntTy, Count, Cause, PrevDefaultSym)
+ : svalBuilder.conjureSymbolVal(baseR, Elem, SF, Ctx.IntTy, Count);
B = B.addBinding(baseR, BindingKey::Default, V);
return;
}
@@ -1318,7 +1343,9 @@ void InvalidateRegionsWorker::VisitCluster(const MemRegion *baseR,
// Invalidate the region by setting its default value to
// conjured symbol. The type of the symbol is irrelevant.
DefinedOrUnknownSVal V =
- svalBuilder.conjureSymbolVal(baseR, Elem, SF, Ctx.IntTy, Count);
+ Cause ? svalBuilder.conjureInvalidationArtifactVal(
+ baseR, Elem, SF, Ctx.IntTy, Count, Cause, PrevDefaultSym)
+ : svalBuilder.conjureSymbolVal(baseR, Elem, SF, Ctx.IntTy, Count);
B = B.addBinding(baseR, BindingKey::Default, V);
return;
}
@@ -1385,14 +1412,20 @@ void InvalidateRegionsWorker::VisitCluster(const MemRegion *baseR,
}
conjure_default:
// Set the default value of the array to conjured symbol.
- DefinedOrUnknownSVal V = svalBuilder.conjureSymbolVal(
- baseR, Elem, SF, AT->getElementType(), Count);
+ DefinedOrUnknownSVal V =
+ Cause ? svalBuilder.conjureInvalidationArtifactVal(
+ baseR, Elem, SF, AT->getElementType(), Count, Cause,
+ PrevDefaultSym)
+ : svalBuilder.conjureSymbolVal(baseR, Elem, SF,
+ AT->getElementType(), Count);
B = B.addBinding(baseR, BindingKey::Default, V);
return;
}
DefinedOrUnknownSVal V =
- svalBuilder.conjureSymbolVal(baseR, Elem, SF, T, Count);
+ Cause ? svalBuilder.conjureInvalidationArtifactVal(
+ baseR, Elem, SF, T, Count, Cause, PrevDirectSym)
+ : svalBuilder.conjureSymbolVal(baseR, Elem, SF, T, Count);
assert(SymbolManager::canSymbolicate(T) || V.isUnknown());
B = B.addBinding(baseR, BindingKey::Direct, V);
}
@@ -1422,14 +1455,24 @@ bool InvalidateRegionsWorker::includeEntireMemorySpace(const MemRegion *Base) {
RegionBindingsRef RegionStoreManager::invalidateGlobalRegion(
MemRegion::Kind K, ConstCFGElementRef Elem, unsigned Count,
- const StackFrame *SF, RegionBindingsRef B,
- InvalidatedRegions *Invalidated) {
+ const StackFrame *SF, RegionBindingsRef B, InvalidatedRegions *Invalidated,
+ const InvalidationCause *Cause) {
// Bind the globals memory space to a new symbol that we will use to derive
// the bindings for all globals.
const GlobalsSpaceRegion *GS = MRMgr.getGlobalsRegion(K);
- SVal V = svalBuilder.conjureSymbolVal(
- /* symbolTag = */ (const void *)GS, Elem, SF,
- /* type does not matter */ Ctx.IntTy, Count);
+
+ SymbolRef PrevSym = nullptr;
+ if (Cause)
+ if (std::optional<SVal> V = B.getDefaultBinding(GS))
+ PrevSym = V->getAsSymbol();
+
+ SVal V = Cause
+ ? svalBuilder.conjureInvalidationArtifactVal(
+ /*symbolTag=*/(const void *)GS, Elem, SF,
+ /*type does not matter*/ Ctx.IntTy, Count, Cause, PrevSym)
+ : svalBuilder.conjureSymbolVal(
+ /*symbolTag=*/(const void *)GS, Elem, SF,
+ /*type does not matter*/ Ctx.IntTy, Count);
B = B.removeBinding(GS)
.addBinding(BindingKey::Make(GS, BindingKey::Default), V);
@@ -1467,7 +1510,8 @@ StoreRef RegionStoreManager::invalidateRegions(
Store store, ArrayRef<SVal> Values, ConstCFGElementRef Elem, unsigned Count,
const StackFrame *SF, const CallEvent *Call, InvalidatedSymbols &IS,
RegionAndSymbolInvalidationTraits &ITraits,
- InvalidatedRegions *TopLevelRegions, InvalidatedRegions *Invalidated) {
+ InvalidatedRegions *TopLevelRegions, InvalidatedRegions *Invalidated,
+ const InvalidationCause *Cause) {
GlobalsFilterKind GlobalsFilter;
if (Call) {
if (Call->isInSystemHeader())
@@ -1480,7 +1524,7 @@ StoreRef RegionStoreManager::invalidateRegions(
RegionBindingsRef B = getRegionBindings(store);
InvalidateRegionsWorker W(*this, StateMgr, B, Elem, Count, SF, IS, ITraits,
- Invalidated, GlobalsFilter);
+ Invalidated, GlobalsFilter, Cause);
// Scan the bindings and generate the clusters.
W.GenerateClusters();
@@ -1500,11 +1544,11 @@ StoreRef RegionStoreManager::invalidateRegions(
switch (GlobalsFilter) {
case GFK_All:
B = invalidateGlobalRegion(MemRegion::GlobalInternalSpaceRegionKind, Elem,
- Count, SF, B, Invalidated);
+ Count, SF, B, Invalidated, Cause);
[[fallthrough]];
case GFK_SystemOnly:
B = invalidateGlobalRegion(MemRegion::GlobalSystemSpaceRegionKind, Elem,
- Count, SF, B, Invalidated);
+ Count, SF, B, Invalidated, Cause);
[[fallthrough]];
case GFK_None:
break;
diff --git a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp
index e816cb2aec4d3..96bd9bd2191af 100644
--- a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp
+++ b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp
@@ -213,6 +213,27 @@ DefinedOrUnknownSVal SValBuilder::conjureSymbolVal(const CallEvent &call,
call.getStackFrame(), type, visitCount);
}
+DefinedOrUnknownSVal SValBuilder::conjureInvalidationArtifactVal(
+ const void *symbolTag, ConstCFGElementRef elem, const StackFrame *SF,
+ QualType type, unsigned count, const InvalidationCause *Cause,
+ SymbolRef PreviousSym) {
+ assert(Cause);
+
+ if (type->isNullPtrType())
+ return makeZeroVal(type);
+
+ if (!SymbolManager::canSymbolicate(type))
+ return UnknownVal();
+
+ SymbolRef sym = SymMgr.conjureInvalidationArtifact(
+ elem, SF, type, count, symbolTag, Cause, PreviousSym);
+
+ if (Loc::isLocType(type))
+ return loc::MemRegionVal(MemMgr.getSymbolicRegion(sym));
+
+ return nonloc::SymbolVal(sym);
+}
+
DefinedSVal SValBuilder::getConjuredHeapSymbolVal(ConstCFGElementRef elem,
const StackFrame *SF,
QualType type,
diff --git a/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp b/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp
index 1b217be452c6b..c7e10e1744e77 100644
--- a/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp
+++ b/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp
@@ -17,6 +17,7 @@
#include "clang/Analysis/Analyses/LiveVariables.h"
#include "clang/Analysis/AnalysisDeclContext.h"
#include "clang/Basic/LLVM.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/InvalidationCause.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/Store.h"
@@ -36,6 +37,7 @@ StringRef SymbolDerived::getKindStr() const { return "derived_$"; }
StringRef SymbolExtent::getKindStr() const { return "extent_$"; }
StringRef SymbolMetadata::getKindStr() const { return "meta_$"; }
StringRef SymbolRegionValue::getKindStr() const { return "reg_$"; }
+StringRef SymbolInvalidationArtifact::getKindStr() const { return "inv_$"; }
LLVM_DUMP_METHOD void SymExpr::dump() const { dumpToStream(llvm::errs()); }
@@ -130,6 +132,22 @@ void SymbolConjured::dumpToStream(raw_ostream &os) const {
os << ", #" << Count << '}';
}
+void SymbolInvalidationArtifact::dumpToStream(raw_ostream &os) const {
+ os << getKindStr() << getSymbolID() << '{' << T << ", LC" << SF->getID()
+ << ", " << *Cause;
+
+ if (const auto *Call = dyn_cast_or_null<UnmodeledCall>(Cause)) {
+ if (const auto *CE = Call->getCallExpr())
+ os << ", S" << CE->getID(SF->getDecl()->getASTContext());
+ } else if (const auto *US = dyn_cast_or_null<UnmodeledStmt>(Cause)) {
+ os << ", S" << US->getStmt()->getID(SF->getDecl()->getASTContext());
+ }
+
+ if (PreviousSym)
+ os << ", prev=" << PreviousSym;
+ os << ", #" << Count << '}';
+}
+
void SymbolDerived::dumpToStream(raw_ostream &os) const {
os << getKindStr() << getSymbolID() << '{' << getParentSymbol() << ','
<< getRegion() << '}';
@@ -181,6 +199,7 @@ void SymExpr::symbol_iterator::expand() {
case SymExpr::SymbolDerivedKind:
case SymExpr::SymbolExtentKind:
case SymExpr::SymbolMetadataKind:
+ case SymExpr::SymbolInvalidationArtifactKind:
return;
case SymExpr::SymbolCastKind:
itr.push_back(cast<SymbolCast>(SE)->getOperand());
@@ -208,6 +227,8 @@ QualType SymbolConjured::getType() const {
return T;
}
+QualType SymbolInvalidationArtifact::getType() const { return T; }
+
QualType SymbolDerived::getType() const {
return R->getValueType();
}
@@ -348,6 +369,7 @@ bool SymbolReaper::isLive(SymbolRef sym) {
KnownLive = isReadableRegion(cast<SymbolRegionValue>(sym)->getRegion());
break;
case SymExpr::SymbolConjuredKind:
+ case SymExpr::SymbolInvalidationArtifactKind:
KnownLive = false;
break;
case SymExpr::SymbolDerivedKind:
diff --git a/clang/test/Analysis/PR57270.cpp b/clang/test/Analysis/PR57270.cpp
index 7d7a658ef441b..b2263270bb344 100644
--- a/clang/test/Analysis/PR57270.cpp
+++ b/clang/test/Analysis/PR57270.cpp
@@ -24,7 +24,7 @@ void foo()
S *arr = new S[x];
delete[] arr;
- clang_analyzer_dump(S::a); // expected-warning-re{{{{derived_\$[0-9]+{conj_\$[0-9]+{int, LC[0-9]+, S[0-9]+, #[0-9]+},a}}}}}
+ clang_analyzer_dump(S::a); // expected-warning-re{{{{derived_\$[0-9]+{inv_\$[0-9]+{int, LC[0-9]+, conservative-call, #[0-9]+},a}}}}}
- clang_analyzer_explain(S::a); // expected-warning-re{{{{value derived from \(symbol of type 'int' conjured at CFG element '->~S\(\) \(Implicit destructor\)'\) for global variable 'S::a'}}}}
+ clang_analyzer_explain(S::a); // expected-warning-re{{{{value derived from \(the result of an invalidation \(conservative-call\) of type 'int' at CFG element '->~S\(\) \(Implicit destructor\)'\) for global variable 'S::a'}}}}
}
diff --git a/clang/test/Analysis/dtor-array.cpp b/clang/test/Analysis/dtor-array.cpp
index 84a34af922516..cbd1526e46d6a 100644
--- a/clang/test/Analysis/dtor-array.cpp
+++ b/clang/test/Analysis/dtor-array.cpp
@@ -337,7 +337,7 @@ void nonConstantRegionExtent(){
memset(&x, 1, sizeof(x));
InlineDtor *arr = new InlineDtor[x];
- clang_analyzer_dumpElementCount(arr); // expected-warning {{conj_$0}}
+ clang_analyzer_dumpElementCount(arr); // expected-warning {{inv_$0}}
delete [] arr;
//FIXME: This should be TRUE but memset also sets this
diff --git a/clang/test/Analysis/dump_egraph.cpp b/clang/test/Analysis/dump_egraph.cpp
index 2cea5f705f116..31693fc67e3cb 100644
--- a/clang/test/Analysis/dump_egraph.cpp
+++ b/clang/test/Analysis/dump_egraph.cpp
@@ -21,6 +21,6 @@ void foo() {
// CHECK: \"location_context\": \"#0 Call\", \"calling\": \"T::T\", \"location\": \{ \"line\": 15, \"column\": 5, \"file\": \"{{.*}}dump_egraph.cpp\" \}, \"items\": [\l \{ \"init_id\": {{[0-9]+}}, \"kind\": \"construct into member variable\", \"argument_index\": null, \"pretty\": \"s\", \"value\": \"&t.s\"
-// CHECK: \"cluster\": \"t\", \"pointer\": \"{{0x[0-9a-f]+}}\", \"items\": [\l \{ \"kind\": \"Default\", \"offset\": 0, \"value\": \"conj_$3\{int, LC5, no stmt, #1\}\"
+// CHECK: \"cluster\": \"t\", \"pointer\": \"{{0x[0-9a-f]+}}\", \"items\": [\l \{ \"kind\": \"Default\", \"offset\": 0, \"value\": \"inv_$4\{int, LC5, conservative-call, #1\}\"
// CHECK: \"dynamic_types\": [\l \{ \"region\": \"HeapSymRegion\{conj_$1\{S *, LC1, S{{[0-9]+}}, #1\}\}\", \"dyn_type\": \"S\", \"sub_classable\": false \}\l
diff --git a/clang/test/Analysis/explain-svals.cpp b/clang/test/Analysis/explain-svals.cpp
index 9474aa7c7dbb1..77e858dea75cc 100644
--- a/clang/test/Analysis/explain-svals.cpp
+++ b/clang/test/Analysis/explain-svals.cpp
@@ -48,8 +48,8 @@ void test_2(char *ptr, int ext) {
clang_analyzer_explain((void *) "asdf"); // expected-warning-re{{{{^pointer to element of type 'char' with index 0 of string literal "asdf"$}}}}
clang_analyzer_explain(strlen(ptr)); // expected-warning-re{{{{^metadata of type '__size_t' tied to pointee of argument 'ptr'$}}}}
clang_analyzer_explain(conjure()); // expected-warning-re{{{{^symbol of type 'int' conjured at CFG element 'conjure\(\)'$}}}}
- clang_analyzer_explain(glob); // expected-warning-re{{{{^value derived from \(symbol of type 'int' conjured at CFG element 'conjure\(\)'\) for global variable 'glob'$}}}}
- clang_analyzer_explain(glob_ptr); // expected-warning-re{{{{^value derived from \(symbol of type 'int' conjured at CFG element 'conjure\(\)'\) for global variable 'glob_ptr'$}}}}
+ clang_analyzer_explain(glob); // expected-warning-re{{{{^value derived from \(the result of an invalidation \(conservative-call\) of type 'int' at CFG element 'conjure\(\)'\) for global variable 'glob'$}}}}
+ clang_analyzer_explain(glob_ptr); // expected-warning-re{{{{^value derived from \(the result of an invalidation \(conservative-call\) of type 'int' at CFG element 'conjure\(\)'\) for global variable 'glob_ptr'$}}}}
clang_analyzer_explain(clang_analyzer_getExtent(ptr)); // expected-warning-re{{{{^extent of pointee of argument 'ptr'$}}}}
int *x = new int[ext];
clang_analyzer_explain(x); // expected-warning-re{{{{^pointer to element of type 'int' with index 0 of heap segment that starts at symbol of type 'int \*' conjured at CFG element 'CFGNewAllocator\(int \*\)'$}}}}
diff --git a/clang/test/Analysis/invalidation-artifact-dump.c b/clang/test/Analysis/invalidation-artifact-dump.c
new file mode 100644
index 0000000000000..8deb0479e0db7
--- /dev/null
+++ b/clang/test/Analysis/invalidation-artifact-dump.c
@@ -0,0 +1,82 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -verify %s
+
+// Verify that values produced by ProgramState::invalidateRegions during a
+// conservative-eval call are bound as SymbolInvalidationArtifact symbols
+// (printed as "inv_$N{... cause, #...}"). Symbols produced by other paths
+// (e.g. uninvalidated initial loads) keep their existing kinds.
+
+void clang_analyzer_dump(int);
+void clang_analyzer_dump_ptr(int *);
+void clang_analyzer_eval(int);
+
+int gGlobal;
+void opaque(int *);
+
+void test_conservative_call_invalidates_arg(void) {
+ int x = 0;
+ opaque(&x);
+ // Scalar conjure site (RegionStore.cpp BindingKey::Direct path).
+ clang_analyzer_dump(x); // expected-warning-re{{{{inv_\$[0-9]+{int, LC[0-9]+, conservative-call, S[0-9]+, #1}}}}}
+
+ // Constraint manager dispatch still works on the new kind.
+ if (x == 42) {
+ clang_analyzer_eval(x == 42); // expected-warning {{TRUE}}
+ }
+}
+
+void test_conservative_call_invalidates_globals(void) {
+ gGlobal = 0;
+ opaque((int *)0);
+ // Global memory-space conjure site (RegionStore::invalidateGlobalRegion).
+ clang_analyzer_dump(gGlobal); // expected-warning-re{{{{derived_\$[0-9]+{inv_\$[0-9]+{int, LC[0-9]+, conservative-call, S[0-9]+, #1},gGlobal}}}}}
+}
+
+struct Foo { int x; int y; };
+void opaqueStruct(struct Foo *);
+
+void test_conservative_call_invalidates_record(void) {
+ struct Foo s = {0, 0};
+ opaqueStruct(&s);
+ // Record-type conjure site (RegionStore.cpp record default-binding path):
+ // members are read as derived_$ over an inv_$ default binding.
+ clang_analyzer_dump(s.x); // expected-warning-re{{{{derived_\$[0-9]+{inv_\$[0-9]+{int, LC[0-9]+, conservative-call, S[0-9]+, #1},s.x}}}}}
+}
+
+void opaqueArr(int *);
+
+void test_conservative_call_invalidates_array(void) {
+ int arr[3] = {0, 0, 0};
+ opaqueArr(arr);
+ // Array-element conjure site (RegionStore.cpp array default-binding path).
+ clang_analyzer_dump(arr[0]); // expected-warning-re{{{{derived_\$[0-9]+{inv_\$[0-9]+{int, LC[0-9]+, conservative-call, S[0-9]+, #1},Element{arr,0 S64b,int}}}}}}
+}
+
+int *opaqueHeap(void);
+void opaquePtr(int *);
+
+void test_conservative_call_invalidates_symbolic_region(void) {
+ int *p = opaqueHeap();
+ opaquePtr(p);
+ // SymbolicRegion conjure site (RegionStore.cpp alloca/symbolic path):
+ // dereferencing the heap pointer after the opaque call yields a value
+ // derived from an inv_$ default binding on the symbolic region.
+ clang_analyzer_dump(*p); // expected-warning-re{{{{inv_\$[0-9]+{int, LC[0-9]+, conservative-call, S[0-9]+, #1}}}}}
+}
+
+int returnsInt(void);
+
+void test_eval_call_returns_conjured(void) {
+ // A pure return value from an opaque call is a SymbolConjured (no
+ // invalidation event) — ensure we did not regress that path.
+ int r = returnsInt();
+ clang_analyzer_dump(r); // expected-warning-re{{{{conj_\$[0-9]+{int, LC[0-9]+, S[0-9]+, #1}}}}}
+}
+
+void test_previous_symbol_is_recorded(void) {
+ // Bind x to a conjured symbol first, then invalidate it. The resulting
+ // SymbolInvalidationArtifact must carry that prior symbol via
+ // getPreviousSymbol(); the dump surfaces it as "prev=conj_$".
+ int x = returnsInt();
+ opaque(&x);
+ clang_analyzer_dump(x); // expected-warning-re{{{{inv_\$[0-9]+{int, LC[0-9]+, conservative-call, S[0-9]+, prev=conj_\$[0-9]+{int, LC[0-9]+, S[0-9]+, #1}, #1}}}}}
+}
diff --git a/clang/test/Analysis/stream-invalidate.c b/clang/test/Analysis/stream-invalidate.c
index 749c53d164fb5..910f651b3c787 100644
--- a/clang/test/Analysis/stream-invalidate.c
+++ b/clang/test/Analysis/stream-invalidate.c
@@ -183,7 +183,7 @@ void test_vfscanf() {
int r = test_vfscanf_inner("%d", &i);
if (r != EOF) {
// i gets invalidated by the call to test_vfscanf_inner, not by vfscanf.
- clang_analyzer_dump(i); // expected-warning {{conj_$}}
+ clang_analyzer_dump(i); // expected-warning {{inv_$}}
clang_analyzer_dump(j); // expected-warning {{43 S32b}}
}
}
diff --git a/clang/test/Analysis/taint-generic.c b/clang/test/Analysis/taint-generic.c
index 1ad491a10e603..db99811b21c18 100644
--- a/clang/test/Analysis/taint-generic.c
+++ b/clang/test/Analysis/taint-generic.c
@@ -499,11 +499,11 @@ void complex_taint_queries(const int *p) {
tmp += p[0] + p[0];
tmp += p[1] + p[1];
tmp += p[2] + p[2];
- clang_analyzer_dump_int(tmp); // expected-warning{{((((conj_}} symbol complexity: 8
+ clang_analyzer_dump_int(tmp); // expected-warning{{((((inv_}} symbol complexity: 8
clang_analyzer_isTainted_int(tmp); // expected-warning{{YES}}
tmp += p[3] + p[3];
- clang_analyzer_dump_int(tmp); // expected-warning{{(((((conj_}} symbol complexity: 10
+ clang_analyzer_dump_int(tmp); // expected-warning{{(((((inv_}} symbol complexity: 10
clang_analyzer_isTainted_int(tmp); // expected-warning{{NO}} 10 is already too complex to be traversed
tmp += p[4] + p[4];
@@ -521,7 +521,7 @@ void complex_taint_queries(const int *p) {
// The SymExpr still holds the full history of the computation, yet, "isTainted" doesn't traverse the tree as the complexity is over the threshold.
clang_analyzer_dump_int(tmp);
- // expected-warning at -1{{(((((((((((((((((conj_}} symbol complexity: 34
+ // expected-warning at -1{{(((((((((((((((((inv_}} symbol complexity: 34
clang_analyzer_isTainted_int(tmp); // expected-warning{{NO}} FIXME: Ideally, this should still result in "tainted".
// By making it even one step more complex, then it would hit the "max-symbol-complexity"
>From b4f4a64dbfd79b2517b082e6acef92f224e4cd39 Mon Sep 17 00:00:00 2001
From: Balazs Benics <benicsbalazs at gmail.com>
Date: Tue, 16 Jun 2026 13:46:53 +0100
Subject: [PATCH 2/6] [analyzer] Mark loop-widening invalidations with a cause
Migrate getWidenedLoopState() to construct a LoopWidening cause from
the widened loop's terminator statement and pass it through
ProgramState::invalidateRegions, so that the symbols produced when a
loop is widened are SymbolInvalidationArtifact carrying a
"loop-widening" cause instead of plain SymbolConjured.
Add a lit test that runs with widen-loops enabled and dumps the
post-widening value of a stack local to confirm the inv_$ kind.
Assisted-By: claude
---
.../Core/PathSensitive/LoopWidening.h | 9 +++++++--
clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 2 +-
.../lib/StaticAnalyzer/Core/LoopWidening.cpp | 12 ++++++++----
.../invalidation-artifact-loop-widening.c | 19 +++++++++++++++++++
4 files changed, 35 insertions(+), 7 deletions(-)
create mode 100644 clang/test/Analysis/invalidation-artifact-loop-widening.c
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/LoopWidening.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/LoopWidening.h
index b9e7732a4fee3..22b67eb7e44a7 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/LoopWidening.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/LoopWidening.h
@@ -15,17 +15,22 @@
#ifndef LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_LOOPWIDENING_H
#define LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_LOOPWIDENING_H
+#include "clang/Analysis/AnalysisDeclContext.h"
#include "clang/Analysis/CFG.h"
-#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState_Fwd.h"
namespace clang {
+class StackFrame;
namespace ento {
/// Get the states that result from widening the loop.
///
/// Widen the loop by invalidating anything that might be modified
/// by the loop body in any iteration.
-ProgramStateRef getWidenedLoopState(ProgramStateRef PrevState,
+/// statement of the widened loop (if available); when non-null it is
+/// attached to the resulting invalidation symbols as a LoopWidening cause.
+ProgramStateRef getWidenedLoopState(const Stmt *LoopStmt,
+ ProgramStateRef PrevState,
const StackFrame *SF, unsigned BlockCount,
ConstCFGElementRef Elem);
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
index cfb294736ee02..656cc624dd667 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -2433,7 +2433,7 @@ void ExprEngine::processCFGBlockEntrance(const BlockEdge &L,
// block, but the terminator cannot be referred as a CFG element.
// Here we just pass the the first CFG element in the block.
ProgramStateRef WidenedState = getWidenedLoopState(
- Pred->getState(), SF, BlockCount, *getCurrBlock()->ref_begin());
+ Term, Pred->getState(), SF, BlockCount, *getCurrBlock()->ref_begin());
Builder.generateNode(BE, WidenedState, Pred);
return;
}
diff --git a/clang/lib/StaticAnalyzer/Core/LoopWidening.cpp b/clang/lib/StaticAnalyzer/Core/LoopWidening.cpp
index 038044308e1fe..03594ea6c5a7f 100644
--- a/clang/lib/StaticAnalyzer/Core/LoopWidening.cpp
+++ b/clang/lib/StaticAnalyzer/Core/LoopWidening.cpp
@@ -16,6 +16,7 @@
#include "clang/StaticAnalyzer/Core/PathSensitive/LoopWidening.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/InvalidationCause.h"
using namespace clang;
using namespace ento;
@@ -26,7 +27,8 @@ const auto MatchRef = "matchref";
namespace clang {
namespace ento {
-ProgramStateRef getWidenedLoopState(ProgramStateRef PrevState,
+ProgramStateRef getWidenedLoopState(const Stmt *LoopStmt,
+ ProgramStateRef PrevState,
const StackFrame *SF, unsigned BlockCount,
ConstCFGElementRef Elem) {
// Invalidate values in the current state.
@@ -36,6 +38,7 @@ ProgramStateRef getWidenedLoopState(ProgramStateRef PrevState,
// being so inprecise. When the invalidation is improved, the handling
// of nested loops will also need to be improved.
ASTContext &ASTCtx = SF->getAnalysisDeclContext()->getASTContext();
+ SymbolManager &SymMgr = PrevState->getStateManager().getSymbolManager();
MemRegionManager &MRMgr = PrevState->getStateManager().getRegionManager();
const MemRegion *Regions[] = {MRMgr.getStackLocalsRegion(SF),
MRMgr.getStackArgumentsRegion(SF),
@@ -59,7 +62,6 @@ ProgramStateRef getWidenedLoopState(ProgramStateRef PrevState,
RegionAndSymbolInvalidationTraits::TK_PreserveContents);
}
-
// 'this' pointer is not an lvalue, we should not invalidate it. If the loop
// is located in a method, constructor or destructor, the value of 'this'
// pointer should remain unchanged. Ignore static methods, since they do not
@@ -72,8 +74,10 @@ ProgramStateRef getWidenedLoopState(ProgramStateRef PrevState,
RegionAndSymbolInvalidationTraits::TK_PreserveContents);
}
- return PrevState->invalidateRegions(Regions, Elem, BlockCount, SF, true,
- nullptr, nullptr, &ITraits);
+ return PrevState->invalidateRegions(
+ Regions, Elem, BlockCount, SF, /*CausesPointerEscape=*/true,
+ /*InvalidatedSymbols=*/nullptr, /*Call=*/nullptr, &ITraits,
+ SymMgr.acquireCause<LoopWidening>(LoopStmt));
}
} // end namespace ento
diff --git a/clang/test/Analysis/invalidation-artifact-loop-widening.c b/clang/test/Analysis/invalidation-artifact-loop-widening.c
new file mode 100644
index 0000000000000..8474fd24caa12
--- /dev/null
+++ b/clang/test/Analysis/invalidation-artifact-loop-widening.c
@@ -0,0 +1,19 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection \
+// RUN: -analyzer-config widen-loops=true -analyzer-max-loop 2 \
+// RUN: -verify %s
+
+void clang_analyzer_dump(int);
+
+// When a loop is widened, the analyzer invalidates stack locals, stack
+// arguments, and globals. After this patch the values produced by that
+// invalidation are SymbolInvalidationArtifact carrying a "loop-widening"
+// cause, instead of plain SymbolConjured.
+
+void test_widening_marks_stack_local(void) {
+ int x = 0;
+ for (int i = 0; i < 1000; ++i) {
+ x = i;
+ }
+ // After widening, x is bound to a SymbolInvalidationArtifact whose cause is "loop-widening".
+ clang_analyzer_dump(x); // expected-warning-re{{{{inv_\$[0-9]+{int, LC[0-9]+, loop-widening, S[0-9]+, #[0-9]+}}}}}
+}
>From cc43c7798edb7b33eecda5d38433b2432a26aa2a Mon Sep 17 00:00:00 2001
From: Balazs Benics <benicsbalazs at gmail.com>
Date: Tue, 16 Jun 2026 13:49:18 +0100
Subject: [PATCH 3/6] [analyzer] Tag atomics and inline asm invalidations as
UnmodeledExpr
Migrate the three call sites in ExprEngine that conservatively
invalidate operands of unmodeled language constructs:
- VisitAtomicExpr (e.g. __c11_atomic_load on regions we don't
otherwise model)
- VisitGCCAsmStmt outputs and inputs
- createTemporaryRegionIfNeeded's MemberPointerAdjustment fallback
Each now constructs an UnmodeledExpr cause from the originating Stmt
and threads it through ProgramState::invalidateRegions, so the
resulting bindings are SymbolInvalidationArtifact carrying an
"unmodeled-expr" cause instead of plain SymbolConjured.
Add a lit test covering the atomic-load and GCCAsm output paths.
Assisted-By: claude
---
clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 30 +++++++++++++------
.../invalidation-artifact-unmodeled-expr.c | 18 +++++++++++
2 files changed, 39 insertions(+), 9 deletions(-)
create mode 100644 clang/test/Analysis/invalidation-artifact-unmodeled-expr.c
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
index 656cc624dd667..dbcfaab5d2b0e 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -50,6 +50,7 @@
#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/EntryPointStats.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/InvalidationCause.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/LoopUnrolling.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/LoopWidening.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h"
@@ -421,9 +422,12 @@ ProgramStateRef ExprEngine::createTemporaryRegionIfNeeded(
break;
case SubobjectAdjustment::MemberPointerAdjustment:
// FIXME: Unimplemented.
- State = State->invalidateRegions(Reg, getCFGElementRef(),
- getNumVisitedCurrent(), SF, true,
- nullptr, nullptr, nullptr);
+ State = State->invalidateRegions(
+ Reg, getCFGElementRef(), getNumVisitedCurrent(), SF,
+ /*CausesPointerEscape=*/true, /*InvalidatedSymbols=*/nullptr,
+ /*Call=*/nullptr, /*ITraits=*/nullptr,
+ getStateManager().getSymbolManager().acquireCause<UnmodeledExpr>(
+ InitWithAdjustments));
return State;
}
}
@@ -3441,10 +3445,11 @@ void ExprEngine::VisitAtomicExpr(const AtomicExpr *AE, ExplodedNode *Pred,
ValuesToInvalidate.push_back(SubExprVal);
}
- State = State->invalidateRegions(ValuesToInvalidate, getCFGElementRef(),
- getNumVisitedCurrent(), SF,
- /*CausedByPointerEscape*/ true,
- /*Symbols=*/nullptr);
+ State = State->invalidateRegions(
+ ValuesToInvalidate, getCFGElementRef(), getNumVisitedCurrent(), SF,
+ /*CausedByPointerEscape*/ true,
+ /*Symbols=*/nullptr, /*Call=*/nullptr, /*ITraits=*/nullptr,
+ getStateManager().getSymbolManager().acquireCause<UnmodeledExpr>(AE));
AfterInvalidateSet.insert(
Engine.makeNodeWithBinding(I, AE, UnknownVal(), State));
@@ -3773,6 +3778,9 @@ void ExprEngine::VisitGCCAsmStmt(const GCCAsmStmt *A, ExplodedNode *Pred,
ProgramStateRef state = Pred->getState();
+ const InvalidationCause *AsmCause =
+ getStateManager().getSymbolManager().acquireCause<UnmodeledExpr>(A);
+
for (const Expr *O : A->outputs()) {
SVal X = state->getSVal(O, Pred->getStackFrame());
assert(!isa<NonLoc>(X)); // Should be an Lval, or unknown, undef.
@@ -3781,7 +3789,9 @@ void ExprEngine::VisitGCCAsmStmt(const GCCAsmStmt *A, ExplodedNode *Pred,
state = state->invalidateRegions(*LV, getCFGElementRef(),
getNumVisitedCurrent(),
Pred->getStackFrame(),
- /*CausedByPointerEscape=*/true);
+ /*CausedByPointerEscape=*/true,
+ /*Symbols=*/nullptr, /*Call=*/nullptr,
+ /*ITraits=*/nullptr, AsmCause);
}
// Do not reason about locations passed inside inline assembly.
@@ -3792,7 +3802,9 @@ void ExprEngine::VisitGCCAsmStmt(const GCCAsmStmt *A, ExplodedNode *Pred,
state = state->invalidateRegions(*LV, getCFGElementRef(),
getNumVisitedCurrent(),
Pred->getStackFrame(),
- /*CausedByPointerEscape=*/true);
+ /*CausedByPointerEscape=*/true,
+ /*Symbols=*/nullptr, /*Call=*/nullptr,
+ /*ITraits=*/nullptr, AsmCause);
}
Dst.insert(Engine.makePostStmtNode(A, state, Pred));
diff --git a/clang/test/Analysis/invalidation-artifact-unmodeled-expr.c b/clang/test/Analysis/invalidation-artifact-unmodeled-expr.c
new file mode 100644
index 0000000000000..679b4680743c1
--- /dev/null
+++ b/clang/test/Analysis/invalidation-artifact-unmodeled-expr.c
@@ -0,0 +1,18 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -verify %s
+
+void clang_analyzer_dump(int);
+
+void test_atomic_load_invalidates_target(void) {
+ int x = 42;
+ int loaded;
+ // The atomic load is conservatively modeled as invalidating its argument.
+ __c11_atomic_load((_Atomic int *)&x, 0);
+ clang_analyzer_dump(x); // expected-warning-re{{{{inv_\$[0-9]+{int, LC[0-9]+, unmodeled-expr AtomicExpr, S[0-9]+, #[0-9]+}}}}}
+ (void)loaded;
+}
+
+void test_inline_asm_invalidates_outputs(void) {
+ int x = 0;
+ asm("nop" : "=r"(x)); // GCC inline asm invalidates its output operand.
+ clang_analyzer_dump(x); // expected-warning-re{{{{inv_\$[0-9]+{int, LC[0-9]+, unmodeled-expr GCCAsmStmt, S[0-9]+, #[0-9]+}}}}}
+}
>From a972675964f0b066176ca28590bc19bf3a514c66 Mon Sep 17 00:00:00 2001
From: Balazs Benics <benicsbalazs at gmail.com>
Date: Tue, 16 Jun 2026 14:00:05 +0100
Subject: [PATCH 4/6] [analyzer] Tag partially modeled call invalidations
Migrate the remaining call sites that conservatively invalidate
regions as part of partial modeling. Each now constructs a
PartiallyModeledCall cause from the originating CallExpr (or
nullptr where unavailable) and threads it through
ProgramState::invalidateRegions, so the resulting bindings are
SymbolInvalidationArtifact carrying a "partial-call" cause:
- CStringChecker::invalidateBufferAux (memcpy/strncpy fallback)
- MallocChecker::handleFree (post-free contents)
- StreamChecker escapeByStartIndexAndCount and escapeArgs
(fread/fwrite/fgets/etc. buffer fallback)
- SmartPtrModeling::handleOstreamOperator
- ErrnoModeling::setErrnoStdMustBeChecked
- MoveChecker::evalCall (moved-from container contents)
- ExprEngine::bindReturnValue's record-type invalidation under
conservatively-evaluated constructors
Add lit tests that exercise the StreamChecker fread path and the
MoveChecker std::move-into-container path, checking the post-call
buffer/destination is bound to inv_$ with a "partial-call" cause.
Update existing golden tests whose dumps embedded conj_$ for sites
now producing inv_$.
Assisted-By: claude
---
.../Core/PathSensitive/CallEvent.h | 1 +
.../Checkers/CStringChecker.cpp | 84 +++++++++----------
.../StaticAnalyzer/Checkers/ErrnoModeling.cpp | 11 ++-
.../StaticAnalyzer/Checkers/ErrnoModeling.h | 3 +-
.../StaticAnalyzer/Checkers/MallocChecker.cpp | 10 ++-
.../StaticAnalyzer/Checkers/MoveChecker.cpp | 9 +-
.../Checkers/SmartPtrModeling.cpp | 9 +-
.../Checkers/StdLibraryFunctionsChecker.cpp | 3 +-
.../StaticAnalyzer/Checkers/StreamChecker.cpp | 13 +--
.../Core/ExprEngineCallAndReturn.cpp | 8 +-
clang/test/Analysis/ctor-trivial-copy.cpp | 12 +--
clang/test/Analysis/explain-svals.cpp | 2 +-
clang/test/Analysis/fread.c | 56 ++++++-------
clang/test/Analysis/getline-unixapi.c | 8 +-
.../Analysis/invalidation-artifact-move.cpp | 25 ++++++
.../invalidation-artifact-partial-call.c | 27 ++++++
clang/test/Analysis/store-dump-orders.cpp | 4 +-
clang/test/Analysis/stream-invalidate.c | 22 ++---
18 files changed, 191 insertions(+), 116 deletions(-)
create mode 100644 clang/test/Analysis/invalidation-artifact-move.cpp
create mode 100644 clang/test/Analysis/invalidation-artifact-partial-call.c
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h
index 31301dd17b6d7..706153676a4ef 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h
@@ -29,6 +29,7 @@
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/InvalidationCause.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState_Fwd.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h"
diff --git a/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp
index 32daa7045b12f..b27f4c46a0cf0 100644
--- a/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp
@@ -23,6 +23,7 @@
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/InvalidationCause.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h"
@@ -265,29 +266,29 @@ class CStringChecker
/// Invalidate the destination buffer determined by characters copied.
static ProgramStateRef
invalidateDestinationBufferBySize(CheckerContext &C, ProgramStateRef S,
- const Expr *BufE, ConstCFGElementRef Elem,
+ const Expr *BufE, const CallEvent &Call,
SVal BufV, SVal SizeV, QualType SizeTy);
/// Operation never overflows, do not invalidate the super region.
static ProgramStateRef invalidateDestinationBufferNeverOverflows(
- CheckerContext &C, ProgramStateRef S, ConstCFGElementRef Elem, SVal BufV);
+ CheckerContext &C, ProgramStateRef S, const CallEvent &Call, SVal BufV);
/// We do not know whether the operation can overflow (e.g. size is unknown),
/// invalidate the super region and escape related pointers.
static ProgramStateRef invalidateDestinationBufferAlwaysEscapeSuperRegion(
- CheckerContext &C, ProgramStateRef S, ConstCFGElementRef Elem, SVal BufV);
+ CheckerContext &C, ProgramStateRef S, const CallEvent &Call, SVal BufV);
/// Invalidate the source buffer for escaping pointers.
static ProgramStateRef invalidateSourceBuffer(CheckerContext &C,
ProgramStateRef S,
- ConstCFGElementRef Elem,
+ const CallEvent &Call,
SVal BufV);
/// @param InvalidationTraitOperations Determine how to invlidate the
/// MemRegion by setting the invalidation traits. Return true to cause pointer
/// escape, or false otherwise.
static ProgramStateRef invalidateBufferAux(
- CheckerContext &C, ProgramStateRef State, ConstCFGElementRef Elem, SVal V,
+ CheckerContext &C, ProgramStateRef State, const CallEvent &Call, SVal V,
llvm::function_ref<bool(RegionAndSymbolInvalidationTraits &,
const MemRegion *)>
InvalidationTraitOperations);
@@ -295,7 +296,7 @@ class CStringChecker
static bool SummarizeRegion(raw_ostream &os, ASTContext &Ctx,
const MemRegion *MR);
- static bool memsetAux(const Expr *DstBuffer, ConstCFGElementRef Elem,
+ static bool memsetAux(const Expr *DstBuffer, const CallEvent &Call,
SVal CharE, const Expr *Size, CheckerContext &C,
ProgramStateRef &State);
@@ -1213,7 +1214,7 @@ bool CStringChecker::isFirstBufInBound(CheckerContext &C, ProgramStateRef State,
ProgramStateRef CStringChecker::invalidateDestinationBufferBySize(
CheckerContext &C, ProgramStateRef S, const Expr *BufE,
- ConstCFGElementRef Elem, SVal BufV, SVal SizeV, QualType SizeTy) {
+ const CallEvent &Call, SVal BufV, SVal SizeV, QualType SizeTy) {
auto InvalidationTraitOperations =
[&C, S, BufTy = BufE->getType(), BufV, SizeV,
SizeTy](RegionAndSymbolInvalidationTraits &ITraits, const MemRegion *R) {
@@ -1228,22 +1229,22 @@ ProgramStateRef CStringChecker::invalidateDestinationBufferBySize(
return false;
};
- return invalidateBufferAux(C, S, Elem, BufV, InvalidationTraitOperations);
+ return invalidateBufferAux(C, S, Call, BufV, InvalidationTraitOperations);
}
ProgramStateRef
CStringChecker::invalidateDestinationBufferAlwaysEscapeSuperRegion(
- CheckerContext &C, ProgramStateRef S, ConstCFGElementRef Elem, SVal BufV) {
+ CheckerContext &C, ProgramStateRef S, const CallEvent &Call, SVal BufV) {
auto InvalidationTraitOperations = [](RegionAndSymbolInvalidationTraits &,
const MemRegion *R) {
return isa<FieldRegion>(R);
};
- return invalidateBufferAux(C, S, Elem, BufV, InvalidationTraitOperations);
+ return invalidateBufferAux(C, S, Call, BufV, InvalidationTraitOperations);
}
ProgramStateRef CStringChecker::invalidateDestinationBufferNeverOverflows(
- CheckerContext &C, ProgramStateRef S, ConstCFGElementRef Elem, SVal BufV) {
+ CheckerContext &C, ProgramStateRef S, const CallEvent &Call, SVal BufV) {
auto InvalidationTraitOperations =
[](RegionAndSymbolInvalidationTraits &ITraits, const MemRegion *R) {
if (MemRegion::FieldRegionKind == R->getKind())
@@ -1253,12 +1254,12 @@ ProgramStateRef CStringChecker::invalidateDestinationBufferNeverOverflows(
return false;
};
- return invalidateBufferAux(C, S, Elem, BufV, InvalidationTraitOperations);
+ return invalidateBufferAux(C, S, Call, BufV, InvalidationTraitOperations);
}
ProgramStateRef CStringChecker::invalidateSourceBuffer(CheckerContext &C,
ProgramStateRef S,
- ConstCFGElementRef Elem,
+ const CallEvent &Call,
SVal BufV) {
auto InvalidationTraitOperations =
[](RegionAndSymbolInvalidationTraits &ITraits, const MemRegion *R) {
@@ -1270,11 +1271,11 @@ ProgramStateRef CStringChecker::invalidateSourceBuffer(CheckerContext &C,
return true;
};
- return invalidateBufferAux(C, S, Elem, BufV, InvalidationTraitOperations);
+ return invalidateBufferAux(C, S, Call, BufV, InvalidationTraitOperations);
}
ProgramStateRef CStringChecker::invalidateBufferAux(
- CheckerContext &C, ProgramStateRef State, ConstCFGElementRef Elem, SVal V,
+ CheckerContext &C, ProgramStateRef State, const CallEvent &Call, SVal V,
llvm::function_ref<bool(RegionAndSymbolInvalidationTraits &,
const MemRegion *)>
InvalidationTraitOperations) {
@@ -1300,9 +1301,10 @@ ProgramStateRef CStringChecker::invalidateBufferAux(
RegionAndSymbolInvalidationTraits ITraits;
bool CausesPointerEscape = InvalidationTraitOperations(ITraits, R);
- return State->invalidateRegions(R, Elem, C.blockCount(), SF,
- CausesPointerEscape, nullptr, nullptr,
- &ITraits);
+ return State->invalidateRegions(
+ R, Call.getCFGElementRef(), C.blockCount(), SF, CausesPointerEscape,
+ /*InvalidatedSymbols=*/nullptr, /*Call=*/nullptr, &ITraits,
+ Call.tryCreateInvalidationCause<PartiallyModeledCall>());
}
// If we have a non-region value by chance, just remove the binding.
@@ -1350,7 +1352,7 @@ bool CStringChecker::SummarizeRegion(raw_ostream &os, ASTContext &Ctx,
}
}
-bool CStringChecker::memsetAux(const Expr *DstBuffer, ConstCFGElementRef Elem,
+bool CStringChecker::memsetAux(const Expr *DstBuffer, const CallEvent &Call,
SVal CharVal, const Expr *Size,
CheckerContext &C, ProgramStateRef &State) {
SVal MemVal = C.getSVal(DstBuffer);
@@ -1406,7 +1408,7 @@ bool CStringChecker::memsetAux(const Expr *DstBuffer, ConstCFGElementRef Elem,
// If the destination buffer's extent is not equal to the value of
// third argument, just invalidate buffer.
State = invalidateDestinationBufferBySize(
- C, State, DstBuffer, Elem, MemVal, SizeVal, Size->getType());
+ C, State, DstBuffer, Call, MemVal, SizeVal, Size->getType());
}
if (StateNullChar && !StateNonNullChar) {
@@ -1431,7 +1433,7 @@ bool CStringChecker::memsetAux(const Expr *DstBuffer, ConstCFGElementRef Elem,
} else {
// If the offset is not zero and char value is not concrete, we can do
// nothing but invalidate the buffer.
- State = invalidateDestinationBufferBySize(C, State, DstBuffer, Elem, MemVal,
+ State = invalidateDestinationBufferBySize(C, State, DstBuffer, Call, MemVal,
SizeVal, Size->getType());
}
return true;
@@ -1531,13 +1533,13 @@ void CStringChecker::evalCopyCommon(CheckerContext &C, const CallEvent &Call,
// This would probably remove any existing bindings past the end of the
// copied region, but that's still an improvement over blank invalidation.
state = invalidateDestinationBufferBySize(
- C, state, Dest.Expression, Call.getCFGElementRef(),
- C.getSVal(Dest.Expression), sizeVal, Size.Expression->getType());
+ C, state, Dest.Expression, Call, C.getSVal(Dest.Expression), sizeVal,
+ Size.Expression->getType());
// Invalidate the source (const-invalidation without const-pointer-escaping
// the address of the top-level region).
- state = invalidateSourceBuffer(C, state, Call.getCFGElementRef(),
- C.getSVal(Source.Expression));
+ state =
+ invalidateSourceBuffer(C, state, Call, C.getSVal(Source.Expression));
C.addTransition(state);
}
@@ -2271,15 +2273,15 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallEvent &Call,
// string, but that's still an improvement over blank invalidation.
if (CouldAccessOutOfBound)
state = invalidateDestinationBufferBySize(
- C, state, Dst.Expression, Call.getCFGElementRef(), *dstRegVal,
- amountCopied, C.getASTContext().getSizeType());
+ C, state, Dst.Expression, Call, *dstRegVal, amountCopied,
+ C.getASTContext().getSizeType());
else
- state = invalidateDestinationBufferNeverOverflows(
- C, state, Call.getCFGElementRef(), *dstRegVal);
+ state =
+ invalidateDestinationBufferNeverOverflows(C, state, Call, *dstRegVal);
// Invalidate the source (const-invalidation without const-pointer-escaping
// the address of the top-level region).
- state = invalidateSourceBuffer(C, state, Call.getCFGElementRef(), srcVal);
+ state = invalidateSourceBuffer(C, state, Call, srcVal);
// Set the C string length of the destination, if we know it.
if (IsBounded && (appendK == ConcatFnKind::none)) {
@@ -2379,8 +2381,8 @@ void CStringChecker::evalStrxfrm(CheckerContext &C,
if (!ComparisonVal) {
// Fallback: invalidate the buffer.
StateSizeNonZero = invalidateDestinationBufferBySize(
- C, StateSizeNonZero, Dest.Expression, Call.getCFGElementRef(), DestVal,
- SizeVal, Size.Expression->getType());
+ C, StateSizeNonZero, Dest.Expression, Call, DestVal, SizeVal,
+ Size.Expression->getType());
return BindReturnAndTransition(StateSizeNonZero);
}
@@ -2389,8 +2391,8 @@ void CStringChecker::evalStrxfrm(CheckerContext &C,
if (StateSuccess) {
// The transformation invalidated the buffer.
StateSuccess = invalidateDestinationBufferBySize(
- C, StateSuccess, Dest.Expression, Call.getCFGElementRef(), DestVal,
- SizeVal, Size.Expression->getType());
+ C, StateSuccess, Dest.Expression, Call, DestVal, SizeVal,
+ Size.Expression->getType());
BindReturnAndTransition(StateSuccess);
// Fallthrough: We also want to add a transition to the failure state below.
}
@@ -2598,8 +2600,7 @@ void CStringChecker::evalStrsep(CheckerContext &C,
// Invalidate the search string, representing the change of one delimiter
// character to NUL.
// As the replacement never overflows, do not invalidate its super region.
- State = invalidateDestinationBufferNeverOverflows(
- C, State, Call.getCFGElementRef(), Result);
+ State = invalidateDestinationBufferNeverOverflows(C, State, Call, Result);
// Overwrite the search string pointer. The new value is either an address
// further along in the same string, or NULL if there are no more tokens.
@@ -2647,8 +2648,8 @@ void CStringChecker::evalStdCopyCommon(CheckerContext &C,
SVal DstVal = State->getSVal(Dst, SF);
// FIXME: As we do not know how many items are copied, we also invalidate the
// super region containing the target location.
- State = invalidateDestinationBufferAlwaysEscapeSuperRegion(
- C, State, Call.getCFGElementRef(), DstVal);
+ State = invalidateDestinationBufferAlwaysEscapeSuperRegion(C, State, Call,
+ DstVal);
SValBuilder &SVB = C.getSValBuilder();
@@ -2701,8 +2702,8 @@ void CStringChecker::evalMemset(CheckerContext &C,
// According to the values of the arguments, bind the value of the second
// argument to the destination buffer and set string length, or just
// invalidate the destination buffer.
- if (!memsetAux(Buffer.Expression, Call.getCFGElementRef(),
- C.getSVal(CharE.Expression), Size.Expression, C, State))
+ if (!memsetAux(Buffer.Expression, Call, C.getSVal(CharE.Expression),
+ Size.Expression, C, State))
return;
State = State->BindExpr(Call.getOriginExpr(), SF, BufferPtrVal);
@@ -2746,8 +2747,7 @@ void CStringChecker::evalBzero(CheckerContext &C, const CallEvent &Call) const {
if (!State)
return;
- if (!memsetAux(Buffer.Expression, Call.getCFGElementRef(), Zero,
- Size.Expression, C, State))
+ if (!memsetAux(Buffer.Expression, Call, Zero, Size.Expression, C, State))
return;
C.addTransition(State);
diff --git a/clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.cpp
index bba5c5e3b1efb..b5f773c535990 100644
--- a/clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.cpp
@@ -24,6 +24,7 @@
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/InvalidationCause.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h"
#include "llvm/ADT/STLExtras.h"
@@ -253,12 +254,16 @@ ProgramStateRef setErrnoForStdFailure(ProgramStateRef State, CheckerContext &C,
ProgramStateRef setErrnoStdMustBeChecked(ProgramStateRef State,
CheckerContext &C,
- ConstCFGElementRef Elem) {
+ const CallEvent &Call) {
const MemRegion *ErrnoR = State->get<ErrnoRegion>();
if (!ErrnoR)
return State;
- State = State->invalidateRegions(ErrnoR, Elem, C.blockCount(),
- C.getStackFrame(), false);
+ State = State->invalidateRegions(
+ ErrnoR, Call.getCFGElementRef(), C.blockCount(), C.getStackFrame(),
+ /*CausesPointerEscape=*/false,
+ /*InvalidatedSymbols=*/nullptr, /*Call=*/nullptr,
+ /*ITraits=*/nullptr,
+ Call.tryCreateInvalidationCause<PartiallyModeledCall>());
if (!State)
return nullptr;
return setErrnoState(State, MustBeChecked);
diff --git a/clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.h b/clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.h
index d1e90e6fc032c..69a1a1497f727 100644
--- a/clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.h
+++ b/clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.h
@@ -13,6 +13,7 @@
#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_ERRNOMODELING_H
#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_ERRNOMODELING_H
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h"
@@ -98,7 +99,7 @@ ProgramStateRef setErrnoForStdFailure(ProgramStateRef State, CheckerContext &C,
/// \arg \c Elem CFG Element that causes invalidation of \c errno.
ProgramStateRef setErrnoStdMustBeChecked(ProgramStateRef State,
CheckerContext &C,
- ConstCFGElementRef Elem);
+ const CallEvent &Call);
} // namespace errno_modeling
} // namespace ento
diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
index 7ea028246a2ee..e502761c7d0dc 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
@@ -76,6 +76,7 @@
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/InvalidationCause.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState_Fwd.h"
@@ -2478,10 +2479,11 @@ MallocChecker::FreeMemAux(CheckerContext &C, const Expr *ArgExpr,
// Assume that after memory is freed, it contains unknown values. This
// conforts languages standards, since reading from freed memory is considered
// UB and may result in arbitrary value.
- State = State->invalidateRegions({location}, Call.getCFGElementRef(),
- C.blockCount(), C.getStackFrame(),
- /*CausesPointerEscape=*/false,
- /*InvalidatedSymbols=*/nullptr);
+ State = State->invalidateRegions(
+ {location}, Call.getCFGElementRef(), C.blockCount(), C.getStackFrame(),
+ /*CausesPointerEscape=*/false,
+ /*InvalidatedSymbols=*/nullptr, /*Call=*/nullptr, /*ITraits=*/nullptr,
+ Call.tryCreateInvalidationCause<PartiallyModeledCall>());
// Normal free.
if (Hold)
diff --git a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
index d6787ae517f6c..ab5f25cd47a74 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
@@ -24,6 +24,7 @@
#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/InvalidationCause.h"
#include "llvm/ADT/StringSet.h"
using namespace clang;
@@ -568,9 +569,11 @@ bool MoveChecker::evalCall(const CallEvent &Call, CheckerContext &C) const {
SVal ReturnVal = SVB.conjureSymbolVal(Call, C.blockCount());
State = State->BindExpr(CE, C.getStackFrame(), ReturnVal);
- State = State->invalidateRegions({DestRegion}, Call.getCFGElementRef(),
- C.blockCount(), C.getStackFrame(),
- /*CausesPointerEscape=*/false);
+ State = State->invalidateRegions(
+ {DestRegion}, Call.getCFGElementRef(), C.blockCount(), C.getStackFrame(),
+ /*CausesPointerEscape=*/false, /*IS=*/nullptr, /*Call=*/nullptr,
+ /*ITraits=*/nullptr,
+ Call.tryCreateInvalidationCause<PartiallyModeledCall>());
if (shouldBeTracked(OK))
State = State->set<TrackedContentsMap>(ContainerRegion,
diff --git a/clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp
index ad81e5076f931..50f397f3687b4 100644
--- a/clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp
@@ -26,6 +26,7 @@
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/InvalidationCause.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h"
@@ -529,8 +530,14 @@ bool SmartPtrModeling::handleOstreamOperator(const CallEvent &Call,
const MemRegion *StreamThisRegion = StreamVal.getAsRegion();
if (!StreamThisRegion)
return false;
+
+ const auto *Cause = Call.tryCreateInvalidationCause<PartiallyModeledCall>();
State = State->invalidateRegions({StreamThisRegion}, Call.getCFGElementRef(),
- C.blockCount(), C.getStackFrame(), false);
+ C.blockCount(), C.getStackFrame(),
+ /*CausesPointerEscape=*/false,
+ /*InvalidatedSymbols=*/nullptr,
+ /*Call=*/nullptr,
+ /*ITraits=*/nullptr, Cause);
State = State->BindExpr(Call.getOriginExpr(), C.getStackFrame(), StreamVal);
C.addTransition(State);
return true;
diff --git a/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp
index 4fe3e1f7623f6..3e75d2d4428db 100644
--- a/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp
@@ -618,8 +618,7 @@ class StdLibraryFunctionsChecker
ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call,
const Summary &Summary,
CheckerContext &C) const override {
- return errno_modeling::setErrnoStdMustBeChecked(State, C,
- Call.getCFGElementRef());
+ return errno_modeling::setErrnoStdMustBeChecked(State, C, Call);
}
std::string describe(CheckerContext &C) const override {
diff --git a/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
index e035a997f520a..13b9e8accb237 100644
--- a/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
@@ -21,6 +21,7 @@
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/InvalidationCause.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
@@ -860,7 +861,8 @@ escapeByStartIndexAndCount(ProgramStateRef State, const CallEvent &Call,
return State->invalidateRegions(
EscapingVals, Call.getCFGElementRef(), BlockCount, SF,
/*CausesPointerEscape=*/false,
- /*InvalidatedSymbols=*/nullptr, &Call, &ITraits);
+ /*InvalidatedSymbols=*/nullptr, &Call, &ITraits,
+ Call.tryCreateInvalidationCause<PartiallyModeledCall>());
}
static ProgramStateRef escapeArgs(ProgramStateRef State, CheckerContext &C,
@@ -868,10 +870,11 @@ static ProgramStateRef escapeArgs(ProgramStateRef State, CheckerContext &C,
ArrayRef<unsigned int> EscapingArgs) {
auto GetArgSVal = [&Call](int Idx) { return Call.getArgSVal(Idx); };
auto EscapingVals = to_vector(map_range(EscapingArgs, GetArgSVal));
- State = State->invalidateRegions(EscapingVals, Call.getCFGElementRef(),
- C.blockCount(), C.getStackFrame(),
- /*CausesPointerEscape=*/false,
- /*InvalidatedSymbols=*/nullptr);
+ State = State->invalidateRegions(
+ EscapingVals, Call.getCFGElementRef(), C.blockCount(), C.getStackFrame(),
+ /*CausesPointerEscape=*/false,
+ /*InvalidatedSymbols=*/nullptr, /*Call=*/nullptr, /*ITraits=*/nullptr,
+ Call.tryCreateInvalidationCause<PartiallyModeledCall>());
return State;
}
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
index b66b5c5e358e4..1891c539a1377 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
@@ -20,6 +20,7 @@
#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/EntryPointStats.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/InvalidationCause.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/SaveAndRestore.h"
@@ -786,9 +787,10 @@ ProgramStateRef ExprEngine::bindReturnValue(const CallEvent &Call,
RegionAndSymbolInvalidationTraits ITraits;
ITraits.setTrait(TargetR,
RegionAndSymbolInvalidationTraits::TK_DoNotInvalidateSuperRegion);
- State = State->invalidateRegions(TargetR, Elem, Count, SF,
- /* CausesPointerEscape=*/false, nullptr,
- &Call, &ITraits);
+ State = State->invalidateRegions(
+ TargetR, Elem, Count, SF,
+ /* CausesPointerEscape=*/false, /*InvalidatedSymbols=*/nullptr, &Call,
+ &ITraits, Call.tryCreateInvalidationCause<PartiallyModeledCall>());
R = State->getSVal(Target.castAs<Loc>(), E->getType());
} else {
diff --git a/clang/test/Analysis/ctor-trivial-copy.cpp b/clang/test/Analysis/ctor-trivial-copy.cpp
index 44990fc631d6d..6a8e399d6e8f4 100644
--- a/clang/test/Analysis/ctor-trivial-copy.cpp
+++ b/clang/test/Analysis/ctor-trivial-copy.cpp
@@ -57,13 +57,13 @@ void _01_empty_structs() {
clang_analyzer_printState();
// CHECK: "store": { "pointer": "0x{{[0-9a-f]+}}", "items": [
// CHECK-NEXT: { "cluster": "GlobalInternalSpaceRegion", "pointer": "0x{{[0-9a-f]+}}", "items": [
- // CHECK-NEXT: { "kind": "Default", "offset": 0, "value": "conj_$
+ // CHECK-NEXT: { "kind": "Default", "offset": 0, "value": "inv_$
// CHECK-NEXT: ]},
// CHECK-NEXT: { "cluster": "GlobalSystemSpaceRegion", "pointer": "0x{{[0-9a-f]+}}", "items": [
- // CHECK-NEXT: { "kind": "Default", "offset": 0, "value": "conj_$
+ // CHECK-NEXT: { "kind": "Default", "offset": 0, "value": "inv_$
// CHECK-NEXT: ]},
// CHECK-NEXT: { "cluster": "Empty", "pointer": "0x{{[0-9a-f]+}}", "items": [
- // CHECK-NEXT: { "kind": "Default", "offset": 0, "value": "[[EMPTY_CONJ:conj_\$[0-9]+{int, LC[0-9]+, S[0-9]+, #[0-9]+}]]" }
+ // CHECK-NEXT: { "kind": "Default", "offset": 0, "value": "[[EMPTY_CONJ:inv_\$[0-9]+{int, LC[0-9]+, partial-call, S[0-9]+, #[0-9]+}]]" }
// CHECK-NEXT: ]}
// CHECK-NEXT: ]},
@@ -86,13 +86,13 @@ void _02_structs_with_members() {
clang_analyzer_printState();
// CHECK: "store": { "pointer": "0x{{[0-9a-f]+}}", "items": [
// CHECK-NEXT: { "cluster": "GlobalInternalSpaceRegion", "pointer": "0x{{[0-9a-f]+}}", "items": [
- // CHECK-NEXT: { "kind": "Default", "offset": 0, "value": "conj_$
+ // CHECK-NEXT: { "kind": "Default", "offset": 0, "value": "inv_$
// CHECK-NEXT: ]},
// CHECK-NEXT: { "cluster": "GlobalSystemSpaceRegion", "pointer": "0x{{[0-9a-f]+}}", "items": [
- // CHECK-NEXT: { "kind": "Default", "offset": 0, "value": "conj_$
+ // CHECK-NEXT: { "kind": "Default", "offset": 0, "value": "inv_$
// CHECK-NEXT: ]},
// CHECK-NEXT: { "cluster": "Aggr", "pointer": "0x{{[0-9a-f]+}}", "items": [
- // CHECK-NEXT: { "kind": "Default", "offset": 0, "value": "[[AGGR_CONJ:conj_\$[0-9]+{int, LC[0-9]+, S[0-9]+, #[0-9]+}]]" }
+ // CHECK-NEXT: { "kind": "Default", "offset": 0, "value": "[[AGGR_CONJ:inv_\$[0-9]+{int, LC[0-9]+, partial-call, S[0-9]+, #[0-9]+}]]" }
// CHECK-NEXT: ]},
// CHECK-NEXT: { "cluster": "Aggr2", "pointer": "0x{{[0-9a-f]+}}", "items": [
// CHECK-NEXT: { "kind": "Direct", "offset": 0, "value": "derived_${{[0-9]+}}{[[AGGR_CONJ]],Aggr.x}" },
diff --git a/clang/test/Analysis/explain-svals.cpp b/clang/test/Analysis/explain-svals.cpp
index 77e858dea75cc..03de76af0755b 100644
--- a/clang/test/Analysis/explain-svals.cpp
+++ b/clang/test/Analysis/explain-svals.cpp
@@ -100,7 +100,7 @@ class C {
void test_6() {
clang_analyzer_explain(conjure_S()); // expected-warning-re{{{{^lazily frozen compound value of 1st parameter of function 'clang_analyzer_explain\(\)'$}}}}
- clang_analyzer_explain(conjure_S().z); // expected-warning-re{{{{^value derived from \(symbol of type 'int' conjured at CFG element 'conjure_S\(\) \(CXXRecordTypedCall, \)'\) for field 'z' of temporary object constructed at statement 'conjure_S\(\)'$}}}}
+ clang_analyzer_explain(conjure_S().z); // expected-warning-re{{{{^value derived from \(the result of an invalidation \(partial-call\) of type 'int' at CFG element 'conjure_S\(\) \(CXXRecordTypedCall, \)'\) for field 'z' of temporary object constructed at statement 'conjure_S\(\)'$}}}}
}
class C_top_level {
diff --git a/clang/test/Analysis/fread.c b/clang/test/Analysis/fread.c
index e3463bce3de47..f810157cf93a2 100644
--- a/clang/test/Analysis/fread.c
+++ b/clang/test/Analysis/fread.c
@@ -123,11 +123,11 @@ void random_access_read1(int index) {
if (success) {
// Unknown value but not garbage.
clang_analyzer_isTainted(c[1]); // expected-warning {{YES}}
- clang_analyzer_dump(c[1]); // expected-warning {{conj_}}
+ clang_analyzer_dump(c[1]); // expected-warning {{inv_}}
} else {
// Possibly indeterminate value but not modeled.
clang_analyzer_isTainted(c[1]); // expected-warning {{YES}}
- clang_analyzer_dump(c[1]); // expected-warning {{conj_}}
+ clang_analyzer_dump(c[1]); // expected-warning {{inv_}}
}
break;
@@ -136,11 +136,11 @@ void random_access_read1(int index) {
long p = c[2]; // Unknown value but not garbage.
// FIXME: Taint analysis only marks the first byte of a memory region. See getPointeeOf in GenericTaintChecker.cpp.
clang_analyzer_isTainted(c[2]); // expected-warning {{NO}}
- clang_analyzer_dump(c[2]); // expected-warning {{conj_}}
+ clang_analyzer_dump(c[2]); // expected-warning {{inv_}}
} else {
// Possibly indeterminate value but not modeled.
clang_analyzer_isTainted(c[2]); // expected-warning {{NO}} // FIXME: See above.
- clang_analyzer_dump(c[2]); // expected-warning {{conj_}}
+ clang_analyzer_dump(c[2]); // expected-warning {{inv_}}
}
break;
@@ -167,7 +167,7 @@ void random_access_read2(int b) {
if (b) {
int p = buffer[1]; // Unknown value but not garbage.
clang_analyzer_isTainted(p); // expected-warning {{YES}}
- clang_analyzer_dump(p); // expected-warning {{conj_}}
+ clang_analyzer_dump(p); // expected-warning {{inv_}}
} else {
int p = buffer[0]; // expected-warning {{Assigned value is uninitialized}}
}
@@ -268,11 +268,11 @@ void compound_read1(void) {
if (1 == fread(&s.b, sizeof(s.b), 1, fp)) {
long p = s.b;
clang_analyzer_isTainted(p); // expected-warning {{YES}}
- clang_analyzer_dump(p); // expected-warning {{conj_}}
+ clang_analyzer_dump(p); // expected-warning {{inv_}}
} else {
long p = s.b;
clang_analyzer_isTainted(p); // expected-warning {{YES}}
- clang_analyzer_dump(p); // expected-warning {{conj_}}
+ clang_analyzer_dump(p); // expected-warning {{inv_}}
}
fclose(fp);
}
@@ -353,14 +353,14 @@ void test_partial_elements_read(void) {
// 3*5: 15 bytes read; which is not exactly 4 integers, but we still invalidate the first 4 ints.
if (5 == fread(buffer + 1, 3, 5, fp)) {
clang_analyzer_dump(buffer[0]); // expected-warning{{1 S32b}}
- clang_analyzer_dump(buffer[1]); // expected-warning{{conj_}}
- clang_analyzer_dump(buffer[2]); // expected-warning{{conj_}}
- clang_analyzer_dump(buffer[3]); // expected-warning{{conj_}}
- clang_analyzer_dump(buffer[4]); // expected-warning{{conj_}}
+ clang_analyzer_dump(buffer[1]); // expected-warning{{inv_}}
+ clang_analyzer_dump(buffer[2]); // expected-warning{{inv_}}
+ clang_analyzer_dump(buffer[3]); // expected-warning{{inv_}}
+ clang_analyzer_dump(buffer[4]); // expected-warning{{inv_}}
clang_analyzer_dump(buffer[5]); // expected-warning{{6 S32b}}
char *c = (char*)buffer;
- clang_analyzer_dump(c[4+12]); // expected-warning{{conj_}} 16th byte of buffer, which is the beginning of the 4th 'int' in the buffer.
+ clang_analyzer_dump(c[4+12]); // expected-warning{{inv_}} 16th byte of buffer, which is the beginning of the 4th 'int' in the buffer.
// FIXME: The store should have returned a partial binding for the 17th byte of the buffer, which is the 2nd byte of the previous int.
// This byte should have been initialized by the 'fread' earlier. However, the Store lies to us and says it's uninitialized.
@@ -368,10 +368,10 @@ void test_partial_elements_read(void) {
clang_analyzer_dump(c[4+16]); // This should be the first byte that 'fread' leaves uninitialized. This should raise the uninit read diag.
} else {
clang_analyzer_dump(buffer[0]); // expected-warning{{1 S32b}} ok
- clang_analyzer_dump(buffer[1]); // expected-warning{{conj_}} ok
- clang_analyzer_dump(buffer[2]); // expected-warning{{conj_}} ok
- clang_analyzer_dump(buffer[3]); // expected-warning{{conj_}} ok
- clang_analyzer_dump(buffer[4]); // expected-warning{{conj_}} ok, but an uninit warning would be also fine.
+ clang_analyzer_dump(buffer[1]); // expected-warning{{inv_}} ok
+ clang_analyzer_dump(buffer[2]); // expected-warning{{inv_}} ok
+ clang_analyzer_dump(buffer[3]); // expected-warning{{inv_}} ok
+ clang_analyzer_dump(buffer[4]); // expected-warning{{inv_}} ok, but an uninit warning would be also fine.
clang_analyzer_dump(buffer[5]); // expected-warning{{6 S32b}} ok
clang_analyzer_dump(buffer[6]); // expected-warning{{1st function call argument is an uninitialized value}} ok
}
@@ -391,12 +391,12 @@ void test_whole_elements_read(void) {
// 3*20: 60 bytes read; which is basically 15 integers.
if (20 == fread(buffer + 1, 3, 20, fp)) {
clang_analyzer_dump(buffer[0]); // expected-warning{{1 S32b}}
- clang_analyzer_dump(buffer[15]); // expected-warning{{conj_}}
+ clang_analyzer_dump(buffer[15]); // expected-warning{{inv_}}
clang_analyzer_dump(buffer[16]); // expected-warning{{3 S32b}}
clang_analyzer_dump(buffer[17]); // expected-warning{{1st function call argument is an uninitialized value}}
} else {
clang_analyzer_dump(buffer[0]); // expected-warning{{1 S32b}}
- clang_analyzer_dump(buffer[15]); // expected-warning{{conj_}}
+ clang_analyzer_dump(buffer[15]); // expected-warning{{inv_}}
clang_analyzer_dump(buffer[16]); // expected-warning{{3 S32b}}
clang_analyzer_dump(buffer[17]); // expected-warning{{1st function call argument is an uninitialized value}}
}
@@ -419,25 +419,25 @@ void test_unaligned_start_read(void) {
// We read 4 bytes at byte offset: 1,2,3,4.
if (4 == fread(asChar + 1, 1, 4, fp)) {
clang_analyzer_dump(buffer[0]); // expected-warning{{3 S32b}} FIXME: The int binding should have been partially overwritten by the read call. This definitely should not be 3.
- clang_analyzer_dump(buffer[1]); // expected-warning{{conj_}}
+ clang_analyzer_dump(buffer[1]); // expected-warning{{inv_}}
clang_analyzer_dump(buffer[2]); // expected-warning{{5 S32b}}
clang_analyzer_dump_char(asChar[0]); // expected-warning{{3 S8b}} This is technically true assuming x86 (little-endian) architecture.
- clang_analyzer_dump_char(asChar[1]); // expected-warning{{conj_}} 1
- clang_analyzer_dump_char(asChar[2]); // expected-warning{{conj_}} 2
- clang_analyzer_dump_char(asChar[3]); // expected-warning{{conj_}} 3
- clang_analyzer_dump_char(asChar[4]); // expected-warning{{conj_}} 4
+ clang_analyzer_dump_char(asChar[1]); // expected-warning{{inv_}} 1
+ clang_analyzer_dump_char(asChar[2]); // expected-warning{{inv_}} 2
+ clang_analyzer_dump_char(asChar[3]); // expected-warning{{inv_}} 3
+ clang_analyzer_dump_char(asChar[4]); // expected-warning{{inv_}} 4
clang_analyzer_dump_char(asChar[5]); // expected-warning{{1st function call argument is an uninitialized value}}
} else {
clang_analyzer_dump(buffer[0]); // expected-warning{{3 S32b}} FIXME: The int binding should have been partially overwritten by the read call. This definitely should not be 3.
- clang_analyzer_dump(buffer[1]); // expected-warning{{conj_}}
+ clang_analyzer_dump(buffer[1]); // expected-warning{{inv_}}
clang_analyzer_dump(buffer[2]); // expected-warning{{5 S32b}}
clang_analyzer_dump_char(asChar[0]); // expected-warning{{3 S8b}} This is technically true assuming x86 (little-endian) architecture.
- clang_analyzer_dump_char(asChar[1]); // expected-warning{{conj_}} 1
- clang_analyzer_dump_char(asChar[2]); // expected-warning{{conj_}} 2
- clang_analyzer_dump_char(asChar[3]); // expected-warning{{conj_}} 3
- clang_analyzer_dump_char(asChar[4]); // expected-warning{{conj_}} 4
+ clang_analyzer_dump_char(asChar[1]); // expected-warning{{inv_}} 1
+ clang_analyzer_dump_char(asChar[2]); // expected-warning{{inv_}} 2
+ clang_analyzer_dump_char(asChar[3]); // expected-warning{{inv_}} 3
+ clang_analyzer_dump_char(asChar[4]); // expected-warning{{inv_}} 4
clang_analyzer_dump_char(asChar[5]); // expected-warning{{1st function call argument is an uninitialized value}}
}
fclose(fp);
diff --git a/clang/test/Analysis/getline-unixapi.c b/clang/test/Analysis/getline-unixapi.c
index 86635ed849979..e4b366e3ea6f3 100644
--- a/clang/test/Analysis/getline-unixapi.c
+++ b/clang/test/Analysis/getline-unixapi.c
@@ -141,8 +141,8 @@ void test_getline_null_buffer() {
clang_analyzer_warnIfReached(); // must not happen
} else {
// The buffer could be allocated both on failure and success
- clang_analyzer_dump_int(n); // expected-warning {{conj_$}}
- clang_analyzer_dump_ptr(buffer); // expected-warning {{conj_$}}
+ clang_analyzer_dump_int(n); // expected-warning {{inv_$}}
+ clang_analyzer_dump_ptr(buffer); // expected-warning {{inv_$}}
}
free(buffer);
fclose(F1);
@@ -203,8 +203,8 @@ void test_getdelim_null_buffer() {
}
else {
// The buffer could be allocated both on failure and success
- clang_analyzer_dump_int(n); // expected-warning {{conj_$}}
- clang_analyzer_dump_ptr(buffer); // expected-warning {{conj_$}}
+ clang_analyzer_dump_int(n); // expected-warning {{inv_$}}
+ clang_analyzer_dump_ptr(buffer); // expected-warning {{inv_$}}
}
free(buffer);
fclose(F1);
diff --git a/clang/test/Analysis/invalidation-artifact-move.cpp b/clang/test/Analysis/invalidation-artifact-move.cpp
new file mode 100644
index 0000000000000..62ceafb4d5a66
--- /dev/null
+++ b/clang/test/Analysis/invalidation-artifact-move.cpp
@@ -0,0 +1,25 @@
+// RUN: %clang_analyze_cc1 -std=c++17 -verify %s \
+// RUN: -analyzer-checker=core,debug.ExprInspection,cplusplus.Move,alpha.cplusplus.IteratorModeling \
+// RUN: -analyzer-config aggressive-binary-operation-simplification=true \
+// RUN: -analyzer-config c++-container-inlining=true
+
+// MoveChecker::evalCall models the 3-argument std::move algorithm. As part of
+// that partial modeling it hands the destination container off to
+// ProgramState::invalidateRegions. After this patch the resulting symbols
+// carry a "partial-call" cause instead of a plain conservative-eval conj_$.
+
+#include "Inputs/system-header-simulator-cxx.h"
+
+template <typename T> void clang_analyzer_dump(T);
+
+void test_move_dest_invalidation() {
+ std::vector<int> src;
+ src.push_back(1);
+ std::vector<int> dst;
+
+ std::move(src.begin(), src.end(), std::back_inserter(dst));
+
+ // The destination container's contents are now bound to an inv_$ artifact
+ // carrying a "partial-call" cause.
+ clang_analyzer_dump(dst[0]); // expected-warning-re{{{{inv_\$[0-9]+{int, LC[0-9]+, partial-call, S[0-9]+, #[0-9]+}}}}}
+}
diff --git a/clang/test/Analysis/invalidation-artifact-partial-call.c b/clang/test/Analysis/invalidation-artifact-partial-call.c
new file mode 100644
index 0000000000000..c3f0cd9573c6c
--- /dev/null
+++ b/clang/test/Analysis/invalidation-artifact-partial-call.c
@@ -0,0 +1,27 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection \
+// RUN: -analyzer-checker=unix.Malloc -analyzer-checker=unix.Stream \
+// RUN: -verify %s
+
+// Several checkers ("partially modeled" calls — MallocChecker, StreamChecker,
+// CStringChecker, etc.) hand off to ProgramState::invalidateRegions for the
+// fallback parts of their modeling. After this patch the resulting symbols
+// carry a "partial-call" cause to distinguish them from values produced by a
+// pure conservative-eval call.
+
+#include "Inputs/system-header-simulator-for-simple-stream.h"
+
+void clang_analyzer_dump_int(int);
+void clang_analyzer_dump_ptr(int *);
+
+// ----- StreamChecker: fread invalidates the buffer with a partial-call cause.
+void test_fread_buffer_invalidation(void) {
+ FILE *F = fopen("/tmp/x", "r");
+ if (!F)
+ return;
+ int buf[2] = {0, 0};
+ fread(buf, sizeof(int), 2, F);
+ // The buffer's default binding is now an inv_$ artifact carrying a
+ // "partial-call" cause.
+ clang_analyzer_dump_int(buf[0]); // expected-warning-re{{{{inv_\$[0-9]+{int, LC[0-9]+, partial-call, S[0-9]+, #[0-9]+}}}}}
+ fclose(F);
+}
diff --git a/clang/test/Analysis/store-dump-orders.cpp b/clang/test/Analysis/store-dump-orders.cpp
index d99f581f00fe1..b1af6c7a18b2b 100644
--- a/clang/test/Analysis/store-dump-orders.cpp
+++ b/clang/test/Analysis/store-dump-orders.cpp
@@ -35,10 +35,10 @@ void test_output(int n) {
// CHECK: "store": { "pointer": "0x{{[0-9a-f]+}}", "items": [
// CHECK-NEXT: { "cluster": "GlobalInternalSpaceRegion", "pointer": "0x{{[0-9a-f]+}}", "items": [
- // CHECK-NEXT: { "kind": "Default", "offset": 0, "value": "conj_$
+ // CHECK-NEXT: { "kind": "Default", "offset": 0, "value": "inv_$
// CHECK-NEXT: ]},
// CHECK-NEXT: { "cluster": "GlobalSystemSpaceRegion", "pointer": "0x{{[0-9a-f]+}}", "items": [
- // CHECK-NEXT: { "kind": "Default", "offset": 0, "value": "conj_$
+ // CHECK-NEXT: { "kind": "Default", "offset": 0, "value": "inv_$
// CHECK-NEXT: ]},
// CHECK-NEXT: { "cluster": "objfirst", "pointer": "0x{{[0-9a-f]+}}", "items": [
// CHECK-NEXT: { "kind": "Default", "offset": 0, "value": "lazyCompoundVal
diff --git a/clang/test/Analysis/stream-invalidate.c b/clang/test/Analysis/stream-invalidate.c
index 910f651b3c787..915a284702561 100644
--- a/clang/test/Analysis/stream-invalidate.c
+++ b/clang/test/Analysis/stream-invalidate.c
@@ -17,8 +17,8 @@ void test_fread(void) {
char Buf[3] = {10, 10, 10};
fread(Buf, 1, 3, F);
// The check applies to success and failure.
- clang_analyzer_dump(Buf[0]); // expected-warning {{conj_$}} Should not preserve the previous value, thus should not be 10.
- clang_analyzer_dump(Buf[2]); // expected-warning {{conj_$}}
+ clang_analyzer_dump(Buf[0]); // expected-warning {{inv_$}} Should not preserve the previous value, thus should not be 10.
+ clang_analyzer_dump(Buf[2]); // expected-warning {{inv_$}}
if (feof(F)) {
char Buf1[3] = {10, 10, 10};
fread(Buf1, 1, 3, F); // expected-warning {{is in EOF state}}
@@ -51,8 +51,8 @@ void test_fgets() {
char Buf[3] = {10, 10, 10};
fgets(Buf, 3, F);
// The check applies to success and failure.
- clang_analyzer_dump(Buf[0]); // expected-warning {{conj_$}} Should not preserve the previous value, thus should not be 10.
- clang_analyzer_dump(Buf[2]); // expected-warning {{conj_$}}
+ clang_analyzer_dump(Buf[0]); // expected-warning {{inv_$}} Should not preserve the previous value, thus should not be 10.
+ clang_analyzer_dump(Buf[2]); // expected-warning {{inv_$}}
if (feof(F)) {
char Buf1[3] = {10, 10, 10};
fgets(Buf1, 3, F); // expected-warning {{is in EOF state}}
@@ -87,17 +87,17 @@ void test_fscanf() {
unsigned b;
int Ret = fscanf(F, "%d %u", &a, &b);
if (Ret == 0) {
- clang_analyzer_dump(a); // expected-warning {{conj_$}}
+ clang_analyzer_dump(a); // expected-warning {{inv_$}}
// FIXME: should be {{1 S32b}}.
- clang_analyzer_dump(b); // expected-warning {{conj_$}}
+ clang_analyzer_dump(b); // expected-warning {{inv_$}}
// FIXME: should be {{uninitialized value}}.
} else if (Ret == 1) {
- clang_analyzer_dump(a); // expected-warning {{conj_$}}
- clang_analyzer_dump(b); // expected-warning {{conj_$}}
+ clang_analyzer_dump(a); // expected-warning {{inv_$}}
+ clang_analyzer_dump(b); // expected-warning {{inv_$}}
// FIXME: should be {{uninitialized value}}.
} else if (Ret >= 2) {
- clang_analyzer_dump(a); // expected-warning {{conj_$}}
- clang_analyzer_dump(b); // expected-warning {{conj_$}}
+ clang_analyzer_dump(a); // expected-warning {{inv_$}}
+ clang_analyzer_dump(b); // expected-warning {{inv_$}}
clang_analyzer_eval(Ret == 2); // expected-warning {{FALSE}} expected-warning {{TRUE}}
// FIXME: should be only TRUE.
} else {
@@ -139,7 +139,7 @@ void test_fgetpos() {
fpos_t Pos = 1;
int Ret = fgetpos(F, &Pos);
if (Ret == 0) {
- clang_analyzer_dump(Pos); // expected-warning {{conj_$}}
+ clang_analyzer_dump(Pos); // expected-warning {{inv_$}}
} else {
clang_analyzer_dump(Pos); // expected-warning {{1 S32b}}
}
>From a41e986755b6e7c1fcf768a6070af7404d2930e9 Mon Sep 17 00:00:00 2001
From: Balazs Benics <benicsbalazs at gmail.com>
Date: Fri, 3 Jul 2026 16:52:07 +0100
Subject: [PATCH 5/6] [analyzer][NFC] Make InvalidationCause mandatory for
invalidateRegions
Clean up the migration by making this param mandatory.
---
.../Core/PathSensitive/ProgramState.h | 12 +++++-----
.../Checkers/CStringChecker.cpp | 4 ++--
.../StaticAnalyzer/Checkers/ErrnoModeling.cpp | 2 --
.../StaticAnalyzer/Checkers/MallocChecker.cpp | 1 -
.../StaticAnalyzer/Checkers/MoveChecker.cpp | 3 +--
.../Checkers/SmartPtrModeling.cpp | 5 +---
.../StaticAnalyzer/Checkers/StreamChecker.cpp | 5 ++--
clang/lib/StaticAnalyzer/Core/CallEvent.cpp | 4 ++--
clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 24 +++++++------------
.../Core/ExprEngineCallAndReturn.cpp | 5 ++--
.../lib/StaticAnalyzer/Core/LoopWidening.cpp | 4 ++--
.../lib/StaticAnalyzer/Core/ProgramState.cpp | 16 ++++++-------
12 files changed, 36 insertions(+), 49 deletions(-)
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h
index 15cbcd10f5055..6a110fd2b8a7f 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h
@@ -335,17 +335,17 @@ class ProgramState : public llvm::FoldingSetNode {
[[nodiscard]] ProgramStateRef invalidateRegions(
ArrayRef<const MemRegion *> Regions, ConstCFGElementRef Elem,
unsigned BlockCount, const StackFrame *SF, bool CausesPointerEscape,
- InvalidatedSymbols *IS = nullptr, const CallEvent *Call = nullptr,
- RegionAndSymbolInvalidationTraits *ITraits = nullptr,
- const InvalidationCause *Cause = nullptr) const;
+ const InvalidationCause *Cause, InvalidatedSymbols *IS = nullptr,
+ const CallEvent *Call = nullptr,
+ RegionAndSymbolInvalidationTraits *ITraits = nullptr) const;
[[nodiscard]] ProgramStateRef
invalidateRegions(ArrayRef<SVal> Values, ConstCFGElementRef Elem,
unsigned BlockCount, const StackFrame *SF,
- bool CausesPointerEscape, InvalidatedSymbols *IS = nullptr,
+ bool CausesPointerEscape, const InvalidationCause *Cause,
+ InvalidatedSymbols *IS = nullptr,
const CallEvent *Call = nullptr,
- RegionAndSymbolInvalidationTraits *ITraits = nullptr,
- const InvalidationCause *Cause = nullptr) const;
+ RegionAndSymbolInvalidationTraits *ITraits = nullptr) const;
/// enterStackFrame - Returns the state for entry to the given stack frame,
/// preserving the current state.
diff --git a/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp
index b27f4c46a0cf0..663abda342603 100644
--- a/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp
@@ -1303,8 +1303,8 @@ ProgramStateRef CStringChecker::invalidateBufferAux(
return State->invalidateRegions(
R, Call.getCFGElementRef(), C.blockCount(), SF, CausesPointerEscape,
- /*InvalidatedSymbols=*/nullptr, /*Call=*/nullptr, &ITraits,
- Call.tryCreateInvalidationCause<PartiallyModeledCall>());
+ Call.tryCreateInvalidationCause<PartiallyModeledCall>(),
+ /*InvalidatedSymbols=*/nullptr, /*Call=*/nullptr, &ITraits);
}
// If we have a non-region value by chance, just remove the binding.
diff --git a/clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.cpp
index b5f773c535990..6ae5359424606 100644
--- a/clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/ErrnoModeling.cpp
@@ -261,8 +261,6 @@ ProgramStateRef setErrnoStdMustBeChecked(ProgramStateRef State,
State = State->invalidateRegions(
ErrnoR, Call.getCFGElementRef(), C.blockCount(), C.getStackFrame(),
/*CausesPointerEscape=*/false,
- /*InvalidatedSymbols=*/nullptr, /*Call=*/nullptr,
- /*ITraits=*/nullptr,
Call.tryCreateInvalidationCause<PartiallyModeledCall>());
if (!State)
return nullptr;
diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
index e502761c7d0dc..4e24608cccc7a 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
@@ -2482,7 +2482,6 @@ MallocChecker::FreeMemAux(CheckerContext &C, const Expr *ArgExpr,
State = State->invalidateRegions(
{location}, Call.getCFGElementRef(), C.blockCount(), C.getStackFrame(),
/*CausesPointerEscape=*/false,
- /*InvalidatedSymbols=*/nullptr, /*Call=*/nullptr, /*ITraits=*/nullptr,
Call.tryCreateInvalidationCause<PartiallyModeledCall>());
// Normal free.
diff --git a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
index ab5f25cd47a74..deaaa78aa81f7 100644
--- a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
@@ -571,8 +571,7 @@ bool MoveChecker::evalCall(const CallEvent &Call, CheckerContext &C) const {
State = State->invalidateRegions(
{DestRegion}, Call.getCFGElementRef(), C.blockCount(), C.getStackFrame(),
- /*CausesPointerEscape=*/false, /*IS=*/nullptr, /*Call=*/nullptr,
- /*ITraits=*/nullptr,
+ /*CausesPointerEscape=*/false,
Call.tryCreateInvalidationCause<PartiallyModeledCall>());
if (shouldBeTracked(OK))
diff --git a/clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp
index 50f397f3687b4..ce224474d21ff 100644
--- a/clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp
@@ -534,10 +534,7 @@ bool SmartPtrModeling::handleOstreamOperator(const CallEvent &Call,
const auto *Cause = Call.tryCreateInvalidationCause<PartiallyModeledCall>();
State = State->invalidateRegions({StreamThisRegion}, Call.getCFGElementRef(),
C.blockCount(), C.getStackFrame(),
- /*CausesPointerEscape=*/false,
- /*InvalidatedSymbols=*/nullptr,
- /*Call=*/nullptr,
- /*ITraits=*/nullptr, Cause);
+ /*CausesPointerEscape=*/false, Cause);
State = State->BindExpr(Call.getOriginExpr(), C.getStackFrame(), StreamVal);
C.addTransition(State);
return true;
diff --git a/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
index 13b9e8accb237..e32bdbc7944a3 100644
--- a/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
@@ -861,8 +861,8 @@ escapeByStartIndexAndCount(ProgramStateRef State, const CallEvent &Call,
return State->invalidateRegions(
EscapingVals, Call.getCFGElementRef(), BlockCount, SF,
/*CausesPointerEscape=*/false,
- /*InvalidatedSymbols=*/nullptr, &Call, &ITraits,
- Call.tryCreateInvalidationCause<PartiallyModeledCall>());
+ Call.tryCreateInvalidationCause<PartiallyModeledCall>(),
+ /*InvalidatedSymbols=*/nullptr, &Call, &ITraits);
}
static ProgramStateRef escapeArgs(ProgramStateRef State, CheckerContext &C,
@@ -873,7 +873,6 @@ static ProgramStateRef escapeArgs(ProgramStateRef State, CheckerContext &C,
State = State->invalidateRegions(
EscapingVals, Call.getCFGElementRef(), C.blockCount(), C.getStackFrame(),
/*CausesPointerEscape=*/false,
- /*InvalidatedSymbols=*/nullptr, /*Call=*/nullptr, /*ITraits=*/nullptr,
Call.tryCreateInvalidationCause<PartiallyModeledCall>());
return State;
}
diff --git a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp
index b2c0825b34dc5..d86ffa14dba4e 100644
--- a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp
+++ b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp
@@ -299,8 +299,8 @@ ProgramStateRef CallEvent::invalidateRegions(unsigned BlockCount,
return State->invalidateRegions(
ValuesToInvalidate, getCFGElementRef(), BlockCount, getStackFrame(),
/*CausedByPointerEscape*/ true,
- /*Symbols=*/nullptr, this, &ETraits,
- tryCreateInvalidationCause<ConservativeEvalCall>());
+ tryCreateInvalidationCause<ConservativeEvalCall>(),
+ /*Symbols=*/nullptr, this, &ETraits);
}
ProgramPoint CallEvent::getProgramPoint(bool IsPreVisit,
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
index dbcfaab5d2b0e..d08a2e001c94b 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -424,8 +424,7 @@ ProgramStateRef ExprEngine::createTemporaryRegionIfNeeded(
// FIXME: Unimplemented.
State = State->invalidateRegions(
Reg, getCFGElementRef(), getNumVisitedCurrent(), SF,
- /*CausesPointerEscape=*/true, /*InvalidatedSymbols=*/nullptr,
- /*Call=*/nullptr, /*ITraits=*/nullptr,
+ /*CausesPointerEscape=*/true,
getStateManager().getSymbolManager().acquireCause<UnmodeledExpr>(
InitWithAdjustments));
return State;
@@ -3448,7 +3447,6 @@ void ExprEngine::VisitAtomicExpr(const AtomicExpr *AE, ExplodedNode *Pred,
State = State->invalidateRegions(
ValuesToInvalidate, getCFGElementRef(), getNumVisitedCurrent(), SF,
/*CausedByPointerEscape*/ true,
- /*Symbols=*/nullptr, /*Call=*/nullptr, /*ITraits=*/nullptr,
getStateManager().getSymbolManager().acquireCause<UnmodeledExpr>(AE));
AfterInvalidateSet.insert(
@@ -3786,12 +3784,10 @@ void ExprEngine::VisitGCCAsmStmt(const GCCAsmStmt *A, ExplodedNode *Pred,
assert(!isa<NonLoc>(X)); // Should be an Lval, or unknown, undef.
if (std::optional<Loc> LV = X.getAs<Loc>())
- state = state->invalidateRegions(*LV, getCFGElementRef(),
- getNumVisitedCurrent(),
- Pred->getStackFrame(),
- /*CausedByPointerEscape=*/true,
- /*Symbols=*/nullptr, /*Call=*/nullptr,
- /*ITraits=*/nullptr, AsmCause);
+ state = state->invalidateRegions(
+ *LV, getCFGElementRef(), getNumVisitedCurrent(),
+ Pred->getStackFrame(),
+ /*CausedByPointerEscape=*/true, AsmCause);
}
// Do not reason about locations passed inside inline assembly.
@@ -3799,12 +3795,10 @@ void ExprEngine::VisitGCCAsmStmt(const GCCAsmStmt *A, ExplodedNode *Pred,
SVal X = state->getSVal(I, Pred->getStackFrame());
if (std::optional<Loc> LV = X.getAs<Loc>())
- state = state->invalidateRegions(*LV, getCFGElementRef(),
- getNumVisitedCurrent(),
- Pred->getStackFrame(),
- /*CausedByPointerEscape=*/true,
- /*Symbols=*/nullptr, /*Call=*/nullptr,
- /*ITraits=*/nullptr, AsmCause);
+ state = state->invalidateRegions(
+ *LV, getCFGElementRef(), getNumVisitedCurrent(),
+ Pred->getStackFrame(),
+ /*CausedByPointerEscape=*/true, AsmCause);
}
Dst.insert(Engine.makePostStmtNode(A, state, Pred));
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
index 1891c539a1377..40c80fefb7c8d 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
@@ -789,8 +789,9 @@ ProgramStateRef ExprEngine::bindReturnValue(const CallEvent &Call,
RegionAndSymbolInvalidationTraits::TK_DoNotInvalidateSuperRegion);
State = State->invalidateRegions(
TargetR, Elem, Count, SF,
- /* CausesPointerEscape=*/false, /*InvalidatedSymbols=*/nullptr, &Call,
- &ITraits, Call.tryCreateInvalidationCause<PartiallyModeledCall>());
+ /* CausesPointerEscape=*/false,
+ Call.tryCreateInvalidationCause<PartiallyModeledCall>(),
+ /*InvalidatedSymbols=*/nullptr, &Call, &ITraits);
R = State->getSVal(Target.castAs<Loc>(), E->getType());
} else {
diff --git a/clang/lib/StaticAnalyzer/Core/LoopWidening.cpp b/clang/lib/StaticAnalyzer/Core/LoopWidening.cpp
index 03594ea6c5a7f..62b635fb150ce 100644
--- a/clang/lib/StaticAnalyzer/Core/LoopWidening.cpp
+++ b/clang/lib/StaticAnalyzer/Core/LoopWidening.cpp
@@ -76,8 +76,8 @@ ProgramStateRef getWidenedLoopState(const Stmt *LoopStmt,
return PrevState->invalidateRegions(
Regions, Elem, BlockCount, SF, /*CausesPointerEscape=*/true,
- /*InvalidatedSymbols=*/nullptr, /*Call=*/nullptr, &ITraits,
- SymMgr.acquireCause<LoopWidening>(LoopStmt));
+ SymMgr.acquireCause<LoopWidening>(LoopStmt),
+ /*InvalidatedSymbols=*/nullptr, /*Call=*/nullptr, &ITraits);
}
} // end namespace ento
diff --git a/clang/lib/StaticAnalyzer/Core/ProgramState.cpp b/clang/lib/StaticAnalyzer/Core/ProgramState.cpp
index 2ae2c501c7fb8..727399eb68475 100644
--- a/clang/lib/StaticAnalyzer/Core/ProgramState.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ProgramState.cpp
@@ -145,22 +145,22 @@ typedef ArrayRef<SVal> ValueList;
ProgramStateRef ProgramState::invalidateRegions(
RegionList Regions, ConstCFGElementRef Elem, unsigned Count,
- const StackFrame *SF, bool CausedByPointerEscape, InvalidatedSymbols *IS,
- const CallEvent *Call, RegionAndSymbolInvalidationTraits *ITraits,
- const InvalidationCause *Cause) const {
+ const StackFrame *SF, bool CausedByPointerEscape,
+ const InvalidationCause *Cause, InvalidatedSymbols *IS,
+ const CallEvent *Call, RegionAndSymbolInvalidationTraits *ITraits) const {
SmallVector<SVal, 8> Values;
for (const MemRegion *Reg : Regions)
Values.push_back(loc::MemRegionVal(Reg));
- return invalidateRegions(Values, Elem, Count, SF, CausedByPointerEscape, IS,
- Call, ITraits, Cause);
+ return invalidateRegions(Values, Elem, Count, SF, CausedByPointerEscape,
+ Cause, IS, Call, ITraits);
}
ProgramStateRef ProgramState::invalidateRegions(
ValueList Values, ConstCFGElementRef Elem, unsigned Count,
- const StackFrame *SF, bool CausedByPointerEscape, InvalidatedSymbols *IS,
- const CallEvent *Call, RegionAndSymbolInvalidationTraits *ITraits,
- const InvalidationCause *Cause) const {
+ const StackFrame *SF, bool CausedByPointerEscape,
+ const InvalidationCause *Cause, InvalidatedSymbols *IS,
+ const CallEvent *Call, RegionAndSymbolInvalidationTraits *ITraits) const {
ProgramStateManager &Mgr = getStateManager();
ExprEngine &Eng = Mgr.getOwningEngine();
>From a32ed29027a8bfbba02f1b68f2d0e9b4597a3b04 Mon Sep 17 00:00:00 2001
From: Balazs Benics <benicsbalazs at gmail.com>
Date: Fri, 3 Jul 2026 15:51:42 +0100
Subject: [PATCH 6/6] [analyzer] Add invalidationHistory for easy iteration
This patchs adds the InvalidationHistoryIterator and exposes it from
SVal and SymExpr to have an easy access for iterating across the
`getPreviousSymbol()` chain from `SymbolInvalidationArtifacts`.
The idea is that every SymExpr is it's own single-element history.
If the symbol was an SymbolInvalidationArtifact, then the iterator will
go to the `getPreviousSymbol()` and present that symbol, and so on.
---
.../InvalidationHistoryIterator.h | 56 +++++++++++++++++++
.../StaticAnalyzer/Core/PathSensitive/SVals.h | 6 ++
.../Core/PathSensitive/SymExpr.h | 3 +
.../Checkers/ExprInspectionChecker.cpp | 16 ++++++
clang/lib/StaticAnalyzer/Core/SVals.cpp | 7 +++
.../lib/StaticAnalyzer/Core/SymbolManager.cpp | 22 +++++++-
...invalidation-artifact-dump-prev-chains.cpp | 28 ++++++++++
7 files changed, 136 insertions(+), 2 deletions(-)
create mode 100644 clang/include/clang/StaticAnalyzer/Core/PathSensitive/InvalidationHistoryIterator.h
create mode 100644 clang/test/Analysis/invalidation-artifact-dump-prev-chains.cpp
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/InvalidationHistoryIterator.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/InvalidationHistoryIterator.h
new file mode 100644
index 0000000000000..e4ab73658865f
--- /dev/null
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/InvalidationHistoryIterator.h
@@ -0,0 +1,56 @@
+//===- InvalidationHistoryIterator.h -----------------------------*- C++ -*-==//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_INVALIDATIONHISTORYITERATOR_H
+#define LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_INVALIDATIONHISTORYITERATOR_H
+
+#include <cassert>
+#include <cstddef>
+#include <iterator>
+
+namespace clang::ento {
+class SVal;
+class SymExpr;
+
+class InvalidationHistoryIterator {
+public:
+ InvalidationHistoryIterator() = default;
+ explicit InvalidationHistoryIterator(const SymExpr *Sym) : Curr(Sym) {}
+ using iterator_category = std::forward_iterator_tag;
+ using difference_type = std::ptrdiff_t;
+ using value_type = const SymExpr *;
+ using reference = const SymExpr *const &;
+ using pointer = const SymExpr *const *;
+
+ InvalidationHistoryIterator &operator++();
+
+ InvalidationHistoryIterator operator++(int) {
+ auto Tmp = *this;
+ ++*this;
+ return Tmp;
+ }
+
+ reference operator*() const {
+ assert(Curr && "Cannot dereference end iterator!");
+ return Curr;
+ }
+
+ bool operator==(InvalidationHistoryIterator Other) const {
+ return Curr == Other.Curr;
+ }
+ bool operator!=(InvalidationHistoryIterator Other) const {
+ return !(*this == Other);
+ }
+
+private:
+ const SymExpr *Curr = nullptr;
+};
+
+} // namespace clang::ento
+
+#endif // LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_INVALIDATIONHISTORYITERATOR_H
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h
index 0561a2b8d1d77..d3eb4b50bd514 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h
@@ -18,6 +18,7 @@
#include "clang/AST/Type.h"
#include "clang/Basic/LLVM.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/APSIntPtr.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/InvalidationHistoryIterator.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h"
#include "llvm/ADT/APSInt.h"
#include "llvm/ADT/FoldingSet.h"
@@ -153,6 +154,11 @@ class SVal {
const MemRegion *getAsRegion() const;
+ /// If this \c SVal is an \c SymbolInvalidationArtifact, it will enumerate the
+ /// history of this symbol through the chains of invalidations.
+ /// This enumeration starts with the current symbol (if any).
+ llvm::iterator_range<InvalidationHistoryIterator> invalidationHistory() const;
+
/// printJson - Pretty-prints in JSON format.
void printJson(raw_ostream &Out, bool AddQuotes) const;
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h
index 6233a22d2ca2b..08f24dab51ff4 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h
@@ -23,6 +23,7 @@
namespace clang {
namespace ento {
+class InvalidationHistoryIterator;
class MemRegion;
using SymbolID = unsigned;
@@ -108,6 +109,8 @@ class SymExpr : public llvm::FoldingSetNode {
return llvm::make_range(symbol_iterator(this), symbol_iterator());
}
+ llvm::iterator_range<InvalidationHistoryIterator> invalidationHistory() const;
+
virtual unsigned computeComplexity() const = 0;
/// Find the region from which this symbol originates.
diff --git a/clang/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp
index 8a5c205b6efba..f6d514cbefd1f 100644
--- a/clang/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp
@@ -49,6 +49,8 @@ class ExprInspectionChecker
void analyzerGetExtent(const CallExpr *CE, CheckerContext &C) const;
void analyzerDumpExtent(const CallExpr *CE, CheckerContext &C) const;
void analyzerDumpElementCount(const CallExpr *CE, CheckerContext &C) const;
+ void analyzerDumpInvalidationHistory(const CallExpr *CE,
+ CheckerContext &C) const;
void analyzerHashDump(const CallExpr *CE, CheckerContext &C) const;
void analyzerDenote(const CallExpr *CE, CheckerContext &C) const;
void analyzerExpress(const CallExpr *CE, CheckerContext &C) const;
@@ -102,6 +104,8 @@ bool ExprInspectionChecker::evalCall(const CallEvent &Call,
&ExprInspectionChecker::analyzerDumpExtent)
.Case("clang_analyzer_dumpElementCount",
&ExprInspectionChecker::analyzerDumpElementCount)
+ .Case("clang_analyzer_dumpInvalidationHistory",
+ &ExprInspectionChecker::analyzerDumpInvalidationHistory)
.Case("clang_analyzer_value", &ExprInspectionChecker::analyzerValue)
.StartsWith("clang_analyzer_dumpSvalType",
&ExprInspectionChecker::analyzerDumpSValType)
@@ -364,6 +368,18 @@ void ExprInspectionChecker::analyzerDumpElementCount(const CallExpr *CE,
printAndReport(C, ElementCount);
}
+void ExprInspectionChecker::analyzerDumpInvalidationHistory(
+ const CallExpr *CE, CheckerContext &C) const {
+ SVal ArgVal = C.getSVal(CE->getArg(0));
+ std::string Msg;
+ llvm::raw_string_ostream OS{Msg};
+ llvm::interleave(ArgVal.invalidationHistory(), OS,
+ /*separator=*/" -> ");
+ if (Msg.empty())
+ Msg = "<empty>";
+ reportBug(Msg, C);
+}
+
void ExprInspectionChecker::analyzerPrintState(const CallExpr *CE,
CheckerContext &C) const {
C.getState()->dump();
diff --git a/clang/lib/StaticAnalyzer/Core/SVals.cpp b/clang/lib/StaticAnalyzer/Core/SVals.cpp
index 483e62d4a9a7e..87408862d860e 100644
--- a/clang/lib/StaticAnalyzer/Core/SVals.cpp
+++ b/clang/lib/StaticAnalyzer/Core/SVals.cpp
@@ -126,6 +126,13 @@ const MemRegion *SVal::getAsRegion() const {
return nullptr;
}
+llvm::iterator_range<InvalidationHistoryIterator>
+SVal::invalidationHistory() const {
+ if (const auto *Sym = getAsSymbol())
+ return Sym->invalidationHistory();
+ return {{}, {}};
+}
+
namespace {
class TypeRetrievingVisitor
: public FullSValVisitor<TypeRetrievingVisitor, QualType> {
diff --git a/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp b/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp
index c7e10e1744e77..9a1b731da390a 100644
--- a/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp
+++ b/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp
@@ -78,6 +78,18 @@ void UnarySymExpr::dumpToStream(raw_ostream &os) const {
os << ')';
}
+InvalidationHistoryIterator &InvalidationHistoryIterator::operator++() {
+ assert(Curr && "Cannot dereference end iterator!");
+ const auto *Artifact = dyn_cast<SymbolInvalidationArtifact>(Curr);
+ Curr = Artifact ? Artifact->getPreviousSymbol() : nullptr;
+ return *this;
+}
+
+llvm::iterator_range<InvalidationHistoryIterator>
+SymExpr::invalidationHistory() const {
+ return {InvalidationHistoryIterator{this}, {}};
+}
+
const Stmt *SymbolConjured::getStmt() const {
// Sometimes the CFG element is invalid, avoid dereferencing it.
if (Elem.getParent() == nullptr ||
@@ -143,8 +155,14 @@ void SymbolInvalidationArtifact::dumpToStream(raw_ostream &os) const {
os << ", S" << US->getStmt()->getID(SF->getDecl()->getASTContext());
}
- if (PreviousSym)
- os << ", prev=" << PreviousSym;
+ if (PreviousSym) {
+ // Avoid recursively printing the whole prev-chain.
+ if (const auto *Data = dyn_cast<SymbolInvalidationArtifact>(PreviousSym))
+ os << ", prev=" << Data->getKindStr() << Data->getSymbolID();
+ else
+ os << ", prev=" << PreviousSym;
+ }
+
os << ", #" << Count << '}';
}
diff --git a/clang/test/Analysis/invalidation-artifact-dump-prev-chains.cpp b/clang/test/Analysis/invalidation-artifact-dump-prev-chains.cpp
new file mode 100644
index 0000000000000..b872476ce7e40
--- /dev/null
+++ b/clang/test/Analysis/invalidation-artifact-dump-prev-chains.cpp
@@ -0,0 +1,28 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -verify %s
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection %s 2>&1 | FileCheck %s
+
+template <class... Ts>
+void escape(Ts&...);
+void clang_analyzer_dump(int);
+void clang_analyzer_dumpInvalidationHistory(int);
+
+void escapeParam(int param) {
+ clang_analyzer_dump(param); // expected-warning {{reg_$}}
+ clang_analyzer_dumpInvalidationHistory(param); // expected-warning {{reg_$}}
+ // CHECK: chains.cpp:[[@LINE-2]]:3: warning: reg_$[[ID1:[0-9]+]]<int param> [debug.ExprInspection]
+ // CHECK: chains.cpp:[[@LINE-2]]:3: warning: reg_$[[ID1:[0-9]+]]<int param> [debug.ExprInspection]
+
+ escape(param);
+
+ clang_analyzer_dump(param); // expected-warning {{inv_$}}
+ clang_analyzer_dumpInvalidationHistory(param); // expected-warning-re {{{{inv_\$.+ -> reg_\$.+}}}}
+ // CHECK: chains.cpp:[[@LINE-2]]:3: warning: inv_$[[ID2:[0-9]+]]{int, LC[[#]], conservative-call, S[[#]], prev=reg_$[[ID1]]<int param>, #[[#]]} [debug.ExprInspection]
+ // CHECK: chains.cpp:[[@LINE-2]]:3: warning: inv_$[[ID2:[0-9]+]]{int, LC[[#]], conservative-call, S[[#]], prev=reg_$[[ID1]]<int param>, #[[#]]} -> reg_$[[ID1]]<int param> [debug.ExprInspection]
+
+ escape(param);
+
+ clang_analyzer_dump(param); // expected-warning {{inv_$}}
+ clang_analyzer_dumpInvalidationHistory(param); // expected-warning-re {{{{inv_\$.+ -> inv_\$.+ -> reg_\$.+}}}}
+ // CHECK: chains.cpp:[[@LINE-2]]:3: warning: inv_$[[ID3:[0-9]+]]{int, LC[[#]], conservative-call, S[[#]], prev=inv_$[[ID2]], #[[#]]} [debug.ExprInspection]
+ // CHECK: chains.cpp:[[@LINE-2]]:3: warning: inv_$[[ID3:[0-9]+]]{int, LC[[#]], conservative-call, S[[#]], prev=inv_$[[ID2]], #[[#]]} -> inv_$[[ID2:[0-9]+]]{int, LC[[#]], conservative-call, S[[#]], prev=reg_$[[ID1]]<int param>, #[[#]]} -> reg_$[[ID1]]<int param> [debug.ExprInspection]
+}
More information about the cfe-commits
mailing list