Skip to content

Commit 830146f

Browse files
committed
[alpha.webkit.ForwardDeclChecker] Add a new WebKit checker for forward declarations (llvm#130554)
Add a new static analyzer which emits warnings for function call arguments, local variables, and member variables that are only forward declared. These forward declaration prevents other WebKit checkers from checking the safety of code.
1 parent d371a2c commit 830146f

File tree

7 files changed

+503
-1
lines changed

7 files changed

+503
-1
lines changed

clang/docs/analyzer/checkers.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3433,6 +3433,24 @@ Check for non-determinism caused by sorting of pointers.
34333433
alpha.WebKit
34343434
^^^^^^^^^^^^
34353435
3436+
alpha.webkit.ForwardDeclChecker
3437+
"""""""""""""""""""""""""""""""
3438+
Check for local variables, member variables, and function arguments that are forward declared.
3439+
3440+
.. code-block:: cpp
3441+
3442+
struct Obj;
3443+
Obj* provide();
3444+
3445+
struct Foo {
3446+
Obj* ptr; // warn
3447+
};
3448+
3449+
void foo() {
3450+
Obj* obj = provide(); // warn
3451+
consume(obj); // warn
3452+
}
3453+
34363454
.. _alpha-webkit-NoUncheckedPtrMemberChecker:
34373455
34383456
alpha.webkit.MemoryUnsafeCastChecker

clang/include/clang/StaticAnalyzer/Checkers/Checkers.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1787,6 +1787,10 @@ def UncountedLambdaCapturesChecker : Checker<"UncountedLambdaCapturesChecker">,
17871787

17881788
let ParentPackage = WebKitAlpha in {
17891789

1790+
def ForwardDeclChecker : Checker<"ForwardDeclChecker">,
1791+
HelpText<"Check for forward declared local or member variables and function arguments">,
1792+
Documentation<HasDocumentation>;
1793+
17901794
def MemoryUnsafeCastChecker : Checker<"MemoryUnsafeCastChecker">,
17911795
HelpText<"Check for memory unsafe casts from base type to derived type.">,
17921796
Documentation<HasDocumentation>;

clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ add_clang_library(clangStaticAnalyzerCheckers
134134
ValistChecker.cpp
135135
VirtualCallChecker.cpp
136136
WebKit/ASTUtils.cpp
137+
WebKit/ForwardDeclChecker.cpp
137138
WebKit/MemoryUnsafeCastChecker.cpp
138139
WebKit/PtrTypesSemantics.cpp
139140
WebKit/RefCntblBaseVirtualDtorChecker.cpp
Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
//=======- ForwardDeclChecker.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 "ASTUtils.h"
10+
#include "DiagOutputUtils.h"
11+
#include "PtrTypesSemantics.h"
12+
#include "clang/AST/CXXInheritance.h"
13+
#include "clang/AST/Decl.h"
14+
#include "clang/AST/DeclCXX.h"
15+
#include "clang/AST/RecursiveASTVisitor.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+
#include "llvm/ADT/DenseSet.h"
23+
#include "llvm/Support/SaveAndRestore.h"
24+
#include <optional>
25+
26+
using namespace clang;
27+
using namespace ento;
28+
29+
namespace {
30+
31+
class ForwardDeclChecker : public Checker<check::ASTDecl<TranslationUnitDecl>> {
32+
BugType Bug;
33+
mutable BugReporter *BR;
34+
mutable RetainTypeChecker RTC;
35+
mutable llvm::DenseSet<const Type *> SystemTypes;
36+
37+
public:
38+
ForwardDeclChecker()
39+
: Bug(this, "Forward declared member or local variable or parameter",
40+
"WebKit coding guidelines") {}
41+
42+
void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR,
43+
BugReporter &BRArg) const {
44+
BR = &BRArg;
45+
46+
// The calls to checkAST* from AnalysisConsumer don't
47+
// visit template instantiations or lambda classes. We
48+
// want to visit those, so we make our own RecursiveASTVisitor.
49+
struct LocalVisitor : public RecursiveASTVisitor<LocalVisitor> {
50+
using Base = RecursiveASTVisitor<LocalVisitor>;
51+
52+
const ForwardDeclChecker *Checker;
53+
Decl *DeclWithIssue{nullptr};
54+
55+
explicit LocalVisitor(const ForwardDeclChecker *Checker)
56+
: Checker(Checker) {
57+
assert(Checker);
58+
}
59+
60+
bool shouldVisitTemplateInstantiations() const { return true; }
61+
bool shouldVisitImplicitCode() const { return false; }
62+
63+
bool VisitTypedefDecl(TypedefDecl *TD) {
64+
Checker->visitTypedef(TD);
65+
return true;
66+
}
67+
68+
bool VisitRecordDecl(const RecordDecl *RD) {
69+
Checker->visitRecordDecl(RD, DeclWithIssue);
70+
return true;
71+
}
72+
73+
bool TraverseDecl(Decl *D) {
74+
llvm::SaveAndRestore SavedDecl(DeclWithIssue);
75+
if (D && (isa<FunctionDecl>(D) || isa<ObjCMethodDecl>(D)))
76+
DeclWithIssue = D;
77+
return Base::TraverseDecl(D);
78+
}
79+
80+
bool VisitVarDecl(VarDecl *V) {
81+
if (V->isLocalVarDecl())
82+
Checker->visitVarDecl(V, DeclWithIssue);
83+
return true;
84+
}
85+
86+
bool VisitCallExpr(const CallExpr *CE) {
87+
Checker->visitCallExpr(CE, DeclWithIssue);
88+
return true;
89+
}
90+
91+
bool VisitCXXConstructExpr(const CXXConstructExpr *CE) {
92+
Checker->visitConstructExpr(CE, DeclWithIssue);
93+
return true;
94+
}
95+
96+
bool VisitObjCMessageExpr(const ObjCMessageExpr *ObjCMsgExpr) {
97+
Checker->visitObjCMessageExpr(ObjCMsgExpr, DeclWithIssue);
98+
return true;
99+
}
100+
};
101+
102+
LocalVisitor visitor(this);
103+
RTC.visitTranslationUnitDecl(TUD);
104+
visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD));
105+
}
106+
107+
void visitTypedef(const TypedefDecl *TD) const {
108+
RTC.visitTypedef(TD);
109+
auto QT = TD->getUnderlyingType().getCanonicalType();
110+
if (BR->getSourceManager().isInSystemHeader(TD->getBeginLoc())) {
111+
if (auto *Type = QT.getTypePtrOrNull(); Type && QT->isPointerType())
112+
SystemTypes.insert(Type);
113+
}
114+
}
115+
116+
bool isUnknownType(QualType QT) const {
117+
auto *Type = QT.getTypePtrOrNull();
118+
if (!Type)
119+
return false;
120+
auto *CanonicalType = QT.getCanonicalType().getTypePtrOrNull();
121+
auto PointeeQT = Type->getPointeeType();
122+
auto *PointeeType = PointeeQT.getTypePtrOrNull();
123+
if (!PointeeType)
124+
return false;
125+
auto *R = PointeeType->getAsCXXRecordDecl();
126+
if (!R) // Forward declaration of a Objective-C interface is safe.
127+
return false;
128+
auto Name = R->getName();
129+
return !R->hasDefinition() && !RTC.isUnretained(QT) &&
130+
!SystemTypes.contains(CanonicalType) &&
131+
!Name.starts_with("Opaque") && Name != "_NSZone";
132+
}
133+
134+
void visitRecordDecl(const RecordDecl *RD, const Decl *DeclWithIssue) const {
135+
if (!RD->isThisDeclarationADefinition())
136+
return;
137+
138+
if (RD->isImplicit() || RD->isLambda())
139+
return;
140+
141+
const auto RDLocation = RD->getLocation();
142+
if (!RDLocation.isValid())
143+
return;
144+
145+
const auto Kind = RD->getTagKind();
146+
if (Kind != TagTypeKind::Struct && Kind != TagTypeKind::Class)
147+
return;
148+
149+
if (BR->getSourceManager().isInSystemHeader(RDLocation))
150+
return;
151+
152+
// Ref-counted smartpointers actually have raw-pointer to uncounted type as
153+
// a member but we trust them to handle it correctly.
154+
auto R = llvm::dyn_cast_or_null<CXXRecordDecl>(RD);
155+
if (!R || isRefCounted(R) || isCheckedPtr(R) || isRetainPtr(R))
156+
return;
157+
158+
for (auto *Member : RD->fields()) {
159+
auto QT = Member->getType();
160+
if (isUnknownType(QT)) {
161+
SmallString<100> Buf;
162+
llvm::raw_svector_ostream Os(Buf);
163+
164+
const std::string TypeName = QT.getAsString();
165+
Os << "Member variable ";
166+
printQuotedName(Os, Member);
167+
Os << " uses a forward declared type '" << TypeName << "'";
168+
169+
const SourceLocation SrcLocToReport = Member->getBeginLoc();
170+
PathDiagnosticLocation BSLoc(SrcLocToReport, BR->getSourceManager());
171+
auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
172+
Report->addRange(Member->getSourceRange());
173+
Report->setDeclWithIssue(DeclWithIssue);
174+
BR->emitReport(std::move(Report));
175+
}
176+
}
177+
}
178+
179+
void visitVarDecl(const VarDecl *V, const Decl *DeclWithIssue) const {
180+
if (BR->getSourceManager().isInSystemHeader(V->getBeginLoc()))
181+
return;
182+
183+
auto QT = V->getType();
184+
if (!isUnknownType(QT))
185+
return;
186+
187+
SmallString<100> Buf;
188+
llvm::raw_svector_ostream Os(Buf);
189+
Os << "Local variable ";
190+
printQuotedQualifiedName(Os, V);
191+
192+
reportBug(V->getBeginLoc(), V->getSourceRange(), DeclWithIssue, Os.str(),
193+
QT);
194+
}
195+
196+
void visitCallExpr(const CallExpr *CE, const Decl *DeclWithIssue) const {
197+
if (BR->getSourceManager().isInSystemHeader(CE->getExprLoc()))
198+
return;
199+
200+
if (auto *F = CE->getDirectCallee()) {
201+
// Skip the first argument for overloaded member operators (e. g. lambda
202+
// or std::function call operator).
203+
unsigned ArgIdx =
204+
isa<CXXOperatorCallExpr>(CE) && isa_and_nonnull<CXXMethodDecl>(F);
205+
206+
for (auto P = F->param_begin();
207+
P < F->param_end() && ArgIdx < CE->getNumArgs(); ++P, ++ArgIdx)
208+
visitCallArg(CE->getArg(ArgIdx), *P, DeclWithIssue);
209+
}
210+
}
211+
212+
void visitConstructExpr(const CXXConstructExpr *CE,
213+
const Decl *DeclWithIssue) const {
214+
if (BR->getSourceManager().isInSystemHeader(CE->getExprLoc()))
215+
return;
216+
217+
if (auto *F = CE->getConstructor()) {
218+
// Skip the first argument for overloaded member operators (e. g. lambda
219+
// or std::function call operator).
220+
unsigned ArgIdx =
221+
isa<CXXOperatorCallExpr>(CE) && isa_and_nonnull<CXXMethodDecl>(F);
222+
223+
for (auto P = F->param_begin();
224+
P < F->param_end() && ArgIdx < CE->getNumArgs(); ++P, ++ArgIdx)
225+
visitCallArg(CE->getArg(ArgIdx), *P, DeclWithIssue);
226+
}
227+
}
228+
229+
void visitObjCMessageExpr(const ObjCMessageExpr *E,
230+
const Decl *DeclWithIssue) const {
231+
if (BR->getSourceManager().isInSystemHeader(E->getExprLoc()))
232+
return;
233+
234+
if (auto *Receiver = E->getInstanceReceiver()->IgnoreParenCasts()) {
235+
if (isUnknownType(E->getReceiverType()))
236+
reportUnknownRecieverType(Receiver, DeclWithIssue);
237+
}
238+
239+
auto *MethodDecl = E->getMethodDecl();
240+
if (!MethodDecl)
241+
return;
242+
243+
auto ArgCount = E->getNumArgs();
244+
for (unsigned i = 0; i < ArgCount && i < MethodDecl->param_size(); ++i)
245+
visitCallArg(E->getArg(i), MethodDecl->getParamDecl(i), DeclWithIssue);
246+
}
247+
248+
void visitCallArg(const Expr *Arg, const ParmVarDecl *Param,
249+
const Decl *DeclWithIssue) const {
250+
auto *ArgExpr = Arg->IgnoreParenCasts();
251+
if (auto *InnerCE = dyn_cast<CallExpr>(Arg)) {
252+
auto *InnerCallee = InnerCE->getDirectCallee();
253+
if (InnerCallee && InnerCallee->isInStdNamespace() &&
254+
safeGetName(InnerCallee) == "move" && InnerCE->getNumArgs() == 1) {
255+
ArgExpr = InnerCE->getArg(0);
256+
if (ArgExpr)
257+
ArgExpr = ArgExpr->IgnoreParenCasts();
258+
}
259+
}
260+
if (isa<CXXNullPtrLiteralExpr>(ArgExpr) || isa<IntegerLiteral>(ArgExpr) ||
261+
isa<CXXDefaultArgExpr>(ArgExpr))
262+
return;
263+
if (auto *DRE = dyn_cast<DeclRefExpr>(ArgExpr)) {
264+
if (auto *ValDecl = DRE->getDecl()) {
265+
if (isa<ParmVarDecl>(ValDecl))
266+
return;
267+
}
268+
}
269+
270+
QualType ArgType = Param->getType();
271+
if (!isUnknownType(ArgType))
272+
return;
273+
274+
reportUnknownArgType(Arg, Param, DeclWithIssue);
275+
}
276+
277+
void reportUnknownArgType(const Expr *CA, const ParmVarDecl *Param,
278+
const Decl *DeclWithIssue) const {
279+
assert(CA);
280+
281+
SmallString<100> Buf;
282+
llvm::raw_svector_ostream Os(Buf);
283+
284+
const std::string paramName = safeGetName(Param);
285+
Os << "Call argument";
286+
if (!paramName.empty()) {
287+
Os << " for parameter ";
288+
printQuotedQualifiedName(Os, Param);
289+
}
290+
291+
reportBug(CA->getExprLoc(), CA->getSourceRange(), DeclWithIssue, Os.str(),
292+
Param->getType());
293+
}
294+
295+
void reportUnknownRecieverType(const Expr *Receiver,
296+
const Decl *DeclWithIssue) const {
297+
assert(Receiver);
298+
reportBug(Receiver->getExprLoc(), Receiver->getSourceRange(), DeclWithIssue,
299+
"Receiver", Receiver->getType());
300+
}
301+
302+
void reportBug(const SourceLocation &SrcLoc, const SourceRange &SrcRange,
303+
const Decl *DeclWithIssue, const StringRef &Description,
304+
QualType Type) const {
305+
SmallString<100> Buf;
306+
llvm::raw_svector_ostream Os(Buf);
307+
308+
const std::string TypeName = Type.getAsString();
309+
Os << Description << " uses a forward declared type '" << TypeName << "'";
310+
311+
PathDiagnosticLocation BSLoc(SrcLoc, BR->getSourceManager());
312+
auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
313+
Report->addRange(SrcRange);
314+
Report->setDeclWithIssue(DeclWithIssue);
315+
BR->emitReport(std::move(Report));
316+
}
317+
};
318+
319+
} // namespace
320+
321+
void ento::registerForwardDeclChecker(CheckerManager &Mgr) {
322+
Mgr.registerChecker<ForwardDeclChecker>();
323+
}
324+
325+
bool ento::shouldRegisterForwardDeclChecker(const CheckerManager &) {
326+
return true;
327+
}

0 commit comments

Comments
 (0)