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,141 @@ +//==-- RetainCountChecker.cpp - Checks for leaks and other issues -*- C++ -*--// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Class definition for NonnullStringConstantsChecker. +// 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 +// +// Checker uses are defined in the test file: +// - test/Analysis/nonnull-string-constants.mm +// +//===----------------------------------------------------------------------===// + +// +// 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 NonnullStringConstantsChecker : public Checker { + mutable IdentifierInfo *NSStringII = nullptr; + mutable IdentifierInfo *CFStringRefII = nullptr; + +public: + NonnullStringConstantsChecker() {} + + /// Add an assumption that const string-like globals are non-null. + 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; + + /// \param val loaded lvalue. + /// \return whether {@code val} is a string-like const global. + bool isGlobalConstString(SVal val) const; + + /// \return whether {@code type} is a string-like type. + bool isStringlike(QualType type) const; +}; + +} // namespace + +void NonnullStringConstantsChecker::initIdentifierInfo(ASTContext &Ctx) const { + if (NSStringII) + return; + + NSStringII = &Ctx.Idents.get("NSString"); + CFStringRefII = &Ctx.Idents.get("CFStringRef"); +} + +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); + } + } +} + +bool NonnullStringConstantsChecker::isGlobalConstString(SVal val) const { + Optional regionVal = val.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 type = decl->getType(); + bool hasConst = type.isConstQualified(); + if (isStringlike(type) && hasConst) + 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. + hasConst = hasConst || type.isConstQualified(); + if (isStringlike(type) && hasConst) + return true; + } + return false; +} + +bool NonnullStringConstantsChecker::isStringlike(QualType type) const { + + if (type->isPointerType() && type->getPointeeType()->isCharType()) + return true; + + if (const ObjCObjectPointerType *T = dyn_cast(type)) { + return T->getInterfaceDecl()->getIdentifier() == NSStringII; + } else if (const TypedefType *T = dyn_cast(type)) { + 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,91 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -verify %s + +// Nullability of const string-like globals. +// Relies on the checker defined in +// lib/StaticAnalyzer/Checkers/NonnullStringConstantsChecker.cpp. + +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}} +}