Coverage Report

Created: 2023-09-30 09:22

/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/lib/StaticAnalyzer/Checkers/OSObjectCStyleCast.cpp
Line
Count
Source (jump to first uncovered line)
1
//===- OSObjectCStyleCast.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 OSObjectCStyleCast checker, which checks for C-style casts
10
// of OSObjects. Such casts almost always indicate a code smell,
11
// as an explicit static or dynamic cast should be used instead.
12
//===----------------------------------------------------------------------===//
13
14
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
15
#include "clang/ASTMatchers/ASTMatchFinder.h"
16
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
17
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
18
#include "clang/StaticAnalyzer/Core/Checker.h"
19
#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
20
#include "llvm/Support/Debug.h"
21
22
using namespace clang;
23
using namespace ento;
24
using namespace ast_matchers;
25
26
namespace {
27
static constexpr const char *const WarnAtNode = "WarnAtNode";
28
static constexpr const char *const WarnRecordDecl = "WarnRecordDecl";
29
30
class OSObjectCStyleCastChecker : public Checker<check::ASTCodeBody> {
31
public:
32
  void checkASTCodeBody(const Decl *D, AnalysisManager &AM,
33
                        BugReporter &BR) const;
34
};
35
} // namespace
36
37
namespace clang {
38
namespace ast_matchers {
39
2
AST_MATCHER_P(StringLiteral, mentionsBoundType, std::string, BindingID) {
40
2
  return Builder->removeBindings([this, &Node](const BoundNodesMap &Nodes) {
41
2
    const auto &BN = Nodes.getNode(this->BindingID);
42
2
    if (const auto *ND = BN.get<NamedDecl>()) {
43
2
      return ND->getName() != Node.getString();
44
2
    }
45
0
    return true;
46
2
  });
47
2
}
48
} // end namespace ast_matchers
49
} // end namespace clang
50
51
static void emitDiagnostics(const BoundNodes &Nodes,
52
                            BugReporter &BR,
53
                            AnalysisDeclContext *ADC,
54
2
                            const OSObjectCStyleCastChecker *Checker) {
55
2
  const auto *CE = Nodes.getNodeAs<CastExpr>(WarnAtNode);
56
2
  const CXXRecordDecl *RD = Nodes.getNodeAs<CXXRecordDecl>(WarnRecordDecl);
57
2
  assert(CE && RD);
58
59
2
  std::string Diagnostics;
60
2
  llvm::raw_string_ostream OS(Diagnostics);
61
2
  OS << "C-style cast of an OSObject is prone to type confusion attacks; "
62
2
     << "use 'OSRequiredCast' if the object is definitely of type '"
63
2
     << RD->getNameAsString() << "', or 'OSDynamicCast' followed by "
64
2
     << "a null check if unsure",
65
66
2
  BR.EmitBasicReport(
67
2
    ADC->getDecl(),
68
2
    Checker,
69
2
    /*Name=*/"OSObject C-Style Cast",
70
2
    categories::SecurityError,
71
2
    OS.str(),
72
2
    PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), ADC),
73
2
    CE->getSourceRange());
74
2
}
75
76
22
static decltype(auto) hasTypePointingTo(DeclarationMatcher DeclM) {
77
22
  return hasType(pointerType(pointee(hasDeclaration(DeclM))));
78
22
}
79
80
void OSObjectCStyleCastChecker::checkASTCodeBody(const Decl *D,
81
                                                 AnalysisManager &AM,
82
11
                                                 BugReporter &BR) const {
83
84
11
  AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D);
85
86
11
  auto DynamicCastM = callExpr(callee(functionDecl(hasName("safeMetaCast"))));
87
  // 'allocClassWithName' allocates an object with the given type.
88
  // The type is actually provided as a string argument (type's name).
89
  // This makes the following pattern possible:
90
  //
91
  // Foo *object = (Foo *)allocClassWithName("Foo");
92
  //
93
  // While OSRequiredCast can be used here, it is still not a useful warning.
94
11
  auto AllocClassWithNameM = callExpr(
95
11
      callee(functionDecl(hasName("allocClassWithName"))),
96
      // Here we want to make sure that the string argument matches the
97
      // type in the cast expression.
98
11
      hasArgument(0, stringLiteral(mentionsBoundType(WarnRecordDecl))));
99
100
11
  auto OSObjTypeM =
101
11
      hasTypePointingTo(cxxRecordDecl(isDerivedFrom("OSMetaClassBase")));
102
11
  auto OSObjSubclassM = hasTypePointingTo(
103
11
      cxxRecordDecl(isDerivedFrom("OSObject")).bind(WarnRecordDecl));
104
105
11
  auto CastM =
106
11
      cStyleCastExpr(
107
11
          allOf(OSObjSubclassM,
108
11
                hasSourceExpression(
109
11
                    allOf(OSObjTypeM,
110
11
                          unless(anyOf(DynamicCastM, AllocClassWithNameM))))))
111
11
          .bind(WarnAtNode);
112
113
11
  auto Matches =
114
11
      match(stmt(forEachDescendant(CastM)), *D->getBody(), AM.getASTContext());
115
11
  for (BoundNodes Match : Matches)
116
2
    emitDiagnostics(Match, BR, ADC, this);
117
11
}
118
119
1
void ento::registerOSObjectCStyleCast(CheckerManager &Mgr) {
120
1
  Mgr.registerChecker<OSObjectCStyleCastChecker>();
121
1
}
122
123
2
bool ento::shouldRegisterOSObjectCStyleCast(const CheckerManager &mgr) {
124
2
  return true;
125
2
}