aboutsummaryrefslogtreecommitdiffstats
path: root/lib/StaticAnalyzer/Checkers
diff options
context:
space:
mode:
authorDimitry Andric <dim@FreeBSD.org>2015-12-30 11:49:41 +0000
committerDimitry Andric <dim@FreeBSD.org>2015-12-30 11:49:41 +0000
commit45b533945f0851ec234ca846e1af5ee1e4df0b6e (patch)
tree0a5b74c0b9ca73aded34df95c91fcaf3815230d8 /lib/StaticAnalyzer/Checkers
parent7e86edd64bfae4e324224452e4ea879b3371a4bd (diff)
downloadsrc-45b533945f0851ec234ca846e1af5ee1e4df0b6e.tar.gz
src-45b533945f0851ec234ca846e1af5ee1e4df0b6e.zip
Vendor import of clang trunk r256633:vendor/clang/clang-trunk-r256633
Notes
Notes: svn path=/vendor/clang/dist/; revision=292920 svn path=/vendor/clang/clang-trunk-r256633/; revision=292923; tag=vendor/clang/clang-trunk-r256633
Diffstat (limited to 'lib/StaticAnalyzer/Checkers')
-rw-r--r--lib/StaticAnalyzer/Checkers/AnalyzerStatsChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp12
-rw-r--r--lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp44
-rw-r--r--lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp77
-rw-r--r--lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp46
-rw-r--r--lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp5
-rw-r--r--lib/StaticAnalyzer/Checkers/CMakeLists.txt6
-rw-r--r--lib/StaticAnalyzer/Checkers/CStringChecker.cpp199
-rw-r--r--lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp48
-rw-r--r--lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp7
-rw-r--r--lib/StaticAnalyzer/Checkers/CastToStructChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp4
-rw-r--r--lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp33
-rw-r--r--lib/StaticAnalyzer/Checkers/CheckSizeofPointer.cpp4
-rw-r--r--lib/StaticAnalyzer/Checkers/CheckerDocumentation.cpp22
-rw-r--r--lib/StaticAnalyzer/Checkers/Checkers.td118
-rw-r--r--lib/StaticAnalyzer/Checkers/ChrootChecker.cpp14
-rw-r--r--lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp71
-rw-r--r--lib/StaticAnalyzer/Checkers/DebugCheckers.cpp36
-rw-r--r--lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp43
-rw-r--r--lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp13
-rw-r--r--lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp4
-rw-r--r--lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp213
-rw-r--r--lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp711
-rw-r--r--lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp62
-rw-r--r--lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp4
-rw-r--r--lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp31
-rw-r--r--lib/StaticAnalyzer/Checkers/IdenticalExprChecker.cpp25
-rw-r--r--lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp6
-rw-r--r--lib/StaticAnalyzer/Checkers/LLVMConventionsChecker.cpp1
-rw-r--r--lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp1201
-rw-r--r--lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp33
-rw-r--r--lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp4
-rw-r--r--lib/StaticAnalyzer/Checkers/Makefile6
-rw-r--r--lib/StaticAnalyzer/Checkers/MallocChecker.cpp226
-rw-r--r--lib/StaticAnalyzer/Checkers/MallocOverflowSecurityChecker.cpp134
-rw-r--r--lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp8
-rw-r--r--lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp6
-rw-r--r--lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp4
-rw-r--r--lib/StaticAnalyzer/Checkers/NoReturnFunctionChecker.cpp7
-rw-r--r--lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp42
-rw-r--r--lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp1066
-rw-r--r--lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp4
-rw-r--r--lib/StaticAnalyzer/Checkers/ObjCContainersASTChecker.cpp5
-rw-r--r--lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp4
-rw-r--r--lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp8
-rw-r--r--lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp21
-rw-r--r--lib/StaticAnalyzer/Checkers/PaddingChecker.cpp314
-rw-r--r--lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp6
-rw-r--r--lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp6
-rw-r--r--lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp38
-rw-r--r--lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp132
-rw-r--r--lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp8
-rw-r--r--lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp8
-rw-r--r--lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp45
-rw-r--r--lib/StaticAnalyzer/Checkers/StreamChecker.cpp34
-rw-r--r--lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp6
-rw-r--r--lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp4
-rw-r--r--lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp16
-rw-r--r--lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp4
-rw-r--r--lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp16
-rw-r--r--lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp13
-rw-r--r--lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp10
-rw-r--r--lib/StaticAnalyzer/Checkers/VforkChecker.cpp218
-rw-r--r--lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp24
69 files changed, 4811 insertions, 741 deletions
diff --git a/lib/StaticAnalyzer/Checkers/AnalyzerStatsChecker.cpp b/lib/StaticAnalyzer/Checkers/AnalyzerStatsChecker.cpp
index 166471aaa1a6..a052d83f5afa 100644
--- a/lib/StaticAnalyzer/Checkers/AnalyzerStatsChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/AnalyzerStatsChecker.cpp
@@ -101,7 +101,7 @@ void AnalyzerStatsChecker::checkEndAnalysis(ExplodedGraph &G,
else if (isa<BlockDecl>(D)) {
output << "block(line:" << Loc.getLine() << ":col:" << Loc.getColumn();
}
-
+
NumBlocksUnreachable += unreachable;
NumBlocks += total;
std::string NameOfRootFunction = output.str();
diff --git a/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp b/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp
index 557439b28881..c092610afe2b 100644
--- a/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp
@@ -23,7 +23,7 @@ using namespace clang;
using namespace ento;
namespace {
-class ArrayBoundChecker :
+class ArrayBoundChecker :
public Checker<check::Location> {
mutable std::unique_ptr<BuiltinBug> BT;
@@ -55,17 +55,17 @@ void ArrayBoundChecker::checkLocation(SVal l, bool isLoad, const Stmt* LoadS,
ProgramStateRef state = C.getState();
// Get the size of the array.
- DefinedOrUnknownSVal NumElements
- = C.getStoreManager().getSizeInElements(state, ER->getSuperRegion(),
+ DefinedOrUnknownSVal NumElements
+ = C.getStoreManager().getSizeInElements(state, ER->getSuperRegion(),
ER->getValueType());
ProgramStateRef StInBound = state->assumeInBound(Idx, NumElements, true);
ProgramStateRef StOutBound = state->assumeInBound(Idx, NumElements, false);
if (StOutBound && !StInBound) {
- ExplodedNode *N = C.generateSink(StOutBound);
+ ExplodedNode *N = C.generateErrorNode(StOutBound);
if (!N)
return;
-
+
if (!BT)
BT.reset(new BuiltinBug(
this, "Out-of-bound array access",
@@ -82,7 +82,7 @@ void ArrayBoundChecker::checkLocation(SVal l, bool isLoad, const Stmt* LoadS,
C.emitReport(std::move(report));
return;
}
-
+
// Array bound check succeeded. From this point forward the array bound
// should always succeed.
C.addTransition(StInBound);
diff --git a/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp b/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp
index d8dc2aaa6363..f4de733bd794 100644
--- a/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp
+++ b/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp
@@ -26,15 +26,15 @@ using namespace clang;
using namespace ento;
namespace {
-class ArrayBoundCheckerV2 :
+class ArrayBoundCheckerV2 :
public Checker<check::Location> {
mutable std::unique_ptr<BuiltinBug> BT;
enum OOB_Kind { OOB_Precedes, OOB_Excedes, OOB_Tainted };
-
+
void reportOOB(CheckerContext &C, ProgramStateRef errorState,
OOB_Kind kind) const;
-
+
public:
void checkLocation(SVal l, bool isLoad, const Stmt*S,
CheckerContext &C) const;
@@ -55,7 +55,7 @@ public:
NonLoc getByteOffset() const { return byteOffset.castAs<NonLoc>(); }
const SubRegion *getRegion() const { return baseRegion; }
-
+
static RegionRawOffsetV2 computeOffset(ProgramStateRef state,
SValBuilder &svalBuilder,
SVal location);
@@ -65,12 +65,12 @@ public:
};
}
-static SVal computeExtentBegin(SValBuilder &svalBuilder,
+static SVal computeExtentBegin(SValBuilder &svalBuilder,
const MemRegion *region) {
while (true)
switch (region->getKind()) {
default:
- return svalBuilder.makeZeroArrayIndex();
+ return svalBuilder.makeZeroArrayIndex();
case MemRegion::SymbolicRegionKind:
// FIXME: improve this later by tracking symbolic lower bounds
// for symbolic regions.
@@ -94,22 +94,22 @@ void ArrayBoundCheckerV2::checkLocation(SVal location, bool isLoad,
// memory access is within the extent of the base region. Since we
// have some flexibility in defining the base region, we can achieve
// various levels of conservatism in our buffer overflow checking.
- ProgramStateRef state = checkerContext.getState();
+ ProgramStateRef state = checkerContext.getState();
ProgramStateRef originalState = state;
SValBuilder &svalBuilder = checkerContext.getSValBuilder();
- const RegionRawOffsetV2 &rawOffset =
+ const RegionRawOffsetV2 &rawOffset =
RegionRawOffsetV2::computeOffset(state, svalBuilder, location);
if (!rawOffset.getRegion())
return;
- // CHECK LOWER BOUND: Is byteOffset < extent begin?
+ // CHECK LOWER BOUND: Is byteOffset < extent begin?
// If so, we are doing a load/store
// before the first valid offset in the memory region.
SVal extentBegin = computeExtentBegin(svalBuilder, rawOffset.getRegion());
-
+
if (Optional<NonLoc> NV = extentBegin.getAs<NonLoc>()) {
SVal lowerBound =
svalBuilder.evalBinOpNN(state, BO_LT, rawOffset.getByteOffset(), *NV,
@@ -118,7 +118,7 @@ void ArrayBoundCheckerV2::checkLocation(SVal location, bool isLoad,
Optional<NonLoc> lowerBoundToCheck = lowerBound.getAs<NonLoc>();
if (!lowerBoundToCheck)
return;
-
+
ProgramStateRef state_precedesLowerBound, state_withinLowerBound;
std::tie(state_precedesLowerBound, state_withinLowerBound) =
state->assume(*lowerBoundToCheck);
@@ -128,12 +128,12 @@ void ArrayBoundCheckerV2::checkLocation(SVal location, bool isLoad,
reportOOB(checkerContext, state_precedesLowerBound, OOB_Precedes);
return;
}
-
+
// Otherwise, assume the constraint of the lower bound.
assert(state_withinLowerBound);
state = state_withinLowerBound;
}
-
+
do {
// CHECK UPPER BOUND: Is byteOffset >= extent(baseRegion)? If so,
// we are doing a load/store after the last valid offset.
@@ -146,11 +146,11 @@ void ArrayBoundCheckerV2::checkLocation(SVal location, bool isLoad,
= svalBuilder.evalBinOpNN(state, BO_GE, rawOffset.getByteOffset(),
extentVal.castAs<NonLoc>(),
svalBuilder.getConditionType());
-
+
Optional<NonLoc> upperboundToCheck = upperbound.getAs<NonLoc>();
if (!upperboundToCheck)
break;
-
+
ProgramStateRef state_exceedsUpperBound, state_withinUpperBound;
std::tie(state_exceedsUpperBound, state_withinUpperBound) =
state->assume(*upperboundToCheck);
@@ -161,19 +161,19 @@ void ArrayBoundCheckerV2::checkLocation(SVal location, bool isLoad,
reportOOB(checkerContext, state_exceedsUpperBound, OOB_Tainted);
return;
}
-
+
// If we are constrained enough to definitely exceed the upper bound, report.
if (state_exceedsUpperBound) {
assert(!state_withinUpperBound);
reportOOB(checkerContext, state_exceedsUpperBound, OOB_Excedes);
return;
}
-
+
assert(state_withinUpperBound);
state = state_withinUpperBound;
}
while (false);
-
+
if (state != originalState)
checkerContext.addTransition(state);
}
@@ -181,8 +181,8 @@ void ArrayBoundCheckerV2::checkLocation(SVal location, bool isLoad,
void ArrayBoundCheckerV2::reportOOB(CheckerContext &checkerContext,
ProgramStateRef errorState,
OOB_Kind kind) const {
-
- ExplodedNode *errorNode = checkerContext.generateSink(errorState);
+
+ ExplodedNode *errorNode = checkerContext.generateErrorNode(errorState);
if (!errorNode)
return;
@@ -259,7 +259,7 @@ RegionRawOffsetV2 RegionRawOffsetV2::computeOffset(ProgramStateRef state,
{
const MemRegion *region = location.getAsRegion();
SVal offset = UndefinedVal();
-
+
while (region) {
switch (region->getKind()) {
default: {
@@ -280,7 +280,7 @@ RegionRawOffsetV2 RegionRawOffsetV2::computeOffset(ProgramStateRef state,
ASTContext &astContext = svalBuilder.getContext();
if (elemType->isIncompleteType())
return RegionRawOffsetV2();
-
+
// Update the offset.
offset = addValue(state,
getValue(offset, svalBuilder),
diff --git a/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp b/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp
index f763284aa2c9..26d42ba59c22 100644
--- a/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp
+++ b/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp
@@ -140,10 +140,10 @@ void NilArgChecker::warnIfNilExpr(const Expr *E,
ProgramStateRef State = C.getState();
if (State->isNull(C.getSVal(E)).isConstrainedTrue()) {
- if (ExplodedNode *N = C.generateSink()) {
+ if (ExplodedNode *N = C.generateErrorNode()) {
generateBugReport(N, Msg, E->getSourceRange(), E, C);
}
-
+
}
}
@@ -156,8 +156,8 @@ void NilArgChecker::warnIfNilArg(CheckerContext &C,
ProgramStateRef State = C.getState();
if (!State->isNull(msg.getArgSVal(Arg)).isConstrainedTrue())
return;
-
- if (ExplodedNode *N = C.generateSink()) {
+
+ if (ExplodedNode *N = C.generateErrorNode()) {
SmallString<128> sbuf;
llvm::raw_svector_ostream os(sbuf);
@@ -193,7 +193,7 @@ void NilArgChecker::warnIfNilArg(CheckerContext &C,
os << "' cannot be nil";
}
}
-
+
generateBugReport(N, os.str(), msg.getArgSourceRange(Arg),
msg.getArgExpr(Arg), C);
}
@@ -224,7 +224,7 @@ void NilArgChecker::checkPreObjCMessage(const ObjCMethodCall &msg,
static const unsigned InvalidArgIndex = UINT_MAX;
unsigned Arg = InvalidArgIndex;
bool CanBeSubscript = false;
-
+
if (Class == FC_NSString) {
Selector S = msg.getSelector();
@@ -307,8 +307,7 @@ void NilArgChecker::checkPreObjCMessage(const ObjCMethodCall &msg,
warnIfNilArg(C, msg, /* Arg */1, Class);
} else if (S == SetObjectForKeyedSubscriptSel) {
CanBeSubscript = true;
- Arg = 0;
- warnIfNilArg(C, msg, /* Arg */1, Class, CanBeSubscript);
+ Arg = 1;
} else if (S == RemoveObjectForKeySel) {
Arg = 0;
}
@@ -433,7 +432,7 @@ void CFNumberCreateChecker::checkPreStmt(const CallExpr *CE,
const FunctionDecl *FD = C.getCalleeDecl(CE);
if (!FD)
return;
-
+
ASTContext &Ctx = C.getASTContext();
if (!II)
II = &Ctx.Idents.get("CFNumberCreate");
@@ -489,23 +488,24 @@ void CFNumberCreateChecker::checkPreStmt(const CallExpr *CE,
if (SourceSize == TargetSize)
return;
- // Generate an error. Only generate a sink if 'SourceSize < TargetSize';
- // otherwise generate a regular node.
+ // Generate an error. Only generate a sink error node
+ // if 'SourceSize < TargetSize'; otherwise generate a non-fatal error node.
//
// FIXME: We can actually create an abstract "CFNumber" object that has
// the bits initialized to the provided values.
//
- if (ExplodedNode *N = SourceSize < TargetSize ? C.generateSink()
- : C.addTransition()) {
+ ExplodedNode *N = SourceSize < TargetSize ? C.generateErrorNode()
+ : C.generateNonFatalErrorNode();
+ if (N) {
SmallString<128> sbuf;
llvm::raw_svector_ostream os(sbuf);
-
+
os << (SourceSize == 8 ? "An " : "A ")
<< SourceSize << " bit integer is used to initialize a CFNumber "
"object that represents "
<< (TargetSize == 8 ? "an " : "a ")
<< TargetSize << " bit integer. ";
-
+
if (SourceSize < TargetSize)
os << (TargetSize - SourceSize)
<< " bits of the CFNumber value will be garbage." ;
@@ -549,7 +549,7 @@ void CFRetainReleaseChecker::checkPreStmt(const CallExpr *CE,
const FunctionDecl *FD = C.getCalleeDecl(CE);
if (!FD)
return;
-
+
if (!BT) {
ASTContext &Ctx = C.getASTContext();
Retain = &Ctx.Idents.get("CFRetain");
@@ -589,7 +589,7 @@ void CFRetainReleaseChecker::checkPreStmt(const CallExpr *CE,
std::tie(stateTrue, stateFalse) = state->assume(ArgIsNull);
if (stateTrue && !stateFalse) {
- ExplodedNode *N = C.generateSink(stateTrue);
+ ExplodedNode *N = C.generateErrorNode(stateTrue);
if (!N)
return;
@@ -635,7 +635,7 @@ public:
void ClassReleaseChecker::checkPreObjCMessage(const ObjCMethodCall &msg,
CheckerContext &C) const {
-
+
if (!BT) {
BT.reset(new APIMisuse(
this, "message incorrectly sent to class instead of class instance"));
@@ -646,7 +646,7 @@ void ClassReleaseChecker::checkPreObjCMessage(const ObjCMethodCall &msg,
autoreleaseS = GetNullarySelector("autorelease", Ctx);
drainS = GetNullarySelector("drain", Ctx);
}
-
+
if (msg.isInstanceMessage())
return;
const ObjCInterfaceDecl *Class = msg.getReceiverInterface();
@@ -655,8 +655,8 @@ void ClassReleaseChecker::checkPreObjCMessage(const ObjCMethodCall &msg,
Selector S = msg.getSelector();
if (!(S == releaseS || S == retainS || S == autoreleaseS || S == drainS))
return;
-
- if (ExplodedNode *N = C.addTransition()) {
+
+ if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
SmallString<200> buf;
llvm::raw_svector_ostream os(buf);
@@ -665,7 +665,7 @@ void ClassReleaseChecker::checkPreObjCMessage(const ObjCMethodCall &msg,
os << "' message should be sent to instances "
"of class '" << Class->getName()
<< "' and not the class directly";
-
+
auto report = llvm::make_unique<BugReport>(*BT, os.str(), N);
report->addRange(msg.getSourceRange());
C.emitReport(std::move(report));
@@ -699,12 +699,12 @@ public:
bool
VariadicMethodTypeChecker::isVariadicMessage(const ObjCMethodCall &msg) const {
const ObjCMethodDecl *MD = msg.getDecl();
-
+
if (!MD || !MD->isVariadic() || isa<ObjCProtocolDecl>(MD->getDeclContext()))
return false;
-
+
Selector S = msg.getSelector();
-
+
if (msg.isInstanceMessage()) {
// FIXME: Ideally we'd look at the receiver interface here, but that's not
// useful for init, because alloc returns 'id'. In theory, this could lead
@@ -751,7 +751,7 @@ void VariadicMethodTypeChecker::checkPreObjCMessage(const ObjCMethodCall &msg,
ASTContext &Ctx = C.getASTContext();
arrayWithObjectsS = GetUnarySelector("arrayWithObjects", Ctx);
- dictionaryWithObjectsAndKeysS =
+ dictionaryWithObjectsAndKeysS =
GetUnarySelector("dictionaryWithObjectsAndKeys", Ctx);
setWithObjectsS = GetUnarySelector("setWithObjects", Ctx);
orderedSetWithObjectsS = GetUnarySelector("orderedSetWithObjects", Ctx);
@@ -789,18 +789,18 @@ void VariadicMethodTypeChecker::checkPreObjCMessage(const ObjCMethodCall &msg,
// Ignore pointer constants.
if (msg.getArgSVal(I).getAs<loc::ConcreteInt>())
continue;
-
+
// Ignore pointer types annotated with 'NSObject' attribute.
if (C.getASTContext().isObjCNSObjectType(ArgTy))
continue;
-
+
// Ignore CF references, which can be toll-free bridged.
if (coreFoundation::isCFObjectRef(ArgTy))
continue;
// Generate only one error node to use for all bug reports.
if (!errorNode.hasValue())
- errorNode = C.addTransition();
+ errorNode = C.generateNonFatalErrorNode();
if (!errorNode.getValue())
continue;
@@ -861,7 +861,7 @@ static bool isKnownNonNilCollectionType(QualType T) {
const ObjCObjectPointerType *PT = T->getAs<ObjCObjectPointerType>();
if (!PT)
return false;
-
+
const ObjCInterfaceDecl *ID = PT->getInterfaceDecl();
if (!ID)
return false;
@@ -992,9 +992,7 @@ static bool alreadyExecutedAtLeastOneLoopIteration(const ExplodedNode *N,
ProgramPoint P = N->getLocation();
if (Optional<BlockEdge> BE = P.getAs<BlockEdge>()) {
- if (BE->getSrc()->getLoopTarget() == FCS)
- return true;
- return false;
+ return BE->getSrc()->getLoopTarget() == FCS;
}
// Keep looking for a block edge.
@@ -1023,9 +1021,9 @@ void ObjCLoopChecker::checkPostStmt(const ObjCForCollectionStmt *FCS,
State = checkElementNonNil(C, State, FCS);
State = assumeCollectionNonEmpty(C, State, FCS, /*Assumption*/true);
}
-
+
if (!State)
- C.generateSink();
+ C.generateSink(C.getState(), C.getPredecessor());
else if (State != C.getState())
C.addTransition(State);
}
@@ -1038,11 +1036,8 @@ bool ObjCLoopChecker::isCollectionCountMethod(const ObjCMethodCall &M,
CountSelectorII = &C.getASTContext().Idents.get("count");
// If the method returns collection count, record the value.
- if (S.isUnarySelector() &&
- (S.getIdentifierInfoForSlot(0) == CountSelectorII))
- return true;
-
- return false;
+ return S.isUnarySelector() &&
+ (S.getIdentifierInfoForSlot(0) == CountSelectorII);
}
void ObjCLoopChecker::checkPostObjCMessage(const ObjCMethodCall &M,
@@ -1069,7 +1064,7 @@ void ObjCLoopChecker::checkPostObjCMessage(const ObjCMethodCall &M,
// a call to "count" and add it to the map.
if (!isCollectionCountMethod(M, C))
return;
-
+
const Expr *MsgExpr = M.getOriginExpr();
SymbolRef CountS = C.getSVal(MsgExpr).getAsSymbol();
if (CountS) {
diff --git a/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp b/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp
index e945c38e77c7..f26f73129e78 100644
--- a/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp
@@ -32,7 +32,7 @@ namespace {
void BoolAssignmentChecker::emitReport(ProgramStateRef state,
CheckerContext &C) const {
- if (ExplodedNode *N = C.addTransition(state)) {
+ if (ExplodedNode *N = C.generateNonFatalErrorNode(state)) {
if (!BT)
BT.reset(new BuiltinBug(this, "Assignment of a non-Boolean value"));
C.emitReport(llvm::make_unique<BugReport>(*BT, BT->getDescription(), N));
@@ -42,45 +42,45 @@ void BoolAssignmentChecker::emitReport(ProgramStateRef state,
static bool isBooleanType(QualType Ty) {
if (Ty->isBooleanType()) // C++ or C99
return true;
-
+
if (const TypedefType *TT = Ty->getAs<TypedefType>())
return TT->getDecl()->getName() == "BOOL" || // Objective-C
TT->getDecl()->getName() == "_Bool" || // stdbool.h < C99
TT->getDecl()->getName() == "Boolean"; // MacTypes.h
-
+
return false;
}
void BoolAssignmentChecker::checkBind(SVal loc, SVal val, const Stmt *S,
CheckerContext &C) const {
-
+
// We are only interested in stores into Booleans.
const TypedValueRegion *TR =
dyn_cast_or_null<TypedValueRegion>(loc.getAsRegion());
-
+
if (!TR)
return;
-
+
QualType valTy = TR->getValueType();
-
+
if (!isBooleanType(valTy))
return;
-
+
// Get the value of the right-hand side. We only care about values
// that are defined (UnknownVals and UndefinedVals are handled by other
// checkers).
Optional<DefinedSVal> DV = val.getAs<DefinedSVal>();
if (!DV)
return;
-
+
// Check if the assigned value meets our criteria for correctness. It must
// be a value that is either 0 or 1. One way to check this is to see if
// the value is possibly < 0 (for a negative value) or greater than 1.
- ProgramStateRef state = C.getState();
+ ProgramStateRef state = C.getState();
SValBuilder &svalBuilder = C.getSValBuilder();
ConstraintManager &CM = C.getConstraintManager();
-
- // First, ensure that the value is >= 0.
+
+ // First, ensure that the value is >= 0.
DefinedSVal zeroVal = svalBuilder.makeIntVal(0, valTy);
SVal greaterThanOrEqualToZeroVal =
svalBuilder.evalBinOp(state, BO_GE, *DV, zeroVal,
@@ -91,13 +91,13 @@ void BoolAssignmentChecker::checkBind(SVal loc, SVal val, const Stmt *S,
if (!greaterThanEqualToZero) {
// The SValBuilder cannot construct a valid SVal for this condition.
- // This means we cannot properly reason about it.
+ // This means we cannot properly reason about it.
return;
}
-
+
ProgramStateRef stateLT, stateGE;
std::tie(stateGE, stateLT) = CM.assumeDual(state, *greaterThanEqualToZero);
-
+
// Is it possible for the value to be less than zero?
if (stateLT) {
// It is possible for the value to be less than zero. We only
@@ -106,15 +106,15 @@ void BoolAssignmentChecker::checkBind(SVal loc, SVal val, const Stmt *S,
// value is underconstrained and there is nothing left to be done.
if (!stateGE)
emitReport(stateLT, C);
-
+
// In either case, we are done.
return;
}
-
+
// If we reach here, it must be the case that the value is constrained
// to only be >= 0.
assert(stateGE == state);
-
+
// At this point we know that the value is >= 0.
// Now check to ensure that the value is <= 1.
DefinedSVal OneVal = svalBuilder.makeIntVal(1, valTy);
@@ -127,13 +127,13 @@ void BoolAssignmentChecker::checkBind(SVal loc, SVal val, const Stmt *S,
if (!lessThanEqToOne) {
// The SValBuilder cannot construct a valid SVal for this condition.
- // This means we cannot properly reason about it.
+ // This means we cannot properly reason about it.
return;
}
-
+
ProgramStateRef stateGT, stateLE;
std::tie(stateLE, stateGT) = CM.assumeDual(state, *lessThanEqToOne);
-
+
// Is it possible for the value to be greater than one?
if (stateGT) {
// It is possible for the value to be greater than one. We only
@@ -142,11 +142,11 @@ void BoolAssignmentChecker::checkBind(SVal loc, SVal val, const Stmt *S,
// value is underconstrained and there is nothing left to be done.
if (!stateLE)
emitReport(stateGT, C);
-
+
// In either case, we are done.
return;
}
-
+
// If we reach here, it must be the case that the value is constrained
// to only be <= 1.
assert(stateLE == state);
diff --git a/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp b/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp
index 104a81eac4c7..dab2f61229a0 100644
--- a/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp
@@ -41,11 +41,12 @@ bool BuiltinFunctionChecker::evalCall(const CallExpr *CE,
default:
return false;
+ case Builtin::BI__builtin_unpredictable:
case Builtin::BI__builtin_expect:
case Builtin::BI__builtin_assume_aligned:
case Builtin::BI__builtin_addressof: {
- // For __builtin_expect and __builtin_assume_aligned, just return the value
- // of the subexpression.
+ // For __builtin_unpredictable, __builtin_expect, and
+ // __builtin_assume_aligned, just return the value of the subexpression.
// __builtin_addressof is going from a reference to a pointer, but those
// are represented the same way in the analyzer.
assert (CE->arg_begin() != CE->arg_end());
diff --git a/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/lib/StaticAnalyzer/Checkers/CMakeLists.txt
index 9fb22ecc852b..58ff48d6a22a 100644
--- a/lib/StaticAnalyzer/Checkers/CMakeLists.txt
+++ b/lib/StaticAnalyzer/Checkers/CMakeLists.txt
@@ -33,12 +33,14 @@ add_clang_library(clangStaticAnalyzerCheckers
DirectIvarAssignment.cpp
DivZeroChecker.cpp
DynamicTypePropagation.cpp
+ DynamicTypeChecker.cpp
ExprInspectionChecker.cpp
FixedAddressChecker.cpp
GenericTaintChecker.cpp
IdenticalExprChecker.cpp
IvarInvalidationChecker.cpp
LLVMConventionsChecker.cpp
+ LocalizationChecker.cpp
MacOSKeychainAPIChecker.cpp
MacOSXAPIChecker.cpp
MallocChecker.cpp
@@ -48,12 +50,14 @@ add_clang_library(clangStaticAnalyzerCheckers
NSErrorChecker.cpp
NoReturnFunctionChecker.cpp
NonNullParamChecker.cpp
+ NullabilityChecker.cpp
ObjCAtSyncChecker.cpp
ObjCContainersASTChecker.cpp
ObjCContainersChecker.cpp
ObjCMissingSuperCallChecker.cpp
ObjCSelfInitChecker.cpp
ObjCUnusedIVarsChecker.cpp
+ PaddingChecker.cpp
PointerArithChecker.cpp
PointerSubChecker.cpp
PthreadLockChecker.cpp
@@ -73,6 +77,7 @@ add_clang_library(clangStaticAnalyzerCheckers
UndefinedAssignmentChecker.cpp
UnixAPIChecker.cpp
UnreachableCodeChecker.cpp
+ VforkChecker.cpp
VLASizeChecker.cpp
VirtualCallChecker.cpp
@@ -83,5 +88,6 @@ add_clang_library(clangStaticAnalyzerCheckers
clangAST
clangAnalysis
clangBasic
+ clangLex
clangStaticAnalyzerCore
)
diff --git a/lib/StaticAnalyzer/Checkers/CStringChecker.cpp b/lib/StaticAnalyzer/Checkers/CStringChecker.cpp
index 54b12410aa56..5d78d9b02e6b 100644
--- a/lib/StaticAnalyzer/Checkers/CStringChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/CStringChecker.cpp
@@ -65,7 +65,7 @@ public:
void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const;
bool wantsRegionChangeUpdate(ProgramStateRef state) const;
- ProgramStateRef
+ ProgramStateRef
checkRegionChanges(ProgramStateRef state,
const InvalidatedSymbols *,
ArrayRef<const MemRegion *> ExplicitRegions,
@@ -92,7 +92,7 @@ public:
void evalstrLength(CheckerContext &C, const CallExpr *CE) const;
void evalstrnLength(CheckerContext &C, const CallExpr *CE) const;
void evalstrLengthCommon(CheckerContext &C,
- const CallExpr *CE,
+ const CallExpr *CE,
bool IsStrnlen = false) const;
void evalStrcpy(CheckerContext &C, const CallExpr *CE) const;
@@ -137,15 +137,16 @@ public:
SVal Buf,
bool hypothetical = false) const;
- const StringLiteral *getCStringLiteral(CheckerContext &C,
+ const StringLiteral *getCStringLiteral(CheckerContext &C,
ProgramStateRef &state,
- const Expr *expr,
+ const Expr *expr,
SVal val) const;
static ProgramStateRef InvalidateBuffer(CheckerContext &C,
ProgramStateRef state,
const Expr *Ex, SVal V,
- bool IsSourceBuffer);
+ bool IsSourceBuffer,
+ const Expr *Size);
static bool SummarizeRegion(raw_ostream &os, ASTContext &Ctx,
const MemRegion *MR);
@@ -193,6 +194,14 @@ public:
ProgramStateRef state,
NonLoc left,
NonLoc right) const;
+
+ // Return true if the destination buffer of the copy function may be in bound.
+ // Expects SVal of Size to be positive and unsigned.
+ // Expects SVal of FirstBuf to be a FieldRegion.
+ static bool IsFirstBufInBound(CheckerContext &C,
+ ProgramStateRef state,
+ const Expr *FirstBuf,
+ const Expr *Size);
};
} //end anonymous namespace
@@ -229,7 +238,7 @@ ProgramStateRef CStringChecker::checkNonNull(CheckerContext &C,
if (!Filter.CheckCStringNullArg)
return nullptr;
- ExplodedNode *N = C.generateSink(stateNull);
+ ExplodedNode *N = C.generateErrorNode(stateNull);
if (!N)
return nullptr;
@@ -282,7 +291,7 @@ ProgramStateRef CStringChecker::CheckLocation(CheckerContext &C,
// Get the size of the array.
const SubRegion *superReg = cast<SubRegion>(ER->getSuperRegion());
SValBuilder &svalBuilder = C.getSValBuilder();
- SVal Extent =
+ SVal Extent =
svalBuilder.convertToArrayIndex(superReg->getExtent(svalBuilder));
DefinedOrUnknownSVal Size = Extent.castAs<DefinedOrUnknownSVal>();
@@ -292,7 +301,7 @@ ProgramStateRef CStringChecker::CheckLocation(CheckerContext &C,
ProgramStateRef StInBound = state->assumeInBound(Idx, Size, true);
ProgramStateRef StOutBound = state->assumeInBound(Idx, Size, false);
if (StOutBound && !StInBound) {
- ExplodedNode *N = C.generateSink(StOutBound);
+ ExplodedNode *N = C.generateErrorNode(StOutBound);
if (!N)
return nullptr;
@@ -327,7 +336,7 @@ ProgramStateRef CStringChecker::CheckLocation(CheckerContext &C,
C.emitReport(std::move(report));
return nullptr;
}
-
+
// Array bound check succeeded. From this point forward the array bound
// should always succeed.
return StInBound;
@@ -442,7 +451,7 @@ ProgramStateRef CStringChecker::CheckOverlap(CheckerContext &C,
return state;
// Are the two values the same?
- SValBuilder &svalBuilder = C.getSValBuilder();
+ SValBuilder &svalBuilder = C.getSValBuilder();
std::tie(stateTrue, stateFalse) =
state->assume(svalBuilder.evalEQ(state, *firstLoc, *secondLoc));
@@ -489,7 +498,7 @@ ProgramStateRef CStringChecker::CheckOverlap(CheckerContext &C,
// Bail out if the cast fails.
ASTContext &Ctx = svalBuilder.getContext();
QualType CharPtrTy = Ctx.getPointerType(Ctx.CharTy);
- SVal FirstStart = svalBuilder.evalCast(*firstLoc, CharPtrTy,
+ SVal FirstStart = svalBuilder.evalCast(*firstLoc, CharPtrTy,
First->getType());
Optional<Loc> FirstStartLoc = FirstStart.getAs<Loc>();
if (!FirstStartLoc)
@@ -525,7 +534,7 @@ ProgramStateRef CStringChecker::CheckOverlap(CheckerContext &C,
void CStringChecker::emitOverlapBug(CheckerContext &C, ProgramStateRef state,
const Stmt *First, const Stmt *Second) const {
- ExplodedNode *N = C.generateSink(state);
+ ExplodedNode *N = C.generateErrorNode(state);
if (!N)
return;
@@ -568,7 +577,7 @@ ProgramStateRef CStringChecker::checkAdditionOverflow(CheckerContext &C,
} else {
// Try switching the operands. (The order of these two assignments is
// important!)
- maxMinusRight = svalBuilder.evalBinOpNN(state, BO_Sub, maxVal, left,
+ maxMinusRight = svalBuilder.evalBinOpNN(state, BO_Sub, maxVal, left,
sizeTy);
left = right;
}
@@ -585,7 +594,7 @@ ProgramStateRef CStringChecker::checkAdditionOverflow(CheckerContext &C,
if (stateOverflow && !stateOkay) {
// We have an overflow. Emit a bug report.
- ExplodedNode *N = C.generateSink(stateOverflow);
+ ExplodedNode *N = C.generateErrorNode(stateOverflow);
if (!N)
return nullptr;
@@ -706,7 +715,7 @@ SVal CStringChecker::getCStringLength(CheckerContext &C, ProgramStateRef &state,
if (!Filter.CheckCStringNotNullTerm)
return UndefinedVal();
- if (ExplodedNode *N = C.addTransition(state)) {
+ if (ExplodedNode *N = C.generateNonFatalErrorNode(state)) {
if (!BT_NotCString)
BT_NotCString.reset(new BuiltinBug(
Filter.CheckNameCStringNotNullTerm, categories::UnixAPI,
@@ -723,7 +732,7 @@ SVal CStringChecker::getCStringLength(CheckerContext &C, ProgramStateRef &state,
auto report = llvm::make_unique<BugReport>(*BT_NotCString, os.str(), N);
report->addRange(Ex->getSourceRange());
- C.emitReport(std::move(report));
+ C.emitReport(std::move(report));
}
return UndefinedVal();
@@ -766,7 +775,7 @@ SVal CStringChecker::getCStringLength(CheckerContext &C, ProgramStateRef &state,
if (!Filter.CheckCStringNotNullTerm)
return UndefinedVal();
- if (ExplodedNode *N = C.addTransition(state)) {
+ if (ExplodedNode *N = C.generateNonFatalErrorNode(state)) {
if (!BT_NotCString)
BT_NotCString.reset(new BuiltinBug(
Filter.CheckNameCStringNotNullTerm, categories::UnixAPI,
@@ -787,7 +796,7 @@ SVal CStringChecker::getCStringLength(CheckerContext &C, ProgramStateRef &state,
auto report = llvm::make_unique<BugReport>(*BT_NotCString, os.str(), N);
report->addRange(Ex->getSourceRange());
- C.emitReport(std::move(report));
+ C.emitReport(std::move(report));
}
return UndefinedVal();
@@ -814,10 +823,74 @@ const StringLiteral *CStringChecker::getCStringLiteral(CheckerContext &C,
return strRegion->getStringLiteral();
}
+bool CStringChecker::IsFirstBufInBound(CheckerContext &C,
+ ProgramStateRef state,
+ const Expr *FirstBuf,
+ const Expr *Size) {
+ // If we do not know that the buffer is long enough we return 'true'.
+ // Otherwise the parent region of this field region would also get
+ // invalidated, which would lead to warnings based on an unknown state.
+
+ // Originally copied from CheckBufferAccess and CheckLocation.
+ SValBuilder &svalBuilder = C.getSValBuilder();
+ ASTContext &Ctx = svalBuilder.getContext();
+ const LocationContext *LCtx = C.getLocationContext();
+
+ QualType sizeTy = Size->getType();
+ QualType PtrTy = Ctx.getPointerType(Ctx.CharTy);
+ SVal BufVal = state->getSVal(FirstBuf, LCtx);
+
+ SVal LengthVal = state->getSVal(Size, LCtx);
+ Optional<NonLoc> Length = LengthVal.getAs<NonLoc>();
+ if (!Length)
+ return true; // cf top comment.
+
+ // Compute the offset of the last element to be accessed: size-1.
+ NonLoc One = svalBuilder.makeIntVal(1, sizeTy).castAs<NonLoc>();
+ NonLoc LastOffset =
+ svalBuilder.evalBinOpNN(state, BO_Sub, *Length, One, sizeTy)
+ .castAs<NonLoc>();
+
+ // Check that the first buffer is sufficiently long.
+ SVal BufStart = svalBuilder.evalCast(BufVal, PtrTy, FirstBuf->getType());
+ Optional<Loc> BufLoc = BufStart.getAs<Loc>();
+ if (!BufLoc)
+ return true; // cf top comment.
+
+ SVal BufEnd =
+ svalBuilder.evalBinOpLN(state, BO_Add, *BufLoc, LastOffset, PtrTy);
+
+ // Check for out of bound array element access.
+ const MemRegion *R = BufEnd.getAsRegion();
+ if (!R)
+ return true; // cf top comment.
+
+ const ElementRegion *ER = dyn_cast<ElementRegion>(R);
+ if (!ER)
+ return true; // cf top comment.
+
+ assert(ER->getValueType() == C.getASTContext().CharTy &&
+ "IsFirstBufInBound should only be called with char* ElementRegions");
+
+ // Get the size of the array.
+ const SubRegion *superReg = cast<SubRegion>(ER->getSuperRegion());
+ SVal Extent =
+ svalBuilder.convertToArrayIndex(superReg->getExtent(svalBuilder));
+ DefinedOrUnknownSVal ExtentSize = Extent.castAs<DefinedOrUnknownSVal>();
+
+ // Get the index of the accessed element.
+ DefinedOrUnknownSVal Idx = ER->getIndex().castAs<DefinedOrUnknownSVal>();
+
+ ProgramStateRef StInBound = state->assumeInBound(Idx, ExtentSize, true);
+
+ return static_cast<bool>(StInBound);
+}
+
ProgramStateRef CStringChecker::InvalidateBuffer(CheckerContext &C,
ProgramStateRef state,
const Expr *E, SVal V,
- bool IsSourceBuffer) {
+ bool IsSourceBuffer,
+ const Expr *Size) {
Optional<Loc> L = V.getAs<Loc>();
if (!L)
return state;
@@ -843,13 +916,23 @@ ProgramStateRef CStringChecker::InvalidateBuffer(CheckerContext &C,
// Invalidate and escape only indirect regions accessible through the source
// buffer.
if (IsSourceBuffer) {
- ITraits.setTrait(R,
+ ITraits.setTrait(R,
RegionAndSymbolInvalidationTraits::TK_PreserveContents);
ITraits.setTrait(R, RegionAndSymbolInvalidationTraits::TK_SuppressEscape);
CausesPointerEscape = true;
+ } else {
+ const MemRegion::Kind& K = R->getKind();
+ if (K == MemRegion::FieldRegionKind)
+ if (Size && IsFirstBufInBound(C, state, E, Size)) {
+ // If destination buffer is a field region and access is in bound,
+ // do not invalidate its super region.
+ ITraits.setTrait(
+ R,
+ RegionAndSymbolInvalidationTraits::TK_DoNotInvalidateSuperRegion);
+ }
}
- return state->invalidateRegions(R, E, C.blockCount(), LCtx,
+ return state->invalidateRegions(R, E, C.blockCount(), LCtx,
CausesPointerEscape, nullptr, nullptr,
&ITraits);
}
@@ -901,7 +984,7 @@ bool CStringChecker::SummarizeRegion(raw_ostream &os, ASTContext &Ctx,
// evaluation of individual function calls.
//===----------------------------------------------------------------------===//
-void CStringChecker::evalCopyCommon(CheckerContext &C,
+void CStringChecker::evalCopyCommon(CheckerContext &C,
const CallExpr *CE,
ProgramStateRef state,
const Expr *Size, const Expr *Dest,
@@ -941,7 +1024,7 @@ void CStringChecker::evalCopyCommon(CheckerContext &C,
// Get the value of the Src.
SVal srcVal = state->getSVal(Source, LCtx);
-
+
// Ensure the source is not null. If it is NULL there will be a
// NULL pointer dereference.
state = checkNonNull(C, state, Source, srcVal);
@@ -959,11 +1042,11 @@ void CStringChecker::evalCopyCommon(CheckerContext &C,
if (!state)
return;
- // If this is mempcpy, get the byte after the last byte copied and
+ // If this is mempcpy, get the byte after the last byte copied and
// bind the expr.
if (IsMempcpy) {
loc::MemRegionVal destRegVal = destVal.castAs<loc::MemRegionVal>();
-
+
// Get the length to copy.
if (Optional<NonLoc> lenValNonLoc = sizeVal.getAs<NonLoc>()) {
// Get the byte after the last byte copied.
@@ -972,11 +1055,11 @@ void CStringChecker::evalCopyCommon(CheckerContext &C,
QualType CharPtrTy = Ctx.getPointerType(Ctx.CharTy);
loc::MemRegionVal DestRegCharVal = SvalBuilder.evalCast(destRegVal,
CharPtrTy, Dest->getType()).castAs<loc::MemRegionVal>();
- SVal lastElement = C.getSValBuilder().evalBinOpLN(state, BO_Add,
+ SVal lastElement = C.getSValBuilder().evalBinOpLN(state, BO_Add,
DestRegCharVal,
- *lenValNonLoc,
+ *lenValNonLoc,
Dest->getType());
-
+
// The byte after the last byte copied is the return value.
state = state->BindExpr(CE, LCtx, lastElement);
} else {
@@ -999,13 +1082,13 @@ void CStringChecker::evalCopyCommon(CheckerContext &C,
// can use LazyCompoundVals to copy the source values into the destination.
// This would probably remove any existing bindings past the end of the
// copied region, but that's still an improvement over blank invalidation.
- state = InvalidateBuffer(C, state, Dest, C.getSVal(Dest),
- /*IsSourceBuffer*/false);
+ state = InvalidateBuffer(C, state, Dest, C.getSVal(Dest),
+ /*IsSourceBuffer*/false, Size);
// Invalidate the source (const-invalidation without const-pointer-escaping
// the address of the top-level region).
- state = InvalidateBuffer(C, state, Source, C.getSVal(Source),
- /*IsSourceBuffer*/true);
+ state = InvalidateBuffer(C, state, Source, C.getSVal(Source),
+ /*IsSourceBuffer*/true, nullptr);
C.addTransition(state);
}
@@ -1032,7 +1115,7 @@ void CStringChecker::evalMempcpy(CheckerContext &C, const CallExpr *CE) const {
// The return value is a pointer to the byte following the last written byte.
const Expr *Dest = CE->getArg(0);
ProgramStateRef state = C.getState();
-
+
evalCopyCommon(C, CE, state, CE->getArg(2), Dest, CE->getArg(1), true, true);
}
@@ -1053,7 +1136,7 @@ void CStringChecker::evalBcopy(CheckerContext &C, const CallExpr *CE) const {
return;
// void bcopy(const void *src, void *dst, size_t n);
- evalCopyCommon(C, CE, C.getState(),
+ evalCopyCommon(C, CE, C.getState(),
CE->getArg(2), CE->getArg(1), CE->getArg(0));
}
@@ -1244,7 +1327,7 @@ void CStringChecker::evalstrLengthCommon(CheckerContext &C, const CallExpr *CE,
state, BO_LE, resultNL, *strLengthNL, cmpTy)
.castAs<DefinedOrUnknownSVal>(), true);
}
-
+
if (maxlenValNL) {
state = state->assume(C.getSValBuilder().evalBinOpNN(
state, BO_LE, resultNL, *maxlenValNL, cmpTy)
@@ -1275,8 +1358,8 @@ void CStringChecker::evalStrcpy(CheckerContext &C, const CallExpr *CE) const {
return;
// char *strcpy(char *restrict dst, const char *restrict src);
- evalStrcpyCommon(C, CE,
- /* returnEnd = */ false,
+ evalStrcpyCommon(C, CE,
+ /* returnEnd = */ false,
/* isBounded = */ false,
/* isAppending = */ false);
}
@@ -1286,8 +1369,8 @@ void CStringChecker::evalStrncpy(CheckerContext &C, const CallExpr *CE) const {
return;
// char *strncpy(char *restrict dst, const char *restrict src, size_t n);
- evalStrcpyCommon(C, CE,
- /* returnEnd = */ false,
+ evalStrcpyCommon(C, CE,
+ /* returnEnd = */ false,
/* isBounded = */ true,
/* isAppending = */ false);
}
@@ -1297,8 +1380,8 @@ void CStringChecker::evalStpcpy(CheckerContext &C, const CallExpr *CE) const {
return;
// char *stpcpy(char *restrict dst, const char *restrict src);
- evalStrcpyCommon(C, CE,
- /* returnEnd = */ true,
+ evalStrcpyCommon(C, CE,
+ /* returnEnd = */ true,
/* isBounded = */ false,
/* isAppending = */ false);
}
@@ -1308,8 +1391,8 @@ void CStringChecker::evalStrcat(CheckerContext &C, const CallExpr *CE) const {
return;
//char *strcat(char *restrict s1, const char *restrict s2);
- evalStrcpyCommon(C, CE,
- /* returnEnd = */ false,
+ evalStrcpyCommon(C, CE,
+ /* returnEnd = */ false,
/* isBounded = */ false,
/* isAppending = */ true);
}
@@ -1319,8 +1402,8 @@ void CStringChecker::evalStrncat(CheckerContext &C, const CallExpr *CE) const {
return;
//char *strncat(char *restrict s1, const char *restrict s2, size_t n);
- evalStrcpyCommon(C, CE,
- /* returnEnd = */ false,
+ evalStrcpyCommon(C, CE,
+ /* returnEnd = */ false,
/* isBounded = */ true,
/* isAppending = */ true);
}
@@ -1515,7 +1598,7 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE,
Optional<NonLoc> srcStrLengthNL = amountCopied.getAs<NonLoc>();
Optional<NonLoc> dstStrLengthNL = dstStrLength.getAs<NonLoc>();
-
+
// If we know both string lengths, we might know the final string length.
if (srcStrLengthNL && dstStrLengthNL) {
// Make sure the two lengths together don't overflow a size_t.
@@ -1523,7 +1606,7 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE,
if (!state)
return;
- finalStrLength = svalBuilder.evalBinOpNN(state, BO_Add, *srcStrLengthNL,
+ finalStrLength = svalBuilder.evalBinOpNN(state, BO_Add, *srcStrLengthNL,
*dstStrLengthNL, sizeTy);
}
@@ -1586,7 +1669,7 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE,
if (Optional<NonLoc> maxLastNL = maxLastElementIndex.getAs<NonLoc>()) {
SVal maxLastElement = svalBuilder.evalBinOpLN(state, BO_Add, *dstRegVal,
*maxLastNL, ptrTy);
- state = CheckLocation(C, state, CE->getArg(2), maxLastElement,
+ state = CheckLocation(C, state, CE->getArg(2), maxLastElement,
boundWarning);
if (!state)
return;
@@ -1620,11 +1703,12 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE,
// This would probably remove any existing bindings past the end of the
// string, but that's still an improvement over blank invalidation.
state = InvalidateBuffer(C, state, Dst, *dstRegVal,
- /*IsSourceBuffer*/false);
+ /*IsSourceBuffer*/false, nullptr);
// Invalidate the source (const-invalidation without const-pointer-escaping
// the address of the top-level region).
- state = InvalidateBuffer(C, state, srcExpr, srcVal, /*IsSourceBuffer*/true);
+ state = InvalidateBuffer(C, state, srcExpr, srcVal, /*IsSourceBuffer*/true,
+ nullptr);
// Set the C string length of the destination, if we know it.
if (isBounded && !isAppending) {
@@ -1667,7 +1751,7 @@ void CStringChecker::evalStrncmp(CheckerContext &C, const CallExpr *CE) const {
evalStrcmpCommon(C, CE, /* isBounded = */ true, /* ignoreCase = */ false);
}
-void CStringChecker::evalStrcasecmp(CheckerContext &C,
+void CStringChecker::evalStrcasecmp(CheckerContext &C,
const CallExpr *CE) const {
if (CE->getNumArgs() < 2)
return;
@@ -1676,7 +1760,7 @@ void CStringChecker::evalStrcasecmp(CheckerContext &C,
evalStrcmpCommon(C, CE, /* isBounded = */ false, /* ignoreCase = */ true);
}
-void CStringChecker::evalStrncasecmp(CheckerContext &C,
+void CStringChecker::evalStrncasecmp(CheckerContext &C,
const CallExpr *CE) const {
if (CE->getNumArgs() < 3)
return;
@@ -1848,7 +1932,7 @@ void CStringChecker::evalStrsep(CheckerContext &C, const CallExpr *CE) const {
// Invalidate the search string, representing the change of one delimiter
// character to NUL.
State = InvalidateBuffer(C, State, SearchStrPtr, Result,
- /*IsSourceBuffer*/false);
+ /*IsSourceBuffer*/false, nullptr);
// Overwrite the search string pointer. The new value is either an address
// further along in the same string, or NULL if there are no more tokens.
@@ -1915,7 +1999,7 @@ bool CStringChecker::evalCall(const CallExpr *CE, CheckerContext &C) const {
evalFunction = &CStringChecker::evalBcopy;
else if (C.isCLibraryFunction(FDecl, "bcmp"))
evalFunction = &CStringChecker::evalMemcmp;
-
+
// If the callee isn't a string function, let another checker handle it.
if (!evalFunction)
return false;
@@ -1929,10 +2013,7 @@ bool CStringChecker::evalCall(const CallExpr *CE, CheckerContext &C) const {
// properties are held. However, if the user chooses to turn off some of these
// checks, we ignore the issues and leave the call evaluation to a generic
// handler.
- if (!C.isDifferent())
- return false;
-
- return true;
+ return C.isDifferent();
}
void CStringChecker::checkPreStmt(const DeclStmt *DS, CheckerContext &C) const {
@@ -1975,7 +2056,7 @@ bool CStringChecker::wantsRegionChangeUpdate(ProgramStateRef state) const {
return !Entries.isEmpty();
}
-ProgramStateRef
+ProgramStateRef
CStringChecker::checkRegionChanges(ProgramStateRef state,
const InvalidatedSymbols *,
ArrayRef<const MemRegion *> ExplicitRegions,
diff --git a/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp b/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp
index 26423b7368ef..145908376996 100644
--- a/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp
@@ -40,6 +40,7 @@ class CallAndMessageChecker
: public Checker< check::PreStmt<CallExpr>,
check::PreStmt<CXXDeleteExpr>,
check::PreObjCMessage,
+ check::ObjCMessageNil,
check::PreCall > {
mutable std::unique_ptr<BugType> BT_call_null;
mutable std::unique_ptr<BugType> BT_call_undef;
@@ -60,6 +61,12 @@ public:
void checkPreStmt(const CallExpr *CE, CheckerContext &C) const;
void checkPreStmt(const CXXDeleteExpr *DE, CheckerContext &C) const;
void checkPreObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const;
+
+ /// Fill in the return value that results from messaging nil based on the
+ /// return type and architecture and diagnose if the return value will be
+ /// garbage.
+ void checkObjCMessageNil(const ObjCMethodCall &msg, CheckerContext &C) const;
+
void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
private:
@@ -82,7 +89,7 @@ private:
BT.reset(new BuiltinBug(this, desc));
}
bool uninitRefOrPointer(CheckerContext &C, const SVal &V,
- const SourceRange &ArgRange,
+ SourceRange ArgRange,
const Expr *ArgEx, std::unique_ptr<BugType> &BT,
const ParmVarDecl *ParamDecl, const char *BD) const;
};
@@ -90,7 +97,7 @@ private:
void CallAndMessageChecker::emitBadCall(BugType *BT, CheckerContext &C,
const Expr *BadE) {
- ExplodedNode *N = C.generateSink();
+ ExplodedNode *N = C.generateErrorNode();
if (!N)
return;
@@ -131,7 +138,7 @@ static StringRef describeUninitializedArgumentInCall(const CallEvent &Call,
bool CallAndMessageChecker::uninitRefOrPointer(CheckerContext &C,
const SVal &V,
- const SourceRange &ArgRange,
+ SourceRange ArgRange,
const Expr *ArgEx,
std::unique_ptr<BugType> &BT,
const ParmVarDecl *ParamDecl,
@@ -162,7 +169,7 @@ bool CallAndMessageChecker::uninitRefOrPointer(CheckerContext &C,
const ProgramStateRef State = C.getState();
const SVal PSV = State->getSVal(SValMemRegion);
if (PSV.isUndef()) {
- if (ExplodedNode *N = C.generateSink()) {
+ if (ExplodedNode *N = C.generateErrorNode()) {
LazyInit_BT(BD, BT);
auto R = llvm::make_unique<BugReport>(*BT, Message, N);
R->addRange(ArgRange);
@@ -193,7 +200,7 @@ bool CallAndMessageChecker::PreVisitProcessArg(CheckerContext &C,
return true;
if (V.isUndef()) {
- if (ExplodedNode *N = C.generateSink()) {
+ if (ExplodedNode *N = C.generateErrorNode()) {
LazyInit_BT(BD, BT);
// Generate a report for this bug.
@@ -258,7 +265,7 @@ bool CallAndMessageChecker::PreVisitProcessArg(CheckerContext &C,
D->getStore());
if (F.Find(D->getRegion())) {
- if (ExplodedNode *N = C.generateSink()) {
+ if (ExplodedNode *N = C.generateErrorNode()) {
LazyInit_BT(BD, BT);
SmallString<512> Str;
llvm::raw_svector_ostream os(Str);
@@ -331,7 +338,7 @@ void CallAndMessageChecker::checkPreStmt(const CXXDeleteExpr *DE,
SVal Arg = C.getSVal(DE->getArgument());
if (Arg.isUndef()) {
StringRef Desc;
- ExplodedNode *N = C.generateSink();
+ ExplodedNode *N = C.generateErrorNode();
if (!N)
return;
if (!BT_cxx_delete_undef)
@@ -388,7 +395,7 @@ void CallAndMessageChecker::checkPreCall(const CallEvent &Call,
// the function.
unsigned Params = FD->getNumParams();
if (Call.getNumArgs() < Params) {
- ExplodedNode *N = C.generateSink();
+ ExplodedNode *N = C.generateErrorNode();
if (!N)
return;
@@ -436,7 +443,7 @@ void CallAndMessageChecker::checkPreObjCMessage(const ObjCMethodCall &msg,
CheckerContext &C) const {
SVal recVal = msg.getReceiverSVal();
if (recVal.isUndef()) {
- if (ExplodedNode *N = C.generateSink()) {
+ if (ExplodedNode *N = C.generateErrorNode()) {
BugType *BT = nullptr;
switch (msg.getMessageKind()) {
case OCM_Message:
@@ -471,22 +478,14 @@ void CallAndMessageChecker::checkPreObjCMessage(const ObjCMethodCall &msg,
C.emitReport(std::move(R));
}
return;
- } else {
- // Bifurcate the state into nil and non-nil ones.
- DefinedOrUnknownSVal receiverVal = recVal.castAs<DefinedOrUnknownSVal>();
-
- ProgramStateRef state = C.getState();
- ProgramStateRef notNilState, nilState;
- std::tie(notNilState, nilState) = state->assume(receiverVal);
-
- // Handle receiver must be nil.
- if (nilState && !notNilState) {
- HandleNilReceiver(C, state, msg);
- return;
- }
}
}
+void CallAndMessageChecker::checkObjCMessageNil(const ObjCMethodCall &msg,
+ CheckerContext &C) const {
+ HandleNilReceiver(C, C.getState(), msg);
+}
+
void CallAndMessageChecker::emitNilReceiverBug(CheckerContext &C,
const ObjCMethodCall &msg,
ExplodedNode *N) const {
@@ -523,7 +522,8 @@ void CallAndMessageChecker::emitNilReceiverBug(CheckerContext &C,
static bool supportsNilWithFloatRet(const llvm::Triple &triple) {
return (triple.getVendor() == llvm::Triple::Apple &&
- (triple.isiOS() || !triple.isMacOSXVersionLT(10,5)));
+ (triple.isiOS() || triple.isWatchOS() ||
+ !triple.isMacOSXVersionLT(10,5)));
}
void CallAndMessageChecker::HandleNilReceiver(CheckerContext &C,
@@ -560,7 +560,7 @@ void CallAndMessageChecker::HandleNilReceiver(CheckerContext &C,
Ctx.LongDoubleTy == CanRetTy ||
Ctx.LongLongTy == CanRetTy ||
Ctx.UnsignedLongLongTy == CanRetTy)))) {
- if (ExplodedNode *N = C.generateSink(state, nullptr, &Tag))
+ if (ExplodedNode *N = C.generateErrorNode(state, &Tag))
emitNilReceiverBug(C, Msg, N);
return;
}
diff --git a/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp b/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp
index 0d683f96df08..2337400750c7 100644
--- a/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp
@@ -82,10 +82,7 @@ static bool evenFlexibleArraySize(ASTContext &Ctx, CharUnits RegionSize,
if (Left.isNegative())
return false;
- if (Left % FlexSize == 0)
- return true;
-
- return false;
+ return Left % FlexSize == 0;
}
void CastSizeChecker::checkPreStmt(const CastExpr *CE,CheckerContext &C) const {
@@ -131,7 +128,7 @@ void CastSizeChecker::checkPreStmt(const CastExpr *CE,CheckerContext &C) const {
if (evenFlexibleArraySize(Ctx, regionSize, typeSize, ToPointeeTy))
return;
- if (ExplodedNode *errorNode = C.generateSink()) {
+ if (ExplodedNode *errorNode = C.generateErrorNode()) {
if (!BT)
BT.reset(new BuiltinBug(this, "Cast region with wrong size.",
"Cast a region whose size is not a multiple"
diff --git a/lib/StaticAnalyzer/Checkers/CastToStructChecker.cpp b/lib/StaticAnalyzer/Checkers/CastToStructChecker.cpp
index ba3024d78a19..fa7841356efb 100644
--- a/lib/StaticAnalyzer/Checkers/CastToStructChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/CastToStructChecker.cpp
@@ -56,7 +56,7 @@ void CastToStructChecker::checkPreStmt(const CastExpr *CE,
// Now the cast-to-type is struct pointer, the original type is not void*.
if (!OrigPointeeTy->isRecordType()) {
- if (ExplodedNode *N = C.addTransition()) {
+ if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
if (!BT)
BT.reset(
new BuiltinBug(this, "Cast from non-struct type to struct type",
diff --git a/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp b/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp
index 12eb0bde28ad..25caa0002598 100644
--- a/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp
+++ b/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp
@@ -51,7 +51,7 @@ static bool scan_ivar_release(Stmt *S, ObjCIvarDecl *ID,
if (E->getDecl()->getIdentifier() == SelfII)
if (ME->getMethodDecl() == PD->getSetterMethodDecl() &&
ME->getNumArgs() == 1 &&
- ME->getArg(0)->isNullPointerConstant(Ctx,
+ ME->getArg(0)->isNullPointerConstant(Ctx,
Expr::NPC_ValueDependentIsNull))
return true;
@@ -61,7 +61,7 @@ static bool scan_ivar_release(Stmt *S, ObjCIvarDecl *ID,
if (ObjCPropertyRefExpr *PRE =
dyn_cast<ObjCPropertyRefExpr>(BO->getLHS()->IgnoreParenCasts()))
if (PRE->isExplicitProperty() && PRE->getExplicitProperty() == PD)
- if (BO->getRHS()->isNullPointerConstant(Ctx,
+ if (BO->getRHS()->isNullPointerConstant(Ctx,
Expr::NPC_ValueDependentIsNull)) {
// This is only a 'release' if the property kind is not
// 'assign'.
diff --git a/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp b/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp
index e0c113c86262..60f16188bcf8 100644
--- a/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp
+++ b/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp
@@ -86,8 +86,7 @@ public:
// Helpers.
bool checkCall_strCommon(const CallExpr *CE, const FunctionDecl *FD);
- typedef void (WalkAST::*FnCheck)(const CallExpr *,
- const FunctionDecl *);
+ typedef void (WalkAST::*FnCheck)(const CallExpr *, const FunctionDecl *);
// Checker-specific methods.
void checkLoopConditionForFloat(const ForStmt *FS);
@@ -115,7 +114,7 @@ void WalkAST::VisitChildren(Stmt *S) {
}
void WalkAST::VisitCallExpr(CallExpr *CE) {
- // Get the callee.
+ // Get the callee.
const FunctionDecl *FD = CE->getDirectCallee();
if (!FD)
@@ -307,7 +306,7 @@ void WalkAST::checkLoopConditionForFloat(const ForStmt *FS) {
void WalkAST::checkCall_gets(const CallExpr *CE, const FunctionDecl *FD) {
if (!filter.check_gets)
return;
-
+
const FunctionProtoType *FPT = FD->getType()->getAs<FunctionProtoType>();
if (!FPT)
return;
@@ -434,18 +433,18 @@ void WalkAST::checkCall_mkstemp(const CallExpr *CE, const FunctionDecl *FD) {
.Case("mkdtemp", std::make_pair(0,-1))
.Case("mkstemps", std::make_pair(0,1))
.Default(std::make_pair(-1, -1));
-
+
assert(ArgSuffix.first >= 0 && "Unsupported function");
// Check if the number of arguments is consistent with out expectations.
unsigned numArgs = CE->getNumArgs();
if ((signed) numArgs <= ArgSuffix.first)
return;
-
+
const StringLiteral *strArg =
dyn_cast<StringLiteral>(CE->getArg((unsigned)ArgSuffix.first)
->IgnoreParenImpCasts());
-
+
// Currently we only handle string literals. It is possible to do better,
// either by looking at references to const variables, or by doing real
// flow analysis.
@@ -470,13 +469,13 @@ void WalkAST::checkCall_mkstemp(const CallExpr *CE, const FunctionDecl *FD) {
suffix = (unsigned) Result.getZExtValue();
n = (n > suffix) ? n - suffix : 0;
}
-
+
for (unsigned i = 0; i < n; ++i)
if (str[i] == 'X') ++numX;
-
+
if (numX >= 6)
return;
-
+
// Issue a warning.
PathDiagnosticLocation CELoc =
PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC);
@@ -502,13 +501,13 @@ void WalkAST::checkCall_mkstemp(const CallExpr *CE, const FunctionDecl *FD) {
//===----------------------------------------------------------------------===//
// Check: Any use of 'strcpy' is insecure.
//
-// CWE-119: Improper Restriction of Operations within
-// the Bounds of a Memory Buffer
+// CWE-119: Improper Restriction of Operations within
+// the Bounds of a Memory Buffer
//===----------------------------------------------------------------------===//
void WalkAST::checkCall_strcpy(const CallExpr *CE, const FunctionDecl *FD) {
if (!filter.check_strcpy)
return;
-
+
if (!checkCall_strCommon(CE, FD))
return;
@@ -529,8 +528,8 @@ void WalkAST::checkCall_strcpy(const CallExpr *CE, const FunctionDecl *FD) {
//===----------------------------------------------------------------------===//
// Check: Any use of 'strcat' is insecure.
//
-// CWE-119: Improper Restriction of Operations within
-// the Bounds of a Memory Buffer
+// CWE-119: Improper Restriction of Operations within
+// the Bounds of a Memory Buffer
//===----------------------------------------------------------------------===//
void WalkAST::checkCall_strcat(const CallExpr *CE, const FunctionDecl *FD) {
if (!filter.check_strcpy)
@@ -684,7 +683,7 @@ void WalkAST::checkCall_vfork(const CallExpr *CE, const FunctionDecl *FD) {
void WalkAST::checkUncheckedReturnValue(CallExpr *CE) {
if (!filter.check_UncheckedReturn)
return;
-
+
const FunctionDecl *FD = CE->getDirectCallee();
if (!FD)
return;
@@ -749,7 +748,7 @@ namespace {
class SecuritySyntaxChecker : public Checker<check::ASTCodeBody> {
public:
ChecksFilter filter;
-
+
void checkASTCodeBody(const Decl *D, AnalysisManager& mgr,
BugReporter &BR) const {
WalkAST walker(BR, mgr.getAnalysisDeclContext(D), filter);
diff --git a/lib/StaticAnalyzer/Checkers/CheckSizeofPointer.cpp b/lib/StaticAnalyzer/Checkers/CheckSizeofPointer.cpp
index 81a20063f972..e079a8cb12be 100644
--- a/lib/StaticAnalyzer/Checkers/CheckSizeofPointer.cpp
+++ b/lib/StaticAnalyzer/Checkers/CheckSizeofPointer.cpp
@@ -55,8 +55,8 @@ void WalkAST::VisitUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr *E) {
QualType T = E->getTypeOfArgument();
if (T->isPointerType()) {
- // Many false positives have the form 'sizeof *p'. This is reasonable
- // because people know what they are doing when they intentionally
+ // Many false positives have the form 'sizeof *p'. This is reasonable
+ // because people know what they are doing when they intentionally
// dereference the pointer.
Expr *ArgEx = E->getArgumentExpr();
if (!isa<DeclRefExpr>(ArgEx->IgnoreParens()))
diff --git a/lib/StaticAnalyzer/Checkers/CheckerDocumentation.cpp b/lib/StaticAnalyzer/Checkers/CheckerDocumentation.cpp
index 956dca7d9258..37b84480f892 100644
--- a/lib/StaticAnalyzer/Checkers/CheckerDocumentation.cpp
+++ b/lib/StaticAnalyzer/Checkers/CheckerDocumentation.cpp
@@ -38,6 +38,7 @@ class CheckerDocumentation : public Checker< check::PreStmt<ReturnStmt>,
check::PostStmt<DeclStmt>,
check::PreObjCMessage,
check::PostObjCMessage,
+ check::ObjCMessageNil,
check::PreCall,
check::PostCall,
check::BranchCondition,
@@ -95,6 +96,15 @@ public:
/// check::PostObjCMessage
void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const {}
+ /// \brief Visit an Objective-C message whose receiver is nil.
+ ///
+ /// This will be called when the analyzer core processes a method call whose
+ /// receiver is definitely nil. In this case, check{Pre/Post}ObjCMessage and
+ /// check{Pre/Post}Call will not be called.
+ ///
+ /// check::ObjCMessageNil
+ void checkObjCMessageNil(const ObjCMethodCall &M, CheckerContext &C) const {}
+
/// \brief Pre-visit an abstract "call" event.
///
/// This is used for checkers that want to check arguments or attributed
@@ -222,7 +232,7 @@ public:
/// changed, this allows the analyzer core to skip the more expensive
/// #checkRegionChanges when no checkers are tracking any state.
bool wantsRegionChangeUpdate(ProgramStateRef St) const { return true; }
-
+
/// \brief Called when the contents of one or more regions change.
///
/// This can occur in many different ways: an explicit bind, a blanket
@@ -246,7 +256,7 @@ public:
/// #wantsRegionChangeUpdate returns \c true.
///
/// check::RegionChanges
- ProgramStateRef
+ ProgramStateRef
checkRegionChanges(ProgramStateRef State,
const InvalidatedSymbols *Invalidated,
ArrayRef<const MemRegion *> ExplicitRegions,
@@ -259,12 +269,12 @@ public:
///
/// This notifies the checkers about pointer escape, which occurs whenever
/// the analyzer cannot track the symbol any more. For example, as a
- /// result of assigning a pointer into a global or when it's passed to a
+ /// result of assigning a pointer into a global or when it's passed to a
/// function call the analyzer cannot model.
- ///
+ ///
/// \param State The state at the point of escape.
/// \param Escaped The list of escaped symbols.
- /// \param Call The corresponding CallEvent, if the symbols escape as
+ /// \param Call The corresponding CallEvent, if the symbols escape as
/// parameters to the given call.
/// \param Kind How the symbols have escaped.
/// \returns Checkers can modify the state by returning a new state.
@@ -285,7 +295,7 @@ public:
PointerEscapeKind Kind) const {
return State;
}
-
+
/// check::Event<ImplicitNullDerefEvent>
void checkEvent(ImplicitNullDerefEvent Event) const {}
diff --git a/lib/StaticAnalyzer/Checkers/Checkers.td b/lib/StaticAnalyzer/Checkers/Checkers.td
index d1d6ac277ffe..8133d290d886 100644
--- a/lib/StaticAnalyzer/Checkers/Checkers.td
+++ b/lib/StaticAnalyzer/Checkers/Checkers.td
@@ -13,6 +13,11 @@ include "clang/StaticAnalyzer/Checkers/CheckerBase.td"
// Packages.
//===----------------------------------------------------------------------===//
+// The Alpha package is for checkers that have too many false positives to be
+// turned on by default. The hierarchy under Alpha should be organized in the
+// hierarchy checkers would have had if they were truly at the top level.
+// (For example, a Cocoa-specific checker that is alpha should be in
+// alpha.osx.cocoa).
def Alpha : Package<"alpha">;
def Core : Package<"core">;
@@ -20,16 +25,33 @@ def CoreBuiltin : Package<"builtin">, InPackage<Core>;
def CoreUninitialized : Package<"uninitialized">, InPackage<Core>;
def CoreAlpha : Package<"core">, InPackage<Alpha>, Hidden;
+// The OptIn package is for checkers that are not alpha and that would normally
+// be on by default but where the driver does not have enough information to
+// determine when they are applicable. For example, localizability checkers fit
+// this criterion because the driver cannot determine whether a project is
+// localized or not -- this is best determined at the IDE or build-system level.
+//
+// The checker hierarchy under OptIn should mirror that in Alpha: checkers
+// should be organized as if they were at the top level.
+//
+// Note: OptIn is *not* intended for checkers that are too noisy to be on by
+// default. Such checkers belong in the alpha package.
+def OptIn : Package<"optin">;
+
+def Nullability : Package<"nullability">;
+
def Cplusplus : Package<"cplusplus">;
def CplusplusAlpha : Package<"cplusplus">, InPackage<Alpha>, Hidden;
def DeadCode : Package<"deadcode">;
def DeadCodeAlpha : Package<"deadcode">, InPackage<Alpha>, Hidden;
+def Performance : Package<"performance">, InPackage<OptIn>;
+
def Security : Package <"security">;
def InsecureAPI : Package<"insecureAPI">, InPackage<Security>;
def SecurityAlpha : Package<"security">, InPackage<Alpha>, Hidden;
-def Taint : Package<"taint">, InPackage<SecurityAlpha>, Hidden;
+def Taint : Package<"taint">, InPackage<SecurityAlpha>, Hidden;
def Unix : Package<"unix">;
def UnixAlpha : Package<"unix">, InPackage<Alpha>, Hidden;
@@ -38,11 +60,18 @@ def CStringAlpha : Package<"cstring">, InPackage<UnixAlpha>, Hidden;
def OSX : Package<"osx">;
def OSXAlpha : Package<"osx">, InPackage<Alpha>, Hidden;
+def OSXOptIn : Package<"osx">, InPackage<OptIn>;
+
def Cocoa : Package<"cocoa">, InPackage<OSX>;
def CocoaAlpha : Package<"cocoa">, InPackage<OSXAlpha>, Hidden;
+def CocoaOptIn : Package<"cocoa">, InPackage<OSXOptIn>;
+
def CoreFoundation : Package<"coreFoundation">, InPackage<OSX>;
def Containers : Package<"containers">, InPackage<CoreFoundation>;
+def LocalizabilityAlpha : Package<"localizability">, InPackage<CocoaAlpha>;
+def LocalizabilityOptIn : Package<"localizability">, InPackage<CocoaOptIn>;
+
def LLVM : Package<"llvm">;
def Debug : Package<"debug">;
@@ -128,8 +157,36 @@ def TestAfterDivZeroChecker : Checker<"TestAfterDivZero">,
HelpText<"Check for division by variable that is later compared against 0. Either the comparison is useless or there is division by zero.">,
DescFile<"TestAfterDivZeroChecker.cpp">;
+def DynamicTypeChecker : Checker<"DynamicTypeChecker">,
+ HelpText<"Check for cases where the dynamic and the static type of an object are unrelated.">,
+ DescFile<"DynamicTypeChecker.cpp">;
+
} // end "alpha.core"
+let ParentPackage = Nullability in {
+
+def NullPassedToNonnullChecker : Checker<"NullPassedToNonnull">,
+ HelpText<"Warns when a null pointer is passed to a pointer which has a _Nonnull type.">,
+ DescFile<"NullabilityChecker.cpp">;
+
+def NullReturnedFromNonnullChecker : Checker<"NullReturnedFromNonnull">,
+ HelpText<"Warns when a null pointer is returned from a function that has _Nonnull return type.">,
+ DescFile<"NullabilityChecker.cpp">;
+
+def NullableDereferencedChecker : Checker<"NullableDereferenced">,
+ HelpText<"Warns when a nullable pointer is dereferenced.">,
+ DescFile<"NullabilityChecker.cpp">;
+
+def NullablePassedToNonnullChecker : Checker<"NullablePassedToNonnull">,
+ HelpText<"Warns when a nullable pointer is passed to a pointer which has a _Nonnull type.">,
+ DescFile<"NullabilityChecker.cpp">;
+
+def NullableReturnedFromNonnullChecker : Checker<"NullablePassedToNonnull">,
+ HelpText<"Warns when a nullable pointer is returned from a function that has _Nonnull return type.">,
+ DescFile<"NullabilityChecker.cpp">;
+
+} // end "nullability"
+
//===----------------------------------------------------------------------===//
// Evaluate "builtin" functions.
//===----------------------------------------------------------------------===//
@@ -167,7 +224,7 @@ def UndefBranchChecker : Checker<"Branch">,
def UndefCapturedBlockVarChecker : Checker<"CapturedBlockVariable">,
HelpText<"Check for blocks that capture uninitialized values">,
DescFile<"UndefCapturedBlockVarChecker.cpp">;
-
+
def ReturnUndefChecker : Checker<"UndefReturn">,
HelpText<"Check for uninitialized values being returned to the caller">,
DescFile<"ReturnUndefChecker.cpp">;
@@ -181,11 +238,11 @@ def ReturnUndefChecker : Checker<"UndefReturn">,
let ParentPackage = Cplusplus in {
def NewDeleteChecker : Checker<"NewDelete">,
- HelpText<"Check for double-free and use-after-free problems. Traces memory managed by new/delete.">,
+ HelpText<"Check for double-free and use-after-free problems. Traces memory managed by new/delete.">,
DescFile<"MallocChecker.cpp">;
def NewDeleteLeaksChecker : Checker<"NewDeleteLeaks">,
- HelpText<"Check for memory leaks. Traces memory managed by new/delete.">,
+ HelpText<"Check for memory leaks. Traces memory managed by new/delete.">,
DescFile<"MallocChecker.cpp">;
} // end: "cplusplus"
@@ -193,7 +250,7 @@ def NewDeleteLeaksChecker : Checker<"NewDeleteLeaks">,
let ParentPackage = CplusplusAlpha in {
def VirtualCallChecker : Checker<"VirtualCall">,
- HelpText<"Check virtual function calls during construction or destruction">,
+ HelpText<"Check virtual function calls during construction or destruction">,
DescFile<"VirtualCallChecker.cpp">;
} // end: "alpha.cplusplus"
@@ -218,6 +275,18 @@ def UnreachableCodeChecker : Checker<"UnreachableCode">,
} // end "alpha.deadcode"
//===----------------------------------------------------------------------===//
+// Performance checkers.
+//===----------------------------------------------------------------------===//
+
+let ParentPackage = Performance in {
+
+def PaddingChecker : Checker<"Padding">,
+ HelpText<"Check for excessively padded structs.">,
+ DescFile<"PaddingChecker.cpp">;
+
+} // end: "padding"
+
+//===----------------------------------------------------------------------===//
// Security checkers.
//===----------------------------------------------------------------------===//
@@ -257,7 +326,7 @@ let ParentPackage = SecurityAlpha in {
def ArrayBoundChecker : Checker<"ArrayBound">,
HelpText<"Warn about buffer overflows (older checker)">,
- DescFile<"ArrayBoundChecker.cpp">;
+ DescFile<"ArrayBoundChecker.cpp">;
def ArrayBoundCheckerV2 : Checker<"ArrayBoundV2">,
HelpText<"Warn about buffer overflows (newer checker)">,
@@ -298,7 +367,7 @@ def UnixAPIChecker : Checker<"API">,
def MallocChecker: Checker<"Malloc">,
HelpText<"Check for memory leaks, double free, and use-after-free problems. Traces memory managed by malloc()/free().">,
DescFile<"MallocChecker.cpp">;
-
+
def MallocSizeofChecker : Checker<"MallocSizeof">,
HelpText<"Check for dubious malloc arguments involving sizeof">,
DescFile<"MallocSizeofChecker.cpp">;
@@ -306,7 +375,11 @@ def MallocSizeofChecker : Checker<"MallocSizeof">,
def MismatchedDeallocatorChecker : Checker<"MismatchedDeallocator">,
HelpText<"Check for mismatched deallocators.">,
DescFile<"MallocChecker.cpp">;
-
+
+def VforkChecker : Checker<"Vfork">,
+ HelpText<"Check for proper usage of vfork">,
+ DescFile<"VforkChecker.cpp">;
+
} // end "unix"
let ParentPackage = UnixAlpha in {
@@ -337,7 +410,7 @@ def CStringNullArg : Checker<"NullArg">,
def CStringSyntaxChecker : Checker<"BadSizeArg">,
HelpText<"Check the size argument passed into C string functions for common erroneous patterns">,
- DescFile<"CStringSyntaxChecker.cpp">;
+ DescFile<"CStringSyntaxChecker.cpp">;
}
let ParentPackage = CStringAlpha in {
@@ -428,6 +501,10 @@ def RetainCountChecker : Checker<"RetainCount">,
HelpText<"Check for leaks and improper reference count management">,
DescFile<"RetainCountChecker.cpp">;
+def ObjCGenericsChecker : Checker<"ObjCGenerics">,
+ HelpText<"Check for type errors when using Objective-C generics">,
+ DescFile<"DynamicTypePropagation.cpp">;
+
} // end "osx.cocoa"
let ParentPackage = CocoaAlpha in {
@@ -477,8 +554,25 @@ def ObjCContainersASTChecker : Checker<"PointerSizedValues">,
def ObjCContainersChecker : Checker<"OutOfBounds">,
HelpText<"Checks for index out-of-bounds when using 'CFArray' API">,
DescFile<"ObjCContainersChecker.cpp">;
-
+
}
+
+let ParentPackage = LocalizabilityOptIn in {
+def NonLocalizedStringChecker : Checker<"NonLocalizedStringChecker">,
+ HelpText<"Warns about uses of non-localized NSStrings passed to UI methods expecting localized NSStrings">,
+ DescFile<"LocalizationChecker.cpp">;
+
+def EmptyLocalizationContextChecker : Checker<"EmptyLocalizationContextChecker">,
+ HelpText<"Check that NSLocalizedString macros include a comment for context">,
+ DescFile<"LocalizationChecker.cpp">;
+}
+
+let ParentPackage = LocalizabilityAlpha in {
+def PluralMisuseChecker : Checker<"PluralMisuseChecker">,
+ HelpText<"Warns against using one vs. many plural pattern in code when generating localized strings.">,
+ DescFile<"LocalizationChecker.cpp">;
+}
+
//===----------------------------------------------------------------------===//
// Checkers for LLVM development.
//===----------------------------------------------------------------------===//
@@ -546,4 +640,8 @@ def ExplodedGraphViewer : Checker<"ViewExplodedGraph">,
HelpText<"View Exploded Graphs using GraphViz">,
DescFile<"DebugCheckers.cpp">;
+def BugHashDumper : Checker<"DumpBugHash">,
+ HelpText<"Dump the bug hash for all statements.">,
+ DescFile<"DebugCheckers.cpp">;
+
} // end "debug"
diff --git a/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp b/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp
index 804e83c0fb2a..3ad1996db893 100644
--- a/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp
@@ -27,7 +27,7 @@ namespace {
// enum value that represent the jail state
enum Kind { NO_CHROOT, ROOT_CHANGED, JAIL_ENTERED };
-
+
bool isRootChanged(intptr_t k) { return k == ROOT_CHANGED; }
//bool isJailEntered(intptr_t k) { return k == JAIL_ENTERED; }
@@ -50,7 +50,7 @@ public:
static int x;
return &x;
}
-
+
bool evalCall(const CallExpr *CE, CheckerContext &C) const;
void checkPreStmt(const CallExpr *CE, CheckerContext &C) const;
@@ -87,8 +87,8 @@ bool ChrootChecker::evalCall(const CallExpr *CE, CheckerContext &C) const {
void ChrootChecker::Chroot(CheckerContext &C, const CallExpr *CE) const {
ProgramStateRef state = C.getState();
ProgramStateManager &Mgr = state->getStateManager();
-
- // Once encouter a chroot(), set the enum value ROOT_CHANGED directly in
+
+ // Once encouter a chroot(), set the enum value ROOT_CHANGED directly in
// the GDM.
state = Mgr.addGDM(state, ChrootChecker::getTag(), (void*) ROOT_CHANGED);
C.addTransition(state);
@@ -106,7 +106,7 @@ void ChrootChecker::Chdir(CheckerContext &C, const CallExpr *CE) const {
// After chdir("/"), enter the jail, set the enum value JAIL_ENTERED.
const Expr *ArgExpr = CE->getArg(0);
SVal ArgVal = state->getSVal(ArgExpr, C.getLocationContext());
-
+
if (const MemRegion *R = ArgVal.getAsRegion()) {
R = R->StripCasts();
if (const StringRegion* StrRegion= dyn_cast<StringRegion>(R)) {
@@ -135,12 +135,12 @@ void ChrootChecker::checkPreStmt(const CallExpr *CE, CheckerContext &C) const {
// Ingnore chroot and chdir.
if (FD->getIdentifier() == II_chroot || FD->getIdentifier() == II_chdir)
return;
-
+
// If jail state is ROOT_CHANGED, generate BugReport.
void *const* k = C.getState()->FindGDM(ChrootChecker::getTag());
if (k)
if (isRootChanged((intptr_t) *k))
- if (ExplodedNode *N = C.addTransition()) {
+ if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
if (!BT_BreakJail)
BT_BreakJail.reset(new BuiltinBug(
this, "Break out of jail", "No call of chdir(\"/\") immediately "
diff --git a/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp b/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp
index f4be5b3e82f4..f2a269a3335c 100644
--- a/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp
@@ -28,36 +28,36 @@
using namespace clang;
using namespace ento;
-namespace {
-
+namespace {
+
/// A simple visitor to record what VarDecls occur in EH-handling code.
class EHCodeVisitor : public RecursiveASTVisitor<EHCodeVisitor> {
public:
bool inEH;
llvm::DenseSet<const VarDecl *> &S;
-
+
bool TraverseObjCAtFinallyStmt(ObjCAtFinallyStmt *S) {
SaveAndRestore<bool> inFinally(inEH, true);
return ::RecursiveASTVisitor<EHCodeVisitor>::TraverseObjCAtFinallyStmt(S);
}
-
+
bool TraverseObjCAtCatchStmt(ObjCAtCatchStmt *S) {
SaveAndRestore<bool> inCatch(inEH, true);
return ::RecursiveASTVisitor<EHCodeVisitor>::TraverseObjCAtCatchStmt(S);
}
-
+
bool TraverseCXXCatchStmt(CXXCatchStmt *S) {
SaveAndRestore<bool> inCatch(inEH, true);
return TraverseStmt(S->getHandlerBlock());
}
-
+
bool VisitDeclRefExpr(DeclRefExpr *DR) {
if (inEH)
if (const VarDecl *D = dyn_cast<VarDecl>(DR->getDecl()))
S.insert(D);
return true;
}
-
+
EHCodeVisitor(llvm::DenseSet<const VarDecl *> &S) :
inEH(false), S(S) {}
};
@@ -70,9 +70,9 @@ class ReachableCode {
public:
ReachableCode(const CFG &cfg)
: cfg(cfg), reachable(cfg.getNumBlockIDs(), false) {}
-
+
void computeReachableBlocks();
-
+
bool isReachable(const CFGBlock *block) const {
return reachable[block->getBlockID()];
}
@@ -82,7 +82,7 @@ public:
void ReachableCode::computeReachableBlocks() {
if (!cfg.getNumBlockIDs())
return;
-
+
SmallVector<const CFGBlock*, 10> worklist;
worklist.push_back(&cfg.getEntry());
@@ -160,19 +160,19 @@ public:
// to analyze that yet.
return InEH->count(D);
}
-
+
void Report(const VarDecl *V, DeadStoreKind dsk,
PathDiagnosticLocation L, SourceRange R) {
if (Escaped.count(V))
return;
-
+
// Compute reachable blocks within the CFG for trivial cases
// where a bogus dead store can be reported because itself is unreachable.
if (!reachableCode.get()) {
reachableCode.reset(new ReachableCode(cfg));
reachableCode->computeReachableBlocks();
}
-
+
if (!reachableCode->isReachable(currentBlock))
return;
@@ -196,7 +196,7 @@ public:
case Enclosing:
// Don't report issues in this case, e.g.: "if (x = foo())",
- // where 'x' is unused later. We have yet to see a case where
+ // where 'x' is unused later. We have yet to see a case where
// this is a real bug.
return;
}
@@ -259,7 +259,7 @@ public:
const LiveVariables::LivenessValues &Live) override {
currentBlock = block;
-
+
// Skip statements in macros.
if (S->getLocStart().isMacroID())
return;
@@ -276,7 +276,7 @@ public:
const Expr *RHS =
LookThroughTransitiveAssignmentsAndCommaOperators(B->getRHS());
RHS = RHS->IgnoreParenCasts();
-
+
QualType T = VD->getType();
if (T->isPointerType() || T->isObjCObjectPointerType()) {
if (RHS->isNullPointerConstant(Ctx, Expr::NPC_ValueDependentIsNull))
@@ -318,27 +318,27 @@ public:
if (!V)
continue;
-
- if (V->hasLocalStorage()) {
+
+ if (V->hasLocalStorage()) {
// Reference types confuse the dead stores checker. Skip them
// for now.
if (V->getType()->getAs<ReferenceType>())
return;
-
+
if (const Expr *E = V->getInit()) {
while (const ExprWithCleanups *exprClean =
dyn_cast<ExprWithCleanups>(E))
E = exprClean->getSubExpr();
-
+
// Look through transitive assignments, e.g.:
// int x = y = 0;
E = LookThroughTransitiveAssignmentsAndCommaOperators(E);
-
+
// Don't warn on C++ objects (yet) until we can show that their
// constructors/destructors don't have side effects.
if (isa<CXXConstructExpr>(E))
return;
-
+
// A dead initialization is a variable that is dead after it
// is initialized. We don't flag warnings for those variables
// marked 'unused' or 'objc_precise_lifetime'.
@@ -401,6 +401,11 @@ public:
// Check for '&'. Any VarDecl whose address has been taken we treat as
// escaped.
// FIXME: What about references?
+ if (auto *LE = dyn_cast<LambdaExpr>(S)) {
+ findLambdaReferenceCaptures(LE);
+ return;
+ }
+
const UnaryOperator *U = dyn_cast<UnaryOperator>(S);
if (!U)
return;
@@ -412,6 +417,28 @@ public:
if (const VarDecl *VD = dyn_cast<VarDecl>(DR->getDecl()))
Escaped.insert(VD);
}
+
+ // Treat local variables captured by reference in C++ lambdas as escaped.
+ void findLambdaReferenceCaptures(const LambdaExpr *LE) {
+ const CXXRecordDecl *LambdaClass = LE->getLambdaClass();
+ llvm::DenseMap<const VarDecl *, FieldDecl *> CaptureFields;
+ FieldDecl *ThisCaptureField;
+ LambdaClass->getCaptureFields(CaptureFields, ThisCaptureField);
+
+ for (const LambdaCapture &C : LE->captures()) {
+ if (!C.capturesVariable())
+ continue;
+
+ VarDecl *VD = C.getCapturedVar();
+ const FieldDecl *FD = CaptureFields[VD];
+ if (!FD)
+ continue;
+
+ // If the capture field is a reference type, it is capture-by-reference.
+ if (FD->getType()->isReferenceType())
+ Escaped.insert(VD);
+ }
+ }
};
} // end anonymous namespace
diff --git a/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp b/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp
index 51e7a3d3ce34..2eef1688d4c4 100644
--- a/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp
+++ b/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp
@@ -16,7 +16,10 @@
#include "clang/Analysis/Analyses/LiveVariables.h"
#include "clang/Analysis/CallGraph.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/IssueHash.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h"
#include "llvm/Support/Process.h"
@@ -209,3 +212,36 @@ public:
void ento::registerExplodedGraphViewer(CheckerManager &mgr) {
mgr.registerChecker<ExplodedGraphViewer>();
}
+
+//===----------------------------------------------------------------------===//
+// DumpBugHash
+//===----------------------------------------------------------------------===//
+
+namespace {
+class BugHashDumper : public Checker<check::PostStmt<Stmt>> {
+public:
+ mutable std::unique_ptr<BugType> BT;
+
+ void checkPostStmt(const Stmt *S, CheckerContext &C) const {
+ if (!BT)
+ BT.reset(new BugType(this, "Dump hash components", "debug"));
+
+ ExplodedNode *N = C.generateNonFatalErrorNode();
+ if (!N)
+ return;
+
+ const LangOptions &Opts = C.getLangOpts();
+ const SourceManager &SM = C.getSourceManager();
+ FullSourceLoc FL(S->getLocStart(), SM);
+ std::string HashContent =
+ GetIssueString(SM, FL, getCheckName().getName(), BT->getCategory(),
+ C.getLocationContext()->getDecl(), Opts);
+
+ C.emitReport(llvm::make_unique<BugReport>(*BT, HashContent, N));
+ }
+};
+}
+
+void ento::registerBugHashDumper(CheckerManager &mgr) {
+ mgr.registerChecker<BugHashDumper>();
+}
diff --git a/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp b/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp
index 2ba7ea4a4e4d..5dd28320f88f 100644
--- a/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp
@@ -14,10 +14,12 @@
#include "ClangSACheckers.h"
#include "clang/AST/ExprObjC.h"
+#include "clang/AST/ExprOpenMP.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/Support/raw_ostream.h"
@@ -83,14 +85,14 @@ DereferenceChecker::AddDerefSource(raw_ostream &os,
SourceLocation L = IV->getLocation();
Ranges.push_back(SourceRange(L, L));
break;
- }
+ }
}
}
void DereferenceChecker::reportBug(ProgramStateRef State, const Stmt *S,
CheckerContext &C, bool IsBind) const {
// Generate an error node.
- ExplodedNode *N = C.generateSink(State);
+ ExplodedNode *N = C.generateErrorNode(State);
if (!N)
return;
@@ -110,15 +112,11 @@ void DereferenceChecker::reportBug(ProgramStateRef State, const Stmt *S,
S = expr->IgnoreParenLValueCasts();
if (IsBind) {
- if (const BinaryOperator *BO = dyn_cast<BinaryOperator>(S)) {
- if (BO->isAssignmentOp())
- S = BO->getRHS();
- } else if (const DeclStmt *DS = dyn_cast<DeclStmt>(S)) {
- assert(DS->isSingleDecl() && "We process decls one by one");
- if (const VarDecl *VD = dyn_cast<VarDecl>(DS->getSingleDecl()))
- if (const Expr *Init = VD->getAnyInitializer())
- S = Init;
- }
+ const VarDecl *VD;
+ const Expr *Init;
+ std::tie(VD, Init) = parseAssignment(S);
+ if (VD && Init)
+ S = Init;
}
switch (S->getStmtClass()) {
@@ -130,6 +128,14 @@ void DereferenceChecker::reportBug(ProgramStateRef State, const Stmt *S,
os << " results in a null pointer dereference";
break;
}
+ case Stmt::OMPArraySectionExprClass: {
+ os << "Array access";
+ const OMPArraySectionExpr *AE = cast<OMPArraySectionExpr>(S);
+ AddDerefSource(os, Ranges, AE->getBase()->IgnoreParenCasts(),
+ State.get(), N->getLocationContext());
+ os << " results in a null pointer dereference";
+ break;
+ }
case Stmt::UnaryOperatorClass: {
os << "Dereference of null pointer";
const UnaryOperator *U = cast<UnaryOperator>(S);
@@ -159,7 +165,6 @@ void DereferenceChecker::reportBug(ProgramStateRef State, const Stmt *S,
break;
}
- os.flush();
auto report = llvm::make_unique<BugReport>(
*BT_null, buf.empty() ? BT_null->getDescription() : StringRef(buf), N);
@@ -176,7 +181,7 @@ void DereferenceChecker::checkLocation(SVal l, bool isLoad, const Stmt* S,
CheckerContext &C) const {
// Check for dereference of an undefined value.
if (l.isUndef()) {
- if (ExplodedNode *N = C.generateSink()) {
+ if (ExplodedNode *N = C.generateErrorNode()) {
if (!BT_undef)
BT_undef.reset(
new BuiltinBug(this, "Dereference of undefined pointer value"));
@@ -211,8 +216,9 @@ void DereferenceChecker::checkLocation(SVal l, bool isLoad, const Stmt* S,
// Otherwise, we have the case where the location could either be
// null or not-null. Record the error node as an "implicit" null
// dereference.
- if (ExplodedNode *N = C.generateSink(nullState)) {
- ImplicitNullDerefEvent event = { l, isLoad, N, &C.getBugReporter() };
+ if (ExplodedNode *N = C.generateSink(nullState, C.getPredecessor())) {
+ ImplicitNullDerefEvent event = {l, isLoad, N, &C.getBugReporter(),
+ /*IsDirectDereference=*/false};
dispatchEvent(event);
}
}
@@ -248,9 +254,10 @@ void DereferenceChecker::checkBind(SVal L, SVal V, const Stmt *S,
// At this point the value could be either null or non-null.
// Record this as an "implicit" null dereference.
- if (ExplodedNode *N = C.generateSink(StNull)) {
- ImplicitNullDerefEvent event = { V, /*isLoad=*/true, N,
- &C.getBugReporter() };
+ if (ExplodedNode *N = C.generateSink(StNull, C.getPredecessor())) {
+ ImplicitNullDerefEvent event = {V, /*isLoad=*/true, N,
+ &C.getBugReporter(),
+ /*IsDirectDereference=*/false};
dispatchEvent(event);
}
}
diff --git a/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp b/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp
index a71def23c0bc..ad478cbf7829 100644
--- a/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp
+++ b/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp
@@ -41,13 +41,12 @@ namespace {
/// Checks for the init, dealloc, and any other functions that might be allowed
/// to perform direct instance variable assignment based on their name.
static bool DefaultMethodFilter(const ObjCMethodDecl *M) {
- if (M->getMethodFamily() == OMF_init || M->getMethodFamily() == OMF_dealloc ||
- M->getMethodFamily() == OMF_copy ||
- M->getMethodFamily() == OMF_mutableCopy ||
- M->getSelector().getNameForSlot(0).find("init") != StringRef::npos ||
- M->getSelector().getNameForSlot(0).find("Init") != StringRef::npos)
- return true;
- return false;
+ return M->getMethodFamily() == OMF_init ||
+ M->getMethodFamily() == OMF_dealloc ||
+ M->getMethodFamily() == OMF_copy ||
+ M->getMethodFamily() == OMF_mutableCopy ||
+ M->getSelector().getNameForSlot(0).find("init") != StringRef::npos ||
+ M->getSelector().getNameForSlot(0).find("Init") != StringRef::npos;
}
class DirectIvarAssignment :
diff --git a/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp b/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp
index 79f9479b1448..598502305633 100644
--- a/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp
@@ -29,13 +29,13 @@ class DivZeroChecker : public Checker< check::PreStmt<BinaryOperator> > {
CheckerContext &C) const ;
public:
void checkPreStmt(const BinaryOperator *B, CheckerContext &C) const;
-};
+};
} // end anonymous namespace
void DivZeroChecker::reportBug(const char *Msg,
ProgramStateRef StateZero,
CheckerContext &C) const {
- if (ExplodedNode *N = C.generateSink(StateZero)) {
+ if (ExplodedNode *N = C.generateErrorNode(StateZero)) {
if (!BT)
BT.reset(new BuiltinBug(this, "Division by zero"));
diff --git a/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp b/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp
new file mode 100644
index 000000000000..7e0cb8e93395
--- /dev/null
+++ b/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp
@@ -0,0 +1,213 @@
+//== DynamicTypeChecker.cpp ------------------------------------ -*- C++ -*--=//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This checker looks for cases where the dynamic type of an object is unrelated
+// to its static type. The type information utilized by this check is collected
+// by the DynamicTypePropagation checker. This check does not report any type
+// error for ObjC Generic types, in order to avoid duplicate erros from the
+// ObjC Generics checker. This checker is not supposed to modify the program
+// state, it is just the observer of the type information provided by other
+// checkers.
+//
+//===----------------------------------------------------------------------===//
+
+#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/CheckerManager.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
+
+using namespace clang;
+using namespace ento;
+
+namespace {
+class DynamicTypeChecker : public Checker<check::PostStmt<ImplicitCastExpr>> {
+ mutable std::unique_ptr<BugType> BT;
+ void initBugType() const {
+ if (!BT)
+ BT.reset(
+ new BugType(this, "Dynamic and static type mismatch", "Type Error"));
+ }
+
+ class DynamicTypeBugVisitor
+ : public BugReporterVisitorImpl<DynamicTypeBugVisitor> {
+ public:
+ DynamicTypeBugVisitor(const MemRegion *Reg) : Reg(Reg) {}
+
+ void Profile(llvm::FoldingSetNodeID &ID) const override {
+ static int X = 0;
+ ID.AddPointer(&X);
+ ID.AddPointer(Reg);
+ }
+
+ PathDiagnosticPiece *VisitNode(const ExplodedNode *N,
+ const ExplodedNode *PrevN,
+ BugReporterContext &BRC,
+ BugReport &BR) override;
+
+ private:
+ // The tracked region.
+ const MemRegion *Reg;
+ };
+
+ void reportTypeError(QualType DynamicType, QualType StaticType,
+ const MemRegion *Reg, const Stmt *ReportedNode,
+ CheckerContext &C) const;
+
+public:
+ void checkPostStmt(const ImplicitCastExpr *CE, CheckerContext &C) const;
+};
+}
+
+void DynamicTypeChecker::reportTypeError(QualType DynamicType,
+ QualType StaticType,
+ const MemRegion *Reg,
+ const Stmt *ReportedNode,
+ CheckerContext &C) const {
+ initBugType();
+ SmallString<192> Buf;
+ llvm::raw_svector_ostream OS(Buf);
+ OS << "Object has a dynamic type '";
+ QualType::print(DynamicType.getTypePtr(), Qualifiers(), OS, C.getLangOpts(),
+ llvm::Twine());
+ OS << "' which is incompatible with static type '";
+ QualType::print(StaticType.getTypePtr(), Qualifiers(), OS, C.getLangOpts(),
+ llvm::Twine());
+ OS << "'";
+ std::unique_ptr<BugReport> R(
+ new BugReport(*BT, OS.str(), C.generateNonFatalErrorNode()));
+ R->markInteresting(Reg);
+ R->addVisitor(llvm::make_unique<DynamicTypeBugVisitor>(Reg));
+ R->addRange(ReportedNode->getSourceRange());
+ C.emitReport(std::move(R));
+}
+
+PathDiagnosticPiece *DynamicTypeChecker::DynamicTypeBugVisitor::VisitNode(
+ const ExplodedNode *N, const ExplodedNode *PrevN, BugReporterContext &BRC,
+ BugReport &BR) {
+ ProgramStateRef State = N->getState();
+ ProgramStateRef StatePrev = PrevN->getState();
+
+ DynamicTypeInfo TrackedType = getDynamicTypeInfo(State, Reg);
+ DynamicTypeInfo TrackedTypePrev = getDynamicTypeInfo(StatePrev, Reg);
+ if (!TrackedType.isValid())
+ return nullptr;
+
+ if (TrackedTypePrev.isValid() &&
+ TrackedTypePrev.getType() == TrackedType.getType())
+ return nullptr;
+
+ // Retrieve the associated statement.
+ const Stmt *S = nullptr;
+ ProgramPoint ProgLoc = N->getLocation();
+ if (Optional<StmtPoint> SP = ProgLoc.getAs<StmtPoint>()) {
+ S = SP->getStmt();
+ }
+
+ if (!S)
+ return nullptr;
+
+ const LangOptions &LangOpts = BRC.getASTContext().getLangOpts();
+
+ SmallString<256> Buf;
+ llvm::raw_svector_ostream OS(Buf);
+ OS << "Type '";
+ QualType::print(TrackedType.getType().getTypePtr(), Qualifiers(), OS,
+ LangOpts, llvm::Twine());
+ OS << "' is inferred from ";
+
+ if (const auto *ExplicitCast = dyn_cast<ExplicitCastExpr>(S)) {
+ OS << "explicit cast (from '";
+ QualType::print(ExplicitCast->getSubExpr()->getType().getTypePtr(),
+ Qualifiers(), OS, LangOpts, llvm::Twine());
+ OS << "' to '";
+ QualType::print(ExplicitCast->getType().getTypePtr(), Qualifiers(), OS,
+ LangOpts, llvm::Twine());
+ OS << "')";
+ } else if (const auto *ImplicitCast = dyn_cast<ImplicitCastExpr>(S)) {
+ OS << "implicit cast (from '";
+ QualType::print(ImplicitCast->getSubExpr()->getType().getTypePtr(),
+ Qualifiers(), OS, LangOpts, llvm::Twine());
+ OS << "' to '";
+ QualType::print(ImplicitCast->getType().getTypePtr(), Qualifiers(), OS,
+ LangOpts, llvm::Twine());
+ OS << "')";
+ } else {
+ OS << "this context";
+ }
+
+ // Generate the extra diagnostic.
+ PathDiagnosticLocation Pos(S, BRC.getSourceManager(),
+ N->getLocationContext());
+ return new PathDiagnosticEventPiece(Pos, OS.str(), true, nullptr);
+}
+
+static bool hasDefinition(const ObjCObjectPointerType *ObjPtr) {
+ const ObjCInterfaceDecl *Decl = ObjPtr->getInterfaceDecl();
+ if (!Decl)
+ return false;
+
+ return Decl->getDefinition();
+}
+
+// TODO: consider checking explicit casts?
+void DynamicTypeChecker::checkPostStmt(const ImplicitCastExpr *CE,
+ CheckerContext &C) const {
+ // TODO: C++ support.
+ if (CE->getCastKind() != CK_BitCast)
+ return;
+
+ const MemRegion *Region = C.getSVal(CE).getAsRegion();
+ if (!Region)
+ return;
+
+ ProgramStateRef State = C.getState();
+ DynamicTypeInfo DynTypeInfo = getDynamicTypeInfo(State, Region);
+
+ if (!DynTypeInfo.isValid())
+ return;
+
+ QualType DynType = DynTypeInfo.getType();
+ QualType StaticType = CE->getType();
+
+ const auto *DynObjCType = DynType->getAs<ObjCObjectPointerType>();
+ const auto *StaticObjCType = StaticType->getAs<ObjCObjectPointerType>();
+
+ if (!DynObjCType || !StaticObjCType)
+ return;
+
+ if (!hasDefinition(DynObjCType) || !hasDefinition(StaticObjCType))
+ return;
+
+ ASTContext &ASTCtxt = C.getASTContext();
+
+ // Strip kindeofness to correctly detect subtyping relationships.
+ DynObjCType = DynObjCType->stripObjCKindOfTypeAndQuals(ASTCtxt);
+ StaticObjCType = StaticObjCType->stripObjCKindOfTypeAndQuals(ASTCtxt);
+
+ // Specialized objects are handled by the generics checker.
+ if (StaticObjCType->isSpecialized())
+ return;
+
+ if (ASTCtxt.canAssignObjCInterfaces(StaticObjCType, DynObjCType))
+ return;
+
+ if (DynTypeInfo.canBeASubClass() &&
+ ASTCtxt.canAssignObjCInterfaces(DynObjCType, StaticObjCType))
+ return;
+
+ reportTypeError(DynType, StaticType, Region, CE, C);
+}
+
+void ento::registerDynamicTypeChecker(CheckerManager &mgr) {
+ mgr.registerChecker<DynamicTypeChecker>();
+}
diff --git a/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp b/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp
index 43a281218775..30f629830c61 100644
--- a/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp
+++ b/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp
@@ -7,42 +7,139 @@
//
//===----------------------------------------------------------------------===//
//
+// This file contains two checkers. One helps the static analyzer core to track
+// types, the other does type inference on Obj-C generics and report type
+// errors.
+//
+// Dynamic Type Propagation:
// This checker defines the rules for dynamic type gathering and propagation.
//
+// Generics Checker for Objective-C:
+// This checker tries to find type errors that the compiler is not able to catch
+// due to the implicit conversions that were introduced for backward
+// compatibility.
+//
//===----------------------------------------------------------------------===//
#include "ClangSACheckers.h"
+#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Basic/Builtins.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
using namespace clang;
using namespace ento;
+// ProgramState trait - The type inflation is tracked by DynamicTypeMap. This is
+// an auxiliary map that tracks more information about generic types, because in
+// some cases the most derived type is not the most informative one about the
+// type parameters. This types that are stored for each symbol in this map must
+// be specialized.
+// TODO: In some case the type stored in this map is exactly the same that is
+// stored in DynamicTypeMap. We should no store duplicated information in those
+// cases.
+REGISTER_MAP_WITH_PROGRAMSTATE(MostSpecializedTypeArgsMap, SymbolRef,
+ const ObjCObjectPointerType *)
+
namespace {
class DynamicTypePropagation:
public Checker< check::PreCall,
check::PostCall,
- check::PostStmt<ImplicitCastExpr>,
- check::PostStmt<CXXNewExpr> > {
+ check::DeadSymbols,
+ check::PostStmt<CastExpr>,
+ check::PostStmt<CXXNewExpr>,
+ check::PreObjCMessage,
+ check::PostObjCMessage > {
const ObjCObjectType *getObjectTypeForAllocAndNew(const ObjCMessageExpr *MsgE,
CheckerContext &C) const;
/// \brief Return a better dynamic type if one can be derived from the cast.
const ObjCObjectPointerType *getBetterObjCType(const Expr *CastE,
CheckerContext &C) const;
+
+ ExplodedNode *dynamicTypePropagationOnCasts(const CastExpr *CE,
+ ProgramStateRef &State,
+ CheckerContext &C) const;
+
+ mutable std::unique_ptr<BugType> ObjCGenericsBugType;
+ void initBugType() const {
+ if (!ObjCGenericsBugType)
+ ObjCGenericsBugType.reset(
+ new BugType(this, "Generics", categories::CoreFoundationObjectiveC));
+ }
+
+ class GenericsBugVisitor : public BugReporterVisitorImpl<GenericsBugVisitor> {
+ public:
+ GenericsBugVisitor(SymbolRef S) : Sym(S) {}
+
+ void Profile(llvm::FoldingSetNodeID &ID) const override {
+ static int X = 0;
+ ID.AddPointer(&X);
+ ID.AddPointer(Sym);
+ }
+
+ PathDiagnosticPiece *VisitNode(const ExplodedNode *N,
+ const ExplodedNode *PrevN,
+ BugReporterContext &BRC,
+ BugReport &BR) override;
+
+ private:
+ // The tracked symbol.
+ SymbolRef Sym;
+ };
+
+ void reportGenericsBug(const ObjCObjectPointerType *From,
+ const ObjCObjectPointerType *To, ExplodedNode *N,
+ SymbolRef Sym, CheckerContext &C,
+ const Stmt *ReportedNode = nullptr) const;
public:
void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
- void checkPostStmt(const ImplicitCastExpr *CastE, CheckerContext &C) const;
+ void checkPostStmt(const CastExpr *CastE, CheckerContext &C) const;
void checkPostStmt(const CXXNewExpr *NewE, CheckerContext &C) const;
+ void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const;
+ void checkPreObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;
+ void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;
+
+ /// This value is set to true, when the Generics checker is turned on.
+ DefaultBool CheckGenerics;
};
}
+void DynamicTypePropagation::checkDeadSymbols(SymbolReaper &SR,
+ CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+ DynamicTypeMapImpl TypeMap = State->get<DynamicTypeMap>();
+ for (DynamicTypeMapImpl::iterator I = TypeMap.begin(), E = TypeMap.end();
+ I != E; ++I) {
+ if (!SR.isLiveRegion(I->first)) {
+ State = State->remove<DynamicTypeMap>(I->first);
+ }
+ }
+
+ if (!SR.hasDeadSymbols()) {
+ C.addTransition(State);
+ return;
+ }
+
+ MostSpecializedTypeArgsMapTy TyArgMap =
+ State->get<MostSpecializedTypeArgsMap>();
+ for (MostSpecializedTypeArgsMapTy::iterator I = TyArgMap.begin(),
+ E = TyArgMap.end();
+ I != E; ++I) {
+ if (SR.isDead(I->first)) {
+ State = State->remove<MostSpecializedTypeArgsMap>(I->first);
+ }
+ }
+
+ C.addTransition(State);
+}
+
static void recordFixedType(const MemRegion *Region, const CXXMethodDecl *MD,
CheckerContext &C) {
assert(Region);
@@ -52,7 +149,7 @@ static void recordFixedType(const MemRegion *Region, const CXXMethodDecl *MD,
QualType Ty = Ctx.getPointerType(Ctx.getRecordType(MD->getParent()));
ProgramStateRef State = C.getState();
- State = State->setDynamicTypeInfo(Region, Ty, /*CanBeSubclass=*/false);
+ State = setDynamicTypeInfo(State, Region, Ty, /*CanBeSubclass=*/false);
C.addTransition(State);
return;
}
@@ -113,7 +210,7 @@ void DynamicTypePropagation::checkPostCall(const CallEvent &Call,
ProgramStateRef State = C.getState();
const ObjCMethodDecl *D = Msg->getDecl();
-
+
if (D && D->hasRelatedResultType()) {
switch (Msg->getMethodFamily()) {
default:
@@ -131,7 +228,7 @@ void DynamicTypePropagation::checkPostCall(const CallEvent &Call,
return;
QualType DynResTy =
C.getASTContext().getObjCObjectPointerType(QualType(ObjTy, 0));
- C.addTransition(State->setDynamicTypeInfo(RetReg, DynResTy, false));
+ C.addTransition(setDynamicTypeInfo(State, RetReg, DynResTy, false));
break;
}
case OMF_init: {
@@ -140,8 +237,8 @@ void DynamicTypePropagation::checkPostCall(const CallEvent &Call,
const MemRegion *RecReg = Msg->getReceiverSVal().getAsRegion();
if (!RecReg)
return;
- DynamicTypeInfo RecDynType = State->getDynamicTypeInfo(RecReg);
- C.addTransition(State->setDynamicTypeInfo(RetReg, RecDynType));
+ DynamicTypeInfo RecDynType = getDynamicTypeInfo(State, RecReg);
+ C.addTransition(setDynamicTypeInfo(State, RetReg, RecDynType));
break;
}
}
@@ -173,23 +270,25 @@ void DynamicTypePropagation::checkPostCall(const CallEvent &Call,
}
}
-void DynamicTypePropagation::checkPostStmt(const ImplicitCastExpr *CastE,
- CheckerContext &C) const {
- // We only track dynamic type info for regions.
- const MemRegion *ToR = C.getSVal(CastE).getAsRegion();
+/// TODO: Handle explicit casts.
+/// Handle C++ casts.
+///
+/// Precondition: the cast is between ObjCObjectPointers.
+ExplodedNode *DynamicTypePropagation::dynamicTypePropagationOnCasts(
+ const CastExpr *CE, ProgramStateRef &State, CheckerContext &C) const {
+ // We only track type info for regions.
+ const MemRegion *ToR = C.getSVal(CE).getAsRegion();
if (!ToR)
- return;
+ return C.getPredecessor();
+
+ if (isa<ExplicitCastExpr>(CE))
+ return C.getPredecessor();
- switch (CastE->getCastKind()) {
- default:
- break;
- case CK_BitCast:
- // Only handle ObjCObjects for now.
- if (const Type *NewTy = getBetterObjCType(CastE, C))
- C.addTransition(C.getState()->setDynamicTypeInfo(ToR, QualType(NewTy,0)));
- break;
+ if (const Type *NewTy = getBetterObjCType(CE, C)) {
+ State = setDynamicTypeInfo(State, ToR, QualType(NewTy, 0));
+ return C.addTransition(State);
}
- return;
+ return C.getPredecessor();
}
void DynamicTypePropagation::checkPostStmt(const CXXNewExpr *NewE,
@@ -201,9 +300,9 @@ void DynamicTypePropagation::checkPostStmt(const CXXNewExpr *NewE,
const MemRegion *MR = C.getSVal(NewE).getAsRegion();
if (!MR)
return;
-
- C.addTransition(C.getState()->setDynamicTypeInfo(MR, NewE->getType(),
- /*CanBeSubclass=*/false));
+
+ C.addTransition(setDynamicTypeInfo(C.getState(), MR, NewE->getType(),
+ /*CanBeSubclass=*/false));
}
const ObjCObjectType *
@@ -254,7 +353,7 @@ DynamicTypePropagation::getBetterObjCType(const Expr *CastE,
CastE->getType()->getAs<ObjCObjectPointerType>();
if (!NewTy)
return nullptr;
- QualType OldDTy = C.getState()->getDynamicTypeInfo(ToR).getType();
+ QualType OldDTy = getDynamicTypeInfo(C.getState(), ToR).getType();
if (OldDTy.isNull()) {
return NewTy;
}
@@ -276,6 +375,566 @@ DynamicTypePropagation::getBetterObjCType(const Expr *CastE,
return nullptr;
}
+static const ObjCObjectPointerType *getMostInformativeDerivedClassImpl(
+ const ObjCObjectPointerType *From, const ObjCObjectPointerType *To,
+ const ObjCObjectPointerType *MostInformativeCandidate, ASTContext &C) {
+ // Checking if from and to are the same classes modulo specialization.
+ if (From->getInterfaceDecl()->getCanonicalDecl() ==
+ To->getInterfaceDecl()->getCanonicalDecl()) {
+ if (To->isSpecialized()) {
+ assert(MostInformativeCandidate->isSpecialized());
+ return MostInformativeCandidate;
+ }
+ return From;
+ }
+ const auto *SuperOfTo =
+ To->getObjectType()->getSuperClassType()->getAs<ObjCObjectType>();
+ assert(SuperOfTo);
+ QualType SuperPtrOfToQual =
+ C.getObjCObjectPointerType(QualType(SuperOfTo, 0));
+ const auto *SuperPtrOfTo = SuperPtrOfToQual->getAs<ObjCObjectPointerType>();
+ if (To->isUnspecialized())
+ return getMostInformativeDerivedClassImpl(From, SuperPtrOfTo, SuperPtrOfTo,
+ C);
+ else
+ return getMostInformativeDerivedClassImpl(From, SuperPtrOfTo,
+ MostInformativeCandidate, C);
+}
+
+/// A downcast may loose specialization information. E. g.:
+/// MutableMap<T, U> : Map
+/// The downcast to MutableMap looses the information about the types of the
+/// Map (due to the type parameters are not being forwarded to Map), and in
+/// general there is no way to recover that information from the
+/// declaration. In order to have to most information, lets find the most
+/// derived type that has all the type parameters forwarded.
+///
+/// Get the a subclass of \p From (which has a lower bound \p To) that do not
+/// loose information about type parameters. \p To has to be a subclass of
+/// \p From. From has to be specialized.
+static const ObjCObjectPointerType *
+getMostInformativeDerivedClass(const ObjCObjectPointerType *From,
+ const ObjCObjectPointerType *To, ASTContext &C) {
+ return getMostInformativeDerivedClassImpl(From, To, To, C);
+}
+
+/// Inputs:
+/// \param StaticLowerBound Static lower bound for a symbol. The dynamic lower
+/// bound might be the subclass of this type.
+/// \param StaticUpperBound A static upper bound for a symbol.
+/// \p StaticLowerBound expected to be the subclass of \p StaticUpperBound.
+/// \param Current The type that was inferred for a symbol in a previous
+/// context. Might be null when this is the first time that inference happens.
+/// Precondition:
+/// \p StaticLowerBound or \p StaticUpperBound is specialized. If \p Current
+/// is not null, it is specialized.
+/// Possible cases:
+/// (1) The \p Current is null and \p StaticLowerBound <: \p StaticUpperBound
+/// (2) \p StaticLowerBound <: \p Current <: \p StaticUpperBound
+/// (3) \p Current <: \p StaticLowerBound <: \p StaticUpperBound
+/// (4) \p StaticLowerBound <: \p StaticUpperBound <: \p Current
+/// Effect:
+/// Use getMostInformativeDerivedClass with the upper and lower bound of the
+/// set {\p StaticLowerBound, \p Current, \p StaticUpperBound}. The computed
+/// lower bound must be specialized. If the result differs from \p Current or
+/// \p Current is null, store the result.
+static bool
+storeWhenMoreInformative(ProgramStateRef &State, SymbolRef Sym,
+ const ObjCObjectPointerType *const *Current,
+ const ObjCObjectPointerType *StaticLowerBound,
+ const ObjCObjectPointerType *StaticUpperBound,
+ ASTContext &C) {
+ // Precondition
+ assert(StaticUpperBound->isSpecialized() ||
+ StaticLowerBound->isSpecialized());
+ assert(!Current || (*Current)->isSpecialized());
+
+ // Case (1)
+ if (!Current) {
+ if (StaticUpperBound->isUnspecialized()) {
+ State = State->set<MostSpecializedTypeArgsMap>(Sym, StaticLowerBound);
+ return true;
+ }
+ // Upper bound is specialized.
+ const ObjCObjectPointerType *WithMostInfo =
+ getMostInformativeDerivedClass(StaticUpperBound, StaticLowerBound, C);
+ State = State->set<MostSpecializedTypeArgsMap>(Sym, WithMostInfo);
+ return true;
+ }
+
+ // Case (3)
+ if (C.canAssignObjCInterfaces(StaticLowerBound, *Current)) {
+ return false;
+ }
+
+ // Case (4)
+ if (C.canAssignObjCInterfaces(*Current, StaticUpperBound)) {
+ // The type arguments might not be forwarded at any point of inheritance.
+ const ObjCObjectPointerType *WithMostInfo =
+ getMostInformativeDerivedClass(*Current, StaticUpperBound, C);
+ WithMostInfo =
+ getMostInformativeDerivedClass(WithMostInfo, StaticLowerBound, C);
+ if (WithMostInfo == *Current)
+ return false;
+ State = State->set<MostSpecializedTypeArgsMap>(Sym, WithMostInfo);
+ return true;
+ }
+
+ // Case (2)
+ const ObjCObjectPointerType *WithMostInfo =
+ getMostInformativeDerivedClass(*Current, StaticLowerBound, C);
+ if (WithMostInfo != *Current) {
+ State = State->set<MostSpecializedTypeArgsMap>(Sym, WithMostInfo);
+ return true;
+ }
+
+ return false;
+}
+
+/// Type inference based on static type information that is available for the
+/// cast and the tracked type information for the given symbol. When the tracked
+/// symbol and the destination type of the cast are unrelated, report an error.
+void DynamicTypePropagation::checkPostStmt(const CastExpr *CE,
+ CheckerContext &C) const {
+ if (CE->getCastKind() != CK_BitCast)
+ return;
+
+ QualType OriginType = CE->getSubExpr()->getType();
+ QualType DestType = CE->getType();
+
+ const auto *OrigObjectPtrType = OriginType->getAs<ObjCObjectPointerType>();
+ const auto *DestObjectPtrType = DestType->getAs<ObjCObjectPointerType>();
+
+ if (!OrigObjectPtrType || !DestObjectPtrType)
+ return;
+
+ ProgramStateRef State = C.getState();
+ ExplodedNode *AfterTypeProp = dynamicTypePropagationOnCasts(CE, State, C);
+
+ ASTContext &ASTCtxt = C.getASTContext();
+
+ // This checker detects the subtyping relationships using the assignment
+ // rules. In order to be able to do this the kindofness must be stripped
+ // first. The checker treats every type as kindof type anyways: when the
+ // tracked type is the subtype of the static type it tries to look up the
+ // methods in the tracked type first.
+ OrigObjectPtrType = OrigObjectPtrType->stripObjCKindOfTypeAndQuals(ASTCtxt);
+ DestObjectPtrType = DestObjectPtrType->stripObjCKindOfTypeAndQuals(ASTCtxt);
+
+ // TODO: erase tracked information when there is a cast to unrelated type
+ // and everything is unspecialized statically.
+ if (OrigObjectPtrType->isUnspecialized() &&
+ DestObjectPtrType->isUnspecialized())
+ return;
+
+ SymbolRef Sym = State->getSVal(CE, C.getLocationContext()).getAsSymbol();
+ if (!Sym)
+ return;
+
+ // Check which assignments are legal.
+ bool OrigToDest =
+ ASTCtxt.canAssignObjCInterfaces(DestObjectPtrType, OrigObjectPtrType);
+ bool DestToOrig =
+ ASTCtxt.canAssignObjCInterfaces(OrigObjectPtrType, DestObjectPtrType);
+ const ObjCObjectPointerType *const *TrackedType =
+ State->get<MostSpecializedTypeArgsMap>(Sym);
+
+ // Downcasts and upcasts handled in an uniform way regardless of being
+ // explicit. Explicit casts however can happen between mismatched types.
+ if (isa<ExplicitCastExpr>(CE) && !OrigToDest && !DestToOrig) {
+ // Mismatched types. If the DestType specialized, store it. Forget the
+ // tracked type otherwise.
+ if (DestObjectPtrType->isSpecialized()) {
+ State = State->set<MostSpecializedTypeArgsMap>(Sym, DestObjectPtrType);
+ C.addTransition(State, AfterTypeProp);
+ } else if (TrackedType) {
+ State = State->remove<MostSpecializedTypeArgsMap>(Sym);
+ C.addTransition(State, AfterTypeProp);
+ }
+ return;
+ }
+
+ // The tracked type should be the sub or super class of the static destination
+ // type. When an (implicit) upcast or a downcast happens according to static
+ // types, and there is no subtyping relationship between the tracked and the
+ // static destination types, it indicates an error.
+ if (TrackedType &&
+ !ASTCtxt.canAssignObjCInterfaces(DestObjectPtrType, *TrackedType) &&
+ !ASTCtxt.canAssignObjCInterfaces(*TrackedType, DestObjectPtrType)) {
+ static CheckerProgramPointTag IllegalConv(this, "IllegalConversion");
+ ExplodedNode *N = C.addTransition(State, AfterTypeProp, &IllegalConv);
+ reportGenericsBug(*TrackedType, DestObjectPtrType, N, Sym, C);
+ return;
+ }
+
+ // Handle downcasts and upcasts.
+
+ const ObjCObjectPointerType *LowerBound = DestObjectPtrType;
+ const ObjCObjectPointerType *UpperBound = OrigObjectPtrType;
+ if (OrigToDest && !DestToOrig)
+ std::swap(LowerBound, UpperBound);
+
+ // The id type is not a real bound. Eliminate it.
+ LowerBound = LowerBound->isObjCIdType() ? UpperBound : LowerBound;
+ UpperBound = UpperBound->isObjCIdType() ? LowerBound : UpperBound;
+
+ if (storeWhenMoreInformative(State, Sym, TrackedType, LowerBound, UpperBound,
+ ASTCtxt)) {
+ C.addTransition(State, AfterTypeProp);
+ }
+}
+
+static const Expr *stripCastsAndSugar(const Expr *E) {
+ E = E->IgnoreParenImpCasts();
+ if (const PseudoObjectExpr *POE = dyn_cast<PseudoObjectExpr>(E))
+ E = POE->getSyntacticForm()->IgnoreParenImpCasts();
+ if (const OpaqueValueExpr *OVE = dyn_cast<OpaqueValueExpr>(E))
+ E = OVE->getSourceExpr()->IgnoreParenImpCasts();
+ return E;
+}
+
+static bool isObjCTypeParamDependent(QualType Type) {
+ // It is illegal to typedef parameterized types inside an interface. Therfore
+ // an Objective-C type can only be dependent on a type parameter when the type
+ // parameter structurally present in the type itself.
+ class IsObjCTypeParamDependentTypeVisitor
+ : public RecursiveASTVisitor<IsObjCTypeParamDependentTypeVisitor> {
+ public:
+ IsObjCTypeParamDependentTypeVisitor() : Result(false) {}
+ bool VisitTypedefType(const TypedefType *Type) {
+ if (isa<ObjCTypeParamDecl>(Type->getDecl())) {
+ Result = true;
+ return false;
+ }
+ return true;
+ }
+
+ bool Result;
+ };
+
+ IsObjCTypeParamDependentTypeVisitor Visitor;
+ Visitor.TraverseType(Type);
+ return Visitor.Result;
+}
+
+/// A method might not be available in the interface indicated by the static
+/// type. However it might be available in the tracked type. In order to
+/// properly substitute the type parameters we need the declaration context of
+/// the method. The more specialized the enclosing class of the method is, the
+/// more likely that the parameter substitution will be successful.
+static const ObjCMethodDecl *
+findMethodDecl(const ObjCMessageExpr *MessageExpr,
+ const ObjCObjectPointerType *TrackedType, ASTContext &ASTCtxt) {
+ const ObjCMethodDecl *Method = nullptr;
+
+ QualType ReceiverType = MessageExpr->getReceiverType();
+ const auto *ReceiverObjectPtrType =
+ ReceiverType->getAs<ObjCObjectPointerType>();
+
+ // Do this "devirtualization" on instance and class methods only. Trust the
+ // static type on super and super class calls.
+ if (MessageExpr->getReceiverKind() == ObjCMessageExpr::Instance ||
+ MessageExpr->getReceiverKind() == ObjCMessageExpr::Class) {
+ // When the receiver type is id, Class, or some super class of the tracked
+ // type, look up the method in the tracked type, not in the receiver type.
+ // This way we preserve more information.
+ if (ReceiverType->isObjCIdType() || ReceiverType->isObjCClassType() ||
+ ASTCtxt.canAssignObjCInterfaces(ReceiverObjectPtrType, TrackedType)) {
+ const ObjCInterfaceDecl *InterfaceDecl = TrackedType->getInterfaceDecl();
+ // The method might not be found.
+ Selector Sel = MessageExpr->getSelector();
+ Method = InterfaceDecl->lookupInstanceMethod(Sel);
+ if (!Method)
+ Method = InterfaceDecl->lookupClassMethod(Sel);
+ }
+ }
+
+ // Fallback to statick method lookup when the one based on the tracked type
+ // failed.
+ return Method ? Method : MessageExpr->getMethodDecl();
+}
+
+/// Get the returned ObjCObjectPointerType by a method based on the tracked type
+/// information, or null pointer when the returned type is not an
+/// ObjCObjectPointerType.
+static QualType getReturnTypeForMethod(
+ const ObjCMethodDecl *Method, ArrayRef<QualType> TypeArgs,
+ const ObjCObjectPointerType *SelfType, ASTContext &C) {
+ QualType StaticResultType = Method->getReturnType();
+
+ // Is the return type declared as instance type?
+ if (StaticResultType == C.getObjCInstanceType())
+ return QualType(SelfType, 0);
+
+ // Check whether the result type depends on a type parameter.
+ if (!isObjCTypeParamDependent(StaticResultType))
+ return QualType();
+
+ QualType ResultType = StaticResultType.substObjCTypeArgs(
+ C, TypeArgs, ObjCSubstitutionContext::Result);
+
+ return ResultType;
+}
+
+/// When the receiver has a tracked type, use that type to validate the
+/// argumments of the message expression and the return value.
+void DynamicTypePropagation::checkPreObjCMessage(const ObjCMethodCall &M,
+ CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+ SymbolRef Sym = M.getReceiverSVal().getAsSymbol();
+ if (!Sym)
+ return;
+
+ const ObjCObjectPointerType *const *TrackedType =
+ State->get<MostSpecializedTypeArgsMap>(Sym);
+ if (!TrackedType)
+ return;
+
+ // Get the type arguments from tracked type and substitute type arguments
+ // before do the semantic check.
+
+ ASTContext &ASTCtxt = C.getASTContext();
+ const ObjCMessageExpr *MessageExpr = M.getOriginExpr();
+ const ObjCMethodDecl *Method =
+ findMethodDecl(MessageExpr, *TrackedType, ASTCtxt);
+
+ // It is possible to call non-existent methods in Obj-C.
+ if (!Method)
+ return;
+
+ Optional<ArrayRef<QualType>> TypeArgs =
+ (*TrackedType)->getObjCSubstitutions(Method->getDeclContext());
+ // This case might happen when there is an unspecialized override of a
+ // specialized method.
+ if (!TypeArgs)
+ return;
+
+ for (unsigned i = 0; i < Method->param_size(); i++) {
+ const Expr *Arg = MessageExpr->getArg(i);
+ const ParmVarDecl *Param = Method->parameters()[i];
+
+ QualType OrigParamType = Param->getType();
+ if (!isObjCTypeParamDependent(OrigParamType))
+ continue;
+
+ QualType ParamType = OrigParamType.substObjCTypeArgs(
+ ASTCtxt, *TypeArgs, ObjCSubstitutionContext::Parameter);
+ // Check if it can be assigned
+ const auto *ParamObjectPtrType = ParamType->getAs<ObjCObjectPointerType>();
+ const auto *ArgObjectPtrType =
+ stripCastsAndSugar(Arg)->getType()->getAs<ObjCObjectPointerType>();
+ if (!ParamObjectPtrType || !ArgObjectPtrType)
+ continue;
+
+ // Check if we have more concrete tracked type that is not a super type of
+ // the static argument type.
+ SVal ArgSVal = M.getArgSVal(i);
+ SymbolRef ArgSym = ArgSVal.getAsSymbol();
+ if (ArgSym) {
+ const ObjCObjectPointerType *const *TrackedArgType =
+ State->get<MostSpecializedTypeArgsMap>(ArgSym);
+ if (TrackedArgType &&
+ ASTCtxt.canAssignObjCInterfaces(ArgObjectPtrType, *TrackedArgType)) {
+ ArgObjectPtrType = *TrackedArgType;
+ }
+ }
+
+ // Warn when argument is incompatible with the parameter.
+ if (!ASTCtxt.canAssignObjCInterfaces(ParamObjectPtrType,
+ ArgObjectPtrType)) {
+ static CheckerProgramPointTag Tag(this, "ArgTypeMismatch");
+ ExplodedNode *N = C.addTransition(State, &Tag);
+ reportGenericsBug(ArgObjectPtrType, ParamObjectPtrType, N, Sym, C, Arg);
+ return;
+ }
+ }
+}
+
+/// This callback is used to infer the types for Class variables. This info is
+/// used later to validate messages that sent to classes. Class variables are
+/// initialized with by invoking the 'class' method on a class.
+/// This method is also used to infer the type information for the return
+/// types.
+// TODO: right now it only tracks generic types. Extend this to track every
+// type in the DynamicTypeMap and diagnose type errors!
+void DynamicTypePropagation::checkPostObjCMessage(const ObjCMethodCall &M,
+ CheckerContext &C) const {
+ const ObjCMessageExpr *MessageExpr = M.getOriginExpr();
+
+ SymbolRef RetSym = M.getReturnValue().getAsSymbol();
+ if (!RetSym)
+ return;
+
+ Selector Sel = MessageExpr->getSelector();
+ ProgramStateRef State = C.getState();
+ // Inference for class variables.
+ // We are only interested in cases where the class method is invoked on a
+ // class. This method is provided by the runtime and available on all classes.
+ if (MessageExpr->getReceiverKind() == ObjCMessageExpr::Class &&
+ Sel.getAsString() == "class") {
+
+ QualType ReceiverType = MessageExpr->getClassReceiver();
+ const auto *ReceiverClassType = ReceiverType->getAs<ObjCObjectType>();
+ QualType ReceiverClassPointerType =
+ C.getASTContext().getObjCObjectPointerType(
+ QualType(ReceiverClassType, 0));
+
+ if (!ReceiverClassType->isSpecialized())
+ return;
+ const auto *InferredType =
+ ReceiverClassPointerType->getAs<ObjCObjectPointerType>();
+ assert(InferredType);
+
+ State = State->set<MostSpecializedTypeArgsMap>(RetSym, InferredType);
+ C.addTransition(State);
+ return;
+ }
+
+ // Tracking for return types.
+ SymbolRef RecSym = M.getReceiverSVal().getAsSymbol();
+ if (!RecSym)
+ return;
+
+ const ObjCObjectPointerType *const *TrackedType =
+ State->get<MostSpecializedTypeArgsMap>(RecSym);
+ if (!TrackedType)
+ return;
+
+ ASTContext &ASTCtxt = C.getASTContext();
+ const ObjCMethodDecl *Method =
+ findMethodDecl(MessageExpr, *TrackedType, ASTCtxt);
+ if (!Method)
+ return;
+
+ Optional<ArrayRef<QualType>> TypeArgs =
+ (*TrackedType)->getObjCSubstitutions(Method->getDeclContext());
+ if (!TypeArgs)
+ return;
+
+ QualType ResultType =
+ getReturnTypeForMethod(Method, *TypeArgs, *TrackedType, ASTCtxt);
+ // The static type is the same as the deduced type.
+ if (ResultType.isNull())
+ return;
+
+ const MemRegion *RetRegion = M.getReturnValue().getAsRegion();
+ ExplodedNode *Pred = C.getPredecessor();
+ // When there is an entry available for the return symbol in DynamicTypeMap,
+ // the call was inlined, and the information in the DynamicTypeMap is should
+ // be precise.
+ if (RetRegion && !State->get<DynamicTypeMap>(RetRegion)) {
+ // TODO: we have duplicated information in DynamicTypeMap and
+ // MostSpecializedTypeArgsMap. We should only store anything in the later if
+ // the stored data differs from the one stored in the former.
+ State = setDynamicTypeInfo(State, RetRegion, ResultType,
+ /*CanBeSubclass=*/true);
+ Pred = C.addTransition(State);
+ }
+
+ const auto *ResultPtrType = ResultType->getAs<ObjCObjectPointerType>();
+
+ if (!ResultPtrType || ResultPtrType->isUnspecialized())
+ return;
+
+ // When the result is a specialized type and it is not tracked yet, track it
+ // for the result symbol.
+ if (!State->get<MostSpecializedTypeArgsMap>(RetSym)) {
+ State = State->set<MostSpecializedTypeArgsMap>(RetSym, ResultPtrType);
+ C.addTransition(State, Pred);
+ }
+}
+
+void DynamicTypePropagation::reportGenericsBug(
+ const ObjCObjectPointerType *From, const ObjCObjectPointerType *To,
+ ExplodedNode *N, SymbolRef Sym, CheckerContext &C,
+ const Stmt *ReportedNode) const {
+ if (!CheckGenerics)
+ return;
+
+ initBugType();
+ SmallString<192> Buf;
+ llvm::raw_svector_ostream OS(Buf);
+ OS << "Conversion from value of type '";
+ QualType::print(From, Qualifiers(), OS, C.getLangOpts(), llvm::Twine());
+ OS << "' to incompatible type '";
+ QualType::print(To, Qualifiers(), OS, C.getLangOpts(), llvm::Twine());
+ OS << "'";
+ std::unique_ptr<BugReport> R(
+ new BugReport(*ObjCGenericsBugType, OS.str(), N));
+ R->markInteresting(Sym);
+ R->addVisitor(llvm::make_unique<GenericsBugVisitor>(Sym));
+ if (ReportedNode)
+ R->addRange(ReportedNode->getSourceRange());
+ C.emitReport(std::move(R));
+}
+
+PathDiagnosticPiece *DynamicTypePropagation::GenericsBugVisitor::VisitNode(
+ const ExplodedNode *N, const ExplodedNode *PrevN, BugReporterContext &BRC,
+ BugReport &BR) {
+ ProgramStateRef state = N->getState();
+ ProgramStateRef statePrev = PrevN->getState();
+
+ const ObjCObjectPointerType *const *TrackedType =
+ state->get<MostSpecializedTypeArgsMap>(Sym);
+ const ObjCObjectPointerType *const *TrackedTypePrev =
+ statePrev->get<MostSpecializedTypeArgsMap>(Sym);
+ if (!TrackedType)
+ return nullptr;
+
+ if (TrackedTypePrev && *TrackedTypePrev == *TrackedType)
+ return nullptr;
+
+ // Retrieve the associated statement.
+ const Stmt *S = nullptr;
+ ProgramPoint ProgLoc = N->getLocation();
+ if (Optional<StmtPoint> SP = ProgLoc.getAs<StmtPoint>()) {
+ S = SP->getStmt();
+ }
+
+ if (!S)
+ return nullptr;
+
+ const LangOptions &LangOpts = BRC.getASTContext().getLangOpts();
+
+ SmallString<256> Buf;
+ llvm::raw_svector_ostream OS(Buf);
+ OS << "Type '";
+ QualType::print(*TrackedType, Qualifiers(), OS, LangOpts, llvm::Twine());
+ OS << "' is inferred from ";
+
+ if (const auto *ExplicitCast = dyn_cast<ExplicitCastExpr>(S)) {
+ OS << "explicit cast (from '";
+ QualType::print(ExplicitCast->getSubExpr()->getType().getTypePtr(),
+ Qualifiers(), OS, LangOpts, llvm::Twine());
+ OS << "' to '";
+ QualType::print(ExplicitCast->getType().getTypePtr(), Qualifiers(), OS,
+ LangOpts, llvm::Twine());
+ OS << "')";
+ } else if (const auto *ImplicitCast = dyn_cast<ImplicitCastExpr>(S)) {
+ OS << "implicit cast (from '";
+ QualType::print(ImplicitCast->getSubExpr()->getType().getTypePtr(),
+ Qualifiers(), OS, LangOpts, llvm::Twine());
+ OS << "' to '";
+ QualType::print(ImplicitCast->getType().getTypePtr(), Qualifiers(), OS,
+ LangOpts, llvm::Twine());
+ OS << "')";
+ } else {
+ OS << "this context";
+ }
+
+ // Generate the extra diagnostic.
+ PathDiagnosticLocation Pos(S, BRC.getSourceManager(),
+ N->getLocationContext());
+ return new PathDiagnosticEventPiece(Pos, OS.str(), true, nullptr);
+}
+
+/// Register checkers.
+void ento::registerObjCGenericsChecker(CheckerManager &mgr) {
+ DynamicTypePropagation *checker =
+ mgr.registerChecker<DynamicTypePropagation>();
+ checker->CheckGenerics = true;
+}
+
void ento::registerDynamicTypePropagation(CheckerManager &mgr) {
mgr.registerChecker<DynamicTypePropagation>();
}
diff --git a/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp b/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp
index 7dc0a8745958..8f6c20ab1906 100644
--- a/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp
@@ -17,22 +17,26 @@ using namespace clang;
using namespace ento;
namespace {
-class ExprInspectionChecker : public Checker< eval::Call > {
+class ExprInspectionChecker : public Checker<eval::Call, check::DeadSymbols> {
mutable std::unique_ptr<BugType> BT;
void analyzerEval(const CallExpr *CE, CheckerContext &C) const;
void analyzerCheckInlined(const CallExpr *CE, CheckerContext &C) const;
void analyzerWarnIfReached(const CallExpr *CE, CheckerContext &C) const;
void analyzerCrash(const CallExpr *CE, CheckerContext &C) const;
+ void analyzerWarnOnDeadSymbol(const CallExpr *CE, CheckerContext &C) const;
typedef void (ExprInspectionChecker::*FnCheck)(const CallExpr *,
CheckerContext &C) const;
public:
bool evalCall(const CallExpr *CE, CheckerContext &C) const;
+ void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
};
}
+REGISTER_SET_WITH_PROGRAMSTATE(MarkedSymbols, const void *)
+
bool ExprInspectionChecker::evalCall(const CallExpr *CE,
CheckerContext &C) const {
// These checks should have no effect on the surrounding environment
@@ -42,7 +46,10 @@ bool ExprInspectionChecker::evalCall(const CallExpr *CE,
.Case("clang_analyzer_checkInlined",
&ExprInspectionChecker::analyzerCheckInlined)
.Case("clang_analyzer_crash", &ExprInspectionChecker::analyzerCrash)
- .Case("clang_analyzer_warnIfReached", &ExprInspectionChecker::analyzerWarnIfReached)
+ .Case("clang_analyzer_warnIfReached",
+ &ExprInspectionChecker::analyzerWarnIfReached)
+ .Case("clang_analyzer_warnOnDeadSymbol",
+ &ExprInspectionChecker::analyzerWarnOnDeadSymbol)
.Default(nullptr);
if (!Handler)
@@ -86,8 +93,7 @@ static const char *getArgumentValueString(const CallExpr *CE,
void ExprInspectionChecker::analyzerEval(const CallExpr *CE,
CheckerContext &C) const {
- ExplodedNode *N = C.getPredecessor();
- const LocationContext *LC = N->getLocationContext();
+ const LocationContext *LC = C.getPredecessor()->getLocationContext();
// A specific instantiation of an inlined function may have more constrained
// values than can generally be assumed. Skip the check.
@@ -97,24 +103,28 @@ void ExprInspectionChecker::analyzerEval(const CallExpr *CE,
if (!BT)
BT.reset(new BugType(this, "Checking analyzer assumptions", "debug"));
+ ExplodedNode *N = C.generateNonFatalErrorNode();
+ if (!N)
+ return;
C.emitReport(
llvm::make_unique<BugReport>(*BT, getArgumentValueString(CE, C), N));
}
void ExprInspectionChecker::analyzerWarnIfReached(const CallExpr *CE,
CheckerContext &C) const {
- ExplodedNode *N = C.getPredecessor();
if (!BT)
BT.reset(new BugType(this, "Checking analyzer assumptions", "debug"));
+ ExplodedNode *N = C.generateNonFatalErrorNode();
+ if (!N)
+ return;
C.emitReport(llvm::make_unique<BugReport>(*BT, "REACHABLE", N));
}
void ExprInspectionChecker::analyzerCheckInlined(const CallExpr *CE,
CheckerContext &C) const {
- ExplodedNode *N = C.getPredecessor();
- const LocationContext *LC = N->getLocationContext();
+ const LocationContext *LC = C.getPredecessor()->getLocationContext();
// An inlined function could conceivably also be analyzed as a top-level
// function. We ignore this case and only emit a message (TRUE or FALSE)
@@ -127,10 +137,48 @@ void ExprInspectionChecker::analyzerCheckInlined(const CallExpr *CE,
if (!BT)
BT.reset(new BugType(this, "Checking analyzer assumptions", "debug"));
+ ExplodedNode *N = C.generateNonFatalErrorNode();
+ if (!N)
+ return;
C.emitReport(
llvm::make_unique<BugReport>(*BT, getArgumentValueString(CE, C), N));
}
+void ExprInspectionChecker::analyzerWarnOnDeadSymbol(const CallExpr *CE,
+ CheckerContext &C) const {
+ if (CE->getNumArgs() == 0)
+ return;
+ SVal Val = C.getSVal(CE->getArg(0));
+ SymbolRef Sym = Val.getAsSymbol();
+ if (!Sym)
+ return;
+
+ ProgramStateRef State = C.getState();
+ State = State->add<MarkedSymbols>(Sym);
+ C.addTransition(State);
+}
+
+void ExprInspectionChecker::checkDeadSymbols(SymbolReaper &SymReaper,
+ CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+ const MarkedSymbolsTy &Syms = State->get<MarkedSymbols>();
+ for (auto I = Syms.begin(), E = Syms.end(); I != E; ++I) {
+ SymbolRef Sym = static_cast<SymbolRef>(*I);
+ if (!SymReaper.isDead(Sym))
+ continue;
+
+ if (!BT)
+ BT.reset(new BugType(this, "Checking analyzer assumptions", "debug"));
+
+ ExplodedNode *N = C.generateNonFatalErrorNode();
+ if (!N)
+ return;
+
+ C.emitReport(llvm::make_unique<BugReport>(*BT, "SYMBOL DEAD", N));
+ C.addTransition(State->remove<MarkedSymbols>(Sym), N);
+ }
+}
+
void ExprInspectionChecker::analyzerCrash(const CallExpr *CE,
CheckerContext &C) const {
LLVM_BUILTIN_TRAP;
diff --git a/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp b/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp
index 48d6bd4b37e6..3fe89f96a43b 100644
--- a/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp
@@ -23,7 +23,7 @@ using namespace clang;
using namespace ento;
namespace {
-class FixedAddressChecker
+class FixedAddressChecker
: public Checker< check::PreStmt<BinaryOperator> > {
mutable std::unique_ptr<BuiltinBug> BT;
@@ -50,7 +50,7 @@ void FixedAddressChecker::checkPreStmt(const BinaryOperator *B,
if (!RV.isConstant() || RV.isZeroConstant())
return;
- if (ExplodedNode *N = C.addTransition()) {
+ if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
if (!BT)
BT.reset(
new BuiltinBug(this, "Use fixed address",
diff --git a/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp b/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp
index 2cf508ff086c..8c8acc637f1f 100644
--- a/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp
@@ -100,8 +100,8 @@ private:
/// Generate a report if the expression is tainted or points to tainted data.
bool generateReportIfTainted(const Expr *E, const char Msg[],
CheckerContext &C) const;
-
-
+
+
typedef SmallVector<unsigned, 2> ArgVector;
/// \brief A struct used to specify taint propagation rules for a function.
@@ -441,7 +441,7 @@ SymbolRef GenericTaintChecker::getPointedToSymbol(CheckerContext &C,
return Val.getAsSymbol();
}
-ProgramStateRef
+ProgramStateRef
GenericTaintChecker::TaintPropagationRule::process(const CallExpr *CE,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
@@ -640,7 +640,7 @@ bool GenericTaintChecker::generateReportIfTainted(const Expr *E,
return false;
// Generate diagnostic.
- if (ExplodedNode *N = C.addTransition()) {
+ if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
initBugType();
auto report = llvm::make_unique<BugReport>(*BT, Msg, N);
report->addRange(E->getSourceRange());
@@ -658,17 +658,15 @@ bool GenericTaintChecker::checkUncontrolledFormatString(const CallExpr *CE,
return false;
// If either the format string content or the pointer itself are tainted, warn.
- if (generateReportIfTainted(CE->getArg(ArgNum),
- MsgUncontrolledFormatString, C))
- return true;
- return false;
+ return generateReportIfTainted(CE->getArg(ArgNum),
+ MsgUncontrolledFormatString, C);
}
bool GenericTaintChecker::checkSystemCall(const CallExpr *CE,
StringRef Name,
CheckerContext &C) const {
- // TODO: It might make sense to run this check on demand. In some cases,
- // we should check if the environment has been cleansed here. We also might
+ // TODO: It might make sense to run this check on demand. In some cases,
+ // we should check if the environment has been cleansed here. We also might
// need to know if the user was reset before these calls(seteuid).
unsigned ArgNum = llvm::StringSwitch<unsigned>(Name)
.Case("system", 0)
@@ -686,11 +684,7 @@ bool GenericTaintChecker::checkSystemCall(const CallExpr *CE,
if (ArgNum == UINT_MAX || CE->getNumArgs() < (ArgNum + 1))
return false;
- if (generateReportIfTainted(CE->getArg(ArgNum),
- MsgSanitizeSystemArgs, C))
- return true;
-
- return false;
+ return generateReportIfTainted(CE->getArg(ArgNum), MsgSanitizeSystemArgs, C);
}
// TODO: Should this check be a part of the CString checker?
@@ -728,11 +722,8 @@ bool GenericTaintChecker::checkTaintedBufferSize(const CallExpr *CE,
ArgNum = 2;
}
- if (ArgNum != InvalidArgIndex && CE->getNumArgs() > ArgNum &&
- generateReportIfTainted(CE->getArg(ArgNum), MsgTaintedBufferSize, C))
- return true;
-
- return false;
+ return ArgNum != InvalidArgIndex && CE->getNumArgs() > ArgNum &&
+ generateReportIfTainted(CE->getArg(ArgNum), MsgTaintedBufferSize, C);
}
void ento::registerGenericTaintChecker(CheckerManager &mgr) {
diff --git a/lib/StaticAnalyzer/Checkers/IdenticalExprChecker.cpp b/lib/StaticAnalyzer/Checkers/IdenticalExprChecker.cpp
index 58d0783f3974..0c3bff5b63b8 100644
--- a/lib/StaticAnalyzer/Checkers/IdenticalExprChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/IdenticalExprChecker.cpp
@@ -96,7 +96,7 @@ void FindIdenticalExprVisitor::checkBitwiseOrLogicalOp(const BinaryOperator *B,
}
LHS = B2->getLHS();
}
-
+
if (isIdenticalStmt(AC->getASTContext(), RHS, LHS)) {
Sr[0] = RHS->getSourceRange();
Sr[1] = LHS->getSourceRange();
@@ -108,6 +108,24 @@ bool FindIdenticalExprVisitor::VisitIfStmt(const IfStmt *I) {
const Stmt *Stmt1 = I->getThen();
const Stmt *Stmt2 = I->getElse();
+ // Check for identical inner condition:
+ //
+ // if (x<10) {
+ // if (x<10) {
+ // ..
+ if (const CompoundStmt *CS = dyn_cast<CompoundStmt>(Stmt1)) {
+ if (!CS->body_empty()) {
+ const IfStmt *InnerIf = dyn_cast<IfStmt>(*CS->body_begin());
+ if (InnerIf && isIdenticalStmt(AC->getASTContext(), I->getCond(), InnerIf->getCond(), /*ignoreSideEffects=*/ false)) {
+ PathDiagnosticLocation ELoc(InnerIf->getCond(), BR.getSourceManager(), AC);
+ BR.EmitBasicReport(AC->getDecl(), Checker, "Identical conditions",
+ categories::LogicError,
+ "conditions of the inner and outer statements are identical",
+ ELoc);
+ }
+ }
+ }
+
// Check for identical conditions:
//
// if (b) {
@@ -287,9 +305,7 @@ static bool isIdenticalStmt(const ASTContext &Ctx, const Stmt *Stmt1,
const Stmt *Stmt2, bool IgnoreSideEffects) {
if (!Stmt1 || !Stmt2) {
- if (!Stmt1 && !Stmt2)
- return true;
- return false;
+ return !Stmt1 && !Stmt2;
}
// If Stmt1 & Stmt2 are of different class then they are not
@@ -332,6 +348,7 @@ static bool isIdenticalStmt(const ASTContext &Ctx, const Stmt *Stmt1,
return false;
case Stmt::CallExprClass:
case Stmt::ArraySubscriptExprClass:
+ case Stmt::OMPArraySectionExprClass:
case Stmt::ImplicitCastExprClass:
case Stmt::ParenExprClass:
case Stmt::BreakStmtClass:
diff --git a/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp b/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp
index 3df5fa034a43..dffff38c91a2 100644
--- a/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp
@@ -20,8 +20,8 @@
// been called on them. An invalidation method should either invalidate all
// the ivars or call another invalidation method (on self).
//
-// Partial invalidor annotation allows to addess cases when ivars are
-// invalidated by other methods, which might or might not be called from
+// Partial invalidor annotation allows to addess cases when ivars are
+// invalidated by other methods, which might or might not be called from
// the invalidation method. The checker checks that each invalidation
// method and all the partial methods cumulatively invalidate all ivars.
// __attribute__((annotate("objc_instance_variable_invalidator_partial")));
@@ -310,7 +310,7 @@ const ObjCIvarDecl *IvarInvalidationCheckerImpl::findPropertyBackingIvar(
// Lookup for the synthesized case.
IvarD = Prop->getPropertyIvarDecl();
- // We only track the ivars/properties that are defined in the current
+ // We only track the ivars/properties that are defined in the current
// class (not the parent).
if (IvarD && IvarD->getContainingInterface() == InterfaceD) {
if (TrackedIvars.count(IvarD)) {
diff --git a/lib/StaticAnalyzer/Checkers/LLVMConventionsChecker.cpp b/lib/StaticAnalyzer/Checkers/LLVMConventionsChecker.cpp
index 4e3f9b73acb2..db4fbca36deb 100644
--- a/lib/StaticAnalyzer/Checkers/LLVMConventionsChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/LLVMConventionsChecker.cpp
@@ -276,7 +276,6 @@ void ASTFieldVisitor::ReportError(QualType T) {
}
}
os << " (type " << FieldChain.back()->getType().getAsString() << ")";
- os.flush();
// Note that this will fire for every translation unit that uses this
// class. This is suboptimal, but at least scan-build will merge
diff --git a/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp b/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp
new file mode 100644
index 000000000000..56346cd4f706
--- /dev/null
+++ b/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp
@@ -0,0 +1,1201 @@
+//=- LocalizationChecker.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 set of checks for localizability including:
+// 1) A checker that warns about uses of non-localized NSStrings passed to
+// UI methods expecting localized strings
+// 2) A syntactic checker that warns against the bad practice of
+// not including a comment in NSLocalizedString macros.
+//
+//===----------------------------------------------------------------------===//
+
+#include "ClangSACheckers.h"
+#include "clang/AST/Attr.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclObjC.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/CheckerManager.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h"
+#include "clang/Lex/Lexer.h"
+#include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/AST/StmtVisitor.h"
+#include "llvm/Support/Unicode.h"
+#include "llvm/ADT/StringSet.h"
+
+using namespace clang;
+using namespace ento;
+
+namespace {
+struct LocalizedState {
+private:
+ enum Kind { NonLocalized, Localized } K;
+ LocalizedState(Kind InK) : K(InK) {}
+
+public:
+ bool isLocalized() const { return K == Localized; }
+ bool isNonLocalized() const { return K == NonLocalized; }
+
+ static LocalizedState getLocalized() { return LocalizedState(Localized); }
+ static LocalizedState getNonLocalized() {
+ return LocalizedState(NonLocalized);
+ }
+
+ // Overload the == operator
+ bool operator==(const LocalizedState &X) const { return K == X.K; }
+
+ // LLVMs equivalent of a hash function
+ void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(K); }
+};
+
+class NonLocalizedStringChecker
+ : public Checker<check::PostCall, check::PreObjCMessage,
+ check::PostObjCMessage,
+ check::PostStmt<ObjCStringLiteral>> {
+
+ mutable std::unique_ptr<BugType> BT;
+
+ // Methods that require a localized string
+ mutable llvm::DenseMap<const IdentifierInfo *,
+ llvm::DenseMap<Selector, uint8_t>> UIMethods;
+ // Methods that return a localized string
+ mutable llvm::SmallSet<std::pair<const IdentifierInfo *, Selector>, 12> LSM;
+ // C Functions that return a localized string
+ mutable llvm::SmallSet<const IdentifierInfo *, 5> LSF;
+
+ void initUIMethods(ASTContext &Ctx) const;
+ void initLocStringsMethods(ASTContext &Ctx) const;
+
+ bool hasNonLocalizedState(SVal S, CheckerContext &C) const;
+ bool hasLocalizedState(SVal S, CheckerContext &C) const;
+ void setNonLocalizedState(SVal S, CheckerContext &C) const;
+ void setLocalizedState(SVal S, CheckerContext &C) const;
+
+ bool isAnnotatedAsLocalized(const Decl *D) const;
+ void reportLocalizationError(SVal S, const ObjCMethodCall &M,
+ CheckerContext &C, int argumentNumber = 0) const;
+
+ int getLocalizedArgumentForSelector(const IdentifierInfo *Receiver,
+ Selector S) const;
+
+public:
+ NonLocalizedStringChecker();
+
+ // When this parameter is set to true, the checker assumes all
+ // methods that return NSStrings are unlocalized. Thus, more false
+ // positives will be reported.
+ DefaultBool IsAggressive;
+
+ void checkPreObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const;
+ void checkPostObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const;
+ void checkPostStmt(const ObjCStringLiteral *SL, CheckerContext &C) const;
+ void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
+};
+
+} // end anonymous namespace
+
+REGISTER_MAP_WITH_PROGRAMSTATE(LocalizedMemMap, const MemRegion *,
+ LocalizedState)
+
+NonLocalizedStringChecker::NonLocalizedStringChecker() {
+ BT.reset(new BugType(this, "Unlocalizable string",
+ "Localizability Issue (Apple)"));
+}
+
+#define NEW_RECEIVER(receiver) \
+ llvm::DenseMap<Selector, uint8_t> &receiver##M = \
+ UIMethods.insert({&Ctx.Idents.get(#receiver), \
+ llvm::DenseMap<Selector, uint8_t>()}) \
+ .first->second;
+#define ADD_NULLARY_METHOD(receiver, method, argument) \
+ receiver##M.insert( \
+ {Ctx.Selectors.getNullarySelector(&Ctx.Idents.get(#method)), argument});
+#define ADD_UNARY_METHOD(receiver, method, argument) \
+ receiver##M.insert( \
+ {Ctx.Selectors.getUnarySelector(&Ctx.Idents.get(#method)), argument});
+#define ADD_METHOD(receiver, method_list, count, argument) \
+ receiver##M.insert({Ctx.Selectors.getSelector(count, method_list), argument});
+
+/// Initializes a list of methods that require a localized string
+/// Format: {"ClassName", {{"selectorName:", LocStringArg#}, ...}, ...}
+void NonLocalizedStringChecker::initUIMethods(ASTContext &Ctx) const {
+ if (!UIMethods.empty())
+ return;
+
+ // UI Methods
+ NEW_RECEIVER(UISearchDisplayController)
+ ADD_UNARY_METHOD(UISearchDisplayController, setSearchResultsTitle, 0)
+
+ NEW_RECEIVER(UITabBarItem)
+ IdentifierInfo *initWithTitleUITabBarItemTag[] = {
+ &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("image"),
+ &Ctx.Idents.get("tag")};
+ ADD_METHOD(UITabBarItem, initWithTitleUITabBarItemTag, 3, 0)
+ IdentifierInfo *initWithTitleUITabBarItemImage[] = {
+ &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("image"),
+ &Ctx.Idents.get("selectedImage")};
+ ADD_METHOD(UITabBarItem, initWithTitleUITabBarItemImage, 3, 0)
+
+ NEW_RECEIVER(NSDockTile)
+ ADD_UNARY_METHOD(NSDockTile, setBadgeLabel, 0)
+
+ NEW_RECEIVER(NSStatusItem)
+ ADD_UNARY_METHOD(NSStatusItem, setTitle, 0)
+ ADD_UNARY_METHOD(NSStatusItem, setToolTip, 0)
+
+ NEW_RECEIVER(UITableViewRowAction)
+ IdentifierInfo *rowActionWithStyleUITableViewRowAction[] = {
+ &Ctx.Idents.get("rowActionWithStyle"), &Ctx.Idents.get("title"),
+ &Ctx.Idents.get("handler")};
+ ADD_METHOD(UITableViewRowAction, rowActionWithStyleUITableViewRowAction, 3, 1)
+ ADD_UNARY_METHOD(UITableViewRowAction, setTitle, 0)
+
+ NEW_RECEIVER(NSBox)
+ ADD_UNARY_METHOD(NSBox, setTitle, 0)
+
+ NEW_RECEIVER(NSButton)
+ ADD_UNARY_METHOD(NSButton, setTitle, 0)
+ ADD_UNARY_METHOD(NSButton, setAlternateTitle, 0)
+
+ NEW_RECEIVER(NSSavePanel)
+ ADD_UNARY_METHOD(NSSavePanel, setPrompt, 0)
+ ADD_UNARY_METHOD(NSSavePanel, setTitle, 0)
+ ADD_UNARY_METHOD(NSSavePanel, setNameFieldLabel, 0)
+ ADD_UNARY_METHOD(NSSavePanel, setNameFieldStringValue, 0)
+ ADD_UNARY_METHOD(NSSavePanel, setMessage, 0)
+
+ NEW_RECEIVER(UIPrintInfo)
+ ADD_UNARY_METHOD(UIPrintInfo, setJobName, 0)
+
+ NEW_RECEIVER(NSTabViewItem)
+ ADD_UNARY_METHOD(NSTabViewItem, setLabel, 0)
+ ADD_UNARY_METHOD(NSTabViewItem, setToolTip, 0)
+
+ NEW_RECEIVER(NSBrowser)
+ IdentifierInfo *setTitleNSBrowser[] = {&Ctx.Idents.get("setTitle"),
+ &Ctx.Idents.get("ofColumn")};
+ ADD_METHOD(NSBrowser, setTitleNSBrowser, 2, 0)
+
+ NEW_RECEIVER(UIAccessibilityElement)
+ ADD_UNARY_METHOD(UIAccessibilityElement, setAccessibilityLabel, 0)
+ ADD_UNARY_METHOD(UIAccessibilityElement, setAccessibilityHint, 0)
+ ADD_UNARY_METHOD(UIAccessibilityElement, setAccessibilityValue, 0)
+
+ NEW_RECEIVER(UIAlertAction)
+ IdentifierInfo *actionWithTitleUIAlertAction[] = {
+ &Ctx.Idents.get("actionWithTitle"), &Ctx.Idents.get("style"),
+ &Ctx.Idents.get("handler")};
+ ADD_METHOD(UIAlertAction, actionWithTitleUIAlertAction, 3, 0)
+
+ NEW_RECEIVER(NSPopUpButton)
+ ADD_UNARY_METHOD(NSPopUpButton, addItemWithTitle, 0)
+ IdentifierInfo *insertItemWithTitleNSPopUpButton[] = {
+ &Ctx.Idents.get("insertItemWithTitle"), &Ctx.Idents.get("atIndex")};
+ ADD_METHOD(NSPopUpButton, insertItemWithTitleNSPopUpButton, 2, 0)
+ ADD_UNARY_METHOD(NSPopUpButton, removeItemWithTitle, 0)
+ ADD_UNARY_METHOD(NSPopUpButton, selectItemWithTitle, 0)
+ ADD_UNARY_METHOD(NSPopUpButton, setTitle, 0)
+
+ NEW_RECEIVER(NSTableViewRowAction)
+ IdentifierInfo *rowActionWithStyleNSTableViewRowAction[] = {
+ &Ctx.Idents.get("rowActionWithStyle"), &Ctx.Idents.get("title"),
+ &Ctx.Idents.get("handler")};
+ ADD_METHOD(NSTableViewRowAction, rowActionWithStyleNSTableViewRowAction, 3, 1)
+ ADD_UNARY_METHOD(NSTableViewRowAction, setTitle, 0)
+
+ NEW_RECEIVER(NSImage)
+ ADD_UNARY_METHOD(NSImage, setAccessibilityDescription, 0)
+
+ NEW_RECEIVER(NSUserActivity)
+ ADD_UNARY_METHOD(NSUserActivity, setTitle, 0)
+
+ NEW_RECEIVER(NSPathControlItem)
+ ADD_UNARY_METHOD(NSPathControlItem, setTitle, 0)
+
+ NEW_RECEIVER(NSCell)
+ ADD_UNARY_METHOD(NSCell, initTextCell, 0)
+ ADD_UNARY_METHOD(NSCell, setTitle, 0)
+ ADD_UNARY_METHOD(NSCell, setStringValue, 0)
+
+ NEW_RECEIVER(NSPathControl)
+ ADD_UNARY_METHOD(NSPathControl, setPlaceholderString, 0)
+
+ NEW_RECEIVER(UIAccessibility)
+ ADD_UNARY_METHOD(UIAccessibility, setAccessibilityLabel, 0)
+ ADD_UNARY_METHOD(UIAccessibility, setAccessibilityHint, 0)
+ ADD_UNARY_METHOD(UIAccessibility, setAccessibilityValue, 0)
+
+ NEW_RECEIVER(NSTableColumn)
+ ADD_UNARY_METHOD(NSTableColumn, setTitle, 0)
+ ADD_UNARY_METHOD(NSTableColumn, setHeaderToolTip, 0)
+
+ NEW_RECEIVER(NSSegmentedControl)
+ IdentifierInfo *setLabelNSSegmentedControl[] = {
+ &Ctx.Idents.get("setLabel"), &Ctx.Idents.get("forSegment")};
+ ADD_METHOD(NSSegmentedControl, setLabelNSSegmentedControl, 2, 0)
+
+ NEW_RECEIVER(NSButtonCell)
+ ADD_UNARY_METHOD(NSButtonCell, setTitle, 0)
+ ADD_UNARY_METHOD(NSButtonCell, setAlternateTitle, 0)
+
+ NEW_RECEIVER(NSSliderCell)
+ ADD_UNARY_METHOD(NSSliderCell, setTitle, 0)
+
+ NEW_RECEIVER(NSControl)
+ ADD_UNARY_METHOD(NSControl, setStringValue, 0)
+
+ NEW_RECEIVER(NSAccessibility)
+ ADD_UNARY_METHOD(NSAccessibility, setAccessibilityValueDescription, 0)
+ ADD_UNARY_METHOD(NSAccessibility, setAccessibilityLabel, 0)
+ ADD_UNARY_METHOD(NSAccessibility, setAccessibilityTitle, 0)
+ ADD_UNARY_METHOD(NSAccessibility, setAccessibilityPlaceholderValue, 0)
+ ADD_UNARY_METHOD(NSAccessibility, setAccessibilityHelp, 0)
+
+ NEW_RECEIVER(NSMatrix)
+ IdentifierInfo *setToolTipNSMatrix[] = {&Ctx.Idents.get("setToolTip"),
+ &Ctx.Idents.get("forCell")};
+ ADD_METHOD(NSMatrix, setToolTipNSMatrix, 2, 0)
+
+ NEW_RECEIVER(NSPrintPanel)
+ ADD_UNARY_METHOD(NSPrintPanel, setDefaultButtonTitle, 0)
+
+ NEW_RECEIVER(UILocalNotification)
+ ADD_UNARY_METHOD(UILocalNotification, setAlertBody, 0)
+ ADD_UNARY_METHOD(UILocalNotification, setAlertAction, 0)
+ ADD_UNARY_METHOD(UILocalNotification, setAlertTitle, 0)
+
+ NEW_RECEIVER(NSSlider)
+ ADD_UNARY_METHOD(NSSlider, setTitle, 0)
+
+ NEW_RECEIVER(UIMenuItem)
+ IdentifierInfo *initWithTitleUIMenuItem[] = {&Ctx.Idents.get("initWithTitle"),
+ &Ctx.Idents.get("action")};
+ ADD_METHOD(UIMenuItem, initWithTitleUIMenuItem, 2, 0)
+ ADD_UNARY_METHOD(UIMenuItem, setTitle, 0)
+
+ NEW_RECEIVER(UIAlertController)
+ IdentifierInfo *alertControllerWithTitleUIAlertController[] = {
+ &Ctx.Idents.get("alertControllerWithTitle"), &Ctx.Idents.get("message"),
+ &Ctx.Idents.get("preferredStyle")};
+ ADD_METHOD(UIAlertController, alertControllerWithTitleUIAlertController, 3, 1)
+ ADD_UNARY_METHOD(UIAlertController, setTitle, 0)
+ ADD_UNARY_METHOD(UIAlertController, setMessage, 0)
+
+ NEW_RECEIVER(UIApplicationShortcutItem)
+ IdentifierInfo *initWithTypeUIApplicationShortcutItemIcon[] = {
+ &Ctx.Idents.get("initWithType"), &Ctx.Idents.get("localizedTitle"),
+ &Ctx.Idents.get("localizedSubtitle"), &Ctx.Idents.get("icon"),
+ &Ctx.Idents.get("userInfo")};
+ ADD_METHOD(UIApplicationShortcutItem,
+ initWithTypeUIApplicationShortcutItemIcon, 5, 1)
+ IdentifierInfo *initWithTypeUIApplicationShortcutItem[] = {
+ &Ctx.Idents.get("initWithType"), &Ctx.Idents.get("localizedTitle")};
+ ADD_METHOD(UIApplicationShortcutItem, initWithTypeUIApplicationShortcutItem,
+ 2, 1)
+
+ NEW_RECEIVER(UIActionSheet)
+ IdentifierInfo *initWithTitleUIActionSheet[] = {
+ &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("delegate"),
+ &Ctx.Idents.get("cancelButtonTitle"),
+ &Ctx.Idents.get("destructiveButtonTitle"),
+ &Ctx.Idents.get("otherButtonTitles")};
+ ADD_METHOD(UIActionSheet, initWithTitleUIActionSheet, 5, 0)
+ ADD_UNARY_METHOD(UIActionSheet, addButtonWithTitle, 0)
+ ADD_UNARY_METHOD(UIActionSheet, setTitle, 0)
+
+ NEW_RECEIVER(NSURLSessionTask)
+ ADD_UNARY_METHOD(NSURLSessionTask, setTaskDescription, 0)
+
+ NEW_RECEIVER(UIAccessibilityCustomAction)
+ IdentifierInfo *initWithNameUIAccessibilityCustomAction[] = {
+ &Ctx.Idents.get("initWithName"), &Ctx.Idents.get("target"),
+ &Ctx.Idents.get("selector")};
+ ADD_METHOD(UIAccessibilityCustomAction,
+ initWithNameUIAccessibilityCustomAction, 3, 0)
+ ADD_UNARY_METHOD(UIAccessibilityCustomAction, setName, 0)
+
+ NEW_RECEIVER(UISearchBar)
+ ADD_UNARY_METHOD(UISearchBar, setText, 0)
+ ADD_UNARY_METHOD(UISearchBar, setPrompt, 0)
+ ADD_UNARY_METHOD(UISearchBar, setPlaceholder, 0)
+
+ NEW_RECEIVER(UIBarItem)
+ ADD_UNARY_METHOD(UIBarItem, setTitle, 0)
+
+ NEW_RECEIVER(UITextView)
+ ADD_UNARY_METHOD(UITextView, setText, 0)
+
+ NEW_RECEIVER(NSView)
+ ADD_UNARY_METHOD(NSView, setToolTip, 0)
+
+ NEW_RECEIVER(NSTextField)
+ ADD_UNARY_METHOD(NSTextField, setPlaceholderString, 0)
+
+ NEW_RECEIVER(NSAttributedString)
+ ADD_UNARY_METHOD(NSAttributedString, initWithString, 0)
+ IdentifierInfo *initWithStringNSAttributedString[] = {
+ &Ctx.Idents.get("initWithString"), &Ctx.Idents.get("attributes")};
+ ADD_METHOD(NSAttributedString, initWithStringNSAttributedString, 2, 0)
+
+ NEW_RECEIVER(NSText)
+ ADD_UNARY_METHOD(NSText, setString, 0)
+
+ NEW_RECEIVER(UIKeyCommand)
+ IdentifierInfo *keyCommandWithInputUIKeyCommand[] = {
+ &Ctx.Idents.get("keyCommandWithInput"), &Ctx.Idents.get("modifierFlags"),
+ &Ctx.Idents.get("action"), &Ctx.Idents.get("discoverabilityTitle")};
+ ADD_METHOD(UIKeyCommand, keyCommandWithInputUIKeyCommand, 4, 3)
+ ADD_UNARY_METHOD(UIKeyCommand, setDiscoverabilityTitle, 0)
+
+ NEW_RECEIVER(UILabel)
+ ADD_UNARY_METHOD(UILabel, setText, 0)
+
+ NEW_RECEIVER(NSAlert)
+ IdentifierInfo *alertWithMessageTextNSAlert[] = {
+ &Ctx.Idents.get("alertWithMessageText"), &Ctx.Idents.get("defaultButton"),
+ &Ctx.Idents.get("alternateButton"), &Ctx.Idents.get("otherButton"),
+ &Ctx.Idents.get("informativeTextWithFormat")};
+ ADD_METHOD(NSAlert, alertWithMessageTextNSAlert, 5, 0)
+ ADD_UNARY_METHOD(NSAlert, addButtonWithTitle, 0)
+ ADD_UNARY_METHOD(NSAlert, setMessageText, 0)
+ ADD_UNARY_METHOD(NSAlert, setInformativeText, 0)
+ ADD_UNARY_METHOD(NSAlert, setHelpAnchor, 0)
+
+ NEW_RECEIVER(UIMutableApplicationShortcutItem)
+ ADD_UNARY_METHOD(UIMutableApplicationShortcutItem, setLocalizedTitle, 0)
+ ADD_UNARY_METHOD(UIMutableApplicationShortcutItem, setLocalizedSubtitle, 0)
+
+ NEW_RECEIVER(UIButton)
+ IdentifierInfo *setTitleUIButton[] = {&Ctx.Idents.get("setTitle"),
+ &Ctx.Idents.get("forState")};
+ ADD_METHOD(UIButton, setTitleUIButton, 2, 0)
+
+ NEW_RECEIVER(NSWindow)
+ ADD_UNARY_METHOD(NSWindow, setTitle, 0)
+ IdentifierInfo *minFrameWidthWithTitleNSWindow[] = {
+ &Ctx.Idents.get("minFrameWidthWithTitle"), &Ctx.Idents.get("styleMask")};
+ ADD_METHOD(NSWindow, minFrameWidthWithTitleNSWindow, 2, 0)
+ ADD_UNARY_METHOD(NSWindow, setMiniwindowTitle, 0)
+
+ NEW_RECEIVER(NSPathCell)
+ ADD_UNARY_METHOD(NSPathCell, setPlaceholderString, 0)
+
+ NEW_RECEIVER(UIDocumentMenuViewController)
+ IdentifierInfo *addOptionWithTitleUIDocumentMenuViewController[] = {
+ &Ctx.Idents.get("addOptionWithTitle"), &Ctx.Idents.get("image"),
+ &Ctx.Idents.get("order"), &Ctx.Idents.get("handler")};
+ ADD_METHOD(UIDocumentMenuViewController,
+ addOptionWithTitleUIDocumentMenuViewController, 4, 0)
+
+ NEW_RECEIVER(UINavigationItem)
+ ADD_UNARY_METHOD(UINavigationItem, initWithTitle, 0)
+ ADD_UNARY_METHOD(UINavigationItem, setTitle, 0)
+ ADD_UNARY_METHOD(UINavigationItem, setPrompt, 0)
+
+ NEW_RECEIVER(UIAlertView)
+ IdentifierInfo *initWithTitleUIAlertView[] = {
+ &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("message"),
+ &Ctx.Idents.get("delegate"), &Ctx.Idents.get("cancelButtonTitle"),
+ &Ctx.Idents.get("otherButtonTitles")};
+ ADD_METHOD(UIAlertView, initWithTitleUIAlertView, 5, 0)
+ ADD_UNARY_METHOD(UIAlertView, addButtonWithTitle, 0)
+ ADD_UNARY_METHOD(UIAlertView, setTitle, 0)
+ ADD_UNARY_METHOD(UIAlertView, setMessage, 0)
+
+ NEW_RECEIVER(NSFormCell)
+ ADD_UNARY_METHOD(NSFormCell, initTextCell, 0)
+ ADD_UNARY_METHOD(NSFormCell, setTitle, 0)
+ ADD_UNARY_METHOD(NSFormCell, setPlaceholderString, 0)
+
+ NEW_RECEIVER(NSUserNotification)
+ ADD_UNARY_METHOD(NSUserNotification, setTitle, 0)
+ ADD_UNARY_METHOD(NSUserNotification, setSubtitle, 0)
+ ADD_UNARY_METHOD(NSUserNotification, setInformativeText, 0)
+ ADD_UNARY_METHOD(NSUserNotification, setActionButtonTitle, 0)
+ ADD_UNARY_METHOD(NSUserNotification, setOtherButtonTitle, 0)
+ ADD_UNARY_METHOD(NSUserNotification, setResponsePlaceholder, 0)
+
+ NEW_RECEIVER(NSToolbarItem)
+ ADD_UNARY_METHOD(NSToolbarItem, setLabel, 0)
+ ADD_UNARY_METHOD(NSToolbarItem, setPaletteLabel, 0)
+ ADD_UNARY_METHOD(NSToolbarItem, setToolTip, 0)
+
+ NEW_RECEIVER(NSProgress)
+ ADD_UNARY_METHOD(NSProgress, setLocalizedDescription, 0)
+ ADD_UNARY_METHOD(NSProgress, setLocalizedAdditionalDescription, 0)
+
+ NEW_RECEIVER(NSSegmentedCell)
+ IdentifierInfo *setLabelNSSegmentedCell[] = {&Ctx.Idents.get("setLabel"),
+ &Ctx.Idents.get("forSegment")};
+ ADD_METHOD(NSSegmentedCell, setLabelNSSegmentedCell, 2, 0)
+ IdentifierInfo *setToolTipNSSegmentedCell[] = {&Ctx.Idents.get("setToolTip"),
+ &Ctx.Idents.get("forSegment")};
+ ADD_METHOD(NSSegmentedCell, setToolTipNSSegmentedCell, 2, 0)
+
+ NEW_RECEIVER(NSUndoManager)
+ ADD_UNARY_METHOD(NSUndoManager, setActionName, 0)
+ ADD_UNARY_METHOD(NSUndoManager, undoMenuTitleForUndoActionName, 0)
+ ADD_UNARY_METHOD(NSUndoManager, redoMenuTitleForUndoActionName, 0)
+
+ NEW_RECEIVER(NSMenuItem)
+ IdentifierInfo *initWithTitleNSMenuItem[] = {
+ &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("action"),
+ &Ctx.Idents.get("keyEquivalent")};
+ ADD_METHOD(NSMenuItem, initWithTitleNSMenuItem, 3, 0)
+ ADD_UNARY_METHOD(NSMenuItem, setTitle, 0)
+ ADD_UNARY_METHOD(NSMenuItem, setToolTip, 0)
+
+ NEW_RECEIVER(NSPopUpButtonCell)
+ IdentifierInfo *initTextCellNSPopUpButtonCell[] = {
+ &Ctx.Idents.get("initTextCell"), &Ctx.Idents.get("pullsDown")};
+ ADD_METHOD(NSPopUpButtonCell, initTextCellNSPopUpButtonCell, 2, 0)
+ ADD_UNARY_METHOD(NSPopUpButtonCell, addItemWithTitle, 0)
+ IdentifierInfo *insertItemWithTitleNSPopUpButtonCell[] = {
+ &Ctx.Idents.get("insertItemWithTitle"), &Ctx.Idents.get("atIndex")};
+ ADD_METHOD(NSPopUpButtonCell, insertItemWithTitleNSPopUpButtonCell, 2, 0)
+ ADD_UNARY_METHOD(NSPopUpButtonCell, removeItemWithTitle, 0)
+ ADD_UNARY_METHOD(NSPopUpButtonCell, selectItemWithTitle, 0)
+ ADD_UNARY_METHOD(NSPopUpButtonCell, setTitle, 0)
+
+ NEW_RECEIVER(NSViewController)
+ ADD_UNARY_METHOD(NSViewController, setTitle, 0)
+
+ NEW_RECEIVER(NSMenu)
+ ADD_UNARY_METHOD(NSMenu, initWithTitle, 0)
+ IdentifierInfo *insertItemWithTitleNSMenu[] = {
+ &Ctx.Idents.get("insertItemWithTitle"), &Ctx.Idents.get("action"),
+ &Ctx.Idents.get("keyEquivalent"), &Ctx.Idents.get("atIndex")};
+ ADD_METHOD(NSMenu, insertItemWithTitleNSMenu, 4, 0)
+ IdentifierInfo *addItemWithTitleNSMenu[] = {
+ &Ctx.Idents.get("addItemWithTitle"), &Ctx.Idents.get("action"),
+ &Ctx.Idents.get("keyEquivalent")};
+ ADD_METHOD(NSMenu, addItemWithTitleNSMenu, 3, 0)
+ ADD_UNARY_METHOD(NSMenu, setTitle, 0)
+
+ NEW_RECEIVER(UIMutableUserNotificationAction)
+ ADD_UNARY_METHOD(UIMutableUserNotificationAction, setTitle, 0)
+
+ NEW_RECEIVER(NSForm)
+ ADD_UNARY_METHOD(NSForm, addEntry, 0)
+ IdentifierInfo *insertEntryNSForm[] = {&Ctx.Idents.get("insertEntry"),
+ &Ctx.Idents.get("atIndex")};
+ ADD_METHOD(NSForm, insertEntryNSForm, 2, 0)
+
+ NEW_RECEIVER(NSTextFieldCell)
+ ADD_UNARY_METHOD(NSTextFieldCell, setPlaceholderString, 0)
+
+ NEW_RECEIVER(NSUserNotificationAction)
+ IdentifierInfo *actionWithIdentifierNSUserNotificationAction[] = {
+ &Ctx.Idents.get("actionWithIdentifier"), &Ctx.Idents.get("title")};
+ ADD_METHOD(NSUserNotificationAction,
+ actionWithIdentifierNSUserNotificationAction, 2, 1)
+
+ NEW_RECEIVER(NSURLSession)
+ ADD_UNARY_METHOD(NSURLSession, setSessionDescription, 0)
+
+ NEW_RECEIVER(UITextField)
+ ADD_UNARY_METHOD(UITextField, setText, 0)
+ ADD_UNARY_METHOD(UITextField, setPlaceholder, 0)
+
+ NEW_RECEIVER(UIBarButtonItem)
+ IdentifierInfo *initWithTitleUIBarButtonItem[] = {
+ &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("style"),
+ &Ctx.Idents.get("target"), &Ctx.Idents.get("action")};
+ ADD_METHOD(UIBarButtonItem, initWithTitleUIBarButtonItem, 4, 0)
+
+ NEW_RECEIVER(UIViewController)
+ ADD_UNARY_METHOD(UIViewController, setTitle, 0)
+
+ NEW_RECEIVER(UISegmentedControl)
+ IdentifierInfo *insertSegmentWithTitleUISegmentedControl[] = {
+ &Ctx.Idents.get("insertSegmentWithTitle"), &Ctx.Idents.get("atIndex"),
+ &Ctx.Idents.get("animated")};
+ ADD_METHOD(UISegmentedControl, insertSegmentWithTitleUISegmentedControl, 3, 0)
+ IdentifierInfo *setTitleUISegmentedControl[] = {
+ &Ctx.Idents.get("setTitle"), &Ctx.Idents.get("forSegmentAtIndex")};
+ ADD_METHOD(UISegmentedControl, setTitleUISegmentedControl, 2, 0)
+}
+
+#define LSF_INSERT(function_name) LSF.insert(&Ctx.Idents.get(function_name));
+#define LSM_INSERT_NULLARY(receiver, method_name) \
+ LSM.insert({&Ctx.Idents.get(receiver), Ctx.Selectors.getNullarySelector( \
+ &Ctx.Idents.get(method_name))});
+#define LSM_INSERT_UNARY(receiver, method_name) \
+ LSM.insert({&Ctx.Idents.get(receiver), \
+ Ctx.Selectors.getUnarySelector(&Ctx.Idents.get(method_name))});
+#define LSM_INSERT_SELECTOR(receiver, method_list, arguments) \
+ LSM.insert({&Ctx.Idents.get(receiver), \
+ Ctx.Selectors.getSelector(arguments, method_list)});
+
+/// Initializes a list of methods and C functions that return a localized string
+void NonLocalizedStringChecker::initLocStringsMethods(ASTContext &Ctx) const {
+ if (!LSM.empty())
+ return;
+
+ IdentifierInfo *LocalizedStringMacro[] = {
+ &Ctx.Idents.get("localizedStringForKey"), &Ctx.Idents.get("value"),
+ &Ctx.Idents.get("table")};
+ LSM_INSERT_SELECTOR("NSBundle", LocalizedStringMacro, 3)
+ LSM_INSERT_UNARY("NSDateFormatter", "stringFromDate")
+ IdentifierInfo *LocalizedStringFromDate[] = {
+ &Ctx.Idents.get("localizedStringFromDate"), &Ctx.Idents.get("dateStyle"),
+ &Ctx.Idents.get("timeStyle")};
+ LSM_INSERT_SELECTOR("NSDateFormatter", LocalizedStringFromDate, 3)
+ LSM_INSERT_UNARY("NSNumberFormatter", "stringFromNumber")
+ LSM_INSERT_NULLARY("UITextField", "text")
+ LSM_INSERT_NULLARY("UITextView", "text")
+ LSM_INSERT_NULLARY("UILabel", "text")
+
+ LSF_INSERT("CFDateFormatterCreateStringWithDate");
+ LSF_INSERT("CFDateFormatterCreateStringWithAbsoluteTime");
+ LSF_INSERT("CFNumberFormatterCreateStringWithNumber");
+}
+
+/// Checks to see if the method / function declaration includes
+/// __attribute__((annotate("returns_localized_nsstring")))
+bool NonLocalizedStringChecker::isAnnotatedAsLocalized(const Decl *D) const {
+ if (!D)
+ return false;
+ return std::any_of(
+ D->specific_attr_begin<AnnotateAttr>(),
+ D->specific_attr_end<AnnotateAttr>(), [](const AnnotateAttr *Ann) {
+ return Ann->getAnnotation() == "returns_localized_nsstring";
+ });
+}
+
+/// Returns true if the given SVal is marked as Localized in the program state
+bool NonLocalizedStringChecker::hasLocalizedState(SVal S,
+ CheckerContext &C) const {
+ const MemRegion *mt = S.getAsRegion();
+ if (mt) {
+ const LocalizedState *LS = C.getState()->get<LocalizedMemMap>(mt);
+ if (LS && LS->isLocalized())
+ return true;
+ }
+ return false;
+}
+
+/// Returns true if the given SVal is marked as NonLocalized in the program
+/// state
+bool NonLocalizedStringChecker::hasNonLocalizedState(SVal S,
+ CheckerContext &C) const {
+ const MemRegion *mt = S.getAsRegion();
+ if (mt) {
+ const LocalizedState *LS = C.getState()->get<LocalizedMemMap>(mt);
+ if (LS && LS->isNonLocalized())
+ return true;
+ }
+ return false;
+}
+
+/// Marks the given SVal as Localized in the program state
+void NonLocalizedStringChecker::setLocalizedState(const SVal S,
+ CheckerContext &C) const {
+ const MemRegion *mt = S.getAsRegion();
+ if (mt) {
+ ProgramStateRef State =
+ C.getState()->set<LocalizedMemMap>(mt, LocalizedState::getLocalized());
+ C.addTransition(State);
+ }
+}
+
+/// Marks the given SVal as NonLocalized in the program state
+void NonLocalizedStringChecker::setNonLocalizedState(const SVal S,
+ CheckerContext &C) const {
+ const MemRegion *mt = S.getAsRegion();
+ if (mt) {
+ ProgramStateRef State = C.getState()->set<LocalizedMemMap>(
+ mt, LocalizedState::getNonLocalized());
+ C.addTransition(State);
+ }
+}
+
+/// Reports a localization error for the passed in method call and SVal
+void NonLocalizedStringChecker::reportLocalizationError(
+ SVal S, const ObjCMethodCall &M, CheckerContext &C,
+ int argumentNumber) const {
+
+ ExplodedNode *ErrNode = C.getPredecessor();
+ static CheckerProgramPointTag Tag("NonLocalizedStringChecker",
+ "UnlocalizedString");
+ ErrNode = C.addTransition(C.getState(), C.getPredecessor(), &Tag);
+
+ if (!ErrNode)
+ return;
+
+ // Generate the bug report.
+ std::unique_ptr<BugReport> R(new BugReport(
+ *BT, "User-facing text should use localized string macro", ErrNode));
+ if (argumentNumber) {
+ R->addRange(M.getArgExpr(argumentNumber - 1)->getSourceRange());
+ } else {
+ R->addRange(M.getSourceRange());
+ }
+ R->markInteresting(S);
+ C.emitReport(std::move(R));
+}
+
+/// Returns the argument number requiring localized string if it exists
+/// otherwise, returns -1
+int NonLocalizedStringChecker::getLocalizedArgumentForSelector(
+ const IdentifierInfo *Receiver, Selector S) const {
+ auto method = UIMethods.find(Receiver);
+
+ if (method == UIMethods.end())
+ return -1;
+
+ auto argumentIterator = method->getSecond().find(S);
+
+ if (argumentIterator == method->getSecond().end())
+ return -1;
+
+ int argumentNumber = argumentIterator->getSecond();
+ return argumentNumber;
+}
+
+/// Check if the string being passed in has NonLocalized state
+void NonLocalizedStringChecker::checkPreObjCMessage(const ObjCMethodCall &msg,
+ CheckerContext &C) const {
+ initUIMethods(C.getASTContext());
+
+ const ObjCInterfaceDecl *OD = msg.getReceiverInterface();
+ if (!OD)
+ return;
+ const IdentifierInfo *odInfo = OD->getIdentifier();
+
+ Selector S = msg.getSelector();
+
+ std::string SelectorString = S.getAsString();
+ StringRef SelectorName = SelectorString;
+ assert(!SelectorName.empty());
+
+ if (odInfo->isStr("NSString")) {
+ // Handle the case where the receiver is an NSString
+ // These special NSString methods draw to the screen
+
+ if (!(SelectorName.startswith("drawAtPoint") ||
+ SelectorName.startswith("drawInRect") ||
+ SelectorName.startswith("drawWithRect")))
+ return;
+
+ SVal svTitle = msg.getReceiverSVal();
+
+ bool isNonLocalized = hasNonLocalizedState(svTitle, C);
+
+ if (isNonLocalized) {
+ reportLocalizationError(svTitle, msg, C);
+ }
+ }
+
+ int argumentNumber = getLocalizedArgumentForSelector(odInfo, S);
+ // Go up each hierarchy of superclasses and their protocols
+ while (argumentNumber < 0 && OD->getSuperClass() != nullptr) {
+ for (const auto *P : OD->all_referenced_protocols()) {
+ argumentNumber = getLocalizedArgumentForSelector(P->getIdentifier(), S);
+ if (argumentNumber >= 0)
+ break;
+ }
+ if (argumentNumber < 0) {
+ OD = OD->getSuperClass();
+ argumentNumber = getLocalizedArgumentForSelector(OD->getIdentifier(), S);
+ }
+ }
+
+ if (argumentNumber < 0) // There was no match in UIMethods
+ return;
+
+ SVal svTitle = msg.getArgSVal(argumentNumber);
+
+ if (const ObjCStringRegion *SR =
+ dyn_cast_or_null<ObjCStringRegion>(svTitle.getAsRegion())) {
+ StringRef stringValue =
+ SR->getObjCStringLiteral()->getString()->getString();
+ if ((stringValue.trim().size() == 0 && stringValue.size() > 0) ||
+ stringValue.empty())
+ return;
+ if (!IsAggressive && llvm::sys::unicode::columnWidthUTF8(stringValue) < 2)
+ return;
+ }
+
+ bool isNonLocalized = hasNonLocalizedState(svTitle, C);
+
+ if (isNonLocalized) {
+ reportLocalizationError(svTitle, msg, C, argumentNumber + 1);
+ }
+}
+
+static inline bool isNSStringType(QualType T, ASTContext &Ctx) {
+
+ const ObjCObjectPointerType *PT = T->getAs<ObjCObjectPointerType>();
+ if (!PT)
+ return false;
+
+ ObjCInterfaceDecl *Cls = PT->getObjectType()->getInterface();
+ if (!Cls)
+ return false;
+
+ IdentifierInfo *ClsName = Cls->getIdentifier();
+
+ // FIXME: Should we walk the chain of classes?
+ return ClsName == &Ctx.Idents.get("NSString") ||
+ ClsName == &Ctx.Idents.get("NSMutableString");
+}
+
+/// Marks a string being returned by any call as localized
+/// if it is in LocStringFunctions (LSF) or the function is annotated.
+/// Otherwise, we mark it as NonLocalized (Aggressive) or
+/// NonLocalized only if it is not backed by a SymRegion (Non-Aggressive),
+/// basically leaving only string literals as NonLocalized.
+void NonLocalizedStringChecker::checkPostCall(const CallEvent &Call,
+ CheckerContext &C) const {
+ initLocStringsMethods(C.getASTContext());
+
+ if (!Call.getOriginExpr())
+ return;
+
+ // Anything that takes in a localized NSString as an argument
+ // and returns an NSString will be assumed to be returning a
+ // localized NSString. (Counter: Incorrectly combining two LocalizedStrings)
+ const QualType RT = Call.getResultType();
+ if (isNSStringType(RT, C.getASTContext())) {
+ for (unsigned i = 0; i < Call.getNumArgs(); ++i) {
+ SVal argValue = Call.getArgSVal(i);
+ if (hasLocalizedState(argValue, C)) {
+ SVal sv = Call.getReturnValue();
+ setLocalizedState(sv, C);
+ return;
+ }
+ }
+ }
+
+ const Decl *D = Call.getDecl();
+ if (!D)
+ return;
+
+ const IdentifierInfo *Identifier = Call.getCalleeIdentifier();
+
+ SVal sv = Call.getReturnValue();
+ if (isAnnotatedAsLocalized(D) || LSF.count(Identifier) != 0) {
+ setLocalizedState(sv, C);
+ } else if (isNSStringType(RT, C.getASTContext()) &&
+ !hasLocalizedState(sv, C)) {
+ if (IsAggressive) {
+ setNonLocalizedState(sv, C);
+ } else {
+ const SymbolicRegion *SymReg =
+ dyn_cast_or_null<SymbolicRegion>(sv.getAsRegion());
+ if (!SymReg)
+ setNonLocalizedState(sv, C);
+ }
+ }
+}
+
+/// Marks a string being returned by an ObjC method as localized
+/// if it is in LocStringMethods or the method is annotated
+void NonLocalizedStringChecker::checkPostObjCMessage(const ObjCMethodCall &msg,
+ CheckerContext &C) const {
+ initLocStringsMethods(C.getASTContext());
+
+ if (!msg.isInstanceMessage())
+ return;
+
+ const ObjCInterfaceDecl *OD = msg.getReceiverInterface();
+ if (!OD)
+ return;
+ const IdentifierInfo *odInfo = OD->getIdentifier();
+
+ Selector S = msg.getSelector();
+ std::string SelectorName = S.getAsString();
+
+ std::pair<const IdentifierInfo *, Selector> MethodDescription = {odInfo, S};
+
+ if (LSM.count(MethodDescription) || isAnnotatedAsLocalized(msg.getDecl())) {
+ SVal sv = msg.getReturnValue();
+ setLocalizedState(sv, C);
+ }
+}
+
+/// Marks all empty string literals as localized
+void NonLocalizedStringChecker::checkPostStmt(const ObjCStringLiteral *SL,
+ CheckerContext &C) const {
+ SVal sv = C.getSVal(SL);
+ setNonLocalizedState(sv, C);
+}
+
+namespace {
+class EmptyLocalizationContextChecker
+ : public Checker<check::ASTDecl<ObjCImplementationDecl>> {
+
+ // A helper class, which walks the AST
+ class MethodCrawler : public ConstStmtVisitor<MethodCrawler> {
+ const ObjCMethodDecl *MD;
+ BugReporter &BR;
+ AnalysisManager &Mgr;
+ const CheckerBase *Checker;
+ LocationOrAnalysisDeclContext DCtx;
+
+ public:
+ MethodCrawler(const ObjCMethodDecl *InMD, BugReporter &InBR,
+ const CheckerBase *Checker, AnalysisManager &InMgr,
+ AnalysisDeclContext *InDCtx)
+ : MD(InMD), BR(InBR), Mgr(InMgr), Checker(Checker), DCtx(InDCtx) {}
+
+ void VisitStmt(const Stmt *S) { VisitChildren(S); }
+
+ void VisitObjCMessageExpr(const ObjCMessageExpr *ME);
+
+ void reportEmptyContextError(const ObjCMessageExpr *M) const;
+
+ void VisitChildren(const Stmt *S) {
+ for (const Stmt *Child : S->children()) {
+ if (Child)
+ this->Visit(Child);
+ }
+ }
+ };
+
+public:
+ void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager &Mgr,
+ BugReporter &BR) const;
+};
+} // end anonymous namespace
+
+void EmptyLocalizationContextChecker::checkASTDecl(
+ const ObjCImplementationDecl *D, AnalysisManager &Mgr,
+ BugReporter &BR) const {
+
+ for (const ObjCMethodDecl *M : D->methods()) {
+ AnalysisDeclContext *DCtx = Mgr.getAnalysisDeclContext(M);
+
+ const Stmt *Body = M->getBody();
+ assert(Body);
+
+ MethodCrawler MC(M->getCanonicalDecl(), BR, this, Mgr, DCtx);
+ MC.VisitStmt(Body);
+ }
+}
+
+/// This check attempts to match these macros, assuming they are defined as
+/// follows:
+///
+/// #define NSLocalizedString(key, comment) \
+/// [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:nil]
+/// #define NSLocalizedStringFromTable(key, tbl, comment) \
+/// [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:(tbl)]
+/// #define NSLocalizedStringFromTableInBundle(key, tbl, bundle, comment) \
+/// [bundle localizedStringForKey:(key) value:@"" table:(tbl)]
+/// #define NSLocalizedStringWithDefaultValue(key, tbl, bundle, val, comment)
+///
+/// We cannot use the path sensitive check because the macro argument we are
+/// checking for (comment) is not used and thus not present in the AST,
+/// so we use Lexer on the original macro call and retrieve the value of
+/// the comment. If it's empty or nil, we raise a warning.
+void EmptyLocalizationContextChecker::MethodCrawler::VisitObjCMessageExpr(
+ const ObjCMessageExpr *ME) {
+
+ const ObjCInterfaceDecl *OD = ME->getReceiverInterface();
+ if (!OD)
+ return;
+
+ const IdentifierInfo *odInfo = OD->getIdentifier();
+
+ if (!(odInfo->isStr("NSBundle") &&
+ ME->getSelector().getAsString() ==
+ "localizedStringForKey:value:table:")) {
+ return;
+ }
+
+ SourceRange R = ME->getSourceRange();
+ if (!R.getBegin().isMacroID())
+ return;
+
+ // getImmediateMacroCallerLoc gets the location of the immediate macro
+ // caller, one level up the stack toward the initial macro typed into the
+ // source, so SL should point to the NSLocalizedString macro.
+ SourceLocation SL =
+ Mgr.getSourceManager().getImmediateMacroCallerLoc(R.getBegin());
+ std::pair<FileID, unsigned> SLInfo =
+ Mgr.getSourceManager().getDecomposedLoc(SL);
+
+ SrcMgr::SLocEntry SE = Mgr.getSourceManager().getSLocEntry(SLInfo.first);
+
+ // If NSLocalizedString macro is wrapped in another macro, we need to
+ // unwrap the expansion until we get to the NSLocalizedStringMacro.
+ while (SE.isExpansion()) {
+ SL = SE.getExpansion().getSpellingLoc();
+ SLInfo = Mgr.getSourceManager().getDecomposedLoc(SL);
+ SE = Mgr.getSourceManager().getSLocEntry(SLInfo.first);
+ }
+
+ llvm::MemoryBuffer *BF = SE.getFile().getContentCache()->getRawBuffer();
+ Lexer TheLexer(SL, LangOptions(), BF->getBufferStart(),
+ BF->getBufferStart() + SLInfo.second, BF->getBufferEnd());
+
+ Token I;
+ Token Result; // This will hold the token just before the last ')'
+ int p_count = 0; // This is for parenthesis matching
+ while (!TheLexer.LexFromRawLexer(I)) {
+ if (I.getKind() == tok::l_paren)
+ ++p_count;
+ if (I.getKind() == tok::r_paren) {
+ if (p_count == 1)
+ break;
+ --p_count;
+ }
+ Result = I;
+ }
+
+ if (isAnyIdentifier(Result.getKind())) {
+ if (Result.getRawIdentifier().equals("nil")) {
+ reportEmptyContextError(ME);
+ return;
+ }
+ }
+
+ if (!isStringLiteral(Result.getKind()))
+ return;
+
+ StringRef Comment =
+ StringRef(Result.getLiteralData(), Result.getLength()).trim("\"");
+
+ if ((Comment.trim().size() == 0 && Comment.size() > 0) || // Is Whitespace
+ Comment.empty()) {
+ reportEmptyContextError(ME);
+ }
+}
+
+void EmptyLocalizationContextChecker::MethodCrawler::reportEmptyContextError(
+ const ObjCMessageExpr *ME) const {
+ // Generate the bug report.
+ BR.EmitBasicReport(MD, Checker, "Context Missing",
+ "Localizability Issue (Apple)",
+ "Localized string macro should include a non-empty "
+ "comment for translators",
+ PathDiagnosticLocation(ME, BR.getSourceManager(), DCtx));
+}
+
+namespace {
+class PluralMisuseChecker : public Checker<check::ASTCodeBody> {
+
+ // A helper class, which walks the AST
+ class MethodCrawler : public RecursiveASTVisitor<MethodCrawler> {
+ BugReporter &BR;
+ const CheckerBase *Checker;
+ AnalysisDeclContext *AC;
+
+ // This functions like a stack. We push on any IfStmt or
+ // ConditionalOperator that matches the condition
+ // and pop it off when we leave that statement
+ llvm::SmallVector<const clang::Stmt *, 8> MatchingStatements;
+ // This is true when we are the direct-child of a
+ // matching statement
+ bool InMatchingStatement = false;
+
+ public:
+ explicit MethodCrawler(BugReporter &InBR, const CheckerBase *Checker,
+ AnalysisDeclContext *InAC)
+ : BR(InBR), Checker(Checker), AC(InAC) {}
+
+ bool VisitIfStmt(const IfStmt *I);
+ bool EndVisitIfStmt(IfStmt *I);
+ bool TraverseIfStmt(IfStmt *x);
+ bool VisitConditionalOperator(const ConditionalOperator *C);
+ bool TraverseConditionalOperator(ConditionalOperator *C);
+ bool VisitCallExpr(const CallExpr *CE);
+ bool VisitObjCMessageExpr(const ObjCMessageExpr *ME);
+
+ private:
+ void reportPluralMisuseError(const Stmt *S) const;
+ bool isCheckingPlurality(const Expr *E) const;
+ };
+
+public:
+ void checkASTCodeBody(const Decl *D, AnalysisManager &Mgr,
+ BugReporter &BR) const {
+ MethodCrawler Visitor(BR, this, Mgr.getAnalysisDeclContext(D));
+ Visitor.TraverseDecl(const_cast<Decl *>(D));
+ }
+};
+} // end anonymous namespace
+
+// Checks the condition of the IfStmt and returns true if one
+// of the following heuristics are met:
+// 1) The conidtion is a variable with "singular" or "plural" in the name
+// 2) The condition is a binary operator with 1 or 2 on the right-hand side
+bool PluralMisuseChecker::MethodCrawler::isCheckingPlurality(
+ const Expr *Condition) const {
+ const BinaryOperator *BO = nullptr;
+ // Accounts for when a VarDecl represents a BinaryOperator
+ if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(Condition)) {
+ if (const VarDecl *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
+ const Expr *InitExpr = VD->getInit();
+ if (InitExpr) {
+ if (const BinaryOperator *B =
+ dyn_cast<BinaryOperator>(InitExpr->IgnoreParenImpCasts())) {
+ BO = B;
+ }
+ }
+ if (VD->getName().lower().find("plural") != StringRef::npos ||
+ VD->getName().lower().find("singular") != StringRef::npos) {
+ return true;
+ }
+ }
+ } else if (const BinaryOperator *B = dyn_cast<BinaryOperator>(Condition)) {
+ BO = B;
+ }
+
+ if (BO == nullptr)
+ return false;
+
+ if (IntegerLiteral *IL = dyn_cast_or_null<IntegerLiteral>(
+ BO->getRHS()->IgnoreParenImpCasts())) {
+ llvm::APInt Value = IL->getValue();
+ if (Value == 1 || Value == 2) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// A CallExpr with "LOC" in its identifier that takes in a string literal
+// has been shown to almost always be a function that returns a localized
+// string. Raise a diagnostic when this is in a statement that matches
+// the condition.
+bool PluralMisuseChecker::MethodCrawler::VisitCallExpr(const CallExpr *CE) {
+ if (InMatchingStatement) {
+ if (const FunctionDecl *FD = CE->getDirectCallee()) {
+ std::string NormalizedName =
+ StringRef(FD->getNameInfo().getAsString()).lower();
+ if (NormalizedName.find("loc") != std::string::npos) {
+ for (const Expr *Arg : CE->arguments()) {
+ if (isa<ObjCStringLiteral>(Arg))
+ reportPluralMisuseError(CE);
+ }
+ }
+ }
+ }
+ return true;
+}
+
+// The other case is for NSLocalizedString which also returns
+// a localized string. It's a macro for the ObjCMessageExpr
+// [NSBundle localizedStringForKey:value:table:] Raise a
+// diagnostic when this is in a statement that matches
+// the condition.
+bool PluralMisuseChecker::MethodCrawler::VisitObjCMessageExpr(
+ const ObjCMessageExpr *ME) {
+ const ObjCInterfaceDecl *OD = ME->getReceiverInterface();
+ if (!OD)
+ return true;
+
+ const IdentifierInfo *odInfo = OD->getIdentifier();
+
+ if (odInfo->isStr("NSBundle") &&
+ ME->getSelector().getAsString() == "localizedStringForKey:value:table:") {
+ if (InMatchingStatement) {
+ reportPluralMisuseError(ME);
+ }
+ }
+ return true;
+}
+
+/// Override TraverseIfStmt so we know when we are done traversing an IfStmt
+bool PluralMisuseChecker::MethodCrawler::TraverseIfStmt(IfStmt *I) {
+ RecursiveASTVisitor<MethodCrawler>::TraverseIfStmt(I);
+ return EndVisitIfStmt(I);
+}
+
+// EndVisit callbacks are not provided by the RecursiveASTVisitor
+// so we override TraverseIfStmt and make a call to EndVisitIfStmt
+// after traversing the IfStmt
+bool PluralMisuseChecker::MethodCrawler::EndVisitIfStmt(IfStmt *I) {
+ MatchingStatements.pop_back();
+ if (!MatchingStatements.empty()) {
+ if (MatchingStatements.back() != nullptr) {
+ InMatchingStatement = true;
+ return true;
+ }
+ }
+ InMatchingStatement = false;
+ return true;
+}
+
+bool PluralMisuseChecker::MethodCrawler::VisitIfStmt(const IfStmt *I) {
+ const Expr *Condition = I->getCond()->IgnoreParenImpCasts();
+ if (isCheckingPlurality(Condition)) {
+ MatchingStatements.push_back(I);
+ InMatchingStatement = true;
+ } else {
+ MatchingStatements.push_back(nullptr);
+ InMatchingStatement = false;
+ }
+
+ return true;
+}
+
+// Preliminary support for conditional operators.
+bool PluralMisuseChecker::MethodCrawler::TraverseConditionalOperator(
+ ConditionalOperator *C) {
+ RecursiveASTVisitor<MethodCrawler>::TraverseConditionalOperator(C);
+ MatchingStatements.pop_back();
+ if (!MatchingStatements.empty()) {
+ if (MatchingStatements.back() != nullptr)
+ InMatchingStatement = true;
+ else
+ InMatchingStatement = false;
+ } else {
+ InMatchingStatement = false;
+ }
+ return true;
+}
+
+bool PluralMisuseChecker::MethodCrawler::VisitConditionalOperator(
+ const ConditionalOperator *C) {
+ const Expr *Condition = C->getCond()->IgnoreParenImpCasts();
+ if (isCheckingPlurality(Condition)) {
+ MatchingStatements.push_back(C);
+ InMatchingStatement = true;
+ } else {
+ MatchingStatements.push_back(nullptr);
+ InMatchingStatement = false;
+ }
+ return true;
+}
+
+void PluralMisuseChecker::MethodCrawler::reportPluralMisuseError(
+ const Stmt *S) const {
+ // Generate the bug report.
+ BR.EmitBasicReport(AC->getDecl(), Checker, "Plural Misuse",
+ "Localizability Issue (Apple)",
+ "Plural cases are not supported accross all languages. "
+ "Use a .stringsdict file instead",
+ PathDiagnosticLocation(S, BR.getSourceManager(), AC));
+}
+
+//===----------------------------------------------------------------------===//
+// Checker registration.
+//===----------------------------------------------------------------------===//
+
+void ento::registerNonLocalizedStringChecker(CheckerManager &mgr) {
+ NonLocalizedStringChecker *checker =
+ mgr.registerChecker<NonLocalizedStringChecker>();
+ checker->IsAggressive =
+ mgr.getAnalyzerOptions().getBooleanOption("AggressiveReport", false);
+}
+
+void ento::registerEmptyLocalizationContextChecker(CheckerManager &mgr) {
+ mgr.registerChecker<EmptyLocalizationContextChecker>();
+}
+
+void ento::registerPluralMisuseChecker(CheckerManager &mgr) {
+ mgr.registerChecker<PluralMisuseChecker>();
+}
diff --git a/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp b/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp
index 783890135ea3..1e56d709e4f9 100644
--- a/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp
@@ -118,7 +118,7 @@ private:
SValBuilder &Builder) const {
return definitelyReturnedError(RetSym, State, Builder, true);
}
-
+
/// Mark an AllocationPair interesting for diagnostic reporting.
void markInteresting(BugReport *R, const AllocationPair &AP) const {
R->markInteresting(AP.first);
@@ -136,7 +136,6 @@ private:
public:
SecKeychainBugVisitor(SymbolRef S) : Sym(S) {}
- ~SecKeychainBugVisitor() override {}
void Profile(llvm::FoldingSetNodeID &ID) const override {
static int X = 0;
@@ -202,12 +201,8 @@ unsigned MacOSKeychainAPIChecker::getTrackedFunctionIndex(StringRef Name,
static bool isBadDeallocationArgument(const MemRegion *Arg) {
if (!Arg)
return false;
- if (isa<AllocaRegion>(Arg) ||
- isa<BlockDataRegion>(Arg) ||
- isa<TypedRegion>(Arg)) {
- return true;
- }
- return false;
+ return isa<AllocaRegion>(Arg) || isa<BlockDataRegion>(Arg) ||
+ isa<TypedRegion>(Arg);
}
/// Given the address expression, retrieve the value it's pointing to. Assume
@@ -241,11 +236,7 @@ bool MacOSKeychainAPIChecker::definitelyReturnedError(SymbolRef RetSym,
DefinedOrUnknownSVal NoErr = Builder.evalEQ(State, NoErrVal,
nonloc::SymbolVal(RetSym));
ProgramStateRef ErrState = State->assume(NoErr, noError);
- if (ErrState == State) {
- return true;
- }
-
- return false;
+ return ErrState == State;
}
// Report deallocator mismatch. Remove the region from tracking - reporting a
@@ -256,7 +247,7 @@ void MacOSKeychainAPIChecker::
CheckerContext &C) const {
ProgramStateRef State = C.getState();
State = State->remove<AllocatedData>(AP.first);
- ExplodedNode *N = C.addTransition(State);
+ ExplodedNode *N = C.generateNonFatalErrorNode(State);
if (!N)
return;
@@ -283,7 +274,7 @@ void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE,
const FunctionDecl *FD = C.getCalleeDecl(CE);
if (!FD || FD->getKind() != Decl::Function)
return;
-
+
StringRef funName = C.getCalleeName(FD);
if (funName.empty())
return;
@@ -302,7 +293,7 @@ void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE,
// Remove the value from the state. The new symbol will be added for
// tracking when the second allocator is processed in checkPostStmt().
State = State->remove<AllocatedData>(V);
- ExplodedNode *N = C.addTransition(State);
+ ExplodedNode *N = C.generateNonFatalErrorNode(State);
if (!N)
return;
initBugType();
@@ -365,7 +356,7 @@ void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE,
if (isEnclosingFunctionParam(ArgExpr))
return;
- ExplodedNode *N = C.addTransition(State);
+ ExplodedNode *N = C.generateNonFatalErrorNode(State);
if (!N)
return;
initBugType();
@@ -431,7 +422,7 @@ void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE,
// report a bad call to free.
if (State->assume(ArgSVal.castAs<DefinedSVal>(), false) &&
!definitelyDidnotReturnError(AS->Region, State, C.getSValBuilder())) {
- ExplodedNode *N = C.addTransition(State);
+ ExplodedNode *N = C.generateNonFatalErrorNode(State);
if (!N)
return;
initBugType();
@@ -585,10 +576,12 @@ void MacOSKeychainAPIChecker::checkDeadSymbols(SymbolReaper &SR,
}
static CheckerProgramPointTag Tag(this, "DeadSymbolsLeak");
- ExplodedNode *N = C.addTransition(C.getState(), C.getPredecessor(), &Tag);
+ ExplodedNode *N = C.generateNonFatalErrorNode(C.getState(), &Tag);
+ if (!N)
+ return;
// Generate the error reports.
- for (const auto P : Errors)
+ for (const auto &P : Errors)
C.emitReport(generateAllocatedDataNotReleasedReport(P, N, C));
// Generate the new, cleaned up state.
diff --git a/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp b/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp
index 11ba6096e2dc..4cbe97b26075 100644
--- a/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp
@@ -62,7 +62,7 @@ void MacOSXAPIChecker::CheckDispatchOnce(CheckerContext &C, const CallExpr *CE,
if (!R || !isa<StackSpaceRegion>(R->getMemorySpace()))
return;
- ExplodedNode *N = C.generateSink(state);
+ ExplodedNode *N = C.generateErrorNode(state);
if (!N)
return;
@@ -79,7 +79,7 @@ void MacOSXAPIChecker::CheckDispatchOnce(CheckerContext &C, const CallExpr *CE,
if (TrimmedFName != FName)
FName = TrimmedFName;
}
-
+
SmallString<256> S;
llvm::raw_svector_ostream os(S);
os << "Call to '" << FName << "' uses";
diff --git a/lib/StaticAnalyzer/Checkers/Makefile b/lib/StaticAnalyzer/Checkers/Makefile
index 2582908b95d0..7c8f7bf1bc4e 100644
--- a/lib/StaticAnalyzer/Checkers/Makefile
+++ b/lib/StaticAnalyzer/Checkers/Makefile
@@ -1,13 +1,13 @@
##===- clang/lib/Checker/Makefile --------------------------*- Makefile -*-===##
-#
+#
# The LLVM Compiler Infrastructure
#
# This file is distributed under the University of Illinois Open Source
# License. See LICENSE.TXT for details.
-#
+#
##===----------------------------------------------------------------------===##
#
-# This implements analyses built on top of source-level CFGs.
+# This implements analyses built on top of source-level CFGs.
#
##===----------------------------------------------------------------------===##
diff --git a/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
index a9e08653b241..713d9fe285a5 100644
--- a/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
@@ -65,10 +65,10 @@ class RefState {
const Stmt *S;
unsigned K : 3; // Kind enum, but stored as a bitfield.
- unsigned Family : 29; // Rest of 32-bit word, currently just an allocation
+ unsigned Family : 29; // Rest of 32-bit word, currently just an allocation
// family.
- RefState(Kind k, const Stmt *s, unsigned family)
+ RefState(Kind k, const Stmt *s, unsigned family)
: S(s), K(k), Family(family) {
assert(family != AF_None);
}
@@ -94,7 +94,7 @@ public:
return RefState(AllocatedOfSizeZero, RS->getStmt(),
RS->getAllocationFamily());
}
- static RefState getReleased(unsigned family, const Stmt *s) {
+ static RefState getReleased(unsigned family, const Stmt *s) {
return RefState(Released, s, family);
}
static RefState getRelinquished(unsigned family, const Stmt *s) {
@@ -169,9 +169,9 @@ class MallocChecker : public Checker<check::DeadSymbols,
{
public:
MallocChecker()
- : II_alloca(nullptr), II_malloc(nullptr), II_free(nullptr),
+ : II_alloca(nullptr), II_malloc(nullptr), II_free(nullptr),
II_realloc(nullptr), II_calloc(nullptr), II_valloc(nullptr),
- II_reallocf(nullptr), II_strndup(nullptr), II_strdup(nullptr),
+ II_reallocf(nullptr), II_strndup(nullptr), II_strdup(nullptr),
II_kmalloc(nullptr), II_if_nameindex(nullptr),
II_if_freenameindex(nullptr) {}
@@ -185,7 +185,7 @@ public:
CK_NumCheckKinds
};
- enum class MemoryOperationKind {
+ enum class MemoryOperationKind {
MOK_Allocate,
MOK_Free,
MOK_Any
@@ -245,19 +245,19 @@ private:
/// \brief Print names of allocators and deallocators.
///
/// \returns true on success.
- bool printAllocDeallocName(raw_ostream &os, CheckerContext &C,
+ bool printAllocDeallocName(raw_ostream &os, CheckerContext &C,
const Expr *E) const;
/// \brief Print expected name of an allocator based on the deallocator's
/// family derived from the DeallocExpr.
- void printExpectedAllocName(raw_ostream &os, CheckerContext &C,
+ void printExpectedAllocName(raw_ostream &os, CheckerContext &C,
const Expr *DeallocExpr) const;
- /// \brief Print expected name of a deallocator based on the allocator's
+ /// \brief Print expected name of a deallocator based on the allocator's
/// family.
void printExpectedDeallocName(raw_ostream &os, AllocationFamily Family) const;
///@{
- /// Check if this is one of the functions which can allocate/reallocate memory
+ /// Check if this is one of the functions which can allocate/reallocate memory
/// pointed to by one of its arguments.
bool isMemFunction(const FunctionDecl *FD, ASTContext &C) const;
bool isCMemFunction(const FunctionDecl *FD,
@@ -292,7 +292,7 @@ private:
const ProgramStateRef &State) const;
/// Update the RefState to reflect the new memory allocation.
- static ProgramStateRef
+ static ProgramStateRef
MallocUpdateRefState(CheckerContext &C, const Expr *E, ProgramStateRef State,
AllocationFamily Family = AF_Malloc);
@@ -312,17 +312,17 @@ private:
bool ReturnsNullOnFailure = false) const;
ProgramStateRef ReallocMem(CheckerContext &C, const CallExpr *CE,
- bool FreesMemOnFailure,
+ bool FreesMemOnFailure,
ProgramStateRef State) const;
static ProgramStateRef CallocMem(CheckerContext &C, const CallExpr *CE,
ProgramStateRef State);
-
+
///\brief Check if the memory associated with this symbol was released.
bool isReleased(SymbolRef Sym, CheckerContext &C) const;
bool checkUseAfterFree(SymbolRef Sym, CheckerContext &C, const Stmt *S) const;
- void checkUseZeroAllocated(SymbolRef Sym, CheckerContext &C,
+ void checkUseZeroAllocated(SymbolRef Sym, CheckerContext &C,
const Stmt *S) const;
bool checkDoubleDelete(SymbolRef Sym, CheckerContext &C) const;
@@ -330,7 +330,7 @@ private:
/// Check if the function is known free memory, or if it is
/// "interesting" and should be modeled explicitly.
///
- /// \param [out] EscapingSymbol A function might not free memory in general,
+ /// \param [out] EscapingSymbol A function might not free memory in general,
/// but could be known to free a particular symbol. In this case, false is
/// returned and the single escaping symbol is returned through the out
/// parameter.
@@ -357,20 +357,20 @@ private:
Optional<CheckKind> getCheckIfTracked(CheckerContext &C,
const Stmt *AllocDeallocStmt,
bool IsALeakCheck = false) const;
- Optional<CheckKind> getCheckIfTracked(CheckerContext &C, SymbolRef Sym,
+ Optional<CheckKind> getCheckIfTracked(CheckerContext &C, SymbolRef Sym,
bool IsALeakCheck = false) const;
///@}
static bool SummarizeValue(raw_ostream &os, SVal V);
static bool SummarizeRegion(raw_ostream &os, const MemRegion *MR);
- void ReportBadFree(CheckerContext &C, SVal ArgVal, SourceRange Range,
+ void ReportBadFree(CheckerContext &C, SVal ArgVal, SourceRange Range,
const Expr *DeallocExpr) const;
void ReportFreeAlloca(CheckerContext &C, SVal ArgVal,
SourceRange Range) const;
void ReportMismatchedDealloc(CheckerContext &C, SourceRange Range,
const Expr *DeallocExpr, const RefState *RS,
SymbolRef Sym, bool OwnershipTransferred) const;
- void ReportOffsetFree(CheckerContext &C, SVal ArgVal, SourceRange Range,
- const Expr *DeallocExpr,
+ void ReportOffsetFree(CheckerContext &C, SVal ArgVal, SourceRange Range,
+ const Expr *DeallocExpr,
const Expr *AllocExpr = nullptr) const;
void ReportUseAfterFree(CheckerContext &C, SourceRange Range,
SymbolRef Sym) const;
@@ -392,7 +392,8 @@ private:
/// The bug visitor which allows us to print extra diagnostics along the
/// BugReport path. For example, showing the allocation site of the leaked
/// region.
- class MallocBugVisitor : public BugReporterVisitorImpl<MallocBugVisitor> {
+ class MallocBugVisitor final
+ : public BugReporterVisitorImpl<MallocBugVisitor> {
protected:
enum NotificationMode {
Normal,
@@ -414,8 +415,6 @@ private:
MallocBugVisitor(SymbolRef S, bool isLeak = false)
: Sym(S), Mode(Normal), FailedReallocSymbol(nullptr), IsLeak(isLeak) {}
- ~MallocBugVisitor() override {}
-
void Profile(llvm::FoldingSetNodeID &ID) const override {
static int X = 0;
ID.AddPointer(&X);
@@ -426,8 +425,8 @@ private:
const Stmt *Stmt) {
// Did not track -> allocated. Other state (released) -> allocated.
return (Stmt && (isa<CallExpr>(Stmt) || isa<CXXNewExpr>(Stmt)) &&
- (S && (S->isAllocated() || S->isAllocatedOfSizeZero())) &&
- (!SPrev || !(SPrev->isAllocated() ||
+ (S && (S->isAllocated() || S->isAllocatedOfSizeZero())) &&
+ (!SPrev || !(SPrev->isAllocated() ||
SPrev->isAllocatedOfSizeZero())));
}
@@ -509,13 +508,14 @@ private:
REGISTER_MAP_WITH_PROGRAMSTATE(RegionState, SymbolRef, RefState)
REGISTER_MAP_WITH_PROGRAMSTATE(ReallocPairs, SymbolRef, ReallocPair)
+REGISTER_SET_WITH_PROGRAMSTATE(ReallocSizeZeroSymbols, SymbolRef)
-// A map from the freed symbol to the symbol representing the return value of
+// A map from the freed symbol to the symbol representing the return value of
// the free function.
REGISTER_MAP_WITH_PROGRAMSTATE(FreeReturnValue, SymbolRef, SymbolRef)
namespace {
-class StopTrackingCallback : public SymbolVisitor {
+class StopTrackingCallback final : public SymbolVisitor {
ProgramStateRef state;
public:
StopTrackingCallback(ProgramStateRef st) : state(st) {}
@@ -634,7 +634,7 @@ bool MallocChecker::isStandardNewDelete(const FunctionDecl *FD,
return false;
OverloadedOperatorKind Kind = FD->getOverloadedOperator();
- if (Kind != OO_New && Kind != OO_Array_New &&
+ if (Kind != OO_New && Kind != OO_Array_New &&
Kind != OO_Delete && Kind != OO_Array_Delete)
return false;
@@ -799,8 +799,8 @@ void MallocChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const {
State = ProcessZeroAllocation(C, CE, 0, State);
} else if (isStandardNewDelete(FD, C.getASTContext())) {
// Process direct calls to operator new/new[]/delete/delete[] functions
- // as distinct from new/new[]/delete/delete[] expressions that are
- // processed by the checkPostStmt callbacks for CXXNewExpr and
+ // as distinct from new/new[]/delete/delete[] expressions that are
+ // processed by the checkPostStmt callbacks for CXXNewExpr and
// CXXDeleteExpr.
OverloadedOperatorKind K = FD->getOverloadedOperator();
if (K == OO_New) {
@@ -870,7 +870,7 @@ ProgramStateRef MallocChecker::ProcessZeroAllocation(CheckerContext &C,
assert(Arg);
- Optional<DefinedSVal> DefArgVal =
+ Optional<DefinedSVal> DefArgVal =
State->getSVal(Arg, C.getLocationContext()).getAs<DefinedSVal>();
if (!DefArgVal)
@@ -882,7 +882,7 @@ ProgramStateRef MallocChecker::ProcessZeroAllocation(CheckerContext &C,
DefinedSVal Zero =
SvalBuilder.makeZeroVal(Arg->getType()).castAs<DefinedSVal>();
- std::tie(TrueState, FalseState) =
+ std::tie(TrueState, FalseState) =
State->assume(SvalBuilder.evalEQ(State, *DefArgVal, Zero));
if (TrueState && !FalseState) {
@@ -892,15 +892,19 @@ ProgramStateRef MallocChecker::ProcessZeroAllocation(CheckerContext &C,
return State;
const RefState *RS = State->get<RegionState>(Sym);
- if (!RS)
- return State; // TODO: change to assert(RS); after realloc() will
- // guarantee have a RegionState attached.
-
- if (!RS->isAllocated())
- return State;
-
- return TrueState->set<RegionState>(Sym,
- RefState::getAllocatedOfSizeZero(RS));
+ if (RS) {
+ if (RS->isAllocated())
+ return TrueState->set<RegionState>(Sym,
+ RefState::getAllocatedOfSizeZero(RS));
+ else
+ return State;
+ } else {
+ // Case of zero-size realloc. Historically 'realloc(ptr, 0)' is treated as
+ // 'free(ptr)' and the returned value from 'realloc(ptr, 0)' is not
+ // tracked. Add zero-reallocated Sym to the state to catch references
+ // to zero-allocated memory.
+ return TrueState->add<ReallocSizeZeroSymbols>(Sym);
+ }
}
// Assume the value is non-zero going forward.
@@ -944,7 +948,7 @@ static bool treatUnusedNewEscaped(const CXXNewExpr *NE) {
return false;
}
-void MallocChecker::checkPostStmt(const CXXNewExpr *NE,
+void MallocChecker::checkPostStmt(const CXXNewExpr *NE,
CheckerContext &C) const {
if (NE->getNumPlacementArgs())
@@ -961,17 +965,17 @@ void MallocChecker::checkPostStmt(const CXXNewExpr *NE,
return;
ProgramStateRef State = C.getState();
- // The return value from operator new is bound to a specified initialization
- // value (if any) and we don't want to loose this value. So we call
- // MallocUpdateRefState() instead of MallocMemAux() which breakes the
+ // The return value from operator new is bound to a specified initialization
+ // value (if any) and we don't want to loose this value. So we call
+ // MallocUpdateRefState() instead of MallocMemAux() which breakes the
// existing binding.
- State = MallocUpdateRefState(C, NE, State, NE->isArray() ? AF_CXXNewArray
+ State = MallocUpdateRefState(C, NE, State, NE->isArray() ? AF_CXXNewArray
: AF_CXXNew);
State = ProcessZeroAllocation(C, NE, 0, State);
C.addTransition(State);
}
-void MallocChecker::checkPreStmt(const CXXDeleteExpr *DE,
+void MallocChecker::checkPreStmt(const CXXDeleteExpr *DE,
CheckerContext &C) const {
if (!ChecksEnabled[CK_NewDeleteChecker])
@@ -996,12 +1000,9 @@ static bool isKnownDeallocObjCMethodName(const ObjCMethodCall &Call) {
// Ex: [NSData dataWithBytesNoCopy:bytes length:10];
// (...unless a 'freeWhenDone' parameter is false, but that's checked later.)
StringRef FirstSlot = Call.getSelector().getNameForSlot(0);
- if (FirstSlot == "dataWithBytesNoCopy" ||
- FirstSlot == "initWithBytesNoCopy" ||
- FirstSlot == "initWithCharactersNoCopy")
- return true;
-
- return false;
+ return FirstSlot == "dataWithBytesNoCopy" ||
+ FirstSlot == "initWithBytesNoCopy" ||
+ FirstSlot == "initWithCharactersNoCopy";
}
static Optional<bool> getFreeWhenDoneArg(const ObjCMethodCall &Call) {
@@ -1038,7 +1039,7 @@ void MallocChecker::checkPostObjCMessage(const ObjCMethodCall &Call,
ProgramStateRef
MallocChecker::MallocMemReturnsAttr(CheckerContext &C, const CallExpr *CE,
- const OwnershipAttr *Att,
+ const OwnershipAttr *Att,
ProgramStateRef State) const {
if (!State)
return nullptr;
@@ -1105,7 +1106,7 @@ ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C,
State = State->assume(extentMatchesSize, true);
assert(State);
}
-
+
return MallocUpdateRefState(C, CE, State, Family);
}
@@ -1132,7 +1133,7 @@ ProgramStateRef MallocChecker::MallocUpdateRefState(CheckerContext &C,
ProgramStateRef MallocChecker::FreeMemAttr(CheckerContext &C,
const CallExpr *CE,
- const OwnershipAttr *Att,
+ const OwnershipAttr *Att,
ProgramStateRef State) const {
if (!State)
return nullptr;
@@ -1184,7 +1185,7 @@ static bool didPreviousFreeFail(ProgramStateRef State,
return false;
}
-AllocationFamily MallocChecker::getAllocationFamily(CheckerContext &C,
+AllocationFamily MallocChecker::getAllocationFamily(CheckerContext &C,
const Stmt *S) const {
if (!S)
return AF_None;
@@ -1229,14 +1230,14 @@ AllocationFamily MallocChecker::getAllocationFamily(CheckerContext &C,
return AF_None;
}
-bool MallocChecker::printAllocDeallocName(raw_ostream &os, CheckerContext &C,
+bool MallocChecker::printAllocDeallocName(raw_ostream &os, CheckerContext &C,
const Expr *E) const {
if (const CallExpr *CE = dyn_cast<CallExpr>(E)) {
// FIXME: This doesn't handle indirect calls.
const FunctionDecl *FD = CE->getDirectCallee();
if (!FD)
return false;
-
+
os << *FD;
if (!FD->isOverloadedOperator())
os << "()";
@@ -1253,14 +1254,14 @@ bool MallocChecker::printAllocDeallocName(raw_ostream &os, CheckerContext &C,
}
if (const CXXNewExpr *NE = dyn_cast<CXXNewExpr>(E)) {
- os << "'"
+ os << "'"
<< getOperatorSpelling(NE->getOperatorNew()->getOverloadedOperator())
<< "'";
return true;
}
if (const CXXDeleteExpr *DE = dyn_cast<CXXDeleteExpr>(E)) {
- os << "'"
+ os << "'"
<< getOperatorSpelling(DE->getOperatorDelete()->getOverloadedOperator())
<< "'";
return true;
@@ -1283,7 +1284,7 @@ void MallocChecker::printExpectedAllocName(raw_ostream &os, CheckerContext &C,
}
}
-void MallocChecker::printExpectedDeallocName(raw_ostream &os,
+void MallocChecker::printExpectedDeallocName(raw_ostream &os,
AllocationFamily Family) const {
switch(Family) {
case AF_Malloc: os << "free()"; return;
@@ -1327,25 +1328,25 @@ ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C,
return nullptr;
const MemRegion *R = ArgVal.getAsRegion();
-
+
// Nonlocs can't be freed, of course.
// Non-region locations (labels and fixed addresses) also shouldn't be freed.
if (!R) {
ReportBadFree(C, ArgVal, ArgExpr->getSourceRange(), ParentExpr);
return nullptr;
}
-
+
R = R->StripCasts();
-
+
// Blocks might show up as heap data, but should not be free()d
if (isa<BlockDataRegion>(R)) {
ReportBadFree(C, ArgVal, ArgExpr->getSourceRange(), ParentExpr);
return nullptr;
}
-
+
const MemSpaceRegion *MS = R->getMemorySpace();
-
- // Parameters, locals, statics, globals, and memory returned by
+
+ // Parameters, locals, statics, globals, and memory returned by
// __builtin_alloca() shouldn't be freed.
if (!(isa<UnknownSpaceRegion>(MS) || isa<HeapSpaceRegion>(MS))) {
// FIXME: at the time this code was written, malloc() regions were
@@ -1391,7 +1392,7 @@ ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C,
// If the pointer is allocated or escaped, but we are now trying to free it,
// check that the call to free is proper.
- } else if (RsBase->isAllocated() || RsBase->isAllocatedOfSizeZero() ||
+ } else if (RsBase->isAllocated() || RsBase->isAllocatedOfSizeZero() ||
RsBase->isEscaped()) {
// Check if an expected deallocation function matches the real one.
@@ -1410,20 +1411,20 @@ ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C,
!Offset.hasSymbolicOffset() &&
Offset.getOffset() != 0) {
const Expr *AllocExpr = cast<Expr>(RsBase->getStmt());
- ReportOffsetFree(C, ArgVal, ArgExpr->getSourceRange(), ParentExpr,
+ ReportOffsetFree(C, ArgVal, ArgExpr->getSourceRange(), ParentExpr,
AllocExpr);
return nullptr;
}
}
}
- ReleasedAllocated = (RsBase != nullptr) && (RsBase->isAllocated() ||
+ ReleasedAllocated = (RsBase != nullptr) && (RsBase->isAllocated() ||
RsBase->isAllocatedOfSizeZero());
// Clean out the info on previous call to free return info.
State = State->remove<FreeReturnValue>(SymBase);
- // Keep track of the return value. If it is NULL, we will know that free
+ // Keep track of the return value. If it is NULL, we will know that free
// failed.
if (ReturnsNullOnFailure) {
SVal RetVal = C.getSVal(ParentExpr);
@@ -1463,7 +1464,7 @@ MallocChecker::getCheckIfTracked(AllocationFamily Family,
if (IsALeakCheck) {
if (ChecksEnabled[CK_NewDeleteLeaksChecker])
return CK_NewDeleteLeaksChecker;
- }
+ }
else {
if (ChecksEnabled[CK_NewDeleteChecker])
return CK_NewDeleteChecker;
@@ -1488,6 +1489,9 @@ MallocChecker::getCheckIfTracked(CheckerContext &C,
Optional<MallocChecker::CheckKind>
MallocChecker::getCheckIfTracked(CheckerContext &C, SymbolRef Sym,
bool IsALeakCheck) const {
+ if (C.getState()->contains<ReallocSizeZeroSymbols>(Sym))
+ return CK_MallocChecker;
+
const RefState *RS = C.getState()->get<RegionState>(Sym);
assert(RS);
return getCheckIfTracked(RS->getAllocationFamily(), IsALeakCheck);
@@ -1502,7 +1506,7 @@ bool MallocChecker::SummarizeValue(raw_ostream &os, SVal V) {
os << "the address of the label '" << Label->getLabel()->getName() << "'";
else
return false;
-
+
return true;
}
@@ -1526,7 +1530,7 @@ bool MallocChecker::SummarizeRegion(raw_ostream &os,
return true;
default: {
const MemSpaceRegion *MS = MR->getMemorySpace();
-
+
if (isa<StackLocalsSpaceRegion>(MS)) {
const VarRegion *VR = dyn_cast<VarRegion>(MR);
const VarDecl *VD;
@@ -1580,8 +1584,8 @@ bool MallocChecker::SummarizeRegion(raw_ostream &os,
}
}
-void MallocChecker::ReportBadFree(CheckerContext &C, SVal ArgVal,
- SourceRange Range,
+void MallocChecker::ReportBadFree(CheckerContext &C, SVal ArgVal,
+ SourceRange Range,
const Expr *DeallocExpr) const {
if (!ChecksEnabled[CK_MallocChecker] &&
@@ -1593,7 +1597,7 @@ void MallocChecker::ReportBadFree(CheckerContext &C, SVal ArgVal,
if (!CheckKind.hasValue())
return;
- if (ExplodedNode *N = C.generateSink()) {
+ if (ExplodedNode *N = C.generateErrorNode()) {
if (!BT_BadFree[*CheckKind])
BT_BadFree[*CheckKind].reset(
new BugType(CheckNames[*CheckKind], "Bad free", "Memory Error"));
@@ -1610,7 +1614,7 @@ void MallocChecker::ReportBadFree(CheckerContext &C, SVal ArgVal,
os << "deallocator";
os << " is ";
- bool Summarized = MR ? SummarizeRegion(os, MR)
+ bool Summarized = MR ? SummarizeRegion(os, MR)
: SummarizeValue(os, ArgVal);
if (Summarized)
os << ", which is not memory allocated by ";
@@ -1626,7 +1630,7 @@ void MallocChecker::ReportBadFree(CheckerContext &C, SVal ArgVal,
}
}
-void MallocChecker::ReportFreeAlloca(CheckerContext &C, SVal ArgVal,
+void MallocChecker::ReportFreeAlloca(CheckerContext &C, SVal ArgVal,
SourceRange Range) const {
Optional<MallocChecker::CheckKind> CheckKind;
@@ -1638,7 +1642,7 @@ void MallocChecker::ReportFreeAlloca(CheckerContext &C, SVal ArgVal,
else
return;
- if (ExplodedNode *N = C.generateSink()) {
+ if (ExplodedNode *N = C.generateErrorNode()) {
if (!BT_FreeAlloca[*CheckKind])
BT_FreeAlloca[*CheckKind].reset(
new BugType(CheckNames[*CheckKind], "Free alloca()", "Memory Error"));
@@ -1652,17 +1656,17 @@ void MallocChecker::ReportFreeAlloca(CheckerContext &C, SVal ArgVal,
}
}
-void MallocChecker::ReportMismatchedDealloc(CheckerContext &C,
+void MallocChecker::ReportMismatchedDealloc(CheckerContext &C,
SourceRange Range,
- const Expr *DeallocExpr,
+ const Expr *DeallocExpr,
const RefState *RS,
- SymbolRef Sym,
+ SymbolRef Sym,
bool OwnershipTransferred) const {
if (!ChecksEnabled[CK_MismatchedDeallocatorChecker])
return;
- if (ExplodedNode *N = C.generateSink()) {
+ if (ExplodedNode *N = C.generateErrorNode()) {
if (!BT_MismatchedDealloc)
BT_MismatchedDealloc.reset(
new BugType(CheckNames[CK_MismatchedDeallocatorChecker],
@@ -1680,7 +1684,7 @@ void MallocChecker::ReportMismatchedDealloc(CheckerContext &C,
if (OwnershipTransferred) {
if (printAllocDeallocName(DeallocOs, C, DeallocExpr))
os << DeallocOs.str() << " cannot";
- else
+ else
os << "Cannot";
os << " take ownership of memory";
@@ -1721,7 +1725,7 @@ void MallocChecker::ReportOffsetFree(CheckerContext &C, SVal ArgVal,
if (!CheckKind.hasValue())
return;
- ExplodedNode *N = C.generateSink();
+ ExplodedNode *N = C.generateErrorNode();
if (!N)
return;
@@ -1775,7 +1779,7 @@ void MallocChecker::ReportUseAfterFree(CheckerContext &C, SourceRange Range,
if (!CheckKind.hasValue())
return;
- if (ExplodedNode *N = C.generateSink()) {
+ if (ExplodedNode *N = C.generateErrorNode()) {
if (!BT_UseFree[*CheckKind])
BT_UseFree[*CheckKind].reset(new BugType(
CheckNames[*CheckKind], "Use-after-free", "Memory Error"));
@@ -1791,7 +1795,7 @@ void MallocChecker::ReportUseAfterFree(CheckerContext &C, SourceRange Range,
}
void MallocChecker::ReportDoubleFree(CheckerContext &C, SourceRange Range,
- bool Released, SymbolRef Sym,
+ bool Released, SymbolRef Sym,
SymbolRef PrevSym) const {
if (!ChecksEnabled[CK_MallocChecker] &&
@@ -1802,7 +1806,7 @@ void MallocChecker::ReportDoubleFree(CheckerContext &C, SourceRange Range,
if (!CheckKind.hasValue())
return;
- if (ExplodedNode *N = C.generateSink()) {
+ if (ExplodedNode *N = C.generateErrorNode()) {
if (!BT_DoubleFree[*CheckKind])
BT_DoubleFree[*CheckKind].reset(
new BugType(CheckNames[*CheckKind], "Double free", "Memory Error"));
@@ -1830,7 +1834,7 @@ void MallocChecker::ReportDoubleDelete(CheckerContext &C, SymbolRef Sym) const {
if (!CheckKind.hasValue())
return;
- if (ExplodedNode *N = C.generateSink()) {
+ if (ExplodedNode *N = C.generateErrorNode()) {
if (!BT_DoubleDelete)
BT_DoubleDelete.reset(new BugType(CheckNames[CK_NewDeleteChecker],
"Double delete", "Memory Error"));
@@ -1857,7 +1861,7 @@ void MallocChecker::ReportUseZeroAllocated(CheckerContext &C,
if (!CheckKind.hasValue())
return;
- if (ExplodedNode *N = C.generateSink()) {
+ if (ExplodedNode *N = C.generateErrorNode()) {
if (!BT_UseZerroAllocated[*CheckKind])
BT_UseZerroAllocated[*CheckKind].reset(new BugType(
CheckNames[*CheckKind], "Use of zero allocated", "Memory Error"));
@@ -1921,7 +1925,7 @@ ProgramStateRef MallocChecker::ReallocMem(CheckerContext &C,
bool PrtIsNull = StatePtrIsNull && !StatePtrNotNull;
bool SizeIsZero = StateSizeIsZero && !StateSizeNotZero;
- // If the ptr is NULL and the size is not 0, the call is equivalent to
+ // If the ptr is NULL and the size is not 0, the call is equivalent to
// malloc(size).
if ( PrtIsNull && !SizeIsZero) {
ProgramStateRef stateMalloc = MallocMemAux(C, CE, CE->getArg(1),
@@ -1930,7 +1934,7 @@ ProgramStateRef MallocChecker::ReallocMem(CheckerContext &C,
}
if (PrtIsNull && SizeIsZero)
- return nullptr;
+ return State;
// Get the from and to pointer symbols as in toPtr = realloc(fromPtr, size).
assert(!PrtIsNull);
@@ -1979,7 +1983,7 @@ ProgramStateRef MallocChecker::ReallocMem(CheckerContext &C,
return nullptr;
}
-ProgramStateRef MallocChecker::CallocMem(CheckerContext &C, const CallExpr *CE,
+ProgramStateRef MallocChecker::CallocMem(CheckerContext &C, const CallExpr *CE,
ProgramStateRef State) {
if (!State)
return nullptr;
@@ -1992,7 +1996,7 @@ ProgramStateRef MallocChecker::CallocMem(CheckerContext &C, const CallExpr *CE,
SVal count = State->getSVal(CE->getArg(0), LCtx);
SVal elementSize = State->getSVal(CE->getArg(1), LCtx);
SVal TotalSize = svalBuilder.evalBinOp(State, BO_Mul, count, elementSize,
- svalBuilder.getContext().getSizeType());
+ svalBuilder.getContext().getSizeType());
SVal zeroVal = svalBuilder.makeZeroVal(svalBuilder.getContext().CharTy);
return MallocMemAux(C, CE, TotalSize, zeroVal, State);
@@ -2079,7 +2083,7 @@ void MallocChecker::reportLeak(SymbolRef Sym, ExplodedNode *N,
const ExplodedNode *AllocNode = nullptr;
const MemRegion *Region = nullptr;
std::tie(AllocNode, Region) = getAllocationSite(N, Sym, C);
-
+
ProgramPoint P = AllocNode->getLocation();
const Stmt *AllocationStmt = nullptr;
if (Optional<CallExitEnd> Exit = P.getAs<CallExitEnd>())
@@ -2128,7 +2132,7 @@ void MallocChecker::checkDeadSymbols(SymbolReaper &SymReaper,
}
}
-
+
// Cleanup the Realloc Pairs Map.
ReallocPairsTy RP = state->get<ReallocPairs>();
for (ReallocPairsTy::iterator I = RP.begin(), E = RP.end(); I != E; ++I) {
@@ -2151,10 +2155,12 @@ void MallocChecker::checkDeadSymbols(SymbolReaper &SymReaper,
ExplodedNode *N = C.getPredecessor();
if (!Errors.empty()) {
static CheckerProgramPointTag Tag("MallocChecker", "DeadSymbolsLeak");
- N = C.addTransition(C.getState(), C.getPredecessor(), &Tag);
- for (SmallVectorImpl<SymbolRef>::iterator
+ N = C.generateNonFatalErrorNode(C.getState(), &Tag);
+ if (N) {
+ for (SmallVectorImpl<SymbolRef>::iterator
I = Errors.begin(), E = Errors.end(); I != E; ++I) {
- reportLeak(*I, N, C);
+ reportLeak(*I, N, C);
+ }
}
}
@@ -2233,7 +2239,7 @@ void MallocChecker::checkPreStmt(const ReturnStmt *S, CheckerContext &C) const {
}
// TODO: Blocks should be either inlined or should call invalidate regions
-// upon invocation. After that's in place, special casing here will not be
+// upon invocation. After that's in place, special casing here will not be
// needed.
void MallocChecker::checkPostStmt(const BlockExpr *BE,
CheckerContext &C) const {
@@ -2292,10 +2298,14 @@ bool MallocChecker::checkUseAfterFree(SymbolRef Sym, CheckerContext &C,
void MallocChecker::checkUseZeroAllocated(SymbolRef Sym, CheckerContext &C,
const Stmt *S) const {
assert(Sym);
- const RefState *RS = C.getState()->get<RegionState>(Sym);
- if (RS && RS->isAllocatedOfSizeZero())
- ReportUseZeroAllocated(C, RS->getStmt()->getSourceRange(), Sym);
+ if (const RefState *RS = C.getState()->get<RegionState>(Sym)) {
+ if (RS->isAllocatedOfSizeZero())
+ ReportUseZeroAllocated(C, RS->getStmt()->getSourceRange(), Sym);
+ }
+ else if (C.getState()->contains<ReallocSizeZeroSymbols>(Sym)) {
+ ReportUseZeroAllocated(C, S->getSourceRange(), Sym);
+ }
}
bool MallocChecker::checkDoubleDelete(SymbolRef Sym, CheckerContext &C) const {
@@ -2377,7 +2387,7 @@ bool MallocChecker::mayFreeAnyEscapedMemoryOrIsModeledExplicitly(
if (const ObjCMethodCall *Msg = dyn_cast<ObjCMethodCall>(Call)) {
// If it's not a framework call, or if it takes a callback, assume it
// can free memory.
- if (!Call->isInSystemHeader() || Call->hasNonZeroCallbackArg())
+ if (!Call->isInSystemHeader() || Call->argumentsMayEscape())
return true;
// If it's a method we know about, handle it explicitly post-call.
@@ -2447,7 +2457,7 @@ bool MallocChecker::mayFreeAnyEscapedMemoryOrIsModeledExplicitly(
StringRef FName = II->getName();
// White list the 'XXXNoCopy' CoreFoundation functions.
- // We specifically check these before
+ // We specifically check these before
if (FName.endswith("NoCopy")) {
// Look for the deallocator argument. We know that the memory ownership
// is not transferred only if the deallocator argument is
@@ -2556,7 +2566,7 @@ ProgramStateRef MallocChecker::checkPointerEscapeAux(ProgramStateRef State,
if (EscapingSymbol && EscapingSymbol != sym)
continue;
-
+
if (const RefState *RS = State->get<RegionState>(sym)) {
if ((RS->isAllocated() || RS->isAllocatedOfSizeZero()) &&
CheckRefState(RS)) {
@@ -2703,7 +2713,7 @@ void ento::registerNewDeleteLeaksChecker(CheckerManager &mgr) {
checker->ChecksEnabled[MallocChecker::CK_NewDeleteLeaksChecker] = true;
checker->CheckNames[MallocChecker::CK_NewDeleteLeaksChecker] =
mgr.getCurrentCheckName();
- // We currently treat NewDeleteLeaks checker as a subchecker of NewDelete
+ // We currently treat NewDeleteLeaks checker as a subchecker of NewDelete
// checker.
if (!checker->ChecksEnabled[MallocChecker::CK_NewDeleteChecker])
checker->ChecksEnabled[MallocChecker::CK_NewDeleteChecker] = true;
diff --git a/lib/StaticAnalyzer/Checkers/MallocOverflowSecurityChecker.cpp b/lib/StaticAnalyzer/Checkers/MallocOverflowSecurityChecker.cpp
index e91347999dc1..99ba90d7a2d9 100644
--- a/lib/StaticAnalyzer/Checkers/MallocOverflowSecurityChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/MallocOverflowSecurityChecker.cpp
@@ -23,19 +23,22 @@
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
+#include "llvm/ADT/APSInt.h"
#include "llvm/ADT/SmallVector.h"
using namespace clang;
using namespace ento;
+using llvm::APInt;
+using llvm::APSInt;
namespace {
struct MallocOverflowCheck {
const BinaryOperator *mulop;
const Expr *variable;
+ APSInt maxVal;
- MallocOverflowCheck (const BinaryOperator *m, const Expr *v)
- : mulop(m), variable (v)
- {}
+ MallocOverflowCheck(const BinaryOperator *m, const Expr *v, APSInt val)
+ : mulop(m), variable(v), maxVal(val) {}
};
class MallocOverflowSecurityChecker : public Checker<check::ASTCodeBody> {
@@ -54,6 +57,11 @@ public:
};
} // end anonymous namespace
+// Return true for computations which evaluate to zero: e.g., mult by 0.
+static inline bool EvaluatesToZero(APSInt &Val, BinaryOperatorKind op) {
+ return (op == BO_Mul) && (Val == 0);
+}
+
void MallocOverflowSecurityChecker::CheckMallocArgument(
SmallVectorImpl<MallocOverflowCheck> &PossibleMallocOverflows,
const Expr *TheArgument,
@@ -64,13 +72,14 @@ void MallocOverflowSecurityChecker::CheckMallocArgument(
Reject anything that applies to the variable: an explicit cast,
conditional expression, an operation that could reduce the range
of the result, or anything too complicated :-). */
- const Expr * e = TheArgument;
+ const Expr *e = TheArgument;
const BinaryOperator * mulop = nullptr;
+ APSInt maxVal;
for (;;) {
+ maxVal = 0;
e = e->IgnoreParenImpCasts();
- if (isa<BinaryOperator>(e)) {
- const BinaryOperator * binop = dyn_cast<BinaryOperator>(e);
+ if (const BinaryOperator *binop = dyn_cast<BinaryOperator>(e)) {
BinaryOperatorKind opc = binop->getOpcode();
// TODO: ignore multiplications by 1, reject if multiplied by 0.
if (mulop == nullptr && opc == BO_Mul)
@@ -80,12 +89,18 @@ void MallocOverflowSecurityChecker::CheckMallocArgument(
const Expr *lhs = binop->getLHS();
const Expr *rhs = binop->getRHS();
- if (rhs->isEvaluatable(Context))
+ if (rhs->isEvaluatable(Context)) {
e = lhs;
- else if ((opc == BO_Add || opc == BO_Mul)
- && lhs->isEvaluatable(Context))
+ maxVal = rhs->EvaluateKnownConstInt(Context);
+ if (EvaluatesToZero(maxVal, opc))
+ return;
+ } else if ((opc == BO_Add || opc == BO_Mul) &&
+ lhs->isEvaluatable(Context)) {
+ maxVal = lhs->EvaluateKnownConstInt(Context);
+ if (EvaluatesToZero(maxVal, opc))
+ return;
e = rhs;
- else
+ } else
return;
}
else if (isa<DeclRefExpr>(e) || isa<MemberExpr>(e))
@@ -103,7 +118,7 @@ void MallocOverflowSecurityChecker::CheckMallocArgument(
// TODO: Could push this into the innermost scope where 'e' is
// defined, rather than the whole function.
- PossibleMallocOverflows.push_back(MallocOverflowCheck(mulop, e));
+ PossibleMallocOverflows.push_back(MallocOverflowCheck(mulop, e, maxVal));
}
namespace {
@@ -126,33 +141,84 @@ private:
return false;
}
- void CheckExpr(const Expr *E_p) {
- const Expr *E = E_p->IgnoreParenImpCasts();
+ const Decl *getDecl(const DeclRefExpr *DR) { return DR->getDecl(); }
+
+ const Decl *getDecl(const MemberExpr *ME) { return ME->getMemberDecl(); }
+ template <typename T1>
+ void Erase(const T1 *DR, std::function<bool(theVecType::iterator)> pred) {
theVecType::iterator i = toScanFor.end();
theVecType::iterator e = toScanFor.begin();
-
- if (const DeclRefExpr *DR = dyn_cast<DeclRefExpr>(E)) {
- const Decl * EdreD = DR->getDecl();
- while (i != e) {
- --i;
- if (const DeclRefExpr *DR_i = dyn_cast<DeclRefExpr>(i->variable)) {
- if (DR_i->getDecl() == EdreD)
- i = toScanFor.erase(i);
- }
+ while (i != e) {
+ --i;
+ if (const T1 *DR_i = dyn_cast<T1>(i->variable)) {
+ if ((getDecl(DR_i) == getDecl(DR)) && pred(i))
+ i = toScanFor.erase(i);
}
}
+ }
+
+ void CheckExpr(const Expr *E_p) {
+ auto PredTrue = [](theVecType::iterator) -> bool { return true; };
+ const Expr *E = E_p->IgnoreParenImpCasts();
+ if (const DeclRefExpr *DR = dyn_cast<DeclRefExpr>(E))
+ Erase<DeclRefExpr>(DR, PredTrue);
else if (const auto *ME = dyn_cast<MemberExpr>(E)) {
- // No points-to analysis, just look at the member
- const Decl *EmeMD = ME->getMemberDecl();
- while (i != e) {
- --i;
- if (const auto *ME_i = dyn_cast<MemberExpr>(i->variable)) {
- if (ME_i->getMemberDecl() == EmeMD)
- i = toScanFor.erase (i);
- }
+ Erase<MemberExpr>(ME, PredTrue);
+ }
+ }
+
+ // Check if the argument to malloc is assigned a value
+ // which cannot cause an overflow.
+ // e.g., malloc (mul * x) and,
+ // case 1: mul = <constant value>
+ // case 2: mul = a/b, where b > x
+ void CheckAssignmentExpr(BinaryOperator *AssignEx) {
+ bool assignKnown = false;
+ bool numeratorKnown = false, denomKnown = false;
+ APSInt denomVal;
+ denomVal = 0;
+
+ // Erase if the multiplicand was assigned a constant value.
+ const Expr *rhs = AssignEx->getRHS();
+ if (rhs->isEvaluatable(Context))
+ assignKnown = true;
+
+ // Discard the report if the multiplicand was assigned a value,
+ // that can never overflow after multiplication. e.g., the assignment
+ // is a division operator and the denominator is > other multiplicand.
+ const Expr *rhse = rhs->IgnoreParenImpCasts();
+ if (const BinaryOperator *BOp = dyn_cast<BinaryOperator>(rhse)) {
+ if (BOp->getOpcode() == BO_Div) {
+ const Expr *denom = BOp->getRHS()->IgnoreParenImpCasts();
+ if (denom->EvaluateAsInt(denomVal, Context))
+ denomKnown = true;
+ const Expr *numerator = BOp->getLHS()->IgnoreParenImpCasts();
+ if (numerator->isEvaluatable(Context))
+ numeratorKnown = true;
}
}
+ if (!assignKnown && !denomKnown)
+ return;
+ auto denomExtVal = denomVal.getExtValue();
+
+ // Ignore negative denominator.
+ if (denomExtVal < 0)
+ return;
+
+ const Expr *lhs = AssignEx->getLHS();
+ const Expr *E = lhs->IgnoreParenImpCasts();
+
+ auto pred = [assignKnown, numeratorKnown,
+ denomExtVal](theVecType::iterator i) {
+ return assignKnown ||
+ (numeratorKnown && (denomExtVal >= i->maxVal.getExtValue()));
+ };
+
+ if (const DeclRefExpr *DR = dyn_cast<DeclRefExpr>(E))
+ Erase<DeclRefExpr>(DR, pred);
+ else if (const auto *ME = dyn_cast<MemberExpr>(E))
+ Erase<MemberExpr>(ME, pred);
}
public:
@@ -162,11 +228,13 @@ private:
const Expr * rhs = E->getRHS();
// Ignore comparisons against zero, since they generally don't
// protect against an overflow.
- if (!isIntZeroExpr(lhs) && ! isIntZeroExpr(rhs)) {
+ if (!isIntZeroExpr(lhs) && !isIntZeroExpr(rhs)) {
CheckExpr(lhs);
CheckExpr(rhs);
}
}
+ if (E->isAssignmentOp())
+ CheckAssignmentExpr(E);
EvaluatedExprVisitor<CheckOverflowOps>::VisitBinaryOperator(E);
}
@@ -243,12 +311,12 @@ void MallocOverflowSecurityChecker::checkASTCodeBody(const Decl *D,
const FunctionDecl *FD = TheCall->getDirectCallee();
if (!FD)
- return;
+ continue;
// Get the name of the callee. If it's a builtin, strip off the prefix.
IdentifierInfo *FnInfo = FD->getIdentifier();
if (!FnInfo)
- return;
+ continue;
if (FnInfo->isStr ("malloc") || FnInfo->isStr ("_MALLOC")) {
if (TheCall->getNumArgs() == 1)
diff --git a/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp b/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp
index fb07484bfcd9..80a3fbe1a409 100644
--- a/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp
@@ -143,20 +143,20 @@ static bool typesCompatible(ASTContext &C, QualType A, QualType B) {
while (true) {
A = A.getCanonicalType();
B = B.getCanonicalType();
-
+
if (A.getTypePtr() == B.getTypePtr())
return true;
-
+
if (const PointerType *ptrA = A->getAs<PointerType>())
if (const PointerType *ptrB = B->getAs<PointerType>()) {
A = ptrA->getPointeeType();
B = ptrB->getPointeeType();
continue;
}
-
+
break;
}
-
+
return false;
}
diff --git a/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp b/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp
index d23708ecbd97..0e7894788c87 100644
--- a/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp
@@ -11,7 +11,7 @@
// about subpar uses of NSAutoreleasePool. Note that while the check itself
// (in its current form) could be written as a flow-insensitive check, in
// can be potentially enhanced in the future with flow-sensitive information.
-// It is also a good example of the CheckerVisitor interface.
+// It is also a good example of the CheckerVisitor interface.
//
//===----------------------------------------------------------------------===//
@@ -48,7 +48,7 @@ void NSAutoreleasePoolChecker::checkPreObjCMessage(const ObjCMethodCall &msg,
const ObjCInterfaceDecl *OD = msg.getReceiverInterface();
if (!OD)
- return;
+ return;
if (!OD->getIdentifier()->isStr("NSAutoreleasePool"))
return;
@@ -62,7 +62,7 @@ void NSAutoreleasePoolChecker::checkPreObjCMessage(const ObjCMethodCall &msg,
BT.reset(new BugType(this, "Use -drain instead of -release",
"API Upgrade (Apple)"));
- ExplodedNode *N = C.addTransition();
+ ExplodedNode *N = C.generateNonFatalErrorNode();
if (!N) {
assert(0);
return;
diff --git a/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp b/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp
index c351c6e9e08b..dab068b27e80 100644
--- a/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp
@@ -58,7 +58,7 @@ void NSErrorMethodChecker::checkASTDecl(const ObjCMethodDecl *D,
return;
if (!II)
- II = &D->getASTContext().Idents.get("NSError");
+ II = &D->getASTContext().Idents.get("NSError");
bool hasNSError = false;
for (const auto *I : D->params()) {
@@ -105,7 +105,7 @@ void CFErrorFunctionChecker::checkASTDecl(const FunctionDecl *D,
return;
if (!II)
- II = &D->getASTContext().Idents.get("CFErrorRef");
+ II = &D->getASTContext().Idents.get("CFErrorRef");
bool hasCFError = false;
for (auto I : D->params()) {
diff --git a/lib/StaticAnalyzer/Checkers/NoReturnFunctionChecker.cpp b/lib/StaticAnalyzer/Checkers/NoReturnFunctionChecker.cpp
index ba82d1d1d41f..c1deadef4202 100644
--- a/lib/StaticAnalyzer/Checkers/NoReturnFunctionChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/NoReturnFunctionChecker.cpp
@@ -66,6 +66,7 @@ void NoReturnFunctionChecker::checkPostCall(const CallEvent &CE,
.Case("assfail", true)
.Case("db_error", true)
.Case("__assert", true)
+ .Case("__assert2", true)
// For the purpose of static analysis, we do not care that
// this MSVC function will return if the user decides to continue.
.Case("_wassert", true)
@@ -81,7 +82,7 @@ void NoReturnFunctionChecker::checkPostCall(const CallEvent &CE,
}
if (BuildSinks)
- C.generateSink();
+ C.generateSink(C.getState(), C.getPredecessor());
}
void NoReturnFunctionChecker::checkPostObjCMessage(const ObjCMethodCall &Msg,
@@ -90,7 +91,7 @@ void NoReturnFunctionChecker::checkPostObjCMessage(const ObjCMethodCall &Msg,
if (const ObjCMethodDecl *MD = Msg.getDecl()) {
MD = MD->getCanonicalDecl();
if (MD->hasAttr<AnalyzerNoReturnAttr>()) {
- C.generateSink();
+ C.generateSink(C.getState(), C.getPredecessor());
return;
}
}
@@ -136,7 +137,7 @@ void NoReturnFunctionChecker::checkPostObjCMessage(const ObjCMethodCall &Msg,
}
// If we got here, it's one of the messages we care about.
- C.generateSink();
+ C.generateSink(C.getState(), C.getPredecessor());
}
void ento::registerNoReturnFunctionChecker(CheckerManager &mgr) {
diff --git a/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp b/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp
index 73f8087fd3c0..1f82ab94af82 100644
--- a/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp
@@ -28,7 +28,7 @@ using namespace ento;
namespace {
class NonNullParamChecker
- : public Checker< check::PreCall > {
+ : public Checker< check::PreCall, EventDispatcher<ImplicitNullDerefEvent> > {
mutable std::unique_ptr<BugType> BTAttrNonNull;
mutable std::unique_ptr<BugType> BTNullRefArg;
@@ -139,26 +139,34 @@ void NonNullParamChecker::checkPreCall(const CallEvent &Call,
ProgramStateRef stateNotNull, stateNull;
std::tie(stateNotNull, stateNull) = CM.assumeDual(state, *DV);
- if (stateNull && !stateNotNull) {
- // Generate an error node. Check for a null node in case
- // we cache out.
- if (ExplodedNode *errorNode = C.generateSink(stateNull)) {
+ if (stateNull) {
+ if (!stateNotNull) {
+ // Generate an error node. Check for a null node in case
+ // we cache out.
+ if (ExplodedNode *errorNode = C.generateErrorNode(stateNull)) {
- std::unique_ptr<BugReport> R;
- if (haveAttrNonNull)
- R = genReportNullAttrNonNull(errorNode, ArgE);
- else if (haveRefTypeParam)
- R = genReportReferenceToNullPointer(errorNode, ArgE);
+ std::unique_ptr<BugReport> R;
+ if (haveAttrNonNull)
+ R = genReportNullAttrNonNull(errorNode, ArgE);
+ else if (haveRefTypeParam)
+ R = genReportReferenceToNullPointer(errorNode, ArgE);
- // Highlight the range of the argument that was null.
- R->addRange(Call.getArgSourceRange(idx));
+ // Highlight the range of the argument that was null.
+ R->addRange(Call.getArgSourceRange(idx));
- // Emit the bug report.
- C.emitReport(std::move(R));
- }
+ // Emit the bug report.
+ C.emitReport(std::move(R));
+ }
- // Always return. Either we cached out or we just emitted an error.
- return;
+ // Always return. Either we cached out or we just emitted an error.
+ return;
+ }
+ if (ExplodedNode *N = C.generateSink(stateNull, C.getPredecessor())) {
+ ImplicitNullDerefEvent event = {
+ V, false, N, &C.getBugReporter(),
+ /*IsDirectDereference=*/haveRefTypeParam};
+ dispatchEvent(event);
+ }
}
// If a pointer value passed the check we should assume that it is
diff --git a/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp b/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp
new file mode 100644
index 000000000000..bb86ea401df5
--- /dev/null
+++ b/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp
@@ -0,0 +1,1066 @@
+//== Nullabilityhecker.cpp - Nullability checker ----------------*- C++ -*--==//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This checker tries to find nullability violations. There are several kinds of
+// possible violations:
+// * Null pointer is passed to a pointer which has a _Nonnull type.
+// * Null pointer is returned from a function which has a _Nonnull return type.
+// * Nullable pointer is passed to a pointer which has a _Nonnull type.
+// * Nullable pointer is returned from a function which has a _Nonnull return
+// type.
+// * Nullable pointer is dereferenced.
+//
+// This checker propagates the nullability information of the pointers and looks
+// for the patterns that are described above. Explicit casts are trusted and are
+// considered a way to suppress false positives for this checker. The other way
+// to suppress warnings would be to add asserts or guarding if statements to the
+// code. In addition to the nullability propagation this checker also uses some
+// heuristics to suppress potential false positives.
+//
+//===----------------------------------------------------------------------===//
+
+#include "ClangSACheckers.h"
+#include "llvm/Support/Path.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/CheckerManager.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
+
+using namespace clang;
+using namespace ento;
+
+namespace {
+// Do not reorder! The getMostNullable method relies on the order.
+// Optimization: Most pointers expected to be unspecified. When a symbol has an
+// unspecified or nonnull type non of the rules would indicate any problem for
+// that symbol. For this reason only nullable and contradicted nullability are
+// stored for a symbol. When a symbol is already contradicted, it can not be
+// casted back to nullable.
+enum class Nullability : char {
+ Contradicted, // Tracked nullability is contradicted by an explicit cast. Do
+ // not report any nullability related issue for this symbol.
+ // This nullability is propagated agressively to avoid false
+ // positive results. See the comment on getMostNullable method.
+ Nullable,
+ Unspecified,
+ Nonnull
+};
+
+/// Returns the most nullable nullability. This is used for message expressions
+/// like [reciever method], where the nullability of this expression is either
+/// the nullability of the receiver or the nullability of the return type of the
+/// method, depending on which is more nullable. Contradicted is considered to
+/// be the most nullable, to avoid false positive results.
+Nullability getMostNullable(Nullability Lhs, Nullability Rhs) {
+ return static_cast<Nullability>(
+ std::min(static_cast<char>(Lhs), static_cast<char>(Rhs)));
+}
+
+const char *getNullabilityString(Nullability Nullab) {
+ switch (Nullab) {
+ case Nullability::Contradicted:
+ return "contradicted";
+ case Nullability::Nullable:
+ return "nullable";
+ case Nullability::Unspecified:
+ return "unspecified";
+ case Nullability::Nonnull:
+ return "nonnull";
+ }
+ llvm_unreachable("Unexpected enumeration.");
+ return "";
+}
+
+// These enums are used as an index to ErrorMessages array.
+enum class ErrorKind : int {
+ NilAssignedToNonnull,
+ NilPassedToNonnull,
+ NilReturnedToNonnull,
+ NullableAssignedToNonnull,
+ NullableReturnedToNonnull,
+ NullableDereferenced,
+ NullablePassedToNonnull
+};
+
+const char *const ErrorMessages[] = {
+ "Null is assigned to a pointer which is expected to have non-null value",
+ "Null passed to a callee that requires a non-null argument",
+ "Null is returned from a function that is expected to return a non-null "
+ "value",
+ "Nullable pointer is assigned to a pointer which is expected to have "
+ "non-null value",
+ "Nullable pointer is returned from a function that is expected to return a "
+ "non-null value",
+ "Nullable pointer is dereferenced",
+ "Nullable pointer is passed to a callee that requires a non-null argument"};
+
+class NullabilityChecker
+ : public Checker<check::Bind, check::PreCall, check::PreStmt<ReturnStmt>,
+ check::PostCall, check::PostStmt<ExplicitCastExpr>,
+ check::PostObjCMessage, check::DeadSymbols,
+ check::Event<ImplicitNullDerefEvent>> {
+ mutable std::unique_ptr<BugType> BT;
+
+public:
+ void checkBind(SVal L, SVal V, const Stmt *S, CheckerContext &C) const;
+ void checkPostStmt(const ExplicitCastExpr *CE, CheckerContext &C) const;
+ void checkPreStmt(const ReturnStmt *S, CheckerContext &C) const;
+ void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;
+ void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
+ void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
+ void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const;
+ void checkEvent(ImplicitNullDerefEvent Event) const;
+
+ void printState(raw_ostream &Out, ProgramStateRef State, const char *NL,
+ const char *Sep) const override;
+
+ struct NullabilityChecksFilter {
+ DefaultBool CheckNullPassedToNonnull;
+ DefaultBool CheckNullReturnedFromNonnull;
+ DefaultBool CheckNullableDereferenced;
+ DefaultBool CheckNullablePassedToNonnull;
+ DefaultBool CheckNullableReturnedFromNonnull;
+
+ CheckName CheckNameNullPassedToNonnull;
+ CheckName CheckNameNullReturnedFromNonnull;
+ CheckName CheckNameNullableDereferenced;
+ CheckName CheckNameNullablePassedToNonnull;
+ CheckName CheckNameNullableReturnedFromNonnull;
+ };
+
+ NullabilityChecksFilter Filter;
+ // When set to false no nullability information will be tracked in
+ // NullabilityMap. It is possible to catch errors like passing a null pointer
+ // to a callee that expects nonnull argument without the information that is
+ // stroed in the NullabilityMap. This is an optimization.
+ DefaultBool NeedTracking;
+
+private:
+ class NullabilityBugVisitor
+ : public BugReporterVisitorImpl<NullabilityBugVisitor> {
+ public:
+ NullabilityBugVisitor(const MemRegion *M) : Region(M) {}
+
+ void Profile(llvm::FoldingSetNodeID &ID) const override {
+ static int X = 0;
+ ID.AddPointer(&X);
+ ID.AddPointer(Region);
+ }
+
+ PathDiagnosticPiece *VisitNode(const ExplodedNode *N,
+ const ExplodedNode *PrevN,
+ BugReporterContext &BRC,
+ BugReport &BR) override;
+
+ private:
+ // The tracked region.
+ const MemRegion *Region;
+ };
+
+ /// When any of the nonnull arguments of the analyzed function is null, do not
+ /// report anything and turn off the check.
+ ///
+ /// When \p SuppressPath is set to true, no more bugs will be reported on this
+ /// path by this checker.
+ void reportBugIfPreconditionHolds(ErrorKind Error, ExplodedNode *N,
+ const MemRegion *Region, CheckerContext &C,
+ const Stmt *ValueExpr = nullptr,
+ bool SuppressPath = false) const;
+
+ void reportBug(ErrorKind Error, ExplodedNode *N, const MemRegion *Region,
+ BugReporter &BR, const Stmt *ValueExpr = nullptr) const {
+ if (!BT)
+ BT.reset(new BugType(this, "Nullability", "Memory error"));
+ const char *Msg = ErrorMessages[static_cast<int>(Error)];
+ std::unique_ptr<BugReport> R(new BugReport(*BT, Msg, N));
+ if (Region) {
+ R->markInteresting(Region);
+ R->addVisitor(llvm::make_unique<NullabilityBugVisitor>(Region));
+ }
+ if (ValueExpr) {
+ R->addRange(ValueExpr->getSourceRange());
+ if (Error == ErrorKind::NilAssignedToNonnull ||
+ Error == ErrorKind::NilPassedToNonnull ||
+ Error == ErrorKind::NilReturnedToNonnull)
+ bugreporter::trackNullOrUndefValue(N, ValueExpr, *R);
+ }
+ BR.emitReport(std::move(R));
+ }
+
+ /// If an SVal wraps a region that should be tracked, it will return a pointer
+ /// to the wrapped region. Otherwise it will return a nullptr.
+ const SymbolicRegion *getTrackRegion(SVal Val,
+ bool CheckSuperRegion = false) const;
+};
+
+class NullabilityState {
+public:
+ NullabilityState(Nullability Nullab, const Stmt *Source = nullptr)
+ : Nullab(Nullab), Source(Source) {}
+
+ const Stmt *getNullabilitySource() const { return Source; }
+
+ Nullability getValue() const { return Nullab; }
+
+ void Profile(llvm::FoldingSetNodeID &ID) const {
+ ID.AddInteger(static_cast<char>(Nullab));
+ ID.AddPointer(Source);
+ }
+
+ void print(raw_ostream &Out) const {
+ Out << getNullabilityString(Nullab) << "\n";
+ }
+
+private:
+ Nullability Nullab;
+ // Source is the expression which determined the nullability. For example in a
+ // message like [nullable nonnull_returning] has nullable nullability, because
+ // the receiver is nullable. Here the receiver will be the source of the
+ // nullability. This is useful information when the diagnostics are generated.
+ const Stmt *Source;
+};
+
+bool operator==(NullabilityState Lhs, NullabilityState Rhs) {
+ return Lhs.getValue() == Rhs.getValue() &&
+ Lhs.getNullabilitySource() == Rhs.getNullabilitySource();
+}
+
+} // end anonymous namespace
+
+REGISTER_MAP_WITH_PROGRAMSTATE(NullabilityMap, const MemRegion *,
+ NullabilityState)
+
+// If the nullability precondition of a function is violated, we should not
+// report nullability related issues on that path. For this reason once a
+// precondition is not met on a path, this checker will be esentially turned off
+// for the rest of the analysis. We do not want to generate a sink node however,
+// so this checker would not lead to reduced coverage.
+REGISTER_TRAIT_WITH_PROGRAMSTATE(PreconditionViolated, bool)
+
+enum class NullConstraint { IsNull, IsNotNull, Unknown };
+
+static NullConstraint getNullConstraint(DefinedOrUnknownSVal Val,
+ ProgramStateRef State) {
+ ConditionTruthVal Nullness = State->isNull(Val);
+ if (Nullness.isConstrainedFalse())
+ return NullConstraint::IsNotNull;
+ if (Nullness.isConstrainedTrue())
+ return NullConstraint::IsNull;
+ return NullConstraint::Unknown;
+}
+
+const SymbolicRegion *
+NullabilityChecker::getTrackRegion(SVal Val, bool CheckSuperRegion) const {
+ if (!NeedTracking)
+ return nullptr;
+
+ auto RegionSVal = Val.getAs<loc::MemRegionVal>();
+ if (!RegionSVal)
+ return nullptr;
+
+ const MemRegion *Region = RegionSVal->getRegion();
+
+ if (CheckSuperRegion) {
+ if (auto FieldReg = Region->getAs<FieldRegion>())
+ return dyn_cast<SymbolicRegion>(FieldReg->getSuperRegion());
+ if (auto ElementReg = Region->getAs<ElementRegion>())
+ return dyn_cast<SymbolicRegion>(ElementReg->getSuperRegion());
+ }
+
+ return dyn_cast<SymbolicRegion>(Region);
+}
+
+PathDiagnosticPiece *NullabilityChecker::NullabilityBugVisitor::VisitNode(
+ const ExplodedNode *N, const ExplodedNode *PrevN, BugReporterContext &BRC,
+ BugReport &BR) {
+ ProgramStateRef State = N->getState();
+ ProgramStateRef StatePrev = PrevN->getState();
+
+ const NullabilityState *TrackedNullab = State->get<NullabilityMap>(Region);
+ const NullabilityState *TrackedNullabPrev =
+ StatePrev->get<NullabilityMap>(Region);
+ if (!TrackedNullab)
+ return nullptr;
+
+ if (TrackedNullabPrev &&
+ TrackedNullabPrev->getValue() == TrackedNullab->getValue())
+ return nullptr;
+
+ // Retrieve the associated statement.
+ const Stmt *S = TrackedNullab->getNullabilitySource();
+ if (!S) {
+ ProgramPoint ProgLoc = N->getLocation();
+ if (Optional<StmtPoint> SP = ProgLoc.getAs<StmtPoint>()) {
+ S = SP->getStmt();
+ }
+ }
+
+ if (!S)
+ return nullptr;
+
+ std::string InfoText =
+ (llvm::Twine("Nullability '") +
+ getNullabilityString(TrackedNullab->getValue()) + "' is infered")
+ .str();
+
+ // Generate the extra diagnostic.
+ PathDiagnosticLocation Pos(S, BRC.getSourceManager(),
+ N->getLocationContext());
+ return new PathDiagnosticEventPiece(Pos, InfoText, true, nullptr);
+}
+
+static Nullability getNullabilityAnnotation(QualType Type) {
+ const auto *AttrType = Type->getAs<AttributedType>();
+ if (!AttrType)
+ return Nullability::Unspecified;
+ if (AttrType->getAttrKind() == AttributedType::attr_nullable)
+ return Nullability::Nullable;
+ else if (AttrType->getAttrKind() == AttributedType::attr_nonnull)
+ return Nullability::Nonnull;
+ return Nullability::Unspecified;
+}
+
+template <typename ParamVarDeclRange>
+static bool
+checkParamsForPreconditionViolation(const ParamVarDeclRange &Params,
+ ProgramStateRef State,
+ const LocationContext *LocCtxt) {
+ for (const auto *ParamDecl : Params) {
+ if (ParamDecl->isParameterPack())
+ break;
+
+ if (getNullabilityAnnotation(ParamDecl->getType()) != Nullability::Nonnull)
+ continue;
+
+ auto RegVal = State->getLValue(ParamDecl, LocCtxt)
+ .template getAs<loc::MemRegionVal>();
+ if (!RegVal)
+ continue;
+
+ auto ParamValue = State->getSVal(RegVal->getRegion())
+ .template getAs<DefinedOrUnknownSVal>();
+ if (!ParamValue)
+ continue;
+
+ if (getNullConstraint(*ParamValue, State) == NullConstraint::IsNull) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool checkPreconditionViolation(ProgramStateRef State, ExplodedNode *N,
+ CheckerContext &C) {
+ if (State->get<PreconditionViolated>())
+ return true;
+
+ const LocationContext *LocCtxt = C.getLocationContext();
+ const Decl *D = LocCtxt->getDecl();
+ if (!D)
+ return false;
+
+ if (const auto *BlockD = dyn_cast<BlockDecl>(D)) {
+ if (checkParamsForPreconditionViolation(BlockD->parameters(), State,
+ LocCtxt)) {
+ if (!N->isSink())
+ C.addTransition(State->set<PreconditionViolated>(true), N);
+ return true;
+ }
+ return false;
+ }
+
+ if (const auto *FuncDecl = dyn_cast<FunctionDecl>(D)) {
+ if (checkParamsForPreconditionViolation(FuncDecl->parameters(), State,
+ LocCtxt)) {
+ if (!N->isSink())
+ C.addTransition(State->set<PreconditionViolated>(true), N);
+ return true;
+ }
+ return false;
+ }
+ return false;
+}
+
+void NullabilityChecker::reportBugIfPreconditionHolds(
+ ErrorKind Error, ExplodedNode *N, const MemRegion *Region,
+ CheckerContext &C, const Stmt *ValueExpr, bool SuppressPath) const {
+ ProgramStateRef OriginalState = N->getState();
+
+ if (checkPreconditionViolation(OriginalState, N, C))
+ return;
+ if (SuppressPath) {
+ OriginalState = OriginalState->set<PreconditionViolated>(true);
+ N = C.addTransition(OriginalState, N);
+ }
+
+ reportBug(Error, N, Region, C.getBugReporter(), ValueExpr);
+}
+
+/// Cleaning up the program state.
+void NullabilityChecker::checkDeadSymbols(SymbolReaper &SR,
+ CheckerContext &C) const {
+ if (!SR.hasDeadSymbols())
+ return;
+
+ ProgramStateRef State = C.getState();
+ NullabilityMapTy Nullabilities = State->get<NullabilityMap>();
+ for (NullabilityMapTy::iterator I = Nullabilities.begin(),
+ E = Nullabilities.end();
+ I != E; ++I) {
+ const auto *Region = I->first->getAs<SymbolicRegion>();
+ assert(Region && "Non-symbolic region is tracked.");
+ if (SR.isDead(Region->getSymbol())) {
+ State = State->remove<NullabilityMap>(I->first);
+ }
+ }
+ // When one of the nonnull arguments are constrained to be null, nullability
+ // preconditions are violated. It is not enough to check this only when we
+ // actually report an error, because at that time interesting symbols might be
+ // reaped.
+ if (checkPreconditionViolation(State, C.getPredecessor(), C))
+ return;
+ C.addTransition(State);
+}
+
+/// This callback triggers when a pointer is dereferenced and the analyzer does
+/// not know anything about the value of that pointer. When that pointer is
+/// nullable, this code emits a warning.
+void NullabilityChecker::checkEvent(ImplicitNullDerefEvent Event) const {
+ if (Event.SinkNode->getState()->get<PreconditionViolated>())
+ return;
+
+ const MemRegion *Region =
+ getTrackRegion(Event.Location, /*CheckSuperregion=*/true);
+ if (!Region)
+ return;
+
+ ProgramStateRef State = Event.SinkNode->getState();
+ const NullabilityState *TrackedNullability =
+ State->get<NullabilityMap>(Region);
+
+ if (!TrackedNullability)
+ return;
+
+ if (Filter.CheckNullableDereferenced &&
+ TrackedNullability->getValue() == Nullability::Nullable) {
+ BugReporter &BR = *Event.BR;
+ // Do not suppress errors on defensive code paths, because dereferencing
+ // a nullable pointer is always an error.
+ if (Event.IsDirectDereference)
+ reportBug(ErrorKind::NullableDereferenced, Event.SinkNode, Region, BR);
+ else
+ reportBug(ErrorKind::NullablePassedToNonnull, Event.SinkNode, Region, BR);
+ }
+}
+
+/// This method check when nullable pointer or null value is returned from a
+/// function that has nonnull return type.
+///
+/// TODO: when nullability preconditons are violated, it is ok to violate the
+/// nullability postconditons (i.e.: when one of the nonnull parameters are null
+/// this check should not report any nullability related issue).
+void NullabilityChecker::checkPreStmt(const ReturnStmt *S,
+ CheckerContext &C) const {
+ auto RetExpr = S->getRetValue();
+ if (!RetExpr)
+ return;
+
+ if (!RetExpr->getType()->isAnyPointerType())
+ return;
+
+ ProgramStateRef State = C.getState();
+ if (State->get<PreconditionViolated>())
+ return;
+
+ auto RetSVal =
+ State->getSVal(S, C.getLocationContext()).getAs<DefinedOrUnknownSVal>();
+ if (!RetSVal)
+ return;
+
+ AnalysisDeclContext *DeclCtxt =
+ C.getLocationContext()->getAnalysisDeclContext();
+ const FunctionType *FuncType = DeclCtxt->getDecl()->getFunctionType();
+ if (!FuncType)
+ return;
+
+ NullConstraint Nullness = getNullConstraint(*RetSVal, State);
+
+ Nullability RequiredNullability =
+ getNullabilityAnnotation(FuncType->getReturnType());
+
+ // If the returned value is null but the type of the expression
+ // generating it is nonnull then we will suppress the diagnostic.
+ // This enables explicit suppression when returning a nil literal in a
+ // function with a _Nonnull return type:
+ // return (NSString * _Nonnull)0;
+ Nullability RetExprTypeLevelNullability =
+ getNullabilityAnnotation(RetExpr->getType());
+
+ if (Filter.CheckNullReturnedFromNonnull &&
+ Nullness == NullConstraint::IsNull &&
+ RetExprTypeLevelNullability != Nullability::Nonnull &&
+ RequiredNullability == Nullability::Nonnull) {
+ static CheckerProgramPointTag Tag(this, "NullReturnedFromNonnull");
+ ExplodedNode *N = C.generateErrorNode(State, &Tag);
+ if (!N)
+ return;
+ reportBugIfPreconditionHolds(ErrorKind::NilReturnedToNonnull, N, nullptr, C,
+ RetExpr);
+ return;
+ }
+
+ const MemRegion *Region = getTrackRegion(*RetSVal);
+ if (!Region)
+ return;
+
+ const NullabilityState *TrackedNullability =
+ State->get<NullabilityMap>(Region);
+ if (TrackedNullability) {
+ Nullability TrackedNullabValue = TrackedNullability->getValue();
+ if (Filter.CheckNullableReturnedFromNonnull &&
+ Nullness != NullConstraint::IsNotNull &&
+ TrackedNullabValue == Nullability::Nullable &&
+ RequiredNullability == Nullability::Nonnull) {
+ static CheckerProgramPointTag Tag(this, "NullableReturnedFromNonnull");
+ ExplodedNode *N = C.addTransition(State, C.getPredecessor(), &Tag);
+ reportBugIfPreconditionHolds(ErrorKind::NullableReturnedToNonnull, N,
+ Region, C);
+ }
+ return;
+ }
+ if (RequiredNullability == Nullability::Nullable) {
+ State = State->set<NullabilityMap>(Region,
+ NullabilityState(RequiredNullability,
+ S));
+ C.addTransition(State);
+ }
+}
+
+/// This callback warns when a nullable pointer or a null value is passed to a
+/// function that expects its argument to be nonnull.
+void NullabilityChecker::checkPreCall(const CallEvent &Call,
+ CheckerContext &C) const {
+ if (!Call.getDecl())
+ return;
+
+ ProgramStateRef State = C.getState();
+ if (State->get<PreconditionViolated>())
+ return;
+
+ ProgramStateRef OrigState = State;
+
+ unsigned Idx = 0;
+ for (const ParmVarDecl *Param : Call.parameters()) {
+ if (Param->isParameterPack())
+ break;
+
+ const Expr *ArgExpr = nullptr;
+ if (Idx < Call.getNumArgs())
+ ArgExpr = Call.getArgExpr(Idx);
+ auto ArgSVal = Call.getArgSVal(Idx++).getAs<DefinedOrUnknownSVal>();
+ if (!ArgSVal)
+ continue;
+
+ if (!Param->getType()->isAnyPointerType() &&
+ !Param->getType()->isReferenceType())
+ continue;
+
+ NullConstraint Nullness = getNullConstraint(*ArgSVal, State);
+
+ Nullability RequiredNullability =
+ getNullabilityAnnotation(Param->getType());
+ Nullability ArgExprTypeLevelNullability =
+ getNullabilityAnnotation(ArgExpr->getType());
+
+ if (Filter.CheckNullPassedToNonnull && Nullness == NullConstraint::IsNull &&
+ ArgExprTypeLevelNullability != Nullability::Nonnull &&
+ RequiredNullability == Nullability::Nonnull) {
+ ExplodedNode *N = C.generateErrorNode(State);
+ if (!N)
+ return;
+ reportBugIfPreconditionHolds(ErrorKind::NilPassedToNonnull, N, nullptr, C,
+ ArgExpr);
+ return;
+ }
+
+ const MemRegion *Region = getTrackRegion(*ArgSVal);
+ if (!Region)
+ continue;
+
+ const NullabilityState *TrackedNullability =
+ State->get<NullabilityMap>(Region);
+
+ if (TrackedNullability) {
+ if (Nullness == NullConstraint::IsNotNull ||
+ TrackedNullability->getValue() != Nullability::Nullable)
+ continue;
+
+ if (Filter.CheckNullablePassedToNonnull &&
+ RequiredNullability == Nullability::Nonnull) {
+ ExplodedNode *N = C.addTransition(State);
+ reportBugIfPreconditionHolds(ErrorKind::NullablePassedToNonnull, N,
+ Region, C, ArgExpr, /*SuppressPath=*/true);
+ return;
+ }
+ if (Filter.CheckNullableDereferenced &&
+ Param->getType()->isReferenceType()) {
+ ExplodedNode *N = C.addTransition(State);
+ reportBugIfPreconditionHolds(ErrorKind::NullableDereferenced, N, Region,
+ C, ArgExpr, /*SuppressPath=*/true);
+ return;
+ }
+ continue;
+ }
+ // No tracked nullability yet.
+ if (ArgExprTypeLevelNullability != Nullability::Nullable)
+ continue;
+ State = State->set<NullabilityMap>(
+ Region, NullabilityState(ArgExprTypeLevelNullability, ArgExpr));
+ }
+ if (State != OrigState)
+ C.addTransition(State);
+}
+
+/// Suppress the nullability warnings for some functions.
+void NullabilityChecker::checkPostCall(const CallEvent &Call,
+ CheckerContext &C) const {
+ auto Decl = Call.getDecl();
+ if (!Decl)
+ return;
+ // ObjC Messages handles in a different callback.
+ if (Call.getKind() == CE_ObjCMessage)
+ return;
+ const FunctionType *FuncType = Decl->getFunctionType();
+ if (!FuncType)
+ return;
+ QualType ReturnType = FuncType->getReturnType();
+ if (!ReturnType->isAnyPointerType())
+ return;
+ ProgramStateRef State = C.getState();
+ if (State->get<PreconditionViolated>())
+ return;
+
+ const MemRegion *Region = getTrackRegion(Call.getReturnValue());
+ if (!Region)
+ return;
+
+ // CG headers are misannotated. Do not warn for symbols that are the results
+ // of CG calls.
+ const SourceManager &SM = C.getSourceManager();
+ StringRef FilePath = SM.getFilename(SM.getSpellingLoc(Decl->getLocStart()));
+ if (llvm::sys::path::filename(FilePath).startswith("CG")) {
+ State = State->set<NullabilityMap>(Region, Nullability::Contradicted);
+ C.addTransition(State);
+ return;
+ }
+
+ const NullabilityState *TrackedNullability =
+ State->get<NullabilityMap>(Region);
+
+ if (!TrackedNullability &&
+ getNullabilityAnnotation(ReturnType) == Nullability::Nullable) {
+ State = State->set<NullabilityMap>(Region, Nullability::Nullable);
+ C.addTransition(State);
+ }
+}
+
+static Nullability getReceiverNullability(const ObjCMethodCall &M,
+ ProgramStateRef State) {
+ if (M.isReceiverSelfOrSuper()) {
+ // For super and super class receivers we assume that the receiver is
+ // nonnull.
+ return Nullability::Nonnull;
+ }
+ // Otherwise look up nullability in the state.
+ SVal Receiver = M.getReceiverSVal();
+ if (auto DefOrUnknown = Receiver.getAs<DefinedOrUnknownSVal>()) {
+ // If the receiver is constrained to be nonnull, assume that it is nonnull
+ // regardless of its type.
+ NullConstraint Nullness = getNullConstraint(*DefOrUnknown, State);
+ if (Nullness == NullConstraint::IsNotNull)
+ return Nullability::Nonnull;
+ }
+ auto ValueRegionSVal = Receiver.getAs<loc::MemRegionVal>();
+ if (ValueRegionSVal) {
+ const MemRegion *SelfRegion = ValueRegionSVal->getRegion();
+ assert(SelfRegion);
+
+ const NullabilityState *TrackedSelfNullability =
+ State->get<NullabilityMap>(SelfRegion);
+ if (TrackedSelfNullability)
+ return TrackedSelfNullability->getValue();
+ }
+ return Nullability::Unspecified;
+}
+
+/// Calculate the nullability of the result of a message expr based on the
+/// nullability of the receiver, the nullability of the return value, and the
+/// constraints.
+void NullabilityChecker::checkPostObjCMessage(const ObjCMethodCall &M,
+ CheckerContext &C) const {
+ auto Decl = M.getDecl();
+ if (!Decl)
+ return;
+ QualType RetType = Decl->getReturnType();
+ if (!RetType->isAnyPointerType())
+ return;
+
+ ProgramStateRef State = C.getState();
+ if (State->get<PreconditionViolated>())
+ return;
+
+ const MemRegion *ReturnRegion = getTrackRegion(M.getReturnValue());
+ if (!ReturnRegion)
+ return;
+
+ auto Interface = Decl->getClassInterface();
+ auto Name = Interface ? Interface->getName() : "";
+ // In order to reduce the noise in the diagnostics generated by this checker,
+ // some framework and programming style based heuristics are used. These
+ // heuristics are for Cocoa APIs which have NS prefix.
+ if (Name.startswith("NS")) {
+ // Developers rely on dynamic invariants such as an item should be available
+ // in a collection, or a collection is not empty often. Those invariants can
+ // not be inferred by any static analysis tool. To not to bother the users
+ // with too many false positives, every item retrieval function should be
+ // ignored for collections. The instance methods of dictionaries in Cocoa
+ // are either item retrieval related or not interesting nullability wise.
+ // Using this fact, to keep the code easier to read just ignore the return
+ // value of every instance method of dictionaries.
+ if (M.isInstanceMessage() && Name.find("Dictionary") != StringRef::npos) {
+ State =
+ State->set<NullabilityMap>(ReturnRegion, Nullability::Contradicted);
+ C.addTransition(State);
+ return;
+ }
+ // For similar reasons ignore some methods of Cocoa arrays.
+ StringRef FirstSelectorSlot = M.getSelector().getNameForSlot(0);
+ if (Name.find("Array") != StringRef::npos &&
+ (FirstSelectorSlot == "firstObject" ||
+ FirstSelectorSlot == "lastObject")) {
+ State =
+ State->set<NullabilityMap>(ReturnRegion, Nullability::Contradicted);
+ C.addTransition(State);
+ return;
+ }
+
+ // Encoding related methods of string should not fail when lossless
+ // encodings are used. Using lossless encodings is so frequent that ignoring
+ // this class of methods reduced the emitted diagnostics by about 30% on
+ // some projects (and all of that was false positives).
+ if (Name.find("String") != StringRef::npos) {
+ for (auto Param : M.parameters()) {
+ if (Param->getName() == "encoding") {
+ State = State->set<NullabilityMap>(ReturnRegion,
+ Nullability::Contradicted);
+ C.addTransition(State);
+ return;
+ }
+ }
+ }
+ }
+
+ const ObjCMessageExpr *Message = M.getOriginExpr();
+ Nullability SelfNullability = getReceiverNullability(M, State);
+
+ const NullabilityState *NullabilityOfReturn =
+ State->get<NullabilityMap>(ReturnRegion);
+
+ if (NullabilityOfReturn) {
+ // When we have a nullability tracked for the return value, the nullability
+ // of the expression will be the most nullable of the receiver and the
+ // return value.
+ Nullability RetValTracked = NullabilityOfReturn->getValue();
+ Nullability ComputedNullab =
+ getMostNullable(RetValTracked, SelfNullability);
+ if (ComputedNullab != RetValTracked &&
+ ComputedNullab != Nullability::Unspecified) {
+ const Stmt *NullabilitySource =
+ ComputedNullab == RetValTracked
+ ? NullabilityOfReturn->getNullabilitySource()
+ : Message->getInstanceReceiver();
+ State = State->set<NullabilityMap>(
+ ReturnRegion, NullabilityState(ComputedNullab, NullabilitySource));
+ C.addTransition(State);
+ }
+ return;
+ }
+
+ // No tracked information. Use static type information for return value.
+ Nullability RetNullability = getNullabilityAnnotation(RetType);
+
+ // Properties might be computed. For this reason the static analyzer creates a
+ // new symbol each time an unknown property is read. To avoid false pozitives
+ // do not treat unknown properties as nullable, even when they explicitly
+ // marked nullable.
+ if (M.getMessageKind() == OCM_PropertyAccess && !C.wasInlined)
+ RetNullability = Nullability::Nonnull;
+
+ Nullability ComputedNullab = getMostNullable(RetNullability, SelfNullability);
+ if (ComputedNullab == Nullability::Nullable) {
+ const Stmt *NullabilitySource = ComputedNullab == RetNullability
+ ? Message
+ : Message->getInstanceReceiver();
+ State = State->set<NullabilityMap>(
+ ReturnRegion, NullabilityState(ComputedNullab, NullabilitySource));
+ C.addTransition(State);
+ }
+}
+
+/// Explicit casts are trusted. If there is a disagreement in the nullability
+/// annotations in the destination and the source or '0' is casted to nonnull
+/// track the value as having contraditory nullability. This will allow users to
+/// suppress warnings.
+void NullabilityChecker::checkPostStmt(const ExplicitCastExpr *CE,
+ CheckerContext &C) const {
+ QualType OriginType = CE->getSubExpr()->getType();
+ QualType DestType = CE->getType();
+ if (!OriginType->isAnyPointerType())
+ return;
+ if (!DestType->isAnyPointerType())
+ return;
+
+ ProgramStateRef State = C.getState();
+ if (State->get<PreconditionViolated>())
+ return;
+
+ Nullability DestNullability = getNullabilityAnnotation(DestType);
+
+ // No explicit nullability in the destination type, so this cast does not
+ // change the nullability.
+ if (DestNullability == Nullability::Unspecified)
+ return;
+
+ auto RegionSVal =
+ State->getSVal(CE, C.getLocationContext()).getAs<DefinedOrUnknownSVal>();
+ const MemRegion *Region = getTrackRegion(*RegionSVal);
+ if (!Region)
+ return;
+
+ // When 0 is converted to nonnull mark it as contradicted.
+ if (DestNullability == Nullability::Nonnull) {
+ NullConstraint Nullness = getNullConstraint(*RegionSVal, State);
+ if (Nullness == NullConstraint::IsNull) {
+ State = State->set<NullabilityMap>(Region, Nullability::Contradicted);
+ C.addTransition(State);
+ return;
+ }
+ }
+
+ const NullabilityState *TrackedNullability =
+ State->get<NullabilityMap>(Region);
+
+ if (!TrackedNullability) {
+ if (DestNullability != Nullability::Nullable)
+ return;
+ State = State->set<NullabilityMap>(Region,
+ NullabilityState(DestNullability, CE));
+ C.addTransition(State);
+ return;
+ }
+
+ if (TrackedNullability->getValue() != DestNullability &&
+ TrackedNullability->getValue() != Nullability::Contradicted) {
+ State = State->set<NullabilityMap>(Region, Nullability::Contradicted);
+ C.addTransition(State);
+ }
+}
+
+/// For a given statement performing a bind, attempt to syntactically
+/// match the expression resulting in the bound value.
+static const Expr * matchValueExprForBind(const Stmt *S) {
+ // For `x = e` the value expression is the right-hand side.
+ if (auto *BinOp = dyn_cast<BinaryOperator>(S)) {
+ if (BinOp->getOpcode() == BO_Assign)
+ return BinOp->getRHS();
+ }
+
+ // For `int x = e` the value expression is the initializer.
+ if (auto *DS = dyn_cast<DeclStmt>(S)) {
+ if (DS->isSingleDecl()) {
+ auto *VD = dyn_cast<VarDecl>(DS->getSingleDecl());
+ if (!VD)
+ return nullptr;
+
+ if (const Expr *Init = VD->getInit())
+ return Init;
+ }
+ }
+
+ return nullptr;
+}
+
+/// Returns true if \param S is a DeclStmt for a local variable that
+/// ObjC automated reference counting initialized with zero.
+static bool isARCNilInitializedLocal(CheckerContext &C, const Stmt *S) {
+ // We suppress diagnostics for ARC zero-initialized _Nonnull locals. This
+ // prevents false positives when a _Nonnull local variable cannot be
+ // initialized with an initialization expression:
+ // NSString * _Nonnull s; // no-warning
+ // @autoreleasepool {
+ // s = ...
+ // }
+ //
+ // FIXME: We should treat implicitly zero-initialized _Nonnull locals as
+ // uninitialized in Sema's UninitializedValues analysis to warn when a use of
+ // the zero-initialized definition will unexpectedly yield nil.
+
+ // Locals are only zero-initialized when automated reference counting
+ // is turned on.
+ if (!C.getASTContext().getLangOpts().ObjCAutoRefCount)
+ return false;
+
+ auto *DS = dyn_cast<DeclStmt>(S);
+ if (!DS || !DS->isSingleDecl())
+ return false;
+
+ auto *VD = dyn_cast<VarDecl>(DS->getSingleDecl());
+ if (!VD)
+ return false;
+
+ // Sema only zero-initializes locals with ObjCLifetimes.
+ if(!VD->getType().getQualifiers().hasObjCLifetime())
+ return false;
+
+ const Expr *Init = VD->getInit();
+ assert(Init && "ObjC local under ARC without initializer");
+
+ // Return false if the local is explicitly initialized (e.g., with '= nil').
+ if (!isa<ImplicitValueInitExpr>(Init))
+ return false;
+
+ return true;
+}
+
+/// Propagate the nullability information through binds and warn when nullable
+/// pointer or null symbol is assigned to a pointer with a nonnull type.
+void NullabilityChecker::checkBind(SVal L, SVal V, const Stmt *S,
+ CheckerContext &C) const {
+ const TypedValueRegion *TVR =
+ dyn_cast_or_null<TypedValueRegion>(L.getAsRegion());
+ if (!TVR)
+ return;
+
+ QualType LocType = TVR->getValueType();
+ if (!LocType->isAnyPointerType())
+ return;
+
+ ProgramStateRef State = C.getState();
+ if (State->get<PreconditionViolated>())
+ return;
+
+ auto ValDefOrUnknown = V.getAs<DefinedOrUnknownSVal>();
+ if (!ValDefOrUnknown)
+ return;
+
+ NullConstraint RhsNullness = getNullConstraint(*ValDefOrUnknown, State);
+
+ Nullability ValNullability = Nullability::Unspecified;
+ if (SymbolRef Sym = ValDefOrUnknown->getAsSymbol())
+ ValNullability = getNullabilityAnnotation(Sym->getType());
+
+ Nullability LocNullability = getNullabilityAnnotation(LocType);
+ if (Filter.CheckNullPassedToNonnull &&
+ RhsNullness == NullConstraint::IsNull &&
+ ValNullability != Nullability::Nonnull &&
+ LocNullability == Nullability::Nonnull &&
+ !isARCNilInitializedLocal(C, S)) {
+ static CheckerProgramPointTag Tag(this, "NullPassedToNonnull");
+ ExplodedNode *N = C.generateErrorNode(State, &Tag);
+ if (!N)
+ return;
+
+ const Stmt *ValueExpr = matchValueExprForBind(S);
+ if (!ValueExpr)
+ ValueExpr = S;
+
+ reportBugIfPreconditionHolds(ErrorKind::NilAssignedToNonnull, N, nullptr, C,
+ ValueExpr);
+ return;
+ }
+ // Intentionally missing case: '0' is bound to a reference. It is handled by
+ // the DereferenceChecker.
+
+ const MemRegion *ValueRegion = getTrackRegion(*ValDefOrUnknown);
+ if (!ValueRegion)
+ return;
+
+ const NullabilityState *TrackedNullability =
+ State->get<NullabilityMap>(ValueRegion);
+
+ if (TrackedNullability) {
+ if (RhsNullness == NullConstraint::IsNotNull ||
+ TrackedNullability->getValue() != Nullability::Nullable)
+ return;
+ if (Filter.CheckNullablePassedToNonnull &&
+ LocNullability == Nullability::Nonnull) {
+ static CheckerProgramPointTag Tag(this, "NullablePassedToNonnull");
+ ExplodedNode *N = C.addTransition(State, C.getPredecessor(), &Tag);
+ reportBugIfPreconditionHolds(ErrorKind::NullableAssignedToNonnull, N,
+ ValueRegion, C);
+ }
+ return;
+ }
+
+ const auto *BinOp = dyn_cast<BinaryOperator>(S);
+
+ if (ValNullability == Nullability::Nullable) {
+ // Trust the static information of the value more than the static
+ // information on the location.
+ const Stmt *NullabilitySource = BinOp ? BinOp->getRHS() : S;
+ State = State->set<NullabilityMap>(
+ ValueRegion, NullabilityState(ValNullability, NullabilitySource));
+ C.addTransition(State);
+ return;
+ }
+
+ if (LocNullability == Nullability::Nullable) {
+ const Stmt *NullabilitySource = BinOp ? BinOp->getLHS() : S;
+ State = State->set<NullabilityMap>(
+ ValueRegion, NullabilityState(LocNullability, NullabilitySource));
+ C.addTransition(State);
+ }
+}
+
+void NullabilityChecker::printState(raw_ostream &Out, ProgramStateRef State,
+ const char *NL, const char *Sep) const {
+
+ NullabilityMapTy B = State->get<NullabilityMap>();
+
+ if (B.isEmpty())
+ return;
+
+ Out << Sep << NL;
+
+ for (NullabilityMapTy::iterator I = B.begin(), E = B.end(); I != E; ++I) {
+ Out << I->first << " : ";
+ I->second.print(Out);
+ Out << NL;
+ }
+}
+
+#define REGISTER_CHECKER(name, trackingRequired) \
+ void ento::register##name##Checker(CheckerManager &mgr) { \
+ NullabilityChecker *checker = mgr.registerChecker<NullabilityChecker>(); \
+ checker->Filter.Check##name = true; \
+ checker->Filter.CheckName##name = mgr.getCurrentCheckName(); \
+ checker->NeedTracking = checker->NeedTracking || trackingRequired; \
+ }
+
+// The checks are likely to be turned on by default and it is possible to do
+// them without tracking any nullability related information. As an optimization
+// no nullability information will be tracked when only these two checks are
+// enables.
+REGISTER_CHECKER(NullPassedToNonnull, false)
+REGISTER_CHECKER(NullReturnedFromNonnull, false)
+
+REGISTER_CHECKER(NullableDereferenced, true)
+REGISTER_CHECKER(NullablePassedToNonnull, true)
+REGISTER_CHECKER(NullableReturnedFromNonnull, true)
diff --git a/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp
index a7b92b4c67f2..cbaa5c23592d 100644
--- a/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp
@@ -43,7 +43,7 @@ void ObjCAtSyncChecker::checkPreStmt(const ObjCAtSynchronizedStmt *S,
// Uninitialized value used for the mutex?
if (V.getAs<UndefinedVal>()) {
- if (ExplodedNode *N = C.generateSink()) {
+ if (ExplodedNode *N = C.generateErrorNode()) {
if (!BT_undef)
BT_undef.reset(new BuiltinBug(this, "Uninitialized value used as mutex "
"for @synchronized"));
@@ -66,7 +66,7 @@ void ObjCAtSyncChecker::checkPreStmt(const ObjCAtSynchronizedStmt *S,
if (!notNullState) {
// Generate an error node. This isn't a sink since
// a null mutex just means no synchronization occurs.
- if (ExplodedNode *N = C.addTransition(nullState)) {
+ if (ExplodedNode *N = C.generateNonFatalErrorNode(nullState)) {
if (!BT_null)
BT_null.reset(new BuiltinBug(
this, "Nil value used as mutex for @synchronized() "
diff --git a/lib/StaticAnalyzer/Checkers/ObjCContainersASTChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCContainersASTChecker.cpp
index 224251beb09a..b10ec848ee46 100644
--- a/lib/StaticAnalyzer/Checkers/ObjCContainersASTChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/ObjCContainersASTChecker.cpp
@@ -66,9 +66,8 @@ class WalkAST : public StmtVisitor<WalkAST> {
// The type must be an array/pointer type.
// This could be a null constant, which is allowed.
- if (E->isNullPointerConstant(ASTC, Expr::NPC_ValueDependentIsNull))
- return true;
- return false;
+ return static_cast<bool>(
+ E->isNullPointerConstant(ASTC, Expr::NPC_ValueDependentIsNull));
}
public:
diff --git a/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp
index 53e159879eb2..0203d79cd00e 100644
--- a/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp
@@ -133,13 +133,13 @@ void ObjCContainersChecker::checkPreStmt(const CallExpr *CE,
if (IdxVal.isUnknownOrUndef())
return;
DefinedSVal Idx = IdxVal.castAs<DefinedSVal>();
-
+
// Now, check if 'Idx in [0, Size-1]'.
const QualType T = IdxExpr->getType();
ProgramStateRef StInBound = State->assumeInBound(Idx, *Size, true, T);
ProgramStateRef StOutBound = State->assumeInBound(Idx, *Size, false, T);
if (StOutBound && !StInBound) {
- ExplodedNode *N = C.generateSink(StOutBound);
+ ExplodedNode *N = C.generateErrorNode(StOutBound);
if (!N)
return;
initBugType();
diff --git a/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp
index 016cb146f84e..32a1adb587bf 100644
--- a/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp
@@ -49,7 +49,7 @@ public:
DoesCallSuper = true;
// Recurse if we didn't find the super call yet.
- return !DoesCallSuper;
+ return !DoesCallSuper;
}
bool DoesCallSuper;
@@ -59,7 +59,7 @@ private:
};
//===----------------------------------------------------------------------===//
-// ObjCSuperCallChecker
+// ObjCSuperCallChecker
//===----------------------------------------------------------------------===//
class ObjCSuperCallChecker : public Checker<
@@ -88,7 +88,7 @@ private:
/// \param[out] SuperclassName On return, the found superclass name.
bool ObjCSuperCallChecker::isCheckableClass(const ObjCImplementationDecl *D,
StringRef &SuperclassName) const {
- const ObjCInterfaceDecl *ID = D->getClassInterface();
+ const ObjCInterfaceDecl *ID = D->getClassInterface()->getSuperClass();
for ( ; ID ; ID = ID->getSuperClass())
{
SuperclassName = ID->getIdentifier()->getName();
@@ -202,7 +202,7 @@ void ObjCSuperCallChecker::checkASTDecl(const ObjCImplementationDecl *D,
SmallString<320> Buf;
llvm::raw_svector_ostream os(Buf);
- os << "The '" << S.getAsString()
+ os << "The '" << S.getAsString()
<< "' instance method in " << SuperclassName.str() << " subclass '"
<< *D << "' is missing a [super " << S.getAsString() << "] call";
diff --git a/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp
index 93b0553b3b72..ffa3a2700616 100644
--- a/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp
@@ -145,15 +145,15 @@ void ObjCSelfInitChecker::checkForInvalidSelf(const Expr *E, CheckerContext &C,
const char *errorStr) const {
if (!E)
return;
-
+
if (!C.getState()->get<CalledInit>())
return;
-
+
if (!isInvalidSelf(E, C))
return;
-
+
// Generate an error node.
- ExplodedNode *N = C.generateSink();
+ ExplodedNode *N = C.generateErrorNode();
if (!N)
return;
@@ -177,12 +177,12 @@ void ObjCSelfInitChecker::checkPostObjCMessage(const ObjCMethodCall &Msg,
if (isInitMessage(Msg)) {
// Tag the return value as the result of an initializer.
ProgramStateRef state = C.getState();
-
+
// FIXME this really should be context sensitive, where we record
// the current stack frame (for IPA). Also, we need to clean this
// value out when we return from this method.
state = state->set<CalledInit>(true);
-
+
SVal V = state->getSVal(Msg.getOriginExpr(), C.getLocationContext());
addSelfFlag(state, V, SelfFlag_InitRes, C);
return;
@@ -318,7 +318,7 @@ void ObjCSelfInitChecker::checkBind(SVal loc, SVal val, const Stmt *S,
CheckerContext &C) const {
// Allow assignment of anything to self. Self is a local variable in the
// initializer, so it is legal to assign anything to it, like results of
- // static functions/method calls. After self is assigned something we cannot
+ // static functions/method calls. After self is assigned something we cannot
// reason about, stop enforcing the rules.
// (Only continue checking if the assigned value should be treated as self.)
if ((isSelfVar(loc, C)) &&
@@ -404,15 +404,12 @@ static bool shouldRunOnFunctionOrMethod(const NamedDecl *ND) {
if (II == NSObjectII)
break;
}
- if (!ID)
- return false;
-
- return true;
+ return ID != nullptr;
}
/// \brief Returns true if the location is 'self'.
static bool isSelfVar(SVal location, CheckerContext &C) {
- AnalysisDeclContext *analCtx = C.getCurrentAnalysisDeclContext();
+ AnalysisDeclContext *analCtx = C.getCurrentAnalysisDeclContext();
if (!analCtx->getSelfDecl())
return false;
if (!location.getAs<loc::MemRegionVal>())
diff --git a/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp b/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp
new file mode 100644
index 000000000000..8ce37357fe1f
--- /dev/null
+++ b/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp
@@ -0,0 +1,314 @@
+//=======- PaddingChecker.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 checks for padding that could be
+// removed by re-ordering members.
+//
+//===----------------------------------------------------------------------===//
+
+#include "ClangSACheckers.h"
+#include "clang/AST/CharUnits.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/RecordLayout.h"
+#include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/Support/MathExtras.h"
+#include "llvm/Support/raw_ostream.h"
+#include <numeric>
+
+using namespace clang;
+using namespace ento;
+
+namespace {
+class PaddingChecker : public Checker<check::ASTDecl<TranslationUnitDecl>> {
+private:
+ mutable std::unique_ptr<BugType> PaddingBug;
+ mutable int64_t AllowedPad;
+ mutable BugReporter *BR;
+
+public:
+ void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR,
+ BugReporter &BRArg) const {
+ BR = &BRArg;
+ AllowedPad =
+ MGR.getAnalyzerOptions().getOptionAsInteger("AllowedPad", 24, this);
+ assert(AllowedPad >= 0 && "AllowedPad option should be non-negative");
+
+ // The calls to checkAST* from AnalysisConsumer don't
+ // visit template instantiations or lambda classes. We
+ // want to visit those, so we make our own RecursiveASTVisitor.
+ struct LocalVisitor : public RecursiveASTVisitor<LocalVisitor> {
+ const PaddingChecker *Checker;
+ bool shouldVisitTemplateInstantiations() const { return true; }
+ bool shouldVisitImplicitCode() const { return true; }
+ explicit LocalVisitor(const PaddingChecker *Checker) : Checker(Checker) {}
+ bool VisitRecordDecl(const RecordDecl *RD) {
+ Checker->visitRecord(RD);
+ return true;
+ }
+ bool VisitVarDecl(const VarDecl *VD) {
+ Checker->visitVariable(VD);
+ return true;
+ }
+ // TODO: Visit array new and mallocs for arrays.
+ };
+
+ LocalVisitor visitor(this);
+ visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD));
+ }
+
+ /// \brief Look for records of overly padded types. If padding *
+ /// PadMultiplier exceeds AllowedPad, then generate a report.
+ /// PadMultiplier is used to share code with the array padding
+ /// checker.
+ void visitRecord(const RecordDecl *RD, uint64_t PadMultiplier = 1) const {
+ if (shouldSkipDecl(RD))
+ return;
+
+ auto &ASTContext = RD->getASTContext();
+ const ASTRecordLayout &RL = ASTContext.getASTRecordLayout(RD);
+ assert(llvm::isPowerOf2_64(RL.getAlignment().getQuantity()));
+
+ CharUnits BaselinePad = calculateBaselinePad(RD, ASTContext, RL);
+ if (BaselinePad.isZero())
+ return;
+ CharUnits OptimalPad = calculateOptimalPad(RD, ASTContext, RL);
+
+ CharUnits DiffPad = PadMultiplier * (BaselinePad - OptimalPad);
+ if (DiffPad.getQuantity() <= AllowedPad) {
+ assert(!DiffPad.isNegative() && "DiffPad should not be negative");
+ // There is not enough excess padding to trigger a warning.
+ return;
+ }
+ reportRecord(RD, BaselinePad, OptimalPad);
+ }
+
+ /// \brief Look for arrays of overly padded types. If the padding of the
+ /// array type exceeds AllowedPad, then generate a report.
+ void visitVariable(const VarDecl *VD) const {
+ const ArrayType *ArrTy = VD->getType()->getAsArrayTypeUnsafe();
+ if (ArrTy == nullptr)
+ return;
+ uint64_t Elts = 0;
+ if (const ConstantArrayType *CArrTy = dyn_cast<ConstantArrayType>(ArrTy))
+ Elts = CArrTy->getSize().getZExtValue();
+ if (Elts == 0)
+ return;
+ const RecordType *RT = ArrTy->getElementType()->getAs<RecordType>();
+ if (RT == nullptr)
+ return;
+
+ // TODO: Recurse into the fields and base classes to see if any
+ // of those have excess padding.
+ visitRecord(RT->getDecl(), Elts);
+ }
+
+ bool shouldSkipDecl(const RecordDecl *RD) const {
+ auto Location = RD->getLocation();
+ // If the construct doesn't have a source file, then it's not something
+ // we want to diagnose.
+ if (!Location.isValid())
+ return true;
+ SrcMgr::CharacteristicKind Kind =
+ BR->getSourceManager().getFileCharacteristic(Location);
+ // Throw out all records that come from system headers.
+ if (Kind != SrcMgr::C_User)
+ return true;
+
+ // Not going to attempt to optimize unions.
+ if (RD->isUnion())
+ return true;
+ // How do you reorder fields if you haven't got any?
+ if (RD->field_empty())
+ return true;
+ if (auto *CXXRD = dyn_cast<CXXRecordDecl>(RD)) {
+ // Tail padding with base classes ends up being very complicated.
+ // We will skip objects with base classes for now.
+ if (CXXRD->getNumBases() != 0)
+ return true;
+ // Virtual bases are complicated, skipping those for now.
+ if (CXXRD->getNumVBases() != 0)
+ return true;
+ // Can't layout a template, so skip it. We do still layout the
+ // instantiations though.
+ if (CXXRD->getTypeForDecl()->isDependentType())
+ return true;
+ if (CXXRD->getTypeForDecl()->isInstantiationDependentType())
+ return true;
+ }
+ auto IsTrickyField = [](const FieldDecl *FD) -> bool {
+ // Bitfield layout is hard.
+ if (FD->isBitField())
+ return true;
+
+ // Variable length arrays are tricky too.
+ QualType Ty = FD->getType();
+ if (Ty->isIncompleteArrayType())
+ return true;
+ return false;
+ };
+
+ if (std::any_of(RD->field_begin(), RD->field_end(), IsTrickyField))
+ return true;
+ return false;
+ }
+
+ static CharUnits calculateBaselinePad(const RecordDecl *RD,
+ const ASTContext &ASTContext,
+ const ASTRecordLayout &RL) {
+ CharUnits PaddingSum;
+ CharUnits Offset = ASTContext.toCharUnitsFromBits(RL.getFieldOffset(0));
+ for (const auto &FD : RD->fields()) {
+ // This checker only cares about the padded size of the
+ // field, and not the data size. If the field is a record
+ // with tail padding, then we won't put that number in our
+ // total because reordering fields won't fix that problem.
+ CharUnits FieldSize = ASTContext.getTypeSizeInChars(FD->getType());
+ auto FieldOffsetBits = RL.getFieldOffset(FD->getFieldIndex());
+ CharUnits FieldOffset = ASTContext.toCharUnitsFromBits(FieldOffsetBits);
+ PaddingSum += (FieldOffset - Offset);
+ Offset = FieldOffset + FieldSize;
+ }
+ PaddingSum += RL.getSize() - Offset;
+ return PaddingSum;
+ }
+
+ /// Optimal padding overview:
+ /// 1. Find a close approximation to where we can place our first field.
+ /// This will usually be at offset 0.
+ /// 2. Try to find the best field that can legally be placed at the current
+ /// offset.
+ /// a. "Best" is the largest alignment that is legal, but smallest size.
+ /// This is to account for overly aligned types.
+ /// 3. If no fields can fit, pad by rounding the current offset up to the
+ /// smallest alignment requirement of our fields. Measure and track the
+ // amount of padding added. Go back to 2.
+ /// 4. Increment the current offset by the size of the chosen field.
+ /// 5. Remove the chosen field from the set of future possibilities.
+ /// 6. Go back to 2 if there are still unplaced fields.
+ /// 7. Add tail padding by rounding the current offset up to the structure
+ /// alignment. Track the amount of padding added.
+
+ static CharUnits calculateOptimalPad(const RecordDecl *RD,
+ const ASTContext &ASTContext,
+ const ASTRecordLayout &RL) {
+ struct CharUnitPair {
+ CharUnits Align;
+ CharUnits Size;
+ bool operator<(const CharUnitPair &RHS) const {
+ // Order from small alignments to large alignments,
+ // then large sizes to small sizes.
+ return std::make_pair(Align, -Size) <
+ std::make_pair(RHS.Align, -RHS.Size);
+ }
+ };
+ SmallVector<CharUnitPair, 20> Fields;
+ auto GatherSizesAndAlignments = [](const FieldDecl *FD) {
+ CharUnitPair RetVal;
+ auto &Ctx = FD->getASTContext();
+ std::tie(RetVal.Size, RetVal.Align) =
+ Ctx.getTypeInfoInChars(FD->getType());
+ assert(llvm::isPowerOf2_64(RetVal.Align.getQuantity()));
+ if (auto Max = FD->getMaxAlignment())
+ RetVal.Align = std::max(Ctx.toCharUnitsFromBits(Max), RetVal.Align);
+ return RetVal;
+ };
+ std::transform(RD->field_begin(), RD->field_end(),
+ std::back_inserter(Fields), GatherSizesAndAlignments);
+ std::sort(Fields.begin(), Fields.end());
+
+ // This lets us skip over vptrs and non-virtual bases,
+ // so that we can just worry about the fields in our object.
+ // Note that this does cause us to miss some cases where we
+ // could pack more bytes in to a base class's tail padding.
+ CharUnits NewOffset = ASTContext.toCharUnitsFromBits(RL.getFieldOffset(0));
+ CharUnits NewPad;
+
+ while (!Fields.empty()) {
+ unsigned TrailingZeros =
+ llvm::countTrailingZeros((unsigned long long)NewOffset.getQuantity());
+ // If NewOffset is zero, then countTrailingZeros will be 64. Shifting
+ // 64 will overflow our unsigned long long. Shifting 63 will turn
+ // our long long (and CharUnits internal type) negative. So shift 62.
+ long long CurAlignmentBits = 1ull << (std::min)(TrailingZeros, 62u);
+ CharUnits CurAlignment = CharUnits::fromQuantity(CurAlignmentBits);
+ CharUnitPair InsertPoint = {CurAlignment, CharUnits::Zero()};
+ auto CurBegin = Fields.begin();
+ auto CurEnd = Fields.end();
+
+ // In the typical case, this will find the last element
+ // of the vector. We won't find a middle element unless
+ // we started on a poorly aligned address or have an overly
+ // aligned field.
+ auto Iter = std::upper_bound(CurBegin, CurEnd, InsertPoint);
+ if (Iter != CurBegin) {
+ // We found a field that we can layout with the current alignment.
+ --Iter;
+ NewOffset += Iter->Size;
+ Fields.erase(Iter);
+ } else {
+ // We are poorly aligned, and we need to pad in order to layout another
+ // field. Round up to at least the smallest field alignment that we
+ // currently have.
+ CharUnits NextOffset = NewOffset.RoundUpToAlignment(Fields[0].Align);
+ NewPad += NextOffset - NewOffset;
+ NewOffset = NextOffset;
+ }
+ }
+ // Calculate tail padding.
+ CharUnits NewSize = NewOffset.RoundUpToAlignment(RL.getAlignment());
+ NewPad += NewSize - NewOffset;
+ return NewPad;
+ }
+
+ void reportRecord(const RecordDecl *RD, CharUnits BaselinePad,
+ CharUnits TargetPad) const {
+ if (!PaddingBug)
+ PaddingBug =
+ llvm::make_unique<BugType>(this, "Excessive Padding", "Performance");
+
+ SmallString<100> Buf;
+ llvm::raw_svector_ostream Os(Buf);
+
+ Os << "Excessive padding in '";
+ Os << QualType::getAsString(RD->getTypeForDecl(), Qualifiers()) << "'";
+
+ if (auto *TSD = dyn_cast<ClassTemplateSpecializationDecl>(RD)) {
+ // TODO: make this show up better in the console output and in
+ // the HTML. Maybe just make it show up in HTML like the path
+ // diagnostics show.
+ SourceLocation ILoc = TSD->getPointOfInstantiation();
+ if (ILoc.isValid())
+ Os << " instantiated here: "
+ << ILoc.printToString(BR->getSourceManager());
+ }
+
+ Os << " (" << BaselinePad.getQuantity() << " padding bytes, where "
+ << TargetPad.getQuantity() << " is optimal). Consider reordering "
+ << "the fields or adding explicit padding members.";
+
+ PathDiagnosticLocation CELoc =
+ PathDiagnosticLocation::create(RD, BR->getSourceManager());
+
+ auto Report = llvm::make_unique<BugReport>(*PaddingBug, Os.str(), CELoc);
+ Report->setDeclWithIssue(RD);
+ Report->addRange(RD->getSourceRange());
+
+ BR->emitReport(std::move(Report));
+ }
+};
+}
+
+void ento::registerPaddingChecker(CheckerManager &Mgr) {
+ Mgr.registerChecker<PaddingChecker>();
+}
diff --git a/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp b/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp
index 806312468beb..e3369677af72 100644
--- a/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp
@@ -22,7 +22,7 @@ using namespace clang;
using namespace ento;
namespace {
-class PointerArithChecker
+class PointerArithChecker
: public Checker< check::PreStmt<BinaryOperator> > {
mutable std::unique_ptr<BuiltinBug> BT;
@@ -48,10 +48,10 @@ void PointerArithChecker::checkPreStmt(const BinaryOperator *B,
// If pointer arithmetic is done on variables of non-array type, this often
// means behavior rely on memory organization, which is dangerous.
- if (isa<VarRegion>(LR) || isa<CodeTextRegion>(LR) ||
+ if (isa<VarRegion>(LR) || isa<CodeTextRegion>(LR) ||
isa<CompoundLiteralRegion>(LR)) {
- if (ExplodedNode *N = C.addTransition()) {
+ if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
if (!BT)
BT.reset(
new BuiltinBug(this, "Dangerous pointer arithmetic",
diff --git a/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp b/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp
index cf1f88a2851b..2d33ebc2610d 100644
--- a/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp
@@ -8,7 +8,7 @@
//===----------------------------------------------------------------------===//
//
// This files defines PointerSubChecker, a builtin checker that checks for
-// pointer subtractions on two pointers pointing to different memory chunks.
+// pointer subtractions on two pointers pointing to different memory chunks.
// This check corresponds to CWE-469.
//
//===----------------------------------------------------------------------===//
@@ -23,7 +23,7 @@ using namespace clang;
using namespace ento;
namespace {
-class PointerSubChecker
+class PointerSubChecker
: public Checker< check::PreStmt<BinaryOperator> > {
mutable std::unique_ptr<BuiltinBug> BT;
@@ -60,7 +60,7 @@ void PointerSubChecker::checkPreStmt(const BinaryOperator *B,
if (isa<SymbolicRegion>(BaseLR) || isa<SymbolicRegion>(BaseRR))
return;
- if (ExplodedNode *N = C.addTransition()) {
+ if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
if (!BT)
BT.reset(
new BuiltinBug(this, "Pointer subtraction",
diff --git a/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp b/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
index 4209017a58d5..28a4a083ea3c 100644
--- a/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
@@ -32,9 +32,9 @@ private:
LockState(Kind K) : K(K) {}
public:
- static LockState getLocked(void) { return LockState(Locked); }
- static LockState getUnlocked(void) { return LockState(Unlocked); }
- static LockState getDestroyed(void) { return LockState(Destroyed); }
+ static LockState getLocked() { return LockState(Locked); }
+ static LockState getUnlocked() { return LockState(Unlocked); }
+ static LockState getDestroyed() { return LockState(Destroyed); }
bool operator==(const LockState &X) const {
return K == X.K;
@@ -62,10 +62,10 @@ class PthreadLockChecker : public Checker< check::PostStmt<CallExpr> > {
};
public:
void checkPostStmt(const CallExpr *CE, CheckerContext &C) const;
-
+
void AcquireLock(CheckerContext &C, const CallExpr *CE, SVal lock,
bool isTryLock, enum LockingSemantics semantics) const;
-
+
void ReleaseLock(CheckerContext &C, const CallExpr *CE, SVal lock) const;
void DestroyLock(CheckerContext &C, const CallExpr *CE, SVal Lock) const;
void InitLock(CheckerContext &C, const CallExpr *CE, SVal Lock) const;
@@ -96,7 +96,7 @@ void PthreadLockChecker::checkPostStmt(const CallExpr *CE,
false, PthreadSemantics);
else if (FName == "lck_mtx_lock" ||
FName == "lck_rw_lock_exclusive" ||
- FName == "lck_rw_lock_shared")
+ FName == "lck_rw_lock_shared")
AcquireLock(C, CE, state->getSVal(CE->getArg(0), LCtx),
false, XNUSemantics);
else if (FName == "pthread_mutex_trylock" ||
@@ -124,17 +124,17 @@ void PthreadLockChecker::checkPostStmt(const CallExpr *CE,
void PthreadLockChecker::AcquireLock(CheckerContext &C, const CallExpr *CE,
SVal lock, bool isTryLock,
enum LockingSemantics semantics) const {
-
+
const MemRegion *lockR = lock.getAsRegion();
if (!lockR)
return;
-
+
ProgramStateRef state = C.getState();
-
+
SVal X = state->getSVal(CE, C.getLocationContext());
if (X.isUnknownOrUndef())
return;
-
+
DefinedSVal retVal = X.castAs<DefinedSVal>();
if (const LockState *LState = state->get<LockMap>(lockR)) {
@@ -142,7 +142,7 @@ void PthreadLockChecker::AcquireLock(CheckerContext &C, const CallExpr *CE,
if (!BT_doublelock)
BT_doublelock.reset(new BugType(this, "Double locking",
"Lock checker"));
- ExplodedNode *N = C.generateSink();
+ ExplodedNode *N = C.generateErrorNode();
if (!N)
return;
auto report = llvm::make_unique<BugReport>(
@@ -183,8 +183,8 @@ void PthreadLockChecker::AcquireLock(CheckerContext &C, const CallExpr *CE,
assert((semantics == XNUSemantics) && "Unknown locking semantics");
lockSucc = state;
}
-
- // Record that the lock was acquired.
+
+ // Record that the lock was acquired.
lockSucc = lockSucc->add<LockSet>(lockR);
lockSucc = lockSucc->set<LockMap>(lockR, LockState::getLocked());
C.addTransition(lockSucc);
@@ -196,7 +196,7 @@ void PthreadLockChecker::ReleaseLock(CheckerContext &C, const CallExpr *CE,
const MemRegion *lockR = lock.getAsRegion();
if (!lockR)
return;
-
+
ProgramStateRef state = C.getState();
if (const LockState *LState = state->get<LockMap>(lockR)) {
@@ -204,7 +204,7 @@ void PthreadLockChecker::ReleaseLock(CheckerContext &C, const CallExpr *CE,
if (!BT_doubleunlock)
BT_doubleunlock.reset(new BugType(this, "Double unlocking",
"Lock checker"));
- ExplodedNode *N = C.generateSink();
+ ExplodedNode *N = C.generateErrorNode();
if (!N)
return;
auto Report = llvm::make_unique<BugReport>(
@@ -227,7 +227,7 @@ void PthreadLockChecker::ReleaseLock(CheckerContext &C, const CallExpr *CE,
if (firstLockR != lockR) {
if (!BT_lor)
BT_lor.reset(new BugType(this, "Lock order reversal", "Lock checker"));
- ExplodedNode *N = C.generateSink();
+ ExplodedNode *N = C.generateErrorNode();
if (!N)
return;
auto report = llvm::make_unique<BugReport>(
@@ -272,7 +272,7 @@ void PthreadLockChecker::DestroyLock(CheckerContext &C, const CallExpr *CE,
if (!BT_destroylock)
BT_destroylock.reset(new BugType(this, "Destroy invalid lock",
"Lock checker"));
- ExplodedNode *N = C.generateSink();
+ ExplodedNode *N = C.generateErrorNode();
if (!N)
return;
auto Report = llvm::make_unique<BugReport>(*BT_destroylock, Message, N);
@@ -307,7 +307,7 @@ void PthreadLockChecker::InitLock(CheckerContext &C, const CallExpr *CE,
if (!BT_initlock)
BT_initlock.reset(new BugType(this, "Init invalid lock",
"Lock checker"));
- ExplodedNode *N = C.generateSink();
+ ExplodedNode *N = C.generateErrorNode();
if (!N)
return;
auto Report = llvm::make_unique<BugReport>(*BT_initlock, Message, N);
@@ -320,7 +320,7 @@ void PthreadLockChecker::reportUseDestroyedBug(CheckerContext &C,
if (!BT_destroylock)
BT_destroylock.reset(new BugType(this, "Use destroyed lock",
"Lock checker"));
- ExplodedNode *N = C.generateSink();
+ ExplodedNode *N = C.generateErrorNode();
if (!N)
return;
auto Report = llvm::make_unique<BugReport>(
diff --git a/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp b/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp
index 6ee87a561e02..f983c3085635 100644
--- a/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp
@@ -234,6 +234,7 @@ public:
return RefVal(getKind(), getObjKind(), getCount(), getAutoreleaseCount(),
getType(), IvarAccessHistory::AccessedDirectly);
}
+
RefVal releaseViaIvar() const {
assert(getIvarAccessHistory() == IvarAccessHistory::AccessedDirectly);
return RefVal(getKind(), getObjKind(), getCount(), getAutoreleaseCount(),
@@ -250,7 +251,7 @@ public:
bool operator==(const RefVal& X) const {
return T == X.T && hasSameState(X) && getObjKind() == X.getObjKind();
}
-
+
void Profile(llvm::FoldingSetNodeID& ID) const {
ID.Add(T);
ID.AddInteger(RawKind);
@@ -426,16 +427,16 @@ public:
/// setRetEffect - Set the effect of the return value of the call.
void setRetEffect(RetEffect E) { Ret = E; }
-
+
/// Sets the effect on the receiver of the message.
void setReceiverEffect(ArgEffect e) { Receiver = e; }
-
+
/// getReceiverEffect - Returns the effect on the receiver of the call.
/// This is only meaningful if the summary applies to an ObjCMessageExpr*.
ArgEffect getReceiverEffect() const { return Receiver; }
/// Test if two retain summaries are identical. Note that merely equivalent
- /// summaries are not necessarily identical (for example, if an explicit
+ /// summaries are not necessarily identical (for example, if an explicit
/// argument effect matches the default effect).
bool operator==(const RetainSummary &Other) const {
return Args == Other.Args && DefaultArgEffect == Other.DefaultArgEffect &&
@@ -484,7 +485,7 @@ public:
IdentifierInfo *getIdentifier() const { return II; }
Selector getSelector() const { return S; }
};
-}
+} // end anonymous namespace
namespace llvm {
template <> struct DenseMapInfo<ObjCSummaryKey> {
@@ -621,7 +622,7 @@ class RetainSummaryManager {
ArgEffects::Factory AF;
/// ScratchArgs - A holding buffer for construct ArgEffects.
- ArgEffects ScratchArgs;
+ ArgEffects ScratchArgs;
/// ObjCAllocRetE - Default return effect for methods returning Objective-C
/// objects.
@@ -644,7 +645,7 @@ class RetainSummaryManager {
ArgEffects getArgEffects();
enum UnaryFuncKind { cfretain, cfrelease, cfautorelease, cfmakecollectable };
-
+
const RetainSummary *getUnarySummary(const FunctionType* FT,
UnaryFuncKind func);
@@ -664,7 +665,7 @@ class RetainSummaryManager {
const RetainSummary *getDoNothingSummary() {
return getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing);
}
-
+
const RetainSummary *getDefaultSummary() {
return getPersistentSummary(RetEffect::MakeNoRet(),
DoNothing, MayEscape);
@@ -689,7 +690,7 @@ private:
void addClassMethSummary(const char* Cls, const char* name,
const RetainSummary *Summ, bool isNullary = true) {
IdentifierInfo* ClsII = &Ctx.Idents.get(Cls);
- Selector S = isNullary ? GetNullarySelector(name, Ctx)
+ Selector S = isNullary ? GetNullarySelector(name, Ctx)
: GetUnarySelector(name, Ctx);
ObjCClassMethodSummaries[ObjCSummaryKey(ClsII, S)] = Summ;
}
@@ -739,7 +740,7 @@ public:
? RetEffect::MakeGCNotOwned()
: (usesARC ? RetEffect::MakeNotOwned(RetEffect::ObjC)
: RetEffect::MakeOwned(RetEffect::ObjC, true))),
- ObjCInitRetE(gcenabled
+ ObjCInitRetE(gcenabled
? RetEffect::MakeGCNotOwned()
: (usesARC ? RetEffect::MakeNotOwned(RetEffect::ObjC)
: RetEffect::MakeOwnedWhenTrackedReceiver())) {
@@ -803,7 +804,7 @@ public:
bool isGCEnabled() const { return GCEnabled; }
bool isARCEnabled() const { return ARCEnabled; }
-
+
bool isARCorGCEnabled() const { return GCEnabled || ARCEnabled; }
RetEffect getObjAllocRetEffect() const { return ObjCAllocRetE; }
@@ -966,7 +967,7 @@ void RetainSummaryManager::updateSummaryForCall(const RetainSummary *&S,
// Additionally, our Self Init checker already warns about it. To avoid
// overwhelming the user with messages from both checkers, we model the case
// of '[super init]' in cases when it is not consumed by another expression
- // as if the call preserves the value of 'self'; essentially, assuming it can
+ // as if the call preserves the value of 'self'; essentially, assuming it can
// never fail and return 'nil'.
// Note, we don't want to just stop tracking the value since we want the
// RetainCount checker to report leaks and use-after-free if SelfInit checker
@@ -985,7 +986,6 @@ void RetainSummaryManager::updateSummaryForCall(const RetainSummary *&S,
ModifiableSummaryTemplate->setRetEffect(RetEffect::MakeNoRet());
}
}
-
}
}
@@ -1150,7 +1150,7 @@ RetainSummaryManager::getFunctionSummary(const FunctionDecl *FD) {
if (S)
break;
- if (RetTy->isPointerType()) {
+ if (RetTy->isPointerType()) {
// For CoreFoundation ('CF') types.
if (cocoa::isRefType(RetTy, "CF", FName)) {
if (isRetain(FD, FName)) {
@@ -1278,14 +1278,14 @@ RetainSummaryManager::getUnarySummary(const FunctionType* FT,
return getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing);
}
-const RetainSummary *
+const RetainSummary *
RetainSummaryManager::getCFSummaryCreateRule(const FunctionDecl *FD) {
assert (ScratchArgs.isEmpty());
return getPersistentSummary(RetEffect::MakeOwned(RetEffect::CF, true));
}
-const RetainSummary *
+const RetainSummary *
RetainSummaryManager::getCFSummaryGetRule(const FunctionDecl *FD) {
assert (ScratchArgs.isEmpty());
return getPersistentSummary(RetEffect::MakeNotOwned(RetEffect::CF),
@@ -1331,7 +1331,7 @@ RetainSummaryManager::updateSummaryFromAnnotations(const RetainSummary *&Summ,
// Effects on the parameters.
unsigned parm_idx = 0;
- for (FunctionDecl::param_const_iterator pi = FD->param_begin(),
+ for (FunctionDecl::param_const_iterator pi = FD->param_begin(),
pe = FD->param_end(); pi != pe; ++pi, ++parm_idx) {
const ParmVarDecl *pd = *pi;
if (pd->hasAttr<NSConsumedAttr>())
@@ -1367,8 +1367,8 @@ RetainSummaryManager::updateSummaryFromAnnotations(const RetainSummary *&Summ,
// Effects on the receiver.
if (MD->hasAttr<NSConsumesSelfAttr>())
- Template->setReceiverEffect(DecRefMsg);
-
+ Template->setReceiverEffect(DecRefMsg);
+
// Effects on the parameters.
unsigned parm_idx = 0;
for (ObjCMethodDecl::param_const_iterator
@@ -1376,9 +1376,9 @@ RetainSummaryManager::updateSummaryFromAnnotations(const RetainSummary *&Summ,
pi != pe; ++pi, ++parm_idx) {
const ParmVarDecl *pd = *pi;
if (pd->hasAttr<NSConsumedAttr>())
- Template->addArg(AF, parm_idx, DecRefMsg);
+ Template->addArg(AF, parm_idx, DecRefMsg);
else if (pd->hasAttr<CFConsumedAttr>()) {
- Template->addArg(AF, parm_idx, DecRef);
+ Template->addArg(AF, parm_idx, DecRef);
} else if (pd->hasAttr<CFReturnsRetainedAttr>()) {
QualType PointeeTy = pd->getType()->getPointeeType();
if (!PointeeTy.isNull())
@@ -1415,7 +1415,7 @@ RetainSummaryManager::getStandardMethodSummary(const ObjCMethodDecl *MD,
if (cocoa::isCocoaObjectRef(RetTy))
ResultEff = RetEffect::MakeNotOwned(RetEffect::ObjC);
else if (coreFoundation::isCFObjectRef(RetTy)) {
- // ObjCMethodDecl currently doesn't consider CF objects as valid return
+ // ObjCMethodDecl currently doesn't consider CF objects as valid return
// values for alloc, new, copy, or mutableCopy, so we have to
// double-check with the selector. This is ugly, but there aren't that
// many Objective-C methods that return CF objects, right?
@@ -1428,11 +1428,11 @@ RetainSummaryManager::getStandardMethodSummary(const ObjCMethodDecl *MD,
ResultEff = RetEffect::MakeOwned(RetEffect::CF, true);
break;
default:
- ResultEff = RetEffect::MakeNotOwned(RetEffect::CF);
+ ResultEff = RetEffect::MakeNotOwned(RetEffect::CF);
break;
}
} else {
- ResultEff = RetEffect::MakeNotOwned(RetEffect::CF);
+ ResultEff = RetEffect::MakeNotOwned(RetEffect::CF);
}
}
break;
@@ -1749,7 +1749,7 @@ namespace {
SymbolRef Sym;
const SummaryLogTy &SummaryLog;
bool GCEnabled;
-
+
public:
CFRefReportVisitor(SymbolRef sym, bool gcEnabled, const SummaryLogTy &log)
: Sym(sym), SummaryLog(log), GCEnabled(gcEnabled) {}
@@ -1869,7 +1869,7 @@ void CFRefReport::addGCModeDescription(const LangOptions &LOpts,
static bool isNumericLiteralExpression(const Expr *E) {
// FIXME: This set of cases was copied from SemaExprObjC.
- return isa<IntegerLiteral>(E) ||
+ return isa<IntegerLiteral>(E) ||
isa<CharacterLiteral>(E) ||
isa<FloatingLiteral>(E) ||
isa<ObjCBoolLiteralExpr>(E) ||
@@ -1948,7 +1948,7 @@ PathDiagnosticPiece *CFRefReportVisitor::VisitNode(const ExplodedNode *N,
else if (isa<ObjCIvarRefExpr>(S)) {
os << "Object loaded from instance variable";
}
- else {
+ else {
if (const CallExpr *CE = dyn_cast<CallExpr>(S)) {
// Get the name of the callee (if it is available).
SVal X = CurrSt->getSValAsScalarOrLoc(CE->getCallee(), LCtx);
@@ -2192,6 +2192,7 @@ PathDiagnosticPiece *CFRefReportVisitor::VisitNode(const ExplodedNode *N,
return P;
}
+namespace {
// Find the first node in the current function context that referred to the
// tracked symbol and the memory location that value was stored to. Note, the
// value is only reported if the allocation occurred in the same function as
@@ -2206,6 +2207,7 @@ struct AllocationInfo {
const LocationContext *InInterestingMethodContext) :
N(InN), R(InR), InterestingMethodContext(InInterestingMethodContext) {}
};
+} // end anonymous namespace
static AllocationInfo
GetAllocationSite(ProgramStateManager& StateMgr, const ExplodedNode *N,
@@ -2228,7 +2230,7 @@ GetAllocationSite(ProgramStateManager& StateMgr, const ExplodedNode *N,
StoreManager::FindUniqueBinding FB(Sym);
StateMgr.iterBindings(St, FB);
-
+
if (FB) {
const MemRegion *R = FB.getRegion();
const VarRegion *VR = R->getBaseRegion()->getAs<VarRegion>();
@@ -2345,10 +2347,10 @@ CFRefLeakReportVisitor::getEndPath(BugReporterContext &BRC,
// objects. Only "copy", "alloc", "retain" and "new" transfer ownership
// to the caller for NS objects.
const Decl *D = &EndN->getCodeDecl();
-
+
os << (isa<ObjCMethodDecl>(D) ? " is returned from a method "
: " is returned from a function ");
-
+
if (D->hasAttr<CFReturnsNotRetainedAttr>())
os << "that is annotated as CF_RETURNS_NOT_RETAINED";
else if (D->hasAttr<NSReturnsNotRetainedAttr>())
@@ -2385,7 +2387,7 @@ CFRefLeakReportVisitor::getEndPath(BugReporterContext &BRC,
}
CFRefLeakReport::CFRefLeakReport(CFRefBug &D, const LangOptions &LOpts,
- bool GCEnabled, const SummaryLogTy &Log,
+ bool GCEnabled, const SummaryLogTy &Log,
ExplodedNode *n, SymbolRef sym,
CheckerContext &Ctx,
bool IncludeAllocationLine)
@@ -2414,7 +2416,7 @@ CFRefLeakReport::CFRefLeakReport(CFRefBug &D, const LangOptions &LOpts,
// FIXME: This will crash the analyzer if an allocation comes from an
// implicit call (ex: a destructor call).
// (Currently there are no such allocations in Cocoa, though.)
- const Stmt *AllocStmt = 0;
+ const Stmt *AllocStmt = nullptr;
ProgramPoint P = AllocNode->getLocation();
if (Optional<CallExitEnd> Exit = P.getAs<CallExitEnd>())
AllocStmt = Exit->getCalleeContext()->getCallSite();
@@ -2492,7 +2494,7 @@ class RetainCountChecker
/// the allocation line.
mutable bool IncludeAllocationLine;
-public:
+public:
RetainCountChecker(AnalyzerOptions &AO)
: ShouldResetSummaryLog(false),
IncludeAllocationLine(shouldIncludeAllocationSiteInLeakDiagnostics(AO)) {}
@@ -2617,7 +2619,7 @@ public:
void checkPostStmt(const ObjCIvarRefExpr *IRE, CheckerContext &C) const;
void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
-
+
void checkSummary(const RetainSummary &Summ, const CallEvent &Call,
CheckerContext &C) const;
@@ -2630,13 +2632,13 @@ public:
ProgramStateRef evalAssume(ProgramStateRef state, SVal Cond,
bool Assumption) const;
- ProgramStateRef
+ ProgramStateRef
checkRegionChanges(ProgramStateRef state,
const InvalidatedSymbols *invalidated,
ArrayRef<const MemRegion *> ExplicitRegions,
ArrayRef<const MemRegion *> Regions,
const CallEvent *Call) const;
-
+
bool wantsRegionChangeUpdate(ProgramStateRef state) const {
return true;
}
@@ -2645,7 +2647,7 @@ public:
void checkReturnWithRetEffect(const ReturnStmt *S, CheckerContext &C,
ExplodedNode *Pred, RetEffect RE, RefVal X,
SymbolRef Sym, ProgramStateRef state) const;
-
+
void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
void checkEndFunction(CheckerContext &C) const;
@@ -2656,7 +2658,7 @@ public:
void processNonLeakError(ProgramStateRef St, SourceRange ErrorRange,
RefVal::Kind ErrorKind, SymbolRef Sym,
CheckerContext &C) const;
-
+
void processObjCLiterals(CheckerContext &C, const Expr *Ex) const;
const ProgramPointTag *getDeadSymbolTag(SymbolRef sym) const;
@@ -2678,7 +2680,7 @@ public:
} // end anonymous namespace
namespace {
-class StopTrackingCallback : public SymbolVisitor {
+class StopTrackingCallback final : public SymbolVisitor {
ProgramStateRef state;
public:
StopTrackingCallback(ProgramStateRef st) : state(st) {}
@@ -2740,21 +2742,21 @@ void RetainCountChecker::checkPostStmt(const CastExpr *CE,
const ObjCBridgedCastExpr *BE = dyn_cast<ObjCBridgedCastExpr>(CE);
if (!BE)
return;
-
+
ArgEffect AE = IncRef;
-
+
switch (BE->getBridgeKind()) {
case clang::OBC_Bridge:
// Do nothing.
return;
case clang::OBC_BridgeRetained:
AE = IncRef;
- break;
+ break;
case clang::OBC_BridgeTransfer:
AE = DecRefBridgedTransferred;
break;
}
-
+
ProgramStateRef state = C.getState();
SymbolRef Sym = state->getSVal(CE, C.getLocationContext()).getAsLocSymbol();
if (!Sym)
@@ -2765,7 +2767,7 @@ void RetainCountChecker::checkPostStmt(const CastExpr *CE,
RefVal::Kind hasErr = (RefVal::Kind) 0;
state = updateSymbol(state, Sym, *T, AE, hasErr, C);
-
+
if (hasErr) {
// FIXME: If we get an error during a bridge cast, should we report it?
return;
@@ -2777,7 +2779,7 @@ void RetainCountChecker::checkPostStmt(const CastExpr *CE,
void RetainCountChecker::processObjCLiterals(CheckerContext &C,
const Expr *Ex) const {
ProgramStateRef state = C.getState();
- const ExplodedNode *pred = C.getPredecessor();
+ const ExplodedNode *pred = C.getPredecessor();
for (const Stmt *Child : Ex->children()) {
SVal V = state->getSVal(Child, pred->getLocationContext());
if (SymbolRef sym = V.getAsSymbol())
@@ -2790,17 +2792,17 @@ void RetainCountChecker::processObjCLiterals(CheckerContext &C,
}
}
}
-
+
// Return the object as autoreleased.
// RetEffect RE = RetEffect::MakeNotOwned(RetEffect::ObjC);
- if (SymbolRef sym =
+ if (SymbolRef sym =
state->getSVal(Ex, pred->getLocationContext()).getAsSymbol()) {
QualType ResultTy = Ex->getType();
state = setRefBinding(state, sym,
RefVal::makeNotOwned(RetEffect::ObjC, ResultTy));
}
-
- C.addTransition(state);
+
+ C.addTransition(state);
}
void RetainCountChecker::checkPostStmt(const ObjCArrayLiteral *AL,
@@ -2817,7 +2819,7 @@ void RetainCountChecker::checkPostStmt(const ObjCDictionaryLiteral *DL,
void RetainCountChecker::checkPostStmt(const ObjCBoxedExpr *Ex,
CheckerContext &C) const {
- const ExplodedNode *Pred = C.getPredecessor();
+ const ExplodedNode *Pred = C.getPredecessor();
const LocationContext *LCtx = Pred->getLocationContext();
ProgramStateRef State = Pred->getState();
@@ -2966,7 +2968,7 @@ void RetainCountChecker::processSummaryOfInlined(const RetainSummary &Summ,
if (Sym)
state = removeRefBinding(state, Sym);
}
-
+
C.addTransition(state);
}
@@ -3062,7 +3064,7 @@ void RetainCountChecker::checkSummary(const RetainSummary &Summ,
if (RE.getKind() == RetEffect::OwnedWhenTrackedReceiver) {
if (ReceiverIsTracked)
- RE = getSummaryManager(C).getObjAllocRetEffect();
+ RE = getSummaryManager(C).getObjAllocRetEffect();
else
RE = RetEffect::MakeNoRet();
}
@@ -3129,8 +3131,7 @@ void RetainCountChecker::checkSummary(const RetainSummary &Summ,
}
}
-
-ProgramStateRef
+ProgramStateRef
RetainCountChecker::updateSymbol(ProgramStateRef state, SymbolRef sym,
RefVal V, ArgEffect E, RefVal::Kind &hasErr,
CheckerContext &C) const {
@@ -3306,7 +3307,7 @@ void RetainCountChecker::processNonLeakError(ProgramStateRef St,
if (RV->getIvarAccessHistory() != RefVal::IvarAccessHistory::None)
return;
- ExplodedNode *N = C.generateSink(St);
+ ExplodedNode *N = C.generateErrorNode(St);
if (!N)
return;
@@ -3388,7 +3389,7 @@ bool RetainCountChecker::evalCall(const CallExpr *CE, CheckerContext &C) const {
isMakeCollectable(FD, FName);
}
}
-
+
if (!canEval)
return false;
@@ -3531,7 +3532,7 @@ void RetainCountChecker::checkReturnWithRetEffect(const ReturnStmt *S,
ExplodedNode *Pred,
RetEffect RE, RefVal X,
SymbolRef Sym,
- ProgramStateRef state) const {
+ ProgramStateRef state) const {
// HACK: Ignore retain-count issues on values accessed through ivars,
// because of cases like this:
// [_contentView retain];
@@ -3669,7 +3670,6 @@ void RetainCountChecker::checkBind(SVal loc, SVal val, const Stmt *S,
ProgramStateRef RetainCountChecker::evalAssume(ProgramStateRef state,
SVal Cond,
bool Assumption) const {
-
// FIXME: We may add to the interface of evalAssume the list of symbols
// whose assumptions have changed. For now we just iterate through the
// bindings and check if any of the tracked symbols are NULL. This isn't
@@ -3700,7 +3700,7 @@ ProgramStateRef RetainCountChecker::evalAssume(ProgramStateRef state,
return state;
}
-ProgramStateRef
+ProgramStateRef
RetainCountChecker::checkRegionChanges(ProgramStateRef state,
const InvalidatedSymbols *invalidated,
ArrayRef<const MemRegion *> ExplicitRegions,
@@ -3810,7 +3810,7 @@ RetainCountChecker::handleAutoreleaseCounts(ProgramStateRef state,
return nullptr;
}
-ProgramStateRef
+ProgramStateRef
RetainCountChecker::handleSymbolDeath(ProgramStateRef state,
SymbolRef sid, RefVal V,
SmallVectorImpl<SymbolRef> &Leaked) const {
@@ -3890,7 +3890,7 @@ void RetainCountChecker::checkEndFunction(CheckerContext &Ctx) const {
// and suggest annotations.
if (LCtx->getParent())
return;
-
+
B = state->get<RefBindings>();
SmallVector<SymbolRef, 10> Leaked;
@@ -3910,7 +3910,7 @@ RetainCountChecker::getDeadSymbolTag(SymbolRef sym) const {
sym->dumpToStream(out);
tag = new CheckerProgramPointTag(this, out.str());
}
- return tag;
+ return tag;
}
void RetainCountChecker::checkDeadSymbols(SymbolReaper &SymReaper,
@@ -3993,7 +3993,9 @@ void ento::registerRetainCountChecker(CheckerManager &Mgr) {
// Implementation of the CallEffects API.
//===----------------------------------------------------------------------===//
-namespace clang { namespace ento { namespace objc_retain {
+namespace clang {
+namespace ento {
+namespace objc_retain {
// This is a bit gross, but it allows us to populate CallEffects without
// creating a bunch of accessors. This kind is very localized, so the
@@ -4022,4 +4024,6 @@ CallEffects CallEffects::getEffect(const FunctionDecl *FD) {
#undef createCallEffect
-}}}
+} // end namespace objc_retain
+} // end namespace ento
+} // end namespace clang
diff --git a/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp b/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp
index acbd0d95d07b..19fa0fb193cc 100644
--- a/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp
@@ -23,7 +23,7 @@ using namespace clang;
using namespace ento;
namespace {
-class ReturnPointerRangeChecker :
+class ReturnPointerRangeChecker :
public Checker< check::PreStmt<ReturnStmt> > {
mutable std::unique_ptr<BuiltinBug> BT;
@@ -39,7 +39,7 @@ void ReturnPointerRangeChecker::checkPreStmt(const ReturnStmt *RS,
const Expr *RetE = RS->getRetValue();
if (!RetE)
return;
-
+
SVal V = state->getSVal(RetE, C.getLocationContext());
const MemRegion *R = V.getAsRegion();
@@ -62,11 +62,11 @@ void ReturnPointerRangeChecker::checkPreStmt(const ReturnStmt *RS,
ProgramStateRef StInBound = state->assumeInBound(Idx, NumElements, true);
ProgramStateRef StOutBound = state->assumeInBound(Idx, NumElements, false);
if (StOutBound && !StInBound) {
- ExplodedNode *N = C.generateSink(StOutBound);
+ ExplodedNode *N = C.generateErrorNode(StOutBound);
if (!N)
return;
-
+
// FIXME: This bug correspond to CWE-466. Eventually we should have bug
// types explicitly reference such exploit categories (when applicable).
if (!BT)
diff --git a/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp b/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp
index 2668ac1e1eca..c5e826a84b84 100644
--- a/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp
@@ -80,7 +80,7 @@ void ReturnUndefChecker::checkPreStmt(const ReturnStmt *RS,
static void emitBug(CheckerContext &C, BuiltinBug &BT, const Expr *RetE,
const Expr *TrackingE = nullptr) {
- ExplodedNode *N = C.generateSink();
+ ExplodedNode *N = C.generateErrorNode();
if (!N)
return;
diff --git a/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp b/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp
index c22e78b7eb62..7026a2ec16a1 100644
--- a/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp
@@ -92,7 +92,7 @@ public:
REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState)
namespace {
-class StopTrackingCallback : public SymbolVisitor {
+class StopTrackingCallback final : public SymbolVisitor {
ProgramStateRef state;
public:
StopTrackingCallback(ProgramStateRef st) : state(st) {}
@@ -200,7 +200,9 @@ void SimpleStreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
State = State->remove<StreamMap>(Sym);
}
- ExplodedNode *N = C.addTransition(State);
+ ExplodedNode *N = C.generateNonFatalErrorNode(State);
+ if (!N)
+ return;
reportLeaks(LeakedStreams, C, N);
}
@@ -208,7 +210,7 @@ void SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym,
const CallEvent &Call,
CheckerContext &C) const {
// We reached a bug, stop exploring the path here by generating a sink.
- ExplodedNode *ErrNode = C.generateSink();
+ ExplodedNode *ErrNode = C.generateErrorNode();
// If we've already reached this node on another path, return.
if (!ErrNode)
return;
diff --git a/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp b/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp
index 813c811ef15f..79fc701d6d58 100644
--- a/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp
@@ -7,7 +7,7 @@
//
//===----------------------------------------------------------------------===//
//
-// This file defines stack address leak checker, which checks if an invalid
+// This file defines stack address leak checker, which checks if an invalid
// stack address is stored into a global or heap location. See CERT DCL30-C.
//
//===----------------------------------------------------------------------===//
@@ -49,20 +49,20 @@ SourceRange StackAddrEscapeChecker::genName(raw_ostream &os, const MemRegion *R,
SourceManager &SM = Ctx.getSourceManager();
SourceRange range;
os << "Address of ";
-
+
// Check if the region is a compound literal.
- if (const CompoundLiteralRegion* CR = dyn_cast<CompoundLiteralRegion>(R)) {
+ if (const CompoundLiteralRegion* CR = dyn_cast<CompoundLiteralRegion>(R)) {
const CompoundLiteralExpr *CL = CR->getLiteralExpr();
os << "stack memory associated with a compound literal "
"declared on line "
<< SM.getExpansionLineNumber(CL->getLocStart())
- << " returned to caller";
+ << " returned to caller";
range = CL->getSourceRange();
}
else if (const AllocaRegion* AR = dyn_cast<AllocaRegion>(R)) {
const Expr *ARE = AR->getExpr();
SourceLocation L = ARE->getLocStart();
- range = ARE->getSourceRange();
+ range = ARE->getSourceRange();
os << "stack memory allocated by call to alloca() on line "
<< SM.getExpansionLineNumber(L);
}
@@ -87,14 +87,14 @@ SourceRange StackAddrEscapeChecker::genName(raw_ostream &os, const MemRegion *R,
}
else {
llvm_unreachable("Invalid region in ReturnStackAddressChecker.");
- }
-
+ }
+
return range;
}
void StackAddrEscapeChecker::EmitStackError(CheckerContext &C, const MemRegion *R,
const Expr *RetE) const {
- ExplodedNode *N = C.generateSink();
+ ExplodedNode *N = C.generateErrorNode();
if (!N)
return;
@@ -118,7 +118,7 @@ void StackAddrEscapeChecker::EmitStackError(CheckerContext &C, const MemRegion *
void StackAddrEscapeChecker::checkPreStmt(const ReturnStmt *RS,
CheckerContext &C) const {
-
+
const Expr *RetE = RS->getRetValue();
if (!RetE)
return;
@@ -130,10 +130,10 @@ void StackAddrEscapeChecker::checkPreStmt(const ReturnStmt *RS,
if (!R)
return;
-
+
const StackSpaceRegion *SS =
dyn_cast_or_null<StackSpaceRegion>(R->getMemorySpace());
-
+
if (!SS)
return;
@@ -156,6 +156,15 @@ void StackAddrEscapeChecker::checkPreStmt(const ReturnStmt *RS,
if (isa<CXXConstructExpr>(RetE) && RetE->getType()->isRecordType())
return;
+ // The CK_CopyAndAutoreleaseBlockObject cast causes the block to be copied
+ // so the stack address is not escaping here.
+ if (auto *ICE = dyn_cast<ImplicitCastExpr>(RetE)) {
+ if (isa<BlockDataRegion>(R) &&
+ ICE->getCastKind() == CK_CopyAndAutoreleaseBlockObject) {
+ return;
+ }
+ }
+
EmitStackError(C, R, RetE);
}
@@ -175,35 +184,35 @@ void StackAddrEscapeChecker::checkEndFunction(CheckerContext &Ctx) const {
Ctx(CC),
CurSFC(CC.getLocationContext()->getCurrentStackFrame())
{}
-
+
bool HandleBinding(StoreManager &SMgr, Store store,
const MemRegion *region, SVal val) override {
if (!isa<GlobalsSpaceRegion>(region->getMemorySpace()))
return true;
-
+
const MemRegion *vR = val.getAsRegion();
if (!vR)
return true;
-
+
// Under automated retain release, it is okay to assign a block
// directly to a global variable.
if (Ctx.getASTContext().getLangOpts().ObjCAutoRefCount &&
isa<BlockDataRegion>(vR))
return true;
- if (const StackSpaceRegion *SSR =
+ if (const StackSpaceRegion *SSR =
dyn_cast<StackSpaceRegion>(vR->getMemorySpace())) {
// If the global variable holds a location in the current stack frame,
// record the binding to emit a warning.
if (SSR->getStackFrame() == CurSFC)
V.push_back(std::make_pair(region, vR));
}
-
+
return true;
}
};
-
+
CallBack cb(Ctx);
state->getStateManager().getStoreManager().iterBindings(state->getStore(),cb);
@@ -211,7 +220,7 @@ void StackAddrEscapeChecker::checkEndFunction(CheckerContext &Ctx) const {
return;
// Generate an error node.
- ExplodedNode *N = Ctx.addTransition(state);
+ ExplodedNode *N = Ctx.generateNonFatalErrorNode(state);
if (!N)
return;
diff --git a/lib/StaticAnalyzer/Checkers/StreamChecker.cpp b/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
index 2109a75b1fb6..82b01fe814da 100644
--- a/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
@@ -43,8 +43,8 @@ struct StreamState {
static StreamState getOpened(const Stmt *s) { return StreamState(Opened, s); }
static StreamState getClosed(const Stmt *s) { return StreamState(Closed, s); }
- static StreamState getOpenFailed(const Stmt *s) {
- return StreamState(OpenFailed, s);
+ static StreamState getOpenFailed(const Stmt *s) {
+ return StreamState(OpenFailed, s);
}
static StreamState getEscaped(const Stmt *s) {
return StreamState(Escaped, s);
@@ -59,14 +59,14 @@ struct StreamState {
class StreamChecker : public Checker<eval::Call,
check::DeadSymbols > {
mutable IdentifierInfo *II_fopen, *II_tmpfile, *II_fclose, *II_fread,
- *II_fwrite,
- *II_fseek, *II_ftell, *II_rewind, *II_fgetpos, *II_fsetpos,
+ *II_fwrite,
+ *II_fseek, *II_ftell, *II_rewind, *II_fgetpos, *II_fsetpos,
*II_clearerr, *II_feof, *II_ferror, *II_fileno;
mutable std::unique_ptr<BuiltinBug> BT_nullfp, BT_illegalwhence,
BT_doubleclose, BT_ResourceLeak;
public:
- StreamChecker()
+ StreamChecker()
: II_fopen(nullptr), II_tmpfile(nullptr), II_fclose(nullptr),
II_fread(nullptr), II_fwrite(nullptr), II_fseek(nullptr),
II_ftell(nullptr), II_rewind(nullptr), II_fgetpos(nullptr),
@@ -93,10 +93,10 @@ private:
void Fileno(CheckerContext &C, const CallExpr *CE) const;
void OpenFileAux(CheckerContext &C, const CallExpr *CE) const;
-
- ProgramStateRef CheckNullStream(SVal SV, ProgramStateRef state,
+
+ ProgramStateRef CheckNullStream(SVal SV, ProgramStateRef state,
CheckerContext &C) const;
- ProgramStateRef CheckDoubleClose(const CallExpr *CE, ProgramStateRef state,
+ ProgramStateRef CheckDoubleClose(const CallExpr *CE, ProgramStateRef state,
CheckerContext &C) const;
};
@@ -216,13 +216,13 @@ void StreamChecker::OpenFileAux(CheckerContext &C, const CallExpr *CE) const {
C.blockCount())
.castAs<DefinedSVal>();
state = state->BindExpr(CE, C.getLocationContext(), RetVal);
-
+
ConstraintManager &CM = C.getConstraintManager();
// Bifurcate the state into two: one with a valid FILE* pointer, the other
// with a NULL.
ProgramStateRef stateNotNull, stateNull;
std::tie(stateNotNull, stateNull) = CM.assumeDual(state, RetVal);
-
+
if (SymbolRef Sym = RetVal.getAsSymbol()) {
// if RetVal is not NULL, set the symbol's state to Opened.
stateNotNull =
@@ -271,7 +271,7 @@ void StreamChecker::Fseek(CheckerContext &C, const CallExpr *CE) const {
if (x >= 0 && x <= 2)
return;
- if (ExplodedNode *N = C.addTransition(state)) {
+ if (ExplodedNode *N = C.generateNonFatalErrorNode(state)) {
if (!BT_illegalwhence)
BT_illegalwhence.reset(
new BuiltinBug(this, "Illegal whence argument",
@@ -349,7 +349,7 @@ ProgramStateRef StreamChecker::CheckNullStream(SVal SV, ProgramStateRef state,
std::tie(stateNotNull, stateNull) = CM.assumeDual(state, *DV);
if (!stateNotNull && stateNull) {
- if (ExplodedNode *N = C.generateSink(stateNull)) {
+ if (ExplodedNode *N = C.generateErrorNode(stateNull)) {
if (!BT_nullfp)
BT_nullfp.reset(new BuiltinBug(this, "NULL stream pointer",
"Stream pointer might be NULL."));
@@ -368,17 +368,17 @@ ProgramStateRef StreamChecker::CheckDoubleClose(const CallExpr *CE,
state->getSVal(CE->getArg(0), C.getLocationContext()).getAsSymbol();
if (!Sym)
return state;
-
+
const StreamState *SS = state->get<StreamMap>(Sym);
// If the file stream is not tracked, return.
if (!SS)
return state;
-
+
// Check: Double close a File Descriptor could cause undefined behaviour.
// Conforming to man-pages
if (SS->isClosed()) {
- ExplodedNode *N = C.generateSink();
+ ExplodedNode *N = C.generateErrorNode();
if (N) {
if (!BT_doubleclose)
BT_doubleclose.reset(new BuiltinBug(
@@ -389,7 +389,7 @@ ProgramStateRef StreamChecker::CheckDoubleClose(const CallExpr *CE,
}
return nullptr;
}
-
+
// Close the File Descriptor.
return state->set<StreamMap>(Sym, StreamState::getClosed(CE));
}
@@ -406,7 +406,7 @@ void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
continue;
if (SS->isOpened()) {
- ExplodedNode *N = C.generateSink();
+ ExplodedNode *N = C.generateErrorNode();
if (N) {
if (!BT_ResourceLeak)
BT_ResourceLeak.reset(new BuiltinBug(
diff --git a/lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp b/lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp
index 6e2477579f55..2e0529015ca6 100644
--- a/lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp
@@ -48,7 +48,7 @@ void TaintTesterChecker::checkPostStmt(const Expr *E,
return;
if (State->isTainted(E, C.getLocationContext())) {
- if (ExplodedNode *N = C.addTransition()) {
+ if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
initBugType();
auto report = llvm::make_unique<BugReport>(*BT, "tainted",N);
report->addRange(E->getSourceRange());
diff --git a/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp b/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp
index 638701da8a01..b794d2f86bbe 100644
--- a/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp
@@ -167,7 +167,7 @@ bool TestAfterDivZeroChecker::hasDivZeroMap(SVal Var,
}
void TestAfterDivZeroChecker::reportBug(SVal Val, CheckerContext &C) const {
- if (ExplodedNode *N = C.generateSink(C.getState())) {
+ if (ExplodedNode *N = C.generateErrorNode(C.getState())) {
if (!DivZeroBug)
DivZeroBug.reset(new BuiltinBug(this, "Division by zero"));
diff --git a/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp b/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp
index 1d8ef9947175..ed17610e4116 100644
--- a/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp
@@ -30,7 +30,7 @@ class UndefBranchChecker : public Checker<check::BranchCondition> {
ProgramStateRef St;
const LocationContext *LCtx;
- FindUndefExpr(ProgramStateRef S, const LocationContext *L)
+ FindUndefExpr(ProgramStateRef S, const LocationContext *L)
: St(S), LCtx(L) {}
const Expr *FindExpr(const Expr *Ex) {
@@ -45,7 +45,7 @@ class UndefBranchChecker : public Checker<check::BranchCondition> {
return Ex;
}
- bool MatchesCriteria(const Expr *Ex) {
+ bool MatchesCriteria(const Expr *Ex) {
return St->getSVal(Ex, LCtx).isUndef();
}
};
@@ -62,7 +62,7 @@ void UndefBranchChecker::checkBranchCondition(const Stmt *Condition,
if (X.isUndef()) {
// Generate a sink node, which implicitly marks both outgoing branches as
// infeasible.
- ExplodedNode *N = Ctx.generateSink();
+ ExplodedNode *N = Ctx.generateErrorNode();
if (N) {
if (!BT)
BT.reset(new BuiltinBug(
diff --git a/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp b/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp
index 53fd069bf150..17fe8610da06 100644
--- a/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp
@@ -74,7 +74,7 @@ UndefCapturedBlockVarChecker::checkPostStmt(const BlockExpr *BE,
// Get the VarRegion associated with VD in the local stack frame.
if (Optional<UndefinedVal> V =
state->getSVal(I.getOriginalRegion()).getAs<UndefinedVal>()) {
- if (ExplodedNode *N = C.generateSink()) {
+ if (ExplodedNode *N = C.generateErrorNode()) {
if (!BT)
BT.reset(
new BuiltinBug(this, "uninitialized variable captured by block"));
@@ -83,7 +83,7 @@ UndefCapturedBlockVarChecker::checkPostStmt(const BlockExpr *BE,
SmallString<128> buf;
llvm::raw_svector_ostream os(buf);
- os << "Variable '" << VD->getName()
+ os << "Variable '" << VD->getName()
<< "' is uninitialized when captured by block";
auto R = llvm::make_unique<BugReport>(*BT, os.str(), N);
diff --git a/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp b/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp
index 5353310e6d5a..38d2aa6d8f9d 100644
--- a/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp
@@ -7,7 +7,7 @@
//
//===----------------------------------------------------------------------===//
//
-// This defines UndefResultChecker, a builtin check in ExprEngine that
+// This defines UndefResultChecker, a builtin check in ExprEngine that
// performs checks for undefined results of non-assignment binary operators.
//
//===----------------------------------------------------------------------===//
@@ -25,7 +25,7 @@ using namespace clang;
using namespace ento;
namespace {
-class UndefResultChecker
+class UndefResultChecker
: public Checker< check::PostStmt<BinaryOperator> > {
mutable std::unique_ptr<BugType> BT;
@@ -50,10 +50,10 @@ void UndefResultChecker::checkPostStmt(const BinaryOperator *B,
return;
// Generate an error node.
- ExplodedNode *N = C.generateSink();
+ ExplodedNode *N = C.generateErrorNode();
if (!N)
return;
-
+
if (!BT)
BT.reset(
new BuiltinBug(this, "Result of operation is garbage or undefined"));
@@ -62,7 +62,7 @@ void UndefResultChecker::checkPostStmt(const BinaryOperator *B,
llvm::raw_svector_ostream OS(sbuf);
const Expr *Ex = nullptr;
bool isLeft = true;
-
+
if (state->getSVal(B->getLHS(), LCtx).isUndef()) {
Ex = B->getLHS()->IgnoreParenCasts();
isLeft = true;
@@ -71,13 +71,13 @@ void UndefResultChecker::checkPostStmt(const BinaryOperator *B,
Ex = B->getRHS()->IgnoreParenCasts();
isLeft = false;
}
-
+
if (Ex) {
OS << "The " << (isLeft ? "left" : "right")
<< " operand of '"
<< BinaryOperator::getOpcodeStr(B->getOpcode())
<< "' is a garbage value";
- }
+ }
else {
// Neither operand was undefined, but the result is undefined.
OS << "The result of the '"
@@ -91,7 +91,7 @@ void UndefResultChecker::checkPostStmt(const BinaryOperator *B,
}
else
bugreporter::trackNullOrUndefValue(N, B, *report);
-
+
C.emitReport(std::move(report));
}
}
diff --git a/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp b/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp
index ba4daf835148..fe07eafd281f 100644
--- a/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp
@@ -32,7 +32,7 @@ public:
};
} // end anonymous namespace
-void
+void
UndefinedArraySubscriptChecker::checkPreStmt(const ArraySubscriptExpr *A,
CheckerContext &C) const {
const Expr *Index = A->getIdx();
@@ -46,7 +46,7 @@ UndefinedArraySubscriptChecker::checkPreStmt(const ArraySubscriptExpr *A,
if (Ctor->isDefaulted())
return;
- ExplodedNode *N = C.generateSink();
+ ExplodedNode *N = C.generateErrorNode();
if (!N)
return;
if (!BT)
diff --git a/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp b/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp
index 81c96c4860bc..7a31efc8cef8 100644
--- a/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp
@@ -46,7 +46,7 @@ void UndefinedAssignmentChecker::checkBind(SVal location, SVal val,
if (C.getCalleeName(EnclosingFunctionDecl) == "swap")
return;
- ExplodedNode *N = C.generateSink();
+ ExplodedNode *N = C.generateErrorNode();
if (!N)
return;
diff --git a/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp b/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp
index a799b4c21982..4b78c2058341 100644
--- a/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp
@@ -77,7 +77,7 @@ void UnixAPIChecker::ReportOpenBug(CheckerContext &C,
ProgramStateRef State,
const char *Msg,
SourceRange SR) const {
- ExplodedNode *N = C.generateSink(State);
+ ExplodedNode *N = C.generateErrorNode(State);
if (!N)
return;
@@ -114,7 +114,7 @@ void UnixAPIChecker::CheckOpen(CheckerContext &C, const CallExpr *CE) const {
// The definition of O_CREAT is platform specific. We need a better way
// of querying this information from the checking environment.
if (!Val_O_CREAT.hasValue()) {
- if (C.getASTContext().getTargetInfo().getTriple().getVendor()
+ if (C.getASTContext().getTargetInfo().getTriple().getVendor()
== llvm::Triple::Apple)
Val_O_CREAT = 0x0200;
else {
@@ -182,7 +182,7 @@ void UnixAPIChecker::CheckPthreadOnce(CheckerContext &C,
if (!R || !isa<StackSpaceRegion>(R->getMemorySpace()))
return;
- ExplodedNode *N = C.generateSink(state);
+ ExplodedNode *N = C.generateErrorNode(state);
if (!N)
return;
@@ -220,7 +220,7 @@ static bool IsZeroByteAllocation(ProgramStateRef state,
ProgramStateRef *falseState) {
std::tie(*trueState, *falseState) =
state->assume(argVal.castAs<DefinedSVal>());
-
+
return (*falseState && !*trueState);
}
@@ -231,7 +231,7 @@ bool UnixAPIChecker::ReportZeroByteAllocation(CheckerContext &C,
ProgramStateRef falseState,
const Expr *arg,
const char *fn_name) const {
- ExplodedNode *N = C.generateSink(falseState);
+ ExplodedNode *N = C.generateErrorNode(falseState);
if (!N)
return false;
@@ -239,7 +239,7 @@ bool UnixAPIChecker::ReportZeroByteAllocation(CheckerContext &C,
"Undefined allocation of 0 bytes (CERT MEM04-C; CWE-131)");
SmallString<256> S;
- llvm::raw_svector_ostream os(S);
+ llvm::raw_svector_ostream os(S);
os << "Call to '" << fn_name << "' has an allocation size of 0 bytes";
auto report = llvm::make_unique<BugReport>(*BT_mallocZero, os.str(), N);
@@ -272,13 +272,13 @@ void UnixAPIChecker::BasicAllocationCheck(CheckerContext &C,
// Is the value perfectly constrained to zero?
if (IsZeroByteAllocation(state, argVal, &trueState, &falseState)) {
- (void) ReportZeroByteAllocation(C, falseState, arg, fn);
+ (void) ReportZeroByteAllocation(C, falseState, arg, fn);
return;
}
// Assume the value is non-zero going forward.
assert(trueState);
if (trueState != state)
- C.addTransition(trueState);
+ C.addTransition(trueState);
}
void UnixAPIChecker::CheckCallocZero(CheckerContext &C,
diff --git a/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp b/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp
index d78de3c6f3a8..a03abce9626b 100644
--- a/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp
@@ -54,7 +54,7 @@ void UnreachableCodeChecker::checkEndAnalysis(ExplodedGraph &G,
BugReporter &B,
ExprEngine &Eng) const {
CFGBlocksSet reachable, visited;
-
+
if (Eng.hasWorkRemaining())
return;
@@ -88,7 +88,7 @@ void UnreachableCodeChecker::checkEndAnalysis(ExplodedGraph &G,
// Bail out if we didn't get the CFG or the ParentMap.
if (!D || !C || !PM)
return;
-
+
// Don't do anything for template instantiations. Proving that code
// in a template instantiation is unreachable means proving that it is
// unreachable in all instantiations.
@@ -235,12 +235,9 @@ bool UnreachableCodeChecker::isInvalidPath(const CFGBlock *CB,
return false;
// Run each of the checks on the conditions
- if (containsMacro(cond) || containsEnum(cond)
- || containsStaticLocal(cond) || containsBuiltinOffsetOf(cond)
- || containsStmt<UnaryExprOrTypeTraitExpr>(cond))
- return true;
-
- return false;
+ return containsMacro(cond) || containsEnum(cond) ||
+ containsStaticLocal(cond) || containsBuiltinOffsetOf(cond) ||
+ containsStmt<UnaryExprOrTypeTraitExpr>(cond);
}
// Returns true if the given CFGBlock is empty
diff --git a/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp b/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp
index 80384bbfdb30..e3b2ed222363 100644
--- a/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp
@@ -7,7 +7,7 @@
//
//===----------------------------------------------------------------------===//
//
-// This defines VLASizeChecker, a builtin check in ExprEngine that
+// This defines VLASizeChecker, a builtin check in ExprEngine that
// performs checks for declaration of VLA of undefined or zero size.
// In addition, VLASizeChecker is responsible for defining the extent
// of the MemRegion that represents a VLA.
@@ -46,7 +46,7 @@ void VLASizeChecker::reportBug(VLASize_Kind Kind,
ProgramStateRef State,
CheckerContext &C) const {
// Generate an error node.
- ExplodedNode *N = C.generateSink(State);
+ ExplodedNode *N = C.generateErrorNode(State);
if (!N)
return;
@@ -82,7 +82,7 @@ void VLASizeChecker::reportBug(VLASize_Kind Kind,
void VLASizeChecker::checkPreStmt(const DeclStmt *DS, CheckerContext &C) const {
if (!DS->isSingleDecl())
return;
-
+
const VarDecl *VD = dyn_cast<VarDecl>(DS->getSingleDecl());
if (!VD)
return;
@@ -106,7 +106,7 @@ void VLASizeChecker::checkPreStmt(const DeclStmt *DS, CheckerContext &C) const {
// warned about that already.
if (sizeV.isUnknown())
return;
-
+
// Check if the size is tainted.
if (state->isTainted(sizeV)) {
reportBug(VLA_Tainted, SE, nullptr, C);
@@ -123,7 +123,7 @@ void VLASizeChecker::checkPreStmt(const DeclStmt *DS, CheckerContext &C) const {
reportBug(VLA_Zero, SE, stateZero, C);
return;
}
-
+
// From this point on, assume that the size is not zero.
state = stateNotZero;
diff --git a/lib/StaticAnalyzer/Checkers/VforkChecker.cpp b/lib/StaticAnalyzer/Checkers/VforkChecker.cpp
new file mode 100644
index 000000000000..26ffee827cff
--- /dev/null
+++ b/lib/StaticAnalyzer/Checkers/VforkChecker.cpp
@@ -0,0 +1,218 @@
+//===- VforkChecker.cpp -------- Vfork usage checks --------------*- 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 vfork checker which checks for dangerous uses of vfork.
+// Vforked process shares memory (including stack) with parent so it's
+// range of actions is significantly limited: can't write variables,
+// can't call functions not in whitelist, etc. For more details, see
+// http://man7.org/linux/man-pages/man2/vfork.2.html
+//
+// This checker checks for prohibited constructs in vforked process.
+// The state transition diagram:
+// PARENT ---(vfork() == 0)--> CHILD
+// |
+// --(*p = ...)--> bug
+// |
+// --foo()--> bug
+// |
+// --return--> bug
+//
+//===----------------------------------------------------------------------===//
+
+#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/CheckerManager.h"
+#include "clang/AST/ParentMap.h"
+
+using namespace clang;
+using namespace ento;
+
+namespace {
+
+class VforkChecker : public Checker<check::PreCall, check::PostCall,
+ check::Bind, check::PreStmt<ReturnStmt>> {
+ mutable std::unique_ptr<BuiltinBug> BT;
+ mutable llvm::SmallSet<const IdentifierInfo *, 10> VforkWhitelist;
+ mutable const IdentifierInfo *II_vfork;
+
+ static bool isChildProcess(const ProgramStateRef State);
+
+ bool isVforkCall(const Decl *D, CheckerContext &C) const;
+ bool isCallWhitelisted(const IdentifierInfo *II, CheckerContext &C) const;
+
+ void reportBug(const char *What, CheckerContext &C,
+ const char *Details = 0) const;
+
+public:
+ VforkChecker() : II_vfork(0) {}
+
+ void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
+ void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
+ void checkBind(SVal L, SVal V, const Stmt *S, CheckerContext &C) const;
+ void checkPreStmt(const ReturnStmt *RS, CheckerContext &C) const;
+};
+
+} // end anonymous namespace
+
+// This trait holds region of variable that is assigned with vfork's
+// return value (this is the only region child is allowed to write).
+// VFORK_RESULT_INVALID means that we are in parent process.
+// VFORK_RESULT_NONE means that vfork's return value hasn't been assigned.
+// Other values point to valid regions.
+REGISTER_TRAIT_WITH_PROGRAMSTATE(VforkResultRegion, const void *)
+#define VFORK_RESULT_INVALID 0
+#define VFORK_RESULT_NONE ((void *)(uintptr_t)1)
+
+bool VforkChecker::isChildProcess(const ProgramStateRef State) {
+ return State->get<VforkResultRegion>() != VFORK_RESULT_INVALID;
+}
+
+bool VforkChecker::isVforkCall(const Decl *D, CheckerContext &C) const {
+ auto FD = dyn_cast_or_null<FunctionDecl>(D);
+ if (!FD || !C.isCLibraryFunction(FD))
+ return false;
+
+ if (!II_vfork) {
+ ASTContext &AC = C.getASTContext();
+ II_vfork = &AC.Idents.get("vfork");
+ }
+
+ return FD->getIdentifier() == II_vfork;
+}
+
+// Returns true iff ok to call function after successful vfork.
+bool VforkChecker::isCallWhitelisted(const IdentifierInfo *II,
+ CheckerContext &C) const {
+ if (VforkWhitelist.empty()) {
+ // According to manpage.
+ const char *ids[] = {
+ "_exit",
+ "_Exit",
+ "execl",
+ "execlp",
+ "execle",
+ "execv",
+ "execvp",
+ "execvpe",
+ 0,
+ };
+
+ ASTContext &AC = C.getASTContext();
+ for (const char **id = ids; *id; ++id)
+ VforkWhitelist.insert(&AC.Idents.get(*id));
+ }
+
+ return VforkWhitelist.count(II);
+}
+
+void VforkChecker::reportBug(const char *What, CheckerContext &C,
+ const char *Details) const {
+ if (ExplodedNode *N = C.generateErrorNode(C.getState())) {
+ if (!BT)
+ BT.reset(new BuiltinBug(this,
+ "Dangerous construct in a vforked process"));
+
+ SmallString<256> buf;
+ llvm::raw_svector_ostream os(buf);
+
+ os << What << " is prohibited after a successful vfork";
+
+ if (Details)
+ os << "; " << Details;
+
+ auto Report = llvm::make_unique<BugReport>(*BT, os.str(), N);
+ // TODO: mark vfork call in BugReportVisitor
+ C.emitReport(std::move(Report));
+ }
+}
+
+// Detect calls to vfork and split execution appropriately.
+void VforkChecker::checkPostCall(const CallEvent &Call,
+ CheckerContext &C) const {
+ // We can't call vfork in child so don't bother
+ // (corresponding warning has already been emitted in checkPreCall).
+ ProgramStateRef State = C.getState();
+ if (isChildProcess(State))
+ return;
+
+ if (!isVforkCall(Call.getDecl(), C))
+ return;
+
+ // Get return value of vfork.
+ SVal VforkRetVal = Call.getReturnValue();
+ Optional<DefinedOrUnknownSVal> DVal =
+ VforkRetVal.getAs<DefinedOrUnknownSVal>();
+ if (!DVal)
+ return;
+
+ // Get assigned variable.
+ const ParentMap &PM = C.getLocationContext()->getParentMap();
+ const Stmt *P = PM.getParentIgnoreParenCasts(Call.getOriginExpr());
+ const VarDecl *LhsDecl;
+ std::tie(LhsDecl, std::ignore) = parseAssignment(P);
+
+ // Get assigned memory region.
+ MemRegionManager &M = C.getStoreManager().getRegionManager();
+ const MemRegion *LhsDeclReg =
+ LhsDecl
+ ? M.getVarRegion(LhsDecl, C.getLocationContext())
+ : (const MemRegion *)VFORK_RESULT_NONE;
+
+ // Parent branch gets nonzero return value (according to manpage).
+ ProgramStateRef ParentState, ChildState;
+ std::tie(ParentState, ChildState) = C.getState()->assume(*DVal);
+ C.addTransition(ParentState);
+ ChildState = ChildState->set<VforkResultRegion>(LhsDeclReg);
+ C.addTransition(ChildState);
+}
+
+// Prohibit calls to non-whitelist functions in child process.
+void VforkChecker::checkPreCall(const CallEvent &Call,
+ CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+ if (isChildProcess(State)
+ && !isCallWhitelisted(Call.getCalleeIdentifier(), C))
+ reportBug("This function call", C);
+}
+
+// Prohibit writes in child process (except for vfork's lhs).
+void VforkChecker::checkBind(SVal L, SVal V, const Stmt *S,
+ CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+ if (!isChildProcess(State))
+ return;
+
+ const MemRegion *VforkLhs =
+ static_cast<const MemRegion *>(State->get<VforkResultRegion>());
+ const MemRegion *MR = L.getAsRegion();
+
+ // Child is allowed to modify only vfork's lhs.
+ if (!MR || MR == VforkLhs)
+ return;
+
+ reportBug("This assignment", C);
+}
+
+// Prohibit return from function in child process.
+void VforkChecker::checkPreStmt(const ReturnStmt *RS, CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+ if (isChildProcess(State))
+ reportBug("Return", C, "call _exit() instead");
+}
+
+void ento::registerVforkChecker(CheckerManager &mgr) {
+ mgr.registerChecker<VforkChecker>();
+}
diff --git a/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp b/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp
index f6ef4aef5c78..550250302611 100644
--- a/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp
@@ -7,7 +7,7 @@
//
//===----------------------------------------------------------------------===//
//
-// This file defines a checker that checks virtual function calls during
+// This file defines a checker that checks virtual function calls during
// construction or destruction of C++ objects.
//
//===----------------------------------------------------------------------===//
@@ -37,13 +37,13 @@ class WalkAST : public StmtVisitor<WalkAST> {
/// A vector representing the worklist which has a chain of CallExprs.
DFSWorkList WList;
-
+
// PreVisited : A CallExpr to this FunctionDecl is in the worklist, but the
// body has not been visited yet.
// PostVisited : A CallExpr to this FunctionDecl is in the worklist, and the
// body has been visited.
enum Kind { NotVisited,
- PreVisited, /**< A CallExpr to this FunctionDecl is in the
+ PreVisited, /**< A CallExpr to this FunctionDecl is in the
worklist, but the body has not yet been
visited. */
PostVisited /**< A CallExpr to this FunctionDecl is in the
@@ -57,7 +57,7 @@ class WalkAST : public StmtVisitor<WalkAST> {
/// generating bug reports. This is null while visiting the body of a
/// constructor or destructor.
const CallExpr *visitingCallExpr;
-
+
public:
WalkAST(const CheckerBase *checker, BugReporter &br,
AnalysisDeclContext *ac)
@@ -70,7 +70,7 @@ public:
void Enqueue(WorkListUnit WLUnit) {
const FunctionDecl *FD = WLUnit->getDirectCallee();
if (!FD || !FD->getBody())
- return;
+ return;
Kind &K = VisitedFunctions[FD];
if (K != NotVisited)
return;
@@ -81,9 +81,9 @@ public:
/// This method returns an item from the worklist without removing it.
WorkListUnit Dequeue() {
assert(!WList.empty());
- return WList.back();
+ return WList.back();
}
-
+
void Execute() {
while (hasWork()) {
WorkListUnit WLUnit = Dequeue();
@@ -95,7 +95,7 @@ public:
// Visit the body.
SaveAndRestore<const CallExpr *> SaveCall(visitingCallExpr, WLUnit);
Visit(FD->getBody());
-
+
// Mark the function as being PostVisited to indicate we have
// scanned the body.
VisitedFunctions[FD] = PostVisited;
@@ -114,7 +114,7 @@ public:
void VisitCXXMemberCallExpr(CallExpr *CE);
void VisitStmt(Stmt *S) { VisitChildren(S); }
void VisitChildren(Stmt *S);
-
+
void ReportVirtualCall(const CallExpr *CE, bool isPure);
};
@@ -138,7 +138,7 @@ void WalkAST::VisitCallExpr(CallExpr *CE) {
void WalkAST::VisitCXXMemberCallExpr(CallExpr *CE) {
VisitChildren(CE);
bool callIsNonVirtual = false;
-
+
// Several situations to elide for checking.
if (MemberExpr *CME = dyn_cast<MemberExpr>(CE->getCallee())) {
// If the member access is fully qualified (i.e., X::F), then treat
@@ -170,7 +170,7 @@ void WalkAST::VisitCXXMemberCallExpr(CallExpr *CE) {
void WalkAST::ReportVirtualCall(const CallExpr *CE, bool isPure) {
SmallString<100> buf;
llvm::raw_svector_ostream os(buf);
-
+
os << "Call Path : ";
// Name of current visiting CallExpr.
os << *CE->getDirectCallee();
@@ -190,7 +190,7 @@ void WalkAST::ReportVirtualCall(const CallExpr *CE, bool isPure) {
PathDiagnosticLocation CELoc =
PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC);
SourceRange R = CE->getCallee()->getSourceRange();
-
+
if (isPure) {
os << "\n" << "Call pure virtual functions during construction or "
<< "destruction may leads undefined behaviour";