2008-03-27 15:25:52 +08:00
|
|
|
//== BasicObjCFoundationChecks.cpp - Simple Apple-Foundation checks -*- C++ -*--
|
|
|
|
//
|
2019-01-19 16:50:56 +08:00
|
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
2008-03-27 15:25:52 +08:00
|
|
|
//
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
//
|
|
|
|
// This file defines BasicObjCFoundationChecks, a class that encapsulates
|
|
|
|
// a set of simple checks to run on Objective-C code using Apple's Foundation
|
|
|
|
// classes.
|
|
|
|
//
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
[analyzer][NFC] Move CheckerRegistry from the Core directory to Frontend
ClangCheckerRegistry is a very non-obvious, poorly documented, weird concept.
It derives from CheckerRegistry, and is placed in lib/StaticAnalyzer/Frontend,
whereas it's base is located in lib/StaticAnalyzer/Core. It was, from what I can
imagine, used to circumvent the problem that the registry functions of the
checkers are located in the clangStaticAnalyzerCheckers library, but that
library depends on clangStaticAnalyzerCore. However, clangStaticAnalyzerFrontend
depends on both of those libraries.
One can make the observation however, that CheckerRegistry has no place in Core,
it isn't used there at all! The only place where it is used is Frontend, which
is where it ultimately belongs.
This move implies that since
include/clang/StaticAnalyzer/Checkers/ClangCheckers.h only contained a single function:
class CheckerRegistry;
void registerBuiltinCheckers(CheckerRegistry ®istry);
it had to re purposed, as CheckerRegistry is no longer available to
clangStaticAnalyzerCheckers. It was renamed to BuiltinCheckerRegistration.h,
which actually describes it a lot better -- it does not contain the registration
functions for checkers, but only those generated by the tblgen files.
Differential Revision: https://reviews.llvm.org/D54436
llvm-svn: 349275
2018-12-16 00:23:51 +08:00
|
|
|
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
|
2012-12-04 17:13:33 +08:00
|
|
|
#include "clang/AST/ASTContext.h"
|
|
|
|
#include "clang/AST/DeclObjC.h"
|
|
|
|
#include "clang/AST/Expr.h"
|
|
|
|
#include "clang/AST/ExprObjC.h"
|
|
|
|
#include "clang/AST/StmtObjC.h"
|
2011-03-17 12:01:35 +08:00
|
|
|
#include "clang/Analysis/DomainSpecific/CocoaConventions.h"
|
2018-08-21 11:09:02 +08:00
|
|
|
#include "clang/Analysis/SelectorExtras.h"
|
2012-12-04 17:13:33 +08:00
|
|
|
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
|
2011-03-01 09:16:21 +08:00
|
|
|
#include "clang/StaticAnalyzer/Core/Checker.h"
|
2011-02-18 05:39:17 +08:00
|
|
|
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
|
2012-07-27 05:39:41 +08:00
|
|
|
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
|
2011-02-23 09:05:36 +08:00
|
|
|
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
|
2011-02-10 09:03:03 +08:00
|
|
|
#include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h"
|
|
|
|
#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h"
|
|
|
|
#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h"
|
2012-12-04 17:13:33 +08:00
|
|
|
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
|
2012-02-04 21:45:25 +08:00
|
|
|
#include "llvm/ADT/SmallString.h"
|
2012-06-12 00:40:37 +08:00
|
|
|
#include "llvm/ADT/StringMap.h"
|
2012-12-02 01:12:56 +08:00
|
|
|
#include "llvm/Support/raw_ostream.h"
|
2008-03-27 15:25:52 +08:00
|
|
|
|
|
|
|
using namespace clang;
|
2010-12-23 15:20:52 +08:00
|
|
|
using namespace ento;
|
2018-08-30 06:39:20 +08:00
|
|
|
using namespace llvm;
|
2008-04-04 01:57:38 +08:00
|
|
|
|
2010-10-21 07:38:56 +08:00
|
|
|
namespace {
|
|
|
|
class APIMisuse : public BugType {
|
|
|
|
public:
|
2014-02-12 05:49:21 +08:00
|
|
|
APIMisuse(const CheckerBase *checker, const char *name)
|
|
|
|
: BugType(checker, name, "API Misuse (Apple)") {}
|
2010-10-21 07:38:56 +08:00
|
|
|
};
|
|
|
|
} // end anonymous namespace
|
|
|
|
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// Utility functions.
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
2012-07-03 03:28:04 +08:00
|
|
|
static StringRef GetReceiverInterfaceName(const ObjCMethodCall &msg) {
|
2011-01-25 08:03:53 +08:00
|
|
|
if (const ObjCInterfaceDecl *ID = msg.getReceiverInterface())
|
2012-07-03 03:28:04 +08:00
|
|
|
return ID->getIdentifier()->getName();
|
|
|
|
return StringRef();
|
2008-04-04 01:57:38 +08:00
|
|
|
}
|
|
|
|
|
2012-06-12 00:40:37 +08:00
|
|
|
enum FoundationClass {
|
|
|
|
FC_None,
|
|
|
|
FC_NSArray,
|
|
|
|
FC_NSDictionary,
|
|
|
|
FC_NSEnumerator,
|
2013-05-11 02:04:46 +08:00
|
|
|
FC_NSNull,
|
2012-06-12 00:40:37 +08:00
|
|
|
FC_NSOrderedSet,
|
|
|
|
FC_NSSet,
|
|
|
|
FC_NSString
|
|
|
|
};
|
|
|
|
|
2013-11-08 09:15:35 +08:00
|
|
|
static FoundationClass findKnownClass(const ObjCInterfaceDecl *ID,
|
|
|
|
bool IncludeSuperclasses = true) {
|
2012-06-12 00:40:37 +08:00
|
|
|
static llvm::StringMap<FoundationClass> Classes;
|
|
|
|
if (Classes.empty()) {
|
|
|
|
Classes["NSArray"] = FC_NSArray;
|
|
|
|
Classes["NSDictionary"] = FC_NSDictionary;
|
|
|
|
Classes["NSEnumerator"] = FC_NSEnumerator;
|
2013-05-11 02:04:46 +08:00
|
|
|
Classes["NSNull"] = FC_NSNull;
|
2012-06-12 00:40:37 +08:00
|
|
|
Classes["NSOrderedSet"] = FC_NSOrderedSet;
|
|
|
|
Classes["NSSet"] = FC_NSSet;
|
|
|
|
Classes["NSString"] = FC_NSString;
|
|
|
|
}
|
2011-03-09 04:05:26 +08:00
|
|
|
|
2012-06-12 00:40:37 +08:00
|
|
|
// FIXME: Should we cache this at all?
|
|
|
|
FoundationClass result = Classes.lookup(ID->getIdentifier()->getName());
|
2013-11-08 09:15:35 +08:00
|
|
|
if (result == FC_None && IncludeSuperclasses)
|
2012-06-12 00:40:37 +08:00
|
|
|
if (const ObjCInterfaceDecl *Super = ID->getSuperClass())
|
|
|
|
return findKnownClass(Super);
|
2008-04-04 01:57:38 +08:00
|
|
|
|
2012-06-12 00:40:37 +08:00
|
|
|
return result;
|
2010-10-21 07:38:56 +08:00
|
|
|
}
|
2009-09-09 23:08:12 +08:00
|
|
|
|
2010-10-21 07:38:56 +08:00
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// NilArgChecker - Check for prohibited nil arguments to ObjC method calls.
|
|
|
|
//===----------------------------------------------------------------------===//
|
2008-03-27 15:25:52 +08:00
|
|
|
|
2010-10-23 00:33:16 +08:00
|
|
|
namespace {
|
2013-05-14 05:48:20 +08:00
|
|
|
class NilArgChecker : public Checker<check::PreObjCMessage,
|
|
|
|
check::PostStmt<ObjCDictionaryLiteral>,
|
|
|
|
check::PostStmt<ObjCArrayLiteral> > {
|
2014-03-08 04:03:18 +08:00
|
|
|
mutable std::unique_ptr<APIMisuse> BT;
|
2011-02-23 08:16:10 +08:00
|
|
|
|
2014-04-09 09:39:22 +08:00
|
|
|
mutable llvm::SmallDenseMap<Selector, unsigned, 16> StringSelectors;
|
|
|
|
mutable Selector ArrayWithObjectSel;
|
|
|
|
mutable Selector AddObjectSel;
|
|
|
|
mutable Selector InsertObjectAtIndexSel;
|
|
|
|
mutable Selector ReplaceObjectAtIndexWithObjectSel;
|
|
|
|
mutable Selector SetObjectAtIndexedSubscriptSel;
|
|
|
|
mutable Selector ArrayByAddingObjectSel;
|
|
|
|
mutable Selector DictionaryWithObjectForKeySel;
|
|
|
|
mutable Selector SetObjectForKeySel;
|
|
|
|
mutable Selector SetObjectForKeyedSubscriptSel;
|
|
|
|
mutable Selector RemoveObjectForKeySel;
|
|
|
|
|
2013-05-14 05:48:20 +08:00
|
|
|
void warnIfNilExpr(const Expr *E,
|
|
|
|
const char *Msg,
|
|
|
|
CheckerContext &C) const;
|
|
|
|
|
|
|
|
void warnIfNilArg(CheckerContext &C,
|
|
|
|
const ObjCMethodCall &msg, unsigned Arg,
|
|
|
|
FoundationClass Class,
|
|
|
|
bool CanBeSubscript = false) const;
|
|
|
|
|
|
|
|
void generateBugReport(ExplodedNode *N,
|
2013-05-14 07:49:51 +08:00
|
|
|
StringRef Msg,
|
2013-05-14 05:48:20 +08:00
|
|
|
SourceRange Range,
|
|
|
|
const Expr *Expr,
|
|
|
|
CheckerContext &C) const;
|
2011-02-23 08:16:10 +08:00
|
|
|
|
2010-10-23 00:33:16 +08:00
|
|
|
public:
|
2012-07-03 03:28:04 +08:00
|
|
|
void checkPreObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;
|
2013-05-14 05:48:20 +08:00
|
|
|
void checkPostStmt(const ObjCDictionaryLiteral *DL,
|
|
|
|
CheckerContext &C) const;
|
|
|
|
void checkPostStmt(const ObjCArrayLiteral *AL,
|
|
|
|
CheckerContext &C) const;
|
2010-10-23 00:33:16 +08:00
|
|
|
};
|
2016-02-11 03:11:58 +08:00
|
|
|
} // end anonymous namespace
|
2009-09-09 23:08:12 +08:00
|
|
|
|
2013-05-14 05:48:20 +08:00
|
|
|
void NilArgChecker::warnIfNilExpr(const Expr *E,
|
|
|
|
const char *Msg,
|
|
|
|
CheckerContext &C) const {
|
|
|
|
ProgramStateRef State = C.getState();
|
2013-05-14 07:49:51 +08:00
|
|
|
if (State->isNull(C.getSVal(E)).isConstrainedTrue()) {
|
2013-05-14 05:48:20 +08:00
|
|
|
|
2015-09-17 06:03:05 +08:00
|
|
|
if (ExplodedNode *N = C.generateErrorNode()) {
|
2013-05-14 07:49:51 +08:00
|
|
|
generateBugReport(N, Msg, E->getSourceRange(), E, C);
|
2013-05-14 05:48:20 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void NilArgChecker::warnIfNilArg(CheckerContext &C,
|
2013-03-23 08:39:21 +08:00
|
|
|
const ObjCMethodCall &msg,
|
|
|
|
unsigned int Arg,
|
|
|
|
FoundationClass Class,
|
|
|
|
bool CanBeSubscript) const {
|
|
|
|
// Check if the argument is nil.
|
|
|
|
ProgramStateRef State = C.getState();
|
|
|
|
if (!State->isNull(msg.getArgSVal(Arg)).isConstrainedTrue())
|
|
|
|
return;
|
2015-09-08 11:50:52 +08:00
|
|
|
|
2018-08-15 08:33:55 +08:00
|
|
|
// NOTE: We cannot throw non-fatal errors from warnIfNilExpr,
|
|
|
|
// because it's called multiple times from some callers, so it'd cause
|
|
|
|
// an unwanted state split if two or more non-fatal errors are thrown
|
|
|
|
// within the same checker callback. For now we don't want to, but
|
|
|
|
// it'll need to be fixed if we ever want to.
|
2015-09-17 06:03:05 +08:00
|
|
|
if (ExplodedNode *N = C.generateErrorNode()) {
|
2012-02-05 10:13:05 +08:00
|
|
|
SmallString<128> sbuf;
|
2010-10-21 07:38:56 +08:00
|
|
|
llvm::raw_svector_ostream os(sbuf);
|
2013-03-23 08:39:21 +08:00
|
|
|
|
|
|
|
if (CanBeSubscript && msg.getMessageKind() == OCM_Subscript) {
|
|
|
|
|
|
|
|
if (Class == FC_NSArray) {
|
|
|
|
os << "Array element cannot be nil";
|
|
|
|
} else if (Class == FC_NSDictionary) {
|
2013-04-06 07:50:18 +08:00
|
|
|
if (Arg == 0) {
|
2013-04-09 02:09:16 +08:00
|
|
|
os << "Value stored into '";
|
2013-04-06 07:50:18 +08:00
|
|
|
os << GetReceiverInterfaceName(msg) << "' cannot be nil";
|
|
|
|
} else {
|
2013-03-23 08:39:21 +08:00
|
|
|
assert(Arg == 1);
|
2013-04-06 07:50:18 +08:00
|
|
|
os << "'"<< GetReceiverInterfaceName(msg) << "' key cannot be nil";
|
2013-03-23 08:39:21 +08:00
|
|
|
}
|
|
|
|
} else
|
|
|
|
llvm_unreachable("Missing foundation class for the subscript expr");
|
|
|
|
|
|
|
|
} else {
|
2013-04-06 07:50:18 +08:00
|
|
|
if (Class == FC_NSDictionary) {
|
|
|
|
if (Arg == 0)
|
|
|
|
os << "Value argument ";
|
|
|
|
else {
|
|
|
|
assert(Arg == 1);
|
|
|
|
os << "Key argument ";
|
|
|
|
}
|
2014-01-04 01:59:55 +08:00
|
|
|
os << "to '";
|
|
|
|
msg.getSelector().print(os);
|
|
|
|
os << "' cannot be nil";
|
2013-04-06 07:50:18 +08:00
|
|
|
} else {
|
2014-01-04 01:59:55 +08:00
|
|
|
os << "Argument to '" << GetReceiverInterfaceName(msg) << "' method '";
|
|
|
|
msg.getSelector().print(os);
|
|
|
|
os << "' cannot be nil";
|
2013-04-06 07:50:18 +08:00
|
|
|
}
|
2013-03-23 08:39:21 +08:00
|
|
|
}
|
2015-09-08 11:50:52 +08:00
|
|
|
|
2013-05-14 07:49:51 +08:00
|
|
|
generateBugReport(N, os.str(), msg.getArgSourceRange(Arg),
|
2013-05-14 05:48:20 +08:00
|
|
|
msg.getArgExpr(Arg), C);
|
2008-04-04 01:57:38 +08:00
|
|
|
}
|
2008-03-28 01:17:22 +08:00
|
|
|
}
|
|
|
|
|
2013-05-14 05:48:20 +08:00
|
|
|
void NilArgChecker::generateBugReport(ExplodedNode *N,
|
2013-05-14 07:49:51 +08:00
|
|
|
StringRef Msg,
|
2013-05-14 05:48:20 +08:00
|
|
|
SourceRange Range,
|
2013-05-14 07:49:51 +08:00
|
|
|
const Expr *E,
|
2013-05-14 05:48:20 +08:00
|
|
|
CheckerContext &C) const {
|
|
|
|
if (!BT)
|
2014-02-12 05:49:21 +08:00
|
|
|
BT.reset(new APIMisuse(this, "nil argument"));
|
2013-05-14 05:48:20 +08:00
|
|
|
|
2015-06-23 21:15:32 +08:00
|
|
|
auto R = llvm::make_unique<BugReport>(*BT, Msg, N);
|
2013-05-14 05:48:20 +08:00
|
|
|
R->addRange(Range);
|
2018-10-24 02:24:53 +08:00
|
|
|
bugreporter::trackExpressionValue(N, E, *R);
|
2015-06-23 21:15:32 +08:00
|
|
|
C.emitReport(std::move(R));
|
2013-05-14 05:48:20 +08:00
|
|
|
}
|
|
|
|
|
2012-07-03 03:28:04 +08:00
|
|
|
void NilArgChecker::checkPreObjCMessage(const ObjCMethodCall &msg,
|
2011-02-23 08:16:10 +08:00
|
|
|
CheckerContext &C) const {
|
2011-03-09 04:05:26 +08:00
|
|
|
const ObjCInterfaceDecl *ID = msg.getReceiverInterface();
|
|
|
|
if (!ID)
|
2010-10-21 07:38:56 +08:00
|
|
|
return;
|
2013-03-19 04:46:56 +08:00
|
|
|
|
|
|
|
FoundationClass Class = findKnownClass(ID);
|
|
|
|
|
|
|
|
static const unsigned InvalidArgIndex = UINT_MAX;
|
|
|
|
unsigned Arg = InvalidArgIndex;
|
2013-03-23 08:39:21 +08:00
|
|
|
bool CanBeSubscript = false;
|
2015-09-08 11:50:52 +08:00
|
|
|
|
2013-03-19 04:46:56 +08:00
|
|
|
if (Class == FC_NSString) {
|
2011-01-25 08:03:53 +08:00
|
|
|
Selector S = msg.getSelector();
|
2014-04-09 09:39:22 +08:00
|
|
|
|
2010-10-21 07:38:56 +08:00
|
|
|
if (S.isUnarySelector())
|
|
|
|
return;
|
2014-04-09 09:39:22 +08:00
|
|
|
|
|
|
|
if (StringSelectors.empty()) {
|
|
|
|
ASTContext &Ctx = C.getASTContext();
|
|
|
|
Selector Sels[] = {
|
2017-05-10 03:31:30 +08:00
|
|
|
getKeywordSelector(Ctx, "caseInsensitiveCompare"),
|
|
|
|
getKeywordSelector(Ctx, "compare"),
|
|
|
|
getKeywordSelector(Ctx, "compare", "options"),
|
|
|
|
getKeywordSelector(Ctx, "compare", "options", "range"),
|
|
|
|
getKeywordSelector(Ctx, "compare", "options", "range", "locale"),
|
|
|
|
getKeywordSelector(Ctx, "componentsSeparatedByCharactersInSet"),
|
|
|
|
getKeywordSelector(Ctx, "initWithFormat"),
|
|
|
|
getKeywordSelector(Ctx, "localizedCaseInsensitiveCompare"),
|
|
|
|
getKeywordSelector(Ctx, "localizedCompare"),
|
|
|
|
getKeywordSelector(Ctx, "localizedStandardCompare"),
|
2014-04-09 09:39:22 +08:00
|
|
|
};
|
|
|
|
for (Selector KnownSel : Sels)
|
|
|
|
StringSelectors[KnownSel] = 0;
|
2013-03-19 04:46:56 +08:00
|
|
|
}
|
2014-04-09 09:39:22 +08:00
|
|
|
auto I = StringSelectors.find(S);
|
|
|
|
if (I == StringSelectors.end())
|
|
|
|
return;
|
|
|
|
Arg = I->second;
|
2013-03-19 04:46:56 +08:00
|
|
|
} else if (Class == FC_NSArray) {
|
|
|
|
Selector S = msg.getSelector();
|
|
|
|
|
|
|
|
if (S.isUnarySelector())
|
|
|
|
return;
|
|
|
|
|
2014-04-09 09:39:22 +08:00
|
|
|
if (ArrayWithObjectSel.isNull()) {
|
|
|
|
ASTContext &Ctx = C.getASTContext();
|
2017-05-10 03:31:30 +08:00
|
|
|
ArrayWithObjectSel = getKeywordSelector(Ctx, "arrayWithObject");
|
|
|
|
AddObjectSel = getKeywordSelector(Ctx, "addObject");
|
2014-04-09 09:39:22 +08:00
|
|
|
InsertObjectAtIndexSel =
|
2017-05-10 03:31:30 +08:00
|
|
|
getKeywordSelector(Ctx, "insertObject", "atIndex");
|
2014-04-09 09:39:22 +08:00
|
|
|
ReplaceObjectAtIndexWithObjectSel =
|
2017-05-10 03:31:30 +08:00
|
|
|
getKeywordSelector(Ctx, "replaceObjectAtIndex", "withObject");
|
2014-04-09 09:39:22 +08:00
|
|
|
SetObjectAtIndexedSubscriptSel =
|
2017-05-10 03:31:30 +08:00
|
|
|
getKeywordSelector(Ctx, "setObject", "atIndexedSubscript");
|
|
|
|
ArrayByAddingObjectSel = getKeywordSelector(Ctx, "arrayByAddingObject");
|
2014-04-09 09:39:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (S == ArrayWithObjectSel || S == AddObjectSel ||
|
|
|
|
S == InsertObjectAtIndexSel || S == ArrayByAddingObjectSel) {
|
2013-03-19 04:46:56 +08:00
|
|
|
Arg = 0;
|
2014-04-09 09:39:22 +08:00
|
|
|
} else if (S == SetObjectAtIndexedSubscriptSel) {
|
2013-03-19 04:46:56 +08:00
|
|
|
Arg = 0;
|
2013-03-23 08:39:21 +08:00
|
|
|
CanBeSubscript = true;
|
2014-04-09 09:39:22 +08:00
|
|
|
} else if (S == ReplaceObjectAtIndexWithObjectSel) {
|
|
|
|
Arg = 1;
|
2010-10-21 07:38:56 +08:00
|
|
|
}
|
2013-03-23 08:39:21 +08:00
|
|
|
} else if (Class == FC_NSDictionary) {
|
|
|
|
Selector S = msg.getSelector();
|
|
|
|
|
|
|
|
if (S.isUnarySelector())
|
|
|
|
return;
|
|
|
|
|
2014-04-09 09:39:22 +08:00
|
|
|
if (DictionaryWithObjectForKeySel.isNull()) {
|
|
|
|
ASTContext &Ctx = C.getASTContext();
|
|
|
|
DictionaryWithObjectForKeySel =
|
2017-05-10 03:31:30 +08:00
|
|
|
getKeywordSelector(Ctx, "dictionaryWithObject", "forKey");
|
|
|
|
SetObjectForKeySel = getKeywordSelector(Ctx, "setObject", "forKey");
|
2014-04-09 09:39:22 +08:00
|
|
|
SetObjectForKeyedSubscriptSel =
|
2017-05-10 03:31:30 +08:00
|
|
|
getKeywordSelector(Ctx, "setObject", "forKeyedSubscript");
|
|
|
|
RemoveObjectForKeySel = getKeywordSelector(Ctx, "removeObjectForKey");
|
2014-04-09 09:39:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (S == DictionaryWithObjectForKeySel || S == SetObjectForKeySel) {
|
2013-03-23 08:39:21 +08:00
|
|
|
Arg = 0;
|
2013-05-14 05:48:20 +08:00
|
|
|
warnIfNilArg(C, msg, /* Arg */1, Class);
|
2014-04-09 09:39:22 +08:00
|
|
|
} else if (S == SetObjectForKeyedSubscriptSel) {
|
2013-03-23 08:39:21 +08:00
|
|
|
CanBeSubscript = true;
|
2015-11-11 08:49:22 +08:00
|
|
|
Arg = 1;
|
2014-04-09 09:39:22 +08:00
|
|
|
} else if (S == RemoveObjectForKeySel) {
|
2013-03-23 08:39:21 +08:00
|
|
|
Arg = 0;
|
|
|
|
}
|
2008-03-28 06:05:32 +08:00
|
|
|
}
|
2013-03-19 04:46:56 +08:00
|
|
|
|
|
|
|
// If argument is '0', report a warning.
|
2013-03-23 08:39:21 +08:00
|
|
|
if ((Arg != InvalidArgIndex))
|
2013-05-14 05:48:20 +08:00
|
|
|
warnIfNilArg(C, msg, Arg, Class, CanBeSubscript);
|
2008-03-27 15:25:52 +08:00
|
|
|
}
|
2008-06-27 07:59:48 +08:00
|
|
|
|
2013-05-14 05:48:20 +08:00
|
|
|
void NilArgChecker::checkPostStmt(const ObjCArrayLiteral *AL,
|
|
|
|
CheckerContext &C) const {
|
2013-05-14 07:49:51 +08:00
|
|
|
unsigned NumOfElements = AL->getNumElements();
|
|
|
|
for (unsigned i = 0; i < NumOfElements; ++i) {
|
2013-05-14 05:48:20 +08:00
|
|
|
warnIfNilExpr(AL->getElement(i), "Array element cannot be nil", C);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void NilArgChecker::checkPostStmt(const ObjCDictionaryLiteral *DL,
|
|
|
|
CheckerContext &C) const {
|
2013-05-14 07:49:51 +08:00
|
|
|
unsigned NumOfElements = DL->getNumElements();
|
|
|
|
for (unsigned i = 0; i < NumOfElements; ++i) {
|
2013-05-14 05:48:20 +08:00
|
|
|
ObjCDictionaryElement Element = DL->getKeyValueElement(i);
|
|
|
|
warnIfNilExpr(Element.Key, "Dictionary key cannot be nil", C);
|
|
|
|
warnIfNilExpr(Element.Value, "Dictionary value cannot be nil", C);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-06-27 07:59:48 +08:00
|
|
|
//===----------------------------------------------------------------------===//
|
2016-10-27 06:51:47 +08:00
|
|
|
// Checking for mismatched types passed to CFNumberCreate/CFNumberGetValue.
|
2008-06-27 07:59:48 +08:00
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
namespace {
|
2016-10-27 06:51:47 +08:00
|
|
|
class CFNumberChecker : public Checker< check::PreStmt<CallExpr> > {
|
2014-03-08 04:03:18 +08:00
|
|
|
mutable std::unique_ptr<APIMisuse> BT;
|
2016-10-27 06:51:47 +08:00
|
|
|
mutable IdentifierInfo *ICreate, *IGetValue;
|
2008-06-27 07:59:48 +08:00
|
|
|
public:
|
2016-10-27 06:51:47 +08:00
|
|
|
CFNumberChecker() : ICreate(nullptr), IGetValue(nullptr) {}
|
2011-02-23 08:16:10 +08:00
|
|
|
|
|
|
|
void checkPreStmt(const CallExpr *CE, CheckerContext &C) const;
|
|
|
|
|
2008-06-27 07:59:48 +08:00
|
|
|
private:
|
2011-08-13 07:37:29 +08:00
|
|
|
void EmitError(const TypedRegion* R, const Expr *Ex,
|
2009-09-09 23:08:12 +08:00
|
|
|
uint64_t SourceSize, uint64_t TargetSize, uint64_t NumberKind);
|
2008-06-27 07:59:48 +08:00
|
|
|
};
|
|
|
|
} // end anonymous namespace
|
|
|
|
|
|
|
|
enum CFNumberType {
|
|
|
|
kCFNumberSInt8Type = 1,
|
|
|
|
kCFNumberSInt16Type = 2,
|
|
|
|
kCFNumberSInt32Type = 3,
|
|
|
|
kCFNumberSInt64Type = 4,
|
|
|
|
kCFNumberFloat32Type = 5,
|
|
|
|
kCFNumberFloat64Type = 6,
|
|
|
|
kCFNumberCharType = 7,
|
|
|
|
kCFNumberShortType = 8,
|
|
|
|
kCFNumberIntType = 9,
|
|
|
|
kCFNumberLongType = 10,
|
|
|
|
kCFNumberLongLongType = 11,
|
|
|
|
kCFNumberFloatType = 12,
|
|
|
|
kCFNumberDoubleType = 13,
|
|
|
|
kCFNumberCFIndexType = 14,
|
|
|
|
kCFNumberNSIntegerType = 15,
|
|
|
|
kCFNumberCGFloatType = 16
|
|
|
|
};
|
|
|
|
|
2011-08-13 07:37:29 +08:00
|
|
|
static Optional<uint64_t> GetCFNumberSize(ASTContext &Ctx, uint64_t i) {
|
2009-12-24 01:49:57 +08:00
|
|
|
static const unsigned char FixedSize[] = { 8, 16, 32, 64, 32, 64 };
|
2009-09-09 23:08:12 +08:00
|
|
|
|
2008-06-27 07:59:48 +08:00
|
|
|
if (i < kCFNumberCharType)
|
|
|
|
return FixedSize[i-1];
|
2009-09-09 23:08:12 +08:00
|
|
|
|
2008-06-27 07:59:48 +08:00
|
|
|
QualType T;
|
2009-09-09 23:08:12 +08:00
|
|
|
|
2008-06-27 07:59:48 +08:00
|
|
|
switch (i) {
|
|
|
|
case kCFNumberCharType: T = Ctx.CharTy; break;
|
|
|
|
case kCFNumberShortType: T = Ctx.ShortTy; break;
|
|
|
|
case kCFNumberIntType: T = Ctx.IntTy; break;
|
|
|
|
case kCFNumberLongType: T = Ctx.LongTy; break;
|
|
|
|
case kCFNumberLongLongType: T = Ctx.LongLongTy; break;
|
|
|
|
case kCFNumberFloatType: T = Ctx.FloatTy; break;
|
|
|
|
case kCFNumberDoubleType: T = Ctx.DoubleTy; break;
|
|
|
|
case kCFNumberCFIndexType:
|
|
|
|
case kCFNumberNSIntegerType:
|
|
|
|
case kCFNumberCGFloatType:
|
2009-09-09 23:08:12 +08:00
|
|
|
// FIXME: We need a way to map from names to Type*.
|
2008-06-27 07:59:48 +08:00
|
|
|
default:
|
2013-02-21 09:47:18 +08:00
|
|
|
return None;
|
2008-06-27 07:59:48 +08:00
|
|
|
}
|
2009-09-09 23:08:12 +08:00
|
|
|
|
2008-06-27 07:59:48 +08:00
|
|
|
return Ctx.getTypeSize(T);
|
|
|
|
}
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
static const char* GetCFNumberTypeStr(uint64_t i) {
|
|
|
|
static const char* Names[] = {
|
|
|
|
"kCFNumberSInt8Type",
|
|
|
|
"kCFNumberSInt16Type",
|
|
|
|
"kCFNumberSInt32Type",
|
|
|
|
"kCFNumberSInt64Type",
|
|
|
|
"kCFNumberFloat32Type",
|
|
|
|
"kCFNumberFloat64Type",
|
|
|
|
"kCFNumberCharType",
|
|
|
|
"kCFNumberShortType",
|
|
|
|
"kCFNumberIntType",
|
|
|
|
"kCFNumberLongType",
|
|
|
|
"kCFNumberLongLongType",
|
|
|
|
"kCFNumberFloatType",
|
|
|
|
"kCFNumberDoubleType",
|
|
|
|
"kCFNumberCFIndexType",
|
|
|
|
"kCFNumberNSIntegerType",
|
|
|
|
"kCFNumberCGFloatType"
|
|
|
|
};
|
2009-09-09 23:08:12 +08:00
|
|
|
|
2008-06-27 07:59:48 +08:00
|
|
|
return i <= kCFNumberCGFloatType ? Names[i-1] : "Invalid CFNumberType";
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2016-10-27 06:51:47 +08:00
|
|
|
void CFNumberChecker::checkPreStmt(const CallExpr *CE,
|
2011-02-23 08:16:10 +08:00
|
|
|
CheckerContext &C) const {
|
2012-01-27 05:29:00 +08:00
|
|
|
ProgramStateRef state = C.getState();
|
2011-12-01 13:57:37 +08:00
|
|
|
const FunctionDecl *FD = C.getCalleeDecl(CE);
|
2010-10-21 07:38:56 +08:00
|
|
|
if (!FD)
|
|
|
|
return;
|
2015-09-08 11:50:52 +08:00
|
|
|
|
2010-10-21 07:38:56 +08:00
|
|
|
ASTContext &Ctx = C.getASTContext();
|
2016-10-27 06:51:47 +08:00
|
|
|
if (!ICreate) {
|
|
|
|
ICreate = &Ctx.Idents.get("CFNumberCreate");
|
|
|
|
IGetValue = &Ctx.Idents.get("CFNumberGetValue");
|
|
|
|
}
|
|
|
|
if (!(FD->getIdentifier() == ICreate || FD->getIdentifier() == IGetValue) ||
|
|
|
|
CE->getNumArgs() != 3)
|
2010-10-21 07:38:56 +08:00
|
|
|
return;
|
2009-09-09 23:08:12 +08:00
|
|
|
|
2008-06-27 07:59:48 +08:00
|
|
|
// Get the value of the "theType" argument.
|
2018-01-18 04:27:29 +08:00
|
|
|
SVal TheTypeVal = C.getSVal(CE->getArg(1));
|
2009-09-09 23:08:12 +08:00
|
|
|
|
2010-10-21 07:38:56 +08:00
|
|
|
// FIXME: We really should allow ranges of valid theType values, and
|
|
|
|
// bifurcate the state appropriately.
|
2013-02-21 06:23:23 +08:00
|
|
|
Optional<nonloc::ConcreteInt> V = TheTypeVal.getAs<nonloc::ConcreteInt>();
|
2008-06-27 07:59:48 +08:00
|
|
|
if (!V)
|
2010-10-21 07:38:56 +08:00
|
|
|
return;
|
2009-09-09 23:08:12 +08:00
|
|
|
|
2008-06-27 07:59:48 +08:00
|
|
|
uint64_t NumberKind = V->getValue().getLimitedValue();
|
2016-10-27 06:51:47 +08:00
|
|
|
Optional<uint64_t> OptCFNumberSize = GetCFNumberSize(Ctx, NumberKind);
|
2009-09-09 23:08:12 +08:00
|
|
|
|
2008-06-27 07:59:48 +08:00
|
|
|
// FIXME: In some cases we can emit an error.
|
2016-10-27 06:51:47 +08:00
|
|
|
if (!OptCFNumberSize)
|
2010-10-21 07:38:56 +08:00
|
|
|
return;
|
2009-09-09 23:08:12 +08:00
|
|
|
|
2016-10-27 06:51:47 +08:00
|
|
|
uint64_t CFNumberSize = *OptCFNumberSize;
|
2013-02-21 06:23:03 +08:00
|
|
|
|
2008-06-27 07:59:48 +08:00
|
|
|
// Look at the value of the integer being passed by reference. Essentially
|
|
|
|
// we want to catch cases where the value passed in is not equal to the
|
|
|
|
// size of the type being created.
|
2018-01-18 04:27:29 +08:00
|
|
|
SVal TheValueExpr = C.getSVal(CE->getArg(2));
|
2009-09-09 23:08:12 +08:00
|
|
|
|
2008-06-27 07:59:48 +08:00
|
|
|
// FIXME: Eventually we should handle arbitrary locations. We can do this
|
|
|
|
// by having an enhanced memory model that does low-level typing.
|
2013-02-21 06:23:23 +08:00
|
|
|
Optional<loc::MemRegionVal> LV = TheValueExpr.getAs<loc::MemRegionVal>();
|
2008-06-27 07:59:48 +08:00
|
|
|
if (!LV)
|
2010-10-21 07:38:56 +08:00
|
|
|
return;
|
2009-09-09 23:08:12 +08:00
|
|
|
|
2011-08-13 04:02:48 +08:00
|
|
|
const TypedValueRegion* R = dyn_cast<TypedValueRegion>(LV->stripCasts());
|
2009-07-30 02:17:40 +08:00
|
|
|
if (!R)
|
2010-10-21 07:38:56 +08:00
|
|
|
return;
|
2009-07-30 02:17:40 +08:00
|
|
|
|
2010-08-11 14:10:55 +08:00
|
|
|
QualType T = Ctx.getCanonicalType(R->getValueType());
|
2009-09-09 23:08:12 +08:00
|
|
|
|
2008-06-27 07:59:48 +08:00
|
|
|
// FIXME: If the pointee isn't an integer type, should we flag a warning?
|
|
|
|
// People can do weird stuff with pointers.
|
2009-09-09 23:08:12 +08:00
|
|
|
|
2013-04-09 10:30:33 +08:00
|
|
|
if (!T->isIntegralOrEnumerationType())
|
2010-10-21 07:38:56 +08:00
|
|
|
return;
|
2009-09-09 23:08:12 +08:00
|
|
|
|
2016-10-27 06:51:47 +08:00
|
|
|
uint64_t PrimitiveTypeSize = Ctx.getTypeSize(T);
|
2009-09-09 23:08:12 +08:00
|
|
|
|
2016-10-27 06:51:47 +08:00
|
|
|
if (PrimitiveTypeSize == CFNumberSize)
|
2010-10-21 07:38:56 +08:00
|
|
|
return;
|
2009-09-09 23:08:12 +08:00
|
|
|
|
2008-06-27 07:59:48 +08:00
|
|
|
// FIXME: We can actually create an abstract "CFNumber" object that has
|
|
|
|
// the bits initialized to the provided values.
|
2016-10-27 06:51:47 +08:00
|
|
|
ExplodedNode *N = C.generateNonFatalErrorNode();
|
2015-09-17 06:03:05 +08:00
|
|
|
if (N) {
|
2012-02-05 10:13:05 +08:00
|
|
|
SmallString<128> sbuf;
|
2010-10-21 07:38:56 +08:00
|
|
|
llvm::raw_svector_ostream os(sbuf);
|
2016-10-27 06:51:47 +08:00
|
|
|
bool isCreate = (FD->getIdentifier() == ICreate);
|
|
|
|
|
|
|
|
if (isCreate) {
|
|
|
|
os << (PrimitiveTypeSize == 8 ? "An " : "A ")
|
|
|
|
<< PrimitiveTypeSize << "-bit integer is used to initialize a "
|
|
|
|
<< "CFNumber object that represents "
|
|
|
|
<< (CFNumberSize == 8 ? "an " : "a ")
|
|
|
|
<< CFNumberSize << "-bit integer; ";
|
|
|
|
} else {
|
|
|
|
os << "A CFNumber object that represents "
|
|
|
|
<< (CFNumberSize == 8 ? "an " : "a ")
|
|
|
|
<< CFNumberSize << "-bit integer is used to initialize "
|
|
|
|
<< (PrimitiveTypeSize == 8 ? "an " : "a ")
|
|
|
|
<< PrimitiveTypeSize << "-bit integer; ";
|
|
|
|
}
|
2015-09-08 11:50:52 +08:00
|
|
|
|
2016-10-27 06:51:47 +08:00
|
|
|
if (PrimitiveTypeSize < CFNumberSize)
|
|
|
|
os << (CFNumberSize - PrimitiveTypeSize)
|
|
|
|
<< " bits of the CFNumber value will "
|
|
|
|
<< (isCreate ? "be garbage." : "overwrite adjacent storage.");
|
2010-10-21 07:38:56 +08:00
|
|
|
else
|
2016-10-27 06:51:47 +08:00
|
|
|
os << (PrimitiveTypeSize - CFNumberSize)
|
|
|
|
<< " bits of the integer value will be "
|
|
|
|
<< (isCreate ? "lost." : "garbage.");
|
2008-06-27 07:59:48 +08:00
|
|
|
|
2010-10-21 07:38:56 +08:00
|
|
|
if (!BT)
|
2016-10-27 06:51:47 +08:00
|
|
|
BT.reset(new APIMisuse(this, "Bad use of CFNumber APIs"));
|
2014-02-12 05:49:21 +08:00
|
|
|
|
2015-06-23 21:15:32 +08:00
|
|
|
auto report = llvm::make_unique<BugReport>(*BT, os.str(), N);
|
2010-10-21 07:38:56 +08:00
|
|
|
report->addRange(CE->getArg(2)->getSourceRange());
|
2015-06-23 21:15:32 +08:00
|
|
|
C.emitReport(std::move(report));
|
2010-10-21 07:38:56 +08:00
|
|
|
}
|
2008-06-27 07:59:48 +08:00
|
|
|
}
|
|
|
|
|
2009-07-14 08:43:42 +08:00
|
|
|
//===----------------------------------------------------------------------===//
|
2014-06-22 07:50:40 +08:00
|
|
|
// CFRetain/CFRelease/CFMakeCollectable/CFAutorelease checking for null arguments.
|
2009-07-14 08:43:42 +08:00
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
namespace {
|
2018-08-30 06:39:20 +08:00
|
|
|
class CFRetainReleaseChecker : public Checker<check::PreCall> {
|
|
|
|
mutable APIMisuse BT{this, "null passed to CF memory management function"};
|
|
|
|
CallDescription CFRetain{"CFRetain", 1},
|
|
|
|
CFRelease{"CFRelease", 1},
|
|
|
|
CFMakeCollectable{"CFMakeCollectable", 1},
|
|
|
|
CFAutorelease{"CFAutorelease", 1};
|
2016-02-11 03:11:58 +08:00
|
|
|
|
2009-07-14 08:43:42 +08:00
|
|
|
public:
|
2018-08-30 06:39:20 +08:00
|
|
|
void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
|
2009-07-14 08:43:42 +08:00
|
|
|
};
|
|
|
|
} // end anonymous namespace
|
|
|
|
|
2018-08-30 06:39:20 +08:00
|
|
|
void CFRetainReleaseChecker::checkPreCall(const CallEvent &Call,
|
2011-08-13 07:37:29 +08:00
|
|
|
CheckerContext &C) const {
|
2018-08-30 06:39:20 +08:00
|
|
|
// TODO: Make this check part of CallDescription.
|
|
|
|
if (!Call.isGlobalCFunction())
|
2010-07-06 10:34:42 +08:00
|
|
|
return;
|
2015-09-08 11:50:52 +08:00
|
|
|
|
2014-06-22 07:50:40 +08:00
|
|
|
// Check if we called CFRetain/CFRelease/CFMakeCollectable/CFAutorelease.
|
2018-08-30 06:39:20 +08:00
|
|
|
if (!(Call.isCalled(CFRetain) || Call.isCalled(CFRelease) ||
|
|
|
|
Call.isCalled(CFMakeCollectable) || Call.isCalled(CFAutorelease)))
|
2010-07-06 10:34:42 +08:00
|
|
|
return;
|
|
|
|
|
|
|
|
// Get the argument's value.
|
2018-08-30 06:39:20 +08:00
|
|
|
SVal ArgVal = Call.getArgSVal(0);
|
2013-02-21 06:23:23 +08:00
|
|
|
Optional<DefinedSVal> DefArgVal = ArgVal.getAs<DefinedSVal>();
|
2010-07-06 10:34:42 +08:00
|
|
|
if (!DefArgVal)
|
|
|
|
return;
|
|
|
|
|
2018-08-30 06:39:20 +08:00
|
|
|
// Is it null?
|
|
|
|
ProgramStateRef state = C.getState();
|
|
|
|
ProgramStateRef stateNonNull, stateNull;
|
|
|
|
std::tie(stateNonNull, stateNull) = state->assume(*DefArgVal);
|
2010-07-06 10:34:42 +08:00
|
|
|
|
2018-08-30 06:39:20 +08:00
|
|
|
if (!stateNonNull) {
|
|
|
|
ExplodedNode *N = C.generateErrorNode(stateNull);
|
2010-07-06 10:34:42 +08:00
|
|
|
if (!N)
|
|
|
|
return;
|
2009-09-09 23:08:12 +08:00
|
|
|
|
2018-08-30 06:39:20 +08:00
|
|
|
SmallString<64> Str;
|
|
|
|
raw_svector_ostream OS(Str);
|
|
|
|
OS << "Null pointer argument in call to "
|
|
|
|
<< cast<FunctionDecl>(Call.getDecl())->getName();
|
2009-07-14 08:43:42 +08:00
|
|
|
|
2018-08-30 06:39:20 +08:00
|
|
|
auto report = llvm::make_unique<BugReport>(BT, OS.str(), N);
|
|
|
|
report->addRange(Call.getArgSourceRange(0));
|
2018-10-24 02:24:53 +08:00
|
|
|
bugreporter::trackExpressionValue(N, Call.getArgExpr(0), *report);
|
2015-06-23 21:15:32 +08:00
|
|
|
C.emitReport(std::move(report));
|
2010-07-06 10:34:42 +08:00
|
|
|
return;
|
|
|
|
}
|
2009-09-09 23:08:12 +08:00
|
|
|
|
2010-07-06 10:34:42 +08:00
|
|
|
// From here on, we know the argument is non-null.
|
2018-08-30 06:39:20 +08:00
|
|
|
C.addTransition(stateNonNull);
|
2009-07-14 08:43:42 +08:00
|
|
|
}
|
|
|
|
|
2009-11-20 13:27:05 +08:00
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// Check for sending 'retain', 'release', or 'autorelease' directly to a Class.
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
namespace {
|
2011-03-01 09:16:21 +08:00
|
|
|
class ClassReleaseChecker : public Checker<check::PreObjCMessage> {
|
2011-02-23 08:16:10 +08:00
|
|
|
mutable Selector releaseS;
|
|
|
|
mutable Selector retainS;
|
|
|
|
mutable Selector autoreleaseS;
|
|
|
|
mutable Selector drainS;
|
2014-03-08 04:03:18 +08:00
|
|
|
mutable std::unique_ptr<BugType> BT;
|
2009-11-20 13:27:05 +08:00
|
|
|
|
2011-02-23 08:16:10 +08:00
|
|
|
public:
|
2012-07-03 03:28:04 +08:00
|
|
|
void checkPreObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const;
|
2009-11-20 13:27:05 +08:00
|
|
|
};
|
2016-02-11 03:11:58 +08:00
|
|
|
} // end anonymous namespace
|
2009-11-20 13:27:05 +08:00
|
|
|
|
2012-07-03 03:28:04 +08:00
|
|
|
void ClassReleaseChecker::checkPreObjCMessage(const ObjCMethodCall &msg,
|
2011-02-23 08:16:10 +08:00
|
|
|
CheckerContext &C) const {
|
2010-10-21 07:38:56 +08:00
|
|
|
if (!BT) {
|
2014-02-12 05:49:21 +08:00
|
|
|
BT.reset(new APIMisuse(
|
|
|
|
this, "message incorrectly sent to class instead of class instance"));
|
|
|
|
|
2010-10-21 07:38:56 +08:00
|
|
|
ASTContext &Ctx = C.getASTContext();
|
|
|
|
releaseS = GetNullarySelector("release", Ctx);
|
|
|
|
retainS = GetNullarySelector("retain", Ctx);
|
|
|
|
autoreleaseS = GetNullarySelector("autorelease", Ctx);
|
|
|
|
drainS = GetNullarySelector("drain", Ctx);
|
|
|
|
}
|
2015-09-08 11:50:52 +08:00
|
|
|
|
2011-01-25 08:03:53 +08:00
|
|
|
if (msg.isInstanceMessage())
|
2009-11-20 13:27:05 +08:00
|
|
|
return;
|
2011-01-25 08:03:53 +08:00
|
|
|
const ObjCInterfaceDecl *Class = msg.getReceiverInterface();
|
|
|
|
assert(Class);
|
Overhaul the AST representation of Objective-C message send
expressions, to improve source-location information, clarify the
actual receiver of the message, and pave the way for proper C++
support. The ObjCMessageExpr node represents four different kinds of
message sends in a single AST node:
1) Send to a object instance described by an expression (e.g., [x method:5])
2) Send to a class described by the class name (e.g., [NSString method:5])
3) Send to a superclass class (e.g, [super method:5] in class method)
4) Send to a superclass instance (e.g., [super method:5] in instance method)
Previously these four cases where tangled together. Now, they have
more distinct representations. Specific changes:
1) Unchanged; the object instance is represented by an Expr*.
2) Previously stored the ObjCInterfaceDecl* referring to the class
receiving the message. Now stores a TypeSourceInfo* so that we know
how the class was spelled. This both maintains typedef information
and opens the door for more complicated C++ types (e.g., dependent
types). There was an alternative, unused representation of these
sends by naming the class via an IdentifierInfo *. In practice, we
either had an ObjCInterfaceDecl *, from which we would get the
IdentifierInfo *, or we fell into the case below...
3) Previously represented by a class message whose IdentifierInfo *
referred to "super". Sema and CodeGen would use isStr("super") to
determine if they had a send to super. Now represented as a
"class super" send, where we have both the location of the "super"
keyword and the ObjCInterfaceDecl* of the superclass we're
targetting (statically).
4) Previously represented by an instance message whose receiver is a
an ObjCSuperExpr, which Sema and CodeGen would check for via
isa<ObjCSuperExpr>(). Now represented as an "instance super" send,
where we have both the location of the "super" keyword and the
ObjCInterfaceDecl* of the superclass we're targetting
(statically). Note that ObjCSuperExpr only has one remaining use in
the AST, which is for "super.prop" references.
The new representation of ObjCMessageExpr is 2 pointers smaller than
the old one, since it combines more storage. It also eliminates a leak
when we loaded message-send expressions from a precompiled header. The
representation also feels much cleaner to me; comments welcome!
This patch attempts to maintain the same semantics we previously had
with Objective-C message sends. In several places, there are massive
changes that boil down to simply replacing a nested-if structure such
as:
if (message has a receiver expression) {
// instance message
if (isa<ObjCSuperExpr>(...)) {
// send to super
} else {
// send to an object
}
} else {
// class message
if (name->isStr("super")) {
// class send to super
} else {
// send to class
}
}
with a switch
switch (E->getReceiverKind()) {
case ObjCMessageExpr::SuperInstance: ...
case ObjCMessageExpr::Instance: ...
case ObjCMessageExpr::SuperClass: ...
case ObjCMessageExpr::Class:...
}
There are quite a few places (particularly in the checkers) where
send-to-super is effectively ignored. I've placed FIXMEs in most of
them, and attempted to address send-to-super in a reasonable way. This
could use some review.
llvm-svn: 101972
2010-04-21 08:45:42 +08:00
|
|
|
|
2011-01-25 08:03:53 +08:00
|
|
|
Selector S = msg.getSelector();
|
2009-11-20 18:03:00 +08:00
|
|
|
if (!(S == releaseS || S == retainS || S == autoreleaseS || S == drainS))
|
2009-11-20 13:27:05 +08:00
|
|
|
return;
|
2015-09-08 11:50:52 +08:00
|
|
|
|
2015-09-17 06:03:05 +08:00
|
|
|
if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
|
2012-02-05 10:13:05 +08:00
|
|
|
SmallString<200> buf;
|
2010-10-21 07:38:56 +08:00
|
|
|
llvm::raw_svector_ostream os(buf);
|
2009-11-20 13:27:05 +08:00
|
|
|
|
2014-01-04 01:59:55 +08:00
|
|
|
os << "The '";
|
|
|
|
S.print(os);
|
|
|
|
os << "' message should be sent to instances "
|
2010-10-21 07:38:56 +08:00
|
|
|
"of class '" << Class->getName()
|
|
|
|
<< "' and not the class directly";
|
2015-09-08 11:50:52 +08:00
|
|
|
|
2015-06-23 21:15:32 +08:00
|
|
|
auto report = llvm::make_unique<BugReport>(*BT, os.str(), N);
|
2011-01-25 08:03:53 +08:00
|
|
|
report->addRange(msg.getSourceRange());
|
2015-06-23 21:15:32 +08:00
|
|
|
C.emitReport(std::move(report));
|
2010-10-21 07:38:56 +08:00
|
|
|
}
|
2009-11-20 13:27:05 +08:00
|
|
|
}
|
|
|
|
|
2011-03-14 04:35:21 +08:00
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// Check for passing non-Objective-C types to variadic methods that expect
|
|
|
|
// only Objective-C types.
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
class VariadicMethodTypeChecker : public Checker<check::PreObjCMessage> {
|
|
|
|
mutable Selector arrayWithObjectsS;
|
|
|
|
mutable Selector dictionaryWithObjectsAndKeysS;
|
|
|
|
mutable Selector setWithObjectsS;
|
2012-04-07 03:06:01 +08:00
|
|
|
mutable Selector orderedSetWithObjectsS;
|
2011-03-14 04:35:21 +08:00
|
|
|
mutable Selector initWithObjectsS;
|
|
|
|
mutable Selector initWithObjectsAndKeysS;
|
2014-03-08 04:03:18 +08:00
|
|
|
mutable std::unique_ptr<BugType> BT;
|
2011-03-14 04:35:21 +08:00
|
|
|
|
2012-07-03 03:28:04 +08:00
|
|
|
bool isVariadicMessage(const ObjCMethodCall &msg) const;
|
2011-03-14 04:35:21 +08:00
|
|
|
|
|
|
|
public:
|
2012-07-03 03:28:04 +08:00
|
|
|
void checkPreObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const;
|
2011-03-14 04:35:21 +08:00
|
|
|
};
|
2016-02-11 03:11:58 +08:00
|
|
|
} // end anonymous namespace
|
2011-03-14 04:35:21 +08:00
|
|
|
|
|
|
|
/// isVariadicMessage - Returns whether the given message is a variadic message,
|
|
|
|
/// where all arguments must be Objective-C types.
|
|
|
|
bool
|
2012-07-03 03:28:04 +08:00
|
|
|
VariadicMethodTypeChecker::isVariadicMessage(const ObjCMethodCall &msg) const {
|
|
|
|
const ObjCMethodDecl *MD = msg.getDecl();
|
2015-09-08 11:50:52 +08:00
|
|
|
|
2011-04-13 05:47:05 +08:00
|
|
|
if (!MD || !MD->isVariadic() || isa<ObjCProtocolDecl>(MD->getDeclContext()))
|
2011-03-14 04:35:21 +08:00
|
|
|
return false;
|
2015-09-08 11:50:52 +08:00
|
|
|
|
2011-03-14 04:35:21 +08:00
|
|
|
Selector S = msg.getSelector();
|
2015-09-08 11:50:52 +08:00
|
|
|
|
2011-03-14 04:35:21 +08:00
|
|
|
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
|
|
|
|
// to false positives, for example if there existed a class that had an
|
|
|
|
// initWithObjects: implementation that does accept non-Objective-C pointer
|
|
|
|
// types, but the chance of that happening is pretty small compared to the
|
|
|
|
// gains that this analysis gives.
|
|
|
|
const ObjCInterfaceDecl *Class = MD->getClassInterface();
|
|
|
|
|
2012-06-12 00:40:37 +08:00
|
|
|
switch (findKnownClass(Class)) {
|
|
|
|
case FC_NSArray:
|
|
|
|
case FC_NSOrderedSet:
|
|
|
|
case FC_NSSet:
|
|
|
|
return S == initWithObjectsS;
|
|
|
|
case FC_NSDictionary:
|
|
|
|
return S == initWithObjectsAndKeysS;
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
2011-03-14 04:35:21 +08:00
|
|
|
} else {
|
|
|
|
const ObjCInterfaceDecl *Class = msg.getReceiverInterface();
|
|
|
|
|
2012-06-12 00:40:37 +08:00
|
|
|
switch (findKnownClass(Class)) {
|
|
|
|
case FC_NSArray:
|
|
|
|
return S == arrayWithObjectsS;
|
|
|
|
case FC_NSOrderedSet:
|
|
|
|
return S == orderedSetWithObjectsS;
|
|
|
|
case FC_NSSet:
|
|
|
|
return S == setWithObjectsS;
|
|
|
|
case FC_NSDictionary:
|
|
|
|
return S == dictionaryWithObjectsAndKeysS;
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
2011-03-14 04:35:21 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-07-03 03:28:04 +08:00
|
|
|
void VariadicMethodTypeChecker::checkPreObjCMessage(const ObjCMethodCall &msg,
|
2011-03-14 04:35:21 +08:00
|
|
|
CheckerContext &C) const {
|
|
|
|
if (!BT) {
|
2014-02-12 05:49:21 +08:00
|
|
|
BT.reset(new APIMisuse(this,
|
|
|
|
"Arguments passed to variadic method aren't all "
|
2011-03-14 04:35:21 +08:00
|
|
|
"Objective-C pointer types"));
|
|
|
|
|
|
|
|
ASTContext &Ctx = C.getASTContext();
|
|
|
|
arrayWithObjectsS = GetUnarySelector("arrayWithObjects", Ctx);
|
2015-09-08 11:50:52 +08:00
|
|
|
dictionaryWithObjectsAndKeysS =
|
2011-03-14 04:35:21 +08:00
|
|
|
GetUnarySelector("dictionaryWithObjectsAndKeys", Ctx);
|
|
|
|
setWithObjectsS = GetUnarySelector("setWithObjects", Ctx);
|
2012-04-07 03:06:01 +08:00
|
|
|
orderedSetWithObjectsS = GetUnarySelector("orderedSetWithObjects", Ctx);
|
2011-03-14 04:35:21 +08:00
|
|
|
|
|
|
|
initWithObjectsS = GetUnarySelector("initWithObjects", Ctx);
|
|
|
|
initWithObjectsAndKeysS = GetUnarySelector("initWithObjectsAndKeys", Ctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isVariadicMessage(msg))
|
|
|
|
return;
|
|
|
|
|
|
|
|
// We are not interested in the selector arguments since they have
|
|
|
|
// well-defined types, so the compiler will issue a warning for them.
|
|
|
|
unsigned variadicArgsBegin = msg.getSelector().getNumArgs();
|
|
|
|
|
|
|
|
// We're not interested in the last argument since it has to be nil or the
|
|
|
|
// compiler would have issued a warning for it elsewhere.
|
|
|
|
unsigned variadicArgsEnd = msg.getNumArgs() - 1;
|
|
|
|
|
|
|
|
if (variadicArgsEnd <= variadicArgsBegin)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Verify that all arguments have Objective-C types.
|
2013-02-21 06:23:23 +08:00
|
|
|
Optional<ExplodedNode*> errorNode;
|
2013-11-27 13:22:15 +08:00
|
|
|
|
2011-03-14 04:35:21 +08:00
|
|
|
for (unsigned I = variadicArgsBegin; I != variadicArgsEnd; ++I) {
|
2012-07-03 03:28:04 +08:00
|
|
|
QualType ArgTy = msg.getArgExpr(I)->getType();
|
2011-03-14 04:35:21 +08:00
|
|
|
if (ArgTy->isObjCObjectPointerType())
|
|
|
|
continue;
|
|
|
|
|
2011-04-19 09:16:46 +08:00
|
|
|
// Block pointers are treaded as Objective-C pointers.
|
|
|
|
if (ArgTy->isBlockPointerType())
|
|
|
|
continue;
|
|
|
|
|
2011-03-16 08:22:51 +08:00
|
|
|
// Ignore pointer constants.
|
2013-02-20 13:52:05 +08:00
|
|
|
if (msg.getArgSVal(I).getAs<loc::ConcreteInt>())
|
2011-03-16 08:22:51 +08:00
|
|
|
continue;
|
2015-09-08 11:50:52 +08:00
|
|
|
|
2011-03-17 12:10:25 +08:00
|
|
|
// Ignore pointer types annotated with 'NSObject' attribute.
|
|
|
|
if (C.getASTContext().isObjCNSObjectType(ArgTy))
|
|
|
|
continue;
|
2015-09-08 11:50:52 +08:00
|
|
|
|
2011-03-17 12:01:35 +08:00
|
|
|
// Ignore CF references, which can be toll-free bridged.
|
2011-07-17 03:50:32 +08:00
|
|
|
if (coreFoundation::isCFObjectRef(ArgTy))
|
2011-03-17 12:01:35 +08:00
|
|
|
continue;
|
2011-03-16 08:22:51 +08:00
|
|
|
|
2011-03-15 03:50:37 +08:00
|
|
|
// Generate only one error node to use for all bug reports.
|
2012-07-03 03:28:04 +08:00
|
|
|
if (!errorNode.hasValue())
|
2015-09-17 06:03:05 +08:00
|
|
|
errorNode = C.generateNonFatalErrorNode();
|
2011-03-15 03:50:37 +08:00
|
|
|
|
|
|
|
if (!errorNode.getValue())
|
2011-03-14 04:35:21 +08:00
|
|
|
continue;
|
|
|
|
|
2012-02-05 10:13:05 +08:00
|
|
|
SmallString<128> sbuf;
|
2011-03-14 04:35:21 +08:00
|
|
|
llvm::raw_svector_ostream os(sbuf);
|
|
|
|
|
2012-07-03 03:28:04 +08:00
|
|
|
StringRef TypeName = GetReceiverInterfaceName(msg);
|
|
|
|
if (!TypeName.empty())
|
2011-03-14 04:35:21 +08:00
|
|
|
os << "Argument to '" << TypeName << "' method '";
|
|
|
|
else
|
|
|
|
os << "Argument to method '";
|
|
|
|
|
2014-01-04 01:59:55 +08:00
|
|
|
msg.getSelector().print(os);
|
|
|
|
os << "' should be an Objective-C pointer type, not '";
|
2012-07-03 03:28:04 +08:00
|
|
|
ArgTy.print(os, C.getLangOpts());
|
|
|
|
os << "'";
|
2011-03-14 04:35:21 +08:00
|
|
|
|
2015-06-23 21:15:32 +08:00
|
|
|
auto R = llvm::make_unique<BugReport>(*BT, os.str(), errorNode.getValue());
|
2011-03-14 04:35:21 +08:00
|
|
|
R->addRange(msg.getArgSourceRange(I));
|
2015-06-23 21:15:32 +08:00
|
|
|
C.emitReport(std::move(R));
|
2011-03-14 04:35:21 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-06-12 00:40:41 +08:00
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// Improves the modeling of loops over Cocoa collections.
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
2013-06-22 08:23:26 +08:00
|
|
|
// The map from container symbol to the container count symbol.
|
Misc typos fixes in ./lib folder
Summary: Found via `codespell -q 3 -I ../clang-whitelist.txt -L uint,importd,crasher,gonna,cant,ue,ons,orign,ned`
Reviewers: teemperor
Reviewed By: teemperor
Subscribers: teemperor, jholewinski, jvesely, nhaehnle, whisperity, jfb, cfe-commits
Differential Revision: https://reviews.llvm.org/D55475
llvm-svn: 348755
2018-12-10 20:37:46 +08:00
|
|
|
// We currently will remember the last container count symbol encountered.
|
2013-06-22 08:23:26 +08:00
|
|
|
REGISTER_MAP_WITH_PROGRAMSTATE(ContainerCountMap, SymbolRef, SymbolRef)
|
2013-11-09 01:23:33 +08:00
|
|
|
REGISTER_MAP_WITH_PROGRAMSTATE(ContainerNonEmptyMap, SymbolRef, bool)
|
2013-06-22 08:23:26 +08:00
|
|
|
|
2012-06-12 00:40:41 +08:00
|
|
|
namespace {
|
|
|
|
class ObjCLoopChecker
|
2013-06-22 08:23:26 +08:00
|
|
|
: public Checker<check::PostStmt<ObjCForCollectionStmt>,
|
|
|
|
check::PostObjCMessage,
|
|
|
|
check::DeadSymbols,
|
|
|
|
check::PointerEscape > {
|
|
|
|
mutable IdentifierInfo *CountSelectorII;
|
|
|
|
|
|
|
|
bool isCollectionCountMethod(const ObjCMethodCall &M,
|
|
|
|
CheckerContext &C) const;
|
|
|
|
|
2012-06-12 00:40:41 +08:00
|
|
|
public:
|
2014-05-27 10:45:47 +08:00
|
|
|
ObjCLoopChecker() : CountSelectorII(nullptr) {}
|
2012-06-12 00:40:41 +08:00
|
|
|
void checkPostStmt(const ObjCForCollectionStmt *FCS, CheckerContext &C) const;
|
2013-06-22 08:23:26 +08:00
|
|
|
void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;
|
|
|
|
void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
|
|
|
|
ProgramStateRef checkPointerEscape(ProgramStateRef State,
|
|
|
|
const InvalidatedSymbols &Escaped,
|
|
|
|
const CallEvent *Call,
|
|
|
|
PointerEscapeKind Kind) const;
|
2012-06-12 00:40:41 +08:00
|
|
|
};
|
2016-02-11 03:11:58 +08:00
|
|
|
} // end anonymous namespace
|
2012-06-12 00:40:41 +08:00
|
|
|
|
|
|
|
static bool isKnownNonNilCollectionType(QualType T) {
|
|
|
|
const ObjCObjectPointerType *PT = T->getAs<ObjCObjectPointerType>();
|
|
|
|
if (!PT)
|
|
|
|
return false;
|
2015-09-08 11:50:52 +08:00
|
|
|
|
2012-06-12 00:40:41 +08:00
|
|
|
const ObjCInterfaceDecl *ID = PT->getInterfaceDecl();
|
|
|
|
if (!ID)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
switch (findKnownClass(ID)) {
|
|
|
|
case FC_NSArray:
|
|
|
|
case FC_NSDictionary:
|
|
|
|
case FC_NSEnumerator:
|
|
|
|
case FC_NSOrderedSet:
|
|
|
|
case FC_NSSet:
|
|
|
|
return true;
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-04-27 05:43:01 +08:00
|
|
|
/// Assumes that the collection is non-nil.
|
|
|
|
///
|
|
|
|
/// If the collection is known to be nil, returns NULL to indicate an infeasible
|
|
|
|
/// path.
|
|
|
|
static ProgramStateRef checkCollectionNonNil(CheckerContext &C,
|
|
|
|
ProgramStateRef State,
|
|
|
|
const ObjCForCollectionStmt *FCS) {
|
|
|
|
if (!State)
|
2014-05-27 10:45:47 +08:00
|
|
|
return nullptr;
|
2013-04-27 05:43:01 +08:00
|
|
|
|
|
|
|
SVal CollectionVal = C.getSVal(FCS->getCollection());
|
|
|
|
Optional<DefinedSVal> KnownCollection = CollectionVal.getAs<DefinedSVal>();
|
|
|
|
if (!KnownCollection)
|
|
|
|
return State;
|
|
|
|
|
|
|
|
ProgramStateRef StNonNil, StNil;
|
2014-03-02 21:01:17 +08:00
|
|
|
std::tie(StNonNil, StNil) = State->assume(*KnownCollection);
|
2013-04-27 05:43:01 +08:00
|
|
|
if (StNil && !StNonNil) {
|
|
|
|
// The collection is nil. This path is infeasible.
|
2014-05-27 10:45:47 +08:00
|
|
|
return nullptr;
|
2013-04-27 05:43:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return StNonNil;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Assumes that the collection elements are non-nil.
|
|
|
|
///
|
|
|
|
/// This only applies if the collection is one of those known not to contain
|
|
|
|
/// nil values.
|
|
|
|
static ProgramStateRef checkElementNonNil(CheckerContext &C,
|
|
|
|
ProgramStateRef State,
|
|
|
|
const ObjCForCollectionStmt *FCS) {
|
|
|
|
if (!State)
|
2014-05-27 10:45:47 +08:00
|
|
|
return nullptr;
|
2013-04-27 05:43:01 +08:00
|
|
|
|
2012-06-12 00:40:41 +08:00
|
|
|
// See if the collection is one where we /know/ the elements are non-nil.
|
2013-04-27 05:43:01 +08:00
|
|
|
if (!isKnownNonNilCollectionType(FCS->getCollection()->getType()))
|
|
|
|
return State;
|
|
|
|
|
|
|
|
const LocationContext *LCtx = C.getLocationContext();
|
2012-06-12 00:40:41 +08:00
|
|
|
const Stmt *Element = FCS->getElement();
|
2013-04-27 05:43:01 +08:00
|
|
|
|
|
|
|
// FIXME: Copied from ExprEngineObjC.
|
|
|
|
Optional<Loc> ElementLoc;
|
2012-06-12 00:40:41 +08:00
|
|
|
if (const DeclStmt *DS = dyn_cast<DeclStmt>(Element)) {
|
|
|
|
const VarDecl *ElemDecl = cast<VarDecl>(DS->getSingleDecl());
|
2014-05-27 10:45:47 +08:00
|
|
|
assert(ElemDecl->getInit() == nullptr);
|
2013-04-27 05:43:01 +08:00
|
|
|
ElementLoc = State->getLValue(ElemDecl, LCtx);
|
2012-06-12 00:40:41 +08:00
|
|
|
} else {
|
2013-04-27 05:43:01 +08:00
|
|
|
ElementLoc = State->getSVal(Element, LCtx).getAs<Loc>();
|
2012-06-12 00:40:41 +08:00
|
|
|
}
|
|
|
|
|
2013-04-27 05:43:01 +08:00
|
|
|
if (!ElementLoc)
|
|
|
|
return State;
|
2012-06-12 00:40:41 +08:00
|
|
|
|
|
|
|
// Go ahead and assume the value is non-nil.
|
2013-04-27 05:43:01 +08:00
|
|
|
SVal Val = State->getSVal(*ElementLoc);
|
|
|
|
return State->assume(Val.castAs<DefinedOrUnknownSVal>(), true);
|
|
|
|
}
|
|
|
|
|
2013-06-22 08:23:26 +08:00
|
|
|
/// Returns NULL state if the collection is known to contain elements
|
|
|
|
/// (or is known not to contain elements if the Assumption parameter is false.)
|
2013-11-08 09:15:35 +08:00
|
|
|
static ProgramStateRef
|
|
|
|
assumeCollectionNonEmpty(CheckerContext &C, ProgramStateRef State,
|
|
|
|
SymbolRef CollectionS, bool Assumption) {
|
|
|
|
if (!State || !CollectionS)
|
2013-06-22 08:23:26 +08:00
|
|
|
return State;
|
2013-11-08 09:15:35 +08:00
|
|
|
|
2013-06-22 08:23:26 +08:00
|
|
|
const SymbolRef *CountS = State->get<ContainerCountMap>(CollectionS);
|
2013-11-08 09:15:35 +08:00
|
|
|
if (!CountS) {
|
2013-11-09 01:23:33 +08:00
|
|
|
const bool *KnownNonEmpty = State->get<ContainerNonEmptyMap>(CollectionS);
|
2013-11-08 09:15:35 +08:00
|
|
|
if (!KnownNonEmpty)
|
|
|
|
return State->set<ContainerNonEmptyMap>(CollectionS, Assumption);
|
2014-05-27 10:45:47 +08:00
|
|
|
return (Assumption == *KnownNonEmpty) ? State : nullptr;
|
2013-11-08 09:15:35 +08:00
|
|
|
}
|
2013-06-22 08:23:26 +08:00
|
|
|
|
|
|
|
SValBuilder &SvalBuilder = C.getSValBuilder();
|
|
|
|
SVal CountGreaterThanZeroVal =
|
|
|
|
SvalBuilder.evalBinOp(State, BO_GT,
|
|
|
|
nonloc::SymbolVal(*CountS),
|
|
|
|
SvalBuilder.makeIntVal(0, (*CountS)->getType()),
|
|
|
|
SvalBuilder.getConditionType());
|
|
|
|
Optional<DefinedSVal> CountGreaterThanZero =
|
|
|
|
CountGreaterThanZeroVal.getAs<DefinedSVal>();
|
|
|
|
if (!CountGreaterThanZero) {
|
|
|
|
// The SValBuilder cannot construct a valid SVal for this condition.
|
|
|
|
// This means we cannot properly reason about it.
|
|
|
|
return State;
|
|
|
|
}
|
|
|
|
|
|
|
|
return State->assume(*CountGreaterThanZero, Assumption);
|
|
|
|
}
|
|
|
|
|
2013-11-08 09:15:35 +08:00
|
|
|
static ProgramStateRef
|
|
|
|
assumeCollectionNonEmpty(CheckerContext &C, ProgramStateRef State,
|
|
|
|
const ObjCForCollectionStmt *FCS,
|
|
|
|
bool Assumption) {
|
|
|
|
if (!State)
|
2014-05-27 10:45:47 +08:00
|
|
|
return nullptr;
|
2013-11-08 09:15:35 +08:00
|
|
|
|
2018-01-18 04:27:29 +08:00
|
|
|
SymbolRef CollectionS = C.getSVal(FCS->getCollection()).getAsSymbol();
|
2013-11-08 09:15:35 +08:00
|
|
|
return assumeCollectionNonEmpty(C, State, CollectionS, Assumption);
|
|
|
|
}
|
|
|
|
|
2013-06-22 08:23:26 +08:00
|
|
|
/// If the fist block edge is a back edge, we are reentering the loop.
|
|
|
|
static bool alreadyExecutedAtLeastOneLoopIteration(const ExplodedNode *N,
|
|
|
|
const ObjCForCollectionStmt *FCS) {
|
|
|
|
if (!N)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
ProgramPoint P = N->getLocation();
|
|
|
|
if (Optional<BlockEdge> BE = P.getAs<BlockEdge>()) {
|
2015-12-28 21:06:58 +08:00
|
|
|
return BE->getSrc()->getLoopTarget() == FCS;
|
2013-06-22 08:23:26 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Keep looking for a block edge.
|
|
|
|
for (ExplodedNode::const_pred_iterator I = N->pred_begin(),
|
|
|
|
E = N->pred_end(); I != E; ++I) {
|
|
|
|
if (alreadyExecutedAtLeastOneLoopIteration(*I, FCS))
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-04-27 05:43:01 +08:00
|
|
|
void ObjCLoopChecker::checkPostStmt(const ObjCForCollectionStmt *FCS,
|
|
|
|
CheckerContext &C) const {
|
2013-06-22 08:23:26 +08:00
|
|
|
ProgramStateRef State = C.getState();
|
|
|
|
|
2013-04-27 05:43:01 +08:00
|
|
|
// Check if this is the branch for the end of the loop.
|
|
|
|
SVal CollectionSentinel = C.getSVal(FCS);
|
2013-06-22 08:23:26 +08:00
|
|
|
if (CollectionSentinel.isZeroConstant()) {
|
|
|
|
if (!alreadyExecutedAtLeastOneLoopIteration(C.getPredecessor(), FCS))
|
|
|
|
State = assumeCollectionNonEmpty(C, State, FCS, /*Assumption*/false);
|
2013-04-27 05:43:01 +08:00
|
|
|
|
2013-06-22 08:23:26 +08:00
|
|
|
// Otherwise, this is a branch that goes through the loop body.
|
|
|
|
} else {
|
|
|
|
State = checkCollectionNonNil(C, State, FCS);
|
|
|
|
State = checkElementNonNil(C, State, FCS);
|
|
|
|
State = assumeCollectionNonEmpty(C, State, FCS, /*Assumption*/true);
|
|
|
|
}
|
2015-09-08 11:50:52 +08:00
|
|
|
|
2013-04-27 05:43:01 +08:00
|
|
|
if (!State)
|
2015-09-17 06:03:05 +08:00
|
|
|
C.generateSink(C.getState(), C.getPredecessor());
|
2013-04-27 05:43:01 +08:00
|
|
|
else if (State != C.getState())
|
|
|
|
C.addTransition(State);
|
2012-06-12 00:40:41 +08:00
|
|
|
}
|
|
|
|
|
2013-06-22 08:23:26 +08:00
|
|
|
bool ObjCLoopChecker::isCollectionCountMethod(const ObjCMethodCall &M,
|
|
|
|
CheckerContext &C) const {
|
|
|
|
Selector S = M.getSelector();
|
|
|
|
// Initialize the identifiers on first use.
|
|
|
|
if (!CountSelectorII)
|
|
|
|
CountSelectorII = &C.getASTContext().Idents.get("count");
|
|
|
|
|
|
|
|
// If the method returns collection count, record the value.
|
2015-12-28 21:06:58 +08:00
|
|
|
return S.isUnarySelector() &&
|
|
|
|
(S.getIdentifierInfoForSlot(0) == CountSelectorII);
|
2013-06-22 08:23:26 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void ObjCLoopChecker::checkPostObjCMessage(const ObjCMethodCall &M,
|
|
|
|
CheckerContext &C) const {
|
|
|
|
if (!M.isInstanceMessage())
|
|
|
|
return;
|
|
|
|
|
|
|
|
const ObjCInterfaceDecl *ClassID = M.getReceiverInterface();
|
|
|
|
if (!ClassID)
|
|
|
|
return;
|
|
|
|
|
|
|
|
FoundationClass Class = findKnownClass(ClassID);
|
|
|
|
if (Class != FC_NSDictionary &&
|
|
|
|
Class != FC_NSArray &&
|
2013-11-05 03:13:08 +08:00
|
|
|
Class != FC_NSSet &&
|
|
|
|
Class != FC_NSOrderedSet)
|
2013-06-22 08:23:26 +08:00
|
|
|
return;
|
|
|
|
|
|
|
|
SymbolRef ContainerS = M.getReceiverSVal().getAsSymbol();
|
|
|
|
if (!ContainerS)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// If we are processing a call to "count", get the symbolic value returned by
|
|
|
|
// a call to "count" and add it to the map.
|
|
|
|
if (!isCollectionCountMethod(M, C))
|
|
|
|
return;
|
2015-09-08 11:50:52 +08:00
|
|
|
|
2013-06-22 08:23:26 +08:00
|
|
|
const Expr *MsgExpr = M.getOriginExpr();
|
|
|
|
SymbolRef CountS = C.getSVal(MsgExpr).getAsSymbol();
|
|
|
|
if (CountS) {
|
|
|
|
ProgramStateRef State = C.getState();
|
2013-11-08 09:15:35 +08:00
|
|
|
|
2013-06-22 08:23:26 +08:00
|
|
|
C.getSymbolManager().addSymbolDependency(ContainerS, CountS);
|
|
|
|
State = State->set<ContainerCountMap>(ContainerS, CountS);
|
2013-11-08 09:15:35 +08:00
|
|
|
|
2013-11-09 01:23:33 +08:00
|
|
|
if (const bool *NonEmpty = State->get<ContainerNonEmptyMap>(ContainerS)) {
|
2013-11-08 09:15:35 +08:00
|
|
|
State = State->remove<ContainerNonEmptyMap>(ContainerS);
|
|
|
|
State = assumeCollectionNonEmpty(C, State, ContainerS, *NonEmpty);
|
|
|
|
}
|
|
|
|
|
2013-06-22 08:23:26 +08:00
|
|
|
C.addTransition(State);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-11-08 09:15:35 +08:00
|
|
|
static SymbolRef getMethodReceiverIfKnownImmutable(const CallEvent *Call) {
|
|
|
|
const ObjCMethodCall *Message = dyn_cast_or_null<ObjCMethodCall>(Call);
|
|
|
|
if (!Message)
|
2014-05-27 10:45:47 +08:00
|
|
|
return nullptr;
|
2013-11-08 09:15:35 +08:00
|
|
|
|
|
|
|
const ObjCMethodDecl *MD = Message->getDecl();
|
|
|
|
if (!MD)
|
2014-05-27 10:45:47 +08:00
|
|
|
return nullptr;
|
2013-11-08 09:15:35 +08:00
|
|
|
|
|
|
|
const ObjCInterfaceDecl *StaticClass;
|
|
|
|
if (isa<ObjCProtocolDecl>(MD->getDeclContext())) {
|
|
|
|
// We can't find out where the method was declared without doing more work.
|
|
|
|
// Instead, see if the receiver is statically typed as a known immutable
|
|
|
|
// collection.
|
|
|
|
StaticClass = Message->getOriginExpr()->getReceiverInterface();
|
|
|
|
} else {
|
|
|
|
StaticClass = MD->getClassInterface();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!StaticClass)
|
2014-05-27 10:45:47 +08:00
|
|
|
return nullptr;
|
2013-11-08 09:15:35 +08:00
|
|
|
|
|
|
|
switch (findKnownClass(StaticClass, /*IncludeSuper=*/false)) {
|
|
|
|
case FC_None:
|
2014-05-27 10:45:47 +08:00
|
|
|
return nullptr;
|
2013-11-08 09:15:35 +08:00
|
|
|
case FC_NSArray:
|
|
|
|
case FC_NSDictionary:
|
|
|
|
case FC_NSEnumerator:
|
|
|
|
case FC_NSNull:
|
|
|
|
case FC_NSOrderedSet:
|
|
|
|
case FC_NSSet:
|
|
|
|
case FC_NSString:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return Message->getReceiverSVal().getAsSymbol();
|
|
|
|
}
|
|
|
|
|
2013-06-22 08:23:26 +08:00
|
|
|
ProgramStateRef
|
|
|
|
ObjCLoopChecker::checkPointerEscape(ProgramStateRef State,
|
|
|
|
const InvalidatedSymbols &Escaped,
|
|
|
|
const CallEvent *Call,
|
|
|
|
PointerEscapeKind Kind) const {
|
2013-11-08 09:15:35 +08:00
|
|
|
SymbolRef ImmutableReceiver = getMethodReceiverIfKnownImmutable(Call);
|
2013-06-22 08:23:26 +08:00
|
|
|
|
|
|
|
// Remove the invalidated symbols form the collection count map.
|
|
|
|
for (InvalidatedSymbols::const_iterator I = Escaped.begin(),
|
|
|
|
E = Escaped.end();
|
|
|
|
I != E; ++I) {
|
|
|
|
SymbolRef Sym = *I;
|
|
|
|
|
2013-11-08 09:15:35 +08:00
|
|
|
// Don't invalidate this symbol's count if we know the method being called
|
|
|
|
// is declared on an immutable class. This isn't completely correct if the
|
|
|
|
// receiver is also passed as an argument, but in most uses of NSArray,
|
|
|
|
// NSDictionary, etc. this isn't likely to happen in a dangerous way.
|
|
|
|
if (Sym == ImmutableReceiver)
|
|
|
|
continue;
|
|
|
|
|
2013-06-22 08:23:26 +08:00
|
|
|
// The symbol escaped. Pessimistically, assume that the count could have
|
|
|
|
// changed.
|
|
|
|
State = State->remove<ContainerCountMap>(Sym);
|
2013-11-08 09:15:35 +08:00
|
|
|
State = State->remove<ContainerNonEmptyMap>(Sym);
|
2013-06-22 08:23:26 +08:00
|
|
|
}
|
|
|
|
return State;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ObjCLoopChecker::checkDeadSymbols(SymbolReaper &SymReaper,
|
|
|
|
CheckerContext &C) const {
|
|
|
|
ProgramStateRef State = C.getState();
|
|
|
|
|
|
|
|
// Remove the dead symbols from the collection count map.
|
|
|
|
ContainerCountMapTy Tracked = State->get<ContainerCountMap>();
|
|
|
|
for (ContainerCountMapTy::iterator I = Tracked.begin(),
|
|
|
|
E = Tracked.end(); I != E; ++I) {
|
|
|
|
SymbolRef Sym = I->first;
|
2013-11-08 09:15:35 +08:00
|
|
|
if (SymReaper.isDead(Sym)) {
|
2013-06-22 08:23:26 +08:00
|
|
|
State = State->remove<ContainerCountMap>(Sym);
|
2013-11-08 09:15:35 +08:00
|
|
|
State = State->remove<ContainerNonEmptyMap>(Sym);
|
|
|
|
}
|
2013-06-22 08:23:26 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
C.addTransition(State);
|
|
|
|
}
|
|
|
|
|
2012-08-23 05:19:56 +08:00
|
|
|
namespace {
|
|
|
|
/// \class ObjCNonNilReturnValueChecker
|
2018-05-09 09:00:01 +08:00
|
|
|
/// The checker restricts the return values of APIs known to
|
2012-08-31 03:40:52 +08:00
|
|
|
/// never (or almost never) return 'nil'.
|
2012-08-23 05:19:56 +08:00
|
|
|
class ObjCNonNilReturnValueChecker
|
2014-02-08 08:04:14 +08:00
|
|
|
: public Checker<check::PostObjCMessage,
|
|
|
|
check::PostStmt<ObjCArrayLiteral>,
|
|
|
|
check::PostStmt<ObjCDictionaryLiteral>,
|
|
|
|
check::PostStmt<ObjCBoxedExpr> > {
|
2012-08-23 05:19:56 +08:00
|
|
|
mutable bool Initialized;
|
|
|
|
mutable Selector ObjectAtIndex;
|
|
|
|
mutable Selector ObjectAtIndexedSubscript;
|
2013-05-11 02:04:46 +08:00
|
|
|
mutable Selector NullSelector;
|
2012-08-23 06:47:58 +08:00
|
|
|
|
2012-08-23 05:19:56 +08:00
|
|
|
public:
|
2012-08-23 06:47:58 +08:00
|
|
|
ObjCNonNilReturnValueChecker() : Initialized(false) {}
|
2014-02-08 08:04:14 +08:00
|
|
|
|
|
|
|
ProgramStateRef assumeExprIsNonNull(const Expr *NonNullExpr,
|
|
|
|
ProgramStateRef State,
|
|
|
|
CheckerContext &C) const;
|
|
|
|
void assumeExprIsNonNull(const Expr *E, CheckerContext &C) const {
|
|
|
|
C.addTransition(assumeExprIsNonNull(E, C.getState(), C));
|
|
|
|
}
|
|
|
|
|
|
|
|
void checkPostStmt(const ObjCArrayLiteral *E, CheckerContext &C) const {
|
|
|
|
assumeExprIsNonNull(E, C);
|
|
|
|
}
|
|
|
|
void checkPostStmt(const ObjCDictionaryLiteral *E, CheckerContext &C) const {
|
|
|
|
assumeExprIsNonNull(E, C);
|
|
|
|
}
|
|
|
|
void checkPostStmt(const ObjCBoxedExpr *E, CheckerContext &C) const {
|
|
|
|
assumeExprIsNonNull(E, C);
|
|
|
|
}
|
|
|
|
|
2012-08-23 05:19:56 +08:00
|
|
|
void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;
|
|
|
|
};
|
2016-02-11 03:11:58 +08:00
|
|
|
} // end anonymous namespace
|
2012-08-23 05:19:56 +08:00
|
|
|
|
2014-02-08 08:04:14 +08:00
|
|
|
ProgramStateRef
|
|
|
|
ObjCNonNilReturnValueChecker::assumeExprIsNonNull(const Expr *NonNullExpr,
|
|
|
|
ProgramStateRef State,
|
|
|
|
CheckerContext &C) const {
|
2018-01-18 04:27:29 +08:00
|
|
|
SVal Val = C.getSVal(NonNullExpr);
|
2013-02-21 06:23:23 +08:00
|
|
|
if (Optional<DefinedOrUnknownSVal> DV = Val.getAs<DefinedOrUnknownSVal>())
|
2012-08-31 06:55:32 +08:00
|
|
|
return State->assume(*DV, true);
|
2012-08-31 06:42:41 +08:00
|
|
|
return State;
|
2012-08-31 03:40:52 +08:00
|
|
|
}
|
|
|
|
|
2012-08-23 05:19:56 +08:00
|
|
|
void ObjCNonNilReturnValueChecker::checkPostObjCMessage(const ObjCMethodCall &M,
|
|
|
|
CheckerContext &C)
|
2012-08-31 03:40:52 +08:00
|
|
|
const {
|
2012-08-23 05:19:56 +08:00
|
|
|
ProgramStateRef State = C.getState();
|
|
|
|
|
|
|
|
if (!Initialized) {
|
|
|
|
ASTContext &Ctx = C.getASTContext();
|
|
|
|
ObjectAtIndex = GetUnarySelector("objectAtIndex", Ctx);
|
|
|
|
ObjectAtIndexedSubscript = GetUnarySelector("objectAtIndexedSubscript", Ctx);
|
2013-05-11 02:04:46 +08:00
|
|
|
NullSelector = GetNullarySelector("null", Ctx);
|
2012-08-23 05:19:56 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check the receiver type.
|
|
|
|
if (const ObjCInterfaceDecl *Interface = M.getReceiverInterface()) {
|
2012-08-31 03:40:52 +08:00
|
|
|
|
|
|
|
// Assume that object returned from '[self init]' or '[super init]' is not
|
|
|
|
// 'nil' if we are processing an inlined function/method.
|
|
|
|
//
|
|
|
|
// A defensive callee will (and should) check if the object returned by
|
|
|
|
// '[super init]' is 'nil' before doing it's own initialization. However,
|
|
|
|
// since 'nil' is rarely returned in practice, we should not warn when the
|
|
|
|
// caller to the defensive constructor uses the object in contexts where
|
|
|
|
// 'nil' is not accepted.
|
2012-11-06 12:20:54 +08:00
|
|
|
if (!C.inTopFrame() && M.getDecl() &&
|
2012-08-31 03:40:52 +08:00
|
|
|
M.getDecl()->getMethodFamily() == OMF_init &&
|
|
|
|
M.isReceiverSelfOrSuper()) {
|
|
|
|
State = assumeExprIsNonNull(M.getOriginExpr(), State, C);
|
|
|
|
}
|
|
|
|
|
2013-05-11 02:04:46 +08:00
|
|
|
FoundationClass Cl = findKnownClass(Interface);
|
|
|
|
|
2012-08-31 03:40:52 +08:00
|
|
|
// Objects returned from
|
|
|
|
// [NSArray|NSOrderedSet]::[ObjectAtIndex|ObjectAtIndexedSubscript]
|
|
|
|
// are never 'nil'.
|
2012-08-23 05:19:56 +08:00
|
|
|
if (Cl == FC_NSArray || Cl == FC_NSOrderedSet) {
|
|
|
|
Selector Sel = M.getSelector();
|
|
|
|
if (Sel == ObjectAtIndex || Sel == ObjectAtIndexedSubscript) {
|
|
|
|
// Go ahead and assume the value is non-nil.
|
2012-08-31 03:40:52 +08:00
|
|
|
State = assumeExprIsNonNull(M.getOriginExpr(), State, C);
|
2012-08-23 05:19:56 +08:00
|
|
|
}
|
2013-05-11 02:04:46 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Objects returned from [NSNull null] are not nil.
|
|
|
|
if (Cl == FC_NSNull) {
|
|
|
|
if (M.getSelector() == NullSelector) {
|
|
|
|
// Go ahead and assume the value is non-nil.
|
|
|
|
State = assumeExprIsNonNull(M.getOriginExpr(), State, C);
|
|
|
|
}
|
2012-08-23 05:19:56 +08:00
|
|
|
}
|
|
|
|
}
|
2012-08-31 03:40:52 +08:00
|
|
|
C.addTransition(State);
|
2012-08-23 05:19:56 +08:00
|
|
|
}
|
2012-06-12 00:40:41 +08:00
|
|
|
|
2008-07-23 00:21:24 +08:00
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// Check registration.
|
2009-07-14 08:43:42 +08:00
|
|
|
//===----------------------------------------------------------------------===//
|
2011-02-16 09:40:52 +08:00
|
|
|
|
2011-02-18 05:39:17 +08:00
|
|
|
void ento::registerNilArgChecker(CheckerManager &mgr) {
|
2011-02-23 08:16:10 +08:00
|
|
|
mgr.registerChecker<NilArgChecker>();
|
2011-02-16 09:40:52 +08:00
|
|
|
}
|
|
|
|
|
2019-01-26 22:23:08 +08:00
|
|
|
bool ento::shouldRegisterNilArgChecker(const LangOptions &LO) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-10-27 06:51:47 +08:00
|
|
|
void ento::registerCFNumberChecker(CheckerManager &mgr) {
|
|
|
|
mgr.registerChecker<CFNumberChecker>();
|
2011-02-16 09:40:52 +08:00
|
|
|
}
|
|
|
|
|
2019-01-26 22:23:08 +08:00
|
|
|
bool ento::shouldRegisterCFNumberChecker(const LangOptions &LO) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2011-02-18 05:39:17 +08:00
|
|
|
void ento::registerCFRetainReleaseChecker(CheckerManager &mgr) {
|
2011-02-23 08:16:10 +08:00
|
|
|
mgr.registerChecker<CFRetainReleaseChecker>();
|
2008-07-23 00:21:24 +08:00
|
|
|
}
|
2011-02-18 05:39:17 +08:00
|
|
|
|
2019-01-26 22:23:08 +08:00
|
|
|
bool ento::shouldRegisterCFRetainReleaseChecker(const LangOptions &LO) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2011-02-18 05:39:17 +08:00
|
|
|
void ento::registerClassReleaseChecker(CheckerManager &mgr) {
|
2011-02-23 08:16:10 +08:00
|
|
|
mgr.registerChecker<ClassReleaseChecker>();
|
2011-02-18 05:39:17 +08:00
|
|
|
}
|
2011-03-14 04:35:21 +08:00
|
|
|
|
2019-01-26 22:23:08 +08:00
|
|
|
bool ento::shouldRegisterClassReleaseChecker(const LangOptions &LO) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2011-03-14 04:35:21 +08:00
|
|
|
void ento::registerVariadicMethodTypeChecker(CheckerManager &mgr) {
|
|
|
|
mgr.registerChecker<VariadicMethodTypeChecker>();
|
|
|
|
}
|
2012-06-12 00:40:41 +08:00
|
|
|
|
2019-01-26 22:23:08 +08:00
|
|
|
bool ento::shouldRegisterVariadicMethodTypeChecker(const LangOptions &LO) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2012-06-12 00:40:41 +08:00
|
|
|
void ento::registerObjCLoopChecker(CheckerManager &mgr) {
|
|
|
|
mgr.registerChecker<ObjCLoopChecker>();
|
|
|
|
}
|
2012-08-23 05:19:56 +08:00
|
|
|
|
2019-01-26 22:23:08 +08:00
|
|
|
bool ento::shouldRegisterObjCLoopChecker(const LangOptions &LO) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ento::registerObjCNonNilReturnValueChecker(CheckerManager &mgr) {
|
2012-08-23 05:19:56 +08:00
|
|
|
mgr.registerChecker<ObjCNonNilReturnValueChecker>();
|
|
|
|
}
|
2019-01-26 22:23:08 +08:00
|
|
|
|
|
|
|
bool ento::shouldRegisterObjCNonNilReturnValueChecker(const LangOptions &LO) {
|
|
|
|
return true;
|
|
|
|
}
|