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 BR.emitReport(R: std::move(Report));
58}
59
60static void emitDiagnosticsUnrelated(const BoundNodes &Nodes, BugReporter &BR,
61 AnalysisDeclContext *ADC,
62 const MemoryUnsafeCastChecker *Checker,
63 const BugType &BT) {
64 const auto *CE = Nodes.getNodeAs<CastExpr>(ID: WarnRecordDecl);
65 const NamedDecl *FromCast = Nodes.getNodeAs<NamedDecl>(ID: FromCastNode);
66 const NamedDecl *ToCast = Nodes.getNodeAs<NamedDecl>(ID: ToCastNode);
67 assert(CE && FromCast && ToCast);
68
69 std::string Diagnostics;
70 llvm::raw_string_ostream OS(Diagnostics);
71 OS << "Unsafe cast from type '" << FromCast->getNameAsString()
72 << "' to an unrelated type '" << ToCast->getNameAsString() << "'";
73 PathDiagnosticLocation BSLoc(CE->getSourceRange().getBegin(),
74 BR.getSourceManager());
75 auto Report = std::make_unique<BasicBugReport>(args: BT, args&: OS.str(), args&: BSLoc);
76 Report->addRange(R: CE->getSourceRange());
77 BR.emitReport(R: std::move(Report));
78}
79
80namespace clang {
81namespace ast_matchers {
82AST_MATCHER_P(StringLiteral, mentionsBoundType, std::string, BindingID) {
83 return Builder->removeBindings(Predicate: [this, &Node](const BoundNodesMap &Nodes) {
84 const auto &BN = Nodes.getNode(ID: this->BindingID);
85 if (const auto *ND = BN.get<NamedDecl>()) {
86 return ND->getName() != Node.getString();
87 }
88 return true;
89 });
90}
91} // end namespace ast_matchers
92} // end namespace clang
93
94static decltype(auto) hasTypePointingTo(DeclarationMatcher DeclM) {
95 return hasType(InnerMatcher: pointerType(pointee(hasDeclaration(InnerMatcher: DeclM))));
96}
97
98void MemoryUnsafeCastChecker::checkASTCodeBody(const Decl *D,
99 AnalysisManager &AM,
100 BugReporter &BR) const {
101
102 AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D);
103
104 // Match downcasts from base type to derived type and warn
105 auto MatchExprPtr = allOf(
106 hasSourceExpression(InnerMatcher: hasTypePointingTo(DeclM: cxxRecordDecl().bind(ID: BaseNode))),
107 hasTypePointingTo(DeclM: cxxRecordDecl(isDerivedFrom(Base: equalsBoundNode(ID: BaseNode)))
108 .bind(ID: DerivedNode)),
109 unless(anyOf(hasSourceExpression(InnerMatcher: cxxThisExpr()),
110 hasTypePointingTo(DeclM: templateTypeParmDecl()))));
111 auto MatchExprPtrObjC = allOf(
112 hasSourceExpression(InnerMatcher: ignoringImpCasts(InnerMatcher: hasType(InnerMatcher: objcObjectPointerType(
113 pointee(hasDeclaration(InnerMatcher: objcInterfaceDecl().bind(ID: BaseNode))))))),
114 ignoringImpCasts(InnerMatcher: hasType(InnerMatcher: objcObjectPointerType(pointee(hasDeclaration(
115 InnerMatcher: objcInterfaceDecl(isDerivedFrom(Base: equalsBoundNode(ID: BaseNode)))
116 .bind(ID: DerivedNode)))))));
117 auto MatchExprRefTypeDef =
118 allOf(hasSourceExpression(InnerMatcher: hasType(InnerMatcher: hasUnqualifiedDesugaredType(InnerMatcher: recordType(
119 hasDeclaration(InnerMatcher: decl(cxxRecordDecl().bind(ID: BaseNode))))))),
120 hasType(InnerMatcher: hasUnqualifiedDesugaredType(InnerMatcher: recordType(hasDeclaration(
121 InnerMatcher: decl(cxxRecordDecl(isDerivedFrom(Base: equalsBoundNode(ID: BaseNode)))
122 .bind(ID: DerivedNode)))))),
123 unless(anyOf(hasSourceExpression(InnerMatcher: hasDescendant(cxxThisExpr())),
124 hasType(InnerMatcher: templateTypeParmDecl()))));
125 auto MatchExprPtrVoidCast = allOf(
126 anyOf(hasSourceExpression(InnerMatcher: explicitCastExpr(
127 hasType(InnerMatcher: pointerType(pointee(voidType()))),
128 hasSourceExpression(InnerMatcher: ignoringImpCasts(
129 InnerMatcher: hasTypePointingTo(DeclM: cxxRecordDecl().bind(ID: BaseNode)))))),
130 hasSourceExpression(
131 InnerMatcher: callExpr(hasType(InnerMatcher: pointerType(pointee(voidType()))),
132 hasAnyArgument(InnerMatcher: ignoringImpCasts(InnerMatcher: hasTypePointingTo(
133 DeclM: cxxRecordDecl().bind(ID: BaseNode))))))),
134 hasTypePointingTo(DeclM: cxxRecordDecl(isDerivedFrom(Base: equalsBoundNode(ID: BaseNode)))
135 .bind(ID: DerivedNode)));
136
137 auto ExplicitCast =
138 explicitCastExpr(anyOf(MatchExprPtr, MatchExprRefTypeDef,
139 MatchExprPtrObjC, MatchExprPtrVoidCast))
140 .bind(ID: WarnRecordDecl);
141 auto Cast = stmt(ExplicitCast);
142
143 auto Matches =
144 match(Matcher: stmt(forEachDescendant(Cast)), Node: *D->getBody(), Context&: AM.getASTContext());
145 for (BoundNodes Match : Matches)
146 emitDiagnostics(Nodes: Match, BR, ADC, Checker: this, BT);
147
148 // Match calls returning derived type where an argument is a void pointer.
149 auto VoidPtrCast =
150 castExpr(hasType(InnerMatcher: pointerType(pointee(voidType()))),
151 hasSourceExpression(InnerMatcher: ignoringImpCasts(
152 InnerMatcher: hasTypePointingTo(DeclM: cxxRecordDecl().bind(ID: BaseNode)))))
153 .bind(ID: WarnRecordDecl);
154 auto MatchCallPtrVoidArgCast = callExpr(
155 hasAnyArgument(InnerMatcher: anyOf(VoidPtrCast,
156 explicitCastExpr(hasSourceExpression(InnerMatcher: VoidPtrCast)))),
157 hasTypePointingTo(DeclM: cxxRecordDecl(isDerivedFrom(Base: equalsBoundNode(ID: BaseNode)))
158 .bind(ID: DerivedNode)));
159 auto CallArgCast = stmt(MatchCallPtrVoidArgCast);
160 auto MatchesCallArgCast = match(Matcher: stmt(forEachDescendant(CallArgCast)),
161 Node: *D->getBody(), Context&: AM.getASTContext());
162 for (BoundNodes Match : MatchesCallArgCast)
163 emitDiagnostics(Nodes: Match, BR, ADC, Checker: this, BT);
164
165 // Match casts between unrelated types and warn
166 auto MatchExprPtrUnrelatedTypes = allOf(
167 hasSourceExpression(
168 InnerMatcher: hasTypePointingTo(DeclM: cxxRecordDecl().bind(ID: FromCastNode))),
169 hasTypePointingTo(DeclM: cxxRecordDecl().bind(ID: ToCastNode)),
170 unless(anyOf(hasTypePointingTo(DeclM: cxxRecordDecl(
171 isSameOrDerivedFrom(Base: equalsBoundNode(ID: FromCastNode)))),
172 hasSourceExpression(InnerMatcher: hasTypePointingTo(DeclM: cxxRecordDecl(
173 isSameOrDerivedFrom(Base: equalsBoundNode(ID: ToCastNode))))))));
174 auto MatchExprPtrObjCUnrelatedTypes = allOf(
175 hasSourceExpression(InnerMatcher: ignoringImpCasts(InnerMatcher: hasType(InnerMatcher: objcObjectPointerType(
176 pointee(hasDeclaration(InnerMatcher: objcInterfaceDecl().bind(ID: FromCastNode))))))),
177 ignoringImpCasts(InnerMatcher: hasType(InnerMatcher: objcObjectPointerType(
178 pointee(hasDeclaration(InnerMatcher: objcInterfaceDecl().bind(ID: ToCastNode)))))),
179 unless(anyOf(
180 ignoringImpCasts(InnerMatcher: hasType(
181 InnerMatcher: objcObjectPointerType(pointee(hasDeclaration(InnerMatcher: objcInterfaceDecl(
182 isSameOrDerivedFrom(Base: equalsBoundNode(ID: FromCastNode)))))))),
183 hasSourceExpression(InnerMatcher: ignoringImpCasts(InnerMatcher: hasType(
184 InnerMatcher: objcObjectPointerType(pointee(hasDeclaration(InnerMatcher: objcInterfaceDecl(
185 isSameOrDerivedFrom(Base: equalsBoundNode(ID: ToCastNode))))))))))));
186 auto MatchExprRefTypeDefUnrelated = allOf(
187 hasSourceExpression(InnerMatcher: hasType(InnerMatcher: hasUnqualifiedDesugaredType(InnerMatcher: recordType(
188 hasDeclaration(InnerMatcher: decl(cxxRecordDecl().bind(ID: FromCastNode))))))),
189 hasType(InnerMatcher: hasUnqualifiedDesugaredType(
190 InnerMatcher: recordType(hasDeclaration(InnerMatcher: decl(cxxRecordDecl().bind(ID: ToCastNode)))))),
191 unless(anyOf(
192 hasType(InnerMatcher: hasUnqualifiedDesugaredType(
193 InnerMatcher: recordType(hasDeclaration(InnerMatcher: decl(cxxRecordDecl(
194 isSameOrDerivedFrom(Base: equalsBoundNode(ID: FromCastNode)))))))),
195 hasSourceExpression(InnerMatcher: hasType(InnerMatcher: hasUnqualifiedDesugaredType(
196 InnerMatcher: recordType(hasDeclaration(InnerMatcher: decl(cxxRecordDecl(
197 isSameOrDerivedFrom(Base: equalsBoundNode(ID: ToCastNode))))))))))));
198
199 auto ExplicitCastUnrelated =
200 explicitCastExpr(anyOf(MatchExprPtrUnrelatedTypes,
201 MatchExprPtrObjCUnrelatedTypes,
202 MatchExprRefTypeDefUnrelated))
203 .bind(ID: WarnRecordDecl);
204 auto CastUnrelated = stmt(ExplicitCastUnrelated);
205 auto MatchesUnrelatedTypes = match(Matcher: stmt(forEachDescendant(CastUnrelated)),
206 Node: *D->getBody(), Context&: AM.getASTContext());
207 for (BoundNodes Match : MatchesUnrelatedTypes)
208 emitDiagnosticsUnrelated(Nodes: Match, BR, ADC, Checker: this, BT);
209}
210
211void ento::registerMemoryUnsafeCastChecker(CheckerManager &Mgr) {
212 Mgr.registerChecker<MemoryUnsafeCastChecker>();
213}
214
215bool ento::shouldRegisterMemoryUnsafeCastChecker(const CheckerManager &mgr) {
216 return true;
217}
218