1//=======- MemoryUnsafeCastChecker.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// This file defines MemoryUnsafeCast checker, which checks for casts from a
10// base type to a derived type.
11//===----------------------------------------------------------------------===//
12
13#include "clang/ASTMatchers/ASTMatchFinder.h"
14#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
15#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
16#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
17#include "clang/StaticAnalyzer/Core/Checker.h"
18#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
19
20using namespace clang;
21using namespace ento;
22using namespace ast_matchers;
23
24namespace {
25static constexpr const char *const BaseNode = "BaseNode";
26static constexpr const char *const DerivedNode = "DerivedNode";
27static constexpr const char *const FromCastNode = "FromCast";
28static constexpr const char *const ToCastNode = "ToCast";
29static constexpr const char *const WarnRecordDecl = "WarnRecordDecl";
30
31class MemoryUnsafeCastChecker : public Checker<check::ASTCodeBody> {
32 BugType BT{this, "Unsafe cast", "WebKit coding guidelines"};
33
34public:
35 void checkASTCodeBody(const Decl *D, AnalysisManager &Mgr,
36 BugReporter &BR) const;
37};
38} // end namespace
39
40static void emitDiagnostics(const BoundNodes &Nodes, BugReporter &BR,
41 AnalysisDeclContext *ADC,
42 const MemoryUnsafeCastChecker *Checker,
43 const BugType &BT) {
44 const auto *CE = Nodes.getNodeAs<CastExpr>(ID: WarnRecordDecl);
45 const NamedDecl *Base = Nodes.getNodeAs<NamedDecl>(ID: BaseNode);
46 const NamedDecl *Derived = Nodes.getNodeAs<NamedDecl>(ID: DerivedNode);
47 assert(CE && Base && Derived);
48
49 std::string Diagnostics;
50 llvm::raw_string_ostream OS(Diagnostics);
51 OS << "Unsafe cast from base type '" << Base->getNameAsString()
52 << "' to derived type '" << Derived->getNameAsString() << "'";
53 PathDiagnosticLocation BSLoc(CE->getSourceRange().getBegin(),
54 BR.getSourceManager());
55 auto Report = std::make_unique<BasicBugReport>(args: BT, args&: OS.str(), args&: BSLoc);
56 Report->addRange(R: CE->getSourceRange());
57 Report->setDeclWithIssue(ADC->getDecl());
58 BR.emitReport(R: std::move(Report));
59}
60
61static void emitDiagnosticsUnrelated(const BoundNodes &Nodes, BugReporter &BR,
62 AnalysisDeclContext *ADC,
63 const MemoryUnsafeCastChecker *Checker,
64 const BugType &BT) {
65 const auto *CE = Nodes.getNodeAs<CastExpr>(ID: WarnRecordDecl);
66 const NamedDecl *FromCast = Nodes.getNodeAs<NamedDecl>(ID: FromCastNode);
67 const NamedDecl *ToCast = Nodes.getNodeAs<NamedDecl>(ID: ToCastNode);
68 assert(CE && FromCast && ToCast);
69
70 std::string Diagnostics;
71 llvm::raw_string_ostream OS(Diagnostics);
72 OS << "Unsafe cast from type '" << FromCast->getNameAsString()
73 << "' to an unrelated type '" << ToCast->getNameAsString() << "'";
74 PathDiagnosticLocation BSLoc(CE->getSourceRange().getBegin(),
75 BR.getSourceManager());
76 auto Report = std::make_unique<BasicBugReport>(args: BT, args&: OS.str(), args&: BSLoc);
77 Report->addRange(R: CE->getSourceRange());
78 Report->setDeclWithIssue(ADC->getDecl());
79 BR.emitReport(R: std::move(Report));
80}
81
82static void emitDiagnosticsIdArg(const BoundNodes &Nodes, BugReporter &BR,
83 AnalysisDeclContext *ADC,
84 const MemoryUnsafeCastChecker *Checker,
85 const BugType &BT) {
86 const auto *CE = Nodes.getNodeAs<CastExpr>(ID: WarnRecordDecl);
87 const NamedDecl *Derived = Nodes.getNodeAs<NamedDecl>(ID: DerivedNode);
88 assert(CE && Derived);
89
90 std::string Diagnostics;
91 llvm::raw_string_ostream OS(Diagnostics);
92 OS << "Unsafe implicit cast from 'id' to specific type '"
93 << Derived->getNameAsString() << "'";
94 PathDiagnosticLocation BSLoc(CE->getSourceRange().getBegin(),
95 BR.getSourceManager());
96 auto Report = std::make_unique<BasicBugReport>(args: BT, args&: OS.str(), args&: BSLoc);
97 Report->addRange(R: CE->getSourceRange());
98 Report->setDeclWithIssue(ADC->getDecl());
99 BR.emitReport(R: std::move(Report));
100}
101
102namespace {
103using BoundNodesMap = ::clang::ast_matchers::internal::BoundNodesMap;
104
105// Matches the plain `id` type.
106AST_MATCHER(QualType, isObjCIdType) { return Node->isObjCIdType(); }
107
108// Like equalsBoundNode, but compares canonical declarations so that different
109// redeclarations of the same class (e.g. a forward declaration and the
110// definition) are treated as the same declaration.
111AST_MATCHER_P(Decl, hasSameCanonicalDeclAsBoundNode, std::string, ID) {
112 return Builder->removeBindings(Predicate: [&](const BoundNodesMap &Nodes) {
113 const auto *Bound = Nodes.getNodeAs<Decl>(ID);
114 return !Bound || Bound->getCanonicalDecl() != Node.getCanonicalDecl();
115 });
116}
117
118// Matches a cast whose previously-bound BaseID node is a class template
119// specialization and whose previously-bound DerivedID node is one of that
120// specialization's type template arguments, i.e. the CRTP pattern
121// `class Derived : Base<Derived>`.
122AST_MATCHER_P2(Expr, isCRTPCast, std::string, BaseID, std::string, DerivedID) {
123 return Builder->removeBindings(Predicate: [this](const BoundNodesMap &Nodes) {
124 const auto *Base = Nodes.getNodeAs<CXXRecordDecl>(ID: this->BaseID);
125 const auto *Derived = Nodes.getNodeAs<CXXRecordDecl>(ID: this->DerivedID);
126 const auto *CTSD =
127 Base ? dyn_cast<ClassTemplateSpecializationDecl>(Val: Base) : nullptr;
128 if (!CTSD || !Derived)
129 return true;
130 for (const TemplateArgument &Arg : CTSD->getTemplateArgs().asArray()) {
131 if (Arg.getKind() != TemplateArgument::Type)
132 continue;
133 QualType ArgType = Arg.getAsType();
134 if (ArgType.isNull())
135 continue;
136 const auto *ArgDecl = ArgType->getAsCXXRecordDecl();
137 if (ArgDecl && ArgDecl->getCanonicalDecl() == Derived->getCanonicalDecl())
138 return false;
139 }
140 return true;
141 });
142}
143} // end anonymous namespace
144
145static decltype(auto) hasTypePointingTo(DeclarationMatcher DeclM) {
146 return hasType(InnerMatcher: pointerType(pointee(hasDeclaration(InnerMatcher: DeclM))));
147}
148
149// Matches `this` or `*this`, but not member accesses like `this->m_field`.
150static decltype(auto) isThisOrDerefThis() {
151 return ignoringParenImpCasts(InnerMatcher: anyOf(
152 cxxThisExpr(),
153 unaryOperator(hasOperatorName(Name: "*"),
154 hasUnaryOperand(InnerMatcher: ignoringParenImpCasts(InnerMatcher: cxxThisExpr())))));
155}
156
157void MemoryUnsafeCastChecker::checkASTCodeBody(const Decl *D,
158 AnalysisManager &AM,
159 BugReporter &BR) const {
160
161 AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D);
162
163 // Match downcasts from base type to derived type and warn
164 auto MatchExprPtr = allOf(
165 hasSourceExpression(InnerMatcher: hasTypePointingTo(DeclM: cxxRecordDecl().bind(ID: BaseNode))),
166 hasTypePointingTo(
167 DeclM: cxxRecordDecl(
168 isDerivedFrom(Base: hasSameCanonicalDeclAsBoundNode(ID: BaseNode)))
169 .bind(ID: DerivedNode)),
170 unless(anyOf(hasTypePointingTo(DeclM: templateTypeParmDecl()),
171 allOf(hasSourceExpression(InnerMatcher: cxxThisExpr()),
172 isCRTPCast(BaseID: BaseNode, DerivedID: DerivedNode)))));
173 auto MatchExprPtrObjC = allOf(
174 hasSourceExpression(InnerMatcher: ignoringImpCasts(InnerMatcher: hasType(InnerMatcher: objcObjectPointerType(
175 pointee(hasDeclaration(InnerMatcher: objcInterfaceDecl().bind(ID: BaseNode))))))),
176 ignoringImpCasts(InnerMatcher: hasType(InnerMatcher: objcObjectPointerType(pointee(hasDeclaration(
177 InnerMatcher: objcInterfaceDecl(
178 isDerivedFrom(Base: hasSameCanonicalDeclAsBoundNode(ID: BaseNode)))
179 .bind(ID: DerivedNode)))))));
180 auto MatchExprRefTypeDef = allOf(
181 hasSourceExpression(InnerMatcher: hasType(InnerMatcher: hasUnqualifiedDesugaredType(
182 InnerMatcher: recordType(hasDeclaration(InnerMatcher: decl(cxxRecordDecl().bind(ID: BaseNode))))))),
183 hasType(InnerMatcher: hasUnqualifiedDesugaredType(InnerMatcher: recordType(hasDeclaration(
184 InnerMatcher: decl(cxxRecordDecl(
185 isDerivedFrom(Base: hasSameCanonicalDeclAsBoundNode(ID: BaseNode)))
186 .bind(ID: DerivedNode)))))),
187 unless(anyOf(hasType(InnerMatcher: templateTypeParmDecl()),
188 allOf(hasSourceExpression(InnerMatcher: isThisOrDerefThis()),
189 isCRTPCast(BaseID: BaseNode, DerivedID: DerivedNode)))));
190 auto MatchExprPtrVoidCast =
191 allOf(anyOf(hasSourceExpression(InnerMatcher: explicitCastExpr(
192 hasType(InnerMatcher: pointerType(pointee(voidType()))),
193 hasSourceExpression(InnerMatcher: ignoringImpCasts(
194 InnerMatcher: hasTypePointingTo(DeclM: cxxRecordDecl().bind(ID: BaseNode)))))),
195 hasSourceExpression(InnerMatcher: callExpr(
196 hasType(InnerMatcher: pointerType(pointee(voidType()))),
197 hasAnyArgument(InnerMatcher: ignoringImpCasts(InnerMatcher: hasTypePointingTo(
198 DeclM: cxxRecordDecl().bind(ID: BaseNode))))))),
199 hasTypePointingTo(
200 DeclM: cxxRecordDecl(
201 isDerivedFrom(Base: hasSameCanonicalDeclAsBoundNode(ID: BaseNode)))
202 .bind(ID: DerivedNode)));
203
204 auto ExplicitCast =
205 explicitCastExpr(anyOf(MatchExprPtr, MatchExprRefTypeDef,
206 MatchExprPtrObjC, MatchExprPtrVoidCast))
207 .bind(ID: WarnRecordDecl);
208 auto Cast = stmt(ExplicitCast);
209
210 auto Matches =
211 match(Matcher: stmt(forEachDescendant(Cast)), Node: *D->getBody(), Context&: AM.getASTContext());
212 for (BoundNodes Match : Matches)
213 emitDiagnostics(Nodes: Match, BR, ADC, Checker: this, BT);
214
215 // Match calls returning derived type where an argument is a void pointer.
216 auto VoidPtrCast =
217 castExpr(hasType(InnerMatcher: pointerType(pointee(voidType()))),
218 hasSourceExpression(InnerMatcher: ignoringImpCasts(
219 InnerMatcher: hasTypePointingTo(DeclM: cxxRecordDecl().bind(ID: BaseNode)))))
220 .bind(ID: WarnRecordDecl);
221 auto MatchCallPtrVoidArgCast = callExpr(
222 hasAnyArgument(InnerMatcher: anyOf(VoidPtrCast,
223 explicitCastExpr(hasSourceExpression(InnerMatcher: VoidPtrCast)))),
224 hasTypePointingTo(
225 DeclM: cxxRecordDecl(
226 isDerivedFrom(Base: hasSameCanonicalDeclAsBoundNode(ID: BaseNode)))
227 .bind(ID: DerivedNode)));
228 auto CallArgCast = stmt(MatchCallPtrVoidArgCast);
229 auto MatchesCallArgCast = match(Matcher: stmt(forEachDescendant(CallArgCast)),
230 Node: *D->getBody(), Context&: AM.getASTContext());
231 for (BoundNodes Match : MatchesCallArgCast)
232 emitDiagnostics(Nodes: Match, BR, ADC, Checker: this, BT);
233
234 // Match casts between unrelated types and warn
235 auto MatchExprPtrUnrelatedTypes = allOf(
236 hasSourceExpression(
237 InnerMatcher: hasTypePointingTo(DeclM: cxxRecordDecl().bind(ID: FromCastNode))),
238 hasTypePointingTo(DeclM: cxxRecordDecl().bind(ID: ToCastNode)),
239 unless(anyOf(hasTypePointingTo(DeclM: cxxRecordDecl(isSameOrDerivedFrom(
240 Base: hasSameCanonicalDeclAsBoundNode(ID: FromCastNode)))),
241 hasSourceExpression(
242 InnerMatcher: hasTypePointingTo(DeclM: cxxRecordDecl(isSameOrDerivedFrom(
243 Base: hasSameCanonicalDeclAsBoundNode(ID: ToCastNode))))))));
244 auto MatchExprPtrObjCUnrelatedTypes = allOf(
245 hasSourceExpression(InnerMatcher: ignoringImpCasts(InnerMatcher: hasType(InnerMatcher: objcObjectPointerType(
246 pointee(hasDeclaration(InnerMatcher: objcInterfaceDecl().bind(ID: FromCastNode))))))),
247 ignoringImpCasts(InnerMatcher: hasType(InnerMatcher: objcObjectPointerType(
248 pointee(hasDeclaration(InnerMatcher: objcInterfaceDecl().bind(ID: ToCastNode)))))),
249 unless(anyOf(
250 ignoringImpCasts(InnerMatcher: hasType(InnerMatcher: objcObjectPointerType(
251 pointee(hasDeclaration(InnerMatcher: objcInterfaceDecl(isSameOrDerivedFrom(
252 Base: hasSameCanonicalDeclAsBoundNode(ID: FromCastNode)))))))),
253 hasSourceExpression(InnerMatcher: ignoringImpCasts(InnerMatcher: hasType(InnerMatcher: objcObjectPointerType(
254 pointee(hasDeclaration(InnerMatcher: objcInterfaceDecl(isSameOrDerivedFrom(
255 Base: hasSameCanonicalDeclAsBoundNode(ID: ToCastNode))))))))))));
256 auto MatchExprRefTypeDefUnrelated = allOf(
257 hasSourceExpression(InnerMatcher: hasType(InnerMatcher: hasUnqualifiedDesugaredType(InnerMatcher: recordType(
258 hasDeclaration(InnerMatcher: decl(cxxRecordDecl().bind(ID: FromCastNode))))))),
259 hasType(InnerMatcher: hasUnqualifiedDesugaredType(
260 InnerMatcher: recordType(hasDeclaration(InnerMatcher: decl(cxxRecordDecl().bind(ID: ToCastNode)))))),
261 unless(anyOf(
262 hasType(InnerMatcher: hasUnqualifiedDesugaredType(
263 InnerMatcher: recordType(hasDeclaration(InnerMatcher: decl(cxxRecordDecl(isSameOrDerivedFrom(
264 Base: hasSameCanonicalDeclAsBoundNode(ID: FromCastNode)))))))),
265 hasSourceExpression(InnerMatcher: hasType(InnerMatcher: hasUnqualifiedDesugaredType(
266 InnerMatcher: recordType(hasDeclaration(InnerMatcher: decl(cxxRecordDecl(isSameOrDerivedFrom(
267 Base: hasSameCanonicalDeclAsBoundNode(ID: ToCastNode))))))))))));
268
269 auto ExplicitCastUnrelated =
270 explicitCastExpr(anyOf(MatchExprPtrUnrelatedTypes,
271 MatchExprPtrObjCUnrelatedTypes,
272 MatchExprRefTypeDefUnrelated))
273 .bind(ID: WarnRecordDecl);
274 auto CastUnrelated = stmt(ExplicitCastUnrelated);
275 auto MatchesUnrelatedTypes = match(Matcher: stmt(forEachDescendant(CastUnrelated)),
276 Node: *D->getBody(), Context&: AM.getASTContext());
277 for (BoundNodes Match : MatchesUnrelatedTypes)
278 emitDiagnosticsUnrelated(Nodes: Match, BR, ADC, Checker: this, BT);
279
280 // Match an `id`-typed argument implicitly converted to a specific
281 // Objective-C type at a call, message send, or constructor call, e.g.
282 // passing an `id` where an `NSString *` parameter is expected. Such
283 // conversions compile without a visible cast but throw at runtime if the
284 // object is not actually of that type.
285 auto CastArgFromIdToSpecificType =
286 implicitCastExpr(
287 hasCastKind(Kind: CK_BitCast),
288 hasSourceExpression(
289 InnerMatcher: ignoringParenImpCasts(InnerMatcher: hasType(InnerMatcher: qualType(isObjCIdType())))),
290 hasType(InnerMatcher: qualType(hasCanonicalType(InnerMatcher: objcObjectPointerType(pointee(
291 hasDeclaration(InnerMatcher: objcInterfaceDecl().bind(ID: DerivedNode))))))))
292 .bind(ID: WarnRecordDecl);
293 auto MatchCallArgFromId =
294 anyOf(callExpr(hasAnyArgument(InnerMatcher: CastArgFromIdToSpecificType)),
295 cxxConstructExpr(hasAnyArgument(InnerMatcher: CastArgFromIdToSpecificType)),
296 objcMessageExpr(hasAnyArgument(InnerMatcher: CastArgFromIdToSpecificType)));
297 auto MatchesCallArgFromId =
298 match(Matcher: stmt(forEachDescendant(stmt(MatchCallArgFromId))), Node: *D->getBody(),
299 Context&: AM.getASTContext());
300 for (BoundNodes Match : MatchesCallArgFromId)
301 emitDiagnosticsIdArg(Nodes: Match, BR, ADC, Checker: this, BT);
302}
303
304void ento::registerMemoryUnsafeCastChecker(CheckerManager &Mgr) {
305 Mgr.registerChecker<MemoryUnsafeCastChecker>();
306}
307
308bool ento::shouldRegisterMemoryUnsafeCastChecker(const CheckerManager &mgr) {
309 return true;
310}
311