File: BugSuppression.cpp

package info (click to toggle)
llvm-toolchain-20 1%3A20.1.6-1~exp1
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 2,111,304 kB
  • sloc: cpp: 7,438,677; ansic: 1,393,822; asm: 1,012,926; python: 241,650; f90: 86,635; objc: 75,479; lisp: 42,144; pascal: 17,286; sh: 10,027; ml: 5,082; perl: 4,730; awk: 3,523; makefile: 3,349; javascript: 2,251; xml: 892; fortran: 672
file content (191 lines) | stat: -rw-r--r-- 7,537 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
//===- BugSuppression.cpp - Suppression interface -------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "clang/StaticAnalyzer/Core/BugReporter/BugSuppression.h"
#include "clang/AST/DynamicRecursiveASTVisitor.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"

using namespace clang;
using namespace ento;

namespace {

using Ranges = llvm::SmallVectorImpl<SourceRange>;

inline bool hasSuppression(const Decl *D) {
  // FIXME: Implement diagnostic identifier arguments
  // (checker names, "hashtags").
  if (const auto *Suppression = D->getAttr<SuppressAttr>())
    return !Suppression->isGSL() &&
           (Suppression->diagnosticIdentifiers().empty());
  return false;
}
inline bool hasSuppression(const AttributedStmt *S) {
  // FIXME: Implement diagnostic identifier arguments
  // (checker names, "hashtags").
  return llvm::any_of(S->getAttrs(), [](const Attr *A) {
    const auto *Suppression = dyn_cast<SuppressAttr>(A);
    return Suppression && !Suppression->isGSL() &&
           (Suppression->diagnosticIdentifiers().empty());
  });
}

template <class NodeType> inline SourceRange getRange(const NodeType *Node) {
  return Node->getSourceRange();
}
template <> inline SourceRange getRange(const AttributedStmt *S) {
  // Begin location for attributed statement node seems to be ALWAYS invalid.
  //
  // It is unlikely that we ever report any warnings on suppression
  // attribute itself, but even if we do, we wouldn't want that warning
  // to be suppressed by that same attribute.
  //
  // Long story short, we can use inner statement and it's not going to break
  // anything.
  return getRange(S->getSubStmt());
}

inline bool isLessOrEqual(SourceLocation LHS, SourceLocation RHS,
                          const SourceManager &SM) {
  // SourceManager::isBeforeInTranslationUnit tests for strict
  // inequality, when we need a non-strict comparison (bug
  // can be reported directly on the annotated note).
  // For this reason, we use the following equivalence:
  //
  //   A <= B <==> !(B < A)
  //
  return !SM.isBeforeInTranslationUnit(RHS, LHS);
}

inline bool fullyContains(SourceRange Larger, SourceRange Smaller,
                          const SourceManager &SM) {
  // Essentially this means:
  //
  //   Larger.fullyContains(Smaller)
  //
  // However, that method has a very trivial implementation and couldn't
  // compare regular locations and locations from macro expansions.
  // We could've converted everything into regular locations as a solution,
  // but the following solution seems to be the most bulletproof.
  return isLessOrEqual(Larger.getBegin(), Smaller.getBegin(), SM) &&
         isLessOrEqual(Smaller.getEnd(), Larger.getEnd(), SM);
}

class CacheInitializer : public DynamicRecursiveASTVisitor {
public:
  static void initialize(const Decl *D, Ranges &ToInit) {
    CacheInitializer(ToInit).TraverseDecl(const_cast<Decl *>(D));
  }

  bool VisitDecl(Decl *D) override {
    // Bug location could be somewhere in the init value of
    // a freshly declared variable.  Even though it looks like the
    // user applied attribute to a statement, it will apply to a
    // variable declaration, and this is where we check for it.
    return VisitAttributedNode(D);
  }

  bool VisitAttributedStmt(AttributedStmt *AS) override {
    // When we apply attributes to statements, it actually creates
    // a wrapper statement that only contains attributes and the wrapped
    // statement.
    return VisitAttributedNode(AS);
  }

private:
  template <class NodeType> bool VisitAttributedNode(NodeType *Node) {
    if (hasSuppression(Node)) {
      // TODO: In the future, when we come up with good stable IDs for checkers
      //       we can return a list of kinds to ignore, or all if no arguments
      //       were provided.
      addRange(getRange(Node));
    }
    // We should keep traversing AST.
    return true;
  }

  void addRange(SourceRange R) {
    if (R.isValid()) {
      Result.push_back(R);
    }
  }

  CacheInitializer(Ranges &R) : Result(R) {}
  Ranges &Result;
};

} // end anonymous namespace

// TODO: Introduce stable IDs for checkers and check for those here
//       to be more specific.  Attribute without arguments should still
//       be considered as "suppress all".
//       It is already much finer granularity than what we have now
//       (i.e. removing the whole function from the analysis).
bool BugSuppression::isSuppressed(const BugReport &R) {
  PathDiagnosticLocation Location = R.getLocation();
  PathDiagnosticLocation UniqueingLocation = R.getUniqueingLocation();
  const Decl *DeclWithIssue = R.getDeclWithIssue();

  return isSuppressed(Location, DeclWithIssue, {}) ||
         isSuppressed(UniqueingLocation, DeclWithIssue, {});
}

bool BugSuppression::isSuppressed(const PathDiagnosticLocation &Location,
                                  const Decl *DeclWithIssue,
                                  DiagnosticIdentifierList Hashtags) {
  if (!Location.isValid())
    return false;

  if (!DeclWithIssue) {
    // FIXME: This defeats the purpose of passing DeclWithIssue to begin with.
    // If this branch is ever hit, we're re-doing all the work we've already
    // done as well as perform a lot of work we'll never need.
    // Gladly, none of our on-by-default checkers currently need it.
    DeclWithIssue = ACtx.getTranslationUnitDecl();
  } else {
    // This is the fast path. However, we should still consider the topmost
    // declaration that isn't TranslationUnitDecl, because we should respect
    // attributes on the entire declaration chain.
    while (true) {
      // Use the "lexical" parent. Eg., if the attribute is on a class, suppress
      // warnings in inline methods but not in out-of-line methods.
      const Decl *Parent =
          dyn_cast_or_null<Decl>(DeclWithIssue->getLexicalDeclContext());
      if (Parent == nullptr || isa<TranslationUnitDecl>(Parent))
        break;

      DeclWithIssue = Parent;
    }
  }

  // While some warnings are attached to AST nodes (mostly path-sensitive
  // checks), others are simply associated with a plain source location
  // or range.  Figuring out the node based on locations can be tricky,
  // so instead, we traverse the whole body of the declaration and gather
  // information on ALL suppressions.  After that we can simply check if
  // any of those suppressions affect the warning in question.
  //
  // Traversing AST of a function is not a heavy operation, but for
  // large functions with a lot of bugs it can make a dent in performance.
  // In order to avoid this scenario, we cache traversal results.
  auto InsertionResult = CachedSuppressionLocations.insert(
      std::make_pair(DeclWithIssue, CachedRanges{}));
  Ranges &SuppressionRanges = InsertionResult.first->second;
  if (InsertionResult.second) {
    // We haven't checked this declaration for suppressions yet!
    CacheInitializer::initialize(DeclWithIssue, SuppressionRanges);
  }

  SourceRange BugRange = Location.asRange();
  const SourceManager &SM = Location.getManager();

  return llvm::any_of(SuppressionRanges,
                      [BugRange, &SM](SourceRange Suppression) {
                        return fullyContains(Suppression, BugRange, SM);
                      });
}