1//=======- NoDeleteChecker.cpp -----------------------------------*- C++ -*-==//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "DiagOutputUtils.h"
10#include "PtrTypesSemantics.h"
11#include "clang/AST/CXXInheritance.h"
12#include "clang/AST/Decl.h"
13#include "clang/AST/DeclCXX.h"
14#include "clang/AST/DynamicRecursiveASTVisitor.h"
15#include "clang/AST/QualTypeNames.h"
16#include "clang/Analysis/DomainSpecific/CocoaConventions.h"
17#include "clang/Basic/SourceLocation.h"
18#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
19#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
20#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
21#include "clang/StaticAnalyzer/Core/Checker.h"
22
23using namespace clang;
24using namespace ento;
25
26namespace {
27
28class NoDeleteChecker : public Checker<check::ASTDecl<TranslationUnitDecl>> {
29 BugType Bug;
30 mutable BugReporter *BR = nullptr;
31 mutable TrivialFunctionAnalysis TFA;
32
33public:
34 NoDeleteChecker()
35 : Bug(this,
36 "Incorrect [[clang::annotate_type(\"webkit.nodelete\")]] "
37 "annotation",
38 "WebKit coding guidelines") {}
39
40 void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR,
41 BugReporter &BRArg) const {
42 BR = &BRArg;
43
44 // The calls to checkAST* from AnalysisConsumer don't
45 // visit template instantiations or lambda classes. We
46 // want to visit those, so we make our own visitor.
47 struct LocalVisitor final : public ConstDynamicRecursiveASTVisitor {
48 const NoDeleteChecker *Checker;
49 Decl *DeclWithIssue{nullptr};
50
51 explicit LocalVisitor(const NoDeleteChecker *Checker) : Checker(Checker) {
52 assert(Checker);
53 ShouldVisitTemplateInstantiations = true;
54 ShouldWalkTypesOfTypeLocs = true;
55 ShouldVisitImplicitCode = false;
56 ShouldVisitLambdaBody = true;
57 }
58
59 bool VisitFunctionDecl(const FunctionDecl *FD) override {
60 Checker->visitFunctionDecl(FD);
61 return true;
62 }
63 };
64
65 LocalVisitor visitor(this);
66 visitor.TraverseDecl(D: const_cast<TranslationUnitDecl *>(TUD));
67 }
68
69 void visitFunctionDecl(const FunctionDecl *FD) const {
70 if (!FD->doesThisDeclarationHaveABody() || FD->isDependentContext())
71 return;
72
73 if (!isNoDeleteFunction(F: FD))
74 return;
75
76 auto Body = FD->getBody();
77 if (!Body)
78 return;
79
80 NamedDecl *ParamDecl = nullptr;
81 for (auto *D : FD->parameters()) {
82 if (!TFA.hasTrivialDtor(VD: D)) {
83 ParamDecl = D;
84 break;
85 }
86 }
87
88 const FieldDecl *Field = nullptr;
89 const Stmt *OffendingInit = nullptr;
90 bool IsCtor = false;
91 bool IsDtor = false;
92 if (auto *Ctor = dyn_cast<CXXConstructorDecl>(Val: FD)) {
93 IsCtor = true;
94 Field = TFA.fieldWithNonTrivialCtor(RD: Ctor->getParent());
95 if (!Field) {
96 for (auto *CtorInit : Ctor->inits()) {
97 auto *Init = CtorInit->getInit();
98 if (!TFA.isTrivial(S: Init)) {
99 OffendingInit = Init;
100 break;
101 }
102 }
103 }
104 } else if (auto *Dtor = dyn_cast<CXXDestructorDecl>(Val: FD)) {
105 IsDtor = true;
106 Field = TFA.fieldWithNonTrivialDtor(RD: Dtor->getParent());
107 }
108
109 if (!ParamDecl && !Field && !OffendingInit && TFA.isTrivial(S: Body))
110 return;
111
112 SmallString<100> Buf;
113 llvm::raw_svector_ostream Os(Buf);
114
115 if (IsCtor)
116 Os << "A constructor ";
117 else if (IsDtor)
118 Os << "A destructor ";
119 else
120 Os << "A function ";
121 printQuotedName(Os, D: FD);
122 // FIXME: Update this to say clang::annotate("webkit.nodelete").
123 Os << " has [[clang::annotate_type(\"webkit.nodelete\")]] but it ";
124 if (IsCtor && Field)
125 Os << "constructs ";
126 else if (IsDtor && Field)
127 Os << "destructs ";
128 else
129 Os << "contains ";
130 SourceLocation SrcLocToReport;
131 SourceRange Range;
132 NonTrivialityReason Reason;
133 if (ParamDecl) {
134 Os << "a parameter ";
135 printQuotedName(Os, D: ParamDecl);
136 Os << " which could destruct an object.";
137 SrcLocToReport = FD->getBeginLoc();
138 Range = ParamDecl->getSourceRange();
139 } else if (Field && !OffendingInit) {
140 Os << "a member variable ";
141 printQuotedName(Os, D: Field);
142 Os << " that could destruct an object.";
143 SrcLocToReport = FD->getBeginLoc();
144 Range = Field->getSourceRange();
145 } else {
146 Reason = TrivialFunctionAnalysis::computeReason(
147 S: OffendingInit ? OffendingInit : Body);
148 Os << "code that could destruct an object.";
149 const Stmt *Offender = Reason.OffendingStmt;
150 SrcLocToReport = Offender ? Offender->getBeginLoc() : FD->getBeginLoc();
151 Range = Offender ? Offender->getSourceRange() : FD->getSourceRange();
152 }
153
154 PathDiagnosticLocation BSLoc(SrcLocToReport, BR->getSourceManager());
155 auto Report = std::make_unique<BasicBugReport>(args: Bug, args: Os.str(), args&: BSLoc);
156 Report->addRange(R: Range);
157 Report->setDeclWithIssue(FD);
158 addCallStackNotes(Report&: *Report, Reason);
159 BR->emitReport(R: std::move(Report));
160 }
161
162 static const FunctionDecl *getDirectCallee(const Stmt *S) {
163 if (const auto *CE = dyn_cast_or_null<CallExpr>(Val: S))
164 return CE->getDirectCallee();
165 if (const auto *CE = dyn_cast_or_null<CXXConstructExpr>(Val: S))
166 return CE->getConstructor();
167 return nullptr;
168 }
169
170 // The offending statement is often just the nearest call to a function that
171 // is itself unsafe several levels down. Walk the whole chain of calls, one
172 // note per function, down to the code that destructs an object or the
173 // function without a visible definition, since that is where the fix belongs.
174 void addCallStackNotes(BasicBugReport &Report,
175 const NonTrivialityReason &Reason) const {
176 ArrayRef<NonTrivialityReason::Frame> CallStack = Reason.CallStack;
177 if (CallStack.empty())
178 return;
179
180 // Nothing to add when the offending statement is the call to a function
181 // that is opaque or rejected outright; a note would only point back at its
182 // declaration, which the primary diagnostic already names.
183 const auto &First = CallStack.front();
184 const FunctionDecl *Callee = getDirectCallee(S: Reason.OffendingStmt);
185 if (CallStack.size() == 1 && !First.OffendingStmt && Callee &&
186 Callee->getCanonicalDecl() == First.Callee->getCanonicalDecl())
187 return;
188
189 for (size_t I = 0; I < CallStack.size(); ++I) {
190 const FunctionDecl *Fn = CallStack[I].Callee;
191 const Stmt *Offender = CallStack[I].OffendingStmt;
192 // Implicit special members have nothing worth pointing at.
193 if (!Offender && !Fn->getLocation().isValid())
194 continue;
195
196 SmallString<100> Buf;
197 llvm::raw_svector_ostream Os(Buf);
198 // Each note sits on the line it describes, which already spells out the
199 // enclosing function and the callee, so name only the callee: naming
200 // both would repeat every function in the chain twice.
201 if (I + 1 < CallStack.size()) {
202 Os << "Calling ";
203 printQuotedName(Os, D: CallStack[I + 1].Callee);
204 } else if (Fn->doesThisDeclarationHaveABody()) {
205 Os << "Could destruct an object";
206 } else {
207 printQuotedName(Os, D: Fn);
208 Os << " has no visible definition here, so it is assumed to destruct "
209 "an object. Annotate it with "
210 "[[clang::annotate_type(\"webkit.nodelete\")]] if it does not.";
211 }
212
213 SourceLocation Loc =
214 Offender ? Offender->getBeginLoc() : Fn->getLocation();
215 SourceRange Range =
216 Offender ? Offender->getSourceRange() : Fn->getSourceRange();
217 Report.addNote(
218 Msg: Os.str(), Pos: PathDiagnosticLocation(Loc, BR->getSourceManager()), Ranges: Range);
219 }
220 }
221};
222
223} // namespace
224
225void ento::registerNoDeleteChecker(CheckerManager &Mgr) {
226 Mgr.registerChecker<NoDeleteChecker>();
227}
228
229bool ento::shouldRegisterNoDeleteChecker(const CheckerManager &) {
230 return true;
231}
232