|
@@ -19,6 +19,7 @@
|
|
|
|
|
|
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
|
|
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
|
|
#include "UninitializedObject.h"
|
|
#include "UninitializedObject.h"
|
|
|
|
+#include "clang/ASTMatchers/ASTMatchFinder.h"
|
|
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
|
|
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
|
|
#include "clang/StaticAnalyzer/Core/Checker.h"
|
|
#include "clang/StaticAnalyzer/Core/Checker.h"
|
|
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
|
|
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
|
|
@@ -26,6 +27,7 @@
|
|
|
|
|
|
using namespace clang;
|
|
using namespace clang;
|
|
using namespace clang::ento;
|
|
using namespace clang::ento;
|
|
|
|
+using namespace clang::ast_matchers;
|
|
|
|
|
|
/// We'll mark fields (and pointee of fields) that are confirmed to be
|
|
/// We'll mark fields (and pointee of fields) that are confirmed to be
|
|
/// uninitialized as already analyzed.
|
|
/// uninitialized as already analyzed.
|
|
@@ -118,6 +120,16 @@ static bool willObjectBeAnalyzedLater(const CXXConstructorDecl *Ctor,
|
|
/// \p Pattern.
|
|
/// \p Pattern.
|
|
static bool shouldIgnoreRecord(const RecordDecl *RD, StringRef Pattern);
|
|
static bool shouldIgnoreRecord(const RecordDecl *RD, StringRef Pattern);
|
|
|
|
|
|
|
|
+/// Checks _syntactically_ whether it is possible to access FD from the record
|
|
|
|
+/// that contains it without a preceding assert (even if that access happens
|
|
|
|
+/// inside a method). This is mainly used for records that act like unions, like
|
|
|
|
+/// having multiple bit fields, with only a fraction being properly initialized.
|
|
|
|
+/// If these fields are properly guarded with asserts, this method returns
|
|
|
|
+/// false.
|
|
|
|
+///
|
|
|
|
+/// Since this check is done syntactically, this method could be inaccurate.
|
|
|
|
+static bool hasUnguardedAccess(const FieldDecl *FD, ProgramStateRef State);
|
|
|
|
+
|
|
//===----------------------------------------------------------------------===//
|
|
//===----------------------------------------------------------------------===//
|
|
// Methods for UninitializedObjectChecker.
|
|
// Methods for UninitializedObjectChecker.
|
|
//===----------------------------------------------------------------------===//
|
|
//===----------------------------------------------------------------------===//
|
|
@@ -234,6 +246,13 @@ bool FindUninitializedFields::addFieldToUninits(FieldChainInfo Chain,
|
|
"One must also pass the pointee region as a parameter for "
|
|
"One must also pass the pointee region as a parameter for "
|
|
"dereferenceable fields!");
|
|
"dereferenceable fields!");
|
|
|
|
|
|
|
|
+ if (State->getStateManager().getContext().getSourceManager().isInSystemHeader(
|
|
|
|
+ FR->getDecl()->getLocation()))
|
|
|
|
+ return false;
|
|
|
|
+
|
|
|
|
+ if (Opts.IgnoreGuardedFields && !hasUnguardedAccess(FR->getDecl(), State))
|
|
|
|
+ return false;
|
|
|
|
+
|
|
if (State->contains<AnalyzedRegions>(FR))
|
|
if (State->contains<AnalyzedRegions>(FR))
|
|
return false;
|
|
return false;
|
|
|
|
|
|
@@ -246,13 +265,10 @@ bool FindUninitializedFields::addFieldToUninits(FieldChainInfo Chain,
|
|
|
|
|
|
State = State->add<AnalyzedRegions>(FR);
|
|
State = State->add<AnalyzedRegions>(FR);
|
|
|
|
|
|
- if (State->getStateManager().getContext().getSourceManager().isInSystemHeader(
|
|
|
|
- FR->getDecl()->getLocation()))
|
|
|
|
- return false;
|
|
|
|
-
|
|
|
|
UninitFieldMap::mapped_type NoteMsgBuf;
|
|
UninitFieldMap::mapped_type NoteMsgBuf;
|
|
llvm::raw_svector_ostream OS(NoteMsgBuf);
|
|
llvm::raw_svector_ostream OS(NoteMsgBuf);
|
|
Chain.printNoteMsg(OS);
|
|
Chain.printNoteMsg(OS);
|
|
|
|
+
|
|
return UninitFields.insert({FR, std::move(NoteMsgBuf)}).second;
|
|
return UninitFields.insert({FR, std::move(NoteMsgBuf)}).second;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -441,8 +457,8 @@ static const TypedValueRegion *
|
|
getConstructedRegion(const CXXConstructorDecl *CtorDecl,
|
|
getConstructedRegion(const CXXConstructorDecl *CtorDecl,
|
|
CheckerContext &Context) {
|
|
CheckerContext &Context) {
|
|
|
|
|
|
- Loc ThisLoc = Context.getSValBuilder().getCXXThis(CtorDecl,
|
|
|
|
- Context.getStackFrame());
|
|
|
|
|
|
+ Loc ThisLoc =
|
|
|
|
+ Context.getSValBuilder().getCXXThis(CtorDecl, Context.getStackFrame());
|
|
|
|
|
|
SVal ObjectV = Context.getState()->getSVal(ThisLoc);
|
|
SVal ObjectV = Context.getState()->getSVal(ThisLoc);
|
|
|
|
|
|
@@ -495,6 +511,75 @@ static bool shouldIgnoreRecord(const RecordDecl *RD, StringRef Pattern) {
|
|
return false;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static const Stmt *getMethodBody(const CXXMethodDecl *M) {
|
|
|
|
+ if (isa<CXXConstructorDecl>(M))
|
|
|
|
+ return nullptr;
|
|
|
|
+
|
|
|
|
+ if (!M->isDefined())
|
|
|
|
+ return nullptr;
|
|
|
|
+
|
|
|
|
+ return M->getDefinition()->getBody();
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static bool hasUnguardedAccess(const FieldDecl *FD, ProgramStateRef State) {
|
|
|
|
+
|
|
|
|
+ if (FD->getAccess() == AccessSpecifier::AS_public)
|
|
|
|
+ return true;
|
|
|
|
+
|
|
|
|
+ const auto *Parent = dyn_cast<CXXRecordDecl>(FD->getParent());
|
|
|
|
+
|
|
|
|
+ if (!Parent)
|
|
|
|
+ return true;
|
|
|
|
+
|
|
|
|
+ Parent = Parent->getDefinition();
|
|
|
|
+ assert(Parent && "The record's definition must be avaible if an uninitialized"
|
|
|
|
+ " field of it was found!");
|
|
|
|
+
|
|
|
|
+ ASTContext &AC = State->getStateManager().getContext();
|
|
|
|
+
|
|
|
|
+ auto FieldAccessM = memberExpr(hasDeclaration(equalsNode(FD))).bind("access");
|
|
|
|
+
|
|
|
|
+ auto AssertLikeM = callExpr(callee(functionDecl(
|
|
|
|
+ anyOf(hasName("exit"), hasName("panic"), hasName("error"),
|
|
|
|
+ hasName("Assert"), hasName("assert"), hasName("ziperr"),
|
|
|
|
+ hasName("assfail"), hasName("db_error"), hasName("__assert"),
|
|
|
|
+ hasName("__assert2"), hasName("_wassert"), hasName("__assert_rtn"),
|
|
|
|
+ hasName("__assert_fail"), hasName("dtrace_assfail"),
|
|
|
|
+ hasName("yy_fatal_error"), hasName("_XCAssertionFailureHandler"),
|
|
|
|
+ hasName("_DTAssertionFailureHandler"),
|
|
|
|
+ hasName("_TSAssertionFailureHandler")))));
|
|
|
|
+
|
|
|
|
+ auto NoReturnFuncM = callExpr(callee(functionDecl(isNoReturn())));
|
|
|
|
+
|
|
|
|
+ auto GuardM =
|
|
|
|
+ stmt(anyOf(ifStmt(), switchStmt(), conditionalOperator(), AssertLikeM,
|
|
|
|
+ NoReturnFuncM))
|
|
|
|
+ .bind("guard");
|
|
|
|
+
|
|
|
|
+ for (const CXXMethodDecl *M : Parent->methods()) {
|
|
|
|
+ const Stmt *MethodBody = getMethodBody(M);
|
|
|
|
+ if (!MethodBody)
|
|
|
|
+ continue;
|
|
|
|
+
|
|
|
|
+ auto Accesses = match(stmt(hasDescendant(FieldAccessM)), *MethodBody, AC);
|
|
|
|
+ if (Accesses.empty())
|
|
|
|
+ continue;
|
|
|
|
+ const auto *FirstAccess = Accesses[0].getNodeAs<MemberExpr>("access");
|
|
|
|
+ assert(FirstAccess);
|
|
|
|
+
|
|
|
|
+ auto Guards = match(stmt(hasDescendant(GuardM)), *MethodBody, AC);
|
|
|
|
+ if (Guards.empty())
|
|
|
|
+ return true;
|
|
|
|
+ const auto *FirstGuard = Guards[0].getNodeAs<Stmt>("guard");
|
|
|
|
+ assert(FirstGuard);
|
|
|
|
+
|
|
|
|
+ if (FirstAccess->getBeginLoc() < FirstGuard->getBeginLoc())
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return false;
|
|
|
|
+}
|
|
|
|
+
|
|
std::string clang::ento::getVariableName(const FieldDecl *Field) {
|
|
std::string clang::ento::getVariableName(const FieldDecl *Field) {
|
|
// If Field is a captured lambda variable, Field->getName() will return with
|
|
// If Field is a captured lambda variable, Field->getName() will return with
|
|
// an empty string. We can however acquire it's name from the lambda's
|
|
// an empty string. We can however acquire it's name from the lambda's
|
|
@@ -528,12 +613,16 @@ void ento::registerUninitializedObjectChecker(CheckerManager &Mgr) {
|
|
ChOpts.IsPedantic =
|
|
ChOpts.IsPedantic =
|
|
AnOpts.getCheckerBooleanOption("Pedantic", /*DefaultVal*/ false, Chk);
|
|
AnOpts.getCheckerBooleanOption("Pedantic", /*DefaultVal*/ false, Chk);
|
|
ChOpts.ShouldConvertNotesToWarnings =
|
|
ChOpts.ShouldConvertNotesToWarnings =
|
|
- AnOpts.getCheckerBooleanOption("NotesAsWarnings", /*DefaultVal*/ false, Chk);
|
|
|
|
|
|
+ AnOpts.getCheckerBooleanOption("NotesAsWarnings",
|
|
|
|
+ /*DefaultVal*/ false, Chk);
|
|
ChOpts.CheckPointeeInitialization = AnOpts.getCheckerBooleanOption(
|
|
ChOpts.CheckPointeeInitialization = AnOpts.getCheckerBooleanOption(
|
|
"CheckPointeeInitialization", /*DefaultVal*/ false, Chk);
|
|
"CheckPointeeInitialization", /*DefaultVal*/ false, Chk);
|
|
ChOpts.IgnoredRecordsWithFieldPattern =
|
|
ChOpts.IgnoredRecordsWithFieldPattern =
|
|
AnOpts.getCheckerStringOption("IgnoreRecordsWithField",
|
|
AnOpts.getCheckerStringOption("IgnoreRecordsWithField",
|
|
- /*DefaultVal*/ "", Chk);
|
|
|
|
|
|
+ /*DefaultVal*/ "", Chk);
|
|
|
|
+ ChOpts.IgnoreGuardedFields =
|
|
|
|
+ AnOpts.getCheckerBooleanOption("IgnoreGuardedFields",
|
|
|
|
+ /*DefaultVal*/ false, Chk);
|
|
}
|
|
}
|
|
|
|
|
|
bool ento::shouldRegisterUninitializedObjectChecker(const LangOptions &LO) {
|
|
bool ento::shouldRegisterUninitializedObjectChecker(const LangOptions &LO) {
|