Coverage Report

Created: 2019-07-24 05:18

/Users/buildslave/jenkins/workspace/clang-stage2-coverage-R/llvm/tools/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
230
      : C(C), BR(BR), ADC(ADC) {}
60
  virtual void run(const MatchFinder::MatchResult &Result);
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
147
70
147
  ASTContext &ACtx = ADC->getASTContext();
71
147
72
147
  if (const Expr *CheckIfNull =
73
54
          Result.Nodes.getNodeAs<Expr>("check_if_null")) {
74
54
    // Unless the macro indicates that the intended type is clearly not
75
54
    // a pointer type, we should avoid warning on comparing pointers
76
54
    // to zero literals in non-pedantic mode.
77
54
    // 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
54
    
if (48
!MacroIndicatesWeShouldSkipTheCheck48
) {
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
147
102
147
  const Stmt *Conv = Result.Nodes.getNodeAs<Stmt>("conv");
103
135
  assert(Conv);
104
135
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
                  : 
IsCpp 45
?
ConvertedCppObject27
112
45
                  : 
ConvertedCObject18
;
113
135
  assert(Obj);
114
135
115
135
  bool IsComparison =
116
135
      (Result.Nodes.getNodeAs<Stmt>("comparison") != nullptr);
117
135
118
135
  bool IsOSNumber =
119
135
      (Result.Nodes.getNodeAs<Decl>("osnumber") != nullptr);
120
135
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
135
128
135
  llvm::SmallString<64> Msg;
129
135
  llvm::raw_svector_ostream OS(Msg);
130
135
131
135
  // Remove ObjC ARC qualifiers.
132
135
  QualType ObjT = Obj->getType().getUnqualifiedType();
133
135
134
135
  // 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
135
141
135
  if (IsComparison)
142
48
    OS << "Comparing ";
143
87
  else
144
87
    OS << "Converting ";
145
135
146
135
  OS << "a pointer value of type '" << ObjT.getAsString() << "' to a ";
147
135
148
135
  std::string EuphemismForPlain = "primitive";
149
135
  std::string SuggestedApi = IsObjC ? 
(IsInteger 90
?
""40
:
"-boolValue"50
)
150
135
                           : 
IsCpp 45
?
(IsOSNumber 27
?
""8
:
"getValue()"19
)
151
45
                           : 
"CFNumberGetValue()"18
;
152
135
  if (SuggestedApi.empty()) {
153
48
    // A generic message if we're not sure what API should be called.
154
48
    // 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
48
    // "scalar" is not quite correct or common, but some documentation uses it
158
48
    // when describing object methods we suggest. For consistency, we use
159
48
    // "scalar" in the whole sentence when we need to use this word in at least
160
48
    // one place, otherwise we use "primitive".
161
48
    EuphemismForPlain = "scalar";
162
48
  }
163
135
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
135
173
135
174
135
  if (IsPedanticMatch)
175
21
    OS << "; instead, either compare the pointer to "
176
21
       << (IsObjC ? 
"nil"10
:
IsCpp 11
?
"nullptr"7
:
"NULL"4
) << " or ";
177
114
  else
178
114
    OS << "; did you mean to ";
179
135
180
135
  if (IsComparison)
181
48
    OS << "compare the result of calling " << SuggestedApi;
182
87
  else
183
87
    OS << "call " << SuggestedApi;
184
135
185
135
  if (!IsPedanticMatch)
186
114
    OS << "?";
187
135
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
230
                                                     BugReporter &BR) const {
198
230
  // Currently this matches CoreFoundation opaque pointer typedefs.
199
230
  auto CSuspiciousNumberObjectExprM =
200
230
      expr(ignoringParenImpCasts(
201
230
          expr(hasType(
202
230
              typedefType(hasDeclaration(anyOf(
203
230
                  typedefDecl(hasName("CFNumberRef")),
204
230
                  typedefDecl(hasName("CFBooleanRef")))))))
205
230
          .bind("c_object")));
206
230
207
230
  // Currently this matches XNU kernel number-object pointers.
208
230
  auto CppSuspiciousNumberObjectExprM =
209
230
      expr(ignoringParenImpCasts(
210
230
          expr(hasType(hasCanonicalType(
211
230
              pointerType(pointee(hasCanonicalType(
212
230
                  recordType(hasDeclaration(
213
230
                      anyOf(
214
230
                        cxxRecordDecl(hasName("OSBoolean")),
215
230
                        cxxRecordDecl(hasName("OSNumber"))
216
230
                            .bind("osnumber"))))))))))
217
230
          .bind("cpp_object")));
218
230
219
230
  // Currently this matches NeXTSTEP number objects.
220
230
  auto ObjCSuspiciousNumberObjectExprM =
221
230
      expr(ignoringParenImpCasts(
222
230
          expr(hasType(hasCanonicalType(
223
230
              objcObjectPointerType(pointee(
224
230
                  qualType(hasCanonicalType(
225
230
                      qualType(hasDeclaration(
226
230
                          objcInterfaceDecl(hasName("NSNumber")))))))))))
227
230
          .bind("objc_object")));
228
230
229
230
  auto SuspiciousNumberObjectExprM = anyOf(
230
230
      CSuspiciousNumberObjectExprM,
231
230
      CppSuspiciousNumberObjectExprM,
232
230
      ObjCSuspiciousNumberObjectExprM);
233
230
234
230
  // Useful for predicates like "Unless we've seen the same object elsewhere".
235
230
  auto AnotherSuspiciousNumberObjectExprM =
236
230
      expr(anyOf(
237
230
          equalsBoundNode("c_object"),
238
230
          equalsBoundNode("objc_object"),
239
230
          equalsBoundNode("cpp_object")));
240
230
241
230
  // The .bind here is in order to compose the error message more accurately.
242
230
  auto ObjCSuspiciousScalarBooleanTypeM =
243
230
      qualType(typedefType(hasDeclaration(
244
230
                   typedefDecl(hasName("BOOL"))))).bind("objc_bool_type");
245
230
246
230
  // The .bind here is in order to compose the error message more accurately.
247
230
  auto SuspiciousScalarBooleanTypeM =
248
230
      qualType(anyOf(qualType(booleanType()).bind("cpp_bool_type"),
249
230
                     ObjCSuspiciousScalarBooleanTypeM));
250
230
251
230
  // The .bind here is in order to compose the error message more accurately.
252
230
  // Also avoid intptr_t and uintptr_t because they were specifically created
253
230
  // for storing pointers.
254
230
  auto SuspiciousScalarNumberTypeM =
255
230
      qualType(hasCanonicalType(isInteger()),
256
230
               unless(typedefType(hasDeclaration(
257
230
                   typedefDecl(matchesName("^::u?intptr_t$"))))))
258
230
      .bind("int_type");
259
230
260
230
  auto SuspiciousScalarTypeM =
261
230
      qualType(anyOf(SuspiciousScalarBooleanTypeM,
262
230
                     SuspiciousScalarNumberTypeM));
263
230
264
230
  auto SuspiciousScalarExprM =
265
230
      expr(ignoringParenImpCasts(expr(hasType(SuspiciousScalarTypeM))));
266
230
267
230
  auto ConversionThroughAssignmentM =
268
230
      binaryOperator(allOf(hasOperatorName("="),
269
230
                           hasLHS(SuspiciousScalarExprM),
270
230
                           hasRHS(SuspiciousNumberObjectExprM)));
271
230
272
230
  auto ConversionThroughBranchingM =
273
230
      ifStmt(allOf(
274
230
          hasCondition(SuspiciousNumberObjectExprM),
275
230
          unless(hasConditionVariableStatement(declStmt())
276
230
      ))).bind("pedantic");
277
230
278
230
  auto ConversionThroughCallM =
279
230
      callExpr(hasAnyArgument(allOf(hasType(SuspiciousScalarTypeM),
280
230
                                    ignoringParenImpCasts(
281
230
                                        SuspiciousNumberObjectExprM))));
282
230
283
230
  // We bind "check_if_null" to modify the warning message
284
230
  // in case it was intended to compare a pointer to 0 with a relatively-ok
285
230
  // construct "x == 0" or "x != 0".
286
230
  auto ConversionThroughEquivalenceM =
287
230
      binaryOperator(allOf(anyOf(hasOperatorName("=="), hasOperatorName("!=")),
288
230
                           hasEitherOperand(SuspiciousNumberObjectExprM),
289
230
                           hasEitherOperand(SuspiciousScalarExprM
290
230
                                            .bind("check_if_null"))))
291
230
      .bind("comparison");
292
230
293
230
  auto ConversionThroughComparisonM =
294
230
      binaryOperator(allOf(anyOf(hasOperatorName(">="), hasOperatorName(">"),
295
230
                                 hasOperatorName("<="), hasOperatorName("<")),
296
230
                           hasEitherOperand(SuspiciousNumberObjectExprM),
297
230
                           hasEitherOperand(SuspiciousScalarExprM)))
298
230
      .bind("comparison");
299
230
300
230
  auto ConversionThroughConditionalOperatorM =
301
230
      conditionalOperator(allOf(
302
230
          hasCondition(SuspiciousNumberObjectExprM),
303
230
          unless(hasTrueExpression(
304
230
              hasDescendant(AnotherSuspiciousNumberObjectExprM))),
305
230
          unless(hasFalseExpression(
306
230
              hasDescendant(AnotherSuspiciousNumberObjectExprM)))))
307
230
      .bind("pedantic");
308
230
309
230
  auto ConversionThroughExclamationMarkM =
310
230
      unaryOperator(allOf(hasOperatorName("!"),
311
230
                          has(expr(SuspiciousNumberObjectExprM))))
312
230
      .bind("pedantic");
313
230
314
230
  auto ConversionThroughExplicitBooleanCastM =
315
230
      explicitCastExpr(allOf(hasType(SuspiciousScalarBooleanTypeM),
316
230
                             has(expr(SuspiciousNumberObjectExprM))));
317
230
318
230
  auto ConversionThroughExplicitNumberCastM =
319
230
      explicitCastExpr(allOf(hasType(SuspiciousScalarNumberTypeM),
320
230
                             has(expr(SuspiciousNumberObjectExprM))));
321
230
322
230
  auto ConversionThroughInitializerM =
323
230
      declStmt(hasSingleDecl(
324
230
          varDecl(hasType(SuspiciousScalarTypeM),
325
230
                  hasInitializer(SuspiciousNumberObjectExprM))));
326
230
327
230
  auto FinalM = stmt(anyOf(ConversionThroughAssignmentM,
328
230
                           ConversionThroughBranchingM,
329
230
                           ConversionThroughCallM,
330
230
                           ConversionThroughComparisonM,
331
230
                           ConversionThroughConditionalOperatorM,
332
230
                           ConversionThroughEquivalenceM,
333
230
                           ConversionThroughExclamationMarkM,
334
230
                           ConversionThroughExplicitBooleanCastM,
335
230
                           ConversionThroughExplicitNumberCastM,
336
230
                           ConversionThroughInitializerM)).bind("conv");
337
230
338
230
  MatchFinder F;
339
230
  Callback CB(this, BR, AM.getAnalysisDeclContext(D));
340
230
341
230
  F.addMatcher(stmt(forEachDescendant(FinalM)), &CB);
342
230
  F.match(*D->getBody(), AM.getASTContext());
343
230
}
344
345
32
void ento::registerNumberObjectConversionChecker(CheckerManager &Mgr) {
346
32
  NumberObjectConversionChecker *Chk =
347
32
      Mgr.registerChecker<NumberObjectConversionChecker>();
348
32
  Chk->Pedantic =
349
32
      Mgr.getAnalyzerOptions().getCheckerBooleanOption(Chk, "Pedantic");
350
32
}
351
352
32
bool ento::shouldRegisterNumberObjectConversionChecker(const LangOptions &LO) {
353
32
  return true;
354
32
}