aboutsummaryrefslogtreecommitdiff
path: root/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp')
-rw-r--r--contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp995
1 files changed, 825 insertions, 170 deletions
diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
index 66332d3b848c..144f034a9dfe 100644
--- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -7,7 +7,7 @@
//===----------------------------------------------------------------------===//
//
// This file defines a meta-engine for path-sensitive dataflow analysis that
-// is built on GREngine, but provides the boilerplate to execute transfer
+// is built on CoreEngine, but provides the boilerplate to execute transfer
// functions and build the ExplodedGraph at the expression level.
//
//===----------------------------------------------------------------------===//
@@ -48,6 +48,7 @@
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ConstraintManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/LoopUnrolling.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/LoopWidening.h"
@@ -64,7 +65,7 @@
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/ImmutableMap.h"
#include "llvm/ADT/ImmutableSet.h"
-#include "llvm/ADT/Optional.h"
+#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/Support/Casting.h"
@@ -77,6 +78,7 @@
#include <cassert>
#include <cstdint>
#include <memory>
+#include <optional>
#include <string>
#include <tuple>
#include <utility>
@@ -118,18 +120,10 @@ namespace {
/// the construction context was present and contained references to these
/// AST nodes.
class ConstructedObjectKey {
- typedef std::pair<ConstructionContextItem, const LocationContext *>
- ConstructedObjectKeyImpl;
-
+ using ConstructedObjectKeyImpl =
+ std::pair<ConstructionContextItem, const LocationContext *>;
const ConstructedObjectKeyImpl Impl;
- const void *getAnyASTNodePtr() const {
- if (const Stmt *S = getItem().getStmtOrNull())
- return S;
- else
- return getItem().getCXXCtorInitializer();
- }
-
public:
explicit ConstructedObjectKey(const ConstructionContextItem &Item,
const LocationContext *LC)
@@ -193,6 +187,31 @@ typedef llvm::ImmutableMap<ConstructedObjectKey, SVal>
REGISTER_TRAIT_WITH_PROGRAMSTATE(ObjectsUnderConstruction,
ObjectsUnderConstructionMap)
+// This trait is responsible for storing the index of the element that is to be
+// constructed in the next iteration. As a result a CXXConstructExpr is only
+// stored if it is array type. Also the index is the index of the continuous
+// memory region, which is important for multi-dimensional arrays. E.g:: int
+// arr[2][2]; assume arr[1][1] will be the next element under construction, so
+// the index is 3.
+typedef llvm::ImmutableMap<
+ std::pair<const CXXConstructExpr *, const LocationContext *>, unsigned>
+ IndexOfElementToConstructMap;
+REGISTER_TRAIT_WITH_PROGRAMSTATE(IndexOfElementToConstruct,
+ IndexOfElementToConstructMap)
+
+// This trait is responsible for holding our pending ArrayInitLoopExprs.
+// It pairs the LocationContext and the initializer CXXConstructExpr with
+// the size of the array that's being copy initialized.
+typedef llvm::ImmutableMap<
+ std::pair<const CXXConstructExpr *, const LocationContext *>, unsigned>
+ PendingInitLoopMap;
+REGISTER_TRAIT_WITH_PROGRAMSTATE(PendingInitLoop, PendingInitLoopMap)
+
+typedef llvm::ImmutableMap<const LocationContext *, unsigned>
+ PendingArrayDestructionMap;
+REGISTER_TRAIT_WITH_PROGRAMSTATE(PendingArrayDestruction,
+ PendingArrayDestructionMap)
+
//===----------------------------------------------------------------------===//
// Engine construction and deletion.
//===----------------------------------------------------------------------===//
@@ -200,24 +219,17 @@ REGISTER_TRAIT_WITH_PROGRAMSTATE(ObjectsUnderConstruction,
static const char* TagProviderName = "ExprEngine";
ExprEngine::ExprEngine(cross_tu::CrossTranslationUnitContext &CTU,
- AnalysisManager &mgr,
- SetOfConstDecls *VisitedCalleesIn,
- FunctionSummariesTy *FS,
- InliningModes HowToInlineIn)
- : CTU(CTU), AMgr(mgr),
- AnalysisDeclContexts(mgr.getAnalysisDeclContextManager()),
+ AnalysisManager &mgr, SetOfConstDecls *VisitedCalleesIn,
+ FunctionSummariesTy *FS, InliningModes HowToInlineIn)
+ : CTU(CTU), IsCTUEnabled(mgr.getAnalyzerOptions().IsNaiveCTUEnabled),
+ AMgr(mgr), AnalysisDeclContexts(mgr.getAnalysisDeclContextManager()),
Engine(*this, FS, mgr.getAnalyzerOptions()), G(Engine.getGraph()),
StateMgr(getContext(), mgr.getStoreManagerCreator(),
- mgr.getConstraintManagerCreator(), G.getAllocator(),
- this),
- SymMgr(StateMgr.getSymbolManager()),
- MRMgr(StateMgr.getRegionManager()),
- svalBuilder(StateMgr.getSValBuilder()),
- ObjCNoRet(mgr.getASTContext()),
- BR(mgr, *this),
- VisitedCallees(VisitedCalleesIn),
- HowToInline(HowToInlineIn)
- {
+ mgr.getConstraintManagerCreator(), G.getAllocator(), this),
+ SymMgr(StateMgr.getSymbolManager()), MRMgr(StateMgr.getRegionManager()),
+ svalBuilder(StateMgr.getSValBuilder()), ObjCNoRet(mgr.getASTContext()),
+ BR(mgr, *this), VisitedCallees(VisitedCalleesIn),
+ HowToInline(HowToInlineIn) {
unsigned TrimInterval = mgr.options.GraphTrimInterval;
if (TrimInterval != 0) {
// Enable eager node reclamation when constructing the ExplodedGraph.
@@ -259,7 +271,7 @@ ProgramStateRef ExprEngine::getInitialState(const LocationContext *InitLoc) {
svalBuilder.makeZeroVal(T),
svalBuilder.getConditionType());
- Optional<DefinedOrUnknownSVal> Constraint =
+ std::optional<DefinedOrUnknownSVal> Constraint =
Constraint_untested.getAs<DefinedOrUnknownSVal>();
if (!Constraint)
@@ -279,7 +291,7 @@ ProgramStateRef ExprEngine::getInitialState(const LocationContext *InitLoc) {
const MemRegion *R = state->getRegion(SelfD, InitLoc);
SVal V = state->getSVal(loc::MemRegionVal(R));
- if (Optional<Loc> LV = V.getAs<Loc>()) {
+ if (std::optional<Loc> LV = V.getAs<Loc>()) {
// Assume that the pointer value in 'self' is non-null.
state = state->assume(*LV, true);
assert(state && "'self' cannot be null");
@@ -295,7 +307,7 @@ ProgramStateRef ExprEngine::getInitialState(const LocationContext *InitLoc) {
if (SFC->getParent() == nullptr) {
loc::MemRegionVal L = svalBuilder.getCXXThis(MD, SFC);
SVal V = state->getSVal(L);
- if (Optional<Loc> LV = V.getAs<Loc>()) {
+ if (std::optional<Loc> LV = V.getAs<Loc>()) {
state = state->assume(*LV, true);
assert(state && "'this' cannot be null");
}
@@ -319,16 +331,16 @@ ProgramStateRef ExprEngine::createTemporaryRegionIfNeeded(
if (!Result) {
// If we don't have an explicit result expression, we're in "if needed"
// mode. Only create a region if the current value is a NonLoc.
- if (!InitValWithAdjustments.getAs<NonLoc>()) {
+ if (!isa<NonLoc>(InitValWithAdjustments)) {
if (OutRegionWithAdjustments)
*OutRegionWithAdjustments = nullptr;
return State;
}
Result = InitWithAdjustments;
} else {
- // We need to create a region no matter what. For sanity, make sure we don't
- // try to stuff a Loc into a non-pointer temporary region.
- assert(!InitValWithAdjustments.getAs<Loc>() ||
+ // We need to create a region no matter what. Make sure we don't try to
+ // stuff a Loc into a non-pointer temporary region.
+ assert(!isa<Loc>(InitValWithAdjustments) ||
Loc::isLocType(Result->getType()) ||
Result->getType()->isMemberPointerType());
}
@@ -371,19 +383,23 @@ ProgramStateRef ExprEngine::createTemporaryRegionIfNeeded(
// into that region. This is not correct, but it is better than nothing.
const TypedValueRegion *TR = nullptr;
if (const auto *MT = dyn_cast<MaterializeTemporaryExpr>(Result)) {
- if (Optional<SVal> V = getObjectUnderConstruction(State, MT, LC)) {
+ if (std::optional<SVal> V = getObjectUnderConstruction(State, MT, LC)) {
State = finishObjectConstruction(State, MT, LC);
State = State->BindExpr(Result, LC, *V);
return State;
- } else {
+ } else if (const ValueDecl *VD = MT->getExtendingDecl()) {
StorageDuration SD = MT->getStorageDuration();
+ assert(SD != SD_FullExpression);
// If this object is bound to a reference with static storage duration, we
// put it in a different region to prevent "address leakage" warnings.
if (SD == SD_Static || SD == SD_Thread) {
- TR = MRMgr.getCXXStaticTempObjectRegion(Init);
+ TR = MRMgr.getCXXStaticLifetimeExtendedObjectRegion(Init, VD);
} else {
- TR = MRMgr.getCXXTempObjectRegion(Init, LC);
+ TR = MRMgr.getCXXLifetimeExtendedObjectRegion(Init, VD, LC);
}
+ } else {
+ assert(MT->getStorageDuration() == SD_FullExpression);
+ TR = MRMgr.getCXXTempObjectRegion(Init, LC);
}
} else {
TR = MRMgr.getCXXTempObjectRegion(Init, LC);
@@ -393,8 +409,7 @@ ProgramStateRef ExprEngine::createTemporaryRegionIfNeeded(
SVal BaseReg = Reg;
// Make the necessary adjustments to obtain the sub-object.
- for (auto I = Adjustments.rbegin(), E = Adjustments.rend(); I != E; ++I) {
- const SubobjectAdjustment &Adj = *I;
+ for (const SubobjectAdjustment &Adj : llvm::reverse(Adjustments)) {
switch (Adj.Kind) {
case SubobjectAdjustment::DerivedToBaseAdjustment:
Reg = StoreMgr.evalDerivedToBase(Reg, Adj.DerivedToBase.BasePath);
@@ -457,25 +472,140 @@ ProgramStateRef ExprEngine::createTemporaryRegionIfNeeded(
return State;
}
+ProgramStateRef ExprEngine::setIndexOfElementToConstruct(
+ ProgramStateRef State, const CXXConstructExpr *E,
+ const LocationContext *LCtx, unsigned Idx) {
+ auto Key = std::make_pair(E, LCtx->getStackFrame());
+
+ assert(!State->contains<IndexOfElementToConstruct>(Key) || Idx > 0);
+
+ return State->set<IndexOfElementToConstruct>(Key, Idx);
+}
+
+std::optional<unsigned>
+ExprEngine::getPendingInitLoop(ProgramStateRef State, const CXXConstructExpr *E,
+ const LocationContext *LCtx) {
+ const unsigned *V = State->get<PendingInitLoop>({E, LCtx->getStackFrame()});
+ return V ? std::make_optional(*V) : std::nullopt;
+}
+
+ProgramStateRef ExprEngine::removePendingInitLoop(ProgramStateRef State,
+ const CXXConstructExpr *E,
+ const LocationContext *LCtx) {
+ auto Key = std::make_pair(E, LCtx->getStackFrame());
+
+ assert(E && State->contains<PendingInitLoop>(Key));
+ return State->remove<PendingInitLoop>(Key);
+}
+
+ProgramStateRef ExprEngine::setPendingInitLoop(ProgramStateRef State,
+ const CXXConstructExpr *E,
+ const LocationContext *LCtx,
+ unsigned Size) {
+ auto Key = std::make_pair(E, LCtx->getStackFrame());
+
+ assert(!State->contains<PendingInitLoop>(Key) && Size > 0);
+
+ return State->set<PendingInitLoop>(Key, Size);
+}
+
+std::optional<unsigned>
+ExprEngine::getIndexOfElementToConstruct(ProgramStateRef State,
+ const CXXConstructExpr *E,
+ const LocationContext *LCtx) {
+ const unsigned *V =
+ State->get<IndexOfElementToConstruct>({E, LCtx->getStackFrame()});
+ return V ? std::make_optional(*V) : std::nullopt;
+}
+
+ProgramStateRef
+ExprEngine::removeIndexOfElementToConstruct(ProgramStateRef State,
+ const CXXConstructExpr *E,
+ const LocationContext *LCtx) {
+ auto Key = std::make_pair(E, LCtx->getStackFrame());
+
+ assert(E && State->contains<IndexOfElementToConstruct>(Key));
+ return State->remove<IndexOfElementToConstruct>(Key);
+}
+
+std::optional<unsigned>
+ExprEngine::getPendingArrayDestruction(ProgramStateRef State,
+ const LocationContext *LCtx) {
+ assert(LCtx && "LocationContext shouldn't be null!");
+
+ const unsigned *V =
+ State->get<PendingArrayDestruction>(LCtx->getStackFrame());
+ return V ? std::make_optional(*V) : std::nullopt;
+}
+
+ProgramStateRef ExprEngine::setPendingArrayDestruction(
+ ProgramStateRef State, const LocationContext *LCtx, unsigned Idx) {
+ assert(LCtx && "LocationContext shouldn't be null!");
+
+ auto Key = LCtx->getStackFrame();
+
+ return State->set<PendingArrayDestruction>(Key, Idx);
+}
+
+ProgramStateRef
+ExprEngine::removePendingArrayDestruction(ProgramStateRef State,
+ const LocationContext *LCtx) {
+ assert(LCtx && "LocationContext shouldn't be null!");
+
+ auto Key = LCtx->getStackFrame();
+
+ assert(LCtx && State->contains<PendingArrayDestruction>(Key));
+ return State->remove<PendingArrayDestruction>(Key);
+}
+
ProgramStateRef
ExprEngine::addObjectUnderConstruction(ProgramStateRef State,
const ConstructionContextItem &Item,
const LocationContext *LC, SVal V) {
ConstructedObjectKey Key(Item, LC->getStackFrame());
+
+ const Expr *Init = nullptr;
+
+ if (auto DS = dyn_cast_or_null<DeclStmt>(Item.getStmtOrNull())) {
+ if (auto VD = dyn_cast_or_null<VarDecl>(DS->getSingleDecl()))
+ Init = VD->getInit();
+ }
+
+ if (auto LE = dyn_cast_or_null<LambdaExpr>(Item.getStmtOrNull()))
+ Init = *(LE->capture_init_begin() + Item.getIndex());
+
+ if (!Init && !Item.getStmtOrNull())
+ Init = Item.getCXXCtorInitializer()->getInit();
+
+ // In an ArrayInitLoopExpr the real initializer is returned by
+ // getSubExpr(). Note that AILEs can be nested in case of
+ // multidimesnional arrays.
+ if (const auto *AILE = dyn_cast_or_null<ArrayInitLoopExpr>(Init))
+ Init = extractElementInitializerFromNestedAILE(AILE);
+
// FIXME: Currently the state might already contain the marker due to
// incorrect handling of temporaries bound to default parameters.
- assert(!State->get<ObjectsUnderConstruction>(Key) ||
- Key.getItem().getKind() ==
- ConstructionContextItem::TemporaryDestructorKind);
+ // The state will already contain the marker if we construct elements
+ // in an array, as we visit the same statement multiple times before
+ // the array declaration. The marker is removed when we exit the
+ // constructor call.
+ assert((!State->get<ObjectsUnderConstruction>(Key) ||
+ Key.getItem().getKind() ==
+ ConstructionContextItem::TemporaryDestructorKind ||
+ State->contains<IndexOfElementToConstruct>(
+ {dyn_cast_or_null<CXXConstructExpr>(Init), LC})) &&
+ "The object is already marked as `UnderConstruction`, when it's not "
+ "supposed to!");
return State->set<ObjectsUnderConstruction>(Key, V);
}
-Optional<SVal>
+std::optional<SVal>
ExprEngine::getObjectUnderConstruction(ProgramStateRef State,
const ConstructionContextItem &Item,
const LocationContext *LC) {
ConstructedObjectKey Key(Item, LC->getStackFrame());
- return Optional<SVal>::create(State->get<ObjectsUnderConstruction>(Key));
+ const SVal *V = State->get<ObjectsUnderConstruction>(Key);
+ return V ? std::make_optional(*V) : std::nullopt;
}
ProgramStateRef
@@ -569,7 +699,7 @@ printObjectsUnderConstructionJson(raw_ostream &Out, ProgramStateRef State,
continue;
if (!HasItem) {
- Out << "[" << NL;
+ Out << '[' << NL;
HasItem = true;
}
@@ -598,29 +728,238 @@ printObjectsUnderConstructionJson(raw_ostream &Out, ProgramStateRef State,
}
}
-void ExprEngine::printJson(raw_ostream &Out, ProgramStateRef State,
- const LocationContext *LCtx, const char *NL,
- unsigned int Space, bool IsDot) const {
- Indent(Out, Space, IsDot) << "\"constructing_objects\": ";
+static void printIndicesOfElementsToConstructJson(
+ raw_ostream &Out, ProgramStateRef State, const char *NL,
+ const LocationContext *LCtx, unsigned int Space = 0, bool IsDot = false) {
+ using KeyT = std::pair<const Expr *, const LocationContext *>;
+
+ const auto &Context = LCtx->getAnalysisDeclContext()->getASTContext();
+ PrintingPolicy PP = Context.getPrintingPolicy();
+
+ ++Space;
+ bool HasItem = false;
+
+ // Store the last key.
+ KeyT LastKey;
+ for (const auto &I : State->get<IndexOfElementToConstruct>()) {
+ const KeyT &Key = I.first;
+ if (Key.second != LCtx)
+ continue;
+
+ if (!HasItem) {
+ Out << '[' << NL;
+ HasItem = true;
+ }
+
+ LastKey = Key;
+ }
+
+ for (const auto &I : State->get<IndexOfElementToConstruct>()) {
+ const KeyT &Key = I.first;
+ unsigned Value = I.second;
+ if (Key.second != LCtx)
+ continue;
+
+ Indent(Out, Space, IsDot) << "{ ";
+
+ // Expr
+ const Expr *E = Key.first;
+ Out << "\"stmt_id\": " << E->getID(Context);
+
+ // Kind
+ Out << ", \"kind\": null";
+
+ // Pretty-print
+ Out << ", \"pretty\": ";
+ Out << "\"" << E->getStmtClassName() << ' '
+ << E->getSourceRange().printToString(Context.getSourceManager()) << " '"
+ << QualType::getAsString(E->getType().split(), PP);
+ Out << "'\"";
+
+ Out << ", \"value\": \"Current index: " << Value - 1 << "\" }";
+
+ if (Key != LastKey)
+ Out << ',';
+ Out << NL;
+ }
+
+ if (HasItem)
+ Indent(Out, --Space, IsDot) << ']'; // End of "location_context".
+ else {
+ Out << "null ";
+ }
+}
+
+static void printPendingInitLoopJson(raw_ostream &Out, ProgramStateRef State,
+ const char *NL,
+ const LocationContext *LCtx,
+ unsigned int Space = 0,
+ bool IsDot = false) {
+ using KeyT = std::pair<const CXXConstructExpr *, const LocationContext *>;
+
+ const auto &Context = LCtx->getAnalysisDeclContext()->getASTContext();
+ PrintingPolicy PP = Context.getPrintingPolicy();
+
+ ++Space;
+ bool HasItem = false;
+
+ // Store the last key.
+ KeyT LastKey;
+ for (const auto &I : State->get<PendingInitLoop>()) {
+ const KeyT &Key = I.first;
+ if (Key.second != LCtx)
+ continue;
+
+ if (!HasItem) {
+ Out << '[' << NL;
+ HasItem = true;
+ }
+
+ LastKey = Key;
+ }
+
+ for (const auto &I : State->get<PendingInitLoop>()) {
+ const KeyT &Key = I.first;
+ unsigned Value = I.second;
+ if (Key.second != LCtx)
+ continue;
+
+ Indent(Out, Space, IsDot) << "{ ";
+
+ const CXXConstructExpr *E = Key.first;
+ Out << "\"stmt_id\": " << E->getID(Context);
+
+ Out << ", \"kind\": null";
+ Out << ", \"pretty\": ";
+ Out << '\"' << E->getStmtClassName() << ' '
+ << E->getSourceRange().printToString(Context.getSourceManager()) << " '"
+ << QualType::getAsString(E->getType().split(), PP);
+ Out << "'\"";
+
+ Out << ", \"value\": \"Flattened size: " << Value << "\"}";
+
+ if (Key != LastKey)
+ Out << ',';
+ Out << NL;
+ }
+
+ if (HasItem)
+ Indent(Out, --Space, IsDot) << ']'; // End of "location_context".
+ else {
+ Out << "null ";
+ }
+}
+
+static void
+printPendingArrayDestructionsJson(raw_ostream &Out, ProgramStateRef State,
+ const char *NL, const LocationContext *LCtx,
+ unsigned int Space = 0, bool IsDot = false) {
+ using KeyT = const LocationContext *;
+
+ ++Space;
+ bool HasItem = false;
+
+ // Store the last key.
+ KeyT LastKey = nullptr;
+ for (const auto &I : State->get<PendingArrayDestruction>()) {
+ const KeyT &Key = I.first;
+ if (Key != LCtx)
+ continue;
+
+ if (!HasItem) {
+ Out << '[' << NL;
+ HasItem = true;
+ }
+
+ LastKey = Key;
+ }
+
+ for (const auto &I : State->get<PendingArrayDestruction>()) {
+ const KeyT &Key = I.first;
+ if (Key != LCtx)
+ continue;
+
+ Indent(Out, Space, IsDot) << "{ ";
+
+ Out << "\"stmt_id\": null";
+ Out << ", \"kind\": null";
+ Out << ", \"pretty\": \"Current index: \"";
+ Out << ", \"value\": \"" << I.second << "\" }";
- if (LCtx && !State->get<ObjectsUnderConstruction>().isEmpty()) {
+ if (Key != LastKey)
+ Out << ',';
+ Out << NL;
+ }
+
+ if (HasItem)
+ Indent(Out, --Space, IsDot) << ']'; // End of "location_context".
+ else {
+ Out << "null ";
+ }
+}
+
+/// A helper function to generalize program state trait printing.
+/// The function invokes Printer as 'Printer(Out, State, NL, LC, Space, IsDot,
+/// std::forward<Args>(args)...)'. \n One possible type for Printer is
+/// 'void()(raw_ostream &, ProgramStateRef, const char *, const LocationContext
+/// *, unsigned int, bool, ...)' \n \param Trait The state trait to be printed.
+/// \param Printer A void function that prints Trait.
+/// \param Args An additional parameter pack that is passed to Print upon
+/// invocation.
+template <typename Trait, typename Printer, typename... Args>
+static void printStateTraitWithLocationContextJson(
+ raw_ostream &Out, ProgramStateRef State, const LocationContext *LCtx,
+ const char *NL, unsigned int Space, bool IsDot,
+ const char *jsonPropertyName, Printer printer, Args &&...args) {
+
+ using RequiredType =
+ void (*)(raw_ostream &, ProgramStateRef, const char *,
+ const LocationContext *, unsigned int, bool, Args &&...);
+
+ // Try to do as much compile time checking as possible.
+ // FIXME: check for invocable instead of function?
+ static_assert(std::is_function_v<std::remove_pointer_t<Printer>>,
+ "Printer is not a function!");
+ static_assert(std::is_convertible_v<Printer, RequiredType>,
+ "Printer doesn't have the required type!");
+
+ if (LCtx && !State->get<Trait>().isEmpty()) {
+ Indent(Out, Space, IsDot) << '\"' << jsonPropertyName << "\": ";
++Space;
Out << '[' << NL;
LCtx->printJson(Out, NL, Space, IsDot, [&](const LocationContext *LC) {
- printObjectsUnderConstructionJson(Out, State, NL, LC, Space, IsDot);
+ printer(Out, State, NL, LC, Space, IsDot, std::forward<Args>(args)...);
});
--Space;
- Indent(Out, Space, IsDot) << "]," << NL; // End of "constructing_objects".
- } else {
- Out << "null," << NL;
+ Indent(Out, Space, IsDot) << "]," << NL; // End of "jsonPropertyName".
}
+}
+
+void ExprEngine::printJson(raw_ostream &Out, ProgramStateRef State,
+ const LocationContext *LCtx, const char *NL,
+ unsigned int Space, bool IsDot) const {
+
+ printStateTraitWithLocationContextJson<ObjectsUnderConstruction>(
+ Out, State, LCtx, NL, Space, IsDot, "constructing_objects",
+ printObjectsUnderConstructionJson);
+ printStateTraitWithLocationContextJson<IndexOfElementToConstruct>(
+ Out, State, LCtx, NL, Space, IsDot, "index_of_element",
+ printIndicesOfElementsToConstructJson);
+ printStateTraitWithLocationContextJson<PendingInitLoop>(
+ Out, State, LCtx, NL, Space, IsDot, "pending_init_loops",
+ printPendingInitLoopJson);
+ printStateTraitWithLocationContextJson<PendingArrayDestruction>(
+ Out, State, LCtx, NL, Space, IsDot, "pending_destructors",
+ printPendingArrayDestructionsJson);
getCheckerManager().runCheckersForPrintStateJson(Out, State, NL, Space,
IsDot);
}
void ExprEngine::processEndWorklist() {
+ // This prints the name of the top-level function if we crash.
+ PrettyStackTraceLocationContext CrashInfo(getRootLocationContext());
getCheckerManager().runCheckersForEndAnalysis(G, BR, *this);
}
@@ -865,7 +1204,7 @@ void ExprEngine::ProcessInitializer(const CFGInitializer CFGInit,
SVal LValue = State->getSVal(Init, stackFrame);
if (!Field->getType()->isReferenceType())
- if (Optional<Loc> LValueLoc = LValue.getAs<Loc>())
+ if (std::optional<Loc> LValueLoc = LValue.getAs<Loc>())
InitVal = State->getSVal(*LValueLoc);
// If we fail to get the value for some reason, use a symbolic value.
@@ -902,6 +1241,43 @@ void ExprEngine::ProcessInitializer(const CFGInitializer CFGInit,
Engine.enqueue(Dst, currBldrCtx->getBlock(), currStmtIdx);
}
+std::pair<ProgramStateRef, uint64_t>
+ExprEngine::prepareStateForArrayDestruction(const ProgramStateRef State,
+ const MemRegion *Region,
+ const QualType &ElementTy,
+ const LocationContext *LCtx,
+ SVal *ElementCountVal) {
+ assert(Region != nullptr && "Not-null region expected");
+
+ QualType Ty = ElementTy.getDesugaredType(getContext());
+ while (const auto *NTy = dyn_cast<ArrayType>(Ty))
+ Ty = NTy->getElementType().getDesugaredType(getContext());
+
+ auto ElementCount = getDynamicElementCount(State, Region, svalBuilder, Ty);
+
+ if (ElementCountVal)
+ *ElementCountVal = ElementCount;
+
+ // Note: the destructors are called in reverse order.
+ unsigned Idx = 0;
+ if (auto OptionalIdx = getPendingArrayDestruction(State, LCtx)) {
+ Idx = *OptionalIdx;
+ } else {
+ // The element count is either unknown, or an SVal that's not an integer.
+ if (!ElementCount.isConstant())
+ return {State, 0};
+
+ Idx = ElementCount.getAsInteger()->getLimitedValue();
+ }
+
+ if (Idx == 0)
+ return {State, 0};
+
+ --Idx;
+
+ return {setPendingArrayDestruction(State, LCtx, Idx), Idx};
+}
+
void ExprEngine::ProcessImplicitDtor(const CFGImplicitDtor D,
ExplodedNode *Pred) {
ExplodedNodeSet Dst;
@@ -942,7 +1318,8 @@ void ExprEngine::ProcessNewAllocator(const CXXNewExpr *NE,
else {
NodeBuilder Bldr(Pred, Dst, *currBldrCtx);
const LocationContext *LCtx = Pred->getLocationContext();
- PostImplicitCall PP(NE->getOperatorNew(), NE->getBeginLoc(), LCtx);
+ PostImplicitCall PP(NE->getOperatorNew(), NE->getBeginLoc(), LCtx,
+ getCFGElementRef());
Bldr.generateNode(PP, Pred->getState(), Pred);
}
Engine.enqueue(Dst, currBldrCtx->getBlock(), currStmtIdx);
@@ -951,11 +1328,14 @@ void ExprEngine::ProcessNewAllocator(const CXXNewExpr *NE,
void ExprEngine::ProcessAutomaticObjDtor(const CFGAutomaticObjDtor Dtor,
ExplodedNode *Pred,
ExplodedNodeSet &Dst) {
+ const auto *DtorDecl = Dtor.getDestructorDecl(getContext());
const VarDecl *varDecl = Dtor.getVarDecl();
QualType varType = varDecl->getType();
ProgramStateRef state = Pred->getState();
- SVal dest = state->getLValue(varDecl, Pred->getLocationContext());
+ const LocationContext *LCtx = Pred->getLocationContext();
+
+ SVal dest = state->getLValue(varDecl, LCtx);
const MemRegion *Region = dest.castAs<loc::MemRegionVal>().getRegion();
if (varType->isReferenceType()) {
@@ -971,12 +1351,47 @@ void ExprEngine::ProcessAutomaticObjDtor(const CFGAutomaticObjDtor Dtor,
varType = cast<TypedValueRegion>(Region)->getValueType();
}
- // FIXME: We need to run the same destructor on every element of the array.
- // This workaround will just run the first destructor (which will still
- // invalidate the entire array).
+ unsigned Idx = 0;
+ if (isa<ArrayType>(varType)) {
+ SVal ElementCount;
+ std::tie(state, Idx) = prepareStateForArrayDestruction(
+ state, Region, varType, LCtx, &ElementCount);
+
+ if (ElementCount.isConstant()) {
+ uint64_t ArrayLength = ElementCount.getAsInteger()->getLimitedValue();
+ assert(ArrayLength &&
+ "An automatic dtor for a 0 length array shouldn't be triggered!");
+
+ // Still handle this case if we don't have assertions enabled.
+ if (!ArrayLength) {
+ static SimpleProgramPointTag PT(
+ "ExprEngine", "Skipping automatic 0 length array destruction, "
+ "which shouldn't be in the CFG.");
+ PostImplicitCall PP(DtorDecl, varDecl->getLocation(), LCtx,
+ getCFGElementRef(), &PT);
+ NodeBuilder Bldr(Pred, Dst, *currBldrCtx);
+ Bldr.generateSink(PP, Pred->getState(), Pred);
+ return;
+ }
+ }
+ }
+
EvalCallOptions CallOpts;
- Region = makeZeroElementRegion(state, loc::MemRegionVal(Region), varType,
- CallOpts.IsArrayCtorOrDtor).getAsRegion();
+ Region = makeElementRegion(state, loc::MemRegionVal(Region), varType,
+ CallOpts.IsArrayCtorOrDtor, Idx)
+ .getAsRegion();
+
+ NodeBuilder Bldr(Pred, Dst, getBuilderContext());
+
+ static SimpleProgramPointTag PT("ExprEngine",
+ "Prepare for object destruction");
+ PreImplicitCall PP(DtorDecl, varDecl->getLocation(), LCtx, getCFGElementRef(),
+ &PT);
+ Pred = Bldr.generateNode(PP, state, Pred);
+
+ if (!Pred)
+ return;
+ Bldr.takeNodes(Pred);
VisitCXXDestructor(varType, Region, Dtor.getTriggerStmt(),
/*IsBase=*/false, Pred, Dst, CallOpts);
@@ -999,26 +1414,62 @@ void ExprEngine::ProcessDeleteDtor(const CFGDeleteDtor Dtor,
const CXXRecordDecl *RD = BTy->getAsCXXRecordDecl();
const CXXDestructorDecl *Dtor = RD->getDestructor();
- PostImplicitCall PP(Dtor, DE->getBeginLoc(), LCtx);
+ PostImplicitCall PP(Dtor, DE->getBeginLoc(), LCtx, getCFGElementRef());
NodeBuilder Bldr(Pred, Dst, *currBldrCtx);
Bldr.generateNode(PP, Pred->getState(), Pred);
return;
}
+ auto getDtorDecl = [](const QualType &DTy) {
+ const CXXRecordDecl *RD = DTy->getAsCXXRecordDecl();
+ return RD->getDestructor();
+ };
+
+ unsigned Idx = 0;
EvalCallOptions CallOpts;
const MemRegion *ArgR = ArgVal.getAsRegion();
+
if (DE->isArrayForm()) {
- // FIXME: We need to run the same destructor on every element of the array.
- // This workaround will just run the first destructor (which will still
- // invalidate the entire array).
CallOpts.IsArrayCtorOrDtor = true;
// Yes, it may even be a multi-dimensional array.
while (const auto *AT = getContext().getAsArrayType(DTy))
DTy = AT->getElementType();
- if (ArgR)
- ArgR = getStoreManager().GetElementZeroRegion(cast<SubRegion>(ArgR), DTy);
+
+ if (ArgR) {
+ SVal ElementCount;
+ std::tie(State, Idx) = prepareStateForArrayDestruction(
+ State, ArgR, DTy, LCtx, &ElementCount);
+
+ // If we're about to destruct a 0 length array, don't run any of the
+ // destructors.
+ if (ElementCount.isConstant() &&
+ ElementCount.getAsInteger()->getLimitedValue() == 0) {
+
+ static SimpleProgramPointTag PT(
+ "ExprEngine", "Skipping 0 length array delete destruction");
+ PostImplicitCall PP(getDtorDecl(DTy), DE->getBeginLoc(), LCtx,
+ getCFGElementRef(), &PT);
+ NodeBuilder Bldr(Pred, Dst, *currBldrCtx);
+ Bldr.generateNode(PP, Pred->getState(), Pred);
+ return;
+ }
+
+ ArgR = State->getLValue(DTy, svalBuilder.makeArrayIndex(Idx), ArgVal)
+ .getAsRegion();
+ }
}
+ NodeBuilder Bldr(Pred, Dst, getBuilderContext());
+ static SimpleProgramPointTag PT("ExprEngine",
+ "Prepare for object destruction");
+ PreImplicitCall PP(getDtorDecl(DTy), DE->getBeginLoc(), LCtx,
+ getCFGElementRef(), &PT);
+ Pred = Bldr.generateNode(PP, State, Pred);
+
+ if (!Pred)
+ return;
+ Bldr.takeNodes(Pred);
+
VisitCXXDestructor(DTy, ArgR, DE, /*IsBase=*/false, Pred, Dst, CallOpts);
}
@@ -1044,6 +1495,7 @@ void ExprEngine::ProcessBaseDtor(const CFGBaseDtor D,
void ExprEngine::ProcessMemberDtor(const CFGMemberDtor D,
ExplodedNode *Pred, ExplodedNodeSet &Dst) {
+ const auto *DtorDecl = D.getDestructorDecl(getContext());
const FieldDecl *Member = D.getFieldDecl();
QualType T = Member->getType();
ProgramStateRef State = Pred->getState();
@@ -1055,12 +1507,46 @@ void ExprEngine::ProcessMemberDtor(const CFGMemberDtor D,
Loc ThisLoc = State->getSVal(ThisStorageLoc).castAs<Loc>();
SVal FieldVal = State->getLValue(Member, ThisLoc);
- // FIXME: We need to run the same destructor on every element of the array.
- // This workaround will just run the first destructor (which will still
- // invalidate the entire array).
+ unsigned Idx = 0;
+ if (isa<ArrayType>(T)) {
+ SVal ElementCount;
+ std::tie(State, Idx) = prepareStateForArrayDestruction(
+ State, FieldVal.getAsRegion(), T, LCtx, &ElementCount);
+
+ if (ElementCount.isConstant()) {
+ uint64_t ArrayLength = ElementCount.getAsInteger()->getLimitedValue();
+ assert(ArrayLength &&
+ "A member dtor for a 0 length array shouldn't be triggered!");
+
+ // Still handle this case if we don't have assertions enabled.
+ if (!ArrayLength) {
+ static SimpleProgramPointTag PT(
+ "ExprEngine", "Skipping member 0 length array destruction, which "
+ "shouldn't be in the CFG.");
+ PostImplicitCall PP(DtorDecl, Member->getLocation(), LCtx,
+ getCFGElementRef(), &PT);
+ NodeBuilder Bldr(Pred, Dst, *currBldrCtx);
+ Bldr.generateSink(PP, Pred->getState(), Pred);
+ return;
+ }
+ }
+ }
+
EvalCallOptions CallOpts;
- FieldVal = makeZeroElementRegion(State, FieldVal, T,
- CallOpts.IsArrayCtorOrDtor);
+ FieldVal =
+ makeElementRegion(State, FieldVal, T, CallOpts.IsArrayCtorOrDtor, Idx);
+
+ NodeBuilder Bldr(Pred, Dst, getBuilderContext());
+
+ static SimpleProgramPointTag PT("ExprEngine",
+ "Prepare for object destruction");
+ PreImplicitCall PP(DtorDecl, Member->getLocation(), LCtx, getCFGElementRef(),
+ &PT);
+ Pred = Bldr.generateNode(PP, State, Pred);
+
+ if (!Pred)
+ return;
+ Bldr.takeNodes(Pred);
VisitCXXDestructor(T, FieldVal.getAsRegion(), CurDtor->getBody(),
/*IsBase=*/false, Pred, Dst, CallOpts);
@@ -1074,9 +1560,8 @@ void ExprEngine::ProcessTemporaryDtor(const CFGTemporaryDtor D,
const LocationContext *LC = Pred->getLocationContext();
const MemRegion *MR = nullptr;
- if (Optional<SVal> V =
- getObjectUnderConstruction(State, D.getBindTemporaryExpr(),
- Pred->getLocationContext())) {
+ if (std::optional<SVal> V = getObjectUnderConstruction(
+ State, D.getBindTemporaryExpr(), Pred->getLocationContext())) {
// FIXME: Currently we insert temporary destructors for default parameters,
// but we don't insert the constructors, so the entry in
// ObjectsUnderConstruction may be missing.
@@ -1092,7 +1577,7 @@ void ExprEngine::ProcessTemporaryDtor(const CFGTemporaryDtor D,
NodeBuilder Bldr(Pred, Dst, *currBldrCtx);
PostImplicitCall PP(D.getDestructorDecl(getContext()),
D.getBindTemporaryExpr()->getBeginLoc(),
- Pred->getLocationContext());
+ Pred->getLocationContext(), getCFGElementRef());
Bldr.generateNode(PP, State, Pred);
return;
}
@@ -1111,15 +1596,31 @@ void ExprEngine::ProcessTemporaryDtor(const CFGTemporaryDtor D,
EvalCallOptions CallOpts;
CallOpts.IsTemporaryCtorOrDtor = true;
if (!MR) {
- // If we have no MR, we still need to unwrap the array to avoid destroying
- // the whole array at once. Regardless, we'd eventually need to model array
- // destructors properly, element-by-element.
+ // FIXME: If we have no MR, we still need to unwrap the array to avoid
+ // destroying the whole array at once.
+ //
+ // For this case there is no universal solution as there is no way to
+ // directly create an array of temporary objects. There are some expressions
+ // however which can create temporary objects and have an array type.
+ //
+ // E.g.: std::initializer_list<S>{S(), S()};
+ //
+ // The expression above has a type of 'const struct S[2]' but it's a single
+ // 'std::initializer_list<>'. The destructors of the 2 temporary 'S()'
+ // objects will be called anyway, because they are 2 separate objects in 2
+ // separate clusters, i.e.: not an array.
+ //
+ // Now the 'std::initializer_list<>' is not an array either even though it
+ // has the type of an array. The point is, we only want to invoke the
+ // destructor for the initializer list once not twice or so.
while (const ArrayType *AT = getContext().getAsArrayType(T)) {
T = AT->getElementType();
- CallOpts.IsArrayCtorOrDtor = true;
+
+ // FIXME: Enable this flag once we handle this case properly.
+ // CallOpts.IsArrayCtorOrDtor = true;
}
} else {
- // We'd eventually need to makeZeroElementRegion() trick here,
+ // FIXME: We'd eventually need to makeElementRegion() trick here,
// but for now we don't have the respective construction contexts,
// so MR would always be null in this case. Do nothing for now.
}
@@ -1252,10 +1753,12 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
case Stmt::OMPParallelForSimdDirectiveClass:
case Stmt::OMPParallelSectionsDirectiveClass:
case Stmt::OMPParallelMasterDirectiveClass:
+ case Stmt::OMPParallelMaskedDirectiveClass:
case Stmt::OMPTaskDirectiveClass:
case Stmt::OMPTaskyieldDirectiveClass:
case Stmt::OMPBarrierDirectiveClass:
case Stmt::OMPTaskwaitDirectiveClass:
+ case Stmt::OMPErrorDirectiveClass:
case Stmt::OMPTaskgroupDirectiveClass:
case Stmt::OMPFlushDirectiveClass:
case Stmt::OMPDepobjDirectiveClass:
@@ -1275,9 +1778,13 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
case Stmt::OMPTaskLoopDirectiveClass:
case Stmt::OMPTaskLoopSimdDirectiveClass:
case Stmt::OMPMasterTaskLoopDirectiveClass:
+ case Stmt::OMPMaskedTaskLoopDirectiveClass:
case Stmt::OMPMasterTaskLoopSimdDirectiveClass:
+ case Stmt::OMPMaskedTaskLoopSimdDirectiveClass:
case Stmt::OMPParallelMasterTaskLoopDirectiveClass:
+ case Stmt::OMPParallelMaskedTaskLoopDirectiveClass:
case Stmt::OMPParallelMasterTaskLoopSimdDirectiveClass:
+ case Stmt::OMPParallelMaskedTaskLoopSimdDirectiveClass:
case Stmt::OMPDistributeDirectiveClass:
case Stmt::OMPDistributeParallelForDirectiveClass:
case Stmt::OMPDistributeParallelForSimdDirectiveClass:
@@ -1297,8 +1804,14 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
case Stmt::OMPInteropDirectiveClass:
case Stmt::OMPDispatchDirectiveClass:
case Stmt::OMPMaskedDirectiveClass:
+ case Stmt::OMPGenericLoopDirectiveClass:
+ case Stmt::OMPTeamsGenericLoopDirectiveClass:
+ case Stmt::OMPTargetTeamsGenericLoopDirectiveClass:
+ case Stmt::OMPParallelGenericLoopDirectiveClass:
+ case Stmt::OMPTargetParallelGenericLoopDirectiveClass:
case Stmt::CapturedStmtClass:
- case Stmt::OMPUnrollDirectiveClass: {
+ case Stmt::OMPUnrollDirectiveClass:
+ case Stmt::OMPMetaDirectiveClass: {
const ExplodedNode *node = Bldr.generateSink(S, Pred, Pred->getState());
Engine.addAbortedBlock(node, currBldrCtx->getBlock());
break;
@@ -1341,8 +1854,9 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
case Stmt::GNUNullExprClass: {
// GNU __null is a pointer-width integer, not an actual pointer.
ProgramStateRef state = Pred->getState();
- state = state->BindExpr(S, Pred->getLocationContext(),
- svalBuilder.makeIntValWithPtrWidth(0, false));
+ state = state->BindExpr(
+ S, Pred->getLocationContext(),
+ svalBuilder.makeIntValWithWidth(getContext().VoidPtrTy, 0));
Bldr.generateNode(S, Pred, state);
break;
}
@@ -1369,10 +1883,14 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
break;
}
+ case Stmt::ArrayInitLoopExprClass:
+ Bldr.takeNodes(Pred);
+ VisitArrayInitLoopExpr(cast<ArrayInitLoopExpr>(S), Pred, Dst);
+ Bldr.addNodes(Dst);
+ break;
// Cases not handled yet; but will handle some day.
case Stmt::DesignatedInitExprClass:
case Stmt::DesignatedInitUpdateExprClass:
- case Stmt::ArrayInitLoopExprClass:
case Stmt::ArrayInitIndexExprClass:
case Stmt::ExtVectorElementExprClass:
case Stmt::ImaginaryLiteralClass:
@@ -1394,6 +1912,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
case Stmt::ConceptSpecializationExprClass:
case Stmt::CXXRewrittenBinaryOperatorClass:
case Stmt::RequiresExprClass:
+ case Expr::CXXParenListInitExprClass:
// Fall through.
// Cases we intentionally don't evaluate, since they don't need
@@ -1453,7 +1972,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
IsTemporary = true;
}
- Optional<SVal> ConstantVal = svalBuilder.getConstantVal(ArgE);
+ std::optional<SVal> ConstantVal = svalBuilder.getConstantVal(ArgE);
if (!ConstantVal)
ConstantVal = UnknownVal();
@@ -1607,8 +2126,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
}
}
}
- // FALLTHROUGH
- LLVM_FALLTHROUGH;
+ [[fallthrough]];
}
case Stmt::CallExprClass:
@@ -1919,7 +2437,7 @@ bool ExprEngine::replayWithoutInlining(ExplodedNode *N,
continue;
if (L.getAs<CallEnter>())
continue;
- if (Optional<StmtPoint> SP = L.getAs<StmtPoint>())
+ if (std::optional<StmtPoint> SP = L.getAs<StmtPoint>())
if (SP->getStmt() == CE)
continue;
break;
@@ -1932,8 +2450,9 @@ bool ExprEngine::replayWithoutInlining(ExplodedNode *N,
// Build an Epsilon node from which we will restart the analyzes.
// Note that CE is permitted to be NULL!
- ProgramPoint NewNodeLoc =
- EpsilonPoint(BeforeProcessingCall->getLocationContext(), CE);
+ static SimpleProgramPointTag PT("ExprEngine", "Replay without inlining");
+ ProgramPoint NewNodeLoc = EpsilonPoint(
+ BeforeProcessingCall->getLocationContext(), CE, nullptr, &PT);
// Add the special flag to GDM to signal retrying with no inlining.
// Note, changing the state ensures that we are not going to cache out.
ProgramStateRef NewNodeState = BeforeProcessingCall->getState();
@@ -1988,8 +2507,7 @@ void ExprEngine::processCFGBlockEntrance(const BlockEdge &L,
if (BlockCount == AMgr.options.maxBlockVisitOnPath - 1 &&
AMgr.options.ShouldWidenLoops) {
const Stmt *Term = nodeBuilder.getContext().getBlock()->getTerminatorStmt();
- if (!(Term &&
- (isa<ForStmt>(Term) || isa<WhileStmt>(Term) || isa<DoStmt>(Term))))
+ if (!isa_and_nonnull<ForStmt, WhileStmt, DoStmt>(Term))
return;
// Widen.
const LocationContext *LCtx = Pred->getLocationContext();
@@ -2123,10 +2641,8 @@ static const Stmt *ResolveCondition(const Stmt *Condition,
// The invariants are still shifting, but it is possible that the
// last element in a CFGBlock is not a CFGStmt. Look for the last
// CFGStmt as the value of the condition.
- CFGBlock::const_reverse_iterator I = B->rbegin(), E = B->rend();
- for (; I != E; ++I) {
- CFGElement Elem = *I;
- Optional<CFGStmt> CS = Elem.getAs<CFGStmt>();
+ for (CFGElement Elem : llvm::reverse(*B)) {
+ std::optional<CFGStmt> CS = Elem.getAs<CFGStmt>();
if (!CS)
continue;
const Stmt *LastStmt = CS->getStmt();
@@ -2164,9 +2680,9 @@ bool ExprEngine::hasMoreIteration(ProgramStateRef State,
}
/// Split the state on whether there are any more iterations left for this loop.
-/// Returns a (HasMoreIteration, HasNoMoreIteration) pair, or None when the
-/// acquisition of the loop condition value failed.
-static Optional<std::pair<ProgramStateRef, ProgramStateRef>>
+/// Returns a (HasMoreIteration, HasNoMoreIteration) pair, or std::nullopt when
+/// the acquisition of the loop condition value failed.
+static std::optional<std::pair<ProgramStateRef, ProgramStateRef>>
assumeCondition(const Stmt *Condition, ExplodedNode *N) {
ProgramStateRef State = N->getState();
if (const auto *ObjCFor = dyn_cast<ObjCForCollectionStmt>(Condition)) {
@@ -2205,7 +2721,7 @@ assumeCondition(const Stmt *Condition, ExplodedNode *N) {
// If the condition is still unknown, give up.
if (X.isUnknownOrUndef())
- return None;
+ return std::nullopt;
DefinedSVal V = X.castAs<DefinedSVal>();
@@ -2265,7 +2781,7 @@ void ExprEngine::processBranch(const Stmt *Condition,
continue;
}
if (StTrue && StFalse)
- assert(!isa<ObjCForCollectionStmt>(Condition));;
+ assert(!isa<ObjCForCollectionStmt>(Condition));
// Process the true branch.
if (builder.isFeasible(true)) {
@@ -2330,12 +2846,12 @@ void ExprEngine::processIndirectGoto(IndirectGotoNodeBuilder &builder) {
using iterator = IndirectGotoNodeBuilder::iterator;
- if (Optional<loc::GotoLabel> LV = V.getAs<loc::GotoLabel>()) {
+ if (std::optional<loc::GotoLabel> LV = V.getAs<loc::GotoLabel>()) {
const LabelDecl *L = LV->getLabel();
- for (iterator I = builder.begin(), E = builder.end(); I != E; ++I) {
- if (I.getLabel() == L) {
- builder.generateNode(I, state);
+ for (iterator Succ : builder) {
+ if (Succ.getLabel() == L) {
+ builder.generateNode(Succ, state);
return;
}
}
@@ -2343,7 +2859,7 @@ void ExprEngine::processIndirectGoto(IndirectGotoNodeBuilder &builder) {
llvm_unreachable("No block with label.");
}
- if (V.getAs<loc::ConcreteInt>() || V.getAs<UndefinedVal>()) {
+ if (isa<UndefinedVal, loc::ConcreteInt>(V)) {
// Dispatch to the first target and mark it as a sink.
//ExplodedNode* N = builder.generateNode(builder.begin(), state, true);
// FIXME: add checker visit.
@@ -2354,8 +2870,8 @@ void ExprEngine::processIndirectGoto(IndirectGotoNodeBuilder &builder) {
// This is really a catch-all. We don't support symbolics yet.
// FIXME: Implement dispatch for symbolic pointers.
- for (iterator I = builder.begin(), E = builder.end(); I != E; ++I)
- builder.generateNode(I, state);
+ for (iterator Succ : builder)
+ builder.generateNode(Succ, state);
}
void ExprEngine::processBeginOfFunction(NodeBuilderContext &BC,
@@ -2482,7 +2998,7 @@ void ExprEngine::processSwitch(SwitchNodeBuilder& builder) {
V2 = V1;
ProgramStateRef StateCase;
- if (Optional<NonLoc> NL = CondV.getAs<NonLoc>())
+ if (std::optional<NonLoc> NL = CondV.getAs<NonLoc>())
std::tie(StateCase, DefaultSt) =
DefaultSt->assumeInclusiveRange(*NL, V1, V2);
else // UnknownVal
@@ -2541,14 +3057,14 @@ void ExprEngine::VisitCommonDeclRefExpr(const Expr *Ex, const NamedDecl *D,
const Decl *D = LocCtxt->getDecl();
const auto *MD = dyn_cast_or_null<CXXMethodDecl>(D);
const auto *DeclRefEx = dyn_cast<DeclRefExpr>(Ex);
- Optional<std::pair<SVal, QualType>> VInfo;
+ std::optional<std::pair<SVal, QualType>> VInfo;
if (AMgr.options.ShouldInlineLambdas && DeclRefEx &&
DeclRefEx->refersToEnclosingVariableOrCapture() && MD &&
MD->getParent()->isLambda()) {
// Lookup the field of the lambda.
const CXXRecordDecl *CXXRec = MD->getParent();
- llvm::DenseMap<const VarDecl *, FieldDecl *> LambdaCaptureFields;
+ llvm::DenseMap<const ValueDecl *, FieldDecl *> LambdaCaptureFields;
FieldDecl *LambdaThisCaptureField;
CXXRec->getCaptureFields(LambdaCaptureFields, LambdaThisCaptureField);
@@ -2593,20 +3109,175 @@ void ExprEngine::VisitCommonDeclRefExpr(const Expr *Ex, const NamedDecl *D,
ProgramPoint::PostLValueKind);
return;
}
- if (isa<FieldDecl>(D) || isa<IndirectFieldDecl>(D)) {
+ if (isa<FieldDecl, IndirectFieldDecl>(D)) {
// Delegate all work related to pointer to members to the surrounding
// operator&.
return;
}
- if (isa<BindingDecl>(D)) {
- // FIXME: proper support for bound declarations.
- // For now, let's just prevent crashing.
+ if (const auto *BD = dyn_cast<BindingDecl>(D)) {
+ const auto *DD = cast<DecompositionDecl>(BD->getDecomposedDecl());
+
+ SVal Base = state->getLValue(DD, LCtx);
+ if (DD->getType()->isReferenceType()) {
+ if (const MemRegion *R = Base.getAsRegion())
+ Base = state->getSVal(R);
+ else
+ Base = UnknownVal();
+ }
+
+ SVal V = UnknownVal();
+
+ // Handle binding to data members
+ if (const auto *ME = dyn_cast<MemberExpr>(BD->getBinding())) {
+ const auto *Field = cast<FieldDecl>(ME->getMemberDecl());
+ V = state->getLValue(Field, Base);
+ }
+ // Handle binding to arrays
+ else if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(BD->getBinding())) {
+ SVal Idx = state->getSVal(ASE->getIdx(), LCtx);
+
+ // Note: the index of an element in a structured binding is automatically
+ // created and it is a unique identifier of the specific element. Thus it
+ // cannot be a value that varies at runtime.
+ assert(Idx.isConstant() && "BindingDecl array index is not a constant!");
+
+ V = state->getLValue(BD->getType(), Idx, Base);
+ }
+ // Handle binding to tuple-like structures
+ else if (const auto *HV = BD->getHoldingVar()) {
+ V = state->getLValue(HV, LCtx);
+
+ if (HV->getType()->isReferenceType()) {
+ if (const MemRegion *R = V.getAsRegion())
+ V = state->getSVal(R);
+ else
+ V = UnknownVal();
+ }
+ } else
+ llvm_unreachable("An unknown case of structured binding encountered!");
+
+ // In case of tuple-like types the references are already handled, so we
+ // don't want to handle them again.
+ if (BD->getType()->isReferenceType() && !BD->getHoldingVar()) {
+ if (const MemRegion *R = V.getAsRegion())
+ V = state->getSVal(R);
+ else
+ V = UnknownVal();
+ }
+
+ Bldr.generateNode(Ex, Pred, state->BindExpr(Ex, LCtx, V), nullptr,
+ ProgramPoint::PostLValueKind);
+
+ return;
+ }
+
+ if (const auto *TPO = dyn_cast<TemplateParamObjectDecl>(D)) {
+ // FIXME: We should meaningfully implement this.
+ (void)TPO;
return;
}
llvm_unreachable("Support for this Decl not implemented.");
}
+/// VisitArrayInitLoopExpr - Transfer function for array init loop.
+void ExprEngine::VisitArrayInitLoopExpr(const ArrayInitLoopExpr *Ex,
+ ExplodedNode *Pred,
+ ExplodedNodeSet &Dst) {
+ ExplodedNodeSet CheckerPreStmt;
+ getCheckerManager().runCheckersForPreStmt(CheckerPreStmt, Pred, Ex, *this);
+
+ ExplodedNodeSet EvalSet;
+ StmtNodeBuilder Bldr(CheckerPreStmt, EvalSet, *currBldrCtx);
+
+ const Expr *Arr = Ex->getCommonExpr()->getSourceExpr();
+
+ for (auto *Node : CheckerPreStmt) {
+
+ // The constructor visitior has already taken care of everything.
+ if (isa<CXXConstructExpr>(Ex->getSubExpr()))
+ break;
+
+ const LocationContext *LCtx = Node->getLocationContext();
+ ProgramStateRef state = Node->getState();
+
+ SVal Base = UnknownVal();
+
+ // As in case of this expression the sub-expressions are not visited by any
+ // other transfer functions, they are handled by matching their AST.
+
+ // Case of implicit copy or move ctor of object with array member
+ //
+ // Note: ExprEngine::VisitMemberExpr is not able to bind the array to the
+ // environment.
+ //
+ // struct S {
+ // int arr[2];
+ // };
+ //
+ //
+ // S a;
+ // S b = a;
+ //
+ // The AST in case of a *copy constructor* looks like this:
+ // ArrayInitLoopExpr
+ // |-OpaqueValueExpr
+ // | `-MemberExpr <-- match this
+ // | `-DeclRefExpr
+ // ` ...
+ //
+ //
+ // S c;
+ // S d = std::move(d);
+ //
+ // In case of a *move constructor* the resulting AST looks like:
+ // ArrayInitLoopExpr
+ // |-OpaqueValueExpr
+ // | `-MemberExpr <-- match this first
+ // | `-CXXStaticCastExpr <-- match this after
+ // | `-DeclRefExpr
+ // ` ...
+ if (const auto *ME = dyn_cast<MemberExpr>(Arr)) {
+ Expr *MEBase = ME->getBase();
+
+ // Move ctor
+ if (auto CXXSCE = dyn_cast<CXXStaticCastExpr>(MEBase)) {
+ MEBase = CXXSCE->getSubExpr();
+ }
+
+ auto ObjDeclExpr = cast<DeclRefExpr>(MEBase);
+ SVal Obj = state->getLValue(cast<VarDecl>(ObjDeclExpr->getDecl()), LCtx);
+
+ Base = state->getLValue(cast<FieldDecl>(ME->getMemberDecl()), Obj);
+ }
+
+ // Case of lambda capture and decomposition declaration
+ //
+ // int arr[2];
+ //
+ // [arr]{ int a = arr[0]; }();
+ // auto[a, b] = arr;
+ //
+ // In both of these cases the AST looks like the following:
+ // ArrayInitLoopExpr
+ // |-OpaqueValueExpr
+ // | `-DeclRefExpr <-- match this
+ // ` ...
+ if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(Arr))
+ Base = state->getLValue(cast<VarDecl>(DRE->getDecl()), LCtx);
+
+ // Create a lazy compound value to the original array
+ if (const MemRegion *R = Base.getAsRegion())
+ Base = state->getSVal(R);
+ else
+ Base = UnknownVal();
+
+ Bldr.generateNode(Ex, Pred, state->BindExpr(Ex, LCtx, Base));
+ }
+
+ getCheckerManager().runCheckersForPostStmt(Dst, EvalSet, Ex, *this);
+}
+
/// VisitArraySubscriptExpr - Transfer function for array accesses
void ExprEngine::VisitArraySubscriptExpr(const ArraySubscriptExpr *A,
ExplodedNode *Pred,
@@ -2670,7 +3341,7 @@ void ExprEngine::VisitMemberExpr(const MemberExpr *M, ExplodedNode *Pred,
// Handle static member variables and enum constants accessed via
// member syntax.
- if (isa<VarDecl>(Member) || isa<EnumConstantDecl>(Member)) {
+ if (isa<VarDecl, EnumConstantDecl>(Member)) {
for (const auto I : CheckedSet)
VisitCommonDeclRefExpr(M, Member, I, EvalSet);
} else {
@@ -2702,6 +3373,14 @@ void ExprEngine::VisitMemberExpr(const MemberExpr *M, ExplodedNode *Pred,
SVal baseExprVal =
MR ? loc::MemRegionVal(MR) : state->getSVal(BaseExpr, LCtx);
+ // FIXME: Copied from RegionStoreManager::bind()
+ if (const auto *SR =
+ dyn_cast_or_null<SymbolicRegion>(baseExprVal.getAsRegion())) {
+ QualType T = SR->getPointeeStaticType();
+ baseExprVal =
+ loc::MemRegionVal(getStoreManager().GetElementZeroRegion(SR, T));
+ }
+
const auto *field = cast<FieldDecl>(Member);
SVal L = state->getLValue(field, baseExprVal);
@@ -2791,7 +3470,8 @@ ProgramStateRef ExprEngine::processPointerEscapedOnBind(
for (const std::pair<SVal, SVal> &LocAndVal : LocAndVals) {
// Cases (1) and (2).
const MemRegion *MR = LocAndVal.first.getAsRegion();
- if (!MR || !MR->hasStackStorage()) {
+ if (!MR ||
+ !isa<StackSpaceRegion, StaticGlobalSpaceRegion>(MR->getMemorySpace())) {
Escaped.push_back(LocAndVal.second);
continue;
}
@@ -2894,7 +3574,7 @@ void ExprEngine::evalBind(ExplodedNodeSet &Dst, const Stmt *StoreE,
// If the location is not a 'Loc', it will already be handled by
// the checkers. There is nothing left to do.
- if (!location.getAs<Loc>()) {
+ if (!isa<Loc>(location)) {
const ProgramPoint L = PostStore(StoreE, LC, /*Loc*/nullptr,
/*tag*/nullptr);
ProgramStateRef state = Pred->getState();
@@ -2915,7 +3595,7 @@ void ExprEngine::evalBind(ExplodedNodeSet &Dst, const Stmt *StoreE,
Val, LC, /* notifyChanges = */ !atDeclInit);
const MemRegion *LocReg = nullptr;
- if (Optional<loc::MemRegionVal> LocRegVal =
+ if (std::optional<loc::MemRegionVal> LocRegVal =
location.getAs<loc::MemRegionVal>()) {
LocReg = LocRegVal->getRegion();
}
@@ -2964,7 +3644,7 @@ void ExprEngine::evalLoad(ExplodedNodeSet &Dst,
SVal location,
const ProgramPointTag *tag,
QualType LoadTy) {
- assert(!location.getAs<NonLoc>() && "location cannot be a NonLoc.");
+ assert(!isa<NonLoc>(location) && "location cannot be a NonLoc.");
assert(NodeEx);
assert(BoundEx);
// Evaluate the location (checks for bad dereferences).
@@ -3056,7 +3736,7 @@ void ExprEngine::evalEagerlyAssumeBinOpBifurcation(ExplodedNodeSet &Dst,
ProgramStateRef state = Pred->getState();
SVal V = state->getSVal(Ex, Pred->getLocationContext());
- Optional<nonloc::SymbolVal> SEV = V.getAs<nonloc::SymbolVal>();
+ std::optional<nonloc::SymbolVal> SEV = V.getAs<nonloc::SymbolVal>();
if (SEV && SEV->isExpression()) {
const std::pair<const ProgramPointTag *, const ProgramPointTag*> &tags =
geteagerlyAssumeBinOpBifurcationTags();
@@ -3095,9 +3775,9 @@ void ExprEngine::VisitGCCAsmStmt(const GCCAsmStmt *A, ExplodedNode *Pred,
for (const Expr *O : A->outputs()) {
SVal X = state->getSVal(O, Pred->getLocationContext());
- assert(!X.getAs<NonLoc>()); // Should be an Lval, or unknown, undef.
+ assert(!isa<NonLoc>(X)); // Should be an Lval, or unknown, undef.
- if (Optional<Loc> LV = X.getAs<Loc>())
+ if (std::optional<Loc> LV = X.getAs<Loc>())
state = state->bindLoc(*LV, UnknownVal(), Pred->getLocationContext());
}
@@ -3114,7 +3794,6 @@ void ExprEngine::VisitMSAsmStmt(const MSAsmStmt *A, ExplodedNode *Pred,
// Visualization.
//===----------------------------------------------------------------------===//
-#ifndef NDEBUG
namespace llvm {
template<>
@@ -3125,12 +3804,9 @@ struct DOTGraphTraits<ExplodedGraph*> : public DefaultDOTGraphTraits {
BugReporter &BR = static_cast<ExprEngine &>(
N->getState()->getStateManager().getOwningEngine()).getBugReporter();
- const auto EQClasses =
- llvm::make_range(BR.EQClasses_begin(), BR.EQClasses_end());
-
- for (const auto &EQ : EQClasses) {
- for (const auto &I : EQ.getReports()) {
- const auto *PR = dyn_cast<PathSensitiveBugReport>(I.get());
+ for (const auto &Class : BR.equivalenceClasses()) {
+ for (const auto &Report : Class.getReports()) {
+ const auto *PR = dyn_cast<PathSensitiveBugReport>(Report.get());
if (!PR)
continue;
const ExplodedNode *EN = PR->getErrorNode();
@@ -3190,7 +3866,7 @@ struct DOTGraphTraits<ExplodedGraph*> : public DefaultDOTGraphTraits {
OtherNode->getLocation().printJson(Out, /*NL=*/"\\l");
Out << ", \"tag\": ";
if (const ProgramPointTag *Tag = OtherNode->getLocation().getTag())
- Out << '\"' << Tag->getTagDescription() << "\"";
+ Out << '\"' << Tag->getTagDescription() << '\"';
else
Out << "null";
Out << ", \"node_id\": " << OtherNode->getID() <<
@@ -3212,72 +3888,51 @@ struct DOTGraphTraits<ExplodedGraph*> : public DefaultDOTGraphTraits {
};
} // namespace llvm
-#endif
void ExprEngine::ViewGraph(bool trim) {
-#ifndef NDEBUG
std::string Filename = DumpGraph(trim);
llvm::DisplayGraph(Filename, false, llvm::GraphProgram::DOT);
-#else
- llvm::errs() << "Warning: viewing graph requires assertions" << "\n";
-#endif
}
-
-void ExprEngine::ViewGraph(ArrayRef<const ExplodedNode*> Nodes) {
-#ifndef NDEBUG
+void ExprEngine::ViewGraph(ArrayRef<const ExplodedNode *> Nodes) {
std::string Filename = DumpGraph(Nodes);
llvm::DisplayGraph(Filename, false, llvm::GraphProgram::DOT);
-#else
- llvm::errs() << "Warning: viewing graph requires assertions" << "\n";
-#endif
}
std::string ExprEngine::DumpGraph(bool trim, StringRef Filename) {
-#ifndef NDEBUG
if (trim) {
std::vector<const ExplodedNode *> Src;
// Iterate through the reports and get their nodes.
- for (BugReporter::EQClasses_iterator
- EI = BR.EQClasses_begin(), EE = BR.EQClasses_end(); EI != EE; ++EI) {
+ for (const auto &Class : BR.equivalenceClasses()) {
const auto *R =
- dyn_cast<PathSensitiveBugReport>(EI->getReports()[0].get());
+ dyn_cast<PathSensitiveBugReport>(Class.getReports()[0].get());
if (!R)
continue;
const auto *N = const_cast<ExplodedNode *>(R->getErrorNode());
Src.push_back(N);
}
return DumpGraph(Src, Filename);
- } else {
- return llvm::WriteGraph(&G, "ExprEngine", /*ShortNames=*/false,
- /*Title=*/"Exploded Graph",
- /*Filename=*/std::string(Filename));
}
-#else
- llvm::errs() << "Warning: dumping graph requires assertions" << "\n";
- return "";
-#endif
+
+ return llvm::WriteGraph(&G, "ExprEngine", /*ShortNames=*/false,
+ /*Title=*/"Exploded Graph",
+ /*Filename=*/std::string(Filename));
}
-std::string ExprEngine::DumpGraph(ArrayRef<const ExplodedNode*> Nodes,
+std::string ExprEngine::DumpGraph(ArrayRef<const ExplodedNode *> Nodes,
StringRef Filename) {
-#ifndef NDEBUG
std::unique_ptr<ExplodedGraph> TrimmedG(G.trim(Nodes));
if (!TrimmedG.get()) {
llvm::errs() << "warning: Trimmed ExplodedGraph is empty.\n";
return "";
- } else {
- return llvm::WriteGraph(TrimmedG.get(), "TrimmedExprEngine",
- /*ShortNames=*/false,
- /*Title=*/"Trimmed Exploded Graph",
- /*Filename=*/std::string(Filename));
- }
-#else
- llvm::errs() << "Warning: dumping graph requires assertions" << "\n";
- return "";
-#endif
+ }
+
+ return llvm::WriteGraph(TrimmedG.get(), "TrimmedExprEngine",
+ /*ShortNames=*/false,
+ /*Title=*/"Trimmed Exploded Graph",
+ /*Filename=*/std::string(Filename));
}
void *ProgramStateTrait<ReplayWithoutInlining>::GDMIndex() {