Access correct arguments for CXXOperatorCallExprs when collecting nullability evidence.
Also, skip only the parameter if unable to emit evidence, rather than the whole function call.
PiperOrigin-RevId: 550532560
Change-Id: Ia4b34023d6fa126f230ca0357c1f3a2f2ac0b890
diff --git a/nullability/inference/collect_evidence.cc b/nullability/inference/collect_evidence.cc
index ac27211..673067b 100644
--- a/nullability/inference/collect_evidence.cc
+++ b/nullability/inference/collect_evidence.cc
@@ -18,6 +18,7 @@
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/AST/Expr.h"
+#include "clang/AST/ExprCXX.h"
#include "clang/AST/OperationKinds.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/AST/Stmt.h"
@@ -37,6 +38,7 @@
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/FunctionExtras.h"
#include "llvm/ADT/STLFunctionalExtras.h"
+#include "llvm/Support/Casting.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/raw_ostream.h"
@@ -166,18 +168,33 @@
dyn_cast_or_null<clang::FunctionDecl>(CallExpr->getCalleeDecl());
if (!CalleeDecl || !isInferenceTarget(*CalleeDecl)) return;
- // For each inferrable parameter of the callee, ...
- for (unsigned I = 0; I < CalleeDecl->param_size(); ++I) {
- if (!CalleeDecl->getParamDecl(I)
+ unsigned ParamI = 0;
+ unsigned ArgI = 0;
+ // Member operator calls hold the function object as the first argument,
+ // offsetting the indices of parameters and corresponding arguments by 1.
+ // For example: Given struct S { bool operator+(int*); }
+ // The CXXMethodDecl has one parameter, but a call S{}+p is a
+ // CXXOperatorCallExpr with two arguments: an S and an int*.
+ if (isa<clang::CXXOperatorCallExpr>(CallExpr) &&
+ isa<clang::CXXMethodDecl>(CalleeDecl))
+ ++ArgI;
+
+ // For each pointer parameter of the callee, ...
+ for (; ParamI < CalleeDecl->param_size(); ++ParamI, ++ArgI) {
+ if (!CalleeDecl->getParamDecl(ParamI)
->getType()
.getNonReferenceType()
->isPointerType())
- return;
+ continue;
+ // the corresponding argument should also be a pointer.
+ CHECK(CallExpr->getArg(ArgI)
+ ->getType()
+ .getNonReferenceType()
+ ->isPointerType());
- // if we're passing a pointer, ...
dataflow::PointerValue *PV =
- getPointerValueFromExpr(CallExpr->getArg(I), Env);
- if (!PV) return;
+ getPointerValueFromExpr(CallExpr->getArg(ArgI), Env);
+ if (!PV) continue;
// TODO: Check if the parameter is annotated. If annotated Nonnull, (instead
// of collecting evidence for it?) collect evidence similar to a
@@ -185,7 +202,7 @@
// evidence for a parameter that could be annotated Nonnull as a way to
// force the argument to be Nonnull.
- // emit evidence of the parameter's nullability. First, calculate that
+ // Emit evidence of the parameter's nullability. First, calculate that
// nullability based on InferrableSlots for the caller being assigned to
// Unknown, to reflect the current annotations and not all possible
// annotations for them.
@@ -203,8 +220,8 @@
default:
ArgEvidenceKind = Evidence::UNKNOWN_ARGUMENT;
}
- Emit(*CalleeDecl, paramSlot(I), ArgEvidenceKind,
- CallExpr->getArg(I)->getExprLoc());
+ Emit(*CalleeDecl, paramSlot(ParamI), ArgEvidenceKind,
+ CallExpr->getArg(ArgI)->getExprLoc());
}
}
diff --git a/nullability/inference/collect_evidence_test.cc b/nullability/inference/collect_evidence_test.cc
index d6b3fb2..a1de037 100644
--- a/nullability/inference/collect_evidence_test.cc
+++ b/nullability/inference/collect_evidence_test.cc
@@ -373,6 +373,51 @@
functionNamed("target"))));
}
+TEST(CollectEvidenceFromImplementationTest, MemberOperatorCall) {
+ static constexpr llvm::StringRef Src = R"cc(
+ struct S {
+ bool operator+(int*);
+ };
+ void target() { S{} + nullptr; }
+ )cc";
+ EXPECT_THAT(collectEvidenceFromTargetFunction(Src),
+ Contains(evidence(paramSlot(0), Evidence::NULLABLE_ARGUMENT,
+ functionNamed("operator+"))));
+}
+
+TEST(CollectEvidenceFromImplementationTest, NonMemberOperatorCall) {
+ static constexpr llvm::StringRef Src = R"cc(
+ struct S {};
+ bool operator+(const S&, int*);
+ void target() { S{} + nullptr; }
+ )cc";
+ EXPECT_THAT(collectEvidenceFromTargetFunction(Src),
+ Contains(evidence(paramSlot(1), Evidence::NULLABLE_ARGUMENT,
+ functionNamed("operator+"))));
+}
+
+TEST(CollectEvidenceFromImplementationTest, VarArgs) {
+ static constexpr llvm::StringRef Src = R"cc(
+ void callee(int*...);
+ void target() { callee(nullptr, nullptr); }
+ )cc";
+ EXPECT_THAT(collectEvidenceFromTargetFunction(Src),
+ Contains(evidence(paramSlot(0), Evidence::NULLABLE_ARGUMENT,
+ functionNamed("callee"))));
+}
+
+TEST(CollectEvidenceFromImplementationTest, MemberOperatorCallVarArgs) {
+ static constexpr llvm::StringRef Src = R"cc(
+ struct S {
+ bool operator()(int*...);
+ };
+ void target() { S{}(nullptr, nullptr); }
+ )cc";
+ EXPECT_THAT(collectEvidenceFromTargetFunction(Src),
+ Contains(evidence(paramSlot(0), Evidence::NULLABLE_ARGUMENT,
+ functionNamed("operator()"))));
+}
+
TEST(CollectEvidenceFromDeclarationTest, VariableDeclIgnored) {
llvm::StringLiteral Src = "Nullable<int *> target;";
EXPECT_THAT(collectEvidenceFromTargetDecl(Src), IsEmpty());