[clang-tools-extra] [clang-tidy] Filter out googletest TUs in bugprone-unchecked-optional-access (PR #115051)
Jan Voung via cfe-commits
cfe-commits at lists.llvm.org
Fri Dec 6 11:33:21 PST 2024
https://github.com/jvoung updated https://github.com/llvm/llvm-project/pull/115051
>From 8d83ec25ccdedad5f6a48e4a23176435f71a3e72 Mon Sep 17 00:00:00 2001
From: Jan Voung <jvoung at gmail.com>
Date: Tue, 5 Nov 2024 19:20:36 +0000
Subject: [PATCH 1/4] [clang-tidy] Filter out googletest TUs in
bugprone-unchecked-optional-access
We currently do not handle ASSERT_TRUE(opt.has_value()) macros from
googletest (and other macros), so would see lots of false positives
in test TUs.
---
.../bugprone/UncheckedOptionalAccessCheck.cpp | 19 ++++++++++++++-
.../bugprone/UncheckedOptionalAccessCheck.h | 8 ++++++-
.../unchecked-optional-access/gtest/gtest.h | 24 +++++++++++++++++++
...cked-optional-access-ignore-googletest.cpp | 24 +++++++++++++++++++
4 files changed, 73 insertions(+), 2 deletions(-)
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/gtest/gtest.h
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access-ignore-googletest.cpp
diff --git a/clang-tools-extra/clang-tidy/bugprone/UncheckedOptionalAccessCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UncheckedOptionalAccessCheck.cpp
index 0a0e212f345ed8..06deea0a446809 100644
--- a/clang-tools-extra/clang-tidy/bugprone/UncheckedOptionalAccessCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/UncheckedOptionalAccessCheck.cpp
@@ -40,10 +40,27 @@ void UncheckedOptionalAccessCheck::registerMatchers(MatchFinder *Finder) {
this);
}
+void UncheckedOptionalAccessCheck::onStartOfTranslationUnit() {
+ // Reset the flag for each TU.
+ is_test_tu_ = false;
+}
+
void UncheckedOptionalAccessCheck::check(
const MatchFinder::MatchResult &Result) {
- if (Result.SourceManager->getDiagnostics().hasUncompilableErrorOccurred())
+ // The googletest assertion macros are not currently recognized, so we have
+ // many false positives in tests. So, do not check functions in a test TU.
+ if (is_test_tu_ ||
+ Result.SourceManager->getDiagnostics().hasUncompilableErrorOccurred())
+ return;
+
+ // Look for two (public) googletest macros; if found, we'll mark this TU as a
+ // test TU. We look for ASSERT_TRUE because it is a problematic macro that
+ // we don't (yet) support, and GTEST_TEST to disambiguate ASSERT_TRUE.
+ if (Result.Context->Idents.get("ASSERT_TRUE").hasMacroDefinition() &&
+ Result.Context->Idents.get("GTEST_TEST").hasMacroDefinition()) {
+ is_test_tu_ = true;
return;
+ }
const auto *FuncDecl = Result.Nodes.getNodeAs<FunctionDecl>(FuncID);
if (FuncDecl->isTemplated())
diff --git a/clang-tools-extra/clang-tidy/bugprone/UncheckedOptionalAccessCheck.h b/clang-tools-extra/clang-tidy/bugprone/UncheckedOptionalAccessCheck.h
index 2d1570f7df8abb..3bd7ff9867b145 100644
--- a/clang-tools-extra/clang-tidy/bugprone/UncheckedOptionalAccessCheck.h
+++ b/clang-tools-extra/clang-tidy/bugprone/UncheckedOptionalAccessCheck.h
@@ -12,7 +12,6 @@
#include "../ClangTidyCheck.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.h"
-#include <optional>
namespace clang::tidy::bugprone {
@@ -30,6 +29,7 @@ class UncheckedOptionalAccessCheck : public ClangTidyCheck {
Options.getLocalOrGlobal("IgnoreSmartPointerDereference", false)} {}
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+ void onStartOfTranslationUnit() override;
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
return LangOpts.CPlusPlus;
}
@@ -40,6 +40,12 @@ class UncheckedOptionalAccessCheck : public ClangTidyCheck {
private:
dataflow::UncheckedOptionalAccessModelOptions ModelOptions;
+
+ // Records whether the current TU includes the googletest headers, in which
+ // case we assume it is a test TU of some sort. The googletest assertion
+ // macros are not currently recognized, so we have many false positives in
+ // tests. So, we disable checking for all test TUs.
+ bool is_test_tu_ = false;
};
} // namespace clang::tidy::bugprone
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/gtest/gtest.h b/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/gtest/gtest.h
new file mode 100644
index 00000000000000..8a9eeac94dbb77
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/gtest/gtest.h
@@ -0,0 +1,24 @@
+#ifndef LLVM_CLANG_TOOLS_EXTRA_TEST_CLANG_TIDY_CHECKERS_INPUTS_GTEST_GTEST_H_
+#define LLVM_CLANG_TOOLS_EXTRA_TEST_CLANG_TIDY_CHECKERS_INPUTS_GTEST_GTEST_H_
+
+// Mock version of googletest macros.
+
+#define GTEST_TEST(test_suite_name, test_name) \
+ class GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) { \
+ public: \
+ GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() = default; \
+ };
+
+#define GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
+ switch (0) \
+ case 0: \
+ default: // NOLINT
+
+#define ASSERT_TRUE(condition) \
+ GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
+ if (condition) \
+ ; \
+ else \
+ return; // should fail...
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_TEST_CLANG_TIDY_CHECKERS_INPUTS_GTEST_GTEST_H_
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access-ignore-googletest.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access-ignore-googletest.cpp
new file mode 100644
index 00000000000000..2f213434d0ad61
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access-ignore-googletest.cpp
@@ -0,0 +1,24 @@
+// RUN: %check_clang_tidy %s bugprone-unchecked-optional-access %t -- -- -I %S/Inputs/unchecked-optional-access
+
+#include "absl/types/optional.h"
+#include "gtest/gtest.h"
+
+// All of the warnings are suppressed once we detect that we are in a test TU
+// even the unchecked_value_access case.
+
+// CHECK-MESSAGE: 1 warning generated
+// CHECK-MESSAGES: Suppressed 1 warnings
+
+void unchecked_value_access(const absl::optional<int> &opt) {
+ opt.value();
+}
+
+void assert_true_check_operator_access(const absl::optional<int> &opt) {
+ ASSERT_TRUE(opt.has_value());
+ *opt;
+}
+
+void assert_true_check_value_access(const absl::optional<int> &opt) {
+ ASSERT_TRUE(opt.has_value());
+ opt.value();
+}
>From 3427fa73c174ccdc68b703c20c0ab40a7418e955 Mon Sep 17 00:00:00 2001
From: Jan Voung <jvoung at gmail.com>
Date: Tue, 5 Nov 2024 19:29:28 +0000
Subject: [PATCH 2/4] Add one more missing macro
---
.../bugprone/Inputs/unchecked-optional-access/gtest/gtest.h | 3 +++
1 file changed, 3 insertions(+)
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/gtest/gtest.h b/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/gtest/gtest.h
index 8a9eeac94dbb77..ba865dfc5a966f 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/gtest/gtest.h
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/gtest/gtest.h
@@ -3,6 +3,9 @@
// Mock version of googletest macros.
+#define GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \
+ test_suite_name##_##test_name##_Test
+
#define GTEST_TEST(test_suite_name, test_name) \
class GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) { \
public: \
>From 2611af1a9a7ecb007d0f0f735d0b7b1581a0f1b1 Mon Sep 17 00:00:00 2001
From: Jan Voung <jvoung at gmail.com>
Date: Wed, 6 Nov 2024 22:10:28 +0000
Subject: [PATCH 3/4] Simplify the mock a bit more
The implementation doesn't matter as much for testing, since the
currently skipping is keying on some top-level macros.
---
.../unchecked-optional-access/gtest/gtest.h | 21 ++++++-------------
1 file changed, 6 insertions(+), 15 deletions(-)
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/gtest/gtest.h b/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/gtest/gtest.h
index ba865dfc5a966f..e3b545c01d2fa1 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/gtest/gtest.h
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/gtest/gtest.h
@@ -3,25 +3,16 @@
// Mock version of googletest macros.
-#define GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \
- test_suite_name##_##test_name##_Test
-
-#define GTEST_TEST(test_suite_name, test_name) \
- class GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) { \
- public: \
- GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() = default; \
- };
-
-#define GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
- switch (0) \
- case 0: \
- default: // NOLINT
+// Normally this declares a class, but it isn't relevant for testing.
+#define GTEST_TEST(test_suite_name, test_name)
+// Normally, this has a relatively complex implementation
+// (wrapping the condition evaluation), more complex failure behavior, etc.,
+// but we keep it simple for testing.
#define ASSERT_TRUE(condition) \
- GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
if (condition) \
; \
else \
- return; // should fail...
+ return; // normally "fails" rather than just return
#endif // LLVM_CLANG_TOOLS_EXTRA_TEST_CLANG_TIDY_CHECKERS_INPUTS_GTEST_GTEST_H_
>From ee10510d15e75da7fc75420fa33d00e97d6477a0 Mon Sep 17 00:00:00 2001
From: Jan Voung <jvoung at gmail.com>
Date: Fri, 6 Dec 2024 19:29:51 +0000
Subject: [PATCH 4/4] Make an option, default off
Also fix a CHECK-MESSAGE -> CHECK-MESSAGES
---
.../bugprone/UncheckedOptionalAccessCheck.cpp | 8 +++--
.../bugprone/UncheckedOptionalAccessCheck.h | 32 ++++++++++++++++---
...cked-optional-access-ignore-googletest.cpp | 20 +++++++++---
3 files changed, 48 insertions(+), 12 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/bugprone/UncheckedOptionalAccessCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UncheckedOptionalAccessCheck.cpp
index 06deea0a446809..71de2fda005bd7 100644
--- a/clang-tools-extra/clang-tidy/bugprone/UncheckedOptionalAccessCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/UncheckedOptionalAccessCheck.cpp
@@ -48,8 +48,9 @@ void UncheckedOptionalAccessCheck::onStartOfTranslationUnit() {
void UncheckedOptionalAccessCheck::check(
const MatchFinder::MatchResult &Result) {
// The googletest assertion macros are not currently recognized, so we have
- // many false positives in tests. So, do not check functions in a test TU.
- if (is_test_tu_ ||
+ // many false positives in tests. So, do not check functions in a test TU
+ // if the option ignore_test_tus_ is set.
+ if ((ignore_test_tus_ && is_test_tu_) ||
Result.SourceManager->getDiagnostics().hasUncompilableErrorOccurred())
return;
@@ -59,7 +60,8 @@ void UncheckedOptionalAccessCheck::check(
if (Result.Context->Idents.get("ASSERT_TRUE").hasMacroDefinition() &&
Result.Context->Idents.get("GTEST_TEST").hasMacroDefinition()) {
is_test_tu_ = true;
- return;
+ if (ignore_test_tus_)
+ return;
}
const auto *FuncDecl = Result.Nodes.getNodeAs<FunctionDecl>(FuncID);
diff --git a/clang-tools-extra/clang-tidy/bugprone/UncheckedOptionalAccessCheck.h b/clang-tools-extra/clang-tidy/bugprone/UncheckedOptionalAccessCheck.h
index 3bd7ff9867b145..3e20fd1a1ed4eb 100644
--- a/clang-tools-extra/clang-tidy/bugprone/UncheckedOptionalAccessCheck.h
+++ b/clang-tools-extra/clang-tidy/bugprone/UncheckedOptionalAccessCheck.h
@@ -10,6 +10,9 @@
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNCHECKEDOPTIONALACCESSCHECK_H
#include "../ClangTidyCheck.h"
+#include "../ClangTidyOptions.h"
+#include "clang/Basic/LLVM.h"
+#include "clang/Basic/LangOptions.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.h"
@@ -26,7 +29,8 @@ class UncheckedOptionalAccessCheck : public ClangTidyCheck {
UncheckedOptionalAccessCheck(StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
ModelOptions{
- Options.getLocalOrGlobal("IgnoreSmartPointerDereference", false)} {}
+ Options.getLocalOrGlobal("IgnoreSmartPointerDereference", false)},
+ ignore_test_tus_(Options.getLocalOrGlobal("IgnoreTestTUs", false)) {}
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
void onStartOfTranslationUnit() override;
@@ -36,15 +40,33 @@ class UncheckedOptionalAccessCheck : public ClangTidyCheck {
void storeOptions(ClangTidyOptions::OptionMap &Opts) override {
Options.store(Opts, "IgnoreSmartPointerDereference",
ModelOptions.IgnoreSmartPointerDereference);
+ Options.store(Opts, "IgnoreTestTUs",
+ ignore_test_tus_);
}
private:
dataflow::UncheckedOptionalAccessModelOptions ModelOptions;
- // Records whether the current TU includes the googletest headers, in which
- // case we assume it is a test TU of some sort. The googletest assertion
- // macros are not currently recognized, so we have many false positives in
- // tests. So, we disable checking for all test TUs.
+ // Tracks the Option of whether we should ignore test TUs (e.g., googletest).
+ // Currently we have many false positives in tests, making it difficult to
+ // find true positives and developers end up ignoring the warnings in tests,
+ // reducing the check's effectiveness.
+ // Reasons for false positives (once fixed we could remove this option):
+ // - has_value() checks wrapped in googletest assertion macros are not handled
+ // (e.g., EXPECT_TRUE() and fall through, or ASSERT_TRUE() and crash,
+ // or more complex ones like ASSERT_THAT(x, Not(Eq(std::nullopt))))
+ // - we don't handle state carried over from test fixture constructors/setup
+ // to test case bodies (constructor may initialize an optional to a value)
+ // - developers may make shortcuts in tests making assumptions and
+ // use the test runs (expecially with sanitizers) to check assumptions.
+ // This is different from production code in that test code should have
+ // near 100% coverage (if not covered by the test itself, it is dead code).
+ bool ignore_test_tus_ = false;
+
+ // Records whether the current TU includes the test-specific headers (e.g.,
+ // googletest), in which case we assume it is a test TU of some sort.
+ // This along with the setting `ignore_test_tus_` allows us to disable
+ // checking for all test TUs.
bool is_test_tu_ = false;
};
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access-ignore-googletest.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access-ignore-googletest.cpp
index 2f213434d0ad61..3effafc041daa4 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access-ignore-googletest.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access-ignore-googletest.cpp
@@ -1,16 +1,25 @@
-// RUN: %check_clang_tidy %s bugprone-unchecked-optional-access %t -- -- -I %S/Inputs/unchecked-optional-access
+// RUN: %check_clang_tidy -check-suffix=IGNORE-TESTS %s \
+// RUN: bugprone-unchecked-optional-access %t -- \
+// RUN: -config="{CheckOptions: \
+// RUN: {bugprone-unchecked-optional-access.IgnoreTestTUs: true}}" \
+// RUN: -- -I %S/Inputs/unchecked-optional-access
+
+// RUN: %check_clang_tidy -check-suffix=DEFAULT %s \
+// RUN: bugprone-unchecked-optional-access %t -- \
+// RUN: -- -I %S/Inputs/unchecked-optional-access
#include "absl/types/optional.h"
#include "gtest/gtest.h"
-// All of the warnings are suppressed once we detect that we are in a test TU
+// When IgnoreTestTUs is set, all of the warnings in a test TU are suppressed,
// even the unchecked_value_access case.
-// CHECK-MESSAGE: 1 warning generated
-// CHECK-MESSAGES: Suppressed 1 warnings
+// CHECK-MESSAGES-IGNORE-TESTS: 1 warning generated
+// CHECK-MESSAGES-DEFAULT: 2 warnings generated
void unchecked_value_access(const absl::optional<int> &opt) {
opt.value();
+ // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:3: warning: unchecked access to optional value
}
void assert_true_check_operator_access(const absl::optional<int> &opt) {
@@ -22,3 +31,6 @@ void assert_true_check_value_access(const absl::optional<int> &opt) {
ASSERT_TRUE(opt.has_value());
opt.value();
}
+
+// CHECK-MESSAGES-IGNORE-TESTS: Suppressed 1 warnings
+// CHECK-MESSAGES-DEFAULT: Suppressed 1 warnings
More information about the cfe-commits
mailing list