Coverage Report

Created: 2020-10-24 06:27

/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
257
      : 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
54
          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
90
  const Expr *Obj = IsObjC ? ConvertedObjCObject
111
45
                  : IsCpp ? 
ConvertedCppObject27
112
18
                  : ConvertedCObject;
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.getAsString() << "' to a ";
147
148
135
  std::string EuphemismForPlain = "primitive";
149
90
  std::string SuggestedApi = IsObjC ? (IsInteger ? 
""40
:
"-boolValue"50
)
150
45
                           : IsCpp ? 
(IsOSNumber 27
?
""8
:
"getValue()"19
)
151
18
                           : "CFNumberGetValue()";
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
11
       << (IsObjC ? 
"nil"10
: IsCpp ?
"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
257
                                                     BugReporter &BR) const {
198
  // Currently this matches CoreFoundation opaque pointer typedefs.
199
257
  auto CSuspiciousNumberObjectExprM =
200
257
      expr(ignoringParenImpCasts(
201
257
          expr(hasType(
202
257
              typedefType(hasDeclaration(anyOf(
203
257
                  typedefDecl(hasName("CFNumberRef")),
204
257
                  typedefDecl(hasName("CFBooleanRef")))))))
205
257
          .bind("c_object")));
206
207
  // Currently this matches XNU kernel number-object pointers.
208
257
  auto CppSuspiciousNumberObjectExprM =
209
257
      expr(ignoringParenImpCasts(
210
257
          expr(hasType(hasCanonicalType(
211
257
              pointerType(pointee(hasCanonicalType(
212
257
                  recordType(hasDeclaration(
213
257
                      anyOf(
214
257
                        cxxRecordDecl(hasName("OSBoolean")),
215
257
                        cxxRecordDecl(hasName("OSNumber"))
216
257
                            .bind("osnumber"))))))))))
217
257
          .bind("cpp_object")));
218
219
  // Currently this matches NeXTSTEP number objects.
220
257
  auto ObjCSuspiciousNumberObjectExprM =
221
257
      expr(ignoringParenImpCasts(
222
257
          expr(hasType(hasCanonicalType(
223
257
              objcObjectPointerType(pointee(
224
257
                  qualType(hasCanonicalType(
225
257
                      qualType(hasDeclaration(
226
257
                          objcInterfaceDecl(hasName("NSNumber")))))))))))
227
257
          .bind("objc_object")));
228
229
257
  auto SuspiciousNumberObjectExprM = anyOf(
230
257
      CSuspiciousNumberObjectExprM,
231
257
      CppSuspiciousNumberObjectExprM,
232
257
      ObjCSuspiciousNumberObjectExprM);
233
234
  // Useful for predicates like "Unless we've seen the same object elsewhere".
235
257
  auto AnotherSuspiciousNumberObjectExprM =
236
257
      expr(anyOf(
237
257
          equalsBoundNode("c_object"),
238
257
          equalsBoundNode("objc_object"),
239
257
          equalsBoundNode("cpp_object")));
240
241
  // The .bind here is in order to compose the error message more accurately.
242
257
  auto ObjCSuspiciousScalarBooleanTypeM =
243
257
      qualType(typedefType(hasDeclaration(
244
257
                   typedefDecl(hasName("BOOL"))))).bind("objc_bool_type");
245
246
  // The .bind here is in order to compose the error message more accurately.
247
257
  auto SuspiciousScalarBooleanTypeM =
248
257
      qualType(anyOf(qualType(booleanType()).bind("cpp_bool_type"),
249
257
                     ObjCSuspiciousScalarBooleanTypeM));
250
251
  // The .bind here is in order to compose the error message more accurately.
252
  // Also avoid intptr_t and uintptr_t because they were specifically created
253
  // for storing pointers.
254
257
  auto SuspiciousScalarNumberTypeM =
255
257
      qualType(hasCanonicalType(isInteger()),
256
257
               unless(typedefType(hasDeclaration(
257
257
                   typedefDecl(matchesName("^::u?intptr_t$"))))))
258
257
      .bind("int_type");
259
260
257
  auto SuspiciousScalarTypeM =
261
257
      qualType(anyOf(SuspiciousScalarBooleanTypeM,
262
257
                     SuspiciousScalarNumberTypeM));
263
264
257
  auto SuspiciousScalarExprM =
265
257
      expr(ignoringParenImpCasts(expr(hasType(SuspiciousScalarTypeM))));
266
267
257
  auto ConversionThroughAssignmentM =
268
257
      binaryOperator(allOf(hasOperatorName("="),
269
257
                           hasLHS(SuspiciousScalarExprM),
270
257
                           hasRHS(SuspiciousNumberObjectExprM)));
271
272
257
  auto ConversionThroughBranchingM =
273
257
      ifStmt(allOf(
274
257
          hasCondition(SuspiciousNumberObjectExprM),
275
257
          unless(hasConditionVariableStatement(declStmt())
276
257
      ))).bind("pedantic");
277
278
257
  auto ConversionThroughCallM =
279
257
      callExpr(hasAnyArgument(allOf(hasType(SuspiciousScalarTypeM),
280
257
                                    ignoringParenImpCasts(
281
257
                                        SuspiciousNumberObjectExprM))));
282
283
  // We bind "check_if_null" to modify the warning message
284
  // in case it was intended to compare a pointer to 0 with a relatively-ok
285
  // construct "x == 0" or "x != 0".
286
257
  auto ConversionThroughEquivalenceM =
287
257
      binaryOperator(allOf(anyOf(hasOperatorName("=="), hasOperatorName("!=")),
288
257
                           hasEitherOperand(SuspiciousNumberObjectExprM),
289
257
                           hasEitherOperand(SuspiciousScalarExprM
290
257
                                            .bind("check_if_null"))))
291
257
      .bind("comparison");
292
293
257
  auto ConversionThroughComparisonM =
294
257
      binaryOperator(allOf(anyOf(hasOperatorName(">="), hasOperatorName(">"),
295
257
                                 hasOperatorName("<="), hasOperatorName("<")),
296
257
                           hasEitherOperand(SuspiciousNumberObjectExprM),
297
257
                           hasEitherOperand(SuspiciousScalarExprM)))
298
257
      .bind("comparison");
299
300
257
  auto ConversionThroughConditionalOperatorM =
301
257
      conditionalOperator(allOf(
302
257
          hasCondition(SuspiciousNumberObjectExprM),
303
257
          unless(hasTrueExpression(
304
257
              hasDescendant(AnotherSuspiciousNumberObjectExprM))),
305
257
          unless(hasFalseExpression(
306
257
              hasDescendant(AnotherSuspiciousNumberObjectExprM)))))
307
257
      .bind("pedantic");
308
309
257
  auto ConversionThroughExclamationMarkM =
310
257
      unaryOperator(allOf(hasOperatorName("!"),
311
257
                          has(expr(SuspiciousNumberObjectExprM))))
312
257
      .bind("pedantic");
313
314
257
  auto ConversionThroughExplicitBooleanCastM =
315
257
      explicitCastExpr(allOf(hasType(SuspiciousScalarBooleanTypeM),
316
257
                             has(expr(SuspiciousNumberObjectExprM))));
317
318
257
  auto ConversionThroughExplicitNumberCastM =
319
257
      explicitCastExpr(allOf(hasType(SuspiciousScalarNumberTypeM),
320
257
                             has(expr(SuspiciousNumberObjectExprM))));
321
322
257
  auto ConversionThroughInitializerM =
323
257
      declStmt(hasSingleDecl(
324
257
          varDecl(hasType(SuspiciousScalarTypeM),
325
257
                  hasInitializer(SuspiciousNumberObjectExprM))));
326
327
257
  auto FinalM = stmt(anyOf(ConversionThroughAssignmentM,
328
257
                           ConversionThroughBranchingM,
329
257
                           ConversionThroughCallM,
330
257
                           ConversionThroughComparisonM,
331
257
                           ConversionThroughConditionalOperatorM,
332
257
                           ConversionThroughEquivalenceM,
333
257
                           ConversionThroughExclamationMarkM,
334
257
                           ConversionThroughExplicitBooleanCastM,
335
257
                           ConversionThroughExplicitNumberCastM,
336
257
                           ConversionThroughInitializerM)).bind("conv");
337
338
257
  MatchFinder F;
339
257
  Callback CB(this, BR, AM.getAnalysisDeclContext(D));
340
341
257
  F.addMatcher(traverse(TK_AsIs, stmt(forEachDescendant(FinalM))), &CB);
342
257
  F.match(*D->getBody(), AM.getASTContext());
343
257
}
344
345
46
void ento::registerNumberObjectConversionChecker(CheckerManager &Mgr) {
346
46
  NumberObjectConversionChecker *Chk =
347
46
      Mgr.registerChecker<NumberObjectConversionChecker>();
348
46
  Chk->Pedantic =
349
46
      Mgr.getAnalyzerOptions().getCheckerBooleanOption(Chk, "Pedantic");
350
46
}
351
352
92
bool ento::shouldRegisterNumberObjectConversionChecker(const CheckerManager &mgr) {
353
92
  return true;
354
92
}