| 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 | |