[clang] [clang-tools-extra] Fix registered matcher for bugprone-unchecked-optional-access (recent changes to libcxx) (PR #191681)
Jan Voung via cfe-commits
cfe-commits at lists.llvm.org
Tue Apr 14 23:06:19 PDT 2026
https://github.com/jvoung updated https://github.com/llvm/llvm-project/pull/191681
>From 4312d9f4d766578067d0cd3495eb31d9ff2df863 Mon Sep 17 00:00:00 2001
From: Jan Voung <jvoung at gmail.com>
Date: Sun, 12 Apr 2026 03:16:45 +0000
Subject: [PATCH 1/2] Fix registered matcher for
bugprone-unchecked-optional-access (recent changes to libcxx)
Further fix for #187788. Previous attempt in PR 188044 only updated the
model and model tests, but forgot to update the registered matcher.
---
.../bugprone/UncheckedOptionalAccessCheck.cpp | 5 ++-
.../std/types/optional.h | 39 +++++++++++++------
.../bugprone/unchecked-optional-access.cpp | 35 ++++++++++++++---
.../Models/UncheckedOptionalAccessModel.h | 5 ++-
.../Models/UncheckedOptionalAccessModel.cpp | 11 ++++--
5 files changed, 71 insertions(+), 24 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/bugprone/UncheckedOptionalAccessCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UncheckedOptionalAccessCheck.cpp
index 79d6e4974c316..cf7829984fab9 100644
--- a/clang-tools-extra/clang-tidy/bugprone/UncheckedOptionalAccessCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/UncheckedOptionalAccessCheck.cpp
@@ -27,8 +27,9 @@ static constexpr StringRef FuncID = "fun";
void UncheckedOptionalAccessCheck::registerMatchers(MatchFinder *Finder) {
using namespace ast_matchers;
- auto HasOptionalCallDescendant = hasDescendant(callExpr(callee(cxxMethodDecl(
- ofClass(UncheckedOptionalAccessModel::optionalClassDecl())))));
+ auto HasOptionalCallDescendant = hasDescendant(callExpr(
+ anyOf(UncheckedOptionalAccessModel::memberCallToOptionalClass(),
+ UncheckedOptionalAccessModel::operatorCallToOptionalClass())));
Finder->addMatcher(
decl(anyOf(functionDecl(
// FIXME: Remove the filter below when lambdas are
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/std/types/optional.h b/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/std/types/optional.h
index d331585a4c21c..c508b2abe6e47 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/std/types/optional.h
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/std/types/optional.h
@@ -1,6 +1,7 @@
#ifndef LLVM_CLANG_TOOLS_EXTRA_TEST_CLANG_TIDY_CHECKERS_INPUTS_STD_TYPES_OPTIONAL_H_
#define LLVM_CLANG_TOOLS_EXTRA_TEST_CLANG_TIDY_CHECKERS_INPUTS_STD_TYPES_OPTIONAL_H_
+/// Mock of `std::optional`.
namespace std {
struct nullopt_t {
@@ -9,16 +10,13 @@ struct nullopt_t {
constexpr nullopt_t nullopt;
-template <typename T>
-class optional {
-public:
- constexpr optional() noexcept;
-
- constexpr optional(nullopt_t) noexcept;
-
- optional(const optional &) = default;
+template <class T> struct __optional_destruct_base {
+ constexpr void reset() noexcept;
+};
- optional(optional &&) = default;
+template <class T>
+struct __optional_storage_base : __optional_destruct_base<T> {
+ constexpr bool has_value() const noexcept;
const T &operator*() const &;
T &operator*() &;
@@ -32,9 +30,28 @@ class optional {
T &value() &;
const T &&value() const &&;
T &&value() &&;
+};
+
+// Note: the inheritance may or may not be private:
+// https://github.com/llvm/llvm-project/issues/187788
+template <typename T> class optional : public __optional_storage_base<T> {
+ using base = __optional_storage_base<T>;
+
+public:
+ constexpr optional() noexcept;
+
+ constexpr optional(nullopt_t) noexcept;
+
+ optional(const optional &) = default;
+
+ optional(optional &&) = default;
+
+ using base::operator*;
+ using base::operator->;
+ using base::value;
constexpr explicit operator bool() const noexcept;
- constexpr bool has_value() const noexcept;
+ using base::has_value;
template <typename U>
constexpr T value_or(U &&v) const &;
@@ -44,7 +61,7 @@ class optional {
template <typename... Args>
T &emplace(Args &&...args);
- void reset() noexcept;
+ using base::reset;
void swap(optional &rhs) noexcept;
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cpp
index 214072de772e2..e4d59528134c0 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cpp
@@ -1,16 +1,28 @@
// RUN: %check_clang_tidy %s bugprone-unchecked-optional-access %t -- -- -I %S/Inputs/unchecked-optional-access
#include "absl/types/optional.h"
-#include "folly/types/Optional.h"
-#include "bde/types/bsl_optional.h"
#include "bde/types/bdlb_nullablevalue.h"
+#include "bde/types/bsl_optional.h"
+#include "folly/types/Optional.h"
+#include "std/types/optional.h"
-void unchecked_value_access(const absl::optional<int> &opt) {
+void unchecked_value_access(std::optional<int> opt) {
opt.value();
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: unchecked access to optional value [bugprone-unchecked-optional-access]
}
-void unchecked_deref_operator_access(const absl::optional<int> &opt) {
+void absl_unchecked_value_access(const absl::optional<int> &opt) {
+ opt.value();
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: unchecked access to optional value
+ // [bugprone-unchecked-optional-access]
+}
+
+void unchecked_deref_operator_access(std::optional<int> opt) {
+ *opt;
+ // CHECK-MESSAGES: :[[@LINE-1]]:4: warning: unchecked access to optional value
+}
+
+void absl_unchecked_deref_operator_access(const absl::optional<int> &opt) {
*opt;
// CHECK-MESSAGES: :[[@LINE-1]]:4: warning: unchecked access to optional value
}
@@ -19,7 +31,12 @@ struct Foo {
void foo() const {}
};
-void unchecked_arrow_operator_access(const absl::optional<Foo> &opt) {
+void unchecked_arrow_operator_access(std::optional<Foo> opt) {
+ opt->foo();
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: unchecked access to optional value
+}
+
+void absl_unchecked_arrow_operator_access(const absl::optional<Foo> &opt) {
opt->foo();
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: unchecked access to optional value
}
@@ -40,7 +57,13 @@ void folly_value_after_swap(folly::Optional<int> opt1, folly::Optional<int> opt2
}
}
-void checked_access(const absl::optional<int> &opt) {
+void checked_access(std::optional<int> opt) {
+ if (opt.has_value()) {
+ opt.value();
+ }
+}
+
+void absl_checked_access(const absl::optional<int> &opt) {
if (opt.has_value()) {
opt.value();
}
diff --git a/clang/include/clang/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.h b/clang/include/clang/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.h
index c547d6ce2e387..e7f1f407d9912 100644
--- a/clang/include/clang/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.h
+++ b/clang/include/clang/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.h
@@ -62,8 +62,9 @@ class UncheckedOptionalAccessModel
public:
UncheckedOptionalAccessModel(ASTContext &Ctx, dataflow::Environment &Env);
- /// Returns a matcher for the optional classes covered by this model.
- static ast_matchers::DeclarationMatcher optionalClassDecl();
+ /// Returns a matcher for calls to optional classes diagnosed by this model.
+ static ast_matchers::StatementMatcher memberCallToOptionalClass();
+ static ast_matchers::StatementMatcher operatorCallToOptionalClass();
static UncheckedOptionalAccessLattice initialElement() { return {}; }
diff --git a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
index 182db36be3513..568564fb361f4 100644
--- a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
+++ b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
@@ -1278,9 +1278,14 @@ auto buildDiagnoseMatchSwitch(
} // namespace
-ast_matchers::DeclarationMatcher
-UncheckedOptionalAccessModel::optionalClassDecl() {
- return cxxRecordDecl(optionalClass());
+ast_matchers::StatementMatcher
+UncheckedOptionalAccessModel::memberCallToOptionalClass() {
+ return cxxMemberCallExpr(hasOptionalReceiverType());
+}
+
+ast_matchers::StatementMatcher
+UncheckedOptionalAccessModel::operatorCallToOptionalClass() {
+ return cxxOperatorCallExpr(hasOptionalOperatorObjectType());
}
UncheckedOptionalAccessModel::UncheckedOptionalAccessModel(ASTContext &Ctx,
>From 092b8ac39a034f2ec7539f69177940afdc36038b Mon Sep 17 00:00:00 2001
From: Jan Voung <jvoung at gmail.com>
Date: Mon, 13 Apr 2026 14:38:43 +0000
Subject: [PATCH 2/2] Fix test CHECK
---
.../clang-tidy/checkers/bugprone/unchecked-optional-access.cpp | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cpp
index e4d59528134c0..337474bdf7535 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cpp
@@ -13,8 +13,7 @@ void unchecked_value_access(std::optional<int> opt) {
void absl_unchecked_value_access(const absl::optional<int> &opt) {
opt.value();
- // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: unchecked access to optional value
- // [bugprone-unchecked-optional-access]
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: unchecked access to optional value [bugprone-unchecked-optional-access]
}
void unchecked_deref_operator_access(std::optional<int> opt) {
More information about the cfe-commits
mailing list