summaryrefslogtreecommitdiffstats
path: root/clang/lib/StaticAnalyzer/Checkers/UninitializedObjectChecker.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'clang/lib/StaticAnalyzer/Checkers/UninitializedObjectChecker.cpp')
-rw-r--r--clang/lib/StaticAnalyzer/Checkers/UninitializedObjectChecker.cpp669
1 files changed, 669 insertions, 0 deletions
diff --git a/clang/lib/StaticAnalyzer/Checkers/UninitializedObjectChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UninitializedObjectChecker.cpp
new file mode 100644
index 00000000000..63e4b817da3
--- /dev/null
+++ b/clang/lib/StaticAnalyzer/Checkers/UninitializedObjectChecker.cpp
@@ -0,0 +1,669 @@
+//===----- UninitializedObjectChecker.cpp ------------------------*- C++ -*-==//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines a checker that reports uninitialized fields in objects
+// created after a constructor call.
+//
+// This checker has an option "Pedantic" (boolean). If its not set or is set to
+// false, the checker won't emit warnings for objects that don't have at least
+// one initialized field. This may be set with
+// `-analyzer-config alpha.cplusplus.UninitializedObject:Pedantic=true`.
+//
+//===----------------------------------------------------------------------===//
+
+#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include <algorithm>
+
+using namespace clang;
+using namespace clang::ento;
+
+namespace {
+
+class UninitializedObjectChecker : public Checker<check::EndFunction> {
+ std::unique_ptr<BuiltinBug> BT_uninitField;
+
+public:
+ bool IsPedantic; // Will be initialized when registering the checker.
+
+ UninitializedObjectChecker()
+ : BT_uninitField(new BuiltinBug(this, "Uninitialized fields")) {}
+ void checkEndFunction(CheckerContext &C) const;
+};
+
+llvm::ImmutableListFactory<const FieldRegion *> Factory;
+
+/// Represents a field chain. A field chain is a vector of fields where the
+/// first element of the chain is the object under checking (not stored), and
+/// every other element is a field, and the element that precedes it is the
+/// object that contains it.
+///
+/// Note that this class is immutable, and new fields may only be added through
+/// constructor calls.
+class FieldChainInfo {
+ using FieldChain = llvm::ImmutableList<const FieldRegion *>;
+
+ FieldChain Chain;
+
+ const bool IsDereferenced = false;
+
+public:
+ FieldChainInfo() = default;
+
+ FieldChainInfo(const FieldChainInfo &Other, const bool IsDereferenced)
+ : Chain(Other.Chain), IsDereferenced(IsDereferenced) {}
+
+ FieldChainInfo(const FieldChainInfo &Other, const FieldRegion *FR,
+ const bool IsDereferenced = false);
+
+ bool contains(const FieldRegion *FR) const { return Chain.contains(FR); }
+ bool isPointer() const;
+
+ /// If this is a fieldchain whose last element is an uninitialized region of a
+ /// pointer type, `IsDereferenced` will store whether the pointer itself or
+ /// the pointee is uninitialized.
+ bool isDereferenced() const;
+ const FieldDecl *getEndOfChain() const;
+ void print(llvm::raw_ostream &Out) const;
+
+private:
+ /// Prints every element except the last to `Out`. Since ImmutableLists store
+ /// elements in reverse order, and have no reverse iterators, we use a
+ /// recursive function to print the fieldchain correctly. The last element in
+ /// the chain is to be printed by `print`.
+ static void printTail(llvm::raw_ostream &Out,
+ const llvm::ImmutableListImpl<const FieldRegion *> *L);
+ friend struct FieldChainInfoComparator;
+};
+
+struct FieldChainInfoComparator {
+ bool operator()(const FieldChainInfo &lhs, const FieldChainInfo &rhs) {
+ assert(!lhs.Chain.isEmpty() && !rhs.Chain.isEmpty() &&
+ "Attempted to store an empty fieldchain!");
+ return *lhs.Chain.begin() < *rhs.Chain.begin();
+ }
+};
+
+using UninitFieldSet = std::set<FieldChainInfo, FieldChainInfoComparator>;
+
+/// Searches for and stores uninitialized fields in a non-union object.
+class FindUninitializedFields {
+ ProgramStateRef State;
+ const TypedValueRegion *const ObjectR;
+
+ const bool IsPedantic;
+ bool IsAnyFieldInitialized = false;
+
+ UninitFieldSet UninitFields;
+
+public:
+ FindUninitializedFields(ProgramStateRef State,
+ const TypedValueRegion *const R, bool IsPedantic);
+ const UninitFieldSet &getUninitFields();
+
+private:
+ /// Adds a FieldChainInfo object to UninitFields. Return true if an insertion
+ /// took place.
+ bool addFieldToUninits(FieldChainInfo LocalChain);
+
+ // For the purposes of this checker, we'll regard the object under checking as
+ // a directed tree, where
+ // * the root is the object under checking
+ // * every node is an object that is
+ // - a union
+ // - a non-union record
+ // - a pointer/reference
+ // - an array
+ // - of a member pointer type
+ // - of a primitive type, which we'll define as either a BuiltinType or
+ // EnumeralType.
+ // * the parent of each node is the object that contains it
+ // * every leaf is an array, a primitive object, a member pointer, a nullptr
+ // or an undefined pointer.
+ //
+ // Example:
+ //
+ // struct A {
+ // struct B {
+ // int x, y = 0;
+ // };
+ // B b;
+ // int *iptr = new int;
+ // B* bptr;
+ //
+ // A() {}
+ // };
+ //
+ // The directed tree:
+ //
+ // ->x
+ // /
+ // ->b--->y
+ // /
+ // A-->iptr->(int value)
+ // \
+ // ->bptr
+ //
+ // From this we'll construct a vector of fieldchains, where each fieldchain
+ // represents an uninitialized field. An uninitialized field may be a
+ // primitive object, a member pointer, a pointer, a pointee or a union without
+ // a single initialized field.
+ // In the above example, for the default constructor call we'll end up with
+ // these fieldchains:
+ //
+ // this->b.x
+ // this->iptr (pointee uninit)
+ // this->bptr (pointer uninit)
+ //
+ // We'll traverse each node of the above graph with the appropiate one of
+ // these methods:
+
+ /// This method checks a region of a union object, and returns true if no
+ /// field is initialized within the region.
+ bool isUnionUninit(const TypedValueRegion *R);
+
+ /// This method checks a region of a non-union object, and returns true if
+ /// an uninitialized field is found within the region.
+ bool isNonUnionUninit(const TypedValueRegion *R, FieldChainInfo LocalChain);
+
+ /// This method checks a region of a pointer or reference object, and returns
+ /// true if the ptr/ref object itself or any field within the pointee's region
+ /// is uninitialized.
+ bool isPointerOrReferenceUninit(const FieldRegion *FR,
+ FieldChainInfo LocalChain);
+
+ /// This method checks a region of MemberPointerType, and returns true if the
+ /// the pointer is uninitialized.
+ bool isMemberPointerUninit(const FieldRegion *FR, FieldChainInfo LocalChain);
+
+ /// This method returns true if the value of a primitive object is
+ /// uninitialized.
+ bool isPrimitiveUninit(const SVal &V);
+
+ // Note that we don't have a method for arrays -- the elements of an array are
+ // often left uninitialized intentionally even when it is of a C++ record
+ // type, so we'll assume that an array is always initialized.
+ // TODO: Add a support for nonloc::LocAsInteger.
+};
+
+// Utility function declarations.
+
+/// Returns the object that was constructed by CtorDecl, or None if that isn't
+/// possible.
+Optional<nonloc::LazyCompoundVal>
+getObjectVal(const CXXConstructorDecl *CtorDecl, CheckerContext &Context);
+
+/// Checks whether the constructor under checking is called by another
+/// constructor.
+bool isCalledByConstructor(const CheckerContext &Context);
+
+/// Returns whether FD can be (transitively) dereferenced to a void pointer type
+/// (void*, void**, ...). The type of the region behind a void pointer isn't
+/// known, and thus FD can not be analyzed.
+bool isVoidPointer(const FieldDecl *FD);
+
+/// Returns true if T is a primitive type. We'll call a type primitive if it's
+/// either a BuiltinType or an EnumeralType.
+bool isPrimitiveType(const QualType &T) {
+ return T->isBuiltinType() || T->isEnumeralType();
+}
+
+} // end of anonymous namespace
+
+//===----------------------------------------------------------------------===//
+// Methods for UninitializedObjectChecker.
+//===----------------------------------------------------------------------===//
+
+void UninitializedObjectChecker::checkEndFunction(
+ CheckerContext &Context) const {
+
+ const auto *CtorDecl = dyn_cast_or_null<CXXConstructorDecl>(
+ Context.getLocationContext()->getDecl());
+ if (!CtorDecl)
+ return;
+
+ if (!CtorDecl->isUserProvided())
+ return;
+
+ if (CtorDecl->getParent()->isUnion())
+ return;
+
+ // This avoids essentially the same error being reported multiple times.
+ if (isCalledByConstructor(Context))
+ return;
+
+ Optional<nonloc::LazyCompoundVal> Object = getObjectVal(CtorDecl, Context);
+ if (!Object)
+ return;
+
+ FindUninitializedFields F(Context.getState(), Object->getRegion(),
+ IsPedantic);
+
+ const UninitFieldSet &UninitFields = F.getUninitFields();
+
+ if (UninitFields.empty())
+ return;
+
+ // There are uninitialized fields in the record.
+
+ ExplodedNode *Node = Context.generateNonFatalErrorNode(Context.getState());
+ if (!Node)
+ return;
+
+ PathDiagnosticLocation LocUsedForUniqueing;
+ const Stmt *CallSite = Context.getStackFrame()->getCallSite();
+ if (CallSite)
+ LocUsedForUniqueing = PathDiagnosticLocation::createBegin(
+ CallSite, Context.getSourceManager(), Node->getLocationContext());
+
+ SmallString<100> WarningBuf;
+ llvm::raw_svector_ostream WarningOS(WarningBuf);
+ WarningOS << UninitFields.size() << " uninitialized field"
+ << (UninitFields.size() == 1 ? "" : "s")
+ << " at the end of the constructor call";
+
+ auto Report = llvm::make_unique<BugReport>(
+ *BT_uninitField, WarningOS.str(), Node, LocUsedForUniqueing,
+ Node->getLocationContext()->getDecl());
+
+ // TODO: As of now, one warning is emitted per constructor call, and the
+ // uninitialized fields are listed in notes. Until there's a better support
+ // for notes avaible, a note-less version of this checker should be
+ // implemented.
+ for (const auto &FieldChain : UninitFields) {
+ SmallString<200> NoteBuf;
+ llvm::raw_svector_ostream NoteOS(NoteBuf);
+
+ if (FieldChain.isPointer()) {
+ if (FieldChain.isDereferenced())
+ NoteOS << "uninitialized pointee 'this->";
+ else
+ NoteOS << "uninitialized pointer 'this->";
+ } else
+ NoteOS << "uninitialized field 'this->";
+ FieldChain.print(NoteOS);
+ NoteOS << "'";
+
+ Report->addNote(NoteOS.str(),
+ PathDiagnosticLocation::create(FieldChain.getEndOfChain(),
+ Context.getSourceManager()));
+ }
+
+ Context.emitReport(std::move(Report));
+}
+
+//===----------------------------------------------------------------------===//
+// Methods for FindUninitializedFields.
+//===----------------------------------------------------------------------===//
+
+FindUninitializedFields::FindUninitializedFields(
+ ProgramStateRef State, const TypedValueRegion *const R, bool IsPedantic)
+ : State(State), ObjectR(R), IsPedantic(IsPedantic) {}
+
+const UninitFieldSet &FindUninitializedFields::getUninitFields() {
+ isNonUnionUninit(ObjectR, FieldChainInfo());
+
+ if (!IsPedantic && !IsAnyFieldInitialized)
+ UninitFields.clear();
+
+ return UninitFields;
+}
+
+bool FindUninitializedFields::addFieldToUninits(FieldChainInfo Chain) {
+ if (State->getStateManager().getContext().getSourceManager().isInSystemHeader(
+ Chain.getEndOfChain()->getLocation()))
+ return false;
+
+ return UninitFields.insert(Chain).second;
+}
+
+bool FindUninitializedFields::isNonUnionUninit(const TypedValueRegion *R,
+ FieldChainInfo LocalChain) {
+ assert(R->getValueType()->isRecordType() &&
+ !R->getValueType()->isUnionType() &&
+ "This method only checks non-union record objects!");
+
+ const RecordDecl *RD =
+ R->getValueType()->getAs<RecordType>()->getDecl()->getDefinition();
+ assert(RD && "Referred record has no definition");
+
+ bool ContainsUninitField = false;
+
+ // Are all of this non-union's fields initialized?
+ for (const FieldDecl *I : RD->fields()) {
+
+ const auto FieldVal =
+ State->getLValue(I, loc::MemRegionVal(R)).castAs<loc::MemRegionVal>();
+ const auto *FR = FieldVal.getRegionAs<FieldRegion>();
+ QualType T = I->getType();
+
+ // If LocalChain already contains FR, then we encountered a cyclic
+ // reference. In this case, region FR is already under checking at an
+ // earlier node in the directed tree.
+ if (LocalChain.contains(FR))
+ return false;
+
+ if (T->isStructureOrClassType()) {
+ if (isNonUnionUninit(FR, {LocalChain, FR}))
+ ContainsUninitField = true;
+ continue;
+ }
+
+ if (T->isUnionType()) {
+ if (isUnionUninit(FR)) {
+ if (addFieldToUninits({LocalChain, FR}))
+ ContainsUninitField = true;
+ } else
+ IsAnyFieldInitialized = true;
+ continue;
+ }
+
+ if (T->isArrayType()) {
+ IsAnyFieldInitialized = true;
+ continue;
+ }
+
+ if (T->isMemberPointerType()) {
+ if (isMemberPointerUninit(FR, LocalChain))
+ ContainsUninitField = true;
+ continue;
+ }
+
+ // If this is a pointer or reference type.
+ if (T->isPointerType() || T->isReferenceType()) {
+ if (isPointerOrReferenceUninit(FR, LocalChain))
+ ContainsUninitField = true;
+ continue;
+ }
+
+ assert(isPrimitiveType(T) && "Non-primitive type! "
+ "At this point FR must be primitive!");
+
+ SVal V = State->getSVal(FieldVal);
+
+ if (isPrimitiveUninit(V)) {
+ if (addFieldToUninits({LocalChain, FR}))
+ ContainsUninitField = true;
+ }
+ }
+
+ // Checking bases.
+ // FIXME: As of now, because of `isCalledByConstructor`, objects whose type
+ // is a descendant of another type will emit warnings for uninitalized
+ // inherited members.
+ // This is not the only way to analyze bases of an object -- if we didn't
+ // filter them out, and didn't analyze the bases, this checker would run for
+ // each base of the object in order of base initailization and in theory would
+ // find every uninitalized field. This approach could also make handling
+ // diamond inheritances more easily.
+ //
+ // This rule (that a descendant type's cunstructor is responsible for
+ // initializing inherited data members) is not obvious, and should it should
+ // be.
+ const auto *CXXRD = dyn_cast<CXXRecordDecl>(RD);
+ if (!CXXRD)
+ return ContainsUninitField;
+
+ for (const CXXBaseSpecifier &BaseSpec : CXXRD->bases()) {
+ const auto *BaseRegion = State->getLValue(BaseSpec, R)
+ .castAs<loc::MemRegionVal>()
+ .getRegionAs<TypedValueRegion>();
+
+ if (isNonUnionUninit(BaseRegion, LocalChain))
+ ContainsUninitField = true;
+ }
+
+ return ContainsUninitField;
+}
+
+bool FindUninitializedFields::isUnionUninit(const TypedValueRegion *R) {
+ assert(R->getValueType()->isUnionType() &&
+ "This method only checks union objects!");
+ // TODO: Implement support for union fields.
+ return false;
+}
+
+// Note that pointers/references don't contain fields themselves, so in this
+// function we won't add anything to LocalChain.
+bool FindUninitializedFields::isPointerOrReferenceUninit(
+ const FieldRegion *FR, FieldChainInfo LocalChain) {
+
+ assert((FR->getDecl()->getType()->isPointerType() ||
+ FR->getDecl()->getType()->isReferenceType()) &&
+ "This method only checks pointer/reference objects!");
+
+ SVal V = State->getSVal(FR);
+
+ if (V.isUnknown() || V.isZeroConstant()) {
+ IsAnyFieldInitialized = true;
+ return false;
+ }
+
+ if (V.isUndef()) {
+ return addFieldToUninits({LocalChain, FR});
+ }
+
+ const FieldDecl *FD = FR->getDecl();
+
+ // TODO: The dynamic type of a void pointer may be retrieved with
+ // `getDynamicTypeInfo`.
+ if (isVoidPointer(FD)) {
+ IsAnyFieldInitialized = true;
+ return false;
+ }
+
+ assert(V.getAs<Loc>() && "V should be Loc at this point!");
+
+ // At this point the pointer itself is initialized and points to a valid
+ // location, we'll now check the pointee.
+ SVal DerefdV = State->getSVal(V.castAs<Loc>());
+
+ // TODO: Dereferencing should be done according to the dynamic type.
+ while (Optional<Loc> L = DerefdV.getAs<Loc>()) {
+ DerefdV = State->getSVal(*L);
+ }
+
+ // If V is a pointer pointing to a record type.
+ if (Optional<nonloc::LazyCompoundVal> RecordV =
+ DerefdV.getAs<nonloc::LazyCompoundVal>()) {
+
+ const TypedValueRegion *R = RecordV->getRegion();
+
+ // We can't reason about symbolic regions, assume its initialized.
+ // Note that this also avoids a potential infinite recursion, because
+ // constructors for list-like classes are checked without being called, and
+ // the Static Analyzer will construct a symbolic region for Node *next; or
+ // similar code snippets.
+ if (R->getSymbolicBase()) {
+ IsAnyFieldInitialized = true;
+ return false;
+ }
+
+ const QualType T = R->getValueType();
+
+ if (T->isStructureOrClassType())
+ return isNonUnionUninit(R, {LocalChain, FR});
+
+ if (T->isUnionType()) {
+ if (isUnionUninit(R)) {
+ return addFieldToUninits({LocalChain, FR, /*IsDereferenced*/ true});
+ } else {
+ IsAnyFieldInitialized = true;
+ return false;
+ }
+ }
+
+ if (T->isArrayType()) {
+ IsAnyFieldInitialized = true;
+ return false;
+ }
+
+ llvm_unreachable("All cases are handled!");
+ }
+
+ // TODO: If possible, it should be asserted that the DerefdV at this point is
+ // primitive.
+
+ if (isPrimitiveUninit(DerefdV))
+ return addFieldToUninits({LocalChain, FR, /*IsDereferenced*/ true});
+
+ IsAnyFieldInitialized = true;
+ return false;
+}
+
+bool FindUninitializedFields::isMemberPointerUninit(const FieldRegion *FR,
+ FieldChainInfo LocalChain) {
+ assert(FR->getDecl()->getType()->isMemberPointerType() &&
+ "This function only checks regions that hold MemberPointerTypes!");
+ // TODO: Implement support for MemberPointerTypes.
+ return false;
+}
+
+bool FindUninitializedFields::isPrimitiveUninit(const SVal &V) {
+ if (V.isUndef())
+ return true;
+
+ IsAnyFieldInitialized = true;
+ return false;
+}
+
+//===----------------------------------------------------------------------===//
+// Methods for FieldChainInfo.
+//===----------------------------------------------------------------------===//
+
+FieldChainInfo::FieldChainInfo(const FieldChainInfo &Other,
+ const FieldRegion *FR, const bool IsDereferenced)
+ : FieldChainInfo(Other, IsDereferenced) {
+ assert(!contains(FR) && "Can't add a field that is already a part of the "
+ "fieldchain! Is this a cyclic reference?");
+ Chain = Factory.add(FR, Other.Chain);
+}
+
+bool FieldChainInfo::isPointer() const {
+ assert(!Chain.isEmpty() && "Empty fieldchain!");
+ return (*Chain.begin())->getDecl()->getType()->isPointerType();
+}
+
+bool FieldChainInfo::isDereferenced() const {
+ assert(isPointer() && "Only pointers may or may not be dereferenced!");
+ return IsDereferenced;
+}
+
+const FieldDecl *FieldChainInfo::getEndOfChain() const {
+ assert(!Chain.isEmpty() && "Empty fieldchain!");
+ return (*Chain.begin())->getDecl();
+}
+
+// TODO: This function constructs an incorrect fieldchain string in the
+// following case:
+//
+// struct Base { int x; };
+// struct D1 : Base {}; struct D2 : Base {};
+//
+// struct MostDerived : D1, D2 {
+// MostDerived() {}
+// }
+//
+// A call to MostDerived::MostDerived() will cause two notes that say
+// "uninitialized field 'this->x'", but we can't refer to 'x' directly,
+// we need an explicit namespace resolution whether the uninit field was
+// 'D1::x' or 'D2::x'.
+//
+// TODO: If a field in the fieldchain is a captured lambda parameter, this
+// function constructs an empty string for it:
+//
+// template <class Callable> struct A {
+// Callable c;
+// A(const Callable &c, int) : c(c) {}
+// };
+//
+// int b; // say that this isn't zero initialized
+// auto alwaysTrue = [&b](int a) { return true; };
+//
+// A call with these parameters: A<decltype(alwaysTrue)>::A(alwaysTrue, int())
+// will emit a note with the message "uninitialized field: 'this->c.'". If
+// possible, the lambda parameter name should be retrieved or be replaced with a
+// "<lambda parameter>" or something similar.
+void FieldChainInfo::print(llvm::raw_ostream &Out) const {
+ if (Chain.isEmpty())
+ return;
+
+ const llvm::ImmutableListImpl<const FieldRegion *> *L =
+ Chain.getInternalPointer();
+ printTail(Out, L->getTail());
+ Out << L->getHead()->getDecl()->getNameAsString();
+}
+
+void FieldChainInfo::printTail(
+ llvm::raw_ostream &Out,
+ const llvm::ImmutableListImpl<const FieldRegion *> *L) {
+ if (!L)
+ return;
+
+ printTail(Out, L->getTail());
+ const FieldDecl *Field = L->getHead()->getDecl();
+ Out << Field->getNameAsString();
+ Out << (Field->getType()->isPointerType() ? "->" : ".");
+}
+
+//===----------------------------------------------------------------------===//
+// Utility functions.
+//===----------------------------------------------------------------------===//
+
+namespace {
+
+bool isVoidPointer(const FieldDecl *FD) {
+ QualType T = FD->getType();
+
+ while (!T.isNull()) {
+ if (T->isVoidPointerType())
+ return true;
+ T = T->getPointeeType();
+ }
+ return false;
+}
+
+Optional<nonloc::LazyCompoundVal>
+getObjectVal(const CXXConstructorDecl *CtorDecl, CheckerContext &Context) {
+
+ Loc ThisLoc = Context.getSValBuilder().getCXXThis(CtorDecl->getParent(),
+ Context.getStackFrame());
+ // Getting the value for 'this'.
+ SVal This = Context.getState()->getSVal(ThisLoc);
+
+ // Getting the value for '*this'.
+ SVal Object = Context.getState()->getSVal(This.castAs<Loc>());
+
+ return Object.getAs<nonloc::LazyCompoundVal>();
+}
+
+// TODO: We should also check that if the constructor was called by another
+// constructor, whether those two are in any relation to one another. In it's
+// current state, this introduces some false negatives.
+bool isCalledByConstructor(const CheckerContext &Context) {
+ const LocationContext *LC = Context.getLocationContext()->getParent();
+
+ while (LC) {
+ if (isa<CXXConstructorDecl>(LC->getDecl()))
+ return true;
+
+ LC = LC->getParent();
+ }
+ return false;
+}
+
+} // end of anonymous namespace
+
+void ento::registerUninitializedObjectChecker(CheckerManager &Mgr) {
+ auto Chk = Mgr.registerChecker<UninitializedObjectChecker>();
+ Chk->IsPedantic = Mgr.getAnalyzerOptions().getBooleanOption(
+ "Pedantic", /*DefaultVal*/ false, Chk);
+}
OpenPOWER on IntegriCloud