| 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 | Report->setDeclWithIssue(ADC->getDecl()); |
| 58 | BR.emitReport(R: std::move(Report)); |
| 59 | } |
| 60 | |
| 61 | static 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 | |
| 82 | static 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 | |
| 102 | namespace { |
| 103 | using BoundNodesMap = ::clang::ast_matchers::internal::BoundNodesMap; |
| 104 | |
| 105 | // Matches the plain `id` type. |
| 106 | AST_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>`. |
| 112 | AST_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 | |
| 132 | static 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`. |
| 137 | static decltype(auto) isThisOrDerefThis() { |
| 138 | return ignoringParenImpCasts(InnerMatcher: anyOf( |
| 139 | cxxThisExpr(), |
| 140 | unaryOperator(hasOperatorName(Name: "*" ), |
| 141 | hasUnaryOperand(InnerMatcher: ignoringParenImpCasts(InnerMatcher: cxxThisExpr()))))); |
| 142 | } |
| 143 | |
| 144 | void 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 | |
| 282 | void ento::registerMemoryUnsafeCastChecker(CheckerManager &Mgr) { |
| 283 | Mgr.registerChecker<MemoryUnsafeCastChecker>(); |
| 284 | } |
| 285 | |
| 286 | bool ento::shouldRegisterMemoryUnsafeCastChecker(const CheckerManager &mgr) { |
| 287 | return true; |
| 288 | } |
| 289 | |