Coverage Report

Created: 2023-09-21 18:56

/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/lib/StaticAnalyzer/Checkers/NumberObjectConversionChecker.cpp
Line
Count
Source
1
//===- NumberObjectConversionChecker.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 NumberObjectConversionChecker, which checks for a
10
// particular common mistake when dealing with numbers represented as objects
11
// passed around by pointers. Namely, the language allows to reinterpret the
12
// pointer as a number directly, often without throwing any warnings,
13
// but in most cases the result of such conversion is clearly unexpected,
14
// as pointer value, rather than number value represented by the pointee object,
15
// becomes the result of such operation.
16
//
17
// Currently the checker supports the Objective-C NSNumber class,
18
// and the OSBoolean class found in macOS low-level code; the latter
19
// can only hold boolean values.
20
//
21
// This checker has an option "Pedantic" (boolean), which enables detection of
22
// more conversion patterns (which are most likely more harmless, and therefore
23
// are more likely to produce false positives) - disabled by default,
24
// enabled with `-analyzer-config osx.NumberObjectConversion:Pedantic=true'.
25
//
26
//===----------------------------------------------------------------------===//
27
28
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
29
#include "clang/ASTMatchers/ASTMatchFinder.h"
30
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
31
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
32
#include "clang/StaticAnalyzer/Core/Checker.h"
33
#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
34
#include "clang/Lex/Lexer.h"
35
#include "llvm/ADT/APSInt.h"
36
37
using namespace clang;
38
using namespace ento;
39
using namespace ast_matchers;
40
41
namespace {
42
43
class NumberObjectConversionChecker : public Checker<check::ASTCodeBody> {
44
public:
45
  bool Pedantic;
46
47
  void checkASTCodeBody(const Decl *D, AnalysisManager &AM,
48
                        BugReporter &BR) const;
49
};
50
51
class Callback : public MatchFinder::MatchCallback {
52
  const NumberObjectConversionChecker *C;
53
  BugReporter &BR;
54
  AnalysisDeclContext *ADC;
55
56
public:
57
  Callback(const NumberObjectConversionChecker *C,
58
           BugReporter &BR, AnalysisDeclContext *ADC)
59
276
      : C(C), BR(BR), ADC(ADC) {}
60
  void run(const MatchFinder::MatchResult &Result) override;
61
};
62
} // end of anonymous namespace
63
64
162
void Callback::run(const MatchFinder::MatchResult &Result) {
65
162
  bool IsPedanticMatch =
66
162
      (Result.Nodes.getNodeAs<Stmt>("pedantic") != nullptr);
67
162
  if (IsPedanticMatch && 
!C->Pedantic30
)
68
15
    return;
69
70
147
  ASTContext &ACtx = ADC->getASTContext();
71
72
147
  if (const Expr *CheckIfNull =
73
147
          Result.Nodes.getNodeAs<Expr>("check_if_null")) {
74
    // Unless the macro indicates that the intended type is clearly not
75
    // a pointer type, we should avoid warning on comparing pointers
76
    // to zero literals in non-pedantic mode.
77
    // FIXME: Introduce an AST matcher to implement the macro-related logic?
78
54
    bool MacroIndicatesWeShouldSkipTheCheck = false;
79
54
    SourceLocation Loc = CheckIfNull->getBeginLoc();
80
54
    if (Loc.isMacroID()) {
81
42
      StringRef MacroName = Lexer::getImmediateMacroName(
82
42
          Loc, ACtx.getSourceManager(), ACtx.getLangOpts());
83
42
      if (MacroName == "NULL" || 
MacroName == "nil"36
)
84
6
        return;
85
36
      if (MacroName == "YES" || 
MacroName == "NO"12
)
86
28
        MacroIndicatesWeShouldSkipTheCheck = true;
87
36
    }
88
48
    if (!MacroIndicatesWeShouldSkipTheCheck) {
89
20
      Expr::EvalResult EVResult;
90
20
      if (CheckIfNull->IgnoreParenCasts()->EvaluateAsInt(
91
20
              EVResult, ACtx, Expr::SE_AllowSideEffects)) {
92
20
        llvm::APSInt Result = EVResult.Val.getInt();
93
20
        if (Result == 0) {
94
12
          if (!C->Pedantic)
95
6
            return;
96
6
          IsPedanticMatch = true;
97
6
        }
98
20
      }
99
20
    }
100
48
  }
101
102
135
  const Stmt *Conv = Result.Nodes.getNodeAs<Stmt>("conv");
103
135
  assert(Conv);
104
105
135
  const Expr *ConvertedCObject = Result.Nodes.getNodeAs<Expr>("c_object");
106
135
  const Expr *ConvertedCppObject = Result.Nodes.getNodeAs<Expr>("cpp_object");
107
135
  const Expr *ConvertedObjCObject = Result.Nodes.getNodeAs<Expr>("objc_object");
108
135
  bool IsCpp = (ConvertedCppObject != nullptr);
109
135
  bool IsObjC = (ConvertedObjCObject != nullptr);
110
135
  const Expr *Obj = IsObjC ? 
ConvertedObjCObject90
111
135
                  : 
IsCpp45
?
ConvertedCppObject27
112
45
                  : 
ConvertedCObject18
;
113
135
  assert(Obj);
114
115
135
  bool IsComparison =
116
135
      (Result.Nodes.getNodeAs<Stmt>("comparison") != nullptr);
117
118
135
  bool IsOSNumber =
119
135
      (Result.Nodes.getNodeAs<Decl>("osnumber") != nullptr);
120
121
135
  bool IsInteger =
122
135
      (Result.Nodes.getNodeAs<QualType>("int_type") != nullptr);
123
135
  bool IsObjCBool =
124
135
      (Result.Nodes.getNodeAs<QualType>("objc_bool_type") != nullptr);
125
135
  bool IsCppBool =
126
135
      (Result.Nodes.getNodeAs<QualType>("cpp_bool_type") != nullptr);
127
128
135
  llvm::SmallString<64> Msg;
129
135
  llvm::raw_svector_ostream OS(Msg);
130
131
  // Remove ObjC ARC qualifiers.
132
135
  QualType ObjT = Obj->getType().getUnqualifiedType();
133
134
  // Remove consts from pointers.
135
135
  if (IsCpp) {
136
27
    assert(ObjT.getCanonicalType()->isPointerType());
137
27
    ObjT = ACtx.getPointerType(
138
27
        ObjT->getPointeeType().getCanonicalType().getUnqualifiedType());
139
27
  }
140
141
135
  if (IsComparison)
142
48
    OS << "Comparing ";
143
87
  else
144
87
    OS << "Converting ";
145
146
135
  OS << "a pointer value of type '" << ObjT << "' to a ";
147
148
135
  std::string EuphemismForPlain = "primitive";
149
135
  std::string SuggestedApi = IsObjC ? 
(90
IsInteger90
?
""40
:
"-boolValue"50
)
150
135
                           : 
IsCpp45
?
(27
IsOSNumber27
?
""8
:
"getValue()"19
)
151
45
                           : 
"CFNumberGetValue()"18
;
152
135
  if (SuggestedApi.empty()) {
153
    // A generic message if we're not sure what API should be called.
154
    // FIXME: Pattern-match the integer type to make a better guess?
155
48
    SuggestedApi =
156
48
        "a method on '" + ObjT.getAsString() + "' to get the scalar value";
157
    // "scalar" is not quite correct or common, but some documentation uses it
158
    // when describing object methods we suggest. For consistency, we use
159
    // "scalar" in the whole sentence when we need to use this word in at least
160
    // one place, otherwise we use "primitive".
161
48
    EuphemismForPlain = "scalar";
162
48
  }
163
164
135
  if (IsInteger)
165
64
    OS << EuphemismForPlain << " integer value";
166
71
  else if (IsObjCBool)
167
44
    OS << EuphemismForPlain << " BOOL value";
168
27
  else if (IsCppBool)
169
12
    OS << EuphemismForPlain << " bool value";
170
15
  else // Branch condition?
171
15
    OS << EuphemismForPlain << " boolean value";
172
173
174
135
  if (IsPedanticMatch)
175
21
    OS << "; instead, either compare the pointer to "
176
21
       << (IsObjC ? 
"nil"10
:
IsCpp11
?
"nullptr"7
:
"NULL"4
) << " or ";
177
114
  else
178
114
    OS << "; did you mean to ";
179
180
135
  if (IsComparison)
181
48
    OS << "compare the result of calling " << SuggestedApi;
182
87
  else
183
87
    OS << "call " << SuggestedApi;
184
185
135
  if (!IsPedanticMatch)
186
114
    OS << "?";
187
188
135
  BR.EmitBasicReport(
189
135
      ADC->getDecl(), C, "Suspicious number object conversion", "Logic error",
190
135
      OS.str(),
191
135
      PathDiagnosticLocation::createBegin(Obj, BR.getSourceManager(), ADC),
192
135
      Conv->getSourceRange());
193
135
}
194
195
void NumberObjectConversionChecker::checkASTCodeBody(const Decl *D,
196
                                                     AnalysisManager &AM,
197
276
                                                     BugReporter &BR) const {
198
  // Currently this matches CoreFoundation opaque pointer typedefs.
199
276
  auto CSuspiciousNumberObjectExprM = expr(ignoringParenImpCasts(
200
276
      expr(hasType(elaboratedType(namesType(typedefType(
201
276
               hasDeclaration(anyOf(typedefDecl(hasName("CFNumberRef")),
202
276
                                    typedefDecl(hasName("CFBooleanRef")))))))))
203
276
          .bind("c_object")));
204
205
  // Currently this matches XNU kernel number-object pointers.
206
276
  auto CppSuspiciousNumberObjectExprM =
207
276
      expr(ignoringParenImpCasts(
208
276
          expr(hasType(hasCanonicalType(
209
276
              pointerType(pointee(hasCanonicalType(
210
276
                  recordType(hasDeclaration(
211
276
                      anyOf(
212
276
                        cxxRecordDecl(hasName("OSBoolean")),
213
276
                        cxxRecordDecl(hasName("OSNumber"))
214
276
                            .bind("osnumber"))))))))))
215
276
          .bind("cpp_object")));
216
217
  // Currently this matches NeXTSTEP number objects.
218
276
  auto ObjCSuspiciousNumberObjectExprM =
219
276
      expr(ignoringParenImpCasts(
220
276
          expr(hasType(hasCanonicalType(
221
276
              objcObjectPointerType(pointee(
222
276
                  qualType(hasCanonicalType(
223
276
                      qualType(hasDeclaration(
224
276
                          objcInterfaceDecl(hasName("NSNumber")))))))))))
225
276
          .bind("objc_object")));
226
227
276
  auto SuspiciousNumberObjectExprM = anyOf(
228
276
      CSuspiciousNumberObjectExprM,
229
276
      CppSuspiciousNumberObjectExprM,
230
276
      ObjCSuspiciousNumberObjectExprM);
231
232
  // Useful for predicates like "Unless we've seen the same object elsewhere".
233
276
  auto AnotherSuspiciousNumberObjectExprM =
234
276
      expr(anyOf(
235
276
          equalsBoundNode("c_object"),
236
276
          equalsBoundNode("objc_object"),
237
276
          equalsBoundNode("cpp_object")));
238
239
  // The .bind here is in order to compose the error message more accurately.
240
276
  auto ObjCSuspiciousScalarBooleanTypeM =
241
276
      qualType(elaboratedType(namesType(
242
276
                   typedefType(hasDeclaration(typedefDecl(hasName("BOOL")))))))
243
276
          .bind("objc_bool_type");
244
245
  // The .bind here is in order to compose the error message more accurately.
246
276
  auto SuspiciousScalarBooleanTypeM =
247
276
      qualType(anyOf(qualType(booleanType()).bind("cpp_bool_type"),
248
276
                     ObjCSuspiciousScalarBooleanTypeM));
249
250
  // The .bind here is in order to compose the error message more accurately.
251
  // Also avoid intptr_t and uintptr_t because they were specifically created
252
  // for storing pointers.
253
276
  auto SuspiciousScalarNumberTypeM =
254
276
      qualType(hasCanonicalType(isInteger()),
255
276
               unless(elaboratedType(namesType(typedefType(hasDeclaration(
256
276
                   typedefDecl(matchesName("^::u?intptr_t$"))))))))
257
276
          .bind("int_type");
258
259
276
  auto SuspiciousScalarTypeM =
260
276
      qualType(anyOf(SuspiciousScalarBooleanTypeM,
261
276
                     SuspiciousScalarNumberTypeM));
262
263
276
  auto SuspiciousScalarExprM =
264
276
      expr(ignoringParenImpCasts(expr(hasType(SuspiciousScalarTypeM))));
265
266
276
  auto ConversionThroughAssignmentM =
267
276
      binaryOperator(allOf(hasOperatorName("="),
268
276
                           hasLHS(SuspiciousScalarExprM),
269
276
                           hasRHS(SuspiciousNumberObjectExprM)));
270
271
276
  auto ConversionThroughBranchingM =
272
276
      ifStmt(allOf(
273
276
          hasCondition(SuspiciousNumberObjectExprM),
274
276
          unless(hasConditionVariableStatement(declStmt())
275
276
      ))).bind("pedantic");
276
277
276
  auto ConversionThroughCallM =
278
276
      callExpr(hasAnyArgument(allOf(hasType(SuspiciousScalarTypeM),
279
276
                                    ignoringParenImpCasts(
280
276
                                        SuspiciousNumberObjectExprM))));
281
282
  // We bind "check_if_null" to modify the warning message
283
  // in case it was intended to compare a pointer to 0 with a relatively-ok
284
  // construct "x == 0" or "x != 0".
285
276
  auto ConversionThroughEquivalenceM =
286
276
      binaryOperator(allOf(anyOf(hasOperatorName("=="), hasOperatorName("!=")),
287
276
                           hasEitherOperand(SuspiciousNumberObjectExprM),
288
276
                           hasEitherOperand(SuspiciousScalarExprM
289
276
                                            .bind("check_if_null"))))
290
276
      .bind("comparison");
291
292
276
  auto ConversionThroughComparisonM =
293
276
      binaryOperator(allOf(anyOf(hasOperatorName(">="), hasOperatorName(">"),
294
276
                                 hasOperatorName("<="), hasOperatorName("<")),
295
276
                           hasEitherOperand(SuspiciousNumberObjectExprM),
296
276
                           hasEitherOperand(SuspiciousScalarExprM)))
297
276
      .bind("comparison");
298
299
276
  auto ConversionThroughConditionalOperatorM =
300
276
      conditionalOperator(allOf(
301
276
          hasCondition(SuspiciousNumberObjectExprM),
302
276
          unless(hasTrueExpression(
303
276
              hasDescendant(AnotherSuspiciousNumberObjectExprM))),
304
276
          unless(hasFalseExpression(
305
276
              hasDescendant(AnotherSuspiciousNumberObjectExprM)))))
306
276
      .bind("pedantic");
307
308
276
  auto ConversionThroughExclamationMarkM =
309
276
      unaryOperator(allOf(hasOperatorName("!"),
310
276
                          has(expr(SuspiciousNumberObjectExprM))))
311
276
      .bind("pedantic");
312
313
276
  auto ConversionThroughExplicitBooleanCastM =
314
276
      explicitCastExpr(allOf(hasType(SuspiciousScalarBooleanTypeM),
315
276
                             has(expr(SuspiciousNumberObjectExprM))));
316
317
276
  auto ConversionThroughExplicitNumberCastM =
318
276
      explicitCastExpr(allOf(hasType(SuspiciousScalarNumberTypeM),
319
276
                             has(expr(SuspiciousNumberObjectExprM))));
320
321
276
  auto ConversionThroughInitializerM =
322
276
      declStmt(hasSingleDecl(
323
276
          varDecl(hasType(SuspiciousScalarTypeM),
324
276
                  hasInitializer(SuspiciousNumberObjectExprM))));
325
326
276
  auto FinalM = stmt(anyOf(ConversionThroughAssignmentM,
327
276
                           ConversionThroughBranchingM,
328
276
                           ConversionThroughCallM,
329
276
                           ConversionThroughComparisonM,
330
276
                           ConversionThroughConditionalOperatorM,
331
276
                           ConversionThroughEquivalenceM,
332
276
                           ConversionThroughExclamationMarkM,
333
276
                           ConversionThroughExplicitBooleanCastM,
334
276
                           ConversionThroughExplicitNumberCastM,
335
276
                           ConversionThroughInitializerM)).bind("conv");
336
337
276
  MatchFinder F;
338
276
  Callback CB(this, BR, AM.getAnalysisDeclContext(D));
339
340
276
  F.addMatcher(traverse(TK_AsIs, stmt(forEachDescendant(FinalM))), &CB);
341
276
  F.match(*D->getBody(), AM.getASTContext());
342
276
}
343
344
55
void ento::registerNumberObjectConversionChecker(CheckerManager &Mgr) {
345
55
  NumberObjectConversionChecker *Chk =
346
55
      Mgr.registerChecker<NumberObjectConversionChecker>();
347
55
  Chk->Pedantic =
348
55
      Mgr.getAnalyzerOptions().getCheckerBooleanOption(Chk, "Pedantic");
349
55
}
350
351
110
bool ento::shouldRegisterNumberObjectConversionChecker(const CheckerManager &mgr) {
352
110
  return true;
353
110
}