https://github.com/NagyDonat updated https://github.com/llvm/llvm-project/pull/106081
From 82e3d871766b132d0ce0b9e8e74371d8598d2431 Mon Sep 17 00:00:00 2001 From: Pavel Skripkin <paskrip...@gmail.com> Date: Tue, 6 Aug 2024 19:12:01 +0300 Subject: [PATCH 1/4] wip --- .../Core/PathSensitive/DynamicExtent.h | 2 +- .../StaticAnalyzer/Checkers/MallocChecker.cpp | 355 +++++++++++------- .../Checkers/VLASizeChecker.cpp | 3 +- .../lib/StaticAnalyzer/Core/DynamicExtent.cpp | 2 +- .../Core/ExprEngineCallAndReturn.cpp | 3 +- .../test/Analysis/NewDelete-checker-test.cpp | 13 - .../test/Analysis/NewDelete-intersections.mm | 6 +- clang/test/Analysis/malloc-interprocedural.c | 35 -- 8 files changed, 233 insertions(+), 186 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h index 50d5d254415af3..1a9bef06b15a44 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h @@ -36,7 +36,7 @@ DefinedOrUnknownSVal getDynamicElementCount(ProgramStateRef State, /// Set the dynamic extent \p Extent of the region \p MR. ProgramStateRef setDynamicExtent(ProgramStateRef State, const MemRegion *MR, - DefinedOrUnknownSVal Extent, SValBuilder &SVB); + DefinedOrUnknownSVal Extent); /// Get the dynamic extent for a symbolic value that represents a buffer. If /// there is an offsetting to the underlying buffer we consider that too. diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index 3ddcb7e94ae5d6..1f524481049fa4 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -315,13 +315,24 @@ struct ReallocPair { REGISTER_MAP_WITH_PROGRAMSTATE(ReallocPairs, SymbolRef, ReallocPair) -/// 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) { +static bool isStandardNew(const FunctionDecl *FD); +static bool isStandardNew(const CallEvent &Call) { + if (!Call.getDecl() || !isa<FunctionDecl>(Call.getDecl())) + return false; + return isStandardNew(cast<FunctionDecl>(Call.getDecl())); +} + +static bool isStandardDelete(const FunctionDecl *FD); +static bool isStandardDelete(const CallEvent &Call) { if (!Call.getDecl() || !isa<FunctionDecl>(Call.getDecl())) return false; - return isStandardNewDelete(cast<FunctionDecl>(Call.getDecl())); + return isStandardDelete(cast<FunctionDecl>(Call.getDecl())); +} + +/// Tells if the callee is one of the builtin new/delete operators, including +/// placement operators and other standard overloads. +template <typename T> static bool isStandardNewDelete(const T &FD) { + return isStandardDelete(FD) || isStandardNew(FD); } //===----------------------------------------------------------------------===// @@ -334,8 +345,9 @@ class MallocChecker : public Checker<check::DeadSymbols, check::PointerEscape, check::ConstPointerEscape, check::PreStmt<ReturnStmt>, check::EndFunction, check::PreCall, check::PostCall, - check::NewAllocator, check::PostStmt<BlockExpr>, - check::PostObjCMessage, check::Location, eval::Assume> { + eval::Call, check::NewAllocator, + check::PostStmt<BlockExpr>, check::PostObjCMessage, + check::Location, eval::Assume> { public: /// In pessimistic mode, the checker assumes that it does not know which /// functions might free the memory. @@ -367,6 +379,7 @@ class MallocChecker void checkPreCall(const CallEvent &Call, CheckerContext &C) const; void checkPostCall(const CallEvent &Call, CheckerContext &C) const; + bool evalCall(const CallEvent &Call, CheckerContext &C) const; void checkNewAllocator(const CXXAllocatorCall &Call, CheckerContext &C) const; void checkPostObjCMessage(const ObjCMethodCall &Call, CheckerContext &C) const; void checkPostStmt(const BlockExpr *BE, CheckerContext &C) const; @@ -403,7 +416,8 @@ class MallocChecker mutable std::unique_ptr<BugType> BT_TaintedAlloc; #define CHECK_FN(NAME) \ - void NAME(const CallEvent &Call, CheckerContext &C) const; + void NAME(ProgramStateRef State, const CallEvent &Call, CheckerContext &C) \ + const; CHECK_FN(checkFree) CHECK_FN(checkIfNameIndex) @@ -423,11 +437,12 @@ class MallocChecker CHECK_FN(checkReallocN) CHECK_FN(checkOwnershipAttr) - void checkRealloc(const CallEvent &Call, CheckerContext &C, - bool ShouldFreeOnFail) const; + void checkRealloc(ProgramStateRef State, const CallEvent &Call, + CheckerContext &C, bool ShouldFreeOnFail) const; - using CheckFn = std::function<void(const MallocChecker *, - const CallEvent &Call, CheckerContext &C)>; + using CheckFn = + std::function<void(const MallocChecker *, ProgramStateRef State, + const CallEvent &Call, CheckerContext &C)>; const CallDescriptionMap<CheckFn> PreFnMap{ // NOTE: the following CallDescription also matches the C++ standard @@ -436,6 +451,13 @@ class MallocChecker {{CDM::CLibrary, {"getdelim"}, 4}, &MallocChecker::preGetdelim}, }; + const CallDescriptionMap<CheckFn> PostFnMap{ + // NOTE: the following CallDescription also matches the C++ standard + // library function std::getline(); the callback will filter it out. + {{CDM::CLibrary, {"getline"}, 3}, &MallocChecker::checkGetdelim}, + {{CDM::CLibrary, {"getdelim"}, 4}, &MallocChecker::checkGetdelim}, + }; + const CallDescriptionMap<CheckFn> FreeingMemFnMap{ {{CDM::CLibrary, {"free"}, 1}, &MallocChecker::checkFree}, {{CDM::CLibrary, {"if_freenameindex"}, 1}, @@ -446,10 +468,13 @@ class MallocChecker bool isFreeingCall(const CallEvent &Call) const; static bool isFreeingOwnershipAttrCall(const FunctionDecl *Func); + static bool isFreeingOwnershipAttrCall(const CallEvent &Call); + static bool isAllocatingOwnershipAttrCall(const FunctionDecl *Func); + static bool isAllocatingOwnershipAttrCall(const CallEvent &Call); friend class NoMemOwnershipChangeVisitor; - CallDescriptionMap<CheckFn> AllocatingMemFnMap{ + CallDescriptionMap<CheckFn> AllocaMemFnMap{ {{CDM::CLibrary, {"alloca"}, 1}, &MallocChecker::checkAlloca}, {{CDM::CLibrary, {"_alloca"}, 1}, &MallocChecker::checkAlloca}, // The line for "alloca" also covers "__builtin_alloca", but the @@ -457,6 +482,9 @@ class MallocChecker // extra argument: {{CDM::CLibrary, {"__builtin_alloca_with_align"}, 2}, &MallocChecker::checkAlloca}, + }; + + CallDescriptionMap<CheckFn> AllocatingMemFnMap{ {{CDM::CLibrary, {"malloc"}, 1}, &MallocChecker::checkBasicAlloc}, {{CDM::CLibrary, {"malloc"}, 3}, &MallocChecker::checkKernelMalloc}, {{CDM::CLibrary, {"calloc"}, 2}, &MallocChecker::checkCalloc}, @@ -481,23 +509,20 @@ class MallocChecker CallDescriptionMap<CheckFn> ReallocatingMemFnMap{ {{CDM::CLibrary, {"realloc"}, 2}, - std::bind(&MallocChecker::checkRealloc, _1, _2, _3, false)}, + std::bind(&MallocChecker::checkRealloc, _1, _2, _3, _4, false)}, {{CDM::CLibrary, {"reallocf"}, 2}, - std::bind(&MallocChecker::checkRealloc, _1, _2, _3, true)}, + std::bind(&MallocChecker::checkRealloc, _1, _2, _3, _4, true)}, {{CDM::CLibrary, {"g_realloc"}, 2}, - std::bind(&MallocChecker::checkRealloc, _1, _2, _3, false)}, + std::bind(&MallocChecker::checkRealloc, _1, _2, _3, _4, false)}, {{CDM::CLibrary, {"g_try_realloc"}, 2}, - std::bind(&MallocChecker::checkRealloc, _1, _2, _3, false)}, + std::bind(&MallocChecker::checkRealloc, _1, _2, _3, _4, false)}, {{CDM::CLibrary, {"g_realloc_n"}, 3}, &MallocChecker::checkReallocN}, {{CDM::CLibrary, {"g_try_realloc_n"}, 3}, &MallocChecker::checkReallocN}, - - // NOTE: the following CallDescription also matches the C++ standard - // library function std::getline(); the callback will filter it out. - {{CDM::CLibrary, {"getline"}, 3}, &MallocChecker::checkGetdelim}, - {{CDM::CLibrary, {"getdelim"}, 4}, &MallocChecker::checkGetdelim}, }; bool isMemCall(const CallEvent &Call) const; + bool hasOwnershipReturns(const CallEvent &Call) const; + bool hasOwnershipTakesHolds(const CallEvent &Call) const; void reportTaintBug(StringRef Msg, ProgramStateRef State, CheckerContext &C, llvm::ArrayRef<SymbolRef> TaintedSyms, AllocationFamily Family) const; @@ -531,8 +556,8 @@ class MallocChecker /// \param [in] RetVal Specifies the newly allocated pointer value; /// if unspecified, the value of expression \p E is used. [[nodiscard]] static ProgramStateRef - ProcessZeroAllocCheck(const CallEvent &Call, const unsigned IndexOfSizeArg, - ProgramStateRef State, + ProcessZeroAllocCheck(CheckerContext &C, const CallEvent &Call, + const unsigned IndexOfSizeArg, ProgramStateRef State, std::optional<SVal> RetVal = std::nullopt); /// Model functions with the ownership_returns attribute. @@ -554,6 +579,17 @@ class MallocChecker [[nodiscard]] ProgramStateRef MallocMemReturnsAttr(CheckerContext &C, const CallEvent &Call, const OwnershipAttr *Att, ProgramStateRef State) const; + /// Models memory allocation. + /// + /// \param [in] C Checker context. + /// \param [in] Call The expression that allocates memory. + /// \param [in] State The \c ProgramState right before allocation. + /// \param [in] isAlloca Is the allocation function alloca-like + /// \returns The ProgramState with returnValue bindinded + [[nodiscard]] ProgramStateRef MallocBindRetval(CheckerContext &C, + const CallEvent &Call, + ProgramStateRef State, + bool isAlloca) const; /// Models memory allocation. /// @@ -1031,13 +1067,28 @@ class StopTrackingCallback final : public SymbolVisitor { }; } // end anonymous namespace -static bool isStandardNewDelete(const FunctionDecl *FD) { +static bool isStandardNew(const FunctionDecl *FD) { + if (!FD) + return false; + + OverloadedOperatorKind Kind = FD->getOverloadedOperator(); + if (Kind != OO_New && Kind != OO_Array_New) + 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() || + FD->getASTContext().getSourceManager().isInSystemHeader(L); +} + +static bool isStandardDelete(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_Delete && Kind != OO_Array_Delete) return false; // This is standard if and only if it's not defined in a user file. @@ -1052,6 +1103,12 @@ static bool isStandardNewDelete(const FunctionDecl *FD) { // Methods of MallocChecker and MallocBugVisitor. //===----------------------------------------------------------------------===// +bool MallocChecker::isFreeingOwnershipAttrCall(const CallEvent &Call) { + const auto *Func = dyn_cast_or_null<FunctionDecl>(Call.getDecl()); + + return Func ? isFreeingOwnershipAttrCall(Func) : false; +} + bool MallocChecker::isFreeingOwnershipAttrCall(const FunctionDecl *Func) { if (Func->hasAttrs()) { for (const auto *I : Func->specific_attrs<OwnershipAttr>()) { @@ -1067,15 +1124,27 @@ bool MallocChecker::isFreeingCall(const CallEvent &Call) const { if (FreeingMemFnMap.lookup(Call) || ReallocatingMemFnMap.lookup(Call)) return true; - if (const auto *Func = dyn_cast_or_null<FunctionDecl>(Call.getDecl())) - return isFreeingOwnershipAttrCall(Func); + return isFreeingOwnershipAttrCall(Call); +} + +bool MallocChecker::isAllocatingOwnershipAttrCall(const CallEvent &Call) { + const auto *Func = dyn_cast_or_null<FunctionDecl>(Call.getDecl()); + + return Func ? isAllocatingOwnershipAttrCall(Func) : false; +} + +bool MallocChecker::isAllocatingOwnershipAttrCall(const FunctionDecl *Func) { + for (const auto *I : Func->specific_attrs<OwnershipAttr>()) { + if (I->getOwnKind() == OwnershipAttr::Returns) + return true; + } return false; } bool MallocChecker::isMemCall(const CallEvent &Call) const { if (FreeingMemFnMap.lookup(Call) || AllocatingMemFnMap.lookup(Call) || - ReallocatingMemFnMap.lookup(Call)) + AllocaMemFnMap.lookup(Call) || ReallocatingMemFnMap.lookup(Call)) return true; if (!ShouldIncludeOwnershipAnnotatedFunctions) @@ -1182,18 +1251,18 @@ SVal MallocChecker::evalMulForBufferSize(CheckerContext &C, const Expr *Blocks, return TotalSize; } -void MallocChecker::checkBasicAlloc(const CallEvent &Call, +void MallocChecker::checkBasicAlloc(ProgramStateRef State, + const CallEvent &Call, CheckerContext &C) const { - ProgramStateRef State = C.getState(); State = MallocMemAux(C, Call, Call.getArgExpr(0), UndefinedVal(), State, AllocationFamily(AF_Malloc)); - State = ProcessZeroAllocCheck(Call, 0, State); + State = ProcessZeroAllocCheck(C, Call, 0, State); C.addTransition(State); } -void MallocChecker::checkKernelMalloc(const CallEvent &Call, +void MallocChecker::checkKernelMalloc(ProgramStateRef State, + const CallEvent &Call, CheckerContext &C) const { - ProgramStateRef State = C.getState(); std::optional<ProgramStateRef> MaybeState = performKernelMalloc(Call, C, State); if (MaybeState) @@ -1226,7 +1295,8 @@ static bool isGRealloc(const CallEvent &Call) { AC.UnsignedLongTy; } -void MallocChecker::checkRealloc(const CallEvent &Call, CheckerContext &C, +void MallocChecker::checkRealloc(ProgramStateRef State, const CallEvent &Call, + CheckerContext &C, bool ShouldFreeOnFail) const { // Ignore calls to functions whose type does not match the expected type of // either the standard realloc or g_realloc from GLib. @@ -1236,24 +1306,22 @@ void MallocChecker::checkRealloc(const CallEvent &Call, CheckerContext &C, if (!isStandardRealloc(Call) && !isGRealloc(Call)) return; - ProgramStateRef State = C.getState(); State = ReallocMemAux(C, Call, ShouldFreeOnFail, State, AllocationFamily(AF_Malloc)); - State = ProcessZeroAllocCheck(Call, 1, State); + State = ProcessZeroAllocCheck(C, Call, 1, State); C.addTransition(State); } -void MallocChecker::checkCalloc(const CallEvent &Call, +void MallocChecker::checkCalloc(ProgramStateRef State, const CallEvent &Call, CheckerContext &C) const { - ProgramStateRef State = C.getState(); State = CallocMem(C, Call, State); - State = ProcessZeroAllocCheck(Call, 0, State); - State = ProcessZeroAllocCheck(Call, 1, State); + State = ProcessZeroAllocCheck(C, Call, 0, State); + State = ProcessZeroAllocCheck(C, Call, 1, State); C.addTransition(State); } -void MallocChecker::checkFree(const CallEvent &Call, CheckerContext &C) const { - ProgramStateRef State = C.getState(); +void MallocChecker::checkFree(ProgramStateRef State, const CallEvent &Call, + CheckerContext &C) const { bool IsKnownToBeAllocatedMemory = false; if (suppressDeallocationsInSuspiciousContexts(Call, C)) return; @@ -1262,29 +1330,28 @@ void MallocChecker::checkFree(const CallEvent &Call, CheckerContext &C) const { C.addTransition(State); } -void MallocChecker::checkAlloca(const CallEvent &Call, +void MallocChecker::checkAlloca(ProgramStateRef State, const CallEvent &Call, CheckerContext &C) const { - ProgramStateRef State = C.getState(); State = MallocMemAux(C, Call, Call.getArgExpr(0), UndefinedVal(), State, AllocationFamily(AF_Alloca)); - State = ProcessZeroAllocCheck(Call, 0, State); + State = ProcessZeroAllocCheck(C, Call, 0, State); C.addTransition(State); } -void MallocChecker::checkStrdup(const CallEvent &Call, +void MallocChecker::checkStrdup(ProgramStateRef State, const CallEvent &Call, CheckerContext &C) const { - ProgramStateRef State = C.getState(); const auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); if (!CE) return; - State = MallocUpdateRefState(C, CE, State, AllocationFamily(AF_Malloc)); + State = MallocMemAux(C, Call, UnknownVal(), UnknownVal(), State, + AllocationFamily(AF_Malloc)); C.addTransition(State); } -void MallocChecker::checkIfNameIndex(const CallEvent &Call, +void MallocChecker::checkIfNameIndex(ProgramStateRef State, + const CallEvent &Call, CheckerContext &C) const { - ProgramStateRef State = C.getState(); // Should we model this differently? We can allocate a fixed number of // elements with zeros in the last one. State = MallocMemAux(C, Call, UnknownVal(), UnknownVal(), State, @@ -1293,18 +1360,18 @@ void MallocChecker::checkIfNameIndex(const CallEvent &Call, C.addTransition(State); } -void MallocChecker::checkIfFreeNameIndex(const CallEvent &Call, +void MallocChecker::checkIfFreeNameIndex(ProgramStateRef State, + const CallEvent &Call, CheckerContext &C) const { - ProgramStateRef State = C.getState(); bool IsKnownToBeAllocatedMemory = false; State = FreeMemAux(C, Call, State, 0, false, IsKnownToBeAllocatedMemory, AllocationFamily(AF_IfNameIndex)); C.addTransition(State); } -void MallocChecker::checkCXXNewOrCXXDelete(const CallEvent &Call, +void MallocChecker::checkCXXNewOrCXXDelete(ProgramStateRef State, + const CallEvent &Call, CheckerContext &C) const { - ProgramStateRef State = C.getState(); bool IsKnownToBeAllocatedMemory = false; const auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); if (!CE) @@ -1321,12 +1388,12 @@ void MallocChecker::checkCXXNewOrCXXDelete(const CallEvent &Call, case OO_New: State = MallocMemAux(C, Call, CE->getArg(0), UndefinedVal(), State, AllocationFamily(AF_CXXNew)); - State = ProcessZeroAllocCheck(Call, 0, State); + State = ProcessZeroAllocCheck(C, Call, 0, State); break; case OO_Array_New: State = MallocMemAux(C, Call, CE->getArg(0), UndefinedVal(), State, AllocationFamily(AF_CXXNewArray)); - State = ProcessZeroAllocCheck(Call, 0, State); + State = ProcessZeroAllocCheck(C, Call, 0, State); break; case OO_Delete: State = FreeMemAux(C, Call, State, 0, false, IsKnownToBeAllocatedMemory, @@ -1344,48 +1411,44 @@ void MallocChecker::checkCXXNewOrCXXDelete(const CallEvent &Call, C.addTransition(State); } -void MallocChecker::checkGMalloc0(const CallEvent &Call, +void MallocChecker::checkGMalloc0(ProgramStateRef State, const CallEvent &Call, CheckerContext &C) const { - ProgramStateRef State = C.getState(); SValBuilder &svalBuilder = C.getSValBuilder(); SVal zeroVal = svalBuilder.makeZeroVal(svalBuilder.getContext().CharTy); State = MallocMemAux(C, Call, Call.getArgExpr(0), zeroVal, State, AllocationFamily(AF_Malloc)); - State = ProcessZeroAllocCheck(Call, 0, State); + State = ProcessZeroAllocCheck(C, Call, 0, State); C.addTransition(State); } -void MallocChecker::checkGMemdup(const CallEvent &Call, +void MallocChecker::checkGMemdup(ProgramStateRef State, const CallEvent &Call, CheckerContext &C) const { - ProgramStateRef State = C.getState(); State = MallocMemAux(C, Call, Call.getArgExpr(1), UnknownVal(), State, AllocationFamily(AF_Malloc)); - State = ProcessZeroAllocCheck(Call, 1, State); + State = ProcessZeroAllocCheck(C, Call, 1, State); C.addTransition(State); } -void MallocChecker::checkGMallocN(const CallEvent &Call, +void MallocChecker::checkGMallocN(ProgramStateRef State, const CallEvent &Call, CheckerContext &C) const { - ProgramStateRef State = C.getState(); SVal Init = UndefinedVal(); SVal TotalSize = evalMulForBufferSize(C, Call.getArgExpr(0), Call.getArgExpr(1)); State = MallocMemAux(C, Call, TotalSize, Init, State, AllocationFamily(AF_Malloc)); - State = ProcessZeroAllocCheck(Call, 0, State); - State = ProcessZeroAllocCheck(Call, 1, State); + State = ProcessZeroAllocCheck(C, Call, 0, State); + State = ProcessZeroAllocCheck(C, Call, 1, State); C.addTransition(State); } -void MallocChecker::checkGMallocN0(const CallEvent &Call, +void MallocChecker::checkGMallocN0(ProgramStateRef State, const CallEvent &Call, CheckerContext &C) const { - ProgramStateRef State = C.getState(); SValBuilder &SB = C.getSValBuilder(); SVal Init = SB.makeZeroVal(SB.getContext().CharTy); SVal TotalSize = evalMulForBufferSize(C, Call.getArgExpr(0), Call.getArgExpr(1)); State = MallocMemAux(C, Call, TotalSize, Init, State, AllocationFamily(AF_Malloc)); - State = ProcessZeroAllocCheck(Call, 0, State); - State = ProcessZeroAllocCheck(Call, 1, State); + State = ProcessZeroAllocCheck(C, Call, 0, State); + State = ProcessZeroAllocCheck(C, Call, 1, State); C.addTransition(State); } @@ -1395,14 +1458,13 @@ static bool isFromStdNamespace(const CallEvent &Call) { return FD->isInStdNamespace(); } -void MallocChecker::preGetdelim(const CallEvent &Call, +void MallocChecker::preGetdelim(ProgramStateRef State, const CallEvent &Call, CheckerContext &C) const { // Discard calls to the C++ standard library function std::getline(), which // is completely unrelated to the POSIX getline() that we're checking. if (isFromStdNamespace(Call)) return; - ProgramStateRef State = C.getState(); const auto LinePtr = getPointeeVal(Call.getArgSVal(0), State); if (!LinePtr) return; @@ -1419,22 +1481,19 @@ void MallocChecker::preGetdelim(const CallEvent &Call, C.addTransition(State); } -void MallocChecker::checkGetdelim(const CallEvent &Call, +void MallocChecker::checkGetdelim(ProgramStateRef State, const CallEvent &Call, CheckerContext &C) const { // Discard calls to the C++ standard library function std::getline(), which // is completely unrelated to the POSIX getline() that we're checking. if (isFromStdNamespace(Call)) return; - ProgramStateRef State = C.getState(); // Handle the post-conditions of getline and getdelim: // Register the new conjured value as an allocated buffer. const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); if (!CE) return; - SValBuilder &SVB = C.getSValBuilder(); - const auto LinePtr = getPointeeVal(Call.getArgSVal(0), State)->getAs<DefinedSVal>(); const auto Size = @@ -1442,25 +1501,24 @@ void MallocChecker::checkGetdelim(const CallEvent &Call, if (!LinePtr || !Size || !LinePtr->getAsRegion()) return; - State = setDynamicExtent(State, LinePtr->getAsRegion(), *Size, SVB); + State = setDynamicExtent(State, LinePtr->getAsRegion(), *Size); C.addTransition(MallocUpdateRefState(C, CE, State, AllocationFamily(AF_Malloc), *LinePtr)); } -void MallocChecker::checkReallocN(const CallEvent &Call, +void MallocChecker::checkReallocN(ProgramStateRef State, const CallEvent &Call, CheckerContext &C) const { - ProgramStateRef State = C.getState(); State = ReallocMemAux(C, Call, /*ShouldFreeOnFail=*/false, State, AllocationFamily(AF_Malloc), /*SuffixWithN=*/true); - State = ProcessZeroAllocCheck(Call, 1, State); - State = ProcessZeroAllocCheck(Call, 2, State); + State = ProcessZeroAllocCheck(C, Call, 1, State); + State = ProcessZeroAllocCheck(C, Call, 2, State); C.addTransition(State); } -void MallocChecker::checkOwnershipAttr(const CallEvent &Call, +void MallocChecker::checkOwnershipAttr(ProgramStateRef State, + const CallEvent &Call, CheckerContext &C) const { - ProgramStateRef State = C.getState(); const auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); if (!CE) return; @@ -1487,56 +1545,78 @@ void MallocChecker::checkOwnershipAttr(const CallEvent &Call, C.addTransition(State); } -void MallocChecker::checkPostCall(const CallEvent &Call, - CheckerContext &C) const { - if (C.wasInlined) - return; +bool MallocChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { if (!Call.getOriginExpr()) - return; + return false; ProgramStateRef State = C.getState(); if (const CheckFn *Callback = FreeingMemFnMap.lookup(Call)) { - (*Callback)(this, Call, C); - return; + (*Callback)(this, State, Call, C); + return true; } if (const CheckFn *Callback = AllocatingMemFnMap.lookup(Call)) { - (*Callback)(this, Call, C); - return; + State = MallocBindRetval(C, Call, State, false); + (*Callback)(this, State, Call, C); + return true; } if (const CheckFn *Callback = ReallocatingMemFnMap.lookup(Call)) { - (*Callback)(this, Call, C); - return; + State = MallocBindRetval(C, Call, State, false); + (*Callback)(this, State, Call, C); + return true; } if (isStandardNewDelete(Call)) { - checkCXXNewOrCXXDelete(Call, C); - return; + if (isStandardNew(Call)) + State = MallocBindRetval(C, Call, State, false); + + checkCXXNewOrCXXDelete(State, Call, C); + return true; + } + + if (const CheckFn *Callback = AllocaMemFnMap.lookup(Call)) { + State = MallocBindRetval(C, Call, State, true); + (*Callback)(this, State, Call, C); + return true; + } + + if (isFreeingOwnershipAttrCall(Call)) { + checkOwnershipAttr(State, Call, C); + return true; + } + + if (isAllocatingOwnershipAttrCall(Call)) { + State = MallocBindRetval(C, Call, State, false); + checkOwnershipAttr(State, Call, C); + return true; } - checkOwnershipAttr(Call, C); + return false; } // Performs a 0-sized allocations check. ProgramStateRef MallocChecker::ProcessZeroAllocCheck( - const CallEvent &Call, const unsigned IndexOfSizeArg, ProgramStateRef State, - std::optional<SVal> RetVal) { + CheckerContext &C, const CallEvent &Call, const unsigned IndexOfSizeArg, + ProgramStateRef State, std::optional<SVal> RetVal) { if (!State) return nullptr; - if (!RetVal) - RetVal = Call.getReturnValue(); - const Expr *Arg = nullptr; if (const CallExpr *CE = dyn_cast<CallExpr>(Call.getOriginExpr())) { Arg = CE->getArg(IndexOfSizeArg); + if (!RetVal) + RetVal = State->getSVal(CE, C.getLocationContext()); + ; } else if (const CXXNewExpr *NE = dyn_cast<CXXNewExpr>(Call.getOriginExpr())) { if (NE->isArray()) { Arg = *NE->getArraySize(); + if (!RetVal) + RetVal = State->getSVal(NE, C.getLocationContext()); + ; } else { return State; } @@ -1656,7 +1736,7 @@ MallocChecker::processNewAllocation(const CXXAllocatorCall &Call, } State = MallocUpdateRefState(C, NE, State, Family, Target); - State = ProcessZeroAllocCheck(Call, 0, State, Target); + State = ProcessZeroAllocCheck(C, Call, 0, State, Target); return State; } @@ -1736,6 +1816,25 @@ MallocChecker::MallocMemReturnsAttr(CheckerContext &C, const CallEvent &Call, return MallocMemAux(C, Call, UnknownVal(), UndefinedVal(), State, Family); } +ProgramStateRef MallocChecker::MallocBindRetval(CheckerContext &C, + const CallEvent &Call, + ProgramStateRef State, + bool isAlloca) const { + const Expr *CE = Call.getOriginExpr(); + + // We expect the allocation functions to return a pointer. + if (!Loc::isLocType(CE->getType())) + return nullptr; + + unsigned Count = C.blockCount(); + SValBuilder &SVB = C.getSValBuilder(); + const LocationContext *LCtx = C.getPredecessor()->getLocationContext(); + DefinedSVal RetVal = (isAlloca ? SVB.getAllocaRegionVal(CE, LCtx, Count) + : SVB.getConjuredHeapSymbolVal(CE, LCtx, Count) + .castAs<DefinedSVal>()); + return State->BindExpr(CE, C.getLocationContext(), RetVal); +} + ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C, const CallEvent &Call, const Expr *SizeEx, SVal Init, @@ -1814,20 +1913,12 @@ ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C, const Expr *CE = Call.getOriginExpr(); // We expect the malloc functions to return a pointer. - if (!Loc::isLocType(CE->getType())) - return nullptr; + // Should have been already checked. + assert(Loc::isLocType(CE->getType()) && + "Allocation functions must return a pointer"); - // Bind the return value to the symbolic value from the heap region. - // TODO: move use of this functions to an EvalCall callback, becasue - // BindExpr() should'nt be used elsewhere. - unsigned Count = C.blockCount(); - SValBuilder &SVB = C.getSValBuilder(); const LocationContext *LCtx = C.getPredecessor()->getLocationContext(); - DefinedSVal RetVal = ((Family.Kind == AF_Alloca) - ? SVB.getAllocaRegionVal(CE, LCtx, Count) - : SVB.getConjuredHeapSymbolVal(CE, LCtx, Count) - .castAs<DefinedSVal>()); - State = State->BindExpr(CE, C.getLocationContext(), RetVal); + SVal RetVal = State->getSVal(CE, C.getLocationContext()); // Fill the region with the initialization value. State = State->bindDefaultInitial(RetVal, Init, LCtx); @@ -1840,7 +1931,7 @@ ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C, // Set the region's extent. State = setDynamicExtent(State, RetVal.getAsRegion(), - Size.castAs<DefinedOrUnknownSVal>(), SVB); + Size.castAs<DefinedOrUnknownSVal>()); return MallocUpdateRefState(C, CE, State, Family); } @@ -1854,7 +1945,7 @@ static ProgramStateRef MallocUpdateRefState(CheckerContext &C, const Expr *E, // Get the return value. if (!RetVal) - RetVal = C.getSVal(E); + RetVal = State->getSVal(E, C.getLocationContext()); // We expect the malloc functions to return a pointer. if (!RetVal->getAs<Loc>()) @@ -1862,11 +1953,8 @@ static ProgramStateRef MallocUpdateRefState(CheckerContext &C, const Expr *E, SymbolRef Sym = RetVal->getAsLocSymbol(); - // This is a return value of a function that was not inlined, such as malloc() - // or new(). We've checked that in the caller. Therefore, it must be a symbol. - assert(Sym); - // FIXME: In theory this assertion should fail for `alloca()` calls (because - // `AllocaRegion`s are not symbolic); but in practice this does not happen. + // FIXME: Following if fails for `alloca()` calls (because + // `AllocaRegion`s are not symbolic); // As the current code appears to work correctly, I'm not touching this issue // now, but it would be good to investigate and clarify this. // Also note that perhaps the special `AllocaRegion` should be replaced by @@ -1874,8 +1962,10 @@ static ProgramStateRef MallocUpdateRefState(CheckerContext &C, const Expr *E, // proper tracking of memory allocated by `alloca()` -- and after that change // this assertion would become valid again. - // Set the symbol's state to Allocated. - return State->set<RegionState>(Sym, RefState::getAllocated(Family, E)); + if (Sym) + return State->set<RegionState>(Sym, RefState::getAllocated(Family, E)); + else + return State; } ProgramStateRef MallocChecker::FreeMemAttr(CheckerContext &C, @@ -2781,6 +2871,7 @@ MallocChecker::ReallocMemAux(CheckerContext &C, const CallEvent &Call, return stateMalloc; } + // Proccess as allocation of 0 bytes. if (PrtIsNull && SizeIsZero) return State; @@ -2815,7 +2906,7 @@ MallocChecker::ReallocMemAux(CheckerContext &C, const CallEvent &Call, // Get the from and to pointer symbols as in toPtr = realloc(fromPtr, size). SymbolRef FromPtr = arg0Val.getLocSymbolInBase(); - SVal RetVal = C.getSVal(CE); + SVal RetVal = stateRealloc->getSVal(CE, C.getLocationContext()); SymbolRef ToPtr = RetVal.getAsSymbol(); assert(FromPtr && ToPtr && "By this point, FreeMemAux and MallocMemAux should have checked " @@ -3014,6 +3105,14 @@ void MallocChecker::checkDeadSymbols(SymbolReaper &SymReaper, C.addTransition(state->set<RegionState>(RS), N); } +void MallocChecker::checkPostCall(const CallEvent &Call, + CheckerContext &C) const { + if (const auto *PostFN = PostFnMap.lookup(Call)) { + (*PostFN)(this, C.getState(), Call, C); + return; + } +} + void MallocChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { @@ -3047,7 +3146,7 @@ void MallocChecker::checkPreCall(const CallEvent &Call, // We need to handle getline pre-conditions here before the pointed region // gets invalidated by StreamChecker if (const auto *PreFN = PreFnMap.lookup(Call)) { - (*PreFN)(this, Call, C); + (*PreFN)(this, C.getState(), Call, C); return; } diff --git a/clang/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp index 87d255eeffc177..8d17ba5d690b90 100644 --- a/clang/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp @@ -266,7 +266,6 @@ void VLASizeChecker::checkPreStmt(const DeclStmt *DS, CheckerContext &C) const { return; ASTContext &Ctx = C.getASTContext(); - SValBuilder &SVB = C.getSValBuilder(); ProgramStateRef State = C.getState(); QualType TypeToCheck; @@ -301,7 +300,7 @@ void VLASizeChecker::checkPreStmt(const DeclStmt *DS, CheckerContext &C) const { if (VD) { State = setDynamicExtent(State, State->getRegion(VD, C.getLocationContext()), - ArraySize.castAs<NonLoc>(), SVB); + ArraySize.castAs<NonLoc>()); } // Remember our assumptions! diff --git a/clang/lib/StaticAnalyzer/Core/DynamicExtent.cpp b/clang/lib/StaticAnalyzer/Core/DynamicExtent.cpp index 6cf06413b5372c..f0c6501758b4d6 100644 --- a/clang/lib/StaticAnalyzer/Core/DynamicExtent.cpp +++ b/clang/lib/StaticAnalyzer/Core/DynamicExtent.cpp @@ -120,7 +120,7 @@ DefinedOrUnknownSVal getDynamicElementCountWithOffset(ProgramStateRef State, } ProgramStateRef setDynamicExtent(ProgramStateRef State, const MemRegion *MR, - DefinedOrUnknownSVal Size, SValBuilder &SVB) { + DefinedOrUnknownSVal Size) { MR = MR->StripCasts(); if (Size.isUnknown()) diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp index 9d3e4fc944fb7b..2ca24d0c5ab22b 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp @@ -817,8 +817,7 @@ ProgramStateRef ExprEngine::bindReturnValue(const CallEvent &Call, if (Size.isUndef()) Size = UnknownVal(); - State = setDynamicExtent(State, MR, Size.castAs<DefinedOrUnknownSVal>(), - svalBuilder); + State = setDynamicExtent(State, MR, Size.castAs<DefinedOrUnknownSVal>()); } else { R = svalBuilder.conjureSymbolVal(nullptr, E, LCtx, ResultTy, Count); } diff --git a/clang/test/Analysis/NewDelete-checker-test.cpp b/clang/test/Analysis/NewDelete-checker-test.cpp index 1100b49e84d3ac..ac8fcdbd0ef1e4 100644 --- a/clang/test/Analysis/NewDelete-checker-test.cpp +++ b/clang/test/Analysis/NewDelete-checker-test.cpp @@ -67,19 +67,6 @@ void testGlobalNoThrowPlacementExprNewBeforeOverload() { int *p = new(std::nothrow) int; } // leak-warning{{Potential leak of memory pointed to by 'p'}} -//----- Standard pointer placement operators -void testGlobalPointerPlacementNew() { - int i; - - void *p1 = operator new(0, &i); // no warn - - void *p2 = operator new[](0, &i); // no warn - - int *p3 = new(&i) int; // no warn - - int *p4 = new(&i) int[0]; // no warn -} - //----- Other cases void testNewMemoryIsInHeap() { int *p = new int; diff --git a/clang/test/Analysis/NewDelete-intersections.mm b/clang/test/Analysis/NewDelete-intersections.mm index 9ac471600e8b9b..968ef9f6e2b56f 100644 --- a/clang/test/Analysis/NewDelete-intersections.mm +++ b/clang/test/Analysis/NewDelete-intersections.mm @@ -8,8 +8,6 @@ // RUN: -analyzer-checker=core \ // RUN: -analyzer-checker=cplusplus.NewDeleteLeaks -// leak-no-diagnostics - // RUN: %clang_analyze_cc1 -std=c++11 -DLEAKS -fblocks %s \ // RUN: -verify=mismatch \ // RUN: -analyzer-checker=core \ @@ -60,14 +58,14 @@ void testFreeOpNew() { void *p = operator new(0); free(p); // mismatch-warning@-1{{Memory allocated by 'operator new' should be deallocated by 'delete', not 'free()'}} -} +} // leak-warning{{Potential leak of memory pointed to by 'p'}} void testFreeNewExpr() { int *p = new int; free(p); // mismatch-warning@-1{{Memory allocated by 'new' should be deallocated by 'delete', not 'free()'}} free(p); -} +} // leak-warning{{Potential leak of memory pointed to by 'p'}} void testObjcFreeNewed() { int *p = new int; diff --git a/clang/test/Analysis/malloc-interprocedural.c b/clang/test/Analysis/malloc-interprocedural.c index ae7a4626288e68..5e5232af7b46e8 100644 --- a/clang/test/Analysis/malloc-interprocedural.c +++ b/clang/test/Analysis/malloc-interprocedural.c @@ -98,38 +98,3 @@ int uafAndCallsFooWithEmptyReturn(void) { fooWithEmptyReturn(12); return *x; // expected-warning {{Use of memory after it is freed}} } - - -// If we inline any of the malloc-family functions, the checker shouldn't also -// try to do additional modeling. -char *strndup(const char *str, size_t n) { - if (!str) - return 0; - - // DO NOT FIX. This is to test that we are actually using the inlined - // behavior! - if (n < 5) - return 0; - - size_t length = strlen(str); - if (length < n) - n = length; - - char *result = malloc(n + 1); - memcpy(result, str, n); - result[n] = '\0'; - return result; -} - -void useStrndup(size_t n) { - if (n == 0) { - (void)strndup(0, 20); // no-warning - return; - } else if (n < 5) { - (void)strndup("hi there", n); // no-warning - return; - } else { - (void)strndup("hi there", n); - return; // expected-warning{{leak}} - } -} From d0ab7314013158b0183861d0faaae001f246c87f Mon Sep 17 00:00:00 2001 From: Pavel Skripkin <paskrip...@gmail.com> Date: Wed, 28 Aug 2024 11:09:05 +0300 Subject: [PATCH 2/4] fix style --- .../StaticAnalyzer/Checkers/MallocChecker.cpp | 52 +++++++++---------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index 1f524481049fa4..092c1c8bc0ca1d 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -585,8 +585,8 @@ class MallocChecker /// \param [in] Call The expression that allocates memory. /// \param [in] State The \c ProgramState right before allocation. /// \param [in] isAlloca Is the allocation function alloca-like - /// \returns The ProgramState with returnValue bindinded - [[nodiscard]] ProgramStateRef MallocBindRetval(CheckerContext &C, + /// \returns The ProgramState with returnValue bound + [[nodiscard]] ProgramStateRef MallocBindRetVal(CheckerContext &C, const CallEvent &Call, ProgramStateRef State, bool isAlloca) const; @@ -1106,7 +1106,7 @@ static bool isStandardDelete(const FunctionDecl *FD) { bool MallocChecker::isFreeingOwnershipAttrCall(const CallEvent &Call) { const auto *Func = dyn_cast_or_null<FunctionDecl>(Call.getDecl()); - return Func ? isFreeingOwnershipAttrCall(Func) : false; + return Func && isFreeingOwnershipAttrCall(Func); } bool MallocChecker::isFreeingOwnershipAttrCall(const FunctionDecl *Func) { @@ -1130,7 +1130,7 @@ bool MallocChecker::isFreeingCall(const CallEvent &Call) const { bool MallocChecker::isAllocatingOwnershipAttrCall(const CallEvent &Call) { const auto *Func = dyn_cast_or_null<FunctionDecl>(Call.getDecl()); - return Func ? isAllocatingOwnershipAttrCall(Func) : false; + return Func && isAllocatingOwnershipAttrCall(Func); } bool MallocChecker::isAllocatingOwnershipAttrCall(const FunctionDecl *Func) { @@ -1557,27 +1557,30 @@ bool MallocChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { } if (const CheckFn *Callback = AllocatingMemFnMap.lookup(Call)) { - State = MallocBindRetval(C, Call, State, false); + State = MallocBindRetVal(C, Call, State, false); (*Callback)(this, State, Call, C); return true; } if (const CheckFn *Callback = ReallocatingMemFnMap.lookup(Call)) { - State = MallocBindRetval(C, Call, State, false); + State = MallocBindRetVal(C, Call, State, false); (*Callback)(this, State, Call, C); return true; } - if (isStandardNewDelete(Call)) { - if (isStandardNew(Call)) - State = MallocBindRetval(C, Call, State, false); + if (isStandardNew(Call)) { + State = MallocBindRetVal(C, Call, State, false); + checkCXXNewOrCXXDelete(State, Call, C); + return true; + } + if (isStandardDelete(Call)) { checkCXXNewOrCXXDelete(State, Call, C); return true; } if (const CheckFn *Callback = AllocaMemFnMap.lookup(Call)) { - State = MallocBindRetval(C, Call, State, true); + State = MallocBindRetVal(C, Call, State, true); (*Callback)(this, State, Call, C); return true; } @@ -1588,7 +1591,7 @@ bool MallocChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { } if (isAllocatingOwnershipAttrCall(Call)) { - State = MallocBindRetval(C, Call, State, false); + State = MallocBindRetVal(C, Call, State, false); checkOwnershipAttr(State, Call, C); return true; } @@ -1607,16 +1610,10 @@ ProgramStateRef MallocChecker::ProcessZeroAllocCheck( if (const CallExpr *CE = dyn_cast<CallExpr>(Call.getOriginExpr())) { Arg = CE->getArg(IndexOfSizeArg); - if (!RetVal) - RetVal = State->getSVal(CE, C.getLocationContext()); - ; } else if (const CXXNewExpr *NE = dyn_cast<CXXNewExpr>(Call.getOriginExpr())) { if (NE->isArray()) { Arg = *NE->getArraySize(); - if (!RetVal) - RetVal = State->getSVal(NE, C.getLocationContext()); - ; } else { return State; } @@ -1625,6 +1622,9 @@ ProgramStateRef MallocChecker::ProcessZeroAllocCheck( return nullptr; } + if (!RetVal) + RetVal = State->getSVal(Call.getOriginExpr(), C.getLocationContext()); + assert(Arg); auto DefArgVal = @@ -1816,7 +1816,7 @@ MallocChecker::MallocMemReturnsAttr(CheckerContext &C, const CallEvent &Call, return MallocMemAux(C, Call, UnknownVal(), UndefinedVal(), State, Family); } -ProgramStateRef MallocChecker::MallocBindRetval(CheckerContext &C, +ProgramStateRef MallocChecker::MallocBindRetVal(CheckerContext &C, const CallEvent &Call, ProgramStateRef State, bool isAlloca) const { @@ -1953,19 +1953,15 @@ static ProgramStateRef MallocUpdateRefState(CheckerContext &C, const Expr *E, SymbolRef Sym = RetVal->getAsLocSymbol(); - // FIXME: Following if fails for `alloca()` calls (because - // `AllocaRegion`s are not symbolic); - // As the current code appears to work correctly, I'm not touching this issue - // now, but it would be good to investigate and clarify this. - // Also note that perhaps the special `AllocaRegion` should be replaced by - // `SymbolicRegion` (or turned into a subclass of `SymbolicRegion`) to enable - // proper tracking of memory allocated by `alloca()` -- and after that change - // this assertion would become valid again. + // NOTE: If this was an `alloca()` call, then `RetVal` holds an + // `AllocaRegion`, so `Sym` will be a nullpointer because `AllocaRegion`s do + // not have an associated symbol. However, this distinct region type means + // that we don't need to store anything about them in `RegionState`. if (Sym) return State->set<RegionState>(Sym, RefState::getAllocated(Family, E)); - else - return State; + + return State; } ProgramStateRef MallocChecker::FreeMemAttr(CheckerContext &C, From ea0536963809bc3b1911d06e05c8af1197ac26fc Mon Sep 17 00:00:00 2001 From: Pavel Skripkin <paskrip...@gmail.com> Date: Wed, 28 Aug 2024 11:38:48 +0300 Subject: [PATCH 3/4] invalidate memory region after --- clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp | 8 ++++++++ clang/test/Analysis/NewDelete-checker-test.cpp | 4 ---- clang/test/Analysis/NewDelete-intersections.mm | 6 ++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index 092c1c8bc0ca1d..b53827494f2a6c 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -2300,6 +2300,14 @@ MallocChecker::FreeMemAux(CheckerContext &C, const Expr *ArgExpr, // that. assert(!RsBase || (RsBase && RsBase->getAllocationFamily() == Family)); + // Assume that after memory is freed, it contains unknown values. This + // conforts languages standards, since reading from freed memory is considered + // UB and may result in arbitrary value. + State = State->invalidateRegions({location}, Call.getOriginExpr(), + C.blockCount(), C.getLocationContext(), + /*CausesPointerEscape=*/false, + /*InvalidatedSymbols=*/nullptr); + // Normal free. if (Hold) return State->set<RegionState>(SymBase, diff --git a/clang/test/Analysis/NewDelete-checker-test.cpp b/clang/test/Analysis/NewDelete-checker-test.cpp index ac8fcdbd0ef1e4..21b4cf817b5df6 100644 --- a/clang/test/Analysis/NewDelete-checker-test.cpp +++ b/clang/test/Analysis/NewDelete-checker-test.cpp @@ -37,10 +37,6 @@ extern "C" void *malloc(size_t); extern "C" void free (void* ptr); int *global; -//------------------ -// check for leaks -//------------------ - //----- Standard non-placement operators void testGlobalOpNew() { void *p = operator new(0); diff --git a/clang/test/Analysis/NewDelete-intersections.mm b/clang/test/Analysis/NewDelete-intersections.mm index 968ef9f6e2b56f..e897f48b839620 100644 --- a/clang/test/Analysis/NewDelete-intersections.mm +++ b/clang/test/Analysis/NewDelete-intersections.mm @@ -3,6 +3,8 @@ // RUN: -analyzer-checker=core \ // RUN: -analyzer-checker=cplusplus.NewDelete +// leak-no-diagnostics + // RUN: %clang_analyze_cc1 -std=c++11 -DLEAKS -fblocks %s \ // RUN: -verify=leak \ // RUN: -analyzer-checker=core \ @@ -58,14 +60,14 @@ void testFreeOpNew() { void *p = operator new(0); free(p); // mismatch-warning@-1{{Memory allocated by 'operator new' should be deallocated by 'delete', not 'free()'}} -} // leak-warning{{Potential leak of memory pointed to by 'p'}} +} void testFreeNewExpr() { int *p = new int; free(p); // mismatch-warning@-1{{Memory allocated by 'new' should be deallocated by 'delete', not 'free()'}} free(p); -} // leak-warning{{Potential leak of memory pointed to by 'p'}} +} void testObjcFreeNewed() { int *p = new int; From 7910b7c4a831c50028c5e514d64ef503f567900e Mon Sep 17 00:00:00 2001 From: Pavel Skripkin <paskrip...@gmail.com> Date: Wed, 28 Aug 2024 11:55:55 +0300 Subject: [PATCH 4/4] make getConjuredHeapSymbolVal return DefinedSVal --- .../Core/PathSensitive/SValBuilder.h | 12 +++++----- .../StaticAnalyzer/Checkers/MallocChecker.cpp | 5 ++--- clang/lib/StaticAnalyzer/Core/SValBuilder.cpp | 22 ++++++++++--------- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h index a560f274c43ccd..6eedaf0544559b 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h @@ -215,17 +215,17 @@ class SValBuilder { /// Conjure a symbol representing heap allocated memory region. /// /// Note, the expression should represent a location. - DefinedOrUnknownSVal getConjuredHeapSymbolVal(const Expr *E, - const LocationContext *LCtx, - unsigned Count); + DefinedSVal getConjuredHeapSymbolVal(const Expr *E, + const LocationContext *LCtx, + unsigned Count); /// Conjure a symbol representing heap allocated memory region. /// /// Note, now, the expression *doesn't* need to represent a location. /// But the type need to! - DefinedOrUnknownSVal getConjuredHeapSymbolVal(const Expr *E, - const LocationContext *LCtx, - QualType type, unsigned Count); + DefinedSVal getConjuredHeapSymbolVal(const Expr *E, + const LocationContext *LCtx, + QualType type, unsigned Count); /// Create an SVal representing the result of an alloca()-like call, that is, /// an AllocaRegion on the stack. diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index b53827494f2a6c..596a885f886e7e 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -1829,9 +1829,8 @@ ProgramStateRef MallocChecker::MallocBindRetVal(CheckerContext &C, unsigned Count = C.blockCount(); SValBuilder &SVB = C.getSValBuilder(); const LocationContext *LCtx = C.getPredecessor()->getLocationContext(); - DefinedSVal RetVal = (isAlloca ? SVB.getAllocaRegionVal(CE, LCtx, Count) - : SVB.getConjuredHeapSymbolVal(CE, LCtx, Count) - .castAs<DefinedSVal>()); + DefinedSVal RetVal = isAlloca ? SVB.getAllocaRegionVal(CE, LCtx, Count) + : SVB.getConjuredHeapSymbolVal(CE, LCtx, Count); return State->BindExpr(CE, C.getLocationContext(), RetVal); } diff --git a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp index eb9cde5c8918fc..7eca0579143f44 100644 --- a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp +++ b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp @@ -210,22 +210,24 @@ DefinedOrUnknownSVal SValBuilder::conjureSymbolVal(const Stmt *stmt, return nonloc::SymbolVal(sym); } -DefinedOrUnknownSVal -SValBuilder::getConjuredHeapSymbolVal(const Expr *E, - const LocationContext *LCtx, - unsigned VisitCount) { +DefinedSVal SValBuilder::getConjuredHeapSymbolVal(const Expr *E, + const LocationContext *LCtx, + unsigned VisitCount) { QualType T = E->getType(); return getConjuredHeapSymbolVal(E, LCtx, T, VisitCount); } -DefinedOrUnknownSVal -SValBuilder::getConjuredHeapSymbolVal(const Expr *E, - const LocationContext *LCtx, - QualType type, unsigned VisitCount) { +DefinedSVal SValBuilder::getConjuredHeapSymbolVal(const Expr *E, + const LocationContext *LCtx, + QualType type, + unsigned VisitCount) { assert(Loc::isLocType(type)); assert(SymbolManager::canSymbolicate(type)); - if (type->isNullPtrType()) - return makeZeroVal(type); + if (type->isNullPtrType()) { + // makeZeroVal() returns UnknownVal only in case of FP number, which + // is not the case. + return makeZeroVal(type).castAs<DefinedSVal>(); + } SymbolRef sym = SymMgr.conjureSymbol(E, LCtx, type, VisitCount); return loc::MemRegionVal(MemMgr.getSymbolicHeapRegion(sym)); _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits