https://github.com/KHicketts created https://github.com/llvm/llvm-project/pull/196272
None >From 52d11c1082709188e7fa4d8b6995febf1bcf19fa Mon Sep 17 00:00:00 2001 From: khickett <[email protected]> Date: Tue, 28 Apr 2026 14:37:52 +0100 Subject: [PATCH 01/11] add sema changes for class attribute --- clang/lib/Sema/SemaDeclAttr.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index e47a30193567f..243c66b474384 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -6462,6 +6462,29 @@ static void handleAbiTagAttr(Sema &S, Decl *D, const ParsedAttr &AL) { AbiTagAttr(S.Context, AL, Tags.data(), Tags.size())); } +// for now this only handles std::optional (POC) +static bool isValidAnalyseAsClassAttr(Decl *D, StringRef Tag) { + if (Tag == "std::optional") + return true; + return false; +} + +static void handleAnalyseAsClass(Sema &S, Decl *D, const ParsedAttr &AL) { + StringRef Str; + if (!S.checkStringLiteralArgumentAttr(AL, 0, Str)) + return; + if (D->hasAttr<AnalyseAsClassAttr>()) { + S.Diag(AL.getLoc(), diag::err_duplicate_attribute) << AL; + return; + } + if (!isValidAnalyseAsClassAttr(D, Str)) { + S.Diag(AL.getLoc(), diag::warn_attribute_type_not_supported) << AL; + return; + } + + D->addAttr(::new (S.Context) AnalyseAsClassAttr(S.Context, AL, Str)); +} + static bool hasBTFDeclTagAttr(Decl *D, StringRef Tag) { for (const auto *I : D->specific_attrs<BTFDeclTagAttr>()) { if (I->getBTFDeclTag() == Tag) @@ -7551,6 +7574,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL, case ParsedAttr::AT_BPFPreserveStaticOffset: handleSimpleAttribute<BPFPreserveStaticOffsetAttr>(S, D, AL); break; + case ParsedAttr::AT_AnalyseAsClass: + handleAnalyseAsClass(S, D, AL); + break; case ParsedAttr::AT_BTFDeclTag: handleBTFDeclTagAttr(S, D, AL); break; >From fe028c7292be79ba6f753ebae1039e65aa0032e9 Mon Sep 17 00:00:00 2001 From: khickett <[email protected]> Date: Tue, 28 Apr 2026 15:43:52 +0100 Subject: [PATCH 02/11] analyse_as_class works ok --- .../Models/UncheckedOptionalAccessModel.cpp | 4 + hicketts_test/hicketts_optional.h | 83 +++++++++++++++++++ hicketts_test/test_hicketts_optional.cpp | 83 +++++++++++++++++++ 3 files changed, 170 insertions(+) create mode 100644 hicketts_test/hicketts_optional.h create mode 100644 hicketts_test/test_hicketts_optional.cpp diff --git a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp index 568564fb361f4..317f5034b0145 100644 --- a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp +++ b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp @@ -12,6 +12,7 @@ //===----------------------------------------------------------------------===// #include "clang/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.h" +#include "clang/AST/Attr.h" #include "clang/AST/ASTContext.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/Expr.h" @@ -89,6 +90,9 @@ static bool hasOptionalClassName(const CXXRecordDecl &RD) { isFullyQualifiedNamespaceEqualTo(*N, "bdlb", "BloombergLP"); } + if (RD.hasAttr<AnalyseAsClassAttr>()) + return true; + return false; } diff --git a/hicketts_test/hicketts_optional.h b/hicketts_test/hicketts_optional.h new file mode 100644 index 0000000000000..b915b01f70290 --- /dev/null +++ b/hicketts_test/hicketts_optional.h @@ -0,0 +1,83 @@ +#ifndef HICKETTS_OPTIONAL_H_ +#define HICKETTS_OPTIONAL_H_ + +/// A custom optional-like type with differently named functions. +/// Mirrors std::optional semantics but uses its own vocabulary +/// In order to test implementation of attributes for clang-tidy +namespace mylib { + +struct nothing_t { + constexpr explicit nothing_t() {} +}; + +constexpr nothing_t nothing; + +template <typename T> +class [[clang::analyse_as_class("std::optional")]] HickettsOptional { + T *storage_ = nullptr; + +public: + constexpr HickettsOptional() noexcept {} + + constexpr HickettsOptional(nothing_t) noexcept {} + + HickettsOptional(const HickettsOptional &) = default; + + HickettsOptional(HickettsOptional &&) = default; + + // Equivalent to std::optional::value() + //[[clang_analyse_as_method(std::optional::value)]] + const T &unwrap() const & { return *storage_; } + T &unwrap() & { return *storage_; } + const T &&unwrap() const && { return static_cast<const T &&>(*storage_); } + T &&unwrap() && { return static_cast<T &&>(*storage_); } + + const T &value() const & { return *storage_; } + T &value() & { return *storage_; } + const T &&value() const && { return static_cast<const T &&>(*storage_); } + T &&value() && { return static_cast<T &&>(*storage_); } + + // Equivalent to std::optional::operator*() + const T &deref() const & { return *storage_; } + T &deref() & { return *storage_; } + + // Equivalent to std::optional::operator->() + const T* operator ->() const { return storage_; } + T* operator ->() { return storage_; } + const T *arrow() const { return storage_; } + T *arrow() { return storage_; } + + // Equivalent to std::optional::operator bool / has_value() + constexpr bool hasValue() const noexcept { return storage_ != nullptr; } + constexpr explicit operator bool() const noexcept { return storage_ != nullptr; } + constexpr bool isPresent() const noexcept { return storage_ != nullptr; } + constexpr bool isEmpty() const noexcept { return storage_ == nullptr; } + + // Equivalent to std::optional::value_or() + template <typename U> + constexpr T unwrapOr(U &&fallback) const & { + return storage_ ? *storage_ : static_cast<T>(fallback); + } + + // Equivalent to std::optional::emplace() + template <typename... Args> + T &construct(Args &&...args) { return *storage_; } + + // Equivalent to std::optional::reset() + void clear() noexcept { storage_ = nullptr; } + + // Equivalent to std::optional::swap() + void exchange(HickettsOptional &other) noexcept { + T *tmp = storage_; + storage_ = other.storage_; + other.storage_ = tmp; + } + + // Assignment + template <typename U> + HickettsOptional &operator=(const U &u) { return *this; } +}; + +} // namespace mylib + +#endif // HICKETTS_OPTIONAL_H_ diff --git a/hicketts_test/test_hicketts_optional.cpp b/hicketts_test/test_hicketts_optional.cpp new file mode 100644 index 0000000000000..fa0153a79f341 --- /dev/null +++ b/hicketts_test/test_hicketts_optional.cpp @@ -0,0 +1,83 @@ +// Test cases for mylib::HickettsOptional — a custom optional-like type +// with differently named functions. +// +// Run from hicketts_test/ with: +// clang-tidy -checks='bugprone-unchecked-optional-access' \ +// test_hicketts_optional.cpp -- -I . -Wno-undefined-inline + +#include "hicketts_optional.h" + +// --- Unchecked access (should warn if the checker recognises HickettsOptional) --- + +static void uncheckedUnwrap(mylib::HickettsOptional<int> &Val) { + Val.unwrap(); // unchecked access — may be empty +} + +static void uncheckedValue(mylib::HickettsOptional<int> &Val) { + Val.value(); // unchecked access — may be empty +} + +static void uncheckedDeref(mylib::HickettsOptional<int> &Val) { + Val.deref(); // unchecked access — may be empty +} + +// --- Checked access (should NOT warn) --- + +static void checkedWithBool(mylib::HickettsOptional<int> &Val) { + if (Val) { + Val.unwrap(); // safe — checked via operator bool + } +} + +static void checkedValueWithBool(mylib::HickettsOptional<int> &Val) { + if (Val.hasValue()) { + Val.value(); // safe — checked via operator bool + } +} + +static void checkedWithIsPresent(mylib::HickettsOptional<int> &Val) { + if (Val.isPresent()) { + Val.unwrap(); // safe — checked via isPresent() + } +} + +static void checkedWithIsEmpty(mylib::HickettsOptional<int> &Val) { + if (!Val.isEmpty()) { + Val.unwrap(); // safe — checked via !isEmpty() + } +} + +// --- State changes --- + +static void safeAfterConstruct(mylib::HickettsOptional<int> &Val) { + Val.construct(42); + Val.unwrap(); // safe — just constructed a value +} + +static void unsafeAfterClear(mylib::HickettsOptional<int> &Val) { + Val.construct(42); + Val.clear(); + Val.unwrap(); // unsafe — value was cleared +} + +static void unsafeAfterExchange(mylib::HickettsOptional<int> &A, + mylib::HickettsOptional<int> &B) { + if (A) { + A.exchange(B); + A.unwrap(); // unsafe — a's state is now unknown + } +} + +// --- Guarded paths --- + +static void constructCoversEmptyBranch(mylib::HickettsOptional<int> &Val) { + if (Val.isEmpty()) { + Val.construct(99); + } + Val.unwrap(); // safe — either was present, or construct filled it +} + +static void unwrapOrIsAlwaysSafe(mylib::HickettsOptional<int> &Val) { + int X = Val.unwrapOr(0); // safe — fallback provided + (void)X; +} >From ec4ea5a3de75bcc71b0140a11f24298d4681fdb0 Mon Sep 17 00:00:00 2001 From: khickett <[email protected]> Date: Tue, 28 Apr 2026 16:35:01 +0100 Subject: [PATCH 03/11] partially working for per method analysis --- .../Models/UncheckedOptionalAccessModel.cpp | 19 +++++++++++-- clang/lib/Sema/SemaDeclAttr.cpp | 27 +++++++++++++++++++ hicketts_test/hicketts_optional.h | 17 ++++++------ 3 files changed, 52 insertions(+), 11 deletions(-) diff --git a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp index 317f5034b0145..f2c9aa756943e 100644 --- a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp +++ b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp @@ -230,6 +230,20 @@ AST_MATCHER(CXXOperatorCallExpr, hasOptionalOperatorObjectType) { return hasReceiverTypeDesugaringToOptional(Node.getArg(0)); } +AST_MATCHER_P(NamedDecl, hasAnalyseAsMethodName, std::string, MethodName) { + if (const auto *MD = dyn_cast<CXXMethodDecl>(&Node)) { + if (const auto *Attr = MD->getAttr<AnalyseAsMethodAttr>()) { + StringRef AttrValue = Attr->getMethodName(); + auto Pos = AttrValue.rfind("::"); + StringRef AttrMethodName = (Pos != StringRef::npos) + ? AttrValue.substr(Pos + 2) + : AttrValue; + return AttrMethodName == MethodName; + } + } + return false; +} + auto isOptionalMemberCallWithNameMatcher( ast_matchers::internal::Matcher<NamedDecl> matcher, const std::optional<StatementMatcher> &Ignorable = std::nullopt) { @@ -982,8 +996,9 @@ ignorableOptional(const UncheckedOptionalAccessModelOptions &Options) { StatementMatcher valueCall(const std::optional<StatementMatcher> &IgnorableOptional) { - return isOptionalMemberCallWithNameMatcher(hasName("value"), - IgnorableOptional); + return isOptionalMemberCallWithNameMatcher( + anyOf(hasName("value"), hasAnalyseAsMethodName("value")), + IgnorableOptional); } StatementMatcher diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 243c66b474384..f999c307b7111 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -6485,6 +6485,30 @@ static void handleAnalyseAsClass(Sema &S, Decl *D, const ParsedAttr &AL) { D->addAttr(::new (S.Context) AnalyseAsClassAttr(S.Context, AL, Str)); } +// for now this only handles std::optional (POC) +static bool isValidAnalyseAsMethodAttr(Decl *D, StringRef Tag) { + // no validation is done currently. if someone writes something with a nonsense name, + // it simply won't be validated but also no warning will be emitted + // would be nice to do something smarter in the real implementation + return true; +} + +static void handleAnalyseAsMethod(Sema &S, Decl *D, const ParsedAttr &AL) { + StringRef Str; + if (!S.checkStringLiteralArgumentAttr(AL, 0, Str)) + return; + if (D->hasAttr<AnalyseAsMethodAttr>()) { + S.Diag(AL.getLoc(), diag::err_duplicate_attribute) << AL; + return; + } + if (!isValidAnalyseAsMethodAttr(D, Str)) { + S.Diag(AL.getLoc(), diag::warn_attribute_type_not_supported) << AL; + return; + } + + D->addAttr(::new (S.Context) AnalyseAsMethodAttr(S.Context, AL, Str)); +} + static bool hasBTFDeclTagAttr(Decl *D, StringRef Tag) { for (const auto *I : D->specific_attrs<BTFDeclTagAttr>()) { if (I->getBTFDeclTag() == Tag) @@ -7577,6 +7601,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL, case ParsedAttr::AT_AnalyseAsClass: handleAnalyseAsClass(S, D, AL); break; + case ParsedAttr::AT_AnalyseAsMethod: + handleAnalyseAsMethod(S, D, AL); + break; case ParsedAttr::AT_BTFDeclTag: handleBTFDeclTagAttr(S, D, AL); break; diff --git a/hicketts_test/hicketts_optional.h b/hicketts_test/hicketts_optional.h index b915b01f70290..43ec98831a74b 100644 --- a/hicketts_test/hicketts_optional.h +++ b/hicketts_test/hicketts_optional.h @@ -26,11 +26,10 @@ class [[clang::analyse_as_class("std::optional")]] HickettsOptional { HickettsOptional(HickettsOptional &&) = default; // Equivalent to std::optional::value() - //[[clang_analyse_as_method(std::optional::value)]] - const T &unwrap() const & { return *storage_; } - T &unwrap() & { return *storage_; } - const T &&unwrap() const && { return static_cast<const T &&>(*storage_); } - T &&unwrap() && { return static_cast<T &&>(*storage_); } + [[clang::analyse_as_method("std::optional::value")]] const T &unwrap() const & { return *storage_; } + [[clang::analyse_as_method("std::optional::value")]] T &unwrap() & { return *storage_; } + [[clang::analyse_as_method("std::optional::value")]] const T &&unwrap() const && { return static_cast<const T &&>(*storage_); } + [[clang::analyse_as_method("std::optional::value")]] T &&unwrap() && { return static_cast<T &&>(*storage_); } const T &value() const & { return *storage_; } T &value() & { return *storage_; } @@ -38,8 +37,8 @@ class [[clang::analyse_as_class("std::optional")]] HickettsOptional { T &&value() && { return static_cast<T &&>(*storage_); } // Equivalent to std::optional::operator*() - const T &deref() const & { return *storage_; } - T &deref() & { return *storage_; } + [[clang::analyse_as_method("std::optional::operator*")]] const T &deref() const & { return *storage_; } + [[clang::analyse_as_method("std::optional::operator*")]] T &deref() & { return *storage_; } // Equivalent to std::optional::operator->() const T* operator ->() const { return storage_; } @@ -47,10 +46,10 @@ class [[clang::analyse_as_class("std::optional")]] HickettsOptional { const T *arrow() const { return storage_; } T *arrow() { return storage_; } - // Equivalent to std::optional::operator bool / has_value() + // Equivalent to std::optional::operator bool / hasValue() constexpr bool hasValue() const noexcept { return storage_ != nullptr; } constexpr explicit operator bool() const noexcept { return storage_ != nullptr; } - constexpr bool isPresent() const noexcept { return storage_ != nullptr; } + [[clang::analyse_as_method("std::optional::hasValue")]] constexpr bool isPresent() const noexcept { return storage_ != nullptr; } constexpr bool isEmpty() const noexcept { return storage_ == nullptr; } // Equivalent to std::optional::value_or() >From 9d6520a6cac0f322cd67ac5a1619a6b8fd0128cd Mon Sep 17 00:00:00 2001 From: khickett <[email protected]> Date: Tue, 28 Apr 2026 17:07:21 +0100 Subject: [PATCH 04/11] extend method matching . --- .../Models/UncheckedOptionalAccessModel.cpp | 11 ++++++----- hicketts_test/hicketts_optional.h | 15 +++++++-------- hicketts_test/test_hicketts_optional.cpp | 10 +++++----- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp index f2c9aa756943e..137b44560c529 100644 --- a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp +++ b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp @@ -358,7 +358,7 @@ auto isValueOrStringEmptyCall() { callee(cxxMethodDecl(hasName("empty"))), onImplicitObjectArgument(ignoringImplicit( cxxMemberCallExpr(on(expr(unless(cxxThisExpr()))), - callee(cxxMethodDecl(hasName("value_or"), + callee(cxxMethodDecl(anyOf(hasName("value_or"), hasAnalyseAsMethodName("value_or")), ofClass(optionalClass()))), hasArgument(0, stringLiteral(hasSize(0)))) .bind(ValueOrCallID)))); @@ -1071,7 +1071,8 @@ auto buildTransferMatchSwitch() { // will also pass for other types .CaseOfCFGStmt<CXXMemberCallExpr>( isOptionalMemberCallWithNameMatcher( - hasAnyName("has_value", "hasValue")), + anyOf(hasAnyName("has_value", "hasValue"), + hasAnalyseAsMethodName("has_value"))), transferOptionalHasValueCall) // optional::operator bool @@ -1101,7 +1102,7 @@ auto buildTransferMatchSwitch() { // optional::emplace .CaseOfCFGStmt<CXXMemberCallExpr>( - isOptionalMemberCallWithNameMatcher(hasName("emplace")), + isOptionalMemberCallWithNameMatcher(anyOf(hasName("emplace"), hasAnalyseAsMethodName("emplace"))), [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &, LatticeTransferState &State) { if (RecordStorageLocation *Loc = @@ -1112,7 +1113,7 @@ auto buildTransferMatchSwitch() { // optional::reset .CaseOfCFGStmt<CXXMemberCallExpr>( - isOptionalMemberCallWithNameMatcher(hasName("reset")), + isOptionalMemberCallWithNameMatcher(anyOf(hasName("reset"), hasAnalyseAsMethodName("reset"))), [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &, LatticeTransferState &State) { if (RecordStorageLocation *Loc = @@ -1124,7 +1125,7 @@ auto buildTransferMatchSwitch() { // optional::swap .CaseOfCFGStmt<CXXMemberCallExpr>( - isOptionalMemberCallWithNameMatcher(hasName("swap")), + isOptionalMemberCallWithNameMatcher(anyOf(hasName("swap"), hasAnalyseAsMethodName("swap"))), transferSwapCall) // std::swap diff --git a/hicketts_test/hicketts_optional.h b/hicketts_test/hicketts_optional.h index 43ec98831a74b..2c6978a7e2339 100644 --- a/hicketts_test/hicketts_optional.h +++ b/hicketts_test/hicketts_optional.h @@ -37,8 +37,8 @@ class [[clang::analyse_as_class("std::optional")]] HickettsOptional { T &&value() && { return static_cast<T &&>(*storage_); } // Equivalent to std::optional::operator*() - [[clang::analyse_as_method("std::optional::operator*")]] const T &deref() const & { return *storage_; } - [[clang::analyse_as_method("std::optional::operator*")]] T &deref() & { return *storage_; } + [[clang::analyse_as_method("std::optional::value")]] const T &deref() const & { return *storage_; } + [[clang::analyse_as_method("std::optional::value")]] T &deref() & { return *storage_; } // Equivalent to std::optional::operator->() const T* operator ->() const { return storage_; } @@ -47,10 +47,9 @@ class [[clang::analyse_as_class("std::optional")]] HickettsOptional { T *arrow() { return storage_; } // Equivalent to std::optional::operator bool / hasValue() - constexpr bool hasValue() const noexcept { return storage_ != nullptr; } + constexpr bool has_value() const noexcept { return storage_ != nullptr; } constexpr explicit operator bool() const noexcept { return storage_ != nullptr; } - [[clang::analyse_as_method("std::optional::hasValue")]] constexpr bool isPresent() const noexcept { return storage_ != nullptr; } - constexpr bool isEmpty() const noexcept { return storage_ == nullptr; } + [[clang::analyse_as_method("std::optional::has_value")]] constexpr bool isPresent() const noexcept { return storage_ != nullptr; } // Equivalent to std::optional::value_or() template <typename U> @@ -60,13 +59,13 @@ class [[clang::analyse_as_class("std::optional")]] HickettsOptional { // Equivalent to std::optional::emplace() template <typename... Args> - T &construct(Args &&...args) { return *storage_; } + [[clang::analyse_as_method("std::optional::emplace")]] T &construct(Args &&...args) { return *storage_; } // Equivalent to std::optional::reset() - void clear() noexcept { storage_ = nullptr; } + [[clang::analyse_as_method("std::optional::reset")]] void clear() noexcept { storage_ = nullptr; } // Equivalent to std::optional::swap() - void exchange(HickettsOptional &other) noexcept { + [[clang::analyse_as_method("std::optional::swap")]] void exchange(HickettsOptional &other) noexcept { T *tmp = storage_; storage_ = other.storage_; other.storage_ = tmp; diff --git a/hicketts_test/test_hicketts_optional.cpp b/hicketts_test/test_hicketts_optional.cpp index fa0153a79f341..1ff16d13fe174 100644 --- a/hicketts_test/test_hicketts_optional.cpp +++ b/hicketts_test/test_hicketts_optional.cpp @@ -30,7 +30,7 @@ static void checkedWithBool(mylib::HickettsOptional<int> &Val) { } static void checkedValueWithBool(mylib::HickettsOptional<int> &Val) { - if (Val.hasValue()) { + if (Val.has_value()) { Val.value(); // safe — checked via operator bool } } @@ -41,11 +41,11 @@ static void checkedWithIsPresent(mylib::HickettsOptional<int> &Val) { } } -static void checkedWithIsEmpty(mylib::HickettsOptional<int> &Val) { +/* static void checkedWithIsEmpty(mylib::HickettsOptional<int> &Val) { if (!Val.isEmpty()) { Val.unwrap(); // safe — checked via !isEmpty() } -} +} NYI */ // --- State changes --- @@ -70,12 +70,12 @@ static void unsafeAfterExchange(mylib::HickettsOptional<int> &A, // --- Guarded paths --- -static void constructCoversEmptyBranch(mylib::HickettsOptional<int> &Val) { +/*static void constructCoversEmptyBranch(mylib::HickettsOptional<int> &Val) { if (Val.isEmpty()) { Val.construct(99); } Val.unwrap(); // safe — either was present, or construct filled it -} +}*/ static void unwrapOrIsAlwaysSafe(mylib::HickettsOptional<int> &Val) { int X = Val.unwrapOr(0); // safe — fallback provided >From 34ce7b2b289e5e5a4cb0d708d34f4675d7bdc426 Mon Sep 17 00:00:00 2001 From: khickett <[email protected]> Date: Thu, 30 Apr 2026 12:48:57 +0100 Subject: [PATCH 05/11] missed a bit --- clang/include/clang/Basic/Attr.td | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index 97536ac7a1966..adc127df5756e 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -921,6 +921,22 @@ def AlignValue : Attr { let Documentation = [AlignValueDocs]; } +def AnalyseAsClass : InheritableAttr { + let Spellings = [Clang<"analyse_as_class">]; + let Args = [StringArgument<"ClassName">]; + let Subjects = SubjectList<[Record], + ErrorDiag>; + let Documentation = [Undocumented]; +} + +def AnalyseAsMethod : InheritableAttr { + let Spellings = [Clang<"analyse_as_method">]; + let Args = [StringArgument<"MethodName">]; + let Subjects = SubjectList<[CXXMethod], + ErrorDiag>; + let Documentation = [Undocumented]; +} + def AlignMac68k : InheritableAttr { // This attribute has no spellings as it is only ever created implicitly. let Spellings = []; >From 3532f8f3ae825e185f7f5c3506e3b96fbd16dda3 Mon Sep 17 00:00:00 2001 From: khickett <[email protected]> Date: Fri, 1 May 2026 15:40:38 +0100 Subject: [PATCH 06/11] add unit tests --- .../UncheckedOptionalAccessModelTest.cpp | 50 +++++++++++++++++-- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp index 074dc95e2c388..72cf12bcd0817 100644 --- a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp +++ b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp @@ -946,7 +946,7 @@ TEST_P(UncheckedOptionalAccessTest, OptionalReturnedFromFuntionCall) { ExpectDiagnosticsFor( R"( #include "unchecked_optional_access_test.h" - + struct S { $ns::$optional<float> x; } s; @@ -2860,14 +2860,14 @@ TEST_P( struct B { const A& getA() const { return a; } - void callWithoutChanges() const { - // no-op + void callWithoutChanges() const { + // no-op } A a; }; - void target(B& b) { + void target(B& b) { if (b.getA().get().has_value()) { b.callWithoutChanges(); // calling const method which cannot change A b.getA().get().value(); @@ -2995,6 +2995,48 @@ TEST_P(UncheckedOptionalAccessTest, AssertFalseGtestMacroWithNullableValue) { )cc"); } +TEST_P(UncheckedOptionalAccessTest, TestCustomAttributeBalik) { + ExpectDiagnosticsFor(R"cc( + #include "unchecked_optional_access_test.h" + + template <typename T> + class __attribute__((analyse_as_class("std::optional"))) MyOptional { + public: + bool has_value() const; + T& value(); + const T& value() const; + }; + + void target(MyOptional<int> opt) { + opt.value(); // [[unsafe]] + if (opt.has_value()) { + opt.value(); + } + } + )cc"); +} + +TEST_P(UncheckedOptionalAccessTest, TestCustomAttributeHicketts) { + ExpectDiagnosticsFor(R"cc( + #include "unchecked_optional_access_test.h" + + template <typename T> + class __attribute__((analyse_as_class("std::optional"))) MyOptional { + public: + __attribute__((analyse_as_method("std::optional::has_value"))) bool isNotNull() const; + __attribute__((analyse_as_method("std::optional::value"))) T& unwrap(); + __attribute__((analyse_as_method("std::optional::value"))) const T& unwrap() const; + }; + + void target(MyOptional<int> opt) { + opt.unwrap(); // [[unsafe]] + if (opt.isNotNull()) { + opt.unwrap(); + } + } + )cc"); +} + // FIXME: Add support for: // - constructors (copy, move) // - assignment operators (default, copy, move) >From 7320c8c063be143928434c9046eb85cf7e22ed78 Mon Sep 17 00:00:00 2001 From: khickett <[email protected]> Date: Fri, 1 May 2026 16:01:22 +0100 Subject: [PATCH 07/11] . --- .../bugprone/unchecked-optional-access.cpp | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) 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 337474bdf7535..cdd8fdb82174b 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 @@ -384,3 +384,72 @@ void foo() { if (!vec.empty()) vec[0].x = 0; } + +// Custom optional-like type using analyse_as_class / analyse_as_method attributes. +template <typename T> +class [[clang::analyse_as_class("std::optional")]] CustomOptional { +public: + [[clang::analyse_as_method("std::optional::has_value")]] bool isEngaged() const; + [[clang::analyse_as_method("std::optional::value")]] T &retrieve(); + [[clang::analyse_as_method("std::optional::value")]] const T &retrieve() const; + [[clang::analyse_as_method("std::optional::emplace")]] T &fill(T val); + [[clang::analyse_as_method("std::optional::reset")]] void clear(); +}; + +void custom_unchecked_access(CustomOptional<int> opt) { + opt.retrieve(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: unchecked access to optional value [bugprone-unchecked-optional-access] +} + +void custom_checked_access(CustomOptional<int> opt) { + if (opt.isEngaged()) { + opt.retrieve(); + } +} + +void custom_emplace_then_access(CustomOptional<int> opt) { + opt.fill(42); + opt.retrieve(); +} + +void custom_clear_then_access(CustomOptional<int> opt) { + opt.fill(42); + opt.clear(); + opt.retrieve(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: unchecked access to optional value [bugprone-unchecked-optional-access] +} + +// Custom optional-like type using standard method names — recognised via +// analyse_as_class alone, without analyse_as_method attributes. +template <typename T> +class [[clang::analyse_as_class("std::optional")]] StdNamedOptional { +public: + bool has_value() const; + T &value(); + const T &value() const; + T &emplace(T val); + void reset(); +}; + +void std_named_unchecked_access(StdNamedOptional<int> opt) { + opt.value(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: unchecked access to optional value [bugprone-unchecked-optional-access] +} + +void std_named_checked_access(StdNamedOptional<int> opt) { + if (opt.has_value()) { + opt.value(); + } +} + +void std_named_emplace_then_access(StdNamedOptional<int> opt) { + opt.emplace(42); + opt.value(); +} + +void std_named_reset_then_access(StdNamedOptional<int> opt) { + opt.emplace(42); + opt.reset(); + opt.value(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: unchecked access to optional value [bugprone-unchecked-optional-access] +} >From dc6922262ffdaba0b0202b93e82f320286249bca Mon Sep 17 00:00:00 2001 From: khickett <[email protected]> Date: Fri, 1 May 2026 16:16:29 +0100 Subject: [PATCH 08/11] . --- hicketts_test/hicketts_optional.h | 81 ----------------------- hicketts_test/test_hicketts_optional.cpp | 83 ------------------------ 2 files changed, 164 deletions(-) delete mode 100644 hicketts_test/hicketts_optional.h delete mode 100644 hicketts_test/test_hicketts_optional.cpp diff --git a/hicketts_test/hicketts_optional.h b/hicketts_test/hicketts_optional.h deleted file mode 100644 index 2c6978a7e2339..0000000000000 --- a/hicketts_test/hicketts_optional.h +++ /dev/null @@ -1,81 +0,0 @@ -#ifndef HICKETTS_OPTIONAL_H_ -#define HICKETTS_OPTIONAL_H_ - -/// A custom optional-like type with differently named functions. -/// Mirrors std::optional semantics but uses its own vocabulary -/// In order to test implementation of attributes for clang-tidy -namespace mylib { - -struct nothing_t { - constexpr explicit nothing_t() {} -}; - -constexpr nothing_t nothing; - -template <typename T> -class [[clang::analyse_as_class("std::optional")]] HickettsOptional { - T *storage_ = nullptr; - -public: - constexpr HickettsOptional() noexcept {} - - constexpr HickettsOptional(nothing_t) noexcept {} - - HickettsOptional(const HickettsOptional &) = default; - - HickettsOptional(HickettsOptional &&) = default; - - // Equivalent to std::optional::value() - [[clang::analyse_as_method("std::optional::value")]] const T &unwrap() const & { return *storage_; } - [[clang::analyse_as_method("std::optional::value")]] T &unwrap() & { return *storage_; } - [[clang::analyse_as_method("std::optional::value")]] const T &&unwrap() const && { return static_cast<const T &&>(*storage_); } - [[clang::analyse_as_method("std::optional::value")]] T &&unwrap() && { return static_cast<T &&>(*storage_); } - - const T &value() const & { return *storage_; } - T &value() & { return *storage_; } - const T &&value() const && { return static_cast<const T &&>(*storage_); } - T &&value() && { return static_cast<T &&>(*storage_); } - - // Equivalent to std::optional::operator*() - [[clang::analyse_as_method("std::optional::value")]] const T &deref() const & { return *storage_; } - [[clang::analyse_as_method("std::optional::value")]] T &deref() & { return *storage_; } - - // Equivalent to std::optional::operator->() - const T* operator ->() const { return storage_; } - T* operator ->() { return storage_; } - const T *arrow() const { return storage_; } - T *arrow() { return storage_; } - - // Equivalent to std::optional::operator bool / hasValue() - constexpr bool has_value() const noexcept { return storage_ != nullptr; } - constexpr explicit operator bool() const noexcept { return storage_ != nullptr; } - [[clang::analyse_as_method("std::optional::has_value")]] constexpr bool isPresent() const noexcept { return storage_ != nullptr; } - - // Equivalent to std::optional::value_or() - template <typename U> - constexpr T unwrapOr(U &&fallback) const & { - return storage_ ? *storage_ : static_cast<T>(fallback); - } - - // Equivalent to std::optional::emplace() - template <typename... Args> - [[clang::analyse_as_method("std::optional::emplace")]] T &construct(Args &&...args) { return *storage_; } - - // Equivalent to std::optional::reset() - [[clang::analyse_as_method("std::optional::reset")]] void clear() noexcept { storage_ = nullptr; } - - // Equivalent to std::optional::swap() - [[clang::analyse_as_method("std::optional::swap")]] void exchange(HickettsOptional &other) noexcept { - T *tmp = storage_; - storage_ = other.storage_; - other.storage_ = tmp; - } - - // Assignment - template <typename U> - HickettsOptional &operator=(const U &u) { return *this; } -}; - -} // namespace mylib - -#endif // HICKETTS_OPTIONAL_H_ diff --git a/hicketts_test/test_hicketts_optional.cpp b/hicketts_test/test_hicketts_optional.cpp deleted file mode 100644 index 1ff16d13fe174..0000000000000 --- a/hicketts_test/test_hicketts_optional.cpp +++ /dev/null @@ -1,83 +0,0 @@ -// Test cases for mylib::HickettsOptional — a custom optional-like type -// with differently named functions. -// -// Run from hicketts_test/ with: -// clang-tidy -checks='bugprone-unchecked-optional-access' \ -// test_hicketts_optional.cpp -- -I . -Wno-undefined-inline - -#include "hicketts_optional.h" - -// --- Unchecked access (should warn if the checker recognises HickettsOptional) --- - -static void uncheckedUnwrap(mylib::HickettsOptional<int> &Val) { - Val.unwrap(); // unchecked access — may be empty -} - -static void uncheckedValue(mylib::HickettsOptional<int> &Val) { - Val.value(); // unchecked access — may be empty -} - -static void uncheckedDeref(mylib::HickettsOptional<int> &Val) { - Val.deref(); // unchecked access — may be empty -} - -// --- Checked access (should NOT warn) --- - -static void checkedWithBool(mylib::HickettsOptional<int> &Val) { - if (Val) { - Val.unwrap(); // safe — checked via operator bool - } -} - -static void checkedValueWithBool(mylib::HickettsOptional<int> &Val) { - if (Val.has_value()) { - Val.value(); // safe — checked via operator bool - } -} - -static void checkedWithIsPresent(mylib::HickettsOptional<int> &Val) { - if (Val.isPresent()) { - Val.unwrap(); // safe — checked via isPresent() - } -} - -/* static void checkedWithIsEmpty(mylib::HickettsOptional<int> &Val) { - if (!Val.isEmpty()) { - Val.unwrap(); // safe — checked via !isEmpty() - } -} NYI */ - -// --- State changes --- - -static void safeAfterConstruct(mylib::HickettsOptional<int> &Val) { - Val.construct(42); - Val.unwrap(); // safe — just constructed a value -} - -static void unsafeAfterClear(mylib::HickettsOptional<int> &Val) { - Val.construct(42); - Val.clear(); - Val.unwrap(); // unsafe — value was cleared -} - -static void unsafeAfterExchange(mylib::HickettsOptional<int> &A, - mylib::HickettsOptional<int> &B) { - if (A) { - A.exchange(B); - A.unwrap(); // unsafe — a's state is now unknown - } -} - -// --- Guarded paths --- - -/*static void constructCoversEmptyBranch(mylib::HickettsOptional<int> &Val) { - if (Val.isEmpty()) { - Val.construct(99); - } - Val.unwrap(); // safe — either was present, or construct filled it -}*/ - -static void unwrapOrIsAlwaysSafe(mylib::HickettsOptional<int> &Val) { - int X = Val.unwrapOr(0); // safe — fallback provided - (void)X; -} >From e50be59fa64622856902e3b0a80d1e8c1699d9ec Mon Sep 17 00:00:00 2001 From: khickett <[email protected]> Date: Fri, 1 May 2026 17:28:47 +0100 Subject: [PATCH 09/11] comment possible cody tidy up locations --- .../Models/UncheckedOptionalAccessModel.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp index 137b44560c529..1fee746ff7a97 100644 --- a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp +++ b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp @@ -71,6 +71,8 @@ static bool hasOptionalClassName(const CXXRecordDecl &RD) { return false; } + // this code could be removed if base::Optional and folly::Optional used + // [[clang::analyse_as_class("std::optional")]] if (RD.getName() == "Optional") { // Check whether namespace is "::base" or "::folly". const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext()); @@ -78,12 +80,16 @@ static bool hasOptionalClassName(const CXXRecordDecl &RD) { isFullyQualifiedNamespaceEqualTo(*N, "folly")); } + // this code could be removed if Optional_Base used + // [[clang::analyse_as_class("std::optional")]] if (RD.getName() == "Optional_Base") { const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext()); return N != nullptr && isFullyQualifiedNamespaceEqualTo(*N, "bslstl", "BloombergLP"); } + // this code could be removed if NullableValue used + // [[clang::analyse_as_class("std::optional")]] if (RD.getName() == "NullableValue") { const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext()); return N != nullptr && @@ -1069,6 +1075,8 @@ auto buildTransferMatchSwitch() { // optional::has_value, optional::hasValue // Of the supported optionals only folly::Optional uses hasValue, but this // will also pass for other types + // "hasValue" could be removed if folly::Optional used + // [[clang::analyse_as_method("std::optional::has_value")]] on hasValue() .CaseOfCFGStmt<CXXMemberCallExpr>( isOptionalMemberCallWithNameMatcher( anyOf(hasAnyName("has_value", "hasValue"), @@ -1080,12 +1088,16 @@ auto buildTransferMatchSwitch() { isOptionalMemberCallWithNameMatcher(hasName("operator bool")), transferOptionalHasValueCall) + // this code could be removed if NullableValue used + // [[clang::analyse_as_inverse_method("std::optional::has_value")]] on isNull() *NYI // NullableValue::isNull // Only NullableValue has isNull .CaseOfCFGStmt<CXXMemberCallExpr>( isOptionalMemberCallWithNameMatcher(hasName("isNull")), transferOptionalIsNullCall) + // this code could be removed if NullableValue used + // [[clang::analyse_as_method("std::optional::emplace")]] on makeValue() and makeValueInplace() // NullableValue::makeValue, NullableValue::makeValueInplace // Only NullableValue has these methods, but this // will also pass for other types >From a0d0dd9c93a309410c15030774368af98c425e1e Mon Sep 17 00:00:00 2001 From: khickett <[email protected]> Date: Fri, 1 May 2026 17:51:13 +0100 Subject: [PATCH 10/11] remove fully qualified names for method attributes --- .../checkers/bugprone/unchecked-optional-access.cpp | 10 +++++----- .../Models/UncheckedOptionalAccessModel.cpp | 10 +++------- .../FlowSensitive/UncheckedOptionalAccessModelTest.cpp | 6 +++--- 3 files changed, 11 insertions(+), 15 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 cdd8fdb82174b..bea941ff2104d 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 @@ -389,11 +389,11 @@ void foo() { template <typename T> class [[clang::analyse_as_class("std::optional")]] CustomOptional { public: - [[clang::analyse_as_method("std::optional::has_value")]] bool isEngaged() const; - [[clang::analyse_as_method("std::optional::value")]] T &retrieve(); - [[clang::analyse_as_method("std::optional::value")]] const T &retrieve() const; - [[clang::analyse_as_method("std::optional::emplace")]] T &fill(T val); - [[clang::analyse_as_method("std::optional::reset")]] void clear(); + [[clang::analyse_as_method("has_value")]] bool isEngaged() const; + [[clang::analyse_as_method("value")]] T &retrieve(); + [[clang::analyse_as_method("value")]] const T &retrieve() const; + [[clang::analyse_as_method("emplace")]] T &fill(T val); + [[clang::analyse_as_method("reset")]] void clear(); }; void custom_unchecked_access(CustomOptional<int> opt) { diff --git a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp index 1fee746ff7a97..2264c7e60f190 100644 --- a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp +++ b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp @@ -240,11 +240,7 @@ AST_MATCHER_P(NamedDecl, hasAnalyseAsMethodName, std::string, MethodName) { if (const auto *MD = dyn_cast<CXXMethodDecl>(&Node)) { if (const auto *Attr = MD->getAttr<AnalyseAsMethodAttr>()) { StringRef AttrValue = Attr->getMethodName(); - auto Pos = AttrValue.rfind("::"); - StringRef AttrMethodName = (Pos != StringRef::npos) - ? AttrValue.substr(Pos + 2) - : AttrValue; - return AttrMethodName == MethodName; + return AttrValue == MethodName; } } return false; @@ -1076,7 +1072,7 @@ auto buildTransferMatchSwitch() { // Of the supported optionals only folly::Optional uses hasValue, but this // will also pass for other types // "hasValue" could be removed if folly::Optional used - // [[clang::analyse_as_method("std::optional::has_value")]] on hasValue() + // [[clang::analyse_as_method("has_value")]] on hasValue() .CaseOfCFGStmt<CXXMemberCallExpr>( isOptionalMemberCallWithNameMatcher( anyOf(hasAnyName("has_value", "hasValue"), @@ -1097,7 +1093,7 @@ auto buildTransferMatchSwitch() { transferOptionalIsNullCall) // this code could be removed if NullableValue used - // [[clang::analyse_as_method("std::optional::emplace")]] on makeValue() and makeValueInplace() + // [[clang::analyse_as_method("emplace")]] on makeValue() and makeValueInplace() // NullableValue::makeValue, NullableValue::makeValueInplace // Only NullableValue has these methods, but this // will also pass for other types diff --git a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp index 72cf12bcd0817..ac67bda300b65 100644 --- a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp +++ b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp @@ -3023,9 +3023,9 @@ TEST_P(UncheckedOptionalAccessTest, TestCustomAttributeHicketts) { template <typename T> class __attribute__((analyse_as_class("std::optional"))) MyOptional { public: - __attribute__((analyse_as_method("std::optional::has_value"))) bool isNotNull() const; - __attribute__((analyse_as_method("std::optional::value"))) T& unwrap(); - __attribute__((analyse_as_method("std::optional::value"))) const T& unwrap() const; + __attribute__((analyse_as_method("has_value"))) bool isNotNull() const; + __attribute__((analyse_as_method("value"))) T& unwrap(); + __attribute__((analyse_as_method("value"))) const T& unwrap() const; }; void target(MyOptional<int> opt) { >From 7263f13aea755ba43cbf6efcfafeaa2326cc2d59 Mon Sep 17 00:00:00 2001 From: khickett <[email protected]> Date: Fri, 1 May 2026 17:55:12 +0100 Subject: [PATCH 11/11] American spelling is just cheating at Scrabble... --- .../bugprone/unchecked-optional-access.cpp | 18 +++++------ clang/include/clang/Basic/Attr.td | 8 ++--- .../Models/UncheckedOptionalAccessModel.cpp | 30 +++++++++---------- clang/lib/Sema/SemaDeclAttr.cpp | 28 ++++++++--------- .../UncheckedOptionalAccessModelTest.cpp | 10 +++---- 5 files changed, 47 insertions(+), 47 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 bea941ff2104d..6d68b95697925 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 @@ -385,15 +385,15 @@ void foo() { vec[0].x = 0; } -// Custom optional-like type using analyse_as_class / analyse_as_method attributes. +// Custom optional-like type using analyze_as_class / analyze_as_method attributes. template <typename T> -class [[clang::analyse_as_class("std::optional")]] CustomOptional { +class [[clang::analyze_as_class("std::optional")]] CustomOptional { public: - [[clang::analyse_as_method("has_value")]] bool isEngaged() const; - [[clang::analyse_as_method("value")]] T &retrieve(); - [[clang::analyse_as_method("value")]] const T &retrieve() const; - [[clang::analyse_as_method("emplace")]] T &fill(T val); - [[clang::analyse_as_method("reset")]] void clear(); + [[clang::analyze_as_method("has_value")]] bool isEngaged() const; + [[clang::analyze_as_method("value")]] T &retrieve(); + [[clang::analyze_as_method("value")]] const T &retrieve() const; + [[clang::analyze_as_method("emplace")]] T &fill(T val); + [[clang::analyze_as_method("reset")]] void clear(); }; void custom_unchecked_access(CustomOptional<int> opt) { @@ -420,9 +420,9 @@ void custom_clear_then_access(CustomOptional<int> opt) { } // Custom optional-like type using standard method names — recognised via -// analyse_as_class alone, without analyse_as_method attributes. +// analyze_as_class alone, without analyze_as_method attributes. template <typename T> -class [[clang::analyse_as_class("std::optional")]] StdNamedOptional { +class [[clang::analyze_as_class("std::optional")]] StdNamedOptional { public: bool has_value() const; T &value(); diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index adc127df5756e..7db521598bff7 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -921,16 +921,16 @@ def AlignValue : Attr { let Documentation = [AlignValueDocs]; } -def AnalyseAsClass : InheritableAttr { - let Spellings = [Clang<"analyse_as_class">]; +def AnalyzeAsClass : InheritableAttr { + let Spellings = [Clang<"analyze_as_class">]; let Args = [StringArgument<"ClassName">]; let Subjects = SubjectList<[Record], ErrorDiag>; let Documentation = [Undocumented]; } -def AnalyseAsMethod : InheritableAttr { - let Spellings = [Clang<"analyse_as_method">]; +def AnalyzeAsMethod : InheritableAttr { + let Spellings = [Clang<"analyze_as_method">]; let Args = [StringArgument<"MethodName">]; let Subjects = SubjectList<[CXXMethod], ErrorDiag>; diff --git a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp index 2264c7e60f190..49743b94eebeb 100644 --- a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp +++ b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp @@ -72,7 +72,7 @@ static bool hasOptionalClassName(const CXXRecordDecl &RD) { } // this code could be removed if base::Optional and folly::Optional used - // [[clang::analyse_as_class("std::optional")]] + // [[clang::analyze_as_class("std::optional")]] if (RD.getName() == "Optional") { // Check whether namespace is "::base" or "::folly". const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext()); @@ -81,7 +81,7 @@ static bool hasOptionalClassName(const CXXRecordDecl &RD) { } // this code could be removed if Optional_Base used - // [[clang::analyse_as_class("std::optional")]] + // [[clang::analyze_as_class("std::optional")]] if (RD.getName() == "Optional_Base") { const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext()); return N != nullptr && @@ -89,14 +89,14 @@ static bool hasOptionalClassName(const CXXRecordDecl &RD) { } // this code could be removed if NullableValue used - // [[clang::analyse_as_class("std::optional")]] + // [[clang::analyze_as_class("std::optional")]] if (RD.getName() == "NullableValue") { const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext()); return N != nullptr && isFullyQualifiedNamespaceEqualTo(*N, "bdlb", "BloombergLP"); } - if (RD.hasAttr<AnalyseAsClassAttr>()) + if (RD.hasAttr<AnalyzeAsClassAttr>()) return true; return false; @@ -236,9 +236,9 @@ AST_MATCHER(CXXOperatorCallExpr, hasOptionalOperatorObjectType) { return hasReceiverTypeDesugaringToOptional(Node.getArg(0)); } -AST_MATCHER_P(NamedDecl, hasAnalyseAsMethodName, std::string, MethodName) { +AST_MATCHER_P(NamedDecl, hasAnalyzeAsMethodName, std::string, MethodName) { if (const auto *MD = dyn_cast<CXXMethodDecl>(&Node)) { - if (const auto *Attr = MD->getAttr<AnalyseAsMethodAttr>()) { + if (const auto *Attr = MD->getAttr<AnalyzeAsMethodAttr>()) { StringRef AttrValue = Attr->getMethodName(); return AttrValue == MethodName; } @@ -360,7 +360,7 @@ auto isValueOrStringEmptyCall() { callee(cxxMethodDecl(hasName("empty"))), onImplicitObjectArgument(ignoringImplicit( cxxMemberCallExpr(on(expr(unless(cxxThisExpr()))), - callee(cxxMethodDecl(anyOf(hasName("value_or"), hasAnalyseAsMethodName("value_or")), + callee(cxxMethodDecl(anyOf(hasName("value_or"), hasAnalyzeAsMethodName("value_or")), ofClass(optionalClass()))), hasArgument(0, stringLiteral(hasSize(0)))) .bind(ValueOrCallID)))); @@ -999,7 +999,7 @@ ignorableOptional(const UncheckedOptionalAccessModelOptions &Options) { StatementMatcher valueCall(const std::optional<StatementMatcher> &IgnorableOptional) { return isOptionalMemberCallWithNameMatcher( - anyOf(hasName("value"), hasAnalyseAsMethodName("value")), + anyOf(hasName("value"), hasAnalyzeAsMethodName("value")), IgnorableOptional); } @@ -1072,11 +1072,11 @@ auto buildTransferMatchSwitch() { // Of the supported optionals only folly::Optional uses hasValue, but this // will also pass for other types // "hasValue" could be removed if folly::Optional used - // [[clang::analyse_as_method("has_value")]] on hasValue() + // [[clang::analyze_as_method("has_value")]] on hasValue() .CaseOfCFGStmt<CXXMemberCallExpr>( isOptionalMemberCallWithNameMatcher( anyOf(hasAnyName("has_value", "hasValue"), - hasAnalyseAsMethodName("has_value"))), + hasAnalyzeAsMethodName("has_value"))), transferOptionalHasValueCall) // optional::operator bool @@ -1085,7 +1085,7 @@ auto buildTransferMatchSwitch() { transferOptionalHasValueCall) // this code could be removed if NullableValue used - // [[clang::analyse_as_inverse_method("std::optional::has_value")]] on isNull() *NYI + // [[clang::analyze_as_inverse_method("std::optional::has_value")]] on isNull() *NYI // NullableValue::isNull // Only NullableValue has isNull .CaseOfCFGStmt<CXXMemberCallExpr>( @@ -1093,7 +1093,7 @@ auto buildTransferMatchSwitch() { transferOptionalIsNullCall) // this code could be removed if NullableValue used - // [[clang::analyse_as_method("emplace")]] on makeValue() and makeValueInplace() + // [[clang::analyze_as_method("emplace")]] on makeValue() and makeValueInplace() // NullableValue::makeValue, NullableValue::makeValueInplace // Only NullableValue has these methods, but this // will also pass for other types @@ -1110,7 +1110,7 @@ auto buildTransferMatchSwitch() { // optional::emplace .CaseOfCFGStmt<CXXMemberCallExpr>( - isOptionalMemberCallWithNameMatcher(anyOf(hasName("emplace"), hasAnalyseAsMethodName("emplace"))), + isOptionalMemberCallWithNameMatcher(anyOf(hasName("emplace"), hasAnalyzeAsMethodName("emplace"))), [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &, LatticeTransferState &State) { if (RecordStorageLocation *Loc = @@ -1121,7 +1121,7 @@ auto buildTransferMatchSwitch() { // optional::reset .CaseOfCFGStmt<CXXMemberCallExpr>( - isOptionalMemberCallWithNameMatcher(anyOf(hasName("reset"), hasAnalyseAsMethodName("reset"))), + isOptionalMemberCallWithNameMatcher(anyOf(hasName("reset"), hasAnalyzeAsMethodName("reset"))), [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &, LatticeTransferState &State) { if (RecordStorageLocation *Loc = @@ -1133,7 +1133,7 @@ auto buildTransferMatchSwitch() { // optional::swap .CaseOfCFGStmt<CXXMemberCallExpr>( - isOptionalMemberCallWithNameMatcher(anyOf(hasName("swap"), hasAnalyseAsMethodName("swap"))), + isOptionalMemberCallWithNameMatcher(anyOf(hasName("swap"), hasAnalyzeAsMethodName("swap"))), transferSwapCall) // std::swap diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index f999c307b7111..0aba360f7b7c2 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -6463,50 +6463,50 @@ static void handleAbiTagAttr(Sema &S, Decl *D, const ParsedAttr &AL) { } // for now this only handles std::optional (POC) -static bool isValidAnalyseAsClassAttr(Decl *D, StringRef Tag) { +static bool isValidAnalyzeAsClassAttr(Decl *D, StringRef Tag) { if (Tag == "std::optional") return true; return false; } -static void handleAnalyseAsClass(Sema &S, Decl *D, const ParsedAttr &AL) { +static void handleAnalyzeAsClass(Sema &S, Decl *D, const ParsedAttr &AL) { StringRef Str; if (!S.checkStringLiteralArgumentAttr(AL, 0, Str)) return; - if (D->hasAttr<AnalyseAsClassAttr>()) { + if (D->hasAttr<AnalyzeAsClassAttr>()) { S.Diag(AL.getLoc(), diag::err_duplicate_attribute) << AL; return; } - if (!isValidAnalyseAsClassAttr(D, Str)) { + if (!isValidAnalyzeAsClassAttr(D, Str)) { S.Diag(AL.getLoc(), diag::warn_attribute_type_not_supported) << AL; return; } - D->addAttr(::new (S.Context) AnalyseAsClassAttr(S.Context, AL, Str)); + D->addAttr(::new (S.Context) AnalyzeAsClassAttr(S.Context, AL, Str)); } // for now this only handles std::optional (POC) -static bool isValidAnalyseAsMethodAttr(Decl *D, StringRef Tag) { +static bool isValidAnalyzeAsMethodAttr(Decl *D, StringRef Tag) { // no validation is done currently. if someone writes something with a nonsense name, // it simply won't be validated but also no warning will be emitted // would be nice to do something smarter in the real implementation return true; } -static void handleAnalyseAsMethod(Sema &S, Decl *D, const ParsedAttr &AL) { +static void handleAnalyzeAsMethod(Sema &S, Decl *D, const ParsedAttr &AL) { StringRef Str; if (!S.checkStringLiteralArgumentAttr(AL, 0, Str)) return; - if (D->hasAttr<AnalyseAsMethodAttr>()) { + if (D->hasAttr<AnalyzeAsMethodAttr>()) { S.Diag(AL.getLoc(), diag::err_duplicate_attribute) << AL; return; } - if (!isValidAnalyseAsMethodAttr(D, Str)) { + if (!isValidAnalyzeAsMethodAttr(D, Str)) { S.Diag(AL.getLoc(), diag::warn_attribute_type_not_supported) << AL; return; } - D->addAttr(::new (S.Context) AnalyseAsMethodAttr(S.Context, AL, Str)); + D->addAttr(::new (S.Context) AnalyzeAsMethodAttr(S.Context, AL, Str)); } static bool hasBTFDeclTagAttr(Decl *D, StringRef Tag) { @@ -7598,11 +7598,11 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL, case ParsedAttr::AT_BPFPreserveStaticOffset: handleSimpleAttribute<BPFPreserveStaticOffsetAttr>(S, D, AL); break; - case ParsedAttr::AT_AnalyseAsClass: - handleAnalyseAsClass(S, D, AL); + case ParsedAttr::AT_AnalyzeAsClass: + handleAnalyzeAsClass(S, D, AL); break; - case ParsedAttr::AT_AnalyseAsMethod: - handleAnalyseAsMethod(S, D, AL); + case ParsedAttr::AT_AnalyzeAsMethod: + handleAnalyzeAsMethod(S, D, AL); break; case ParsedAttr::AT_BTFDeclTag: handleBTFDeclTagAttr(S, D, AL); diff --git a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp index ac67bda300b65..5d224ceb7cf3f 100644 --- a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp +++ b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp @@ -3000,7 +3000,7 @@ TEST_P(UncheckedOptionalAccessTest, TestCustomAttributeBalik) { #include "unchecked_optional_access_test.h" template <typename T> - class __attribute__((analyse_as_class("std::optional"))) MyOptional { + class __attribute__((analyze_as_class("std::optional"))) MyOptional { public: bool has_value() const; T& value(); @@ -3021,11 +3021,11 @@ TEST_P(UncheckedOptionalAccessTest, TestCustomAttributeHicketts) { #include "unchecked_optional_access_test.h" template <typename T> - class __attribute__((analyse_as_class("std::optional"))) MyOptional { + class __attribute__((analyze_as_class("std::optional"))) MyOptional { public: - __attribute__((analyse_as_method("has_value"))) bool isNotNull() const; - __attribute__((analyse_as_method("value"))) T& unwrap(); - __attribute__((analyse_as_method("value"))) const T& unwrap() const; + __attribute__((analyze_as_method("has_value"))) bool isNotNull() const; + __attribute__((analyze_as_method("value"))) T& unwrap(); + __attribute__((analyze_as_method("value"))) const T& unwrap() const; }; void target(MyOptional<int> opt) { _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
