Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[analyzer] Unbreak [[clang::suppress]] on checkers without decl-with-issue. #79398

Conversation

haoNoQ
Copy link
Collaborator

@haoNoQ haoNoQ commented Jan 25, 2024

There are currently a few checkers that don't fill in the bug report's "decl-with-issue" field (typically a function in which the bug is found).

The new attribute [[clang::suppress]] uses decl-with-issue to reduce the size of the suppression source range map so that it didn't need to do that for the entire translation unit.

I'm already seeing a few problems with this approach so I'll probably redesign it in some point as it looks like a premature optimization. Not only checkers shouldn't be required to pass decl-with-issue (consider clang-tidy checkers that never had such notion), but also it's not necessarily uniquely determined (consider leak suppressions at allocation site).

For now I'm adding a simple stop-gap solution that falls back to building the suppression map for the entire TU whenever decl-with-issue isn't specified. Which won't happen in the default setup because luckily all default checkers do provide decl-with-issue.

…issue.

There are currently a few checkers that don't fill in the bug report's
"decl with issue" field (typically a function in which the bug is found).

The new attribute [[clang::suppress]] uses decl-with-issue to reduce
the size of the suppression source range map so that it didn't need
to do that for the entire translation unit.

I'm already seeing a few problems with this approach so I'll probably
redesign it in some point as it looks like a premature optimization.
Not only checkers shouldn't be required to pass decl-with-issue
(consider clang-tidy checkers that never had such notion),
but also it's not necessarily uniquely determined (consider
leak suppressions at allocation site).

For now I'm adding a simple stop-gap solution that falls back to
building the suppression map for the entire TU whenever decl-with-issue
isn't specified. Which typically won't happen by
default because luckily all default checkers do provide decl-with-issue.
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:static analyzer labels Jan 25, 2024
@llvmbot
Copy link
Collaborator

llvmbot commented Jan 25, 2024

@llvm/pr-subscribers-clang-static-analyzer-1

Author: Artem Dergachev (haoNoQ)

Changes

There are currently a few checkers that don't fill in the bug report's "decl-with-issue" field (typically a function in which the bug is found).

The new attribute [[clang::suppress]] uses decl-with-issue to reduce the size of the suppression source range map so that it didn't need to do that for the entire translation unit.

I'm already seeing a few problems with this approach so I'll probably redesign it in some point as it looks like a premature optimization. Not only checkers shouldn't be required to pass decl-with-issue (consider clang-tidy checkers that never had such notion), but also it's not necessarily uniquely determined (consider leak suppressions at allocation site).

For now I'm adding a simple stop-gap solution that falls back to building the suppression map for the entire TU whenever decl-with-issue isn't specified. Which won't happen in the default setup because luckily all default checkers do provide decl-with-issue.


Full diff: https://github.com/llvm/llvm-project/pull/79398.diff

5 Files Affected:

  • (modified) clang/include/clang/StaticAnalyzer/Core/BugReporter/BugSuppression.h (+6-1)
  • (modified) clang/lib/StaticAnalyzer/Core/BugReporter.cpp (+3-1)
  • (modified) clang/lib/StaticAnalyzer/Core/BugSuppression.cpp (+9-1)
  • (modified) clang/test/Analysis/Checkers/WebKit/call-args.cpp (+6)
  • (modified) clang/test/Analysis/copypaste/suspicious-clones.cpp (+24)
diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugSuppression.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugSuppression.h
index 4fd81b627519744..419752edbb34578 100644
--- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugSuppression.h
+++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugSuppression.h
@@ -19,6 +19,7 @@
 #include "llvm/ADT/SmallVector.h"
 
 namespace clang {
+class ASTContext;
 class Decl;
 
 namespace ento {
@@ -27,6 +28,8 @@ class PathDiagnosticLocation;
 
 class BugSuppression {
 public:
+  BugSuppression(ASTContext &ACtx): ACtx(ACtx) {}
+
   using DiagnosticIdentifierList = llvm::ArrayRef<llvm::StringRef>;
 
   /// Return true if the given bug report was explicitly suppressed by the user.
@@ -44,7 +47,9 @@ class BugSuppression {
   using CachedRanges =
       llvm::SmallVector<SourceRange, EXPECTED_NUMBER_OF_SUPPRESSIONS>;
 
-  llvm::DenseMap<const Decl *, CachedRanges> CachedSuppressionLocations;
+  llvm::DenseMap<const Decl *, CachedRanges> CachedSuppressionLocations{};
+
+  ASTContext &ACtx;
 };
 
 } // end namespace ento
diff --git a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp
index f3e0a5f9f314ade..ac9a988a7efe298 100644
--- a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp
+++ b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp
@@ -2467,7 +2467,9 @@ ProgramStateManager &PathSensitiveBugReporter::getStateManager() const {
   return Eng.getStateManager();
 }
 
-BugReporter::BugReporter(BugReporterData &d) : D(d) {}
+BugReporter::BugReporter(BugReporterData &d)
+    : D(d), UserSuppressions(d.getASTContext()) {}
+
 BugReporter::~BugReporter() {
   // Make sure reports are flushed.
   assert(StrBugTypes.empty() &&
diff --git a/clang/lib/StaticAnalyzer/Core/BugSuppression.cpp b/clang/lib/StaticAnalyzer/Core/BugSuppression.cpp
index b5991e47a538875..fded071567f9582 100644
--- a/clang/lib/StaticAnalyzer/Core/BugSuppression.cpp
+++ b/clang/lib/StaticAnalyzer/Core/BugSuppression.cpp
@@ -138,9 +138,17 @@ bool BugSuppression::isSuppressed(const BugReport &R) {
 bool BugSuppression::isSuppressed(const PathDiagnosticLocation &Location,
                                   const Decl *DeclWithIssue,
                                   DiagnosticIdentifierList Hashtags) {
-  if (!Location.isValid() || DeclWithIssue == nullptr)
+  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();
+  }
+
   // 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,
diff --git a/clang/test/Analysis/Checkers/WebKit/call-args.cpp b/clang/test/Analysis/Checkers/WebKit/call-args.cpp
index 716219836e6b445..e5c498810704203 100644
--- a/clang/test/Analysis/Checkers/WebKit/call-args.cpp
+++ b/clang/test/Analysis/Checkers/WebKit/call-args.cpp
@@ -10,6 +10,12 @@ namespace simple {
     consume_refcntbl(provide());
     // expected-warning@-1{{Call argument is uncounted and unsafe}}
   }
+
+  // Test that the checker works with [[clang::suppress]].
+  void foo_suppressed() {
+    [[clang::suppress]]
+    consume_refcntbl(provide()); // no-warning
+  }
 }
 
 namespace multi_arg {
diff --git a/clang/test/Analysis/copypaste/suspicious-clones.cpp b/clang/test/Analysis/copypaste/suspicious-clones.cpp
index 61eb45a37a07e6f..8a7b46618893298 100644
--- a/clang/test/Analysis/copypaste/suspicious-clones.cpp
+++ b/clang/test/Analysis/copypaste/suspicious-clones.cpp
@@ -21,6 +21,30 @@ int maxClone(int x, int y, int z) {
   return z; // expected-warning{{Potential copy-paste error; did you really mean to use 'z' here?}}
 }
 
+// Test that the checker works with [[clang::suppress]].
+int max_suppressed(int a, int b) {
+  log();
+  if (a > b)
+    return a;
+
+  // This [[clang::suppress]] doesn't suppress anything but we need it here
+  // because otherwise the other function won't count as a perfect clone.
+  // FIXME: The checker should probably skip the attribute entirely
+  // when detecting clones. Otherwise warnings will still get suppressed,
+  // but for a completely wrong reason.
+  [[clang::suppress]]
+  return b; // no-note
+}
+
+int maxClone_suppressed(int x, int y, int z) {
+  log();
+  if (x > y)
+    return x;
+  [[clang::suppress]]
+  return z; // no-warning
+}
+
+
 // Tests finding a suspicious clone that references global variables.
 
 struct mutex {

@llvmbot
Copy link
Collaborator

llvmbot commented Jan 25, 2024

@llvm/pr-subscribers-clang

Author: Artem Dergachev (haoNoQ)

Changes

There are currently a few checkers that don't fill in the bug report's "decl-with-issue" field (typically a function in which the bug is found).

The new attribute [[clang::suppress]] uses decl-with-issue to reduce the size of the suppression source range map so that it didn't need to do that for the entire translation unit.

I'm already seeing a few problems with this approach so I'll probably redesign it in some point as it looks like a premature optimization. Not only checkers shouldn't be required to pass decl-with-issue (consider clang-tidy checkers that never had such notion), but also it's not necessarily uniquely determined (consider leak suppressions at allocation site).

For now I'm adding a simple stop-gap solution that falls back to building the suppression map for the entire TU whenever decl-with-issue isn't specified. Which won't happen in the default setup because luckily all default checkers do provide decl-with-issue.


Full diff: https://github.com/llvm/llvm-project/pull/79398.diff

5 Files Affected:

  • (modified) clang/include/clang/StaticAnalyzer/Core/BugReporter/BugSuppression.h (+6-1)
  • (modified) clang/lib/StaticAnalyzer/Core/BugReporter.cpp (+3-1)
  • (modified) clang/lib/StaticAnalyzer/Core/BugSuppression.cpp (+9-1)
  • (modified) clang/test/Analysis/Checkers/WebKit/call-args.cpp (+6)
  • (modified) clang/test/Analysis/copypaste/suspicious-clones.cpp (+24)
diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugSuppression.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugSuppression.h
index 4fd81b627519744..419752edbb34578 100644
--- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugSuppression.h
+++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugSuppression.h
@@ -19,6 +19,7 @@
 #include "llvm/ADT/SmallVector.h"
 
 namespace clang {
+class ASTContext;
 class Decl;
 
 namespace ento {
@@ -27,6 +28,8 @@ class PathDiagnosticLocation;
 
 class BugSuppression {
 public:
+  BugSuppression(ASTContext &ACtx): ACtx(ACtx) {}
+
   using DiagnosticIdentifierList = llvm::ArrayRef<llvm::StringRef>;
 
   /// Return true if the given bug report was explicitly suppressed by the user.
@@ -44,7 +47,9 @@ class BugSuppression {
   using CachedRanges =
       llvm::SmallVector<SourceRange, EXPECTED_NUMBER_OF_SUPPRESSIONS>;
 
-  llvm::DenseMap<const Decl *, CachedRanges> CachedSuppressionLocations;
+  llvm::DenseMap<const Decl *, CachedRanges> CachedSuppressionLocations{};
+
+  ASTContext &ACtx;
 };
 
 } // end namespace ento
diff --git a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp
index f3e0a5f9f314ade..ac9a988a7efe298 100644
--- a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp
+++ b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp
@@ -2467,7 +2467,9 @@ ProgramStateManager &PathSensitiveBugReporter::getStateManager() const {
   return Eng.getStateManager();
 }
 
-BugReporter::BugReporter(BugReporterData &d) : D(d) {}
+BugReporter::BugReporter(BugReporterData &d)
+    : D(d), UserSuppressions(d.getASTContext()) {}
+
 BugReporter::~BugReporter() {
   // Make sure reports are flushed.
   assert(StrBugTypes.empty() &&
diff --git a/clang/lib/StaticAnalyzer/Core/BugSuppression.cpp b/clang/lib/StaticAnalyzer/Core/BugSuppression.cpp
index b5991e47a538875..fded071567f9582 100644
--- a/clang/lib/StaticAnalyzer/Core/BugSuppression.cpp
+++ b/clang/lib/StaticAnalyzer/Core/BugSuppression.cpp
@@ -138,9 +138,17 @@ bool BugSuppression::isSuppressed(const BugReport &R) {
 bool BugSuppression::isSuppressed(const PathDiagnosticLocation &Location,
                                   const Decl *DeclWithIssue,
                                   DiagnosticIdentifierList Hashtags) {
-  if (!Location.isValid() || DeclWithIssue == nullptr)
+  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();
+  }
+
   // 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,
diff --git a/clang/test/Analysis/Checkers/WebKit/call-args.cpp b/clang/test/Analysis/Checkers/WebKit/call-args.cpp
index 716219836e6b445..e5c498810704203 100644
--- a/clang/test/Analysis/Checkers/WebKit/call-args.cpp
+++ b/clang/test/Analysis/Checkers/WebKit/call-args.cpp
@@ -10,6 +10,12 @@ namespace simple {
     consume_refcntbl(provide());
     // expected-warning@-1{{Call argument is uncounted and unsafe}}
   }
+
+  // Test that the checker works with [[clang::suppress]].
+  void foo_suppressed() {
+    [[clang::suppress]]
+    consume_refcntbl(provide()); // no-warning
+  }
 }
 
 namespace multi_arg {
diff --git a/clang/test/Analysis/copypaste/suspicious-clones.cpp b/clang/test/Analysis/copypaste/suspicious-clones.cpp
index 61eb45a37a07e6f..8a7b46618893298 100644
--- a/clang/test/Analysis/copypaste/suspicious-clones.cpp
+++ b/clang/test/Analysis/copypaste/suspicious-clones.cpp
@@ -21,6 +21,30 @@ int maxClone(int x, int y, int z) {
   return z; // expected-warning{{Potential copy-paste error; did you really mean to use 'z' here?}}
 }
 
+// Test that the checker works with [[clang::suppress]].
+int max_suppressed(int a, int b) {
+  log();
+  if (a > b)
+    return a;
+
+  // This [[clang::suppress]] doesn't suppress anything but we need it here
+  // because otherwise the other function won't count as a perfect clone.
+  // FIXME: The checker should probably skip the attribute entirely
+  // when detecting clones. Otherwise warnings will still get suppressed,
+  // but for a completely wrong reason.
+  [[clang::suppress]]
+  return b; // no-note
+}
+
+int maxClone_suppressed(int x, int y, int z) {
+  log();
+  if (x > y)
+    return x;
+  [[clang::suppress]]
+  return z; // no-warning
+}
+
+
 // Tests finding a suspicious clone that references global variables.
 
 struct mutex {

Copy link

github-actions bot commented Jan 25, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

@haoNoQ
Copy link
Collaborator Author

haoNoQ commented Jan 25, 2024

The affected checkers were:

  • Checkers in the webkit package (hence cc @jkorous-apple). These checkers manually scan the entire TU in order (rejecting the comforts of checkASTCodeBody) in order to properly cover uninstantiated templates. There's no reason they can't provide a decl-with-issue but it's often annoying to detect.
  • Copy-paste error checkers. Again, the checker can provide decl-with-issue, but plumbing gets quite annoying. The test case is super funny though!

Copy link
Contributor

@steakhal steakhal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only have minor nits. No objections.

Copy link
Contributor

@jkorous-apple jkorous-apple left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for fixing this!
LGTM from the perspective of WebKit checkers.

haoNoQ and others added 2 commits January 25, 2024 13:29
…sion.h

Co-authored-by: Balazs Benics <benicsbalazs@gmail.com>
…sion.h

Co-authored-by: Balazs Benics <benicsbalazs@gmail.com>
@haoNoQ haoNoQ merged commit 56e241a into llvm:main Jan 31, 2024
3 of 4 checks passed
carlosgalvezp pushed a commit to carlosgalvezp/llvm-project that referenced this pull request Feb 1, 2024
…issue. (llvm#79398)

There are currently a few checkers that don't fill in the bug report's
"decl-with-issue" field (typically a function in which the bug is
found).

The new attribute `[[clang::suppress]]` uses decl-with-issue to reduce
the size of the suppression source range map so that it didn't need to
do that for the entire translation unit.

I'm already seeing a few problems with this approach so I'll probably
redesign it in some point as it looks like a premature optimization. Not
only checkers shouldn't be required to pass decl-with-issue (consider
clang-tidy checkers that never had such notion), but also it's not
necessarily uniquely determined (consider leak suppressions at
allocation site).

For now I'm adding a simple stop-gap solution that falls back to
building the suppression map for the entire TU whenever decl-with-issue
isn't specified. Which won't happen in the default setup because luckily
all default checkers do provide decl-with-issue.

---------

Co-authored-by: Balazs Benics <benicsbalazs@gmail.com>
haoNoQ added a commit to haoNoQ/llvm-project that referenced this pull request Feb 13, 2024
…issue. (llvm#79398)

There are currently a few checkers that don't fill in the bug report's
"decl-with-issue" field (typically a function in which the bug is
found).

The new attribute `[[clang::suppress]]` uses decl-with-issue to reduce
the size of the suppression source range map so that it didn't need to
do that for the entire translation unit.

I'm already seeing a few problems with this approach so I'll probably
redesign it in some point as it looks like a premature optimization. Not
only checkers shouldn't be required to pass decl-with-issue (consider
clang-tidy checkers that never had such notion), but also it's not
necessarily uniquely determined (consider leak suppressions at
allocation site).

For now I'm adding a simple stop-gap solution that falls back to
building the suppression map for the entire TU whenever decl-with-issue
isn't specified. Which won't happen in the default setup because luckily
all default checkers do provide decl-with-issue.

---------

Co-authored-by: Balazs Benics <benicsbalazs@gmail.com>
(cherry picked from commit 56e241a)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:static analyzer clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants