[clang][analyzer] Fix a nullptr dereference when -ftime-trace is used (Reland) (#139980)
Fixes #139779.
The bug was introduced in #137355 in `SymbolConjured::getStmt`, when
trying to obtain a statement for a CFG initializer without an
initializer. This commit adds a null check before access.
Previous PR #139820, Revert #139936
Additional notes since previous PR:
When conjuring a symbol, sometimes there is no valid CFG element, e.g.
in the file causing the crash, there is no element at all in the CFG. In
these cases, the CFG element reference in the expression engine will be
invalid. As a consequence, there needs to be extra checks to ensure the
validity of the CFG element reference.
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h
index 9e7c98f..86774ad 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h
@@ -100,41 +100,7 @@
ConstCFGElementRef getCFGElementRef() const { return Elem; }
// It might return null.
- const Stmt *getStmt() const {
- switch (Elem->getKind()) {
- case CFGElement::Initializer:
- return Elem->castAs<CFGInitializer>().getInitializer()->getInit();
- case CFGElement::ScopeBegin:
- return Elem->castAs<CFGScopeBegin>().getTriggerStmt();
- case CFGElement::ScopeEnd:
- return Elem->castAs<CFGScopeEnd>().getTriggerStmt();
- case CFGElement::NewAllocator:
- return Elem->castAs<CFGNewAllocator>().getAllocatorExpr();
- case CFGElement::LifetimeEnds:
- return Elem->castAs<CFGLifetimeEnds>().getTriggerStmt();
- case CFGElement::LoopExit:
- return Elem->castAs<CFGLoopExit>().getLoopStmt();
- case CFGElement::Statement:
- return Elem->castAs<CFGStmt>().getStmt();
- case CFGElement::Constructor:
- return Elem->castAs<CFGConstructor>().getStmt();
- case CFGElement::CXXRecordTypedCall:
- return Elem->castAs<CFGCXXRecordTypedCall>().getStmt();
- case CFGElement::AutomaticObjectDtor:
- return Elem->castAs<CFGAutomaticObjDtor>().getTriggerStmt();
- case CFGElement::DeleteDtor:
- return Elem->castAs<CFGDeleteDtor>().getDeleteExpr();
- case CFGElement::BaseDtor:
- return nullptr;
- case CFGElement::MemberDtor:
- return nullptr;
- case CFGElement::TemporaryDtor:
- return Elem->castAs<CFGTemporaryDtor>().getBindTemporaryExpr();
- case CFGElement::CleanupFunction:
- return nullptr;
- }
- return nullptr;
- }
+ const Stmt *getStmt() const;
unsigned getCount() const { return Count; }
/// It might return null.
diff --git a/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp b/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp
index a6ade66..a469df4 100644
--- a/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp
+++ b/clang/lib/StaticAnalyzer/Core/SymbolManager.cpp
@@ -80,6 +80,49 @@
os << ')';
}
+const Stmt *SymbolConjured::getStmt() const {
+ // Sometimes the CFG element is invalid, avoid dereferencing it.
+ if (Elem.getParent() == nullptr ||
+ Elem.getIndexInBlock() >= Elem.getParent()->size())
+ return nullptr;
+ switch (Elem->getKind()) {
+ case CFGElement::Initializer:
+ if (const auto *Init = Elem->castAs<CFGInitializer>().getInitializer()) {
+ return Init->getInit();
+ }
+ return nullptr;
+ case CFGElement::ScopeBegin:
+ return Elem->castAs<CFGScopeBegin>().getTriggerStmt();
+ case CFGElement::ScopeEnd:
+ return Elem->castAs<CFGScopeEnd>().getTriggerStmt();
+ case CFGElement::NewAllocator:
+ return Elem->castAs<CFGNewAllocator>().getAllocatorExpr();
+ case CFGElement::LifetimeEnds:
+ return Elem->castAs<CFGLifetimeEnds>().getTriggerStmt();
+ case CFGElement::LoopExit:
+ return Elem->castAs<CFGLoopExit>().getLoopStmt();
+ case CFGElement::Statement:
+ return Elem->castAs<CFGStmt>().getStmt();
+ case CFGElement::Constructor:
+ return Elem->castAs<CFGConstructor>().getStmt();
+ case CFGElement::CXXRecordTypedCall:
+ return Elem->castAs<CFGCXXRecordTypedCall>().getStmt();
+ case CFGElement::AutomaticObjectDtor:
+ return Elem->castAs<CFGAutomaticObjDtor>().getTriggerStmt();
+ case CFGElement::DeleteDtor:
+ return Elem->castAs<CFGDeleteDtor>().getDeleteExpr();
+ case CFGElement::BaseDtor:
+ return nullptr;
+ case CFGElement::MemberDtor:
+ return nullptr;
+ case CFGElement::TemporaryDtor:
+ return Elem->castAs<CFGTemporaryDtor>().getBindTemporaryExpr();
+ case CFGElement::CleanupFunction:
+ return nullptr;
+ }
+ return nullptr;
+}
+
void SymbolConjured::dumpToStream(raw_ostream &os) const {
os << getKindStr() << getSymbolID() << '{' << T << ", LC" << LCtx->getID();
if (auto *S = getStmt())
diff --git a/clang/test/Analysis/ftime-trace-no-init.cpp b/clang/test/Analysis/ftime-trace-no-init.cpp
new file mode 100644
index 0000000..7fb289b
--- /dev/null
+++ b/clang/test/Analysis/ftime-trace-no-init.cpp
@@ -0,0 +1,5 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=core,apiModeling %s -ftime-trace=%t.raw.json -verify
+// expected-no-diagnostics
+
+// GitHub issue 139779
+struct {} a; // no-crash