Skip to content

Commit ffe93a1

Browse files
committed
[analyzer][UninitializedObjectChecker] New flag to ignore guarded uninitialized fields
This patch is an implementation of the ideas discussed on the mailing list[1]. The idea is to somewhat heuristically guess whether the field that was confirmed to be uninitialized is actually guarded with ifs, asserts, switch/cases and so on. Since this is a syntactic check, it is very much prone to drastically reduce the amount of reports the checker emits. The reports however that do not get filtered out though have greater likelihood of them manifesting into actual runtime errors. [1] http://lists.llvm.org/pipermail/cfe-dev/2018-September/059255.html Differential Revision: https://reviews.llvm.org/D51866 llvm-svn: 352959
1 parent 509b48a commit ffe93a1

File tree

3 files changed

+550
-13
lines changed

3 files changed

+550
-13
lines changed

clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedObject.h

+13-5
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,25 @@
3434
// `-analyzer-config \
3535
// alpha.cplusplus.UninitializedObject:CheckPointeeInitialization=true`.
3636
//
37+
// TODO: With some clever heuristics, some pointers should be dereferenced
38+
// by default. For example, if the pointee is constructed within the
39+
// constructor call, it's reasonable to say that no external object
40+
// references it, and we wouldn't generate multiple report on the same
41+
// pointee.
42+
//
3743
// - "IgnoreRecordsWithField" (string). If supplied, the checker will not
3844
// analyze structures that have a field with a name or type name that
3945
// matches the given pattern. Defaults to "".
4046
//
4147
// `-analyzer-config \
4248
// alpha.cplusplus.UninitializedObject:IgnoreRecordsWithField="[Tt]ag|[Kk]ind"`.
4349
//
44-
// TODO: With some clever heuristics, some pointers should be dereferenced
45-
// by default. For example, if the pointee is constructed within the
46-
// constructor call, it's reasonable to say that no external object
47-
// references it, and we wouldn't generate multiple report on the same
48-
// pointee.
50+
// - "IgnoreGuardedFields" (boolean). If set to true, the checker will analyze
51+
// _syntactically_ whether the found uninitialized object is used without a
52+
// preceding assert call. Defaults to false.
53+
//
54+
// `-analyzer-config \
55+
// alpha.cplusplus.UninitializedObject:IgnoreGuardedFields=true`.
4956
//
5057
// Most of the following methods as well as the checker itself is defined in
5158
// UninitializedObjectChecker.cpp.
@@ -68,6 +75,7 @@ struct UninitObjCheckerOptions {
6875
bool ShouldConvertNotesToWarnings = false;
6976
bool CheckPointeeInitialization = false;
7077
std::string IgnoredRecordsWithFieldPattern;
78+
bool IgnoreGuardedFields = false;
7179
};
7280

7381
/// A lightweight polymorphic wrapper around FieldRegion *. We'll use this

clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedObjectChecker.cpp

+97-8
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@
1919

2020
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
2121
#include "UninitializedObject.h"
22+
#include "clang/ASTMatchers/ASTMatchFinder.h"
2223
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
2324
#include "clang/StaticAnalyzer/Core/Checker.h"
2425
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
2526
#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h"
2627

2728
using namespace clang;
2829
using namespace clang::ento;
30+
using namespace clang::ast_matchers;
2931

3032
/// We'll mark fields (and pointee of fields) that are confirmed to be
3133
/// uninitialized as already analyzed.
@@ -118,6 +120,16 @@ static bool willObjectBeAnalyzedLater(const CXXConstructorDecl *Ctor,
118120
/// \p Pattern.
119121
static bool shouldIgnoreRecord(const RecordDecl *RD, StringRef Pattern);
120122

123+
/// Checks _syntactically_ whether it is possible to access FD from the record
124+
/// that contains it without a preceding assert (even if that access happens
125+
/// inside a method). This is mainly used for records that act like unions, like
126+
/// having multiple bit fields, with only a fraction being properly initialized.
127+
/// If these fields are properly guarded with asserts, this method returns
128+
/// false.
129+
///
130+
/// Since this check is done syntactically, this method could be inaccurate.
131+
static bool hasUnguardedAccess(const FieldDecl *FD, ProgramStateRef State);
132+
121133
//===----------------------------------------------------------------------===//
122134
// Methods for UninitializedObjectChecker.
123135
//===----------------------------------------------------------------------===//
@@ -234,6 +246,13 @@ bool FindUninitializedFields::addFieldToUninits(FieldChainInfo Chain,
234246
"One must also pass the pointee region as a parameter for "
235247
"dereferenceable fields!");
236248

249+
if (State->getStateManager().getContext().getSourceManager().isInSystemHeader(
250+
FR->getDecl()->getLocation()))
251+
return false;
252+
253+
if (Opts.IgnoreGuardedFields && !hasUnguardedAccess(FR->getDecl(), State))
254+
return false;
255+
237256
if (State->contains<AnalyzedRegions>(FR))
238257
return false;
239258

@@ -246,13 +265,10 @@ bool FindUninitializedFields::addFieldToUninits(FieldChainInfo Chain,
246265

247266
State = State->add<AnalyzedRegions>(FR);
248267

249-
if (State->getStateManager().getContext().getSourceManager().isInSystemHeader(
250-
FR->getDecl()->getLocation()))
251-
return false;
252-
253268
UninitFieldMap::mapped_type NoteMsgBuf;
254269
llvm::raw_svector_ostream OS(NoteMsgBuf);
255270
Chain.printNoteMsg(OS);
271+
256272
return UninitFields.insert({FR, std::move(NoteMsgBuf)}).second;
257273
}
258274

@@ -441,8 +457,8 @@ static const TypedValueRegion *
441457
getConstructedRegion(const CXXConstructorDecl *CtorDecl,
442458
CheckerContext &Context) {
443459

444-
Loc ThisLoc = Context.getSValBuilder().getCXXThis(CtorDecl,
445-
Context.getStackFrame());
460+
Loc ThisLoc =
461+
Context.getSValBuilder().getCXXThis(CtorDecl, Context.getStackFrame());
446462

447463
SVal ObjectV = Context.getState()->getSVal(ThisLoc);
448464

@@ -495,6 +511,75 @@ static bool shouldIgnoreRecord(const RecordDecl *RD, StringRef Pattern) {
495511
return false;
496512
}
497513

514+
static const Stmt *getMethodBody(const CXXMethodDecl *M) {
515+
if (isa<CXXConstructorDecl>(M))
516+
return nullptr;
517+
518+
if (!M->isDefined())
519+
return nullptr;
520+
521+
return M->getDefinition()->getBody();
522+
}
523+
524+
static bool hasUnguardedAccess(const FieldDecl *FD, ProgramStateRef State) {
525+
526+
if (FD->getAccess() == AccessSpecifier::AS_public)
527+
return true;
528+
529+
const auto *Parent = dyn_cast<CXXRecordDecl>(FD->getParent());
530+
531+
if (!Parent)
532+
return true;
533+
534+
Parent = Parent->getDefinition();
535+
assert(Parent && "The record's definition must be avaible if an uninitialized"
536+
" field of it was found!");
537+
538+
ASTContext &AC = State->getStateManager().getContext();
539+
540+
auto FieldAccessM = memberExpr(hasDeclaration(equalsNode(FD))).bind("access");
541+
542+
auto AssertLikeM = callExpr(callee(functionDecl(
543+
anyOf(hasName("exit"), hasName("panic"), hasName("error"),
544+
hasName("Assert"), hasName("assert"), hasName("ziperr"),
545+
hasName("assfail"), hasName("db_error"), hasName("__assert"),
546+
hasName("__assert2"), hasName("_wassert"), hasName("__assert_rtn"),
547+
hasName("__assert_fail"), hasName("dtrace_assfail"),
548+
hasName("yy_fatal_error"), hasName("_XCAssertionFailureHandler"),
549+
hasName("_DTAssertionFailureHandler"),
550+
hasName("_TSAssertionFailureHandler")))));
551+
552+
auto NoReturnFuncM = callExpr(callee(functionDecl(isNoReturn())));
553+
554+
auto GuardM =
555+
stmt(anyOf(ifStmt(), switchStmt(), conditionalOperator(), AssertLikeM,
556+
NoReturnFuncM))
557+
.bind("guard");
558+
559+
for (const CXXMethodDecl *M : Parent->methods()) {
560+
const Stmt *MethodBody = getMethodBody(M);
561+
if (!MethodBody)
562+
continue;
563+
564+
auto Accesses = match(stmt(hasDescendant(FieldAccessM)), *MethodBody, AC);
565+
if (Accesses.empty())
566+
continue;
567+
const auto *FirstAccess = Accesses[0].getNodeAs<MemberExpr>("access");
568+
assert(FirstAccess);
569+
570+
auto Guards = match(stmt(hasDescendant(GuardM)), *MethodBody, AC);
571+
if (Guards.empty())
572+
return true;
573+
const auto *FirstGuard = Guards[0].getNodeAs<Stmt>("guard");
574+
assert(FirstGuard);
575+
576+
if (FirstAccess->getBeginLoc() < FirstGuard->getBeginLoc())
577+
return true;
578+
}
579+
580+
return false;
581+
}
582+
498583
std::string clang::ento::getVariableName(const FieldDecl *Field) {
499584
// If Field is a captured lambda variable, Field->getName() will return with
500585
// an empty string. We can however acquire it's name from the lambda's
@@ -528,12 +613,16 @@ void ento::registerUninitializedObjectChecker(CheckerManager &Mgr) {
528613
ChOpts.IsPedantic =
529614
AnOpts.getCheckerBooleanOption("Pedantic", /*DefaultVal*/ false, Chk);
530615
ChOpts.ShouldConvertNotesToWarnings =
531-
AnOpts.getCheckerBooleanOption("NotesAsWarnings", /*DefaultVal*/ false, Chk);
616+
AnOpts.getCheckerBooleanOption("NotesAsWarnings",
617+
/*DefaultVal*/ false, Chk);
532618
ChOpts.CheckPointeeInitialization = AnOpts.getCheckerBooleanOption(
533619
"CheckPointeeInitialization", /*DefaultVal*/ false, Chk);
534620
ChOpts.IgnoredRecordsWithFieldPattern =
535621
AnOpts.getCheckerStringOption("IgnoreRecordsWithField",
536-
/*DefaultVal*/ "", Chk);
622+
/*DefaultVal*/ "", Chk);
623+
ChOpts.IgnoreGuardedFields =
624+
AnOpts.getCheckerBooleanOption("IgnoreGuardedFields",
625+
/*DefaultVal*/ false, Chk);
537626
}
538627

539628
bool ento::shouldRegisterUninitializedObjectChecker(const LangOptions &LO) {

0 commit comments

Comments
 (0)