void ObjCAtSyncChecker::checkPreStmt(const ObjCAtSynchronizedStmt *S, CheckerContext &C) const { const Expr *Ex = S->getSynchExpr(); ProgramStateRef state = C.getState(); SVal V = C.getSVal(Ex); // Uninitialized value used for the mutex? if (V.getAs<UndefinedVal>()) { if (ExplodedNode *N = C.generateErrorNode()) { if (!BT_undef) BT_undef.reset(new BuiltinBug(this, "Uninitialized value used as mutex " "for @synchronized")); auto report = llvm::make_unique<BugReport>(*BT_undef, BT_undef->getDescription(), N); bugreporter::trackExpressionValue(N, Ex, *report); C.emitReport(std::move(report)); } return; } if (V.isUnknown()) return; // Check for null mutexes. ProgramStateRef notNullState, nullState; std::tie(notNullState, nullState) = state->assume(V.castAs<DefinedSVal>()); if (nullState) { if (!notNullState) { // Generate an error node. This isn't a sink since // a null mutex just means no synchronization occurs. if (ExplodedNode *N = C.generateNonFatalErrorNode(nullState)) { if (!BT_null) BT_null.reset(new BuiltinBug( this, "Nil value used as mutex for @synchronized() " "(no synchronization will occur)")); auto report = llvm::make_unique<BugReport>(*BT_null, BT_null->getDescription(), N); bugreporter::trackExpressionValue(N, Ex, *report); C.emitReport(std::move(report)); return; } } // Don't add a transition for 'nullState'. If the value is // under-constrained to be null or non-null, assume it is non-null // afterwards. } if (notNullState) C.addTransition(notNullState); }
void PthreadLockChecker::reportUseDestroyedBug(CheckerContext &C, const CallExpr *CE) const { if (!BT_destroylock) BT_destroylock.reset(new BugType(this, "Use destroyed lock", "Lock checker")); ExplodedNode *N = C.generateErrorNode(); if (!N) return; auto Report = llvm::make_unique<BugReport>( *BT_destroylock, "This lock has already been destroyed", N); Report->addRange(CE->getArg(0)->getSourceRange()); C.emitReport(std::move(Report)); }
void DoubleFetchChecker::reportDoubleFetch(CheckerContext &Ctx, const CallEvent &Call) const { // We reached a bug, stop exploring the path here by generating a sink. ExplodedNode *ErrNode = Ctx.generateErrorNode(Ctx.getState()); // If we've already reached this node on another path, return. if (!ErrNode) return; // Generate the report. auto R = llvm::make_unique<BugReport>(*DoubleFetchType, "Double-Fetch", ErrNode); R->addRange(Call.getSourceRange()); Ctx.emitReport(std::move(R)); }
void DivZeroChecker::reportBug( const char *Msg, ProgramStateRef StateZero, CheckerContext &C, std::unique_ptr<BugReporterVisitor> Visitor) const { if (ExplodedNode *N = C.generateErrorNode(StateZero)) { if (!BT) BT.reset(new BuiltinBug(this, "Division by zero")); auto R = llvm::make_unique<BugReport>(*BT, Msg, N); R->addVisitor(std::move(Visitor)); bugreporter::trackExpressionValue(N, getDenomExpr(N), *R); C.emitReport(std::move(R)); } }
void NilArgChecker::warnIfNilArg(CheckerContext &C, const ObjCMethodCall &msg, unsigned int Arg, FoundationClass Class, bool CanBeSubscript) const { // Check if the argument is nil. ProgramStateRef State = C.getState(); if (!State->isNull(msg.getArgSVal(Arg)).isConstrainedTrue()) return; if (ExplodedNode *N = C.generateErrorNode()) { SmallString<128> sbuf; llvm::raw_svector_ostream os(sbuf); if (CanBeSubscript && msg.getMessageKind() == OCM_Subscript) { if (Class == FC_NSArray) { os << "Array element cannot be nil"; } else if (Class == FC_NSDictionary) { if (Arg == 0) { os << "Value stored into '"; os << GetReceiverInterfaceName(msg) << "' cannot be nil"; } else { assert(Arg == 1); os << "'"<< GetReceiverInterfaceName(msg) << "' key cannot be nil"; } } else llvm_unreachable("Missing foundation class for the subscript expr"); } else { if (Class == FC_NSDictionary) { if (Arg == 0) os << "Value argument "; else { assert(Arg == 1); os << "Key argument "; } os << "to '"; msg.getSelector().print(os); os << "' cannot be nil"; } else { os << "Argument to '" << GetReceiverInterfaceName(msg) << "' method '"; msg.getSelector().print(os); os << "' cannot be nil"; } } generateBugReport(N, os.str(), msg.getArgSourceRange(Arg), msg.getArgExpr(Arg), C); } }
void UnixAPIChecker::ReportOpenBug(CheckerContext &C, ProgramStateRef State, const char *Msg, SourceRange SR) const { ExplodedNode *N = C.generateErrorNode(State); if (!N) return; LazyInitialize(BT_open, "Improper use of 'open'"); auto Report = llvm::make_unique<BugReport>(*BT_open, Msg, N); Report->addRange(SR); C.emitReport(std::move(Report)); }
void CallAndMessageChecker::emitBadCall(BugType *BT, CheckerContext &C, const Expr *BadE) { ExplodedNode *N = C.generateErrorNode(); if (!N) return; auto R = llvm::make_unique<BugReport>(*BT, BT->getName(), N); if (BadE) { R->addRange(BadE->getSourceRange()); if (BadE->isGLValue()) BadE = bugreporter::getDerefExpr(BadE); bugreporter::trackExpressionValue(N, BadE, *R); } C.emitReport(std::move(R)); }
void TestAfterDivZeroChecker::reportBug(SVal Val, CheckerContext &C) const { if (ExplodedNode *N = C.generateErrorNode(C.getState())) { if (!DivZeroBug) DivZeroBug.reset(new BuiltinBug(this, "Division by zero")); auto R = llvm::make_unique<BugReport>( *DivZeroBug, "Value being compared against zero has already been used " "for division", N); R->addVisitor(llvm::make_unique<DivisionBRVisitor>(Val.getAsSymbol(), C.getStackFrame())); C.emitReport(std::move(R)); } }
void SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym, const CallEvent &Call, CheckerContext &C) const { // We reached a bug, stop exploring the path here by generating a sink. ExplodedNode *ErrNode = C.generateErrorNode(); // If we've already reached this node on another path, return. if (!ErrNode) return; // Generate the report. auto R = llvm::make_unique<BugReport>(*DoubleCloseBugType, "Closing a previously closed file stream", ErrNode); R->addRange(Call.getSourceRange()); R->markInteresting(FileDescSym); C.emitReport(std::move(R)); }
bool CallAndMessageChecker::uninitRefOrPointer( CheckerContext &C, const SVal &V, SourceRange ArgRange, const Expr *ArgEx, std::unique_ptr<BugType> &BT, const ParmVarDecl *ParamDecl, const char *BD, int ArgumentNumber) const { if (!Filter.Check_CallAndMessageUnInitRefArg) return false; // No parameter declaration available, i.e. variadic function argument. if(!ParamDecl) return false; // If parameter is declared as pointer to const in function declaration, // then check if corresponding argument in function call is // pointing to undefined symbol value (uninitialized memory). SmallString<200> Buf; llvm::raw_svector_ostream Os(Buf); if (ParamDecl->getType()->isPointerType()) { Os << (ArgumentNumber + 1) << llvm::getOrdinalSuffix(ArgumentNumber + 1) << " function call argument is a pointer to uninitialized value"; } else if (ParamDecl->getType()->isReferenceType()) { Os << (ArgumentNumber + 1) << llvm::getOrdinalSuffix(ArgumentNumber + 1) << " function call argument is an uninitialized value"; } else return false; if(!ParamDecl->getType()->getPointeeType().isConstQualified()) return false; if (const MemRegion *SValMemRegion = V.getAsRegion()) { const ProgramStateRef State = C.getState(); const SVal PSV = State->getSVal(SValMemRegion, C.getASTContext().CharTy); if (PSV.isUndef()) { if (ExplodedNode *N = C.generateErrorNode()) { LazyInit_BT(BD, BT); auto R = llvm::make_unique<BugReport>(*BT, Os.str(), N); R->addRange(ArgRange); if (ArgEx) bugreporter::trackExpressionValue(N, ArgEx, *R); C.emitReport(std::move(R)); } return true; } } return false; }
void CFRetainReleaseChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { // TODO: Make this check part of CallDescription. if (!Call.isGlobalCFunction()) return; // Check if we called CFRetain/CFRelease/CFMakeCollectable/CFAutorelease. if (!(Call.isCalled(CFRetain) || Call.isCalled(CFRelease) || Call.isCalled(CFMakeCollectable) || Call.isCalled(CFAutorelease))) return; // Get the argument's value. SVal ArgVal = Call.getArgSVal(0); Optional<DefinedSVal> DefArgVal = ArgVal.getAs<DefinedSVal>(); if (!DefArgVal) return; // Is it null? ProgramStateRef state = C.getState(); ProgramStateRef stateNonNull, stateNull; std::tie(stateNonNull, stateNull) = state->assume(*DefArgVal); if (!stateNonNull) { ExplodedNode *N = C.generateErrorNode(stateNull); if (!N) return; SmallString<64> Str; raw_svector_ostream OS(Str); OS << "Null pointer argument in call to " << cast<FunctionDecl>(Call.getDecl())->getName(); auto report = llvm::make_unique<BugReport>(BT, OS.str(), N); report->addRange(Call.getArgSourceRange(0)); bugreporter::trackExpressionValue(N, Call.getArgExpr(0), *report); C.emitReport(std::move(report)); return; } // From here on, we know the argument is non-null. C.addTransition(stateNonNull); }
void CallAndMessageChecker::checkPreObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const { SVal recVal = msg.getReceiverSVal(); if (recVal.isUndef()) { if (ExplodedNode *N = C.generateErrorNode()) { BugType *BT = nullptr; switch (msg.getMessageKind()) { case OCM_Message: if (!BT_msg_undef) BT_msg_undef.reset(new BuiltinBug(this, "Receiver in message expression " "is an uninitialized value")); BT = BT_msg_undef.get(); break; case OCM_PropertyAccess: if (!BT_objc_prop_undef) BT_objc_prop_undef.reset(new BuiltinBug( this, "Property access on an uninitialized object pointer")); BT = BT_objc_prop_undef.get(); break; case OCM_Subscript: if (!BT_objc_subscript_undef) BT_objc_subscript_undef.reset(new BuiltinBug( this, "Subscript access on an uninitialized object pointer")); BT = BT_objc_subscript_undef.get(); break; } assert(BT && "Unknown message kind."); auto R = llvm::make_unique<BugReport>(*BT, BT->getName(), N); const ObjCMessageExpr *ME = msg.getOriginExpr(); R->addRange(ME->getReceiverRange()); // FIXME: getTrackNullOrUndefValueVisitor can't handle "super" yet. if (const Expr *ReceiverE = ME->getInstanceReceiver()) bugreporter::trackExpressionValue(N, ReceiverE, *R); C.emitReport(std::move(R)); } return; } }
void ObjCSelfInitChecker::checkForInvalidSelf(const Expr *E, CheckerContext &C, const char *errorStr) const { if (!E) return; if (!C.getState()->get<CalledInit>()) return; if (!isInvalidSelf(E, C)) return; // Generate an error node. ExplodedNode *N = C.generateErrorNode(); if (!N) return; if (!BT) BT.reset(new BugType(this, "Missing \"self = [(super or self) init...]\"", categories::CoreFoundationObjectiveC)); C.emitReport(llvm::make_unique<BugReport>(*BT, errorStr, N)); }
void UnixAPIChecker::CheckPthreadOnce(CheckerContext &C, const CallExpr *CE) const { // This is similar to 'CheckDispatchOnce' in the MacOSXAPIChecker. // They can possibly be refactored. if (CE->getNumArgs() < 1) return; // Check if the first argument is stack allocated. If so, issue a warning // because that's likely to be bad news. ProgramStateRef state = C.getState(); const MemRegion *R = state->getSVal(CE->getArg(0), C.getLocationContext()).getAsRegion(); if (!R || !isa<StackSpaceRegion>(R->getMemorySpace())) return; ExplodedNode *N = C.generateErrorNode(state); if (!N) return; SmallString<256> S; llvm::raw_svector_ostream os(S); os << "Call to 'pthread_once' uses"; if (const VarRegion *VR = dyn_cast<VarRegion>(R)) os << " the local variable '" << VR->getDecl()->getName() << '\''; else os << " stack allocated memory"; os << " for the \"control\" value. Using such transient memory for " "the control value is potentially dangerous."; if (isa<VarRegion>(R) && isa<StackLocalsSpaceRegion>(R->getMemorySpace())) os << " Perhaps you intended to declare the variable as 'static'?"; LazyInitialize(BT_pthreadOnce, "Improper use of 'pthread_once'"); auto report = llvm::make_unique<BugReport>(*BT_pthreadOnce, os.str(), N); report->addRange(CE->getArg(0)->getSourceRange()); C.emitReport(std::move(report)); }
ProgramStateRef StreamChecker::CheckNullStream(SVal SV, ProgramStateRef state, CheckerContext &C) const { Optional<DefinedSVal> DV = SV.getAs<DefinedSVal>(); if (!DV) return nullptr; ConstraintManager &CM = C.getConstraintManager(); ProgramStateRef stateNotNull, stateNull; std::tie(stateNotNull, stateNull) = CM.assumeDual(state, *DV); if (!stateNotNull && stateNull) { if (ExplodedNode *N = C.generateErrorNode(stateNull)) { if (!BT_nullfp) BT_nullfp.reset(new BuiltinBug(this, "NULL stream pointer", "Stream pointer might be NULL.")); C.emitReport(llvm::make_unique<BugReport>( *BT_nullfp, BT_nullfp->getDescription(), N)); } return nullptr; } return stateNotNull; }
// Generates an error report, indicating that the function whose name is given // will perform a zero byte allocation. // Returns false if an error occurred, true otherwise. bool UnixAPIChecker::ReportZeroByteAllocation(CheckerContext &C, ProgramStateRef falseState, const Expr *arg, const char *fn_name) const { ExplodedNode *N = C.generateErrorNode(falseState); if (!N) return false; LazyInitialize(BT_mallocZero, "Undefined allocation of 0 bytes (CERT MEM04-C; CWE-131)"); SmallString<256> S; llvm::raw_svector_ostream os(S); os << "Call to '" << fn_name << "' has an allocation size of 0 bytes"; auto report = llvm::make_unique<BugReport>(*BT_mallocZero, os.str(), N); report->addRange(arg->getSourceRange()); bugreporter::trackNullOrUndefValue(N, arg, *report); C.emitReport(std::move(report)); return true; }
/// Report a use-after-dealloc on Sym. If not empty, /// Desc will be used to describe the error; otherwise, /// a default warning will be used. void ObjCSuperDeallocChecker::reportUseAfterDealloc(SymbolRef Sym, StringRef Desc, const Stmt *S, CheckerContext &C) const { // We have a use of self after free. // This likely causes a crash, so stop exploring the // path by generating a sink. ExplodedNode *ErrNode = C.generateErrorNode(); // If we've already reached this node on another path, return. if (!ErrNode) return; if (Desc.empty()) Desc = "Use of 'self' after it has been deallocated"; // Generate the report. std::unique_ptr<BugReport> BR( new BugReport(*DoubleSuperDeallocBugType, Desc, ErrNode)); BR->addRange(S->getSourceRange()); BR->addVisitor(llvm::make_unique<SuperDeallocBRVisitor>(Sym)); C.emitReport(std::move(BR)); }
void PthreadLockChecker::InitLock(CheckerContext &C, const CallExpr *CE, SVal Lock) const { const MemRegion *LockR = Lock.getAsRegion(); if (!LockR) return; ProgramStateRef State = C.getState(); const SymbolRef *sym = State->get<DestroyRetVal>(LockR); if (sym) State = resolvePossiblyDestroyedMutex(State, LockR, sym); const struct LockState *LState = State->get<LockMap>(LockR); if (!LState || LState->isDestroyed()) { State = State->set<LockMap>(LockR, LockState::getUnlocked()); C.addTransition(State); return; } StringRef Message; if (LState->isLocked()) { Message = "This lock is still being held"; } else { Message = "This lock has already been initialized"; } if (!BT_initlock) BT_initlock.reset(new BugType(this, "Init invalid lock", "Lock checker")); ExplodedNode *N = C.generateErrorNode(); if (!N) return; auto Report = llvm::make_unique<BugReport>(*BT_initlock, Message, N); Report->addRange(CE->getArg(0)->getSourceRange()); C.emitReport(std::move(Report)); }
void StackAddrEscapeChecker::EmitStackError(CheckerContext &C, const MemRegion *R, const Expr *RetE) const { ExplodedNode *N = C.generateErrorNode(); if (!N) return; if (!BT_returnstack) BT_returnstack.reset( new BuiltinBug(this, "Return of address to stack-allocated memory")); // Generate a report for this bug. SmallString<512> buf; llvm::raw_svector_ostream os(buf); SourceRange range = genName(os, R, C.getASTContext()); os << " returned to caller"; auto report = llvm::make_unique<BugReport>(*BT_returnstack, os.str(), N); report->addRange(RetE->getSourceRange()); if (range.isValid()) report->addRange(range); C.emitReport(std::move(report)); }
void CallAndMessageChecker::checkPreStmt(const CXXDeleteExpr *DE, CheckerContext &C) const { SVal Arg = C.getSVal(DE->getArgument()); if (Arg.isUndef()) { StringRef Desc; ExplodedNode *N = C.generateErrorNode(); if (!N) return; if (!BT_cxx_delete_undef) BT_cxx_delete_undef.reset( new BuiltinBug(this, "Uninitialized argument value")); if (DE->isArrayFormAsWritten()) Desc = "Argument to 'delete[]' is uninitialized"; else Desc = "Argument to 'delete' is uninitialized"; BugType *BT = BT_cxx_delete_undef.get(); auto R = llvm::make_unique<BugReport>(*BT, Desc, N); bugreporter::trackExpressionValue(N, DE, *R); C.emitReport(std::move(R)); return; } }
void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const { // TODO: Clean up the state. for (SymbolReaper::dead_iterator I = SymReaper.dead_begin(), E = SymReaper.dead_end(); I != E; ++I) { SymbolRef Sym = *I; ProgramStateRef state = C.getState(); const StreamState *SS = state->get<StreamMap>(Sym); if (!SS) continue; if (SS->isOpened()) { ExplodedNode *N = C.generateErrorNode(); if (N) { if (!BT_ResourceLeak) BT_ResourceLeak.reset(new BuiltinBug( this, "Resource Leak", "Opened File never closed. Potential Resource leak.")); C.emitReport(llvm::make_unique<BugReport>( *BT_ResourceLeak, BT_ResourceLeak->getDescription(), N)); } } } }
/// This callback warns when a nullable pointer or a null value is passed to a /// function that expects its argument to be nonnull. void NullabilityChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { if (!Call.getDecl()) return; ProgramStateRef State = C.getState(); if (State->get<InvariantViolated>()) return; ProgramStateRef OrigState = State; unsigned Idx = 0; for (const ParmVarDecl *Param : Call.parameters()) { if (Param->isParameterPack()) break; if (Idx >= Call.getNumArgs()) break; const Expr *ArgExpr = Call.getArgExpr(Idx); auto ArgSVal = Call.getArgSVal(Idx++).getAs<DefinedOrUnknownSVal>(); if (!ArgSVal) continue; if (!Param->getType()->isAnyPointerType() && !Param->getType()->isReferenceType()) continue; NullConstraint Nullness = getNullConstraint(*ArgSVal, State); Nullability RequiredNullability = getNullabilityAnnotation(Param->getType()); Nullability ArgExprTypeLevelNullability = getNullabilityAnnotation(ArgExpr->getType()); unsigned ParamIdx = Param->getFunctionScopeIndex() + 1; if (Filter.CheckNullPassedToNonnull && Nullness == NullConstraint::IsNull && ArgExprTypeLevelNullability != Nullability::Nonnull && RequiredNullability == Nullability::Nonnull && isDiagnosableCall(Call)) { ExplodedNode *N = C.generateErrorNode(State); if (!N) return; SmallString<256> SBuf; llvm::raw_svector_ostream OS(SBuf); OS << (Param->getType()->isObjCObjectPointerType() ? "nil" : "Null"); OS << " passed to a callee that requires a non-null " << ParamIdx << llvm::getOrdinalSuffix(ParamIdx) << " parameter"; reportBugIfInvariantHolds(OS.str(), ErrorKind::NilPassedToNonnull, N, nullptr, C, ArgExpr, /*SuppressPath=*/false); return; } const MemRegion *Region = getTrackRegion(*ArgSVal); if (!Region) continue; const NullabilityState *TrackedNullability = State->get<NullabilityMap>(Region); if (TrackedNullability) { if (Nullness == NullConstraint::IsNotNull || TrackedNullability->getValue() != Nullability::Nullable) continue; if (Filter.CheckNullablePassedToNonnull && RequiredNullability == Nullability::Nonnull && isDiagnosableCall(Call)) { ExplodedNode *N = C.addTransition(State); SmallString<256> SBuf; llvm::raw_svector_ostream OS(SBuf); OS << "Nullable pointer is passed to a callee that requires a non-null " << ParamIdx << llvm::getOrdinalSuffix(ParamIdx) << " parameter"; reportBugIfInvariantHolds(OS.str(), ErrorKind::NullablePassedToNonnull, N, Region, C, ArgExpr, /*SuppressPath=*/true); return; } if (Filter.CheckNullableDereferenced && Param->getType()->isReferenceType()) { ExplodedNode *N = C.addTransition(State); reportBugIfInvariantHolds("Nullable pointer is dereferenced", ErrorKind::NullableDereferenced, N, Region, C, ArgExpr, /*SuppressPath=*/true); return; } continue; } // No tracked nullability yet. if (ArgExprTypeLevelNullability != Nullability::Nullable) continue; State = State->set<NullabilityMap>( Region, NullabilityState(ArgExprTypeLevelNullability, ArgExpr)); } if (State != OrigState) C.addTransition(State); }
/// Propagate the nullability information through binds and warn when nullable /// pointer or null symbol is assigned to a pointer with a nonnull type. void NullabilityChecker::checkBind(SVal L, SVal V, const Stmt *S, CheckerContext &C) const { const TypedValueRegion *TVR = dyn_cast_or_null<TypedValueRegion>(L.getAsRegion()); if (!TVR) return; QualType LocType = TVR->getValueType(); if (!LocType->isAnyPointerType()) return; ProgramStateRef State = C.getState(); if (State->get<PreconditionViolated>()) return; auto ValDefOrUnknown = V.getAs<DefinedOrUnknownSVal>(); if (!ValDefOrUnknown) return; NullConstraint RhsNullness = getNullConstraint(*ValDefOrUnknown, State); Nullability ValNullability = Nullability::Unspecified; if (SymbolRef Sym = ValDefOrUnknown->getAsSymbol()) ValNullability = getNullabilityAnnotation(Sym->getType()); Nullability LocNullability = getNullabilityAnnotation(LocType); if (Filter.CheckNullPassedToNonnull && RhsNullness == NullConstraint::IsNull && ValNullability != Nullability::Nonnull && LocNullability == Nullability::Nonnull) { static CheckerProgramPointTag Tag(this, "NullPassedToNonnull"); ExplodedNode *N = C.generateErrorNode(State, &Tag); if (!N) return; const Stmt *ValueExpr = matchValueExprForBind(S); if (!ValueExpr) ValueExpr = S; reportBugIfPreconditionHolds(ErrorKind::NilAssignedToNonnull, N, nullptr, C, ValueExpr); return; } // Intentionally missing case: '0' is bound to a reference. It is handled by // the DereferenceChecker. const MemRegion *ValueRegion = getTrackRegion(*ValDefOrUnknown); if (!ValueRegion) return; const NullabilityState *TrackedNullability = State->get<NullabilityMap>(ValueRegion); if (TrackedNullability) { if (RhsNullness == NullConstraint::IsNotNull || TrackedNullability->getValue() != Nullability::Nullable) return; if (Filter.CheckNullablePassedToNonnull && LocNullability == Nullability::Nonnull) { static CheckerProgramPointTag Tag(this, "NullablePassedToNonnull"); ExplodedNode *N = C.addTransition(State, C.getPredecessor(), &Tag); reportBugIfPreconditionHolds(ErrorKind::NullableAssignedToNonnull, N, ValueRegion, C); } return; } const auto *BinOp = dyn_cast<BinaryOperator>(S); if (ValNullability == Nullability::Nullable) { // Trust the static information of the value more than the static // information on the location. const Stmt *NullabilitySource = BinOp ? BinOp->getRHS() : S; State = State->set<NullabilityMap>( ValueRegion, NullabilityState(ValNullability, NullabilitySource)); C.addTransition(State); return; } if (LocNullability == Nullability::Nullable) { const Stmt *NullabilitySource = BinOp ? BinOp->getLHS() : S; State = State->set<NullabilityMap>( ValueRegion, NullabilityState(LocNullability, NullabilitySource)); C.addTransition(State); } }
/// This callback warns when a nullable pointer or a null value is passed to a /// function that expects its argument to be nonnull. void NullabilityChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { if (!Call.getDecl()) return; ProgramStateRef State = C.getState(); if (State->get<PreconditionViolated>()) return; ProgramStateRef OrigState = State; unsigned Idx = 0; for (const ParmVarDecl *Param : Call.parameters()) { if (Param->isParameterPack()) break; const Expr *ArgExpr = nullptr; if (Idx < Call.getNumArgs()) ArgExpr = Call.getArgExpr(Idx); auto ArgSVal = Call.getArgSVal(Idx++).getAs<DefinedOrUnknownSVal>(); if (!ArgSVal) continue; if (!Param->getType()->isAnyPointerType() && !Param->getType()->isReferenceType()) continue; NullConstraint Nullness = getNullConstraint(*ArgSVal, State); Nullability ParamNullability = getNullabilityAnnotation(Param->getType()); Nullability ArgStaticNullability = getNullabilityAnnotation(ArgExpr->getType()); if (Filter.CheckNullPassedToNonnull && Nullness == NullConstraint::IsNull && ArgStaticNullability != Nullability::Nonnull && ParamNullability == Nullability::Nonnull) { ExplodedNode *N = C.generateErrorNode(State); if (!N) return; reportBugIfPreconditionHolds(ErrorKind::NilPassedToNonnull, N, nullptr, C, ArgExpr); return; } const MemRegion *Region = getTrackRegion(*ArgSVal); if (!Region) continue; const NullabilityState *TrackedNullability = State->get<NullabilityMap>(Region); if (TrackedNullability) { if (Nullness == NullConstraint::IsNotNull || TrackedNullability->getValue() != Nullability::Nullable) continue; if (Filter.CheckNullablePassedToNonnull && ParamNullability == Nullability::Nonnull) { ExplodedNode *N = C.addTransition(State); reportBugIfPreconditionHolds(ErrorKind::NullablePassedToNonnull, N, Region, C, ArgExpr, /*SuppressPath=*/true); return; } if (Filter.CheckNullableDereferenced && Param->getType()->isReferenceType()) { ExplodedNode *N = C.addTransition(State); reportBugIfPreconditionHolds(ErrorKind::NullableDereferenced, N, Region, C, ArgExpr, /*SuppressPath=*/true); return; } continue; } // No tracked nullability yet. if (ArgStaticNullability != Nullability::Nullable) continue; State = State->set<NullabilityMap>( Region, NullabilityState(ArgStaticNullability, ArgExpr)); } if (State != OrigState) C.addTransition(State); }
/// This method check when nullable pointer or null value is returned from a /// function that has nonnull return type. /// /// TODO: when nullability preconditons are violated, it is ok to violate the /// nullability postconditons (i.e.: when one of the nonnull parameters are null /// this check should not report any nullability related issue). void NullabilityChecker::checkPreStmt(const ReturnStmt *S, CheckerContext &C) const { auto RetExpr = S->getRetValue(); if (!RetExpr) return; if (!RetExpr->getType()->isAnyPointerType()) return; ProgramStateRef State = C.getState(); if (State->get<PreconditionViolated>()) return; auto RetSVal = State->getSVal(S, C.getLocationContext()).getAs<DefinedOrUnknownSVal>(); if (!RetSVal) return; AnalysisDeclContext *DeclCtxt = C.getLocationContext()->getAnalysisDeclContext(); const FunctionType *FuncType = DeclCtxt->getDecl()->getFunctionType(); if (!FuncType) return; NullConstraint Nullness = getNullConstraint(*RetSVal, State); Nullability StaticNullability = getNullabilityAnnotation(FuncType->getReturnType()); if (Filter.CheckNullReturnedFromNonnull && Nullness == NullConstraint::IsNull && StaticNullability == Nullability::Nonnull) { static CheckerProgramPointTag Tag(this, "NullReturnedFromNonnull"); ExplodedNode *N = C.generateErrorNode(State, &Tag); if (!N) return; reportBugIfPreconditionHolds(ErrorKind::NilReturnedToNonnull, N, nullptr, C, RetExpr); return; } const MemRegion *Region = getTrackRegion(*RetSVal); if (!Region) return; const NullabilityState *TrackedNullability = State->get<NullabilityMap>(Region); if (TrackedNullability) { Nullability TrackedNullabValue = TrackedNullability->getValue(); if (Filter.CheckNullableReturnedFromNonnull && Nullness != NullConstraint::IsNotNull && TrackedNullabValue == Nullability::Nullable && StaticNullability == Nullability::Nonnull) { static CheckerProgramPointTag Tag(this, "NullableReturnedFromNonnull"); ExplodedNode *N = C.addTransition(State, C.getPredecessor(), &Tag); reportBugIfPreconditionHolds(ErrorKind::NullableReturnedToNonnull, N, Region, C); } return; } if (StaticNullability == Nullability::Nullable) { State = State->set<NullabilityMap>(Region, NullabilityState(StaticNullability, S)); C.addTransition(State); } }
void PthreadLockChecker::AcquireLock(CheckerContext &C, const CallExpr *CE, SVal lock, bool isTryLock, enum LockingSemantics semantics) const { const MemRegion *lockR = lock.getAsRegion(); if (!lockR) return; ProgramStateRef state = C.getState(); SVal X = state->getSVal(CE, C.getLocationContext()); if (X.isUnknownOrUndef()) return; DefinedSVal retVal = X.castAs<DefinedSVal>(); if (const LockState *LState = state->get<LockMap>(lockR)) { if (LState->isLocked()) { if (!BT_doublelock) BT_doublelock.reset(new BugType(this, "Double locking", "Lock checker")); ExplodedNode *N = C.generateErrorNode(); if (!N) return; auto report = llvm::make_unique<BugReport>( *BT_doublelock, "This lock has already been acquired", N); report->addRange(CE->getArg(0)->getSourceRange()); C.emitReport(std::move(report)); return; } else if (LState->isDestroyed()) { reportUseDestroyedBug(C, CE); return; } } ProgramStateRef lockSucc = state; if (isTryLock) { // Bifurcate the state, and allow a mode where the lock acquisition fails. ProgramStateRef lockFail; switch (semantics) { case PthreadSemantics: std::tie(lockFail, lockSucc) = state->assume(retVal); break; case XNUSemantics: std::tie(lockSucc, lockFail) = state->assume(retVal); break; default: llvm_unreachable("Unknown tryLock locking semantics"); } assert(lockFail && lockSucc); C.addTransition(lockFail); } else if (semantics == PthreadSemantics) { // Assume that the return value was 0. lockSucc = state->assume(retVal, false); assert(lockSucc); } else { // XNU locking semantics return void on non-try locks assert((semantics == XNUSemantics) && "Unknown locking semantics"); lockSucc = state; } // Record that the lock was acquired. lockSucc = lockSucc->add<LockSet>(lockR); lockSucc = lockSucc->set<LockMap>(lockR, LockState::getLocked()); C.addTransition(lockSucc); }
/// Propagate the nullability information through binds and warn when nullable /// pointer or null symbol is assigned to a pointer with a nonnull type. void NullabilityChecker::checkBind(SVal L, SVal V, const Stmt *S, CheckerContext &C) const { const TypedValueRegion *TVR = dyn_cast_or_null<TypedValueRegion>(L.getAsRegion()); if (!TVR) return; QualType LocType = TVR->getValueType(); if (!LocType->isAnyPointerType()) return; ProgramStateRef State = C.getState(); if (State->get<InvariantViolated>()) return; auto ValDefOrUnknown = V.getAs<DefinedOrUnknownSVal>(); if (!ValDefOrUnknown) return; NullConstraint RhsNullness = getNullConstraint(*ValDefOrUnknown, State); Nullability ValNullability = Nullability::Unspecified; if (SymbolRef Sym = ValDefOrUnknown->getAsSymbol()) ValNullability = getNullabilityAnnotation(Sym->getType()); Nullability LocNullability = getNullabilityAnnotation(LocType); // If the type of the RHS expression is nonnull, don't warn. This // enables explicit suppression with a cast to nonnull. Nullability ValueExprTypeLevelNullability = Nullability::Unspecified; const Expr *ValueExpr = matchValueExprForBind(S); if (ValueExpr) { ValueExprTypeLevelNullability = getNullabilityAnnotation(lookThroughImplicitCasts(ValueExpr)->getType()); } bool NullAssignedToNonNull = (LocNullability == Nullability::Nonnull && RhsNullness == NullConstraint::IsNull); if (Filter.CheckNullPassedToNonnull && NullAssignedToNonNull && ValNullability != Nullability::Nonnull && ValueExprTypeLevelNullability != Nullability::Nonnull && !isARCNilInitializedLocal(C, S)) { static CheckerProgramPointTag Tag(this, "NullPassedToNonnull"); ExplodedNode *N = C.generateErrorNode(State, &Tag); if (!N) return; const Stmt *ValueStmt = S; if (ValueExpr) ValueStmt = ValueExpr; SmallString<256> SBuf; llvm::raw_svector_ostream OS(SBuf); OS << (LocType->isObjCObjectPointerType() ? "nil" : "Null"); OS << " assigned to a pointer which is expected to have non-null value"; reportBugIfInvariantHolds(OS.str(), ErrorKind::NilAssignedToNonnull, N, nullptr, C, ValueStmt); return; } // If null was returned from a non-null function, mark the nullability // invariant as violated even if the diagnostic was suppressed. if (NullAssignedToNonNull) { State = State->set<InvariantViolated>(true); C.addTransition(State); return; } // Intentionally missing case: '0' is bound to a reference. It is handled by // the DereferenceChecker. const MemRegion *ValueRegion = getTrackRegion(*ValDefOrUnknown); if (!ValueRegion) return; const NullabilityState *TrackedNullability = State->get<NullabilityMap>(ValueRegion); if (TrackedNullability) { if (RhsNullness == NullConstraint::IsNotNull || TrackedNullability->getValue() != Nullability::Nullable) return; if (Filter.CheckNullablePassedToNonnull && LocNullability == Nullability::Nonnull) { static CheckerProgramPointTag Tag(this, "NullablePassedToNonnull"); ExplodedNode *N = C.addTransition(State, C.getPredecessor(), &Tag); reportBugIfInvariantHolds("Nullable pointer is assigned to a pointer " "which is expected to have non-null value", ErrorKind::NullableAssignedToNonnull, N, ValueRegion, C); } return; } const auto *BinOp = dyn_cast<BinaryOperator>(S); if (ValNullability == Nullability::Nullable) { // Trust the static information of the value more than the static // information on the location. const Stmt *NullabilitySource = BinOp ? BinOp->getRHS() : S; State = State->set<NullabilityMap>( ValueRegion, NullabilityState(ValNullability, NullabilitySource)); C.addTransition(State); return; } if (LocNullability == Nullability::Nullable) { const Stmt *NullabilitySource = BinOp ? BinOp->getLHS() : S; State = State->set<NullabilityMap>( ValueRegion, NullabilityState(LocNullability, NullabilitySource)); C.addTransition(State); } }
void CFNumberCreateChecker::checkPreStmt(const CallExpr *CE, CheckerContext &C) const { ProgramStateRef state = C.getState(); const FunctionDecl *FD = C.getCalleeDecl(CE); if (!FD) return; ASTContext &Ctx = C.getASTContext(); if (!II) II = &Ctx.Idents.get("CFNumberCreate"); if (FD->getIdentifier() != II || CE->getNumArgs() != 3) return; // Get the value of the "theType" argument. const LocationContext *LCtx = C.getLocationContext(); SVal TheTypeVal = state->getSVal(CE->getArg(1), LCtx); // FIXME: We really should allow ranges of valid theType values, and // bifurcate the state appropriately. Optional<nonloc::ConcreteInt> V = TheTypeVal.getAs<nonloc::ConcreteInt>(); if (!V) return; uint64_t NumberKind = V->getValue().getLimitedValue(); Optional<uint64_t> OptTargetSize = GetCFNumberSize(Ctx, NumberKind); // FIXME: In some cases we can emit an error. if (!OptTargetSize) return; uint64_t TargetSize = *OptTargetSize; // Look at the value of the integer being passed by reference. Essentially // we want to catch cases where the value passed in is not equal to the // size of the type being created. SVal TheValueExpr = state->getSVal(CE->getArg(2), LCtx); // FIXME: Eventually we should handle arbitrary locations. We can do this // by having an enhanced memory model that does low-level typing. Optional<loc::MemRegionVal> LV = TheValueExpr.getAs<loc::MemRegionVal>(); if (!LV) return; const TypedValueRegion* R = dyn_cast<TypedValueRegion>(LV->stripCasts()); if (!R) return; QualType T = Ctx.getCanonicalType(R->getValueType()); // FIXME: If the pointee isn't an integer type, should we flag a warning? // People can do weird stuff with pointers. if (!T->isIntegralOrEnumerationType()) return; uint64_t SourceSize = Ctx.getTypeSize(T); // CHECK: is SourceSize == TargetSize if (SourceSize == TargetSize) return; // Generate an error. Only generate a sink error node // if 'SourceSize < TargetSize'; otherwise generate a non-fatal error node. // // FIXME: We can actually create an abstract "CFNumber" object that has // the bits initialized to the provided values. // ExplodedNode *N = SourceSize < TargetSize ? C.generateErrorNode() : C.generateNonFatalErrorNode(); if (N) { SmallString<128> sbuf; llvm::raw_svector_ostream os(sbuf); os << (SourceSize == 8 ? "An " : "A ") << SourceSize << " bit integer is used to initialize a CFNumber " "object that represents " << (TargetSize == 8 ? "an " : "a ") << TargetSize << " bit integer. "; if (SourceSize < TargetSize) os << (TargetSize - SourceSize) << " bits of the CFNumber value will be garbage." ; else os << (SourceSize - TargetSize) << " bits of the input integer will be lost."; if (!BT) BT.reset(new APIMisuse(this, "Bad use of CFNumberCreate")); auto report = llvm::make_unique<BugReport>(*BT, os.str(), N); report->addRange(CE->getArg(2)->getSourceRange()); C.emitReport(std::move(report)); } }
void CFRetainReleaseChecker::checkPreStmt(const CallExpr *CE, CheckerContext &C) const { // If the CallExpr doesn't have exactly 1 argument just give up checking. if (CE->getNumArgs() != 1) return; ProgramStateRef state = C.getState(); const FunctionDecl *FD = C.getCalleeDecl(CE); if (!FD) return; if (!BT) { ASTContext &Ctx = C.getASTContext(); Retain = &Ctx.Idents.get("CFRetain"); Release = &Ctx.Idents.get("CFRelease"); MakeCollectable = &Ctx.Idents.get("CFMakeCollectable"); Autorelease = &Ctx.Idents.get("CFAutorelease"); BT.reset(new APIMisuse( this, "null passed to CF memory management function")); } // Check if we called CFRetain/CFRelease/CFMakeCollectable/CFAutorelease. const IdentifierInfo *FuncII = FD->getIdentifier(); if (!(FuncII == Retain || FuncII == Release || FuncII == MakeCollectable || FuncII == Autorelease)) return; // FIXME: The rest of this just checks that the argument is non-null. // It should probably be refactored and combined with NonNullParamChecker. // Get the argument's value. const Expr *Arg = CE->getArg(0); SVal ArgVal = state->getSVal(Arg, C.getLocationContext()); Optional<DefinedSVal> DefArgVal = ArgVal.getAs<DefinedSVal>(); if (!DefArgVal) return; // Get a NULL value. SValBuilder &svalBuilder = C.getSValBuilder(); DefinedSVal zero = svalBuilder.makeZeroVal(Arg->getType()).castAs<DefinedSVal>(); // Make an expression asserting that they're equal. DefinedOrUnknownSVal ArgIsNull = svalBuilder.evalEQ(state, zero, *DefArgVal); // Are they equal? ProgramStateRef stateTrue, stateFalse; std::tie(stateTrue, stateFalse) = state->assume(ArgIsNull); if (stateTrue && !stateFalse) { ExplodedNode *N = C.generateErrorNode(stateTrue); if (!N) return; const char *description; if (FuncII == Retain) description = "Null pointer argument in call to CFRetain"; else if (FuncII == Release) description = "Null pointer argument in call to CFRelease"; else if (FuncII == MakeCollectable) description = "Null pointer argument in call to CFMakeCollectable"; else if (FuncII == Autorelease) description = "Null pointer argument in call to CFAutorelease"; else llvm_unreachable("impossible case"); auto report = llvm::make_unique<BugReport>(*BT, description, N); report->addRange(Arg->getSourceRange()); bugreporter::trackNullOrUndefValue(N, Arg, *report); C.emitReport(std::move(report)); return; } // From here on, we know the argument is non-null. C.addTransition(stateFalse); }
/// This method check when nullable pointer or null value is returned from a /// function that has nonnull return type. void NullabilityChecker::checkPreStmt(const ReturnStmt *S, CheckerContext &C) const { auto RetExpr = S->getRetValue(); if (!RetExpr) return; if (!RetExpr->getType()->isAnyPointerType()) return; ProgramStateRef State = C.getState(); if (State->get<InvariantViolated>()) return; auto RetSVal = C.getSVal(S).getAs<DefinedOrUnknownSVal>(); if (!RetSVal) return; bool InSuppressedMethodFamily = false; QualType RequiredRetType; AnalysisDeclContext *DeclCtxt = C.getLocationContext()->getAnalysisDeclContext(); const Decl *D = DeclCtxt->getDecl(); if (auto *MD = dyn_cast<ObjCMethodDecl>(D)) { // HACK: This is a big hammer to avoid warning when there are defensive // nil checks in -init and -copy methods. We should add more sophisticated // logic here to suppress on common defensive idioms but still // warn when there is a likely problem. ObjCMethodFamily Family = MD->getMethodFamily(); if (OMF_init == Family || OMF_copy == Family || OMF_mutableCopy == Family) InSuppressedMethodFamily = true; RequiredRetType = MD->getReturnType(); } else if (auto *FD = dyn_cast<FunctionDecl>(D)) { RequiredRetType = FD->getReturnType(); } else { return; } NullConstraint Nullness = getNullConstraint(*RetSVal, State); Nullability RequiredNullability = getNullabilityAnnotation(RequiredRetType); // If the returned value is null but the type of the expression // generating it is nonnull then we will suppress the diagnostic. // This enables explicit suppression when returning a nil literal in a // function with a _Nonnull return type: // return (NSString * _Nonnull)0; Nullability RetExprTypeLevelNullability = getNullabilityAnnotation(lookThroughImplicitCasts(RetExpr)->getType()); bool NullReturnedFromNonNull = (RequiredNullability == Nullability::Nonnull && Nullness == NullConstraint::IsNull); if (Filter.CheckNullReturnedFromNonnull && NullReturnedFromNonNull && RetExprTypeLevelNullability != Nullability::Nonnull && !InSuppressedMethodFamily && C.getLocationContext()->inTopFrame()) { static CheckerProgramPointTag Tag(this, "NullReturnedFromNonnull"); ExplodedNode *N = C.generateErrorNode(State, &Tag); if (!N) return; SmallString<256> SBuf; llvm::raw_svector_ostream OS(SBuf); OS << (RetExpr->getType()->isObjCObjectPointerType() ? "nil" : "Null"); OS << " returned from a " << C.getDeclDescription(D) << " that is expected to return a non-null value"; reportBugIfInvariantHolds(OS.str(), ErrorKind::NilReturnedToNonnull, N, nullptr, C, RetExpr); return; } // If null was returned from a non-null function, mark the nullability // invariant as violated even if the diagnostic was suppressed. if (NullReturnedFromNonNull) { State = State->set<InvariantViolated>(true); C.addTransition(State); return; } const MemRegion *Region = getTrackRegion(*RetSVal); if (!Region) return; const NullabilityState *TrackedNullability = State->get<NullabilityMap>(Region); if (TrackedNullability) { Nullability TrackedNullabValue = TrackedNullability->getValue(); if (Filter.CheckNullableReturnedFromNonnull && Nullness != NullConstraint::IsNotNull && TrackedNullabValue == Nullability::Nullable && RequiredNullability == Nullability::Nonnull) { static CheckerProgramPointTag Tag(this, "NullableReturnedFromNonnull"); ExplodedNode *N = C.addTransition(State, C.getPredecessor(), &Tag); SmallString<256> SBuf; llvm::raw_svector_ostream OS(SBuf); OS << "Nullable pointer is returned from a " << C.getDeclDescription(D) << " that is expected to return a non-null value"; reportBugIfInvariantHolds(OS.str(), ErrorKind::NullableReturnedToNonnull, N, Region, C); } return; } if (RequiredNullability == Nullability::Nullable) { State = State->set<NullabilityMap>(Region, NullabilityState(RequiredNullability, S)); C.addTransition(State); } }