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 NonnullStringConstantsChecker: 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 + NonnullStringConstantsChecker.cpp NullabilityChecker.cpp NumberObjectConversionChecker.cpp ObjCAtSyncChecker.cpp Index: lib/StaticAnalyzer/Checkers/NonnullStringConstantsChecker.cpp =================================================================== --- /dev/null +++ lib/StaticAnalyzer/Checkers/NonnullStringConstantsChecker.cpp @@ -0,0 +1,134 @@ +//==- NonnullStringConstantsChecker.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 adds an assumption that constant string-like globals are +// non-null, as otherwise they generally do not convey any useful information. +// The assumption is useful, as many framework use such global const strings, +// and the analyzer might not be able to infer the global value if the +// definition is in a separate translation unit. +// The following types (and their typedef aliases) are considered string-like: +// - `char* const` +// - `const CFStringRef` from CoreFoundation +// - `NSString* const` from Foundation +// +//===----------------------------------------------------------------------===// + +#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 NonnullStringConstantsChecker : public Checker { + mutable IdentifierInfo *NSStringII = nullptr; + mutable IdentifierInfo *CFStringRefII = nullptr; + +public: + NonnullStringConstantsChecker() {} + + void checkLocation(SVal l, bool isLoad, const Stmt *S, + CheckerContext &C) const; + +private: + void initIdentifierInfo(ASTContext &Ctx) const; + + bool isGlobalConstString(SVal V) const; + + bool isStringlike(QualType Ty) const; +}; + +} // namespace + +/// Lazily initialize cache for required identifier informations. +void NonnullStringConstantsChecker::initIdentifierInfo(ASTContext &Ctx) const { + if (NSStringII) + return; + + NSStringII = &Ctx.Idents.get("NSString"); + CFStringRefII = &Ctx.Idents.get("CFStringRef"); +} + +/// Add an assumption that const string-like globals are non-null. +void NonnullStringConstantsChecker::checkLocation(SVal location, bool isLoad, + const Stmt *S, + CheckerContext &C) const { + initIdentifierInfo(C.getASTContext()); + if (!isLoad || !location.isValid()) + return; + + ProgramStateRef State = C.getState(); + SVal 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); + } + } +} + +/// \param V loaded lvalue. +/// \return whether {@code val} is a string-like const global. +bool NonnullStringConstantsChecker::isGlobalConstString(SVal V) const { + Optional RegionVal = V.getAs(); + if (!RegionVal) + return false; + auto *Region = dyn_cast(RegionVal->getAsRegion()); + if (!Region) + return false; + const VarDecl *Decl = Region->getDecl(); + + if (!Decl->hasGlobalStorage()) + return false; + + QualType Ty = Decl->getType(); + bool HasConst = Ty.isConstQualified(); + if (isStringlike(Ty) && HasConst) + return true; + + // Look through the typedefs. + while (auto *T = dyn_cast(Ty)) { + Ty = T->getDecl()->getUnderlyingType(); + + // It is sufficient for any intermediate typedef + // to be classified const. + HasConst = HasConst || Ty.isConstQualified(); + if (isStringlike(Ty) && HasConst) + return true; + } + return false; +} + +/// \return whether {@code type} is a string-like type. +bool NonnullStringConstantsChecker::isStringlike(QualType Ty) const { + + if (Ty->isPointerType() && Ty->getPointeeType()->isCharType()) + return true; + + if (auto *T = dyn_cast(Ty)) { + return T->getInterfaceDecl()->getIdentifier() == NSStringII; + } else if (auto *T = dyn_cast(Ty)) { + return T->getDecl()->getIdentifier() == CFStringRefII; + } + return false; +} + +void ento::registerNonnullStringConstantsChecker(CheckerManager &Mgr) { + Mgr.registerChecker(); +} Index: test/Analysis/nonnull-string-constants.mm =================================================================== --- /dev/null +++ test/Analysis/nonnull-string-constants.mm @@ -0,0 +1,90 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -verify %s + +// Nullability of const string-like globals, testing +// NonnullStringConstantsChecker. + +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}} +} + +// char* const is also assumed to be non-null. +extern const char *const ConstCharStarConst; +void constCharStarCheckGlobal() { + clang_analyzer_eval(ConstCharStarConst); // expected-warning{{TRUE}} +} + +// Pointer value can be mutable. +extern char *const CharStarConst; +void charStarCheckGlobal() { + clang_analyzer_eval(CharStarConst); // expected-warning{{TRUE}} +} + +// But the pointer itself should be immutable. +extern char *CharStar; +void charStartCheckMutableGlobal() { + clang_analyzer_eval(CharStar); // expected-warning{{UNKNOWN}} +} + +// Type definitions should also work across typedefs, for pointers: +typedef char *const 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}} +} + +// And for NSString *. +typedef NSString *const nstr1; +typedef nstr1 nstr2; +extern nstr2 nglobalStr2; +void testNestedTypedefsForNSString() { + clang_analyzer_eval(nglobalStr2); // expected-warning{{TRUE}} +}