blob: b657ce6cecf030c914f046705e4e986a6a8cb43e [file] [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 "common/annotation_reader.h"
#include <optional>
#include <string>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/log/check.h"
#include "absl/status/status.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
#include "common/status_test_matchers.h"
#include "common/string_view_conversion.h"
#include "clang/AST/Attr.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/AST/TypeBase.h"
#include "clang/Testing/TestAST.h"
namespace crubit {
namespace {
using ::testing::AllOf;
using ::testing::Each;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::HasSubstr;
using ::testing::NotNull;
using ::testing::Optional;
using ::testing::Pointee;
using ::testing::Property;
using ::testing::ResultOf;
using ::testing::SizeIs;
template <class T>
T& LookupDecl(clang::ASTContext& context, absl::string_view name) {
clang::DeclContextLookupResult result =
context.getTranslationUnitDecl()->lookup(
&context.Idents.get(StringRefFromStringView(name)));
CHECK(result.isSingleResult());
return *cast<T>(result.front());
}
// Returns a matcher that can be used with `EXPECT_THAT` to check the result of
// `GetExprAsBool` for a given matcher.
inline auto MakeExprAsBoolMatcher(const clang::ASTContext& ast_context) {
return [&ast_context](auto matcher) {
return ResultOf(
[&ast_context](const clang::Expr& expr) {
return GetExprAsBool(expr, ast_context);
},
matcher);
};
}
TEST(AnnotationReaderTest, GetAnnotateAttrArgsSuccess) {
clang::TestAST ast(R"cc(
[[clang::annotate("foo")]] int i;
)cc");
auto& var = LookupDecl<clang::VarDecl>(ast.context(), "i");
EXPECT_THAT(GetAnnotateAttrArgs(var, "foo"),
IsOkAndHolds(Optional(ElementsAre())));
EXPECT_THAT(GetAnnotateAttrArgs(var, "bar"), IsOkAndHolds(std::nullopt));
}
TEST(AnnotationReaderTest, GetAnnotateAttrArgsFailureDoubleAnnotation) {
clang::TestAST ast(R"cc(
[[clang::annotate("foo")]] [[clang::annotate("foo")]] int i;
)cc");
auto& var = LookupDecl<clang::VarDecl>(ast.context(), "i");
EXPECT_THAT(
GetAnnotateAttrArgs(var, "foo"),
StatusIs(
absl::StatusCode::kInvalidArgument,
HasSubstr(
"Only one `foo` annotation may be placed on a declaration.")));
}
TEST(AnnotationReaderTest, GetAnnotateAttrFailureArgNotIntegralOrString) {
clang::TestAST ast(R"cc(
[[clang::annotate("foo", 1.0)]] extern int i;
)cc");
auto& var = LookupDecl<clang::VarDecl>(ast.context(), "i");
ASSERT_THAT(
GetAnnotateAttrArgs(var, "foo"),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Arguments of `foo` annotation must be of integral "
"type, string literal, or constructor")));
}
TEST(AnnotationReaderTest, GetAnnotateAttrSuccessConsistentAnnotations) {
clang::TestAST ast(R"cc(
[[clang::annotate("foo", "arg1", 1)]] extern int i;
[[clang::annotate("foo", "arg1", 1)]] extern int i;
)cc");
auto& var = LookupDecl<clang::VarDecl>(ast.context(), "i");
ASSERT_THAT(GetAnnotateAttrArgs(var, "foo"),
IsOkAndHolds(Optional(ElementsAre(NotNull(), NotNull()))));
}
TEST(AnnotationReaderTest, GetAnnotateAttrFailureConflictingIntArgs) {
clang::TestAST ast(R"cc(
[[clang::annotate("foo", 1)]] extern int i;
[[clang::annotate("foo", 2)]] extern int i;
)cc");
auto& var = LookupDecl<clang::VarDecl>(ast.context(), "i");
ASSERT_THAT(
GetAnnotateAttrArgs(var, "foo"),
StatusIs(
absl::StatusCode::kInvalidArgument,
HasSubstr(
"Different declarations have inconsistent `foo` annotations.")));
}
TEST(AnnotationReaderTest, GetAnnotateAttrFailureConflictingStringArgs) {
clang::TestAST ast(R"cc(
[[clang::annotate("foo", "1")]] extern int i;
[[clang::annotate("foo", "2")]] extern int i;
)cc");
auto& var = LookupDecl<clang::VarDecl>(ast.context(), "i");
ASSERT_THAT(
GetAnnotateAttrArgs(var, "foo"),
StatusIs(
absl::StatusCode::kInvalidArgument,
HasSubstr(
"Different declarations have inconsistent `foo` annotations.")));
}
TEST(AnnotationReaderTest, GetAnnotateAttrFailureConflictingArgCounts) {
clang::TestAST ast(R"cc(
[[clang::annotate("foo")]] extern int i;
[[clang::annotate("foo", 1)]] extern int i;
)cc");
auto& var = LookupDecl<clang::VarDecl>(ast.context(), "i");
ASSERT_THAT(
GetAnnotateAttrArgs(var, "foo"),
StatusIs(
absl::StatusCode::kInvalidArgument,
HasSubstr(
"Different declarations have inconsistent `foo` annotations.")));
}
TEST(AnnotationReaderTest,
GetAnnotateAttrSuccessAnnotationMissingFromDefinition) {
clang::TestAST ast(R"cc(
[[clang::annotate("foo")]] extern int i;
int i;
)cc");
auto& var = LookupDecl<clang::VarDecl>(ast.context(), "i");
ASSERT_THAT(GetAnnotateAttrArgs(var, "foo"),
IsOkAndHolds(Optional(ElementsAre())));
}
TEST(AnnotationReaderTest,
GetAnnotateAttrSuccessAnnotationMissingFromForwardDeclaration) {
clang::TestAST ast(R"cc(
extern int i;
[[clang::annotate("foo")]] int i;
)cc");
auto& var = LookupDecl<clang::VarDecl>(ast.context(), "i");
ASSERT_THAT(GetAnnotateAttrArgs(var, "foo"),
IsOkAndHolds(Optional(ElementsAre())));
}
TEST(AnnotationReaderTest, GetAnnotationWithStringArgsSuccess) {
clang::TestAST ast(R"cc(
[[clang::annotate("foo", "arg1", "arg2")]] int i;
)cc");
auto& var = LookupDecl<clang::VarDecl>(ast.context(), "i");
EXPECT_THAT(GetAnnotationWithStringArgs(var, "foo"),
IsOkAndHolds(Optional(ElementsAre("arg1", "arg2"))));
}
TEST(AnnotationReaderTest, GetAnnotationWithStringArgsNone) {
clang::TestAST ast(R"cc(
int i;
)cc");
auto& var = LookupDecl<clang::VarDecl>(ast.context(), "i");
EXPECT_THAT(GetAnnotationWithStringArgs(var, "foo"),
IsOkAndHolds(std::nullopt));
}
TEST(AnnotationReaderTest, GetAnnotationWithStringArgsFailureNonString) {
clang::TestAST ast(R"cc(
[[clang::annotate("foo", "arg1", 42)]] int i;
)cc");
auto& var = LookupDecl<clang::VarDecl>(ast.context(), "i");
EXPECT_THAT(
GetAnnotationWithStringArgs(var, "foo"),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Annotation foo arguments must be string literals.")));
}
TEST(AnnotationReaderTest, GetAnnotationWithStringArgSuccess) {
clang::TestAST ast(R"cc(
[[clang::annotate("foo", "bar")]] int i;
)cc");
auto& var = LookupDecl<clang::VarDecl>(ast.context(), "i");
EXPECT_THAT(GetAnnotationWithStringArg(var, "foo"),
IsOkAndHolds(Optional(Eq("bar"))));
EXPECT_THAT(GetAnnotationWithStringArg(var, "missing"),
IsOkAndHolds(std::nullopt));
}
TEST(AnnotationReaderTest, GetAnnotationWithStringArgFailureNoArgs) {
clang::TestAST ast(R"cc(
[[clang::annotate("foo")]] int i;
)cc");
auto& var = LookupDecl<clang::VarDecl>(ast.context(), "i");
EXPECT_THAT(
GetAnnotationWithStringArg(var, "foo"),
StatusIs(
absl::StatusCode::kInvalidArgument,
HasSubstr("Annotation foo must have a single string argument.")));
}
TEST(AnnotationReaderTest, GetAnnotationWithStringArgFailureMultipleArgs) {
clang::TestAST ast(R"cc(
[[clang::annotate("foo", "a", "b")]] int i;
)cc");
auto& var = LookupDecl<clang::VarDecl>(ast.context(), "i");
EXPECT_THAT(
GetAnnotationWithStringArg(var, "foo"),
StatusIs(
absl::StatusCode::kInvalidArgument,
HasSubstr("Annotation foo must have a single string argument.")));
}
TEST(AnnotationReaderTest, GetAnnotationWithStringArgFailureNonStringArg) {
clang::TestAST ast(R"cc(
[[clang::annotate("foo", 123)]] int i;
)cc");
auto& var = LookupDecl<clang::VarDecl>(ast.context(), "i");
EXPECT_THAT(
GetAnnotationWithStringArg(var, "foo"),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Annotation foo arguments must be string literals.")));
}
TEST(AnnotationReaderTest, HasAnnotationWithoutArgsCases) {
clang::TestAST ast(R"cc(
[[clang::annotate("no_args")]] [[clang::annotate("with_args", 1)]] int i;
int j;
)cc");
auto& var_i = LookupDecl<clang::VarDecl>(ast.context(), "i");
auto& var_j = LookupDecl<clang::VarDecl>(ast.context(), "j");
EXPECT_THAT(HasAnnotationWithoutArgs(var_i, "no_args"), IsOkAndHolds(true));
EXPECT_THAT(HasAnnotationWithoutArgs(var_j, "no_args"), IsOkAndHolds(false));
EXPECT_THAT(
HasAnnotationWithoutArgs(var_i, "with_args"),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Annotation with_args does not expect arguments.")));
}
TEST(AnnotationReaderTest, GetExprAsBoolSuccess) {
clang::TestAST ast(R"cc(
[[clang::annotate("foo", true, false)]] int i;
)cc");
auto& var = LookupDecl<clang::VarDecl>(ast.context(), "i");
auto expr_as_bool = MakeExprAsBoolMatcher(ast.context());
EXPECT_THAT(GetAnnotateAttrArgs(var, "foo"),
IsOkAndHolds(Optional(
ElementsAre(Pointee(expr_as_bool(IsOkAndHolds(true))),
Pointee(expr_as_bool(IsOkAndHolds(false)))))));
}
TEST(AnnotationReaderTest, GetExprAsBoolRejectsNonBooleanTypes) {
clang::TestAST ast(R"cc(
[[clang::annotate("foo", 1, 0, 'a', 1.5)]] int i;
)cc");
auto& var = LookupDecl<clang::VarDecl>(ast.context(), "i");
auto* attr = var.getAttr<clang::AnnotateAttr>();
ASSERT_THAT(attr, NotNull());
auto expr_as_bool = MakeExprAsBoolMatcher(ast.context());
EXPECT_THAT(
absl::MakeSpan(attr->args_begin(), attr->args_end()),
AllOf(
SizeIs(4),
Each(Pointee(expr_as_bool(StatusIs(
absl::StatusCode::kInvalidArgument,
HasSubstr("annotation expression must evaluate to a bool")))))));
}
TEST(AnnotationReaderTest, ConsistentAnnotationsWithTypedefType) {
clang::TestAST ast(R"cc(
using MyInt = int;
[[clang::annotate("foo", (MyInt)1)]] extern int i;
[[clang::annotate("foo", (int)1)]] extern int i;
)cc");
auto& var = LookupDecl<clang::VarDecl>(ast.context(), "i");
EXPECT_THAT(GetAnnotateAttrArgs(var, "foo"),
IsOkAndHolds(Optional(ElementsAre(NotNull()))));
}
TEST(AnnotationReaderTest, GetTypeAnnotationSingleDeclBasic) {
clang::TestAST ast(R"cc(
int [[clang::annotate_type("my_type_annot")]] x;
)cc");
auto& var = LookupDecl<clang::VarDecl>(ast.context(), "x");
const clang::Type* type = var.getType().getTypePtr();
EXPECT_THAT(GetTypeAnnotationSingleDecl(type, "my_type_annot"),
IsOkAndHolds(Pointee(Property(
&clang::AnnotateTypeAttr::getAnnotation, "my_type_annot"))));
EXPECT_THAT(GetTypeAnnotationSingleDecl(type, "nonexistent"),
IsOkAndHolds(nullptr));
}
TEST(AnnotationReaderTest,
GetTypeAnnotationSingleDeclMultipleAnnotationsOnType) {
clang::TestAST ast(R"cc(
int [[clang::annotate_type("inner")]] [[clang::annotate_type("outer")]] x;
)cc");
auto& var = LookupDecl<clang::VarDecl>(ast.context(), "x");
const clang::Type* type = var.getType().getTypePtr();
EXPECT_THAT(GetTypeAnnotationSingleDecl(type, "outer"),
IsOkAndHolds(Pointee(
Property(&clang::AnnotateTypeAttr::getAnnotation, "outer"))));
EXPECT_THAT(GetTypeAnnotationSingleDecl(type, "inner"),
IsOkAndHolds(Pointee(
Property(&clang::AnnotateTypeAttr::getAnnotation, "inner"))));
}
TEST(AnnotationReaderTest,
GetTypeAnnotationSingleDeclDuplicateAnnotationError) {
clang::TestAST ast(R"cc(
int [[clang::annotate_type("duplicate")]] [[clang::annotate_type(
"duplicate")]] x;
)cc");
auto& var = LookupDecl<clang::VarDecl>(ast.context(), "x");
const clang::Type* type = var.getType().getTypePtr();
EXPECT_THAT(
GetTypeAnnotationSingleDecl(type, "duplicate"),
StatusIs(
absl::StatusCode::kInvalidArgument,
HasSubstr(
"Only one `duplicate` annotation may be placed on a type.")));
}
} // namespace
} // namespace crubit