diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h @@ -258,6 +258,13 @@ /// calls. bool isCalled(const CallDescription &CD) const; + /// Returns true whether the CallEvent is any of the CallDescriptions supplied + /// as a parameter. + template + bool isCalled(const FirstCallDesc &First, const CallDescs &... Rest) const { + return isCalled(First) || isCalled(Rest...); + } + /// Returns a source range for the entire call, suitable for /// outputting in diagnostics. virtual SourceRange getSourceRange() const { diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -268,8 +268,6 @@ namespace { -enum class MemoryOperationKind { MOK_Allocate, MOK_Free, MOK_Any }; - struct MemFunctionInfoTy { /// The value of the MallocChecker:Optimistic is stored in this variable. /// @@ -279,44 +277,41 @@ /// which might free a pointer are annotated. DefaultBool ShouldIncludeOwnershipAnnotatedFunctions; - // TODO: Change these to CallDescription, and get rid of lazy initialization. - mutable IdentifierInfo *II_alloca = nullptr, *II_win_alloca = nullptr, - *II_malloc = nullptr, *II_free = nullptr, - *II_realloc = nullptr, *II_calloc = nullptr, - *II_valloc = nullptr, *II_reallocf = nullptr, - *II_strndup = nullptr, *II_strdup = nullptr, - *II_win_strdup = nullptr, *II_kmalloc = nullptr, - *II_if_nameindex = nullptr, - *II_if_freenameindex = nullptr, *II_wcsdup = nullptr, - *II_win_wcsdup = nullptr, *II_g_malloc = nullptr, - *II_g_malloc0 = nullptr, *II_g_realloc = nullptr, - *II_g_try_malloc = nullptr, - *II_g_try_malloc0 = nullptr, - *II_g_try_realloc = nullptr, *II_g_free = nullptr, - *II_g_memdup = nullptr, *II_g_malloc_n = nullptr, - *II_g_malloc0_n = nullptr, *II_g_realloc_n = nullptr, - *II_g_try_malloc_n = nullptr, - *II_g_try_malloc0_n = nullptr, *II_kfree = nullptr, - *II_g_try_realloc_n = nullptr; - - void initIdentifierInfo(ASTContext &C) const; - - ///@{ - /// Check if this is one of the functions which can allocate/reallocate - /// memory pointed to by one of its arguments. - bool isMemFunction(const FunctionDecl *FD, ASTContext &C) const; - bool isCMemFunction(const FunctionDecl *FD, ASTContext &C, - AllocationFamily Family, - MemoryOperationKind MemKind) const; - - /// Tells if the callee is one of the builtin new/delete operators, including - /// placement operators and other standard overloads. - bool isStandardNewDelete(const FunctionDecl *FD, ASTContext &C) const; - ///@} + CallDescription CD_alloca{{"alloca"}, 1}, CD_win_alloca{{"_alloca"}, 1}, + CD_malloc{{"malloc"}, 1}, CD_BSD_malloc{{"malloc"}, 3}, + CD_free{{"free"}, 1}, CD_realloc{{"realloc"}, 2}, + CD_calloc{{"calloc"}, 2}, CD_valloc{{"valloc"}, 1}, + CD_reallocf{{"reallocf"}, 2}, CD_strndup{{"strndup"}, 2}, + CD_strdup{{"strdup"}, 1}, CD_win_strdup{{"_strdup"}, 1}, + CD_kmalloc{{"kmalloc"}, 2}, CD_if_nameindex{{"if_nameindex"}, 1}, + CD_if_freenameindex{{"if_freenameindex"}, 1}, CD_wcsdup{{"wcsdup"}, 1}, + CD_win_wcsdup{{"_wcsdup"}, 1}, CD_kfree{{"kfree"}, 2}, + CD_g_malloc{{"g_malloc"}, 1}, CD_g_malloc0{{"g_malloc0"}, 1}, + CD_g_realloc{{"g_realloc"}, 2}, CD_g_try_malloc{{"g_try_malloc"}, 1}, + CD_g_try_malloc0{{"g_try_malloc0"}, 1}, + CD_g_try_realloc{{"g_try_realloc"}, 2}, CD_g_free{{"g_free"}, 1}, + CD_g_memdup{{"g_memdup"}, 2}, CD_g_malloc_n{{"g_malloc_n"}, 2}, + CD_g_malloc0_n{{"g_malloc0_n"}, 2}, CD_g_realloc_n{{"g_realloc_n"}, 3}, + CD_g_try_malloc_n{{"g_try_malloc_n"}, 2}, + CD_g_try_malloc0_n{{"g_try_malloc0_n"}, 2}, + CD_g_try_realloc_n{{"g_try_realloc_n"}, 3}; + + bool isMemFunction(const CallEvent &Call) const; + bool isCMemFunction(const CallEvent &Call) const; + bool isCMemFreeFunction(const CallEvent &Call) const; + bool isCMemAllocFunction(const CallEvent &Call) const; }; - } // end of anonymous namespace +/// Tells if the callee is one of the builtin new/delete operators, including +/// placement operators and other standard overloads. +static bool isStandardNewDelete(const FunctionDecl *FD); +static bool isStandardNewDelete(const CallEvent &Call) { + if (!Call.getDecl()) + return false; + return isStandardNewDelete(cast(Call.getDecl())); +} + //===----------------------------------------------------------------------===// // Definition of the MallocChecker class. //===----------------------------------------------------------------------===// @@ -326,11 +321,10 @@ class MallocChecker : public Checker, - check::EndFunction, check::PreCall, - check::PostStmt, check::PostStmt, - check::NewAllocator, check::PreStmt, - check::PostStmt, check::PostObjCMessage, - check::Location, eval::Assume> { + check::EndFunction, check::PreCall, check::PostCall, + check::PostStmt, check::NewAllocator, + check::PreStmt, check::PostStmt, + check::PostObjCMessage, check::Location, eval::Assume> { public: MemFunctionInfoTy MemFunctionInfo; @@ -354,7 +348,7 @@ CheckerNameRef CheckNames[CK_NumCheckKinds]; void checkPreCall(const CallEvent &Call, CheckerContext &C) const; - void checkPostStmt(const CallExpr *CE, CheckerContext &C) const; + void checkPostCall(const CallEvent &Call, CheckerContext &C) const; void checkPostStmt(const CXXNewExpr *NE, CheckerContext &C) const; void checkNewAllocator(const CXXNewExpr *NE, SVal Target, CheckerContext &C) const; @@ -804,6 +798,7 @@ namespace { class StopTrackingCallback final : public SymbolVisitor { ProgramStateRef state; + public: StopTrackingCallback(ProgramStateRef st) : state(std::move(st)) {} ProgramStateRef getState() const { return state; } @@ -819,148 +814,81 @@ // Methods of MemFunctionInfoTy. //===----------------------------------------------------------------------===// -void MemFunctionInfoTy::initIdentifierInfo(ASTContext &Ctx) const { - if (II_malloc) - return; - II_alloca = &Ctx.Idents.get("alloca"); - II_malloc = &Ctx.Idents.get("malloc"); - II_free = &Ctx.Idents.get("free"); - II_realloc = &Ctx.Idents.get("realloc"); - II_reallocf = &Ctx.Idents.get("reallocf"); - II_calloc = &Ctx.Idents.get("calloc"); - II_valloc = &Ctx.Idents.get("valloc"); - II_strdup = &Ctx.Idents.get("strdup"); - II_strndup = &Ctx.Idents.get("strndup"); - II_wcsdup = &Ctx.Idents.get("wcsdup"); - II_kmalloc = &Ctx.Idents.get("kmalloc"); - II_kfree = &Ctx.Idents.get("kfree"); - II_if_nameindex = &Ctx.Idents.get("if_nameindex"); - II_if_freenameindex = &Ctx.Idents.get("if_freenameindex"); - - //MSVC uses `_`-prefixed instead, so we check for them too. - II_win_strdup = &Ctx.Idents.get("_strdup"); - II_win_wcsdup = &Ctx.Idents.get("_wcsdup"); - II_win_alloca = &Ctx.Idents.get("_alloca"); - - // Glib - II_g_malloc = &Ctx.Idents.get("g_malloc"); - II_g_malloc0 = &Ctx.Idents.get("g_malloc0"); - II_g_realloc = &Ctx.Idents.get("g_realloc"); - II_g_try_malloc = &Ctx.Idents.get("g_try_malloc"); - II_g_try_malloc0 = &Ctx.Idents.get("g_try_malloc0"); - II_g_try_realloc = &Ctx.Idents.get("g_try_realloc"); - II_g_free = &Ctx.Idents.get("g_free"); - II_g_memdup = &Ctx.Idents.get("g_memdup"); - II_g_malloc_n = &Ctx.Idents.get("g_malloc_n"); - II_g_malloc0_n = &Ctx.Idents.get("g_malloc0_n"); - II_g_realloc_n = &Ctx.Idents.get("g_realloc_n"); - II_g_try_malloc_n = &Ctx.Idents.get("g_try_malloc_n"); - II_g_try_malloc0_n = &Ctx.Idents.get("g_try_malloc0_n"); - II_g_try_realloc_n = &Ctx.Idents.get("g_try_realloc_n"); -} - -bool MemFunctionInfoTy::isMemFunction(const FunctionDecl *FD, - ASTContext &C) const { - if (isCMemFunction(FD, C, AF_Malloc, MemoryOperationKind::MOK_Any)) - return true; +bool MemFunctionInfoTy::isMemFunction(const CallEvent &Call) const { + return isCMemFunction(Call) || isStandardNewDelete(Call); +} - if (isCMemFunction(FD, C, AF_IfNameIndex, MemoryOperationKind::MOK_Any)) - return true; +bool MemFunctionInfoTy::isCMemFunction(const CallEvent &Call) const { + return isCMemFreeFunction(Call) || isCMemAllocFunction(Call); +} - if (isCMemFunction(FD, C, AF_Alloca, MemoryOperationKind::MOK_Any)) +bool MemFunctionInfoTy::isCMemFreeFunction(const CallEvent &Call) const { + if (Call.isCalled(CD_free, CD_realloc, CD_reallocf, CD_g_free, CD_kfree)) return true; - if (isStandardNewDelete(FD, C)) + if (Call.isCalled(CD_if_freenameindex)) return true; - return false; -} - -bool MemFunctionInfoTy::isCMemFunction(const FunctionDecl *FD, ASTContext &C, - AllocationFamily Family, - MemoryOperationKind MemKind) const { - if (!FD) + if (!ShouldIncludeOwnershipAnnotatedFunctions) return false; - bool CheckFree = (MemKind == MemoryOperationKind::MOK_Any || - MemKind == MemoryOperationKind::MOK_Free); - bool CheckAlloc = (MemKind == MemoryOperationKind::MOK_Any || - MemKind == MemoryOperationKind::MOK_Allocate); - - if (FD->getKind() == Decl::Function) { - const IdentifierInfo *FunI = FD->getIdentifier(); - initIdentifierInfo(C); - - if (Family == AF_Malloc && CheckFree) { - if (FunI == II_free || FunI == II_realloc || FunI == II_reallocf || - FunI == II_g_free || FunI == II_kfree) - return true; - } - - if (Family == AF_Malloc && CheckAlloc) { - if (FunI == II_malloc || FunI == II_realloc || FunI == II_reallocf || - FunI == II_calloc || FunI == II_valloc || FunI == II_strdup || - FunI == II_win_strdup || FunI == II_strndup || FunI == II_wcsdup || - FunI == II_win_wcsdup || FunI == II_kmalloc || - FunI == II_g_malloc || FunI == II_g_malloc0 || - FunI == II_g_realloc || FunI == II_g_try_malloc || - FunI == II_g_try_malloc0 || FunI == II_g_try_realloc || - FunI == II_g_memdup || FunI == II_g_malloc_n || - FunI == II_g_malloc0_n || FunI == II_g_realloc_n || - FunI == II_g_try_malloc_n || FunI == II_g_try_malloc0_n || - FunI == II_g_try_realloc_n) + const auto *Func = dyn_cast(Call.getDecl()); + if (Func && Func->hasAttrs()) { + for (const auto *I : Func->specific_attrs()) { + OwnershipAttr::OwnershipKind OwnKind = I->getOwnKind(); + if (OwnKind == OwnershipAttr::Takes || OwnKind == OwnershipAttr::Holds) return true; } + } + return false; +} - if (Family == AF_IfNameIndex && CheckFree) { - if (FunI == II_if_freenameindex) - return true; - } +bool MemFunctionInfoTy::isCMemAllocFunction(const CallEvent &Call) const { + if (Call.isCalled(CD_malloc, CD_realloc, CD_reallocf, CD_calloc, CD_valloc, + CD_strdup, CD_win_strdup, CD_strndup, CD_wcsdup, + CD_win_wcsdup, CD_kmalloc, CD_g_malloc, CD_g_malloc0, + CD_g_realloc, CD_g_try_malloc, CD_g_try_malloc0, + CD_g_try_realloc, CD_g_memdup, CD_g_malloc_n, + CD_g_malloc0_n, CD_g_realloc_n, CD_g_try_malloc_n, + CD_g_try_malloc0_n, CD_g_try_realloc_n)) + return true; - if (Family == AF_IfNameIndex && CheckAlloc) { - if (FunI == II_if_nameindex) - return true; - } + if (Call.isCalled(CD_if_nameindex)) + return true; - if (Family == AF_Alloca && CheckAlloc) { - if (FunI == II_alloca || FunI == II_win_alloca) - return true; - } - } + if (Call.isCalled(CD_alloca, CD_win_alloca)) + return true; - if (Family != AF_Malloc) + if (!ShouldIncludeOwnershipAnnotatedFunctions) return false; - if (ShouldIncludeOwnershipAnnotatedFunctions && FD->hasAttrs()) { - for (const auto *I : FD->specific_attrs()) { + const auto *Func = dyn_cast(Call.getDecl()); + if (Func && Func->hasAttrs()) { + for (const auto *I : Func->specific_attrs()) { OwnershipAttr::OwnershipKind OwnKind = I->getOwnKind(); - if(OwnKind == OwnershipAttr::Takes || OwnKind == OwnershipAttr::Holds) { - if (CheckFree) - return true; - } else if (OwnKind == OwnershipAttr::Returns) { - if (CheckAlloc) - return true; - } + if (OwnKind == OwnershipAttr::Returns) + return true; } } return false; } -bool MemFunctionInfoTy::isStandardNewDelete(const FunctionDecl *FD, - ASTContext &C) const { + +static bool isStandardNewDelete(const FunctionDecl *FD) { if (!FD) return false; OverloadedOperatorKind Kind = FD->getOverloadedOperator(); - if (Kind != OO_New && Kind != OO_Array_New && - Kind != OO_Delete && Kind != OO_Array_Delete) + if (Kind != OO_New && Kind != OO_Array_New && Kind != OO_Delete && + Kind != OO_Array_Delete) return false; // This is standard if and only if it's not defined in a user file. SourceLocation L = FD->getLocation(); // If the header for operator delete is not included, it's still defined // in an invalid source location. Check to make sure we don't crash. - return !L.isValid() || C.getSourceManager().isInSystemHeader(L); + return !L.isValid() || + FD->getASTContext().getSourceManager().isInSystemHeader(L); } //===----------------------------------------------------------------------===// @@ -1056,10 +984,15 @@ return TotalSize; } -void MallocChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const { +void MallocChecker::checkPostCall(const CallEvent &Call, + CheckerContext &C) const { if (C.wasInlined) return; + const auto *CE = dyn_cast_or_null(Call.getOriginExpr()); + if (!CE) + return; + const FunctionDecl *FD = C.getCalleeDecl(CE); if (!FD) return; @@ -1068,12 +1001,9 @@ bool IsKnownToBeAllocatedMemory = false; if (FD->getKind() == Decl::Function) { - MemFunctionInfo.initIdentifierInfo(C.getASTContext()); - IdentifierInfo *FunI = FD->getIdentifier(); - - if (FunI == MemFunctionInfo.II_malloc || - FunI == MemFunctionInfo.II_g_malloc || - FunI == MemFunctionInfo.II_g_try_malloc) { + if (Call.isCalled(MemFunctionInfo.CD_malloc, MemFunctionInfo.CD_BSD_malloc, + MemFunctionInfo.CD_g_malloc, + MemFunctionInfo.CD_g_try_malloc)) { switch (CE->getNumArgs()) { default: return; @@ -1083,8 +1013,7 @@ State = ProcessZeroAllocCheck(C, CE, 0, State); break; case 2: - State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State, - AF_Malloc); + llvm_unreachable("There shouldn't be a 2-argument malloc!"); break; case 3: llvm::Optional MaybeState = @@ -1096,58 +1025,56 @@ AF_Malloc); break; } - } else if (FunI == MemFunctionInfo.II_kmalloc) { + } else if (Call.isCalled(MemFunctionInfo.CD_kmalloc)) { if (CE->getNumArgs() < 1) return; llvm::Optional MaybeState = - performKernelMalloc(CE, C, State); + performKernelMalloc(CE, C, State); if (MaybeState.hasValue()) State = MaybeState.getValue(); else State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State, AF_Malloc); - } else if (FunI == MemFunctionInfo.II_valloc) { + } else if (Call.isCalled(MemFunctionInfo.CD_valloc)) { if (CE->getNumArgs() < 1) return; State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State, AF_Malloc); State = ProcessZeroAllocCheck(C, CE, 0, State); - } else if (FunI == MemFunctionInfo.II_realloc || - FunI == MemFunctionInfo.II_g_realloc || - FunI == MemFunctionInfo.II_g_try_realloc) { + } else if (Call.isCalled(MemFunctionInfo.CD_realloc, + MemFunctionInfo.CD_g_realloc, + MemFunctionInfo.CD_g_try_realloc)) { State = ReallocMemAux(C, CE, /*ShouldFreeOnFail*/ false, State, AF_Malloc); State = ProcessZeroAllocCheck(C, CE, 1, State); - } else if (FunI == MemFunctionInfo.II_reallocf) { + } else if (Call.isCalled(MemFunctionInfo.CD_reallocf)) { State = ReallocMemAux(C, CE, /*ShouldFreeOnFail*/ true, State, AF_Malloc); State = ProcessZeroAllocCheck(C, CE, 1, State); - } else if (FunI == MemFunctionInfo.II_calloc) { + } else if (Call.isCalled(MemFunctionInfo.CD_calloc)) { State = CallocMem(C, CE, State); State = ProcessZeroAllocCheck(C, CE, 0, State); State = ProcessZeroAllocCheck(C, CE, 1, State); - } else if (FunI == MemFunctionInfo.II_free || - FunI == MemFunctionInfo.II_g_free || - FunI == MemFunctionInfo.II_kfree) { + } else if (Call.isCalled(MemFunctionInfo.CD_free, MemFunctionInfo.CD_g_free, + MemFunctionInfo.CD_kfree)) { if (suppressDeallocationsInSuspiciousContexts(CE, C)) return; State = FreeMemAux(C, CE, State, 0, false, IsKnownToBeAllocatedMemory, AF_Malloc); - } else if (FunI == MemFunctionInfo.II_strdup || - FunI == MemFunctionInfo.II_win_strdup || - FunI == MemFunctionInfo.II_wcsdup || - FunI == MemFunctionInfo.II_win_wcsdup) { + } else if (Call.isCalled( + MemFunctionInfo.CD_strdup, MemFunctionInfo.CD_win_strdup, + MemFunctionInfo.CD_wcsdup, MemFunctionInfo.CD_win_wcsdup)) { State = MallocUpdateRefState(C, CE, State, AF_Malloc); - } else if (FunI == MemFunctionInfo.II_strndup) { + } else if (Call.isCalled(MemFunctionInfo.CD_strndup)) { State = MallocUpdateRefState(C, CE, State, AF_Malloc); - } else if (FunI == MemFunctionInfo.II_alloca || - FunI == MemFunctionInfo.II_win_alloca) { + } else if (Call.isCalled(MemFunctionInfo.CD_alloca, + MemFunctionInfo.CD_win_alloca)) { if (CE->getNumArgs() < 1) return; State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State, AF_Alloca); State = ProcessZeroAllocCheck(C, CE, 0, State); - } else if (MemFunctionInfo.isStandardNewDelete(FD, C.getASTContext())) { + } else if (isStandardNewDelete(FD)) { // Process direct calls to operator new/new[]/delete/delete[] functions // as distinct from new/new[]/delete/delete[] expressions that are // processed by the checkPostStmt callbacks for CXXNewExpr and @@ -1174,37 +1101,37 @@ default: llvm_unreachable("not a new/delete operator"); } - } else if (FunI == MemFunctionInfo.II_if_nameindex) { + } else if (Call.isCalled(MemFunctionInfo.CD_if_nameindex)) { // Should we model this differently? We can allocate a fixed number of // elements with zeros in the last one. State = MallocMemAux(C, CE, UnknownVal(), UnknownVal(), State, AF_IfNameIndex); - } else if (FunI == MemFunctionInfo.II_if_freenameindex) { + } else if (Call.isCalled(MemFunctionInfo.CD_if_freenameindex)) { State = FreeMemAux(C, CE, State, 0, false, IsKnownToBeAllocatedMemory, AF_IfNameIndex); - } else if (FunI == MemFunctionInfo.II_g_malloc0 || - FunI == MemFunctionInfo.II_g_try_malloc0) { + } else if (Call.isCalled(MemFunctionInfo.CD_g_malloc0, + MemFunctionInfo.CD_g_try_malloc0)) { if (CE->getNumArgs() < 1) return; SValBuilder &svalBuilder = C.getSValBuilder(); SVal zeroVal = svalBuilder.makeZeroVal(svalBuilder.getContext().CharTy); State = MallocMemAux(C, CE, CE->getArg(0), zeroVal, State, AF_Malloc); State = ProcessZeroAllocCheck(C, CE, 0, State); - } else if (FunI == MemFunctionInfo.II_g_memdup) { + } else if (Call.isCalled(MemFunctionInfo.CD_g_memdup)) { if (CE->getNumArgs() < 2) return; State = MallocMemAux(C, CE, CE->getArg(1), UndefinedVal(), State, AF_Malloc); State = ProcessZeroAllocCheck(C, CE, 1, State); - } else if (FunI == MemFunctionInfo.II_g_malloc_n || - FunI == MemFunctionInfo.II_g_try_malloc_n || - FunI == MemFunctionInfo.II_g_malloc0_n || - FunI == MemFunctionInfo.II_g_try_malloc0_n) { + } else if (Call.isCalled(MemFunctionInfo.CD_g_malloc_n, + MemFunctionInfo.CD_g_try_malloc_n, + MemFunctionInfo.CD_g_malloc0_n, + MemFunctionInfo.CD_g_try_malloc0_n)) { if (CE->getNumArgs() < 2) return; SVal Init = UndefinedVal(); - if (FunI == MemFunctionInfo.II_g_malloc0_n || - FunI == MemFunctionInfo.II_g_try_malloc0_n) { + if (Call.isCalled(MemFunctionInfo.CD_g_malloc0_n, + MemFunctionInfo.CD_g_try_malloc0_n)) { SValBuilder &SB = C.getSValBuilder(); Init = SB.makeZeroVal(SB.getContext().CharTy); } @@ -1212,8 +1139,8 @@ State = MallocMemAux(C, CE, TotalSize, Init, State, AF_Malloc); State = ProcessZeroAllocCheck(C, CE, 0, State); State = ProcessZeroAllocCheck(C, CE, 1, State); - } else if (FunI == MemFunctionInfo.II_g_realloc_n || - FunI == MemFunctionInfo.II_g_try_realloc_n) { + } else if (Call.isCalled(MemFunctionInfo.CD_g_realloc_n, + MemFunctionInfo.CD_g_try_realloc_n)) { if (CE->getNumArgs() < 3) return; State = ReallocMemAux(C, CE, /*ShouldFreeOnFail*/ false, State, AF_Malloc, @@ -1350,8 +1277,7 @@ void MallocChecker::processNewAllocation(const CXXNewExpr *NE, CheckerContext &C, SVal Target, AllocationFamily Family) const { - if (!MemFunctionInfo.isStandardNewDelete(NE->getOperatorNew(), - C.getASTContext())) + if (!isStandardNewDelete(NE->getOperatorNew())) return; const ParentMap &PM = C.getLocationContext()->getParentMap(); @@ -1444,8 +1370,7 @@ if (SymbolRef Sym = C.getSVal(DE->getArgument()).getAsSymbol()) checkUseAfterFree(Sym, C, DE->getArgument()); - if (!MemFunctionInfo.isStandardNewDelete(DE->getOperatorDelete(), - C.getASTContext())) + if (!isStandardNewDelete(DE->getOperatorDelete())) return; ProgramStateRef State = C.getState(); @@ -1511,7 +1436,8 @@ if (!State) return nullptr; - if (Att->getModule() != MemFunctionInfo.II_malloc) + if (Att->getModule()->getName() != + MemFunctionInfo.CD_malloc.getFunctionName()) return nullptr; OwnershipAttr::args_iterator I = Att->args_begin(), E = Att->args_end(); @@ -1607,7 +1533,8 @@ if (!State) return nullptr; - if (Att->getModule() != MemFunctionInfo.II_malloc) + if (Att->getModule()->getName() != + MemFunctionInfo.CD_malloc.getFunctionName()) return nullptr; bool IsKnownToBeAllocated = false; @@ -2652,12 +2579,8 @@ if (!FD) return; - ASTContext &Ctx = C.getASTContext(); if (ChecksEnabled[CK_MallocChecker] && - (MemFunctionInfo.isCMemFunction(FD, Ctx, AF_Malloc, - MemoryOperationKind::MOK_Free) || - MemFunctionInfo.isCMemFunction(FD, Ctx, AF_IfNameIndex, - MemoryOperationKind::MOK_Free))) + (MemFunctionInfo.isCMemFreeFunction(Call))) return; } @@ -2954,11 +2877,9 @@ if (!FD) return true; - ASTContext &ASTC = State->getStateManager().getContext(); - // If it's one of the allocation functions we can reason about, we model // its behavior explicitly. - if (MemFunctionInfo.isMemFunction(FD, ASTC)) + if (MemFunctionInfo.isMemFunction(*Call)) return false; // If it's not a system call, assume it frees memory. diff --git a/clang/test/Analysis/kmalloc-linux.c b/clang/test/Analysis/kmalloc-linux.c --- a/clang/test/Analysis/kmalloc-linux.c +++ b/clang/test/Analysis/kmalloc-linux.c @@ -1,10 +1,10 @@ // RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux %s -#include "Inputs/system-header-simulator.h" - #define __GFP_ZERO 0x8000 #define NULL ((void *)0) +typedef __typeof(sizeof(int)) size_t; + void *kmalloc(size_t, int); struct test { @@ -56,3 +56,54 @@ } kfree(list); } + +typedef unsigned long long uint64_t; + +struct malloc_type; + +void *malloc(unsigned long size, struct malloc_type *mtp, int flags); + +void test_3arg_malloc(struct malloc_type *mtp) { + struct test **list, *t; + int i; + + list = malloc(sizeof(*list) * 10, mtp, __GFP_ZERO); + if (list == NULL) + return; + + for (i = 0; i < 10; i++) { + t = list[i]; + foo(t); + } + kfree(list); // no-warning +} + +void test_3arg_malloc_nonzero(struct malloc_type *mtp) { + struct test **list, *t; + int i; + + list = malloc(sizeof(*list) * 10, mtp, 0); + if (list == NULL) + return; + + for (i = 0; i < 10; i++) { + t = list[i]; // expected-warning{{undefined}} + foo(t); + } + kfree(list); +} + +void test_3arg_malloc_indeterminate(struct malloc_type *mtp, int flags) { + struct test **list, *t; + int i; + + list = alloc(sizeof(*list) * 10, mtp, flags); + if (list == NULL) + return; + + for (i = 0; i < 10; i++) { + t = list[i]; // expected-warning{{undefined}} + foo(t); + } + kfree(list); +} diff --git a/clang/test/Analysis/malloc-annotations.cpp b/clang/test/Analysis/malloc-annotations.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/malloc-annotations.cpp @@ -0,0 +1,99 @@ +// RUN: %clang_analyze_cc1 -analyzer-store=region -verify \ +// RUN: -analyzer-checker=core \ +// RUN: -analyzer-checker=alpha.deadcode.UnreachableCode \ +// RUN: -analyzer-checker=alpha.core.CastSize \ +// RUN: -analyzer-checker=unix.Malloc \ +// RUN: -analyzer-config unix.DynamicMemoryModeling:Optimistic=true %s + +typedef __typeof(sizeof(int)) size_t; +void *malloc(size_t); +void free(void *); + +struct MemoryAllocator { + void __attribute((ownership_returns(malloc))) * my_malloc(size_t); + void __attribute((ownership_takes(malloc, 2))) my_free(void *); + void __attribute((ownership_holds(malloc, 2))) my_hold(void *); +}; + +void *myglobalpointer; + +struct stuff { + void *somefield; +}; + +struct stuff myglobalstuff; + +void af1(MemoryAllocator &Alloc) { + void *p = Alloc.my_malloc(12); + return; // expected-warning{{Potential leak of memory pointed to by}} +} + +void af1_b(MemoryAllocator &Alloc) { + void *p = Alloc.my_malloc(12); +} // expected-warning{{Potential leak of memory pointed to by}} + +void af1_c(MemoryAllocator &Alloc) { + myglobalpointer = Alloc.my_malloc(12); // no-warning +} + +// Test that we can pass out allocated memory via pointer-to-pointer. +void af1_e(MemoryAllocator &Alloc, void **pp) { + *pp = Alloc.my_malloc(42); // no-warning +} + +void af1_f(MemoryAllocator &Alloc, struct stuff *somestuff) { + somestuff->somefield = Alloc.my_malloc(12); // no-warning +} + +// Allocating memory for a field via multiple indirections to our arguments is OK. +void af1_g(MemoryAllocator &Alloc, struct stuff **pps) { + *pps = (struct stuff *)Alloc.my_malloc(sizeof(struct stuff)); // no-warning + (*pps)->somefield = Alloc.my_malloc(42); // no-warning +} + +void af2(MemoryAllocator &Alloc) { + void *p = Alloc.my_malloc(12); + Alloc.my_free(p); + free(p); // expected-warning{{Attempt to free released memory}} +} + +void af2b(MemoryAllocator &Alloc) { + void *p = Alloc.my_malloc(12); + free(p); + Alloc.my_free(p); // expected-warning{{Attempt to free released memory}} +} + +void af2c(MemoryAllocator &Alloc) { + void *p = Alloc.my_malloc(12); + free(p); + Alloc.my_hold(p); // expected-warning{{Attempt to free released memory}} +} + +// No leak if malloc returns null. +void af2e(MemoryAllocator &Alloc) { + void *p = Alloc.my_malloc(12); + if (!p) + return; // no-warning + free(p); // no-warning +} + +// This case inflicts a possible double-free. +void af3(MemoryAllocator &Alloc) { + void *p = Alloc.my_malloc(12); + Alloc.my_hold(p); + free(p); // expected-warning{{Attempt to free non-owned memory}} +} + +void * af4(MemoryAllocator &Alloc) { + void *p = Alloc.my_malloc(12); + Alloc.my_free(p); + return p; // expected-warning{{Use of memory after it is freed}} +} + +// This case is (possibly) ok, be conservative +void * af5(MemoryAllocator &Alloc) { + void *p = Alloc.my_malloc(12); + Alloc.my_hold(p); + return p; // no-warning +} +