blob: 24e6c002c3b798dcd69c6f947376bc096a398a3e [file] [log] [blame]
// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "nullability/inference/infer_nullability_constraints.h"
#include <string>
#include <utility>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/log/check.h"
#include "nullability/inference/analyze_target_for_test.h"
#include "nullability/inference/inference.proto.h"
#include "clang/AST/Decl.h"
#include "clang/AST/Expr.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Analysis/CFG.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/STLFunctionalExtras.h"
#include "llvm/ADT/StringRef.h"
namespace clang::tidy::nullability {
namespace {
using ::clang::ast_matchers::MatchFinder;
using ::testing::EqualsProto;
using ::testing::IsEmpty;
using ::testing::Pair;
using ::testing::UnorderedElementsAre;
using InferredNullabilityConstraints =
llvm::DenseMap<const clang::Decl*, NullabilityConstraint>;
InferredNullabilityConstraints inferAnnotationsForTargetFunction(
llvm::StringRef Source) {
InferredNullabilityConstraints Constraints;
analyzeTargetForTest(
Source, [&Constraints](const clang::FunctionDecl& Func,
const MatchFinder::MatchResult& Result) mutable {
auto Results = inferNullabilityConstraints(Func, *Result.Context);
CHECK(Results);
Constraints = std::move(*Results);
});
return Constraints;
}
MATCHER_P(Parameter, Name, std::string("is a parameter named ") + Name) {
const auto* Param = clang::dyn_cast_or_null<clang::ParmVarDecl>(arg);
if (!Param) {
return false;
}
const auto* Identifier = Param->getIdentifier();
if (!Identifier) {
return false;
}
return testing::ExplainMatchResult(testing::StrEq(Name),
Identifier->getName(), result_listener);
}
TEST(InferAnnotationsTest, NoParams) {
static constexpr llvm::StringRef Src = R"cc(
void Target() {}
)cc";
EXPECT_THAT(inferAnnotationsForTargetFunction(Src), IsEmpty());
}
TEST(InferAnnotationsTest, OneParamUnused) {
static constexpr llvm::StringRef Src = R"cc(
void Target(int* p0) {}
)cc";
EXPECT_THAT(inferAnnotationsForTargetFunction(Src), IsEmpty());
}
TEST(InferAnnotationsTest, OneParamUsedWithoutRestriction) {
static constexpr llvm::StringRef Src = R"cc(
void TakesUnknown(int* unknown) {}
void Target(int* p0) { TakesUnknown(p0); }
)cc";
EXPECT_THAT(
inferAnnotationsForTargetFunction(Src),
UnorderedElementsAre(
Pair(Parameter("p0"), EqualsProto(R"pb(must_be_nonnull: false)pb"))));
}
TEST(InferAnnotationsTest, Deref) {
static constexpr llvm::StringRef Src = R"cc(
void Target(int* p0, int* p1) {
int a = *p0;
if (p1 != nullptr) {
int b = *p1;
}
}
)cc";
EXPECT_THAT(
inferAnnotationsForTargetFunction(Src),
UnorderedElementsAre(
Pair(Parameter("p0"), EqualsProto(R"pb(must_be_nonnull: true)pb")),
Pair(Parameter("p1"), EqualsProto(R"pb(must_be_nonnull: false)pb"))));
}
TEST(InferAnnotationsTest, DereferenceBeforeAssignment) {
static constexpr llvm::StringRef Src = R"cc(
void Target(int* p) {
*p;
int i = 1;
p = &i;
}
)cc";
EXPECT_THAT(
inferAnnotationsForTargetFunction(Src),
UnorderedElementsAre(
Pair(Parameter("p"), EqualsProto(R"pb(must_be_nonnull: true)pb"))));
}
TEST(InferAnnotationsTest, DereferenceAfterAssignment) {
static constexpr llvm::StringRef Src = R"cc(
void Target(int* p) {
int i = 1;
p = &i;
*p;
}
)cc";
EXPECT_THAT(
inferAnnotationsForTargetFunction(Src),
UnorderedElementsAre(
Pair(Parameter("p"), EqualsProto(R"pb(must_be_nonnull: false)pb"))));
}
TEST(InferAnnotationsTest, DerefOfPtrRef) {
static constexpr llvm::StringRef Src = R"cc(
void Target(int*& p0, int*& p1) {
int a = *p0;
if (p1 != nullptr) {
int b = *p1;
}
}
)cc";
EXPECT_THAT(
inferAnnotationsForTargetFunction(Src),
UnorderedElementsAre(
Pair(Parameter("p0"), EqualsProto(R"pb(must_be_nonnull: true)pb")),
Pair(Parameter("p1"), EqualsProto(R"pb(must_be_nonnull: false)pb"))));
}
TEST(InferAnnotationsTest, UnrelatedCondition) {
static constexpr llvm::StringRef Src = R"cc(
void Target(int* p0, int* p1, int* p2, bool b) {
if (b) {
int a = *p0;
int b = *p1;
} else {
int a = *p0;
int c = *p2;
}
}
)cc";
EXPECT_THAT(
inferAnnotationsForTargetFunction(Src),
UnorderedElementsAre(
Pair(Parameter("p0"), EqualsProto(R"pb(must_be_nonnull: true)pb")),
Pair(Parameter("p1"), EqualsProto(R"pb(must_be_nonnull: true)pb")),
Pair(Parameter("p2"), EqualsProto(R"pb(must_be_nonnull: true)pb"))));
}
TEST(InferAnnotationsTest, LaterDeref) {
static constexpr llvm::StringRef Src = R"cc(
void Target(int* p0) {
if (p0 == nullptr) {
(void)0;
} else {
(void)0;
}
int a = *p0;
}
)cc";
EXPECT_THAT(
inferAnnotationsForTargetFunction(Src),
UnorderedElementsAre(
Pair(Parameter("p0"), EqualsProto(R"pb(must_be_nonnull: true)pb"))));
}
TEST(InferAnnotationsTest, DerefBeforeGuardedDeref) {
static constexpr llvm::StringRef Src = R"cc(
void Target(int* p0) {
int a = *p0;
if (p0 != nullptr) {
int b = *p0;
}
}
)cc";
EXPECT_THAT(
inferAnnotationsForTargetFunction(Src),
UnorderedElementsAre(
Pair(Parameter("p0"), EqualsProto(R"pb(must_be_nonnull: true)pb"))));
}
TEST(InferAnnotationsTest, EarlyReturn) {
static constexpr llvm::StringRef Src = R"cc(
void Target(int* p0) {
if (!p0) {
return;
}
int a = *p0;
}
)cc";
EXPECT_THAT(
inferAnnotationsForTargetFunction(Src),
UnorderedElementsAre(
Pair(Parameter("p0"), EqualsProto(R"pb(must_be_nonnull: false)pb"))));
}
TEST(InferAnnotationsTest, UnreachableCode) {
static constexpr llvm::StringRef Src = R"cc(
void Target(int* p0, int* p1, int* p2, int* p3) {
if (true) {
int a = *p0;
} else {
int a = *p1;
}
if (false) {
int a = *p2;
}
return;
int a = *p3;
}
)cc";
EXPECT_THAT(
inferAnnotationsForTargetFunction(Src),
UnorderedElementsAre(
Pair(Parameter("p0"), EqualsProto(R"pb(must_be_nonnull: true)pb"))));
}
TEST(InferAnnotationsTest,
RequiresNonNullWhenAnnotatedWithClangNullabilityAttributeAtDefinition) {
static constexpr llvm::StringRef Src = R"cc(
void Target(int* unannotated, int* _Nonnull non_null,
int* _Nonnull* only_inner_layer_nonnull) {
unannotated;
non_null;
only_inner_layer_nonnull;
}
)cc";
EXPECT_THAT(inferAnnotationsForTargetFunction(Src),
UnorderedElementsAre(
Pair(Parameter("unannotated"),
EqualsProto(R"pb(must_be_nonnull: false)pb")),
Pair(Parameter("non_null"), EqualsProto(R"pb(must_be_nonnull:
true)pb")),
Pair(Parameter("only_inner_layer_nonnull"),
EqualsProto(R"pb(must_be_nonnull: false)pb"))));
}
TEST(InferAnnotationsTest,
RequiresNonNullWhenAnnotatedWithClangAnnotationAtDefinition) {
static constexpr llvm::StringRef Src = R"cc(
namespace custom {
template <class T>
using NonNull [[clang::annotate("Nonnull")]] = T;
} // namespace custom
void Target(int* unannotated, custom::NonNull<int*> non_null,
custom::NonNull<int*>* only_inner_layer_nonnull) {
unannotated;
non_null;
only_inner_layer_nonnull;
}
)cc";
EXPECT_THAT(inferAnnotationsForTargetFunction(Src),
UnorderedElementsAre(
Pair(Parameter("unannotated"),
EqualsProto(R"pb(must_be_nonnull: false)pb")),
Pair(Parameter("non_null"), EqualsProto(R"pb(must_be_nonnull:
true)pb")),
Pair(Parameter("only_inner_layer_nonnull"),
EqualsProto(R"pb(must_be_nonnull: false)pb"))));
}
} // namespace
} // namespace clang::tidy::nullability