aboutsummaryrefslogtreecommitdiff
path: root/clang/lib
diff options
context:
space:
mode:
authorChris Cotter <ccotter14@bloomberg.net>2024-09-25 10:54:31 -0400
committerGitHub <noreply@github.com>2024-09-25 10:54:31 -0400
commit11c423f9bebc3be27722225ca8120e8775be836c (patch)
treee05542cc2bb689bd45572783c244a79caf2916d5 /clang/lib
parent97189492a1a75d39c09b0a54982f2a028c9bd652 (diff)
downloadllvm-11c423f9bebc3be27722225ca8120e8775be836c.zip
llvm-11c423f9bebc3be27722225ca8120e8775be836c.tar.gz
llvm-11c423f9bebc3be27722225ca8120e8775be836c.tar.bz2
[clang-tidy] Add support for bsl::optional (#101450)
Diffstat (limited to 'clang/lib')
-rw-r--r--clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp67
1 files changed, 54 insertions, 13 deletions
diff --git a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
index 0707aa6..70ffe92 100644
--- a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
+++ b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
@@ -38,10 +38,25 @@
namespace clang {
namespace dataflow {
-static bool isTopLevelNamespaceWithName(const NamespaceDecl &NS,
- llvm::StringRef Name) {
- return NS.getDeclName().isIdentifier() && NS.getName() == Name &&
- NS.getParent() != nullptr && NS.getParent()->isTranslationUnit();
+// Note: the Names appear in reverse order. E.g., to check
+// if NS is foo::bar::, call isFullyQualifiedNamespaceEqualTo(NS, "bar", "foo")
+template <class... NameTypes>
+static bool isFullyQualifiedNamespaceEqualTo(const NamespaceDecl &NS,
+ llvm::StringRef Name,
+ NameTypes... Names) {
+ if (!(NS.getDeclName().isIdentifier() && NS.getName() == Name &&
+ NS.getParent() != nullptr))
+ return false;
+
+ if constexpr (sizeof...(NameTypes) > 0) {
+ if (NS.getParent()->isTranslationUnit())
+ return false;
+ if (const auto *NextNS = dyn_cast_or_null<NamespaceDecl>(NS.getParent()))
+ return isFullyQualifiedNamespaceEqualTo(*NextNS, Names...);
+ return false;
+ } else {
+ return NS.getParent()->isTranslationUnit();
+ }
}
static bool hasOptionalClassName(const CXXRecordDecl &RD) {
@@ -50,15 +65,23 @@ static bool hasOptionalClassName(const CXXRecordDecl &RD) {
if (RD.getName() == "optional") {
if (const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext()))
- return N->isStdNamespace() || isTopLevelNamespaceWithName(*N, "absl");
+ return N->isStdNamespace() ||
+ isFullyQualifiedNamespaceEqualTo(*N, "absl") ||
+ isFullyQualifiedNamespaceEqualTo(*N, "bsl");
return false;
}
if (RD.getName() == "Optional") {
// Check whether namespace is "::base" or "::folly".
const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext());
- return N != nullptr && (isTopLevelNamespaceWithName(*N, "base") ||
- isTopLevelNamespaceWithName(*N, "folly"));
+ return N != nullptr && (isFullyQualifiedNamespaceEqualTo(*N, "base") ||
+ isFullyQualifiedNamespaceEqualTo(*N, "folly"));
+ }
+
+ if (RD.getName() == "NullableValue") {
+ const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext());
+ return N != nullptr &&
+ isFullyQualifiedNamespaceEqualTo(*N, "bdlb", "BloombergLP");
}
return false;
@@ -195,22 +218,25 @@ auto isOptionalOperatorCallWithName(
}
auto isMakeOptionalCall() {
- return callExpr(callee(functionDecl(hasAnyName(
- "std::make_optional", "base::make_optional",
- "absl::make_optional", "folly::make_optional"))),
- hasOptionalType());
+ return callExpr(
+ callee(functionDecl(hasAnyName(
+ "std::make_optional", "base::make_optional", "absl::make_optional",
+ "folly::make_optional", "bsl::make_optional"))),
+ hasOptionalType());
}
auto nulloptTypeDecl() {
return namedDecl(hasAnyName("std::nullopt_t", "absl::nullopt_t",
- "base::nullopt_t", "folly::None"));
+ "base::nullopt_t", "folly::None",
+ "bsl::nullopt_t"));
}
auto hasNulloptType() { return hasType(nulloptTypeDecl()); }
auto inPlaceClass() {
return recordDecl(hasAnyName("std::in_place_t", "absl::in_place_t",
- "base::in_place_t", "folly::in_place_t"));
+ "base::in_place_t", "folly::in_place_t",
+ "bsl::in_place_t"));
}
auto isOptionalNulloptConstructor() {
@@ -415,6 +441,15 @@ void transferOptionalHasValueCall(const CXXMemberCallExpr *CallExpr,
}
}
+void transferOptionalIsNullCall(const CXXMemberCallExpr *CallExpr,
+ const MatchFinder::MatchResult &,
+ LatticeTransferState &State) {
+ if (auto *HasValueVal = getHasValue(
+ State.Env, getImplicitObjectLocation(*CallExpr, State.Env))) {
+ State.Env.setValue(*CallExpr, State.Env.makeNot(*HasValueVal));
+ }
+}
+
/// `ModelPred` builds a logical formula relating the predicate in
/// `ValueOrPredExpr` to the optional's `has_value` property.
void transferValueOrImpl(
@@ -784,6 +819,12 @@ auto buildTransferMatchSwitch() {
isOptionalMemberCallWithNameMatcher(hasName("operator bool")),
transferOptionalHasValueCall)
+ // NullableValue::isNull
+ // Only NullableValue has isNull
+ .CaseOfCFGStmt<CXXMemberCallExpr>(
+ isOptionalMemberCallWithNameMatcher(hasName("isNull")),
+ transferOptionalIsNullCall)
+
// optional::emplace
.CaseOfCFGStmt<CXXMemberCallExpr>(
isOptionalMemberCallWithNameMatcher(hasName("emplace")),