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// Matches a cast whose previously-bound BaseID node is a class template
109// specialization and whose previously-bound DerivedID node is one of that
110// specialization's type template arguments, i.e. the CRTP pattern
111// `class Derived : Base<Derived>`.
112AST_MATCHER_P2(Expr, isCRTPCast, std::string, BaseID, std::string, DerivedID) {
113 return Builder->removeBindings(Predicate: [this](const BoundNodesMap &Nodes) {
114 const auto *Base = Nodes.getNodeAs<CXXRecordDecl>(ID: this->BaseID);
115 const auto *Derived = Nodes.getNodeAs<CXXRecordDecl>(ID: this->DerivedID);
116 const auto *CTSD =
117 Base ? dyn_cast<ClassTemplateSpecializationDecl>(Val: Base) : nullptr;
118 if (!CTSD || !Derived)
119 return true;
120 for (const TemplateArgument &Arg : CTSD->getTemplateArgs().asArray()) {
121 if (Arg.getKind() != TemplateArgument::Type)
122 continue;
123 QualType ArgType = Arg.getAsType();
124 if (!ArgType.isNull() && ArgType->getAsCXXRecordDecl() == Derived)
125 return false;
126 }
127 return true;
128 });
129}
130} // end anonymous namespace
131
132static decltype(auto) hasTypePointingTo(DeclarationMatcher DeclM) {
133 return hasType(InnerMatcher: pointerType(pointee(hasDeclaration(InnerMatcher: DeclM))));
134}
135
136// Matches `this` or `*this`, but not member accesses like `this->m_field`.
137static decltype(auto) isThisOrDerefThis() {
138 return ignoringParenImpCasts(InnerMatcher: anyOf(
139 cxxThisExpr(),
140 unaryOperator(hasOperatorName(Name: "*"),
141 hasUnaryOperand(InnerMatcher: ignoringParenImpCasts(InnerMatcher: cxxThisExpr())))));
142}
143
144void MemoryUnsafeCastChecker::checkASTCodeBody(const Decl *D,
145 AnalysisManager &AM,
146 BugReporter &BR) const {
147
148 AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D);
149
150 // Match downcasts from base type to derived type and warn
151 auto MatchExprPtr = allOf(
152 hasSourceExpression(InnerMatcher: hasTypePointingTo(DeclM: cxxRecordDecl().bind(ID: BaseNode))),
153 hasTypePointingTo(DeclM: cxxRecordDecl(isDerivedFrom(Base: equalsBoundNode(ID: BaseNode)))
154 .bind(ID: DerivedNode)),
155 unless(anyOf(hasTypePointingTo(DeclM: templateTypeParmDecl()),
156 allOf(hasSourceExpression(InnerMatcher: cxxThisExpr()),
157 isCRTPCast(BaseID: BaseNode, DerivedID: DerivedNode)))));
158 auto MatchExprPtrObjC = allOf(
159 hasSourceExpression(InnerMatcher: ignoringImpCasts(InnerMatcher: hasType(InnerMatcher: objcObjectPointerType(
160 pointee(hasDeclaration(InnerMatcher: objcInterfaceDecl().bind(ID: BaseNode))))))),
161 ignoringImpCasts(InnerMatcher: hasType(InnerMatcher: objcObjectPointerType(pointee(hasDeclaration(
162 InnerMatcher: objcInterfaceDecl(isDerivedFrom(Base: equalsBoundNode(ID: BaseNode)))
163 .bind(ID: DerivedNode)))))));
164 auto MatchExprRefTypeDef =
165 allOf(hasSourceExpression(InnerMatcher: hasType(InnerMatcher: hasUnqualifiedDesugaredType(InnerMatcher: recordType(
166 hasDeclaration(InnerMatcher: decl(cxxRecordDecl().bind(ID: BaseNode))))))),
167 hasType(InnerMatcher: hasUnqualifiedDesugaredType(InnerMatcher: recordType(hasDeclaration(
168 InnerMatcher: decl(cxxRecordDecl(isDerivedFrom(Base: equalsBoundNode(ID: BaseNode)))
169 .bind(ID: DerivedNode)))))),
170 unless(anyOf(hasType(InnerMatcher: templateTypeParmDecl()),
171 allOf(hasSourceExpression(InnerMatcher: isThisOrDerefThis()),
172 isCRTPCast(BaseID: BaseNode, DerivedID: DerivedNode)))));
173 auto MatchExprPtrVoidCast = allOf(
174 anyOf(hasSourceExpression(InnerMatcher: explicitCastExpr(
175 hasType(InnerMatcher: pointerType(pointee(voidType()))),
176 hasSourceExpression(InnerMatcher: ignoringImpCasts(
177 InnerMatcher: hasTypePointingTo(DeclM: cxxRecordDecl().bind(ID: BaseNode)))))),
178 hasSourceExpression(
179 InnerMatcher: callExpr(hasType(InnerMatcher: pointerType(pointee(voidType()))),
180 hasAnyArgument(InnerMatcher: ignoringImpCasts(InnerMatcher: hasTypePointingTo(
181 DeclM: cxxRecordDecl().bind(ID: BaseNode))))))),
182 hasTypePointingTo(DeclM: cxxRecordDecl(isDerivedFrom(Base: equalsBoundNode(ID: BaseNode)))
183 .bind(ID: DerivedNode)));
184
185 auto ExplicitCast =
186 explicitCastExpr(anyOf(MatchExprPtr, MatchExprRefTypeDef,
187 MatchExprPtrObjC, MatchExprPtrVoidCast))
188 .bind(ID: WarnRecordDecl);
189 auto Cast = stmt(ExplicitCast);
190
191 auto Matches =
192 match(Matcher: stmt(forEachDescendant(Cast)), Node: *D->getBody(), Context&: AM.getASTContext());
193 for (BoundNodes Match : Matches)
194 emitDiagnostics(Nodes: Match, BR, ADC, Checker: this, BT);
195
196 // Match calls returning derived type where an argument is a void pointer.
197 auto VoidPtrCast =
198 castExpr(hasType(InnerMatcher: pointerType(pointee(voidType()))),
199 hasSourceExpression(InnerMatcher: ignoringImpCasts(
200 InnerMatcher: hasTypePointingTo(DeclM: cxxRecordDecl().bind(ID: BaseNode)))))
201 .bind(ID: WarnRecordDecl);
202 auto MatchCallPtrVoidArgCast = callExpr(
203 hasAnyArgument(InnerMatcher: anyOf(VoidPtrCast,
204 explicitCastExpr(hasSourceExpression(InnerMatcher: VoidPtrCast)))),
205 hasTypePointingTo(DeclM: cxxRecordDecl(isDerivedFrom(Base: equalsBoundNode(ID: BaseNode)))
206 .bind(ID: DerivedNode)));
207 auto CallArgCast = stmt(MatchCallPtrVoidArgCast);
208 auto MatchesCallArgCast = match(Matcher: stmt(forEachDescendant(CallArgCast)),
209 Node: *D->getBody(), Context&: AM.getASTContext());
210 for (BoundNodes Match : MatchesCallArgCast)
211 emitDiagnostics(Nodes: Match, BR, ADC, Checker: this, BT);
212
213 // Match casts between unrelated types and warn
214 auto MatchExprPtrUnrelatedTypes = allOf(
215 hasSourceExpression(
216 InnerMatcher: hasTypePointingTo(DeclM: cxxRecordDecl().bind(ID: FromCastNode))),
217 hasTypePointingTo(DeclM: cxxRecordDecl().bind(ID: ToCastNode)),
218 unless(anyOf(hasTypePointingTo(DeclM: cxxRecordDecl(
219 isSameOrDerivedFrom(Base: equalsBoundNode(ID: FromCastNode)))),
220 hasSourceExpression(InnerMatcher: hasTypePointingTo(DeclM: cxxRecordDecl(
221 isSameOrDerivedFrom(Base: equalsBoundNode(ID: ToCastNode))))))));
222 auto MatchExprPtrObjCUnrelatedTypes = allOf(
223 hasSourceExpression(InnerMatcher: ignoringImpCasts(InnerMatcher: hasType(InnerMatcher: objcObjectPointerType(
224 pointee(hasDeclaration(InnerMatcher: objcInterfaceDecl().bind(ID: FromCastNode))))))),
225 ignoringImpCasts(InnerMatcher: hasType(InnerMatcher: objcObjectPointerType(
226 pointee(hasDeclaration(InnerMatcher: objcInterfaceDecl().bind(ID: ToCastNode)))))),
227 unless(anyOf(
228 ignoringImpCasts(InnerMatcher: hasType(
229 InnerMatcher: objcObjectPointerType(pointee(hasDeclaration(InnerMatcher: objcInterfaceDecl(
230 isSameOrDerivedFrom(Base: equalsBoundNode(ID: FromCastNode)))))))),
231 hasSourceExpression(InnerMatcher: ignoringImpCasts(InnerMatcher: hasType(
232 InnerMatcher: objcObjectPointerType(pointee(hasDeclaration(InnerMatcher: objcInterfaceDecl(
233 isSameOrDerivedFrom(Base: equalsBoundNode(ID: ToCastNode))))))))))));
234 auto MatchExprRefTypeDefUnrelated = allOf(
235 hasSourceExpression(InnerMatcher: hasType(InnerMatcher: hasUnqualifiedDesugaredType(InnerMatcher: recordType(
236 hasDeclaration(InnerMatcher: decl(cxxRecordDecl().bind(ID: FromCastNode))))))),
237 hasType(InnerMatcher: hasUnqualifiedDesugaredType(
238 InnerMatcher: recordType(hasDeclaration(InnerMatcher: decl(cxxRecordDecl().bind(ID: ToCastNode)))))),
239 unless(anyOf(
240 hasType(InnerMatcher: hasUnqualifiedDesugaredType(
241 InnerMatcher: recordType(hasDeclaration(InnerMatcher: decl(cxxRecordDecl(
242 isSameOrDerivedFrom(Base: equalsBoundNode(ID: FromCastNode)))))))),
243 hasSourceExpression(InnerMatcher: hasType(InnerMatcher: hasUnqualifiedDesugaredType(
244 InnerMatcher: recordType(hasDeclaration(InnerMatcher: decl(cxxRecordDecl(
245 isSameOrDerivedFrom(Base: equalsBoundNode(ID: ToCastNode))))))))))));
246
247 auto ExplicitCastUnrelated =
248 explicitCastExpr(anyOf(MatchExprPtrUnrelatedTypes,
249 MatchExprPtrObjCUnrelatedTypes,
250 MatchExprRefTypeDefUnrelated))
251 .bind(ID: WarnRecordDecl);
252 auto CastUnrelated = stmt(ExplicitCastUnrelated);
253 auto MatchesUnrelatedTypes = match(Matcher: stmt(forEachDescendant(CastUnrelated)),
254 Node: *D->getBody(), Context&: AM.getASTContext());
255 for (BoundNodes Match : MatchesUnrelatedTypes)
256 emitDiagnosticsUnrelated(Nodes: Match, BR, ADC, Checker: this, BT);
257
258 // Match an `id`-typed argument implicitly converted to a specific
259 // Objective-C type at a call, message send, or constructor call, e.g.
260 // passing an `id` where an `NSString *` parameter is expected. Such
261 // conversions compile without a visible cast but throw at runtime if the
262 // object is not actually of that type.
263 auto CastArgFromIdToSpecificType =
264 implicitCastExpr(
265 hasCastKind(Kind: CK_BitCast),
266 hasSourceExpression(
267 InnerMatcher: ignoringParenImpCasts(InnerMatcher: hasType(InnerMatcher: qualType(isObjCIdType())))),
268 hasType(InnerMatcher: qualType(hasCanonicalType(InnerMatcher: objcObjectPointerType(pointee(
269 hasDeclaration(InnerMatcher: objcInterfaceDecl().bind(ID: DerivedNode))))))))
270 .bind(ID: WarnRecordDecl);
271 auto MatchCallArgFromId =
272 anyOf(callExpr(hasAnyArgument(InnerMatcher: CastArgFromIdToSpecificType)),
273 cxxConstructExpr(hasAnyArgument(InnerMatcher: CastArgFromIdToSpecificType)),
274 objcMessageExpr(hasAnyArgument(InnerMatcher: CastArgFromIdToSpecificType)));
275 auto MatchesCallArgFromId =
276 match(Matcher: stmt(forEachDescendant(stmt(MatchCallArgFromId))), Node: *D->getBody(),
277 Context&: AM.getASTContext());
278 for (BoundNodes Match : MatchesCallArgFromId)
279 emitDiagnosticsIdArg(Nodes: Match, BR, ADC, Checker: this, BT);
280}
281
282void ento::registerMemoryUnsafeCastChecker(CheckerManager &Mgr) {
283 Mgr.registerChecker<MemoryUnsafeCastChecker>();
284}
285
286bool ento::shouldRegisterMemoryUnsafeCastChecker(const CheckerManager &mgr) {
287 return true;
288}
289