Don't match non-member binary operator* as if it was a unary operator* for smart pointers
There might not be a receiver arg that is a pointer (failing an assert).
PiperOrigin-RevId: 698390811
Change-Id: I6a05a546d247299383ce701e2ee5114d3f00c763
diff --git a/nullability/pointer_nullability_analysis.cc b/nullability/pointer_nullability_analysis.cc
index c2e2793..3cf7713 100644
--- a/nullability/pointer_nullability_analysis.cc
+++ b/nullability/pointer_nullability_analysis.cc
@@ -665,10 +665,8 @@
// here as well to get the same behavior:
// https://github.com/llvm/llvm-project/blob/a58c3d3ac7c6b2fd9710ab2189d7971ef37e714f/clang/include/clang/ASTMatchers/ASTMatchers.h#L4563
const Expr *Receiver = OpCall->getArg(0)->IgnoreImpCasts();
- if (Receiver->isPRValue()) {
- assert(Receiver->getType()->isPointerType());
+ if (Receiver->isPRValue() && Receiver->getType()->isPointerType())
return Receiver->getType()->getPointeeType();
- }
return Receiver->getType();
}
@@ -1810,7 +1808,7 @@
transferValue_PointerAddOrSubAssign)
.CaseOfCFGStmt<CXXConstructExpr>(isSmartPointerConstructor(),
transferValue_SmartPointerConstructor)
- .CaseOfCFGStmt<CXXOperatorCallExpr>(isSmartPointerOperatorCall("="),
+ .CaseOfCFGStmt<CXXOperatorCallExpr>(isSmartPointerOperatorCall("=", 2),
transferValue_SmartPointerAssignment)
.CaseOfCFGStmt<CXXMemberCallExpr>(isSmartPointerMethodCall("release"),
transferValue_SmartPointerReleaseCall)
@@ -1827,10 +1825,10 @@
isSmartPointerBoolConversionCall(),
transferValue_SmartPointerBoolConversionCall)
.CaseOfCFGStmt<CXXOperatorCallExpr>(
- isSmartPointerOperatorCall("*"),
+ isSmartPointerOperatorCall("*", 1),
transferValue_SmartPointerOperatorStar)
.CaseOfCFGStmt<CXXOperatorCallExpr>(
- isSmartPointerOperatorCall("->"),
+ isSmartPointerOperatorCall("->", 1),
transferValue_SmartPointerOperatorArrow)
.CaseOfCFGStmt<CallExpr>(isSmartPointerFactoryCall(),
transferValue_SmartPointerFactoryCall)
diff --git a/nullability/pointer_nullability_diagnosis.cc b/nullability/pointer_nullability_diagnosis.cc
index 19ce474..58025d6 100644
--- a/nullability/pointer_nullability_diagnosis.cc
+++ b/nullability/pointer_nullability_diagnosis.cc
@@ -553,7 +553,7 @@
isSmartPointerMethodCall("reset"),
unless(hasArgument(0, hasType(isNullPtrType()))),
onImplicitObjectArgument(expr().bind("e"))),
- cxxOperatorCallExpr(isSmartPointerOperatorCall("="),
+ cxxOperatorCallExpr(isSmartPointerOperatorCall("=", 2),
hasArgument(0, expr().bind("e")))))),
*Func->getBody(), Func->getASTContext())) {
AllowedExprs.insert(normalize(Node.getNodeAs<Expr>("e")));
@@ -670,12 +670,12 @@
SmallVector<PointerNullabilityDiagnostic>>()
// `*`
.CaseOfCFGStmt<UnaryOperator>(isPointerDereference(), diagnoseDereference)
- .CaseOfCFGStmt<CXXOperatorCallExpr>(isSmartPointerOperatorCall("*"),
+ .CaseOfCFGStmt<CXXOperatorCallExpr>(isSmartPointerOperatorCall("*", 1),
diagnoseSmartPointerDereference)
// `[]`
.CaseOfCFGStmt<ArraySubscriptExpr>(isPointerSubscript(),
diagnoseSubscript)
- .CaseOfCFGStmt<CXXOperatorCallExpr>(isSmartPointerOperatorCall("[]"),
+ .CaseOfCFGStmt<CXXOperatorCallExpr>(isSmartPointerOperatorCall("[]", 2),
diagnoseSmartPointerDereference)
// `->`. Covers raw and smart pointers, because smart-pointer
// `operator->` doesn't dereference. It just returns a pointer from which
@@ -685,7 +685,7 @@
.CaseOfCFGStmt<BinaryOperator>(
binaryOperator(hasOperatorName("="), hasLHS(isPointerExpr())),
diagnoseAssignment)
- .CaseOfCFGStmt<CXXOperatorCallExpr>(isSmartPointerOperatorCall("="),
+ .CaseOfCFGStmt<CXXOperatorCallExpr>(isSmartPointerOperatorCall("=", 2),
diagnoseSmartPointerAssignment)
.CaseOfCFGStmt<CXXMemberCallExpr>(isSmartPointerMethodCall("reset"),
diagnoseSmartPointerReset)
diff --git a/nullability/pointer_nullability_matchers.cc b/nullability/pointer_nullability_matchers.cc
index c12ae0a..0646ac0 100644
--- a/nullability/pointer_nullability_matchers.cc
+++ b/nullability/pointer_nullability_matchers.cc
@@ -9,7 +9,7 @@
#include "clang/AST/Stmt.h"
#include "clang/AST/Type.h"
#include "clang/ASTMatchers/ASTMatchers.h"
-#include "clang/ASTMatchers/ASTMatchersMacros.h"
+#include "clang/ASTMatchers/ASTMatchersInternal.h"
namespace clang::tidy::nullability {
@@ -142,9 +142,9 @@
return cxxConstructExpr(hasType(isSupportedSmartPointer()));
}
-Matcher<Stmt> isSmartPointerOperatorCall(llvm::StringRef Name) {
+Matcher<Stmt> isSmartPointerOperatorCall(llvm::StringRef Name, int NumArgs) {
return cxxOperatorCallExpr(
- hasOverloadedOperatorName(Name),
+ hasOverloadedOperatorName(Name), argumentCountIs(NumArgs),
hasArgument(0, hasType(isSupportedSmartPointer())));
}
diff --git a/nullability/pointer_nullability_matchers.h b/nullability/pointer_nullability_matchers.h
index 83f0e80..cf6353c 100644
--- a/nullability/pointer_nullability_matchers.h
+++ b/nullability/pointer_nullability_matchers.h
@@ -57,7 +57,7 @@
ast_matchers::internal::Matcher<Stmt> isSmartPointerArrowMemberExpr();
ast_matchers::internal::Matcher<Stmt> isSmartPointerConstructor();
ast_matchers::internal::Matcher<Stmt> isSmartPointerOperatorCall(
- llvm::StringRef Name);
+ llvm::StringRef Name, int NumArgs);
ast_matchers::internal::Matcher<Stmt> isSmartPointerMethodCall(
llvm::StringRef Name);
ast_matchers::internal::Matcher<Stmt> isSmartPointerFreeSwapCall();
diff --git a/nullability/test/smart_pointers.cc b/nullability/test/smart_pointers.cc
index 588cfcf..98004a0 100644
--- a/nullability/test/smart_pointers.cc
+++ b/nullability/test/smart_pointers.cc
@@ -755,3 +755,45 @@
}
} // namespace unusual_smart_pointer_type
+
+// Check non-member operator overloading (check if we have assumptions
+// about presence of receiver arg that may be a pointer type, etc.).
+namespace free_standing_operator_calls {
+
+struct A {
+ int X;
+};
+
+static Nonnull<std::unique_ptr<A>> operator*(std::unique_ptr<A> L,
+ std::unique_ptr<A> R) {
+ return std::make_unique<A>(L->X * R->X);
+}
+
+TEST void nonMemberBinaryOperatorStar() {
+ auto P1 = std::make_unique<A>(1);
+ auto P2 = std::make_unique<A>(2);
+ nonnull(std::move(P1) * std::move(P2));
+}
+
+template <class T>
+struct _Nullable MySmartPtr {
+ using pointer = T *;
+
+ T *get() const;
+ T *Ptr;
+};
+
+// A bit unusual, but one can define a non-member operator*.
+template <typename T>
+static T &operator*(MySmartPtr<T> P) {
+ return *P.Ptr;
+}
+
+TEST void nonMemberUnaryOperatorStar() {
+ int X = 42;
+ Nonnull<int *> PtrToX = &X;
+ MySmartPtr<int *> NonNull = {&PtrToX};
+ provable(NonNull.get() == &*NonNull);
+}
+
+} // namespace free_standing_operator_calls
diff --git a/nullability/test/smart_pointers_diagnosis.cc b/nullability/test/smart_pointers_diagnosis.cc
index c48f7f9..d1b0c50 100644
--- a/nullability/test/smart_pointers_diagnosis.cc
+++ b/nullability/test/smart_pointers_diagnosis.cc
@@ -463,6 +463,26 @@
)cc"));
}
+TEST(SmartPointerTest, DereferenceViaNonMemberStar) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+#include <memory>
+#include <utility>
+ template <class T>
+ struct _Nullable MySmartPtr {
+ using pointer = T*;
+ };
+
+ // A bit unusual, but one can define a non-member operator*.
+ template <typename T>
+ static T& operator*(MySmartPtr<T> P);
+
+ void target() {
+ MySmartPtr<int> Null;
+ *Null; // [[unsafe]]
+ }
+ )cc"));
+}
+
TEST(SmartPointerTest, ArrowOperatorReturnsPointerThatNeedsNullState) {
EXPECT_TRUE(checkDiagnostics(R"cc(
// Take by reference to avoid an implicit cast of the argument to rvalue.