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 | |
20 | using namespace clang; |
21 | using namespace ento; |
22 | using namespace ast_matchers; |
23 | |
24 | namespace { |
25 | static constexpr const char *const BaseNode = "BaseNode" ; |
26 | static constexpr const char *const DerivedNode = "DerivedNode" ; |
27 | static constexpr const char *const FromCastNode = "FromCast" ; |
28 | static constexpr const char *const ToCastNode = "ToCast" ; |
29 | static constexpr const char *const WarnRecordDecl = "WarnRecordDecl" ; |
30 | |
31 | class MemoryUnsafeCastChecker : public Checker<check::ASTCodeBody> { |
32 | BugType BT{this, "Unsafe cast" , "WebKit coding guidelines" }; |
33 | |
34 | public: |
35 | void checkASTCodeBody(const Decl *D, AnalysisManager &Mgr, |
36 | BugReporter &BR) const; |
37 | }; |
38 | } // end namespace |
39 | |
40 | static 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 | |
60 | static 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 | |
80 | namespace clang { |
81 | namespace ast_matchers { |
82 | AST_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 | |
94 | static decltype(auto) hasTypePointingTo(DeclarationMatcher DeclM) { |
95 | return hasType(InnerMatcher: pointerType(pointee(hasDeclaration(InnerMatcher: DeclM)))); |
96 | } |
97 | |
98 | void 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 | |
126 | auto ExplicitCast = explicitCastExpr(anyOf(MatchExprPtr, MatchExprRefTypeDef, |
127 | MatchExprPtrObjC)) |
128 | .bind(ID: WarnRecordDecl); |
129 | auto Cast = stmt(ExplicitCast); |
130 | |
131 | auto Matches = |
132 | match(Matcher: stmt(forEachDescendant(Cast)), Node: *D->getBody(), Context&: AM.getASTContext()); |
133 | for (BoundNodes Match : Matches) |
134 | emitDiagnostics(Nodes: Match, BR, ADC, Checker: this, BT); |
135 | |
136 | // Match casts between unrelated types and warn |
137 | auto MatchExprPtrUnrelatedTypes = allOf( |
138 | hasSourceExpression( |
139 | InnerMatcher: hasTypePointingTo(DeclM: cxxRecordDecl().bind(ID: FromCastNode))), |
140 | hasTypePointingTo(DeclM: cxxRecordDecl().bind(ID: ToCastNode)), |
141 | unless(anyOf(hasTypePointingTo(DeclM: cxxRecordDecl( |
142 | isSameOrDerivedFrom(Base: equalsBoundNode(ID: FromCastNode)))), |
143 | hasSourceExpression(InnerMatcher: hasTypePointingTo(DeclM: cxxRecordDecl( |
144 | isSameOrDerivedFrom(Base: equalsBoundNode(ID: ToCastNode)))))))); |
145 | auto MatchExprPtrObjCUnrelatedTypes = allOf( |
146 | hasSourceExpression(InnerMatcher: ignoringImpCasts(InnerMatcher: hasType(InnerMatcher: objcObjectPointerType( |
147 | pointee(hasDeclaration(InnerMatcher: objcInterfaceDecl().bind(ID: FromCastNode))))))), |
148 | ignoringImpCasts(InnerMatcher: hasType(InnerMatcher: objcObjectPointerType( |
149 | pointee(hasDeclaration(InnerMatcher: objcInterfaceDecl().bind(ID: ToCastNode)))))), |
150 | unless(anyOf( |
151 | ignoringImpCasts(InnerMatcher: hasType( |
152 | InnerMatcher: objcObjectPointerType(pointee(hasDeclaration(InnerMatcher: objcInterfaceDecl( |
153 | isSameOrDerivedFrom(Base: equalsBoundNode(ID: FromCastNode)))))))), |
154 | hasSourceExpression(InnerMatcher: ignoringImpCasts(InnerMatcher: hasType( |
155 | InnerMatcher: objcObjectPointerType(pointee(hasDeclaration(InnerMatcher: objcInterfaceDecl( |
156 | isSameOrDerivedFrom(Base: equalsBoundNode(ID: ToCastNode)))))))))))); |
157 | auto MatchExprRefTypeDefUnrelated = allOf( |
158 | hasSourceExpression(InnerMatcher: hasType(InnerMatcher: hasUnqualifiedDesugaredType(InnerMatcher: recordType( |
159 | hasDeclaration(InnerMatcher: decl(cxxRecordDecl().bind(ID: FromCastNode))))))), |
160 | hasType(InnerMatcher: hasUnqualifiedDesugaredType( |
161 | InnerMatcher: recordType(hasDeclaration(InnerMatcher: decl(cxxRecordDecl().bind(ID: ToCastNode)))))), |
162 | unless(anyOf( |
163 | hasType(InnerMatcher: hasUnqualifiedDesugaredType( |
164 | InnerMatcher: recordType(hasDeclaration(InnerMatcher: decl(cxxRecordDecl( |
165 | isSameOrDerivedFrom(Base: equalsBoundNode(ID: FromCastNode)))))))), |
166 | hasSourceExpression(InnerMatcher: hasType(InnerMatcher: hasUnqualifiedDesugaredType( |
167 | InnerMatcher: recordType(hasDeclaration(InnerMatcher: decl(cxxRecordDecl( |
168 | isSameOrDerivedFrom(Base: equalsBoundNode(ID: ToCastNode)))))))))))); |
169 | |
170 | auto ExplicitCastUnrelated = |
171 | explicitCastExpr(anyOf(MatchExprPtrUnrelatedTypes, |
172 | MatchExprPtrObjCUnrelatedTypes, |
173 | MatchExprRefTypeDefUnrelated)) |
174 | .bind(ID: WarnRecordDecl); |
175 | auto CastUnrelated = stmt(ExplicitCastUnrelated); |
176 | auto MatchesUnrelatedTypes = match(Matcher: stmt(forEachDescendant(CastUnrelated)), |
177 | Node: *D->getBody(), Context&: AM.getASTContext()); |
178 | for (BoundNodes Match : MatchesUnrelatedTypes) |
179 | emitDiagnosticsUnrelated(Nodes: Match, BR, ADC, Checker: this, BT); |
180 | } |
181 | |
182 | void ento::registerMemoryUnsafeCastChecker(CheckerManager &Mgr) { |
183 | Mgr.registerChecker<MemoryUnsafeCastChecker>(); |
184 | } |
185 | |
186 | bool ento::shouldRegisterMemoryUnsafeCastChecker(const CheckerManager &mgr) { |
187 | return true; |
188 | } |
189 | |