blob: ff7e0c3b0e8fa465793f3e94373b6e59193627a4 [file] [log] [blame] [edit]
// 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/eligible_ranges.h"
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "absl/log/check.h"
#include "absl/strings/str_cat.h"
#include "nullability/inference/augmented_test_inputs.h"
#include "nullability/inference/eligible_ranges_for_test.h"
#include "nullability/inference/inference.proto.h"
#include "nullability/pragma.h"
#include "nullability/proto_matchers.h"
#include "nullability/type_nullability.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Basic/LLVM.h"
#include "clang/Testing/TestAST.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Testing/Annotations/Annotations.h"
#include "external/llvm-project/third-party/unittest/googlemock/include/gmock/gmock.h"
#include "external/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
namespace clang::tidy::nullability {
namespace {
using ::clang::ast_matchers::classTemplateSpecializationDecl;
using ::clang::ast_matchers::fieldDecl;
using ::clang::ast_matchers::findAll;
using ::clang::ast_matchers::functionDecl;
using ::clang::ast_matchers::hasName;
using ::clang::ast_matchers::isTemplateInstantiation;
using ::clang::ast_matchers::match;
using ::clang::ast_matchers::selectFirst;
using ::clang::ast_matchers::varDecl;
using ::llvm::Annotations;
using ::testing::AllOf;
using ::testing::Contains;
using ::testing::Each;
using ::testing::ExplainMatchResult;
using ::testing::IsEmpty;
using ::testing::Not;
using ::testing::Pointwise;
using ::testing::UnorderedElementsAre;
using ::testing::UnorderedElementsAreArray;
constexpr char MainFileName[] = "input.cc";
template <typename DeclT, typename MatcherT>
EligibleRanges getRanges(ASTContext &Ctx, MatcherT Matcher,
const TypeNullabilityDefaults &Defaults) {
const auto *D = selectFirst<DeclT>("d", match(Matcher.bind("d"), Ctx));
CHECK(D != nullptr);
return clang::tidy::nullability::getEligibleRanges(*D, Defaults);
}
template <typename DeclT, typename MatcherT>
EligibleRanges getRanges(llvm::StringRef Input, MatcherT Matcher) {
NullabilityPragmas Pragmas;
TestAST TU(getAugmentedTestInputs(Input, Pragmas));
return getRanges<DeclT>(TU.context(), Matcher,
TypeNullabilityDefaults(TU.context(), Pragmas));
}
EligibleRanges getFunctionRanges(llvm::StringRef Input,
llvm::StringRef FunctionName = "target") {
return getRanges<FunctionDecl>(Input, functionDecl(hasName(FunctionName)));
}
EligibleRanges getFieldRanges(llvm::StringRef Input,
llvm::StringRef FieldName = "Target") {
return getRanges<FieldDecl>(
Input, FieldName.empty() ? fieldDecl() : fieldDecl(hasName(FieldName)));
}
EligibleRanges getVarRanges(llvm::StringRef Input,
llvm::StringRef VarName = "Target") {
return getRanges<VarDecl>(Input, varDecl(hasName(VarName)));
}
// Used in matcher message strings below, which is not detected.
[[maybe_unused]] std::string printSlot(unsigned Slot) {
return absl::StrCat(Slot);
}
// Used in matcher message strings below, which is not detected.
[[maybe_unused]] std::string printSlot(std::optional<Slot> Slot) {
if (Slot) return absl::StrCat(*Slot);
return "nullopt";
}
MATCHER_P2(eligibleRange, SlotInDecl, InsertionOffset,
absl::StrCat("has a SlotRange for slot ", printSlot(SlotInDecl),
" with insertion offset at ", InsertionOffset)) {
std::optional<Slot> Slot = arg.Slot;
const SlotRange &ArgRange = arg.Range;
return ExplainMatchResult(SlotInDecl, Slot, result_listener) &&
ExplainMatchResult(InsertionOffset,
ArgRange.qualifier_annotation_insertion_offset(),
result_listener);
}
MATCHER_P2(eligibleRangeWithNoExistingAnnotation, SlotInDecl, InsertionOffset,
"") {
return ExplainMatchResult(false, arg.Range.has_existing_annotation(),
result_listener) &&
ExplainMatchResult(eligibleRange(SlotInDecl, InsertionOffset), arg,
result_listener);
}
MATCHER_P3(eligibleRange, SlotInDecl, InsertionOffset, ExistingAnnotation,
absl::StrCat("has a SlotRange for slot ", printSlot(SlotInDecl),
" with insertion offset at ", InsertionOffset,
" and existing annotation ",
Nullability_Name(ExistingAnnotation))) {
return ExplainMatchResult(eligibleRange(SlotInDecl, InsertionOffset), arg,
result_listener) &&
ExplainMatchResult(true, arg.Range.has_existing_annotation(),
result_listener) &&
ExplainMatchResult(ExistingAnnotation, arg.Range.existing_annotation(),
result_listener);
}
MATCHER_P(hasPath, Path, "") {
return ExplainMatchResult(Path, arg.Range.path(), result_listener);
}
MATCHER(hasNoPragmaNullability, "") {
return !arg.Range.has_pragma_nullability();
}
MATCHER_P(hasPragmaNullability, PragmaNullability, "") {
return ExplainMatchResult(true, arg.Range.has_pragma_nullability(),
result_listener) &&
ExplainMatchResult(PragmaNullability, arg.Range.pragma_nullability(),
result_listener);
}
// Matcher for 2-tuple where the first element is expected to be a RemovalRange
// and the second an Annotations::Range.
MATCHER(areEquivalentRanges, "") {
return std::get<0>(arg).begin() == std::get<1>(arg).Begin &&
std::get<0>(arg).end() == std::get<1>(arg).End;
}
MATCHER_P(hasRemovalRanges, RemovalRanges, "") {
return ExplainMatchResult(Pointwise(areEquivalentRanges(), RemovalRanges),
arg.Range.existing_annotation_removal_range(),
result_listener);
}
auto removalRanges(std::vector<Annotations::Range> RemovalRanges) {
return hasRemovalRanges(RemovalRanges);
}
// Convenience version of `removalRanges({})`, which makes it more clear that
// the argument is intentionally empty and has better messaging during failure.
MATCHER(hasNoRemovalRanges, "") {
return ExplainMatchResult(IsEmpty(),
arg.Range.existing_annotation_removal_range(),
result_listener);
}
TEST(EligibleRangesTest, RawPointersReturnsAndParametersHaveRanges) {
auto Input =
Annotations("int *$r^target(int *$p^P, bool *$q^Q) { return P; }");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRangeWithNoExistingAnnotation(0, Input.point("r")),
eligibleRangeWithNoExistingAnnotation(1, Input.point("p")),
eligibleRangeWithNoExistingAnnotation(2, Input.point("q")))));
}
TEST(EligibleRangesTest, NonPointerReturnsAndParametersHaveNoRanges) {
auto Input = Annotations("void target(int *^P1, int P2) { return; }");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(1, Input.point()))));
}
TEST(EligibleRangesTest, DeclWithoutBodyHasRanges) {
auto Input = Annotations("void target(int *^P1, int P2);");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(1, Input.point()))));
}
TEST(EligibleRangesTest, AllNestedPointersEligible) {
auto Input =
Annotations("void target(int *$inner^*$middle^*$outer^P1, int P2);");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRange(1, Input.point("outer")),
eligibleRange(std::nullopt, Input.point("middle")),
eligibleRange(std::nullopt, Input.point("inner")))));
}
TEST(EligibleRangesTest, DeclConstFollowsInsertionOffset) {
auto Input = Annotations(R"(
void target(int *$one^ const P1,
int *$two_i^ const *$two_o^ const P2);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRange(1, Input.point("one")),
eligibleRange(2, Input.point("two_o")),
eligibleRange(std::nullopt, Input.point("two_i")))));
}
TEST(EligibleRangesTest, NestedPointeeConst) {
auto Input = Annotations("void target(const int *$i^ const *$o^P);");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(
Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(1, Input.point("o")),
eligibleRange(std::nullopt, Input.point("i")))));
}
TEST(EligibleRangesTest, NamespacedAliasAnnotatedSlotsGetRanges) {
auto Input = Annotations(R"(
namespace custom {
namespace internal {
template <typename T>
using CustomNonnull = absl_nonnull T;
template <typename T>
using CustomNullable = absl_nullable T;
template <typename T>
using CustomUnknown = absl_nullability_unknown T;
}
template <typename T>
using CustomNonnull = internal::CustomNonnull<T>;
template <typename T>
using CustomNullable = internal::CustomNullable<T>;
template <typename T>
using CustomUnknown = internal::CustomUnknown<T>;
}
// Aliases of any depth that apply a nullability annotation are detected.
void target(custom::CustomNonnull<int *$one^> NonnullP,
custom::CustomNullable<int *$two^> NullableP,
custom::CustomUnknown<int *$three^> UnknownP);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRange(1, Input.point("one"), Nullability::NONNULL),
eligibleRange(2, Input.point("two"), Nullability::NULLABLE),
eligibleRange(3, Input.point("three"), Nullability::UNKNOWN))));
}
TEST(EligibleRangesTest, DuplicateAnnotationsGetOneRange) {
auto Input =
Annotations(R"(void target(int *^ absl_nonnull absl_nonnull P);)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRange(1, Input.point(), Nullability::NONNULL))));
}
TEST(EligibleRangesTest, NestedPointersOuterAnnotated) {
auto Input = Annotations(R"(
namespace std {
template <typename T>
class unique_ptr;
}
void target(
int *$one_i^*$one_o^ absl_nonnull P,
absl_nonnull $two_o^std::unique_ptr<int*$two_i^> Q,
$three_i^std::unique_ptr<int>*$three_o^ absl_nonnull R,
absl_nonnull $four_o^std::unique_ptr<$four_i^std::unique_ptr<int>> S);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRange(1, Input.point("one_o"), Nullability::NONNULL),
eligibleRange(std::nullopt, Input.point("one_i")),
eligibleRange(2, Input.point("two_o"), Nullability::NONNULL),
eligibleRange(std::nullopt, Input.point("two_i")),
eligibleRange(3, Input.point("three_o"), Nullability::NONNULL),
eligibleRange(std::nullopt, Input.point("three_i")),
eligibleRange(4, Input.point("four_o"), Nullability::NONNULL),
eligibleRange(std::nullopt, Input.point("four_i")))));
}
TEST(EligibleRangesTest, NestedPointersInnerAnnotated) {
auto Input = Annotations(R"(
namespace std {
template <typename T>
class unique_ptr;
}
void target(
int *$one_i^absl_nonnull *$one_o^P,
$two_o^std::unique_ptr<int *$two_i^absl_nonnull> Q,
absl_nonnull $three_i^std::unique_ptr<int> *$three_o^R,
$four_o^std::unique_ptr<absl_nonnull $four_i^std::unique_ptr<int>> S);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRange(1, Input.point("one_o")),
eligibleRange(std::nullopt, Input.point("one_i"),
Nullability::NONNULL),
eligibleRange(2, Input.point("two_o")),
eligibleRange(std::nullopt, Input.point("two_i"),
Nullability::NONNULL),
eligibleRange(3, Input.point("three_o")),
eligibleRange(std::nullopt, Input.point("three_i"),
Nullability::NONNULL),
eligibleRange(4, Input.point("four_o")),
eligibleRange(std::nullopt, Input.point("four_i"),
Nullability::NONNULL))));
}
TEST(EligibleRangesTest, RefToPointer) {
auto Input = Annotations("void target(int *^&P);");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(1, Input.point()))));
}
TEST(EligibleRangesTest, TemplateOfPointers) {
auto Input = Annotations(R"(
template <typename One, typename Two>
struct S {};
void target(S<int *$one^, bool *$two_inner^*$two^> P);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRange(std::nullopt, Input.point("one")),
eligibleRange(std::nullopt, Input.point("two")),
eligibleRange(std::nullopt, Input.point("two_inner")))));
}
TEST(EligibleRangesTest, TemplateOfConstPointers) {
auto Input = Annotations(R"(
template <typename One, typename Two>
struct S {};
void target(
S<const int *$one^, const int *$two_i^ const *$two_o^> P,
S<int *$three^ const, int *$four_i^ const *$four_o^ const> Q);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRange(std::nullopt, Input.point("one")),
eligibleRange(std::nullopt, Input.point("two_o")),
eligibleRange(std::nullopt, Input.point("two_i")),
eligibleRange(std::nullopt, Input.point("three")),
eligibleRange(std::nullopt, Input.point("four_o")),
eligibleRange(std::nullopt, Input.point("four_i")))));
}
TEST(EligibleRangesTest, SmartPointer) {
auto Input = Annotations(R"(
namespace std {
template <typename T>
class unique_ptr;
}
void target($one^std::unique_ptr<int> StdSmart,
absl_nonnull $two^std::unique_ptr<int> NonnullStdSmart);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRange(1, Input.point("one")),
eligibleRange(2, Input.point("two"), Nullability::NONNULL))));
}
TEST(EligibleRangesTest,
SmartPointerPrecedingDeclConstPrecedesInsertionOffset) {
auto Input = Annotations(R"(
namespace std {
template <typename T>
class unique_ptr;
}
void target(const ^std::unique_ptr<int> P);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(1, Input.point()))));
}
TEST(EligibleRangesTest, UserDefinedSmartPointer) {
auto Input = Annotations(R"(
struct _Nullable MySmartIntPtr {
using pointer = int *;
};
void target($one^MySmartIntPtr UserDefinedSmart,
absl_nonnull $two^MySmartIntPtr NonnullUserDefinedSmart);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRange(1, Input.point("one")),
eligibleRange(2, Input.point("two"), Nullability::NONNULL))));
}
TEST(EligibleRangesTest, UserDefinedTemplatedSmartPointer) {
auto Input = Annotations(R"(
template <typename T>
struct _Nullable MySmartPtr {};
void target($one^MySmartPtr<int> UserDefinedSmart,
absl_nonnull $two^MySmartPtr<int> NonnullUserDefinedSmart);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRange(1, Input.point("one")),
eligibleRange(2, Input.point("two"), Nullability::NONNULL))));
}
TEST(EligibleRangesTest, SimpleAlias) {
auto Input = Annotations(R"(
using IntPtr = int *;
void target(^IntPtr P);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(1, Input.point()))));
}
TEST(EligibleRangesTest, InaccessibleAlias) {
auto Input = Annotations(R"(
template <typename T>
class TemplateClass {};
using Inaccessible = TemplateClass<int *>;
void target(Inaccessible P);
)");
EXPECT_THAT(getFunctionRanges(Input.code()), IsEmpty());
}
TEST(EligibleRangesTest, NestedAlias) {
auto Input = Annotations(R"(
using Nested = int **;
void target(^Nested P);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(1, Input.point()))));
}
TEST(EligibleRangesTest, AliasTemplate) {
auto Input = Annotations(R"(
template <typename T>
using AliasTemplate = T;
void target(AliasTemplate<int*^> P, AliasTemplate<int> Q);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(1, Input.point()))));
}
TEST(EligibleRangesTest, DependentAliasSimple) {
auto Input = Annotations(R"(
namespace std {
template <typename T>
class unique_ptr;
}
template <typename T>
struct S {
using Type = T;
};
void target(S<int *$raw^>::Type P, S<int>::Type Q, S<$smart^std::unique_ptr<int>>::Type R);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(1, Input.point("raw")),
eligibleRange(3, Input.point("smart")))));
}
TEST(EligibleRangesTest, DependentAliasAnnotated) {
auto Input = Annotations(R"(
template <typename T>
struct S {
using type = T;
};
void target(S<int *$macro^absl_nullable>::type P, S<int *$qualifier^ _Nullable>::type Q);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRange(1, Input.point("macro"), Nullability::NULLABLE),
eligibleRange(2, Input.point("qualifier"),
Nullability::NULLABLE))));
}
TEST(EligibleRangesTest, DependentAliasOfDependentAlias) {
auto Input = Annotations(R"(
template <typename T>
struct vector {
using value_type = T;
};
template <typename T>
struct S {
using type = vector<T>::value_type;
};
void target(S<int *^>::type P);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(1, Input.point()))));
}
TEST(EligibleRangesTest, DependentAliasTemplate) {
auto Input = Annotations(R"(
template <typename V>
struct vector {};
template <typename T>
struct S {
template <template<typename> typename U>
using type = U<T>;
};
void target(S<int*^>::type<vector> P);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(std::nullopt, Input.point()))));
}
TEST(EligibleRangesTest, DependentAliasNested) {
auto Input = Annotations(R"(
template <typename V>
struct vector {
using value_type = V;
};
void target(vector<int *$inner^*$middle^*$outer^>::value_type P);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRange(1, Input.point("outer")),
eligibleRange(std::nullopt, Input.point("middle")),
eligibleRange(std::nullopt, Input.point("inner")))));
}
TEST(EligibleRangesTest, NoreturnAliasLosesFunctionTypeSourceInfo) {
// This previously crashed because the noreturn attribute causes the
// TypedefType to be unwrapped and rewritten without the Typedef layer and
// the source information below that layer to be dropped.
//
// Because we see it as a function pointer type, we place the annotation
// following the type, which is allowed, but not our preferred style.
auto Input = Annotations(R"(
typedef void (*Alias)(const char *, ...);
__attribute__((__noreturn__)) Alias^ Target;
)");
EXPECT_THAT(
getVarRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(0, Input.point("")))));
}
TEST(EligibleRangesTest, TemplatedClassContext) {
auto Input = Annotations(R"(
template <typename T>
struct Outer {
struct Inner {};
};
void target(Outer<int *^>::Inner P);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(std::nullopt, Input.point()))));
}
TEST(EligibleRangesTest, NestedTemplatedClasses) {
auto Input = Annotations(R"(
template <typename S>
struct Outermost {
template <typename T>
struct Outer {
template <typename U>
struct Inner {};
};
};
void target(
Outermost<char *$zero^>::Outer<int *$one^>::Inner<bool *$two^> P);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRange(std::nullopt, Input.point("zero")),
eligibleRange(std::nullopt, Input.point("one")),
eligibleRange(std::nullopt, Input.point("two")))));
}
TEST(EligibleRangesTest, DependentAliasReferencingFurtherOutTemplateParam) {
auto Input = Annotations(R"(
template <typename S>
struct Outermost {
template <typename T>
struct Outer {
template <typename U>
using Inner = S;
};
};
void target(Outermost<int*^>::Outer<bool>::Inner<char*> P);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(1, Input.point()))));
}
TEST(EligibleRangesTest, DependentAliasForwardingMultipleTemplateArguments) {
auto Input = Annotations(R"(
template <typename T, class U>
struct Pair;
template <typename T, class U>
struct PairWrapper {
using type = Pair<T , U>;
};
void target(PairWrapper<int *$zero^, bool *$one^>::type P);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRange(std::nullopt, Input.point("zero")),
eligibleRange(std::nullopt, Input.point("one")))));
}
TEST(EligibleRangesTest, DependentAliasInMultipleNestedClassContexts) {
auto Input = Annotations(R"(
template <typename A, class B>
struct Pair;
template <typename T>
struct Outer {
template <typename U>
struct Inner {
using type = Pair<T, U>;
};
};
void target(Outer<int *$zero^>::Inner<bool *$one^>::type P);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRange(std::nullopt, Input.point("zero")),
eligibleRange(std::nullopt, Input.point("one")))));
}
TEST(EligibleRangesTest, AliasTemplateInNestedClassContext) {
auto Input = Annotations(R"(
template <typename A, class B>
struct Pair;
template <typename T>
struct Outer {
template <typename U>
using Inner = Pair<T, U>;
};
void target(Outer<int *$zero^>::Inner<bool *$one^> P);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRange(std::nullopt, Input.point("zero")),
eligibleRange(std::nullopt, Input.point("one")))));
}
TEST(EligibleRangesTest, DependentAliasWrappingTemplateArg) {
auto Input = Annotations(R"(
namespace std {
template <typename T>
class unique_ptr;
}
template <typename T>
struct S {
using type = std::unique_ptr<T>;
};
void target($unique_ptr_outer^S<int *$inner^>::type P);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRange(1, Input.point("unique_ptr_outer")),
eligibleRange(std::nullopt, Input.point("inner")))));
}
TEST(EligibleRangesTest, DependentlyNamedTemplate) {
auto Input = Annotations(R"(
struct Wrapper {
template <typename T>
using Alias = T;
};
template <typename U, class WrapT>
struct S {
using type = typename WrapT::template Alias<U> *;
};
// P's canonical type is int**. The outer pointer's source is the whole type,
// and the inner pointer's source is the first template argument to S.
void target($outer^S<int *$inner^, Wrapper>::type P);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRange(1, Input.point("outer")),
eligibleRange(std::nullopt, Input.point("inner")))));
}
TEST(EligibleRangesTest, PartialSpecialization) {
auto Input = Annotations(R"(
template <typename T>
struct S {
};
template <typename P>
struct S<P *> {
using Alias = P;
};
// P's canonical type is int * and derives its nullability from the template
// argument minus a layer of pointer indirection. But NullabilityWalker
// doesn't support resugaring template arguments in partial specializations,
// so we only see the pointer type at the alias' Loc.
void target(^S<int **>::Alias P);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(1, Input.point()))));
}
TEST(EligibleRangesTest, TypeTemplateParamPack) {
auto Input = Annotations(R"(
template <typename... T>
struct Tuple {
using type = int;
};
void target(Tuple<int *$zero^, int *$two^*$one^> P,
Tuple<int *, int **>::type Q);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRange(std::nullopt, Input.point("zero")),
eligibleRange(std::nullopt, Input.point("one")),
eligibleRange(std::nullopt, Input.point("two")))));
}
TEST(EligibleRangesTest, DefaultTemplateArgs) {
auto Input = Annotations(R"(
template <typename T1, typename T2 = int*>
struct S {};
template <typename T1, typename T2 = T1>
using Alias = T2;
void target(S<int *$one^> P, Alias<int *$two^> Q);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRange(std::nullopt, Input.point("one")),
eligibleRange(2, Input.point("two")))));
}
TEST(EligibleRangesTest, MultipleSlotsOneRange) {
auto Input = Annotations(R"(
template <typename T1, typename T2>
struct Pair {
T1 first;
T2 second;
};
template <typename T>
using Couple = Pair<T, T>;
void target(Couple<int *^> P);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
// Eventually, two different valid slot values for the two
// ranges, but for now, inference looks at neither of
// them, so both have no slot.
UnorderedElementsAre(eligibleRange(std::nullopt, Input.point()),
eligibleRange(std::nullopt, Input.point()))));
}
TEST(EligibleRangesTest, Field) {
auto Input = Annotations(R"(
struct S {
int *$one^*$zero^ Target;
};
)");
EXPECT_THAT(
getFieldRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRange(0, Input.point("zero")),
eligibleRange(std::nullopt, Input.point("one")))));
}
TEST(EligibleRangesTest, StaticFieldAkaGlobal) {
auto Input = Annotations(R"(
struct S {
static int *$one^*$zero^ Target;
};
)");
EXPECT_THAT(
getVarRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRange(0, Input.point("zero")),
eligibleRange(std::nullopt, Input.point("one")))));
}
TEST(EligibleRangesTest, GlobalVariable) {
auto Input = Annotations(R"(
int *$one^*$zero^ Target;
)");
EXPECT_THAT(
getVarRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRange(0, Input.point("zero")),
eligibleRange(std::nullopt, Input.point("one")))));
}
TEST(EligibleRangesTest, Lambda) {
auto Input = Annotations(R"(
auto Lambda = [](int *$one^) -> int *$zero^ {};
)");
EXPECT_THAT(
getFunctionRanges(Input.code(), "operator()"),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(0, Input.point("zero")),
eligibleRange(1, Input.point("one")))));
}
TEST(EligibleRangesTest, LambdaCaptureWithInit) {
std::string Input = R"cc(
void foo(int* p) {
[capture_with_init = p]() {};
}
)cc";
EXPECT_THAT(getVarRanges(Input, "capture_with_init"), IsEmpty());
}
TEST(EligibleRangesTest, LambdaCaptureWithFunctionTypeInTemplateArg) {
std::string Input = R"cc(
template <typename T>
using ATemplate = T;
void func(ATemplate<void(const int)> *P) {
[&P]() { P(0); }();
}
)cc";
// We expect no ranges for the lambda's implicit FieldDecl, which for some
// reason has an incomplete FunctionTypeLoc that has only nullptrs where the
// ParmVarDecls should be for the function parameters.
EXPECT_THAT(getFieldRanges(Input, ""), IsEmpty());
}
TEST(EligibleRangesTest, Pragma) {
auto Input = Annotations(R"(
#pragma nullability file_default nonnull
int *$one^*$zero^ target(int *$param_one^, int *$param_two^);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(
Each(AllOf(hasPath(MainFileName),
hasPragmaNullability(Nullability::NONNULL))),
UnorderedElementsAre(
eligibleRange(0, Input.point("zero"), Nullability::NONNULL),
eligibleRange(std::nullopt, Input.point("one"),
Nullability::NONNULL),
eligibleRange(1, Input.point("param_one"), Nullability::NONNULL),
eligibleRange(2, Input.point("param_two"),
Nullability::NONNULL))));
Input = Annotations(R"(
#pragma nullability file_default nullable
int*^ Target;
)");
EXPECT_THAT(getVarRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName),
hasPragmaNullability(Nullability::NULLABLE))),
UnorderedElementsAre(eligibleRange(
0, Input.point(), Nullability::NULLABLE))));
Input = Annotations(R"(
int*^ Target;
)");
EXPECT_THAT(
getVarRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRangeWithNoExistingAnnotation(0, Input.point()))));
}
TEST(EligibleRangesTest, RangesEntirelyWithinMacro) {
auto Input = Annotations(R"(
#define MACRO(IpGlobalVar, funcName) int *$within^ funcName##Func() {return IpGlobalVar;}
int* GlobalVar1;
int* GlobalVar2;
MACRO(GlobalVar1, getVar1);
MACRO(GlobalVar2, getVar2);
#define A_TYPE int*
A_TYPE$whole_type_1^ GlobalVar3;
A_TYPE$whole_type_2^ GlobalVar4;
)");
EXPECT_THAT(
getFunctionRanges(Input.code(), "getVar1Func"),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(0, Input.point("within")))));
EXPECT_THAT(
getFunctionRanges(Input.code(), "getVar2Func"),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(0, Input.point("within")))));
EXPECT_THAT(
getVarRanges(Input.code(), "GlobalVar3"),
AllOf(
Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(0, Input.point("whole_type_1")))));
EXPECT_THAT(
getVarRanges(Input.code(), "GlobalVar4"),
AllOf(
Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(0, Input.point("whole_type_2")))));
}
// We saw specific difficulty finding previous tokens during our search for
// `const`s when passing over the line continuation in multi-line macros.
TEST(EligibleRangesTest, RangesInMultiLineMacro) {
auto Input = Annotations(R"(
using IntPtr = int*;
#define MACRO(IpGlobalVar, funcName) int *$return^ funcName##Func( \
const char*$param_one^ p, \
const \
int*$param_two^ p2, \
$removal_before_3[[absl_nonnull ]]const \
$param_three^IntPtr p3) \
{return IpGlobalVar;}
int* GlobalVar;
MACRO(GlobalVar, getVar);
)");
EXPECT_THAT(
getFunctionRanges(Input.code(), "getVarFunc"),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRange(0, Input.point("return")),
eligibleRange(1, Input.point("param_one")),
eligibleRange(2, Input.point("param_two")),
AllOf(eligibleRange(3, Input.point("param_three"),
Nullability::NONNULL),
removalRanges({Input.range("removal_before_3")})))));
}
// We saw specific difficulty finding starts of tokens that begin with no
// unescaped whitespace between the `\` and the token's first character. Clang
// considers the tokens to begin at the preceding `\` location. Any oddities
// produced by edits using these locations are fixed by auto-formatting after
// applying edits.
TEST(EligibleRangesTest, RangesInMultiLineMacroNoIndentation) {
auto Input = Annotations(R"(
using IntPtr = int*;
#define MACRO(IpGlobalVar, funcName) int *$return^ funcName##Func( $removal_before_west[[\
absl_nonnull ]]\
const $param_west^\
IntPtr \
p, \
int*$param_east^ \
const$removal_before_east[[ \
absl_nullable]] \
p2) \
{return IpGlobalVar;}
int* GlobalVar;
MACRO(GlobalVar, getVar);
)");
EXPECT_THAT(
getFunctionRanges(Input.code(), "getVarFunc"),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRange(0, Input.point("return")),
AllOf(eligibleRange(1, Input.point("param_west"),
Nullability::NONNULL),
removalRanges({Input.range("removal_before_west")})),
AllOf(eligibleRange(2, Input.point("param_east"),
Nullability::NULLABLE),
removalRanges({Input.range("removal_before_east")})))));
}
TEST(EligibleRangesTest, BareAutoTypeGetsNoRanges) {
auto Input = Annotations(R"(
auto$func_auto^
noStar(int* P) {
P = nullptr;
return P;
}
int* getPtr();
auto GNoStar = getPtr();
auto _Nullable GNoStarNullable = getPtr();
)");
EXPECT_THAT(
getFunctionRanges(Input.code(), "noStar"),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
Not(Contains(eligibleRangeWithNoExistingAnnotation(
0, Input.point("func_auto"))))));
EXPECT_THAT(getVarRanges(Input.code(), "GNoStar"), IsEmpty());
EXPECT_THAT(getVarRanges(Input.code(), "GNoStarNullable"), IsEmpty());
}
TEST(EligibleRangesTest, BareAutoAsTemplateParameterGetsNoRanges) {
auto Input = Annotations(R"(
namespace std {
template <typename T>
class unique_ptr;
}
void func(auto P, auto& Q) { }
void lambdaRecipient(void (*$A^A)(const $B^std::unique_ptr<int>& B)) {}
void usage() {
int *$I^ I;
func(I, I);
lambdaRecipient([](const auto& X) {});
}
)");
NullabilityPragmas Pragmas;
TestAST TU(getAugmentedTestInputs(Input.code(), Pragmas));
TypeNullabilityDefaults Defaults(TU.context(), Pragmas);
EXPECT_THAT(
getEligibleRanges(TU.context(), Defaults),
// The only eligible ranges in the entire snippet are the function pointer
// parameter `A`, it's unique_ptr parameter `B`, and the local variable
// `I`. No ranges are collected for the bare `auto` parameters of `func`
// or the bare `auto` parameter of the lambda.
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(1, Input.point("A")),
eligibleRange(std::nullopt, Input.point("B")),
eligibleRange(0, Input.point("I")))));
}
TEST(EligibleRangesTest, AutoStarGetsRanges) {
auto Input = Annotations(R"(
auto*$func_auto^ star(int*$func_not_auto^ P) {
P = nullptr;
return P;
}
int* getPtr();
auto*$var_auto^ GStar = getPtr();
auto*$var_auto_const^ const GStarConst = getPtr();
const auto*$var_const_auto^ GConstStar = getPtr();
auto*$var_auto_const_ref^ const& GStarConstRef = getPtr();
const auto*$var_const_auto_ref^& GConstStarRef = GConstStar;
auto*$var_auto_attributed^ _Nullable GStarNullable = getPtr();
auto*$var_auto_star_inner^*$var_auto_star_star^ GStarStar = &GStar;
)");
EXPECT_THAT(
getFunctionRanges(Input.code(), "star"),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRangeWithNoExistingAnnotation(
0, Input.point("func_auto")),
eligibleRangeWithNoExistingAnnotation(
1, Input.point("func_not_auto")))));
EXPECT_THAT(
getVarRanges(Input.code(), "GStar"),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRangeWithNoExistingAnnotation(
0, Input.point("var_auto")))));
EXPECT_THAT(
getVarRanges(Input.code(), "GStarConst"),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRangeWithNoExistingAnnotation(
0, Input.point("var_auto_const")))));
EXPECT_THAT(
getVarRanges(Input.code(), "GConstStar"),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRangeWithNoExistingAnnotation(
0, Input.point("var_const_auto")))));
EXPECT_THAT(
getVarRanges(Input.code(), "GStarConstRef"),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRangeWithNoExistingAnnotation(
0, Input.point("var_auto_const_ref")))));
EXPECT_THAT(
getVarRanges(Input.code(), "GConstStarRef"),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRangeWithNoExistingAnnotation(
0, Input.point("var_const_auto_ref")))));
EXPECT_THAT(
getVarRanges(Input.code(), "GStarNullable"),
AllOf(
Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(
0, Input.point("var_auto_attributed"), Nullability::NULLABLE))));
EXPECT_THAT(
getVarRanges(Input.code(), "GStarStar"),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRangeWithNoExistingAnnotation(
0, Input.point("var_auto_star_star")),
eligibleRangeWithNoExistingAnnotation(
std::nullopt, Input.point("var_auto_star_inner")))));
}
TEST(RemovalRangesTest, AnnotationInMacro) {
auto Input = Annotations(R"(
#define UNKNOWN(T) T absl_nullability_unknown
void target(UNKNOWN(int *^) P);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
AllOf(eligibleRange(1, Input.point("")),
// The token checks looking for annotations are done
// without expansion of macros, so we see a left
// paren as the preceding token and report no
// existing ranges to remove.
hasNoRemovalRanges()))));
}
TEST(RemovalRangesTest, ClangAttribute) {
auto Input = Annotations(R"(
void target(int *$no^ P, int *$yes^$yes_removal[[ _Null_unspecified]] Q,
int*$no_space^$no_space_removal[[_Null_unspecified]] R,
int*$with_comment^$with_comment_removal[[/* a comment */_Null_unspecified]] S,
int *$nullable^$nullable_removal[[ _Nullable]] T,
int *$nonnull^$nonnull_removal[[ _Nonnull]] U);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(
Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
AllOf(eligibleRange(1, Input.point("no")), hasNoRemovalRanges()),
AllOf(eligibleRange(2, Input.point("yes")),
removalRanges({Input.range("yes_removal")})),
AllOf(eligibleRange(3, Input.point("no_space")),
removalRanges({Input.range("no_space_removal")})),
AllOf(eligibleRange(4, Input.point("with_comment")),
removalRanges({Input.range("with_comment_removal")})),
AllOf(eligibleRange(5, Input.point("nullable")),
removalRanges({Input.range("nullable_removal")})),
AllOf(eligibleRange(6, Input.point("nonnull")),
removalRanges({Input.range("nonnull_removal")})))));
}
TEST(RemovalRangesTest, ClangAttributeSmartPointersEast) {
auto Input = Annotations(R"(
namespace std {
template <typename T>
class unique_ptr;
}
void target($no^std::unique_ptr<int> P,
$yes^std::unique_ptr<int>$yes_removal[[ _Null_unspecified]] Q,
$no_space^std::unique_ptr<int>$no_space_removal[[_Null_unspecified]] R,
$with_comment^std::unique_ptr<int>$with_comment_removal[[/* a comment */_Null_unspecified]] S,
$nullable^std::unique_ptr<int>$nullable_removal[[ _Nullable]] T,
$nonnull^std::unique_ptr<int>$nonnull_removal[[ _Nonnull]] U);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(
Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
AllOf(eligibleRange(1, Input.point("no")), hasNoRemovalRanges()),
AllOf(eligibleRange(2, Input.point("yes")),
removalRanges({Input.range("yes_removal")})),
AllOf(eligibleRange(3, Input.point("no_space")),
removalRanges({Input.range("no_space_removal")})),
AllOf(eligibleRange(4, Input.point("with_comment")),
removalRanges({Input.range("with_comment_removal")})),
AllOf(eligibleRange(5, Input.point("nullable")),
removalRanges({Input.range("nullable_removal")})),
AllOf(eligibleRange(6, Input.point("nonnull")),
removalRanges({Input.range("nonnull_removal")})))));
}
TEST(RemovalRangesTest, ClangAttributeSmartPointersWest) {
auto Input = Annotations(R"(
namespace std {
template <typename T>
class unique_ptr;
}
void target($no^std::unique_ptr<int> P,
$yes_removal[[_Null_unspecified ]]$yes^std::unique_ptr<int> Q,
$with_comment_removal[[_Null_unspecified/* a comment */]]$with_comment^std::unique_ptr<int> R,
$nullable_removal[[_Nullable ]]$nullable^std::unique_ptr<int> S,
$nonnull_removal[[_Nonnull ]]$nonnull^std::unique_ptr<int> T);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(
Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
AllOf(eligibleRange(1, Input.point("no")), hasNoRemovalRanges()),
AllOf(eligibleRange(2, Input.point("yes")),
removalRanges({Input.range("yes_removal")})),
AllOf(eligibleRange(3, Input.point("with_comment")),
removalRanges({Input.range("with_comment_removal")})),
AllOf(eligibleRange(4, Input.point("nullable")),
removalRanges({Input.range("nullable_removal")})),
AllOf(eligibleRange(5, Input.point("nonnull")),
removalRanges({Input.range("nonnull_removal")})))));
}
TEST(RemovalRangesTest, ClangAttributeSmartPointersWithNearerConst) {
auto Input = Annotations(R"(
namespace std {
template <typename T>
class unique_ptr;
}
void target($const_west_removal[[_Nullable ]]const $insert_const_west^std::unique_ptr<int> P,
$insert_const_east^std::unique_ptr<int> const$const_east_removal[[ _Nullable]] Q,
$insert_const_east_no_space^std::unique_ptr<int>const$const_east_removal_no_space[[ _Nullable]] R,
$insert_const_east_comment^std::unique_ptr<int>/* a comment */const$const_east_removal_comment[[ _Nullable]] S);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(
Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
AllOf(eligibleRange(1, Input.point("insert_const_west")),
removalRanges({Input.range("const_west_removal")})),
AllOf(eligibleRange(2, Input.point("insert_const_east")),
removalRanges({Input.range("const_east_removal")})),
AllOf(
eligibleRange(3, Input.point("insert_const_east_no_space")),
removalRanges({Input.range("const_east_removal_no_space")})),
AllOf(eligibleRange(4, Input.point("insert_const_east_comment")),
removalRanges(
{Input.range("const_east_removal_comment")})))));
}
TEST(RemovalRangesTest, ClangAttributeWithFunctionPointer) {
auto Input = Annotations(R"(
void target(void (*^[[ _Nonnull]] P)(int));
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
AllOf(eligibleRange(1, Input.point(""), Nullability::NONNULL),
removalRanges({Input.range("")})))));
}
TEST(RemovalRangesTest, AbslMacro) {
auto Input = Annotations(R"(
void target(int *$no^ P, int *$yes^$yes_removal[[ absl_nullability_unknown]] Q,
int*$no_space^$no_space_removal[[absl_nullability_unknown]] R,
int*$with_comment^$with_comment_removal[[/* a comment */absl_nullability_unknown]] S,
int *$nullable^$nullable_removal[[ absl_nullable]] T,
int *$nonnull^$nonnull_removal[[ absl_nonnull]] U);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(
Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
AllOf(eligibleRange(1, Input.point("no")), hasNoRemovalRanges()),
AllOf(eligibleRange(2, Input.point("yes")),
removalRanges({Input.range("yes_removal")})),
AllOf(eligibleRange(3, Input.point("no_space")),
removalRanges({Input.range("no_space_removal")})),
AllOf(eligibleRange(4, Input.point("with_comment")),
removalRanges({Input.range("with_comment_removal")})),
AllOf(eligibleRange(5, Input.point("nullable")),
removalRanges({Input.range("nullable_removal")})),
AllOf(eligibleRange(6, Input.point("nonnull")),
removalRanges({Input.range("nonnull_removal")})))));
}
TEST(RemovalRangesTest, AbslMacroSmartPointersEast) {
auto Input = Annotations(R"(
namespace std {
template <typename T>
class unique_ptr;
}
void target($no^std::unique_ptr<int> P,
$yes^std::unique_ptr<int>$yes_removal[[ absl_nullability_unknown]] Q,
$no_space^std::unique_ptr<int>$no_space_removal[[absl_nullability_unknown]] R,
$with_comment^std::unique_ptr<int>$with_comment_removal[[/* a comment */absl_nullability_unknown]] S,
$nullable^std::unique_ptr<int>$nullable_removal[[ absl_nullable]] T,
$nonnull^std::unique_ptr<int>$nonnull_removal[[ absl_nonnull]] U);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(
Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
AllOf(eligibleRange(1, Input.point("no")), hasNoRemovalRanges()),
AllOf(eligibleRange(2, Input.point("yes")),
removalRanges({Input.range("yes_removal")})),
AllOf(eligibleRange(3, Input.point("no_space")),
removalRanges({Input.range("no_space_removal")})),
AllOf(eligibleRange(4, Input.point("with_comment")),
removalRanges({Input.range("with_comment_removal")})),
AllOf(eligibleRange(5, Input.point("nullable")),
removalRanges({Input.range("nullable_removal")})),
AllOf(eligibleRange(6, Input.point("nonnull")),
removalRanges({Input.range("nonnull_removal")})))));
}
TEST(RemovalRangesTest, AbslMacroSmartPointersWest) {
auto Input = Annotations(R"(
namespace std {
template <typename T>
class unique_ptr;
}
void target($no^std::unique_ptr<int> P,
$yes_removal[[absl_nullability_unknown ]]$yes^std::unique_ptr<int> Q,
$with_comment_removal[[absl_nullability_unknown/* a comment */]]$with_comment^std::unique_ptr<int> R,
$nullable_removal[[absl_nullable ]]$nullable^std::unique_ptr<int> S,
$nonnull_removal[[absl_nonnull ]]$nonnull^std::unique_ptr<int> T);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(
Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
AllOf(eligibleRange(1, Input.point("no")), hasNoRemovalRanges()),
AllOf(eligibleRange(2, Input.point("yes")),
removalRanges({Input.range("yes_removal")})),
AllOf(eligibleRange(3, Input.point("with_comment")),
removalRanges({Input.range("with_comment_removal")})),
AllOf(eligibleRange(4, Input.point("nullable")),
removalRanges({Input.range("nullable_removal")})),
AllOf(eligibleRange(5, Input.point("nonnull")),
removalRanges({Input.range("nonnull_removal")})))));
}
TEST(RemovalRangesTest, AbslMacroSmartPointersWithNearerConst) {
auto Input = Annotations(R"(
namespace std {
template <typename T>
class unique_ptr;
}
void target($const_west_removal[[absl_nullable ]]const $insert_const_west^std::unique_ptr<int> P,
$insert_const_east^std::unique_ptr<int> const$const_east_removal[[ absl_nullable]] Q,
$insert_const_east_no_space^std::unique_ptr<int>const$const_east_removal_no_space[[ absl_nullable]] R,
$insert_const_east_comment^std::unique_ptr<int>/* a comment */const$const_east_removal_comment[[ absl_nullable]] S);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(
Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
AllOf(eligibleRange(1, Input.point("insert_const_west")),
removalRanges({Input.range("const_west_removal")})),
AllOf(eligibleRange(2, Input.point("insert_const_east")),
removalRanges({Input.range("const_east_removal")})),
AllOf(
eligibleRange(3, Input.point("insert_const_east_no_space")),
removalRanges({Input.range("const_east_removal_no_space")})),
AllOf(eligibleRange(4, Input.point("insert_const_east_comment")),
removalRanges(
{Input.range("const_east_removal_comment")})))));
}
TEST(RemovalRangesTest, AbslMacroWithFunctionPointer) {
auto Input = Annotations(R"(
void target(void (*^[[ absl_nonnull]] P)(int));
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
AllOf(eligibleRange(1, Input.point(""), Nullability::NONNULL),
removalRanges({Input.range("")})))));
}
TEST(RemovalRangesTest, SimpleAlias) {
auto Input = Annotations(R"(
using IntPtr = int *;
void target($one_removal[[_Nullable ]]$one^IntPtr P,
$two^IntPtr$two_removal[[ _Nullable]] Q);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(
Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
AllOf(eligibleRange(1, Input.point("one"), Nullability::NULLABLE),
removalRanges({Input.range("one_removal")})),
AllOf(eligibleRange(2, Input.point("two"), Nullability::NULLABLE),
removalRanges({Input.range("two_removal")})))));
}
TEST(EligibleRangesTest, FunctionPointer) {
auto Input = Annotations(R"(
void target(int (*$after_star_0^P)(int, int*$pointer_param^));
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRange(1, Input.point("after_star_0")),
eligibleRange(std::nullopt, Input.point("pointer_param")))));
Input = Annotations("void target(int (*$after_star^)(int));");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(1, Input.point("after_star")))));
}
TEST(EligibleRangesTest, ArrayOfNonPointersHasNoRanges) {
std::string Input = "void target(int P[]);";
EXPECT_THAT(getFunctionRanges(Input), IsEmpty());
}
TEST(EligibleRangesTest, ArrayOfSimplePointers) {
auto Input = Annotations("void target(int*^ P[]);");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(std::nullopt, Input.point()))));
}
TEST(EligibleRangesTest, ArrayOfFunctionPointers) {
auto Input = Annotations("void target([[int (*$after_star^P[3])(float)]]);");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRange(std::nullopt, Input.point("after_star")))));
// An unnamed array of function pointers.
Input = Annotations("void target([[void(*$after_star^[])(int)]]);");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRange(std::nullopt, Input.point("after_star")))));
}
TEST(EligibleRangesTest, ArrayOfArrayOfPointersToArray) {
auto Input = Annotations(R"(
void target(int *$element^ (*$ptr_to_array^P[3][2])[1]);)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRange(std::nullopt, Input.point("element")),
eligibleRange(std::nullopt, Input.point("ptr_to_array")))));
}
TEST(EligibleRangesTest, PointerToArray) {
auto Input = Annotations(R"(void target(int (*$after_star^P)[]);)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(1, Input.point("after_star")))));
// An unnamed pointer to an array.
Input = Annotations(R"(void target(int (*$after_star^)[]);)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(1, Input.point("after_star")))));
}
TEST(EligibleRangesTest,
ArrayOfPointersWithExtraParensAroundNameAndInSizeBrackets) {
auto Input =
Annotations(R"(void target(int (*$after_star^((P))[(1 + 2)]));)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRange(std::nullopt, Input.point("after_star")))));
}
TEST(EligibleRangesTest, PointerToPointerToArray) {
auto Input =
Annotations(R"(void target(int (*$after_star_2^*$after_star_1^Q)[1]);)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRange(1, Input.point("after_star_1")),
eligibleRange(std::nullopt, Input.point("after_star_2")))));
}
TEST(EligibleRangesTest, PointerToArrayOfFunctionPointers) {
auto Input = Annotations(
R"(void target(void (*$after_star_2^(*$after_star_1^(P))[])(int));)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(
eligibleRange(1, Input.point("after_star_1")),
eligibleRange(std::nullopt, Input.point("after_star_2")))));
}
TEST(EligibleRangesTest, StarInMacroAtEndOfDefinition) {
auto Input = Annotations(R"(
#define MY_TYPE int*
void target(MY_TYPE$one^ P, MY_TYPE$two^ Q);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(1, Input.point("one")),
eligibleRange(2, Input.point("two")))));
}
TEST(EligibleRangesTest, StarInFunctionPointerMacro) {
auto Input = Annotations(R"(
#define FUNCTION_POINTER void(*^)(int)
void target(FUNCTION_POINTER, FUNCTION_POINTER);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(1, Input.point()),
eligibleRange(2, Input.point()))));
}
TEST(EligibleRangesTest, StarInArrayPointerMacro) {
auto Input = Annotations(R"(
#define ARRAY_POINTER int(*$after_star^)[]
void target(ARRAY_POINTER, ARRAY_POINTER);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(1, Input.point("after_star")),
eligibleRange(2, Input.point("after_star")))));
}
TEST(EligibleRangesTest, StarInPointerArrayMacro) {
auto Input = Annotations(R"(
#define POINTER_ARRAY int*^[]
void target(POINTER_ARRAY, POINTER_ARRAY);
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(std::nullopt, Input.point()),
eligibleRange(std::nullopt, Input.point()))));
}
TEST(EligibleRangesTest, StarInFunctionPointerAlias) {
auto Input = Annotations(R"(
typedef void(*FunctionPointer)(int);
using FunctionPointerUsing = void(*)(int);
void target($typedef^FunctionPointer, $using^FunctionPointerUsing);
)");
// It's legal to place the annotation immediately before (or after) an alias
// for a complex declarator type, instead of immediately after the `*` inside
// the type, and it allows for more precise annotations, so we supply the
// range for doing so, preferring to place the annotation before the alias.
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(1, Input.point("typedef")),
eligibleRange(2, Input.point("using")))));
}
TEST(EligibleRangesTest, StarInArrayPointerAlias) {
auto Input = Annotations(R"(
typedef int(*ArrayPointer)[];
using ArrayPointerUsing = int(*)[];
void target($typedef^ArrayPointer, $using^ArrayPointerUsing);
)");
// It's legal to place the annotation immediately before (or after) an alias
// for a complex declarator type, instead of immediately after the `*` inside
// the type, and it allows for more precise annotations, so we supply the
// range for doing so, preferring to place the annotation before the alias.
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(1, Input.point("typedef")),
eligibleRange(2, Input.point("using")))));
}
TEST(EligibleRangesTest, StarInPointerArrayAlias) {
auto Input = Annotations(R"(
typedef int* PointerArray[];
using PointerArrayUsing = int*[];
void target(PointerArray, PointerArrayUsing);
)");
// We don't dig into the alias for sub-ranges, and the array types are not
// eligible pointers.
EXPECT_THAT(getFunctionRanges(Input.code()), IsEmpty());
}
TEST(EligibleRangesTest, StarInFunctionPointerInMacroArg) {
auto Input = Annotations(R"(
#define MACRO(ARG) void target(ARG)
MACRO(void (*^)(int));
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(1, Input.point()))));
}
TEST(EligibleRangesTest, StarInFunctionPointerDeclEntirelyInMacro) {
auto Input = Annotations(R"(
#define MACRO void target(void (*^)(int))
MACRO;
)");
EXPECT_THAT(
getFunctionRanges(Input.code()),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(1, Input.point()))));
}
TEST(EligibleRangesTest, DotsRemovedFromPath) {
auto Header1 = Annotations(R"(
void target1(int *$param^);
)");
auto Header2 = Annotations(R"(
int*$return^ target2(int);
)");
auto Input = Annotations(R"cc(
#include "./header1.h"
#include "./dir/../header2.h"
)cc");
NullabilityPragmas Pragmas;
TestInputs Inputs = getAugmentedTestInputs(Input.code(), Pragmas);
Inputs.ExtraFiles["header1.h"] = Header1.code();
Inputs.ExtraFiles["dir/../header2.h"] = Header2.code();
TestAST TU(Inputs);
auto Ranges1 =
getRanges<FunctionDecl>(TU.context(), functionDecl(hasName("target1")),
TypeNullabilityDefaults(TU.context(), Pragmas));
auto Ranges2 =
getRanges<FunctionDecl>(TU.context(), functionDecl(hasName("target2")),
TypeNullabilityDefaults(TU.context(), Pragmas));
EXPECT_THAT(
Ranges1,
AllOf(Each(AllOf(hasPath("header1.h"), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(1, Header1.point("param")))));
EXPECT_THAT(
Ranges2,
AllOf(Each(AllOf(hasPath("header2.h"), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(0, Header2.point("return")))));
}
MATCHER_P(eligibleRange, Expected, "") {
return ExplainMatchResult(Expected.Slot, arg.Slot, result_listener) &&
ExplainMatchResult(EqualsProto(Expected.Range), arg.Range,
result_listener);
}
template <typename DeclT, typename MatcherT>
std::vector<testing::Matcher<EligibleRange>> rangesFor(
std::vector<std::pair<std::string, MatcherT>> DeclMatchers, ASTContext &Ctx,
const TypeNullabilityDefaults &Defaults) {
std::vector<testing::Matcher<EligibleRange>> RangeMatchers;
for (const auto &[Name, Matcher] : DeclMatchers) {
llvm::errs() << "Getting ranges for " << Name << "\n";
EligibleRanges Ranges = getRanges<DeclT>(Ctx, Matcher, Defaults);
if (Ranges.empty()) {
ADD_FAILURE() << "No ranges found for " << Name << "!";
return {};
}
for (const EligibleRange &ER : Ranges) {
RangeMatchers.push_back(eligibleRange(ER));
}
}
return RangeMatchers;
}
auto rangesForFunctions(std::vector<std::string> FunctionNames, ASTContext &Ctx,
const TypeNullabilityDefaults &Defaults) {
std::vector<std::pair<std::string, decltype(functionDecl())>>
FunctionMatchers;
for (const auto &Name : FunctionNames) {
FunctionMatchers.push_back({Name, functionDecl(hasName(Name))});
}
return rangesFor<FunctionDecl>(FunctionMatchers, Ctx, Defaults);
}
auto rangesForVars(std::vector<std::string> VarNames, ASTContext &Ctx,
const TypeNullabilityDefaults &Defaults) {
std::vector<std::pair<std::string, decltype(varDecl())>> VarMatchers;
for (const auto &Name : VarNames) {
VarMatchers.push_back({Name, varDecl(hasName(Name))});
}
return rangesFor<VarDecl>(VarMatchers, Ctx, Defaults);
}
auto rangesForFields(std::vector<std::string> FieldNames, ASTContext &Ctx,
const TypeNullabilityDefaults &Defaults) {
std::vector<std::pair<std::string, decltype(fieldDecl())>> FieldMatchers;
for (const auto &Name : FieldNames) {
FieldMatchers.push_back({Name, fieldDecl(hasName(Name))});
}
return rangesFor<FieldDecl>(FieldMatchers, Ctx, Defaults);
}
TEST(GetEligibleRangesFromASTTest, Functions) {
std::string Input = R"cc(
namespace std {
template <typename T>
class unique_ptr;
} // namespace std
void ptrParam(int* P);
int* ptrReturn();
void smartPtrParam(std::unique_ptr<int> P);
void noPtrs();
auto autoReturn() { return ptrReturn(); }
auto* autoStarReturn() { return ptrReturn(); }
)cc";
NullabilityPragmas Pragmas;
TestAST TU(getAugmentedTestInputs(Input, Pragmas));
TypeNullabilityDefaults Defaults(TU.context(), Pragmas);
EXPECT_THAT(getEligibleRanges(TU.context(), Defaults),
UnorderedElementsAreArray(rangesForFunctions(
{"ptrParam", "smartPtrParam", "ptrReturn", "autoStarReturn"},
TU.context(), Defaults)));
}
TEST(GetEligibleRangesFromASTTest, Variables) {
std::string Input = R"cc(
int* IntPtr;
int** IntPtrPtr = nullptr;
auto Auto = IntPtr;
auto* AutoStar = IntPtr;
void func() {
int* LocalIntPtr;
int** LocalIntPtrPtr = nullptr;
auto LocalAuto = LocalIntPtr;
auto* LocalAutoStar = LocalIntPtr;
static int* StaticInFuncIntPtr;
}
namespace std {
template <typename T>
class unique_ptr {
// we need a more complete type for the pointer to use it for variables
// than we do to use it for functions
using pointer = T*;
};
} // namespace std
std::unique_ptr<int> UniquePtr;
auto AutoUniquePtr =
UniquePtr; // not realistic, but just to get the auto deduced
void smartFunc() {
std::unique_ptr<int> LocalUniquePtr;
auto LocalAutoUniquePtr = LocalUniquePtr;
}
)cc";
NullabilityPragmas Pragmas;
TestAST TU(getAugmentedTestInputs(Input, Pragmas));
TypeNullabilityDefaults Defaults(TU.context(), Pragmas);
EXPECT_THAT(getEligibleRanges(TU.context(), Defaults),
UnorderedElementsAreArray(rangesForVars(
{"IntPtr", "IntPtrPtr", "AutoStar", "LocalIntPtr",
"LocalIntPtrPtr", "LocalAutoStar", "StaticInFuncIntPtr",
"UniquePtr", "LocalUniquePtr"},
TU.context(), Defaults)));
}
TEST(GetEligibleRangesFromASTTest, Lambda) {
auto Input = Annotations(R"(
auto Lambda = []() {};
auto LambdaWithPtrParam = [](int *$param^) {};
auto LambdaWithPtrReturn = []() -> int *$return^ { return nullptr; };
)");
NullabilityPragmas Pragmas;
TestAST TU(getAugmentedTestInputs(Input.code(), Pragmas));
TypeNullabilityDefaults Defaults(TU.context(), Pragmas);
EXPECT_THAT(
getEligibleRanges(TU.context(), Defaults),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(1, Input.point("param")),
eligibleRange(0, Input.point("return")))));
}
TEST(GetEligibleRangesFromASTTest, ClassMembers) {
std::string Input = R"cc(
namespace std {
template <typename T>
struct unique_ptr {
using pointer = T*;
};
} // namespace std
template <typename T>
struct _Nullable custom_smart_ptr {
using pointer = T*;
};
class C {
void method();
int* methodWithPtr();
int NonPtrField;
int* PtrField;
static int* StaticField;
std::unique_ptr<int> StdSmartField;
custom_smart_ptr<int> CustomSmartField;
};
)cc";
NullabilityPragmas Pragmas;
TestAST TU(getAugmentedTestInputs(Input, Pragmas));
TypeNullabilityDefaults Defaults(TU.context(), Pragmas);
auto Expected = rangesForFunctions({"methodWithPtr"}, TU.context(), Defaults);
auto AlsoExpected =
rangesForFields({"PtrField", "StdSmartField", "CustomSmartField"},
TU.context(), Defaults);
Expected.insert(Expected.end(), AlsoExpected.begin(), AlsoExpected.end());
AlsoExpected = rangesForVars({"StaticField"}, TU.context(), Defaults);
Expected.insert(Expected.end(), AlsoExpected.begin(), AlsoExpected.end());
EXPECT_THAT(getEligibleRanges(TU.context(), Defaults),
UnorderedElementsAreArray(Expected));
}
TEST(GetEligibleRangesFromASTTest, ClassTemplateMembersNoInstantiation) {
std::string Input = R"cc(
template <typename T>
class CTemplate {
void method();
T* methodWithPtr();
T NonPtrField;
T* PtrField;
static T* StaticField;
};
)cc";
NullabilityPragmas Pragmas;
TestAST TU(getAugmentedTestInputs(Input, Pragmas));
TypeNullabilityDefaults Defaults(TU.context(), Pragmas);
// No ranges without an instantiation.
EXPECT_THAT(getEligibleRanges(TU.context(), Defaults), IsEmpty());
}
TEST(GetEligibleRangesFromASTTest, ClassTemplateMembersHasInstantiation) {
std::string Input = R"cc(
template <typename T>
class CTemplate {
void method();
T* methodWithPtr();
T NonPtrField;
T* PtrField;
static T* StaticField;
};
CTemplate<int> Int;
)cc";
NullabilityPragmas Pragmas;
TestAST TU(getAugmentedTestInputs(Input, Pragmas));
TypeNullabilityDefaults Defaults(TU.context(), Pragmas);
auto &InstantiationDecl = *selectFirst<ClassTemplateSpecializationDecl>(
"b",
match(classTemplateSpecializationDecl(hasName("CTemplate")).bind("b"),
TU.context()));
// Matches the ranges for decls in the instantiation.
std::vector<testing::Matcher<EligibleRange>> Expected;
for (const EligibleRange &ER : getEligibleRanges(
*selectFirst<FunctionDecl>(
"b",
match(findAll(functionDecl(hasName("methodWithPtr")).bind("b")),
InstantiationDecl, TU.context())),
Defaults)) {
Expected.push_back(eligibleRange(ER));
}
for (const EligibleRange &ER : getEligibleRanges(
*selectFirst<FieldDecl>(
"b", match(findAll(fieldDecl(hasName("PtrField")).bind("b")),
InstantiationDecl, TU.context())),
Defaults)) {
Expected.push_back(eligibleRange(ER));
}
for (const EligibleRange &ER : getEligibleRanges(
*selectFirst<VarDecl>(
"b", match(findAll(varDecl(hasName("StaticField")).bind("b")),
InstantiationDecl, TU.context())),
Defaults)) {
Expected.push_back(eligibleRange(ER));
}
EXPECT_THAT(getEligibleRanges(TU.context(), Defaults),
UnorderedElementsAreArray(Expected));
}
TEST(GetEligibleRangesFromASTTest, ClassTemplateExplicitSpecializationMembers) {
std::string Input = R"cc(
template <typename T>
class CTemplate {
void method();
T* methodWithPtr();
T NonPtrField;
T* PtrField;
static T* StaticField;
};
template <>
class CTemplate<int> {
void method();
int* methodWithPtr();
int NonPtrField;
int* PtrField;
static int* StaticField;
int* ExtraFieldInSpecialization;
};
)cc";
NullabilityPragmas Pragmas;
TestAST TU(getAugmentedTestInputs(Input, Pragmas));
TypeNullabilityDefaults Defaults(TU.context(), Pragmas);
auto &ExplicitSpecialization = *selectFirst<ClassTemplateSpecializationDecl>(
"b", match(classTemplateSpecializationDecl().bind("b"), TU.context()));
// Matches only the ranges in the explicit specialization.
std::vector<testing::Matcher<EligibleRange>> Expected;
for (const EligibleRange &ER : getEligibleRanges(
*selectFirst<FunctionDecl>(
"b",
match(findAll(functionDecl(hasName("methodWithPtr")).bind("b")),
ExplicitSpecialization, TU.context())),
Defaults)) {
Expected.push_back(eligibleRange(ER));
}
for (const EligibleRange &ER : getEligibleRanges(
*selectFirst<FieldDecl>(
"b", match(findAll(fieldDecl(hasName("PtrField")).bind("b")),
ExplicitSpecialization, TU.context())),
Defaults)) {
Expected.push_back(eligibleRange(ER));
}
for (const EligibleRange &ER : getEligibleRanges(
*selectFirst<VarDecl>(
"b", match(findAll(varDecl(hasName("StaticField")).bind("b")),
ExplicitSpecialization, TU.context())),
Defaults)) {
Expected.push_back(eligibleRange(ER));
}
for (const EligibleRange &ER : getEligibleRanges(
*selectFirst<FieldDecl>(
"b",
match(findAll(fieldDecl(hasName("ExtraFieldInSpecialization"))
.bind("b")),
ExplicitSpecialization, TU.context())),
Defaults)) {
Expected.push_back(eligibleRange(ER));
}
EXPECT_THAT(getEligibleRanges(TU.context(), Defaults),
UnorderedElementsAreArray(Expected));
}
TEST(GetEligibleRangesFromASTTest, FunctionTemplateNoInstantiation) {
std::string Input = R"cc(
template <typename T>
int funcTemplate(T*) {
T* LocalInTemplate;
return 0;
}
)cc";
NullabilityPragmas Pragmas;
TestAST TU(getAugmentedTestInputs(Input, Pragmas));
TypeNullabilityDefaults Defaults(TU.context(), Pragmas);
EXPECT_THAT(getEligibleRanges(TU.context(), Defaults), IsEmpty());
}
TEST(GetEligibleRangesFromASTTest, FunctionTemplateHasInstantiation) {
auto Input = Annotations(R"(
template <typename T>
int funcTemplate(T*$param^ A, T B) {
T*$local^ LocalPointerInTemplate;
T LocalInTemplate;
return 0;
}
int I = funcTemplate<int>(nullptr, 0);
)");
NullabilityPragmas Pragmas;
TestAST TU(getAugmentedTestInputs(Input.code(), Pragmas));
TypeNullabilityDefaults Defaults(TU.context(), Pragmas);
auto &InstantiationDecl = *selectFirst<FunctionDecl>(
"b",
match(functionDecl(isTemplateInstantiation(), hasName("funcTemplate"))
.bind("b"),
TU.context()));
std::vector<testing::Matcher<EligibleRange>> Expected;
for (const EligibleRange &ER : getEligibleRanges(
*selectFirst<FunctionDecl>(
"b",
match(findAll(functionDecl(hasName("funcTemplate")).bind("b")),
InstantiationDecl, TU.context())),
Defaults)) {
Expected.push_back(eligibleRange(ER));
}
for (const EligibleRange &ER : getEligibleRanges(
*selectFirst<VarDecl>(
"b",
match(findAll(
varDecl(hasName("LocalPointerInTemplate")).bind("b")),
InstantiationDecl, TU.context())),
Defaults)) {
Expected.push_back(eligibleRange(ER));
}
EXPECT_THAT(
getEligibleRanges(TU.context(), Defaults),
AllOf(UnorderedElementsAreArray(Expected),
Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(1, Input.point("param")),
eligibleRange(0, Input.point("local")))));
}
TEST(GetEligibleRangesFromASTTest, AutoFunctionTemplateSyntax) {
auto Input = Annotations(R"(
void funcTemplate(auto P, auto*$star^ Q) {}
void usage() {
int*$local_one^ A = nullptr;
int*$local_two^ B = nullptr;
funcTemplate(A, B);
}
)");
NullabilityPragmas Pragmas;
TestAST TU(getAugmentedTestInputs(Input.code(), Pragmas));
TypeNullabilityDefaults Defaults(TU.context(), Pragmas);
EXPECT_THAT(
getEligibleRanges(TU.context(), Defaults),
AllOf(Each(AllOf(hasPath(MainFileName), hasNoPragmaNullability())),
UnorderedElementsAre(eligibleRange(2, Input.point("star")),
eligibleRange(0, Input.point("local_one")),
eligibleRange(0, Input.point("local_two")))));
}
// This is a crash repro related to a function template's return type that is
// somehow not itself dependent, but has a nested name specifier which is a
// dependent type.
TEST(GetEligibleRangesFromASTTest, NonDependentTypeWithDependentTypeNamespace) {
std::string Input = R"cc(
template <typename T>
struct Inner {
typedef int Alias;
};
template <typename T>
struct Outer {
typedef Inner<bool> InnerAlias;
};
template <typename T>
Outer<T>::InnerAlias::Alias* Foo() {
return nullptr;
}
)cc";
NullabilityPragmas Pragmas;
TestAST TU(getAugmentedTestInputs(Input, Pragmas));
TypeNullabilityDefaults Defaults(TU.context(), Pragmas);
EXPECT_THAT(getEligibleRanges(TU.context(), Defaults), IsEmpty());
}
TEST(skipEscapedNewLinePrefixesTest, NoPrefix) {
EXPECT_EQ(skipEscapedNewLinePrefixes(""), "");
EXPECT_EQ(skipEscapedNewLinePrefixes("foo"), "foo");
EXPECT_EQ(skipEscapedNewLinePrefixes("\too"), "\too");
EXPECT_EQ(skipEscapedNewLinePrefixes("\\too"), "\\too");
EXPECT_EQ(skipEscapedNewLinePrefixes("\foo"), "\foo");
}
TEST(skipEscapedNewLinePrefixesTest, EscapedNewlinePresent) {
EXPECT_EQ(skipEscapedNewLinePrefixes("\\\nfoo"), "foo");
EXPECT_EQ(skipEscapedNewLinePrefixes("\\\rfoo"), "foo");
EXPECT_EQ(skipEscapedNewLinePrefixes("\\\n foo"), " foo");
EXPECT_EQ(skipEscapedNewLinePrefixes("\\ \t \nfoo"), "foo");
EXPECT_EQ(skipEscapedNewLinePrefixes("\\ \nfoo"), "foo");
EXPECT_EQ(skipEscapedNewLinePrefixes("\\ \n\rfoo"), "foo");
EXPECT_EQ(skipEscapedNewLinePrefixes("\\ \r\nfoo"), "foo");
EXPECT_EQ(skipEscapedNewLinePrefixes("\\ \n\nfoo"), "\nfoo");
EXPECT_EQ(skipEscapedNewLinePrefixes("\\ \r\n\\\r\n\\ \r\nfoo"), "foo");
}
} // namespace
} // namespace clang::tidy::nullability