10b57cec5SDimitry Andric //===- OSObjectCStyleCast.cpp ------------------------------------*- C++ -*-==//
20b57cec5SDimitry Andric //
30b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
40b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
50b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
60b57cec5SDimitry Andric //
70b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
80b57cec5SDimitry Andric //
90b57cec5SDimitry Andric // This file defines OSObjectCStyleCast checker, which checks for C-style casts
100b57cec5SDimitry Andric // of OSObjects. Such casts almost always indicate a code smell,
110b57cec5SDimitry Andric // as an explicit static or dynamic cast should be used instead.
120b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
130b57cec5SDimitry Andric
140b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
150b57cec5SDimitry Andric #include "clang/ASTMatchers/ASTMatchFinder.h"
160b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
170b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
180b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/Checker.h"
190b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
200b57cec5SDimitry Andric #include "llvm/Support/Debug.h"
210b57cec5SDimitry Andric
220b57cec5SDimitry Andric using namespace clang;
230b57cec5SDimitry Andric using namespace ento;
240b57cec5SDimitry Andric using namespace ast_matchers;
250b57cec5SDimitry Andric
260b57cec5SDimitry Andric namespace {
27e8d8bef9SDimitry Andric static constexpr const char *const WarnAtNode = "WarnAtNode";
28e8d8bef9SDimitry Andric static constexpr const char *const WarnRecordDecl = "WarnRecordDecl";
290b57cec5SDimitry Andric
300b57cec5SDimitry Andric class OSObjectCStyleCastChecker : public Checker<check::ASTCodeBody> {
310b57cec5SDimitry Andric public:
32e8d8bef9SDimitry Andric void checkASTCodeBody(const Decl *D, AnalysisManager &AM,
330b57cec5SDimitry Andric BugReporter &BR) const;
340b57cec5SDimitry Andric };
35*fe6060f1SDimitry Andric } // namespace
36*fe6060f1SDimitry Andric
37*fe6060f1SDimitry Andric namespace clang {
38*fe6060f1SDimitry Andric namespace ast_matchers {
AST_MATCHER_P(StringLiteral,mentionsBoundType,std::string,BindingID)39*fe6060f1SDimitry Andric AST_MATCHER_P(StringLiteral, mentionsBoundType, std::string, BindingID) {
40*fe6060f1SDimitry Andric return Builder->removeBindings([this, &Node](const BoundNodesMap &Nodes) {
41*fe6060f1SDimitry Andric const auto &BN = Nodes.getNode(this->BindingID);
42*fe6060f1SDimitry Andric if (const auto *ND = BN.get<NamedDecl>()) {
43*fe6060f1SDimitry Andric return ND->getName() != Node.getString();
44e8d8bef9SDimitry Andric }
45*fe6060f1SDimitry Andric return true;
46*fe6060f1SDimitry Andric });
47*fe6060f1SDimitry Andric }
48*fe6060f1SDimitry Andric } // end namespace ast_matchers
49*fe6060f1SDimitry Andric } // end namespace clang
500b57cec5SDimitry Andric
emitDiagnostics(const BoundNodes & Nodes,BugReporter & BR,AnalysisDeclContext * ADC,const OSObjectCStyleCastChecker * Checker)510b57cec5SDimitry Andric static void emitDiagnostics(const BoundNodes &Nodes,
520b57cec5SDimitry Andric BugReporter &BR,
530b57cec5SDimitry Andric AnalysisDeclContext *ADC,
540b57cec5SDimitry Andric const OSObjectCStyleCastChecker *Checker) {
550b57cec5SDimitry Andric const auto *CE = Nodes.getNodeAs<CastExpr>(WarnAtNode);
56e8d8bef9SDimitry Andric const CXXRecordDecl *RD = Nodes.getNodeAs<CXXRecordDecl>(WarnRecordDecl);
57e8d8bef9SDimitry Andric assert(CE && RD);
580b57cec5SDimitry Andric
590b57cec5SDimitry Andric std::string Diagnostics;
600b57cec5SDimitry Andric llvm::raw_string_ostream OS(Diagnostics);
61e8d8bef9SDimitry Andric OS << "C-style cast of an OSObject is prone to type confusion attacks; "
62e8d8bef9SDimitry Andric << "use 'OSRequiredCast' if the object is definitely of type '"
63e8d8bef9SDimitry Andric << RD->getNameAsString() << "', or 'OSDynamicCast' followed by "
64e8d8bef9SDimitry Andric << "a null check if unsure",
650b57cec5SDimitry Andric
660b57cec5SDimitry Andric BR.EmitBasicReport(
670b57cec5SDimitry Andric ADC->getDecl(),
680b57cec5SDimitry Andric Checker,
690b57cec5SDimitry Andric /*Name=*/"OSObject C-Style Cast",
70e8d8bef9SDimitry Andric categories::SecurityError,
710b57cec5SDimitry Andric OS.str(),
720b57cec5SDimitry Andric PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), ADC),
730b57cec5SDimitry Andric CE->getSourceRange());
740b57cec5SDimitry Andric }
750b57cec5SDimitry Andric
hasTypePointingTo(DeclarationMatcher DeclM)765ffd83dbSDimitry Andric static decltype(auto) hasTypePointingTo(DeclarationMatcher DeclM) {
770b57cec5SDimitry Andric return hasType(pointerType(pointee(hasDeclaration(DeclM))));
780b57cec5SDimitry Andric }
790b57cec5SDimitry Andric
checkASTCodeBody(const Decl * D,AnalysisManager & AM,BugReporter & BR) const80*fe6060f1SDimitry Andric void OSObjectCStyleCastChecker::checkASTCodeBody(const Decl *D,
81*fe6060f1SDimitry Andric AnalysisManager &AM,
820b57cec5SDimitry Andric BugReporter &BR) const {
830b57cec5SDimitry Andric
840b57cec5SDimitry Andric AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D);
850b57cec5SDimitry Andric
860b57cec5SDimitry Andric auto DynamicCastM = callExpr(callee(functionDecl(hasName("safeMetaCast"))));
87*fe6060f1SDimitry Andric // 'allocClassWithName' allocates an object with the given type.
88*fe6060f1SDimitry Andric // The type is actually provided as a string argument (type's name).
89*fe6060f1SDimitry Andric // This makes the following pattern possible:
90*fe6060f1SDimitry Andric //
91*fe6060f1SDimitry Andric // Foo *object = (Foo *)allocClassWithName("Foo");
92*fe6060f1SDimitry Andric //
93*fe6060f1SDimitry Andric // While OSRequiredCast can be used here, it is still not a useful warning.
94*fe6060f1SDimitry Andric auto AllocClassWithNameM = callExpr(
95*fe6060f1SDimitry Andric callee(functionDecl(hasName("allocClassWithName"))),
96*fe6060f1SDimitry Andric // Here we want to make sure that the string argument matches the
97*fe6060f1SDimitry Andric // type in the cast expression.
98*fe6060f1SDimitry Andric hasArgument(0, stringLiteral(mentionsBoundType(WarnRecordDecl))));
990b57cec5SDimitry Andric
100*fe6060f1SDimitry Andric auto OSObjTypeM =
101*fe6060f1SDimitry Andric hasTypePointingTo(cxxRecordDecl(isDerivedFrom("OSMetaClassBase")));
1020b57cec5SDimitry Andric auto OSObjSubclassM = hasTypePointingTo(
103e8d8bef9SDimitry Andric cxxRecordDecl(isDerivedFrom("OSObject")).bind(WarnRecordDecl));
1040b57cec5SDimitry Andric
105*fe6060f1SDimitry Andric auto CastM =
106*fe6060f1SDimitry Andric cStyleCastExpr(
107*fe6060f1SDimitry Andric allOf(OSObjSubclassM,
108*fe6060f1SDimitry Andric hasSourceExpression(
109*fe6060f1SDimitry Andric allOf(OSObjTypeM,
110*fe6060f1SDimitry Andric unless(anyOf(DynamicCastM, AllocClassWithNameM))))))
111*fe6060f1SDimitry Andric .bind(WarnAtNode);
1120b57cec5SDimitry Andric
113*fe6060f1SDimitry Andric auto Matches =
114*fe6060f1SDimitry Andric match(stmt(forEachDescendant(CastM)), *D->getBody(), AM.getASTContext());
1150b57cec5SDimitry Andric for (BoundNodes Match : Matches)
1160b57cec5SDimitry Andric emitDiagnostics(Match, BR, ADC, this);
1170b57cec5SDimitry Andric }
1180b57cec5SDimitry Andric
registerOSObjectCStyleCast(CheckerManager & Mgr)1190b57cec5SDimitry Andric void ento::registerOSObjectCStyleCast(CheckerManager &Mgr) {
1200b57cec5SDimitry Andric Mgr.registerChecker<OSObjectCStyleCastChecker>();
1210b57cec5SDimitry Andric }
1220b57cec5SDimitry Andric
shouldRegisterOSObjectCStyleCast(const CheckerManager & mgr)1235ffd83dbSDimitry Andric bool ento::shouldRegisterOSObjectCStyleCast(const CheckerManager &mgr) {
1240b57cec5SDimitry Andric return true;
1250b57cec5SDimitry Andric }
126