Index: include/clang/StaticAnalyzer/Checkers/Checkers.td =================================================================== --- include/clang/StaticAnalyzer/Checkers/Checkers.td +++ include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -132,6 +132,10 @@ HelpText<"Generate dynamic type information">, DescFile<"DynamicTypePropagation.cpp">; +def NonnilStringConstantsChecker: Checker<"NonnilStringConstants">, + HelpText<"Assume that const string-like globals are non-null">, + DescFile<"NonilStringConstantsChecker.cpp">; + } // end "core" let ParentPackage = CoreAlpha in { Index: lib/StaticAnalyzer/Checkers/CMakeLists.txt =================================================================== --- lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -57,6 +57,7 @@ NSErrorChecker.cpp NoReturnFunctionChecker.cpp NonNullParamChecker.cpp + NonnilStringConstantsChecker.cpp NullabilityChecker.cpp NumberObjectConversionChecker.cpp ObjCAtSyncChecker.cpp Index: lib/StaticAnalyzer/Checkers/NonnilStringConstantsChecker.cpp =================================================================== --- /dev/null +++ lib/StaticAnalyzer/Checkers/NonnilStringConstantsChecker.cpp @@ -0,0 +1,121 @@ + +// +// This checker ensures that const string globals are assumed to be non-null. +// +#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/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" + +using namespace clang; +using namespace ento; + +namespace { + +class NonnilStringConstantsChecker : public Checker { + mutable IdentifierInfo *NSStringII = nullptr; + mutable IdentifierInfo *CFStringRefII = nullptr; + +public: + NonnilStringConstantsChecker(AnalyzerOptions &AO) {} + + void checkLocation(SVal l, bool isLoad, const Stmt *S, + CheckerContext &C) const; + +private: + /// Lazily initialize cache for required identifier informations. + void initIdentifierInfo(ASTContext &Ctx) const; + bool typeIsConstString(QualType type, bool isConstQualified) const; + bool isGlobalConstString(SVal val) const; +}; + +} // namespace + +void NonnilStringConstantsChecker::initIdentifierInfo(ASTContext &Ctx) const { + if (NSStringII) + return; + + NSStringII = &Ctx.Idents.get("NSString"); + CFStringRefII = &Ctx.Idents.get("CFStringRef"); +} + +void NonnilStringConstantsChecker::checkLocation(SVal location, bool isLoad, + const Stmt *S, + CheckerContext &C) const { + initIdentifierInfo(C.getASTContext()); + if (!isLoad) + return; + + ProgramStateRef State = C.getState(); + SVal V = UnknownVal(); + if (location.isValid()) { + V = State->getSVal(location.castAs()); + } + + if (isGlobalConstString(location)) { + Optional Constr = V.getAs(); + + if (Constr) { + + // Assume that the variable is non-null. + ProgramStateRef OutputState = State->assume(*Constr, true); + C.addTransition(OutputState); + } + } +} + + +bool NonnilStringConstantsChecker::isGlobalConstString(SVal val) const { + Optional regionVal = val.getAs(); + if (!regionVal) + return false; + const VarRegion *region = dyn_cast(regionVal->getAsRegion()); + if (!region) + return false; + const VarDecl *decl = region->getDecl(); + + if (!decl->hasGlobalStorage()) + return false; + + QualType type = decl->getType(); + bool hasOuterConst = type.isConstQualified(); + if (typeIsConstString(type, hasOuterConst)) + return true; + + // Look through the typedefs. + while (const TypedefType *T = dyn_cast(type)) { + type = T->getDecl()->getUnderlyingType(); + + // It is sufficient for any intermediate typedef + // to be classified const. + hasOuterConst |= type.isConstQualified(); + if (typeIsConstString(type, hasOuterConst)) + return true; + } + return false; +} + +bool NonnilStringConstantsChecker::typeIsConstString( + QualType type, bool isConstQualified) const { + + // Match `const char*`. + if (type->isPointerType() && type->getPointeeType()->isCharType() && + type->getPointeeType().isConstQualified()) + return true; + + IdentifierInfo *II; + if (const ObjCObjectPointerType *T = dyn_cast(type)) { + II = T->getInterfaceDecl()->getIdentifier(); + } else if (const TypedefType *T = dyn_cast(type)) { + II = T->getDecl()->getIdentifier(); + } + + // Match `NSString* const` or `const CFStringRef`. + return isConstQualified && (II == NSStringII || II == CFStringRefII); +} + +void ento::registerNonnilStringConstantsChecker(CheckerManager &Mgr) { + Mgr.registerChecker(Mgr.getAnalyzerOptions()); +} Index: test/Analysis/nonnil-string-constants.mm =================================================================== --- /dev/null +++ test/Analysis/nonnil-string-constants.mm @@ -0,0 +1,80 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -verify %s + +// Nullability of const string-like globals. +void clang_analyzer_eval(bool); + +@class NSString; +typedef const struct __CFString *CFStringRef; + +// Global NSString* is non-null. +extern NSString *const StringConstGlobal; +void stringConstGlobal() { + clang_analyzer_eval(StringConstGlobal); // expected-warning{{TRUE}} +} + +// The logic does not apply to local variables though. +extern NSString *stringGetter(); +void stringConstLocal() { + NSString *const local = stringGetter(); + clang_analyzer_eval(local); // expected-warning{{UNKNOWN}} +} + +// Global const CFStringRef's are also assumed to be non-null. +extern const CFStringRef CFStringConstGlobal; +void cfStringCheckGlobal() { + clang_analyzer_eval(CFStringConstGlobal); // expected-warning{{TRUE}} +} + +// But only "const" ones. +extern CFStringRef CFStringNonConstGlobal; +void cfStringCheckMutableGlobal() { + clang_analyzer_eval(CFStringNonConstGlobal); // expected-warning{{UNKNOWN}} +} + +// Const char* is also assumed to be non-null. +extern const char *const ConstCharStarConst; +void constCharStarCheckGlobal() { + clang_analyzer_eval(ConstCharStarConst); // expected-warning{{TRUE}} +} + +// For char* we do not require a pointer itself to be immutable. +extern const char *CharStarConst; +void charStarCheckGlobal() { + clang_analyzer_eval(CharStarConst); // expected-warning{{TRUE}} +} + +// But the pointed data should be. +extern char *CharStar; +void charStartCheckMutableGlobal() { + clang_analyzer_eval(CharStar); // expected-warning{{UNKNOWN}} +} + +// Type definitions should also work across typedefs, for pointers: +typedef const char *str; +extern str globalStr; +void charStarCheckTypedef() { + clang_analyzer_eval(globalStr); // expected-warning{{TRUE}} +} + +// And for types. +typedef NSString *const NStr; +extern NStr globalNSString; +void NSStringCheckTypedef() { + clang_analyzer_eval(globalNSString); // expected-warning{{TRUE}} +} + +// Note that constness could be either inside +// the var declaration, or in a typedef. +typedef NSString *NStr2; +extern const NStr2 globalNSString2; +void NSStringCheckConstTypedef() { + clang_analyzer_eval(globalNSString2); // expected-warning{{TRUE}} +} + +// Nested typedefs should work as well. +typedef const CFStringRef str1; +typedef str1 str2; +extern str2 globalStr2; +void testNestedTypedefs() { + clang_analyzer_eval(globalStr2); // expected-warning{{TRUE}} +}