| 1 | //===--- NonNullParamChecker.cpp - Undefined arguments checker -*- 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 defines NonNullParamChecker, which checks for arguments expected not to |
| 10 | // be null due to: |
| 11 | // - the corresponding parameters being declared to have nonnull attribute |
| 12 | // - the corresponding parameters being references; since the call would form |
| 13 | // a reference to a null pointer |
| 14 | // |
| 15 | //===----------------------------------------------------------------------===// |
| 16 | |
| 17 | #include "clang/AST/Attr.h" |
| 18 | #include "clang/Analysis/AnyCall.h" |
| 19 | #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
| 20 | #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" |
| 21 | #include "clang/StaticAnalyzer/Core/Checker.h" |
| 22 | #include "clang/StaticAnalyzer/Core/CheckerManager.h" |
| 23 | #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" |
| 24 | #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" |
| 25 | #include "llvm/ADT/StringExtras.h" |
| 26 | |
| 27 | using namespace clang; |
| 28 | using namespace ento; |
| 29 | |
| 30 | namespace { |
| 31 | class NonNullParamChecker |
| 32 | : public Checker<check::PreCall, check::BeginFunction, |
| 33 | EventDispatcher<ImplicitNullDerefEvent>> { |
| 34 | const BugType BTAttrNonNull{ |
| 35 | this, "Argument with 'nonnull' attribute passed null" , "API" }; |
| 36 | const BugType BTNullRefArg{this, "Dereference of null pointer" }; |
| 37 | |
| 38 | public: |
| 39 | void checkPreCall(const CallEvent &Call, CheckerContext &C) const; |
| 40 | void checkBeginFunction(CheckerContext &C) const; |
| 41 | |
| 42 | std::unique_ptr<PathSensitiveBugReport> |
| 43 | genReportNullAttrNonNull(const ExplodedNode *ErrorN, const Expr *ArgE, |
| 44 | unsigned IdxOfArg) const; |
| 45 | std::unique_ptr<PathSensitiveBugReport> |
| 46 | genReportReferenceToNullPointer(const ExplodedNode *ErrorN, |
| 47 | const Expr *ArgE) const; |
| 48 | }; |
| 49 | |
| 50 | template <class CallType> |
| 51 | void setBitsAccordingToFunctionAttributes(const CallType &Call, |
| 52 | llvm::SmallBitVector &AttrNonNull) { |
| 53 | const Decl *FD = Call.getDecl(); |
| 54 | |
| 55 | for (const auto *NonNull : FD->specific_attrs<NonNullAttr>()) { |
| 56 | if (!NonNull->args_size()) { |
| 57 | // Lack of attribute parameters means that all of the parameters are |
| 58 | // implicitly marked as non-null. |
| 59 | AttrNonNull.set(); |
| 60 | break; |
| 61 | } |
| 62 | |
| 63 | for (const ParamIdx &Idx : NonNull->args()) { |
| 64 | // 'nonnull' attribute's parameters are 1-based and should be adjusted to |
| 65 | // match actual AST parameter/argument indices. |
| 66 | unsigned IdxAST = Idx.getASTIndex(); |
| 67 | if (IdxAST >= AttrNonNull.size()) |
| 68 | continue; |
| 69 | AttrNonNull.set(IdxAST); |
| 70 | } |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | template <class CallType> |
| 75 | void setBitsAccordingToParameterAttributes(const CallType &Call, |
| 76 | llvm::SmallBitVector &AttrNonNull) { |
| 77 | for (const ParmVarDecl *Parameter : Call.parameters()) { |
| 78 | unsigned ParameterIndex = Parameter->getFunctionScopeIndex(); |
| 79 | if (ParameterIndex == AttrNonNull.size()) |
| 80 | break; |
| 81 | |
| 82 | if (Parameter->hasAttr<NonNullAttr>()) |
| 83 | AttrNonNull.set(ParameterIndex); |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | template <class CallType> |
| 88 | llvm::SmallBitVector getNonNullAttrsImpl(const CallType &Call, |
| 89 | unsigned ExpectedSize) { |
| 90 | llvm::SmallBitVector AttrNonNull(ExpectedSize); |
| 91 | |
| 92 | setBitsAccordingToFunctionAttributes(Call, AttrNonNull); |
| 93 | setBitsAccordingToParameterAttributes(Call, AttrNonNull); |
| 94 | |
| 95 | return AttrNonNull; |
| 96 | } |
| 97 | |
| 98 | /// \return Bitvector marking non-null attributes. |
| 99 | llvm::SmallBitVector getNonNullAttrs(const CallEvent &Call) { |
| 100 | return getNonNullAttrsImpl(Call, ExpectedSize: Call.getNumArgs()); |
| 101 | } |
| 102 | |
| 103 | /// \return Bitvector marking non-null attributes. |
| 104 | llvm::SmallBitVector getNonNullAttrs(const AnyCall &Call) { |
| 105 | return getNonNullAttrsImpl(Call, ExpectedSize: Call.param_size()); |
| 106 | } |
| 107 | } // end anonymous namespace |
| 108 | |
| 109 | void NonNullParamChecker::checkPreCall(const CallEvent &Call, |
| 110 | CheckerContext &C) const { |
| 111 | if (!Call.getDecl()) |
| 112 | return; |
| 113 | |
| 114 | llvm::SmallBitVector AttrNonNull = getNonNullAttrs(Call); |
| 115 | unsigned NumArgs = Call.getNumArgs(); |
| 116 | |
| 117 | ProgramStateRef state = C.getState(); |
| 118 | ArrayRef<ParmVarDecl *> parms = Call.parameters(); |
| 119 | |
| 120 | for (unsigned idx = 0; idx < NumArgs; ++idx) { |
| 121 | // For vararg functions, a corresponding parameter decl may not exist. |
| 122 | bool HasParam = idx < parms.size(); |
| 123 | |
| 124 | // Check if the parameter is a reference. We want to report when reference |
| 125 | // to a null pointer is passed as a parameter. |
| 126 | bool HasRefTypeParam = |
| 127 | HasParam ? parms[idx]->getType()->isReferenceType() : false; |
| 128 | bool ExpectedToBeNonNull = AttrNonNull.test(Idx: idx); |
| 129 | |
| 130 | if (!ExpectedToBeNonNull && !HasRefTypeParam) |
| 131 | continue; |
| 132 | |
| 133 | // If the value is unknown or undefined, we can't perform this check. |
| 134 | const Expr *ArgE = Call.getArgExpr(Index: idx); |
| 135 | SVal V = Call.getArgSVal(Index: idx); |
| 136 | auto DV = V.getAs<DefinedSVal>(); |
| 137 | if (!DV) |
| 138 | continue; |
| 139 | |
| 140 | assert(!HasRefTypeParam || isa<Loc>(*DV)); |
| 141 | |
| 142 | // Process the case when the argument is not a location. |
| 143 | if (ExpectedToBeNonNull && !isa<Loc>(Val: *DV)) { |
| 144 | // If the argument is a union type, we want to handle a potential |
| 145 | // transparent_union GCC extension. |
| 146 | if (!ArgE) |
| 147 | continue; |
| 148 | |
| 149 | QualType T = ArgE->getType(); |
| 150 | const RecordType *UT = T->getAsUnionType(); |
| 151 | if (!UT || !UT->getDecl()->hasAttr<TransparentUnionAttr>()) |
| 152 | continue; |
| 153 | |
| 154 | auto CSV = DV->getAs<nonloc::CompoundVal>(); |
| 155 | |
| 156 | // FIXME: Handle LazyCompoundVals? |
| 157 | if (!CSV) |
| 158 | continue; |
| 159 | |
| 160 | V = *(CSV->begin()); |
| 161 | DV = V.getAs<DefinedSVal>(); |
| 162 | assert(++CSV->begin() == CSV->end()); |
| 163 | // FIXME: Handle (some_union){ some_other_union_val }, which turns into |
| 164 | // a LazyCompoundVal inside a CompoundVal. |
| 165 | if (!isa<Loc>(Val: V)) |
| 166 | continue; |
| 167 | |
| 168 | // Retrieve the corresponding expression. |
| 169 | if (const auto *CE = dyn_cast<CompoundLiteralExpr>(Val: ArgE)) |
| 170 | if (const auto *IE = dyn_cast<InitListExpr>(Val: CE->getInitializer())) |
| 171 | ArgE = dyn_cast<Expr>(Val: *(IE->begin())); |
| 172 | } |
| 173 | |
| 174 | ConstraintManager &CM = C.getConstraintManager(); |
| 175 | ProgramStateRef stateNotNull, stateNull; |
| 176 | std::tie(args&: stateNotNull, args&: stateNull) = CM.assumeDual(State: state, Cond: *DV); |
| 177 | |
| 178 | // Generate an error node. Check for a null node in case |
| 179 | // we cache out. |
| 180 | if (stateNull && !stateNotNull) { |
| 181 | if (ExplodedNode *errorNode = C.generateErrorNode(State: stateNull)) { |
| 182 | |
| 183 | std::unique_ptr<BugReport> R; |
| 184 | if (ExpectedToBeNonNull) |
| 185 | R = genReportNullAttrNonNull(ErrorN: errorNode, ArgE, IdxOfArg: idx + 1); |
| 186 | else if (HasRefTypeParam) |
| 187 | R = genReportReferenceToNullPointer(ErrorN: errorNode, ArgE); |
| 188 | |
| 189 | // Highlight the range of the argument that was null. |
| 190 | R->addRange(R: Call.getArgSourceRange(Index: idx)); |
| 191 | |
| 192 | // Emit the bug report. |
| 193 | C.emitReport(R: std::move(R)); |
| 194 | } |
| 195 | |
| 196 | // Always return. Either we cached out or we just emitted an error. |
| 197 | return; |
| 198 | } |
| 199 | |
| 200 | if (stateNull) { |
| 201 | if (ExplodedNode *N = C.generateSink(State: stateNull, Pred: C.getPredecessor())) { |
| 202 | ImplicitNullDerefEvent event = { |
| 203 | .Location: V, .IsLoad: false, .SinkNode: N, .BR: &C.getBugReporter(), |
| 204 | /*IsDirectDereference=*/HasRefTypeParam}; |
| 205 | dispatchEvent(event); |
| 206 | } |
| 207 | } |
| 208 | |
| 209 | // If a pointer value passed the check we should assume that it is |
| 210 | // indeed not null from this point forward. |
| 211 | state = stateNotNull; |
| 212 | } |
| 213 | |
| 214 | // If we reach here all of the arguments passed the nonnull check. |
| 215 | // If 'state' has been updated generated a new node. |
| 216 | C.addTransition(State: state); |
| 217 | } |
| 218 | |
| 219 | /// We want to trust developer annotations and consider all 'nonnull' parameters |
| 220 | /// as non-null indeed. Each marked parameter will get a corresponding |
| 221 | /// constraint. |
| 222 | /// |
| 223 | /// This approach will not only help us to get rid of some false positives, but |
| 224 | /// remove duplicates and shorten warning traces as well. |
| 225 | /// |
| 226 | /// \code |
| 227 | /// void foo(int *x) [[gnu::nonnull]] { |
| 228 | /// // . . . |
| 229 | /// *x = 42; // we don't want to consider this as an error... |
| 230 | /// // . . . |
| 231 | /// } |
| 232 | /// |
| 233 | /// foo(nullptr); // ...and report here instead |
| 234 | /// \endcode |
| 235 | void NonNullParamChecker::checkBeginFunction(CheckerContext &Context) const { |
| 236 | // Planned assumption makes sense only for top-level functions. |
| 237 | // Inlined functions will get similar constraints as part of 'checkPreCall'. |
| 238 | if (!Context.inTopFrame()) |
| 239 | return; |
| 240 | |
| 241 | const LocationContext *LocContext = Context.getLocationContext(); |
| 242 | |
| 243 | const Decl *FD = LocContext->getDecl(); |
| 244 | // AnyCall helps us here to avoid checking for FunctionDecl and ObjCMethodDecl |
| 245 | // separately and aggregates interfaces of these classes. |
| 246 | auto AbstractCall = AnyCall::forDecl(D: FD); |
| 247 | if (!AbstractCall) |
| 248 | return; |
| 249 | |
| 250 | ProgramStateRef State = Context.getState(); |
| 251 | llvm::SmallBitVector ParameterNonNullMarks = getNonNullAttrs(Call: *AbstractCall); |
| 252 | |
| 253 | for (const ParmVarDecl *Parameter : AbstractCall->parameters()) { |
| 254 | // 1. Check parameter if it is annotated as non-null |
| 255 | if (!ParameterNonNullMarks.test(Idx: Parameter->getFunctionScopeIndex())) |
| 256 | continue; |
| 257 | |
| 258 | // 2. Check that parameter is a pointer. |
| 259 | // Nonnull attribute can be applied to non-pointers (by default |
| 260 | // __attribute__(nonnull) implies "all parameters"). |
| 261 | if (!Parameter->getType()->isPointerType()) |
| 262 | continue; |
| 263 | |
| 264 | Loc ParameterLoc = State->getLValue(VD: Parameter, LC: LocContext); |
| 265 | // We never consider top-level function parameters undefined. |
| 266 | auto StoredVal = |
| 267 | State->getSVal(LV: ParameterLoc).castAs<DefinedOrUnknownSVal>(); |
| 268 | |
| 269 | // 3. Assume that it is indeed non-null |
| 270 | if (ProgramStateRef NewState = State->assume(Cond: StoredVal, Assumption: true)) { |
| 271 | State = NewState; |
| 272 | } |
| 273 | } |
| 274 | |
| 275 | Context.addTransition(State); |
| 276 | } |
| 277 | |
| 278 | std::unique_ptr<PathSensitiveBugReport> |
| 279 | NonNullParamChecker::genReportNullAttrNonNull(const ExplodedNode *ErrorNode, |
| 280 | const Expr *ArgE, |
| 281 | unsigned IdxOfArg) const { |
| 282 | llvm::SmallString<256> SBuf; |
| 283 | llvm::raw_svector_ostream OS(SBuf); |
| 284 | OS << "Null pointer passed to " |
| 285 | << IdxOfArg << llvm::getOrdinalSuffix(Val: IdxOfArg) |
| 286 | << " parameter expecting 'nonnull'" ; |
| 287 | |
| 288 | auto R = |
| 289 | std::make_unique<PathSensitiveBugReport>(args: BTAttrNonNull, args&: SBuf, args&: ErrorNode); |
| 290 | if (ArgE) |
| 291 | bugreporter::trackExpressionValue(N: ErrorNode, E: ArgE, R&: *R); |
| 292 | |
| 293 | return R; |
| 294 | } |
| 295 | |
| 296 | std::unique_ptr<PathSensitiveBugReport> |
| 297 | NonNullParamChecker::genReportReferenceToNullPointer( |
| 298 | const ExplodedNode *ErrorNode, const Expr *ArgE) const { |
| 299 | auto R = std::make_unique<PathSensitiveBugReport>( |
| 300 | args: BTNullRefArg, args: "Forming reference to null pointer" , args&: ErrorNode); |
| 301 | if (ArgE) { |
| 302 | const Expr *ArgEDeref = bugreporter::getDerefExpr(S: ArgE); |
| 303 | if (!ArgEDeref) |
| 304 | ArgEDeref = ArgE; |
| 305 | bugreporter::trackExpressionValue(N: ErrorNode, E: ArgEDeref, R&: *R); |
| 306 | } |
| 307 | return R; |
| 308 | } |
| 309 | |
| 310 | void ento::registerNonNullParamChecker(CheckerManager &mgr) { |
| 311 | mgr.registerChecker<NonNullParamChecker>(); |
| 312 | } |
| 313 | |
| 314 | bool ento::shouldRegisterNonNullParamChecker(const CheckerManager &mgr) { |
| 315 | return true; |
| 316 | } |
| 317 | |