aboutsummaryrefslogtreecommitdiff
path: root/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
diff options
context:
space:
mode:
authorDimitry Andric <dim@FreeBSD.org>2022-07-04 19:20:19 +0000
committerDimitry Andric <dim@FreeBSD.org>2023-02-08 19:02:26 +0000
commit81ad626541db97eb356e2c1d4a20eb2a26a766ab (patch)
tree311b6a8987c32b1e1dcbab65c54cfac3fdb56175 /contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
parent5fff09660e06a66bed6482da9c70df328e16bbb6 (diff)
parent145449b1e420787bb99721a429341fa6be3adfb6 (diff)
Diffstat (limited to 'contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp')
-rw-r--r--contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp150
1 files changed, 98 insertions, 52 deletions
diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
index 57080a84451a..92d7cef78b13 100644
--- a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
+++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
@@ -306,9 +306,9 @@ public:
/// functions might free the memory.
/// In optimistic mode, the checker assumes that all user-defined functions
/// which might free a pointer are annotated.
- DefaultBool ShouldIncludeOwnershipAnnotatedFunctions;
+ bool ShouldIncludeOwnershipAnnotatedFunctions = false;
- DefaultBool ShouldRegisterNoOwnershipChangeVisitor;
+ bool ShouldRegisterNoOwnershipChangeVisitor = false;
/// Many checkers are essentially built into this one, so enabling them will
/// make MallocChecker perform additional modeling and reporting.
@@ -326,7 +326,7 @@ public:
using LeakInfo = std::pair<const ExplodedNode *, const MemRegion *>;
- DefaultBool ChecksEnabled[CK_NumCheckKinds];
+ bool ChecksEnabled[CK_NumCheckKinds] = {false};
CheckerNameRef CheckNames[CK_NumCheckKinds];
void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
@@ -398,6 +398,9 @@ private:
};
bool isFreeingCall(const CallEvent &Call) const;
+ static bool isFreeingOwnershipAttrCall(const FunctionDecl *Func);
+
+ friend class NoOwnershipChangeVisitor;
CallDescriptionMap<CheckFn> AllocatingMemFnMap{
{{"alloca", 1}, &MallocChecker::checkAlloca},
@@ -742,7 +745,9 @@ private:
namespace {
class NoOwnershipChangeVisitor final : public NoStateChangeFuncVisitor {
+ // The symbol whose (lack of) ownership change we are interested in.
SymbolRef Sym;
+ const MallocChecker &Checker;
using OwnerSet = llvm::SmallPtrSet<const MemRegion *, 8>;
// Collect which entities point to the allocated memory, and could be
@@ -794,18 +799,57 @@ protected:
return "";
}
+ /// Syntactically checks whether the callee is a deallocating function. Since
+ /// we have no path-sensitive information on this call (we would need a
+ /// CallEvent instead of a CallExpr for that), its possible that a
+ /// deallocation function was called indirectly through a function pointer,
+ /// but we are not able to tell, so this is a best effort analysis.
+ /// See namespace `memory_passed_to_fn_call_free_through_fn_ptr` in
+ /// clang/test/Analysis/NewDeleteLeaks.cpp.
+ bool isFreeingCallAsWritten(const CallExpr &Call) const {
+ if (Checker.FreeingMemFnMap.lookupAsWritten(Call) ||
+ Checker.ReallocatingMemFnMap.lookupAsWritten(Call))
+ return true;
+
+ if (const auto *Func =
+ llvm::dyn_cast_or_null<FunctionDecl>(Call.getCalleeDecl()))
+ return MallocChecker::isFreeingOwnershipAttrCall(Func);
+
+ return false;
+ }
+
+ /// Heuristically guess whether the callee intended to free memory. This is
+ /// done syntactically, because we are trying to argue about alternative
+ /// paths of execution, and as a consequence we don't have path-sensitive
+ /// information.
bool doesFnIntendToHandleOwnership(const Decl *Callee, ASTContext &ACtx) {
using namespace clang::ast_matchers;
const FunctionDecl *FD = dyn_cast<FunctionDecl>(Callee);
- if (!FD)
+
+ // Given that the stack frame was entered, the body should always be
+ // theoretically obtainable. In case of body farms, the synthesized body
+ // is not attached to declaration, thus triggering the '!FD->hasBody()'
+ // branch. That said, would a synthesized body ever intend to handle
+ // ownership? As of today they don't. And if they did, how would we
+ // put notes inside it, given that it doesn't match any source locations?
+ if (!FD || !FD->hasBody())
return false;
- // TODO: Operator delete is hardly the only deallocator -- Can we reuse
- // isFreeingCall() or something thats already here?
- auto Deallocations = match(
- stmt(hasDescendant(cxxDeleteExpr().bind("delete"))
- ), *FD->getBody(), ACtx);
- // TODO: Ownership my change with an attempt to store the allocated memory.
- return !Deallocations.empty();
+
+ auto Matches = match(findAll(stmt(anyOf(cxxDeleteExpr().bind("delete"),
+ callExpr().bind("call")))),
+ *FD->getBody(), ACtx);
+ for (BoundNodes Match : Matches) {
+ if (Match.getNodeAs<CXXDeleteExpr>("delete"))
+ return true;
+
+ if (const auto *Call = Match.getNodeAs<CallExpr>("call"))
+ if (isFreeingCallAsWritten(*Call))
+ return true;
+ }
+ // TODO: Ownership might change with an attempt to store the allocated
+ // memory, not only through deallocation. Check for attempted stores as
+ // well.
+ return false;
}
virtual bool
@@ -874,20 +918,15 @@ protected:
}
public:
- NoOwnershipChangeVisitor(SymbolRef Sym)
- : NoStateChangeFuncVisitor(bugreporter::TrackingKind::Thorough),
- Sym(Sym) {}
+ NoOwnershipChangeVisitor(SymbolRef Sym, const MallocChecker *Checker)
+ : NoStateChangeFuncVisitor(bugreporter::TrackingKind::Thorough), Sym(Sym),
+ Checker(*Checker) {}
void Profile(llvm::FoldingSetNodeID &ID) const override {
static int Tag = 0;
ID.AddPointer(&Tag);
ID.AddPointer(Sym);
}
-
- void *getTag() const {
- static int Tag = 0;
- return static_cast<void *>(&Tag);
- }
};
} // end anonymous namespace
@@ -1061,12 +1100,8 @@ static bool isStandardNewDelete(const FunctionDecl *FD) {
// Methods of MallocChecker and MallocBugVisitor.
//===----------------------------------------------------------------------===//
-bool MallocChecker::isFreeingCall(const CallEvent &Call) const {
- if (FreeingMemFnMap.lookup(Call) || ReallocatingMemFnMap.lookup(Call))
- return true;
-
- const auto *Func = dyn_cast<FunctionDecl>(Call.getDecl());
- if (Func && Func->hasAttrs()) {
+bool MallocChecker::isFreeingOwnershipAttrCall(const FunctionDecl *Func) {
+ if (Func->hasAttrs()) {
for (const auto *I : Func->specific_attrs<OwnershipAttr>()) {
OwnershipAttr::OwnershipKind OwnKind = I->getOwnKind();
if (OwnKind == OwnershipAttr::Takes || OwnKind == OwnershipAttr::Holds)
@@ -1076,6 +1111,16 @@ bool MallocChecker::isFreeingCall(const CallEvent &Call) const {
return false;
}
+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 false;
+}
+
bool MallocChecker::isMemCall(const CallEvent &Call) const {
if (FreeingMemFnMap.lookup(Call) || AllocatingMemFnMap.lookup(Call) ||
ReallocatingMemFnMap.lookup(Call))
@@ -1110,7 +1155,7 @@ MallocChecker::performKernelMalloc(const CallEvent &Call, CheckerContext &C,
ASTContext &Ctx = C.getASTContext();
llvm::Triple::OSType OS = Ctx.getTargetInfo().getTriple().getOS();
- if (!KernelZeroFlagVal.hasValue()) {
+ if (!KernelZeroFlagVal) {
if (OS == llvm::Triple::FreeBSD)
KernelZeroFlagVal = 0x0100;
else if (OS == llvm::Triple::NetBSD)
@@ -1137,7 +1182,7 @@ MallocChecker::performKernelMalloc(const CallEvent &Call, CheckerContext &C,
const Expr *FlagsEx = Call.getArgExpr(Call.getNumArgs() - 1);
const SVal V = C.getSVal(FlagsEx);
- if (!V.getAs<NonLoc>()) {
+ if (!isa<NonLoc>(V)) {
// The case where 'V' can be a location can only be due to a bad header,
// so in this case bail out.
return None;
@@ -1193,7 +1238,7 @@ void MallocChecker::checkKernelMalloc(const CallEvent &Call,
ProgramStateRef State = C.getState();
llvm::Optional<ProgramStateRef> MaybeState =
performKernelMalloc(Call, C, State);
- if (MaybeState.hasValue())
+ if (MaybeState)
State = MaybeState.getValue();
else
State = MallocMemAux(C, Call, Call.getArgExpr(0), UndefinedVal(), State,
@@ -1358,8 +1403,8 @@ void MallocChecker::checkGMalloc0(const CallEvent &Call,
void MallocChecker::checkGMemdup(const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
- State = MallocMemAux(C, Call, Call.getArgExpr(1), UndefinedVal(), State,
- AF_Malloc);
+ State =
+ MallocMemAux(C, Call, Call.getArgExpr(1), UnknownVal(), State, AF_Malloc);
State = ProcessZeroAllocCheck(Call, 1, State);
C.addTransition(State);
}
@@ -1862,12 +1907,12 @@ ProgramStateRef MallocChecker::FreeMemAux(
return nullptr;
SVal ArgVal = C.getSVal(ArgExpr);
- if (!ArgVal.getAs<DefinedOrUnknownSVal>())
+ if (!isa<DefinedOrUnknownSVal>(ArgVal))
return nullptr;
DefinedOrUnknownSVal location = ArgVal.castAs<DefinedOrUnknownSVal>();
// Check for null dereferences.
- if (!location.getAs<Loc>())
+ if (!isa<Loc>(location))
return nullptr;
// The explicit NULL case, no operation is performed.
@@ -2173,7 +2218,7 @@ void MallocChecker::HandleNonHeapDealloc(CheckerContext &C, SVal ArgVal,
}
Optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(Family);
- if (!CheckKind.hasValue())
+ if (!CheckKind)
return;
if (ExplodedNode *N = C.generateErrorNode()) {
@@ -2306,7 +2351,7 @@ void MallocChecker::HandleOffsetFree(CheckerContext &C, SVal ArgVal,
}
Optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(Family);
- if (!CheckKind.hasValue())
+ if (!CheckKind)
return;
ExplodedNode *N = C.generateErrorNode();
@@ -2363,7 +2408,7 @@ void MallocChecker::HandleUseAfterFree(CheckerContext &C, SourceRange Range,
}
Optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(C, Sym);
- if (!CheckKind.hasValue())
+ if (!CheckKind)
return;
if (ExplodedNode *N = C.generateErrorNode()) {
@@ -2402,7 +2447,7 @@ void MallocChecker::HandleDoubleFree(CheckerContext &C, SourceRange Range,
}
Optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(C, Sym);
- if (!CheckKind.hasValue())
+ if (!CheckKind)
return;
if (ExplodedNode *N = C.generateErrorNode()) {
@@ -2432,7 +2477,7 @@ void MallocChecker::HandleDoubleDelete(CheckerContext &C, SymbolRef Sym) const {
}
Optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(C, Sym);
- if (!CheckKind.hasValue())
+ if (!CheckKind)
return;
if (ExplodedNode *N = C.generateErrorNode()) {
@@ -2460,7 +2505,7 @@ void MallocChecker::HandleUseZeroAlloc(CheckerContext &C, SourceRange Range,
Optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(C, Sym);
- if (!CheckKind.hasValue())
+ if (!CheckKind)
return;
if (ExplodedNode *N = C.generateErrorNode()) {
@@ -2492,7 +2537,7 @@ void MallocChecker::HandleFunctionPtrFree(CheckerContext &C, SVal ArgVal,
}
Optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(Family);
- if (!CheckKind.hasValue())
+ if (!CheckKind)
return;
if (ExplodedNode *N = C.generateErrorNode()) {
@@ -2537,14 +2582,14 @@ MallocChecker::ReallocMemAux(CheckerContext &C, const CallEvent &Call,
const Expr *arg0Expr = CE->getArg(0);
SVal Arg0Val = C.getSVal(arg0Expr);
- if (!Arg0Val.getAs<DefinedOrUnknownSVal>())
+ if (!isa<DefinedOrUnknownSVal>(Arg0Val))
return nullptr;
DefinedOrUnknownSVal arg0Val = Arg0Val.castAs<DefinedOrUnknownSVal>();
SValBuilder &svalBuilder = C.getSValBuilder();
- DefinedOrUnknownSVal PtrEQ =
- svalBuilder.evalEQ(State, arg0Val, svalBuilder.makeNull());
+ DefinedOrUnknownSVal PtrEQ = svalBuilder.evalEQ(
+ State, arg0Val, svalBuilder.makeNullWithType(arg0Expr->getType()));
// Get the size argument.
const Expr *Arg1 = CE->getArg(1);
@@ -2553,13 +2598,14 @@ MallocChecker::ReallocMemAux(CheckerContext &C, const CallEvent &Call,
SVal TotalSize = C.getSVal(Arg1);
if (SuffixWithN)
TotalSize = evalMulForBufferSize(C, Arg1, CE->getArg(2));
- if (!TotalSize.getAs<DefinedOrUnknownSVal>())
+ if (!isa<DefinedOrUnknownSVal>(TotalSize))
return nullptr;
// Compare the size argument to 0.
DefinedOrUnknownSVal SizeZero =
- svalBuilder.evalEQ(State, TotalSize.castAs<DefinedOrUnknownSVal>(),
- svalBuilder.makeIntValWithPtrWidth(0, false));
+ svalBuilder.evalEQ(State, TotalSize.castAs<DefinedOrUnknownSVal>(),
+ svalBuilder.makeIntValWithWidth(
+ svalBuilder.getContext().getSizeType(), 0));
ProgramStateRef StatePtrIsNull, StatePtrNotNull;
std::tie(StatePtrIsNull, StatePtrNotNull) = State->assume(PtrEQ);
@@ -2704,7 +2750,7 @@ void MallocChecker::HandleLeak(SymbolRef Sym, ExplodedNode *N,
Optional<MallocChecker::CheckKind>
CheckKind = getCheckIfTracked(Family, true);
- if (!CheckKind.hasValue())
+ if (!CheckKind)
return;
assert(N);
@@ -2748,7 +2794,7 @@ void MallocChecker::HandleLeak(SymbolRef Sym, ExplodedNode *N,
R->markInteresting(Sym);
R->addVisitor<MallocBugVisitor>(Sym, true);
if (ShouldRegisterNoOwnershipChangeVisitor)
- R->addVisitor<NoOwnershipChangeVisitor>(Sym);
+ R->addVisitor<NoOwnershipChangeVisitor>(Sym, this);
C.emitReport(std::move(R));
}
@@ -2862,7 +2908,7 @@ void MallocChecker::checkPreCall(const CallEvent &Call,
// Check arguments for being used after free.
for (unsigned I = 0, E = Call.getNumArgs(); I != E; ++I) {
SVal ArgSVal = Call.getArgSVal(I);
- if (ArgSVal.getAs<Loc>()) {
+ if (isa<Loc>(ArgSVal)) {
SymbolRef Sym = ArgSVal.getAsSymbol();
if (!Sym)
continue;
@@ -3394,7 +3440,7 @@ PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N,
allocation_state::getContainerObjRegion(statePrev, Sym);
const auto *TypedRegion = cast<TypedValueRegion>(ObjRegion);
QualType ObjTy = TypedRegion->getValueType();
- OS << "Inner buffer of '" << ObjTy.getAsString() << "' ";
+ OS << "Inner buffer of '" << ObjTy << "' ";
if (N->getLocation().getKind() == ProgramPoint::PostImplicitCallKind) {
OS << "deallocated by call to destructor";
@@ -3525,13 +3571,13 @@ void MallocChecker::printState(raw_ostream &Out, ProgramStateRef State,
const RefState *RefS = State->get<RegionState>(I.getKey());
AllocationFamily Family = RefS->getAllocationFamily();
Optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(Family);
- if (!CheckKind.hasValue())
- CheckKind = getCheckIfTracked(Family, true);
+ if (!CheckKind)
+ CheckKind = getCheckIfTracked(Family, true);
I.getKey()->dumpToStream(Out);
Out << " : ";
I.getData().dump(Out);
- if (CheckKind.hasValue())
+ if (CheckKind)
Out << " (" << CheckNames[*CheckKind].getName() << ")";
Out << NL;
}