1//==- NonnullGlobalConstantsChecker.cpp ---------------------------*- C++ -*--// 2// 3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4// See https://llvm.org/LICENSE.txt for license information. 5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6// 7//===----------------------------------------------------------------------===// 8// 9// This checker adds an assumption that constant globals of certain types* are 10// non-null, as otherwise they generally do not convey any useful information. 11// The assumption is useful, as many framework use e. g. global const strings, 12// and the analyzer might not be able to infer the global value if the 13// definition is in a separate translation unit. 14// The following types (and their typedef aliases) are considered to be 15// non-null: 16// - `char* const` 17// - `const CFStringRef` from CoreFoundation 18// - `NSString* const` from Foundation 19// - `CFBooleanRef` from Foundation 20// 21//===----------------------------------------------------------------------===// 22 23#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" 24#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 25#include "clang/StaticAnalyzer/Core/Checker.h" 26#include "clang/StaticAnalyzer/Core/CheckerManager.h" 27#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 28#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" 29#include <optional> 30 31using namespace clang; 32using namespace ento; 33 34namespace { 35 36class NonnullGlobalConstantsChecker : public Checker<check::Location> { 37 mutable IdentifierInfo *NSStringII = nullptr; 38 mutable IdentifierInfo *CFStringRefII = nullptr; 39 mutable IdentifierInfo *CFBooleanRefII = nullptr; 40 mutable IdentifierInfo *CFNullRefII = nullptr; 41 42public: 43 NonnullGlobalConstantsChecker() {} 44 45 void checkLocation(SVal l, bool isLoad, const Stmt *S, 46 CheckerContext &C) const; 47 48private: 49 void initIdentifierInfo(ASTContext &Ctx) const; 50 51 bool isGlobalConstString(SVal V) const; 52 53 bool isNonnullType(QualType Ty) const; 54}; 55 56} // namespace 57 58/// Lazily initialize cache for required identifier information. 59void NonnullGlobalConstantsChecker::initIdentifierInfo(ASTContext &Ctx) const { 60 if (NSStringII) 61 return; 62 63 NSStringII = &Ctx.Idents.get("NSString"); 64 CFStringRefII = &Ctx.Idents.get("CFStringRef"); 65 CFBooleanRefII = &Ctx.Idents.get("CFBooleanRef"); 66 CFNullRefII = &Ctx.Idents.get("CFNullRef"); 67} 68 69/// Add an assumption that const string-like globals are non-null. 70void NonnullGlobalConstantsChecker::checkLocation(SVal location, bool isLoad, 71 const Stmt *S, 72 CheckerContext &C) const { 73 initIdentifierInfo(C.getASTContext()); 74 if (!isLoad || !location.isValid()) 75 return; 76 77 ProgramStateRef State = C.getState(); 78 79 if (isGlobalConstString(location)) { 80 SVal V = State->getSVal(location.castAs<Loc>()); 81 std::optional<DefinedOrUnknownSVal> Constr = 82 V.getAs<DefinedOrUnknownSVal>(); 83 84 if (Constr) { 85 86 // Assume that the variable is non-null. 87 ProgramStateRef OutputState = State->assume(*Constr, true); 88 C.addTransition(OutputState); 89 } 90 } 91} 92 93/// \param V loaded lvalue. 94/// \return whether @c val is a string-like const global. 95bool NonnullGlobalConstantsChecker::isGlobalConstString(SVal V) const { 96 std::optional<loc::MemRegionVal> RegionVal = V.getAs<loc::MemRegionVal>(); 97 if (!RegionVal) 98 return false; 99 auto *Region = dyn_cast<VarRegion>(RegionVal->getAsRegion()); 100 if (!Region) 101 return false; 102 const VarDecl *Decl = Region->getDecl(); 103 104 if (!Decl->hasGlobalStorage()) 105 return false; 106 107 QualType Ty = Decl->getType(); 108 bool HasConst = Ty.isConstQualified(); 109 if (isNonnullType(Ty) && HasConst) 110 return true; 111 112 // Look through the typedefs. 113 while (const Type *T = Ty.getTypePtr()) { 114 if (const auto *AT = dyn_cast<AttributedType>(T)) { 115 if (AT->getAttrKind() == attr::TypeNonNull) 116 return true; 117 Ty = AT->getModifiedType(); 118 } else if (const auto *ET = dyn_cast<ElaboratedType>(T)) { 119 const auto *TT = dyn_cast<TypedefType>(ET->getNamedType()); 120 if (!TT) 121 return false; 122 Ty = TT->getDecl()->getUnderlyingType(); 123 // It is sufficient for any intermediate typedef 124 // to be classified const. 125 HasConst = HasConst || Ty.isConstQualified(); 126 if (isNonnullType(Ty) && HasConst) 127 return true; 128 } else { 129 return false; 130 } 131 } 132 return false; 133} 134 135/// \return whether @c type is extremely unlikely to be null 136bool NonnullGlobalConstantsChecker::isNonnullType(QualType Ty) const { 137 138 if (Ty->isPointerType() && Ty->getPointeeType()->isCharType()) 139 return true; 140 141 if (auto *T = dyn_cast<ObjCObjectPointerType>(Ty)) { 142 return T->getInterfaceDecl() && 143 T->getInterfaceDecl()->getIdentifier() == NSStringII; 144 } else if (auto *T = Ty->getAs<TypedefType>()) { 145 IdentifierInfo* II = T->getDecl()->getIdentifier(); 146 return II == CFStringRefII || II == CFBooleanRefII || II == CFNullRefII; 147 } 148 return false; 149} 150 151void ento::registerNonnullGlobalConstantsChecker(CheckerManager &Mgr) { 152 Mgr.registerChecker<NonnullGlobalConstantsChecker>(); 153} 154 155bool ento::shouldRegisterNonnullGlobalConstantsChecker(const CheckerManager &mgr) { 156 return true; 157} 158