blob: 0a85ce9b08591106b2c2c7cb6772ec69549375aa [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 <memory>
#include <string>
#include <utility>
#include <vector>
#include "rs_bindings_from_cc/ir.h"
#include "rs_bindings_from_cc/ir_from_cc.h"
#include "testing/base/public/gmock.h"
#include "testing/base/public/gunit.h"
#include "third_party/absl/container/flat_hash_map.h"
#include "third_party/absl/strings/string_view.h"
#include "third_party/absl/strings/substitute.h"
#include "third_party/absl/types/span.h"
namespace rs_bindings_from_cc {
namespace {
using ::testing::AllOf;
using ::testing::ElementsAre;
using ::testing::IsEmpty;
using ::testing::Not;
using ::testing::Property;
using ::testing::SizeIs;
using ::testing::VariantWith;
// Matches an IR node that has the given identifier.
MATCHER_P(IdentifierIs, identifier, "") {
if (arg.identifier.Ident() == identifier) return true;
*result_listener << "actual identifier: '" << arg.identifier.Ident() << "'";
return false;
}
// Matches an IR node that has the given doc comment.
MATCHER_P(DocCommentIs, doc_comment, "") {
if (arg.doc_comment && *arg.doc_comment == doc_comment) return true;
*result_listener << "actual doc comment: '"
<< (arg.doc_comment ? *arg.doc_comment : "<none>") << "'";
return false;
}
// Matches a Func that has the given mangled name.
MATCHER_P(MangledNameIs, mangled_name, "") {
if (arg.mangled_name == mangled_name) return true;
*result_listener << "actual mangled name: '" << arg.mangled_name << "'";
return false;
}
// Matches a Func that has a return type matching `matcher`.
template <typename Matcher>
auto ReturnType(const Matcher& matcher) {
return testing::Field("return_type", &Func::return_type, matcher);
}
// Matches a Func that has parameters matching `matchers`.
template <typename... Args>
auto ParamsAre(const Args&... matchers) {
return testing::Field("params", &Func::params, ElementsAre(matchers...));
}
// Matches a Func that is inline.
MATCHER(IsInline, "") { return arg.is_inline; }
// Matches a FuncParam with a type that matches all given matchers.
template <typename... Args>
auto ParamType(const Args&... matchers) {
return testing::Field("type", &FuncParam::type, AllOf(matchers...));
}
// Matches a RsType or CcType that has the given name.
MATCHER_P(NameIs, name, "") {
if (arg.name == name) return true;
*result_listener << "actual name: '" << arg.name << "'";
return false;
}
// Matches a MappedType with a CcType that matches all given matchers.
template <typename... Args>
auto CcTypeIs(const Args&... matchers) {
return testing::Field("cc_type", &MappedType::cc_type, AllOf(matchers...));
}
// Matches a MappedType with a RsType that matches all given matchers.
template <typename... Args>
auto RsTypeIs(const Args&... matchers) {
return testing::Field("rs_type", &MappedType::rs_type, AllOf(matchers...));
}
// Matches an RsType that has type parameters matching `matchers`.
template <typename... Args>
auto RsTypeParamsAre(const Args&... matchers) {
return testing::Field("type_params", &RsType::type_params,
ElementsAre(matchers...));
}
// Matches a CcType that has type parameters matching `matchers`.
template <typename... Args>
auto CcTypeParamsAre(const Args&... matchers) {
return testing::Field("type_params", &CcType::type_params,
ElementsAre(matchers...));
}
auto IsCcInt() { return AllOf(NameIs("int"), CcTypeParamsAre()); }
auto IsRsInt() { return AllOf(NameIs("i32"), RsTypeParamsAre()); }
// Matches a CcType that is a pointer to a type matching `matcher`.
template <typename Matcher>
auto CcPointsTo(const Matcher& matcher) {
return AllOf(NameIs("*"), CcTypeParamsAre(matcher));
}
// Matches an RsType that is a pointer to a type matching `matcher`.
template <typename Matcher>
auto RsPointsTo(const Matcher& matcher) {
return AllOf(NameIs("*mut"), RsTypeParamsAre(matcher));
}
// Matches a MappedType that is void.
MATCHER(IsVoid, "") { return arg.IsVoid(); }
// Matches a MappedType that is an integer.
auto IsInt() { return AllOf(CcTypeIs(IsCcInt()), RsTypeIs(IsRsInt())); }
// Matches a MappedType that is a pointer to integer.
auto IsIntPtr() {
return AllOf(CcTypeIs(CcPointsTo(IsCcInt())),
RsTypeIs(RsPointsTo(IsRsInt())));
}
// Matches a MappedType for cc and rs types with no type parameters.
auto IsSimpleType(absl::string_view rs_name, absl::string_view cc_name) {
return AllOf(CcTypeIs(NameIs(cc_name), CcTypeParamsAre()),
RsTypeIs(NameIs(rs_name), RsTypeParamsAre()));
}
// Matches a Record that has fields matching `matchers`.
template <typename... Args>
auto FieldsAre(const Args&... matchers) {
return testing::Field("fields", &Record::fields, ElementsAre(matchers...));
}
// Matches a Record that has the given size.
MATCHER_P(RecordSizeIs, size, "") {
if (arg.size == size) return true;
*result_listener << "actual size: " << arg.size;
return false;
}
// Matches a Record that has the given alignment.
MATCHER_P(AlignmentIs, alignment, "") {
if (arg.alignment == alignment) return true;
*result_listener << "actual alignment: " << arg.alignment;
return false;
}
// Matches a Record with a copy_constructor that matches all given matchers.
template <typename... Args>
auto CopyConstructor(const Args&... matchers) {
return testing::Field("copy_constructor", &Record::copy_constructor,
AllOf(matchers...));
}
// Matches a Record with a move_constructor that matches all given matchers.
template <typename... Args>
auto MoveConstructor(const Args&... matchers) {
return testing::Field("move_constructor", &Record::move_constructor,
AllOf(matchers...));
}
// Matches a Record with a destructor that matches all given matchers.
template <typename... Args>
auto Destructor(const Args&... matchers) {
return testing::Field("destructor", &Record::destructor, AllOf(matchers...));
}
// Matches a Record which is trivial for calls.
MATCHER(IsTrivialAbi, "") { return arg.is_trivial_abi; }
// Matches a SpecialMemberFunc that has the given definition.
MATCHER_P(DefinitionIs, definition, "") { return arg.definition == definition; }
// Matches a Field that has the given access specifier.
MATCHER_P(AccessIs, access, "") { return arg.access == access; }
// Matches a Field that has the given offset.
MATCHER_P(OffsetIs, offset, "") {
if (arg.offset == offset) return true;
*result_listener << "actual offset: " << arg.offset;
return false;
}
// Matches a Field with a type that matches all given matchers.
template <typename... Args>
auto FieldType(const Args&... matchers) {
return testing::Field("type", &Field::type, AllOf(matchers...));
}
TEST(AstVisitorTest, Noop) {
IR ir = IrFromCc({"// nothing interesting there."}, {});
EXPECT_THAT(ir.items, IsEmpty());
EXPECT_THAT(ir.used_headers,
ElementsAre(Property(&HeaderName::IncludePath,
"test/testing_header_0.h")));
}
TEST(AstVisitorTest, IREmptyOnInvalidInput) {
IR ir = IrFromCc({"int foo(); But this is not C++"}, {});
EXPECT_THAT(ir.items, IsEmpty());
}
TEST(AstVisitorTest, FuncWithVoidReturnType) {
IR ir = IrFromCc({"void Foo();"}, {});
EXPECT_THAT(ir.items, ElementsAre(VariantWith<Func>(
AllOf(IdentifierIs("Foo"), MangledNameIs("_Z3Foov"),
ReturnType(IsVoid()), ParamsAre()))));
}
TEST(AstVisitorTest, TwoFuncs) {
IR ir = IrFromCc({"void Foo(); void Bar();"}, {});
EXPECT_THAT(
ir.items,
ElementsAre(
VariantWith<Func>(AllOf(IdentifierIs("Foo"), MangledNameIs("_Z3Foov"),
ReturnType(IsVoid()), ParamsAre())),
VariantWith<Func>(AllOf(IdentifierIs("Bar"), MangledNameIs("_Z3Barv"),
ReturnType(IsVoid()), ParamsAre()))));
}
TEST(AstVisitorTest, TwoFuncsFromTwoHeaders) {
IR ir = IrFromCc({"void Foo();", "void Bar();"}, {});
EXPECT_THAT(ir.items, ElementsAre(VariantWith<Func>(IdentifierIs("Foo")),
VariantWith<Func>(IdentifierIs("Bar"))));
}
TEST(AstVisitorTest, NonInlineFunc) {
IR ir = IrFromCc({"void Foo() {}"}, {});
EXPECT_THAT(ir.items, ElementsAre(VariantWith<Func>(
AllOf(IdentifierIs("Foo"), Not(IsInline())))));
}
TEST(AstVisitorTest, InlineFunc) {
IR ir = IrFromCc({"inline void Foo() {}"}, {});
EXPECT_THAT(
ir.items,
ElementsAre(VariantWith<Func>(AllOf(IdentifierIs("Foo"), IsInline()))));
}
TEST(AstVisitorTest, FuncJustOnce) {
IR ir = IrFromCc({"void Foo(); void Foo();"}, {});
EXPECT_THAT(ir.items,
ElementsAre(VariantWith<Func>(AllOf(IdentifierIs("Foo")))));
}
TEST(AstVisitorTest, TestImportPointerFunc) {
IR ir = IrFromCc({"int* Foo(int* a);"}, {});
EXPECT_THAT(ir.items,
ElementsAre(VariantWith<Func>(AllOf(
ReturnType(IsIntPtr()), ParamsAre(ParamType(IsIntPtr()))))));
}
TEST(AstVisitorTest, Struct) {
IR ir = IrFromCc(
{"struct SomeStruct { int first_field; int second_field; };"}, {});
EXPECT_THAT(ir.items,
ElementsAre(VariantWith<Record>(AllOf(
IdentifierIs("SomeStruct"), RecordSizeIs(8), AlignmentIs(4),
FieldsAre(AllOf(IdentifierIs("first_field"),
FieldType(IsInt()), OffsetIs(0)),
AllOf(IdentifierIs("second_field"),
FieldType(IsInt()), OffsetIs(32)))))));
}
TEST(AstVisitorTest, TrivialCopyConstructor) {
absl::string_view file =
"struct Implicit {};\n"
"struct Defaulted { Defaulted(const Defaulted&) = default; };\n";
IR ir = IrFromCc({file}, {});
EXPECT_THAT(ir.items, SizeIs(2));
EXPECT_THAT(ir.items, Each(VariantWith<Record>(CopyConstructor(DefinitionIs(
SpecialMemberFunc::Definition::kTrivial)))));
}
TEST(AstVisitorTest, NontrivialCopyConstructor) {
absl::string_view file = "struct Defined { Defined(const Defined&);};\n";
// TODO(b/202113881): "struct MemberImplicit { Defined x; };\n"
// TODO(b/202113881): "struct MemberDefaulted { MemberDefaulted(const
// MemberDefaulted&) = default; Defined x; };\n"
IR ir = IrFromCc({file}, {});
EXPECT_THAT(ir.items, SizeIs(1));
EXPECT_THAT(ir.items, Each(VariantWith<Record>(CopyConstructor(DefinitionIs(
SpecialMemberFunc::Definition::kNontrivial)))));
}
TEST(AstVisitorTest, DeletedCopyConstructor) {
absl::string_view file =
"struct Deleted { Deleted(const Deleted&) = delete;};\n"
// TODO(b/202113881): "struct DeletedByMember { Deleted x; };\n"
"struct DeletedByCtorDef { DeletedByCtorDef(DeletedByCtorDef&&) {} };\n";
IR ir = IrFromCc({file}, {});
EXPECT_THAT(ir.items, SizeIs(2));
EXPECT_THAT(ir.items, Each(VariantWith<Record>(CopyConstructor(DefinitionIs(
SpecialMemberFunc::Definition::kDeleted)))));
}
TEST(AstVisitorTest, PublicCopyConstructor) {
absl::string_view file =
"class Implicit {};\n"
"struct Defaulted { Defaulted(const Defaulted&) = default; };\n"
"class Section { public: Section(const Section&) = default; };\n";
IR ir = IrFromCc({file}, {});
EXPECT_THAT(ir.items, SizeIs(3));
EXPECT_THAT(ir.items,
Each(VariantWith<Record>(CopyConstructor(AccessIs(kPublic)))));
}
TEST(AstVisitorTest, PrivateCopyConstructor) {
absl::string_view file =
"class Defaulted { Defaulted(const Defaulted&) = default; };\n"
"struct Section { private: Section(const Section&) = default; };\n";
IR ir = IrFromCc({file}, {});
EXPECT_THAT(ir.items, SizeIs(2));
EXPECT_THAT(ir.items,
Each(VariantWith<Record>(CopyConstructor(AccessIs(kPrivate)))));
}
TEST(AstVisitorTest, TrivialMoveConstructor) {
absl::string_view file =
"struct Implicit {};\n"
"struct Defaulted { Defaulted(Defaulted&&) = default; };\n";
IR ir = IrFromCc({file}, {});
EXPECT_THAT(ir.items, SizeIs(2));
EXPECT_THAT(ir.items, Each(VariantWith<Record>(MoveConstructor(DefinitionIs(
SpecialMemberFunc::Definition::kTrivial)))));
}
TEST(AstVisitorTest, NontrivialMoveConstructor) {
absl::string_view file = "struct Defined { Defined(Defined&&);};\n";
// TODO(b/202113881): "struct MemberImplicit { Defined x; };\n"
// TODO(b/202113881): "struct MemberDefaulted { MemberDefaulted(
// MemberDefaulted&&) = default; Defined x; };\n"
IR ir = IrFromCc({file}, {});
EXPECT_THAT(ir.items, SizeIs(1));
EXPECT_THAT(ir.items, Each(VariantWith<Record>(MoveConstructor(DefinitionIs(
SpecialMemberFunc::Definition::kNontrivial)))));
}
TEST(AstVisitorTest, DeletedMoveConstructor) {
absl::string_view file =
"struct Deleted { Deleted(Deleted&&) = delete;};\n"
// TODO(b/202113881): "struct DeletedByMember { Deleted x; };\n"
"struct SuppressedByCtorDef {"
" SuppressedByCtorDef(const SuppressedByCtorDef&) {}};\n";
IR ir = IrFromCc({file}, {});
EXPECT_THAT(ir.items, SizeIs(2));
EXPECT_THAT(ir.items, Each(VariantWith<Record>(MoveConstructor(DefinitionIs(
SpecialMemberFunc::Definition::kDeleted)))));
}
TEST(AstVisitorTest, PublicMoveConstructor) {
absl::string_view file =
"class Implicit {};\n"
"struct Defaulted { Defaulted(Defaulted&&) = default; };\n"
"class Section { public: Section(Section&&) = default; };\n";
IR ir = IrFromCc({file}, {});
EXPECT_THAT(ir.items, SizeIs(3));
EXPECT_THAT(ir.items,
Each(VariantWith<Record>(MoveConstructor(AccessIs(kPublic)))));
}
TEST(AstVisitorTest, PrivateMoveConstructor) {
absl::string_view file =
"class Defaulted { Defaulted(Defaulted&&) = default; };\n"
"struct Section { private: Section(Section&&) = default; };\n";
IR ir = IrFromCc({file}, {});
EXPECT_THAT(ir.items, SizeIs(2));
EXPECT_THAT(ir.items,
Each(VariantWith<Record>(MoveConstructor(AccessIs(kPrivate)))));
}
TEST(AstVisitorTest, TrivialDestructor) {
absl::string_view file =
"struct Implicit {};\n"
"struct Defaulted { ~Defaulted() = default; };\n";
IR ir = IrFromCc({file}, {});
EXPECT_THAT(ir.items, SizeIs(2));
EXPECT_THAT(ir.items, Each(VariantWith<Record>(Destructor(DefinitionIs(
SpecialMemberFunc::Definition::kTrivial)))));
}
TEST(AstVisitorTest, NontrivialDestructor) {
absl::string_view file = "struct Defined { ~Defined();};\n";
// TODO(b/202113881): "struct MemberImplicit { Defined x; };\n"
// TODO(b/202113881): "struct MemberDefaulted { ~MemberDefaulted() = default;
// Defined x; };\n"
IR ir = IrFromCc({file}, {});
EXPECT_THAT(ir.items, SizeIs(1));
EXPECT_THAT(ir.items, Each(VariantWith<Record>(Destructor(DefinitionIs(
SpecialMemberFunc::Definition::kNontrivial)))));
}
TEST(AstVisitorTest, DeletedDestructor) {
absl::string_view file = "struct Deleted { ~Deleted() = delete;};\n";
// TODO(b/202113881): "struct DeletedByMember { Deleted x; };\n"
IR ir = IrFromCc({file}, {});
EXPECT_THAT(ir.items, SizeIs(1));
EXPECT_THAT(ir.items, Each(VariantWith<Record>(Destructor(DefinitionIs(
SpecialMemberFunc::Definition::kDeleted)))));
}
TEST(AstVisitorTest, PublicDestructor) {
absl::string_view file =
"class Implicit {};\n"
"struct Defaulted { ~Defaulted() = default; };\n"
"class Section { public: ~Section() = default; };\n";
IR ir = IrFromCc({file}, {});
EXPECT_THAT(ir.items, SizeIs(3));
EXPECT_THAT(ir.items,
Each(VariantWith<Record>(Destructor(AccessIs(kPublic)))));
}
TEST(AstVisitorTest, PrivateDestructor) {
absl::string_view file =
"class Defaulted { ~Defaulted() = default; };\n"
"struct Section { private: ~Section() = default; };\n";
IR ir = IrFromCc({file}, {});
EXPECT_THAT(ir.items, SizeIs(2));
EXPECT_THAT(ir.items,
Each(VariantWith<Record>(Destructor(AccessIs(kPrivate)))));
}
TEST(AstVisitorTest, TrivialAbi) {
absl::string_view file = R"cc(
struct Empty {};
struct Defaulted {
Defaulted(const Defaulted&) = default;
};
struct [[clang::trivial_abi]] Nontrivial {
Nontrivial(const Nontrivial&) {}
};
)cc";
IR ir = IrFromCc({file}, {});
EXPECT_THAT(ir.items, SizeIs(3));
EXPECT_THAT(ir.items, Each(VariantWith<Record>(IsTrivialAbi())));
}
TEST(AstVisitorTest, NotTrivialAbi) {
absl::string_view file = R"cc(
struct Nontrivial {
Nontrivial(const Nontrivial&) {}
};
)cc";
IR ir = IrFromCc({file}, {});
EXPECT_THAT(ir.items, SizeIs(1));
EXPECT_THAT(ir.items, Each(VariantWith<Record>(Not(IsTrivialAbi()))));
}
TEST(AstVisitorTest, MemberVariableAccessSpecifiers) {
IR ir = IrFromCc({R"(
struct SomeStruct {
int default_access_int;
public:
int public_int;
protected:
int protected_int;
private:
int private_int;
};
class SomeClass {
int default_access_int;
};
)"},
{});
EXPECT_THAT(
ir.items,
ElementsAre(
VariantWith<Record>(AllOf(
IdentifierIs("SomeStruct"),
FieldsAre(
AllOf(IdentifierIs("default_access_int"), AccessIs(kPublic)),
AllOf(IdentifierIs("public_int"), AccessIs(kPublic)),
AllOf(IdentifierIs("protected_int"), AccessIs(kProtected)),
AllOf(IdentifierIs("private_int"), AccessIs(kPrivate))))),
VariantWith<Record>(
AllOf(IdentifierIs("SomeClass"),
FieldsAre(AllOf(IdentifierIs("default_access_int"),
AccessIs(kPrivate)))))));
}
TEST(AstVisitorTest, IntegerTypes) {
auto ir = IrFromCc({"#include <stdint.h>\n"
"#include <stddef.h>\n"
"struct S { "
" bool b;"
" char c;"
" unsigned char uc;"
" signed char sc;"
" char16_t c16;"
" char32_t c32;"
" wchar_t wc;"
" short s;"
" int i;"
" long l;"
" long long ll;"
" unsigned short us;"
" unsigned int ui;"
" unsigned long ul;"
" unsigned long long ull;"
" signed short ss;"
" signed int si;"
" signed long sl;"
" signed long long sll;"
" int8_t i8;"
" int16_t i16;"
" int32_t i32;"
" int64_t i64;"
" uint8_t u8;"
" uint16_t u16;"
" uint32_t u32;"
" uint64_t u64;"
" ptrdiff_t pt;"
" size_t st;",
" intptr_t ip;"
" uintptr_t up;"
" float f;"
" double d;"
"};"},
{});
EXPECT_THAT(
ir.items,
ElementsAre(VariantWith<Record>(FieldsAre(
FieldType(IsSimpleType("bool", "bool")),
FieldType(IsSimpleType("i8", "char")),
FieldType(IsSimpleType("u8", "unsigned char")),
FieldType(IsSimpleType("i8", "signed char")),
FieldType(IsSimpleType("u16", "char16_t")),
// We cannot map C++ char32_t or wchar_t to Rust char,
// because Rust requires that chars are valid UTF scalar values.
FieldType(IsSimpleType("u32", "char32_t")),
FieldType(IsSimpleType("i32", "wchar_t")),
FieldType(IsSimpleType("i16", "short")),
FieldType(IsSimpleType("i32", "int")),
FieldType(IsSimpleType("i64", "long")),
FieldType(IsSimpleType("i64", "long long")),
FieldType(IsSimpleType("u16", "unsigned short")),
FieldType(IsSimpleType("u32", "unsigned int")),
FieldType(IsSimpleType("u64", "unsigned long")),
FieldType(IsSimpleType("u64", "unsigned long long")),
FieldType(IsSimpleType("i16", "short")),
FieldType(IsSimpleType("i32", "int")),
FieldType(IsSimpleType("i64", "long")),
FieldType(IsSimpleType("i64", "long long")),
FieldType(IsSimpleType("i8", "int8_t")),
FieldType(IsSimpleType("i16", "int16_t")),
FieldType(IsSimpleType("i32", "int32_t")),
FieldType(IsSimpleType("i64", "int64_t")),
FieldType(IsSimpleType("u8", "uint8_t")),
FieldType(IsSimpleType("u16", "uint16_t")),
FieldType(IsSimpleType("u32", "uint32_t")),
FieldType(IsSimpleType("u64", "uint64_t")),
FieldType(IsSimpleType("isize", "ptrdiff_t")),
FieldType(IsSimpleType("usize", "size_t")),
FieldType(IsSimpleType("isize", "intptr_t")),
FieldType(IsSimpleType("usize", "uintptr_t")),
FieldType(IsSimpleType("f32", "float")),
FieldType(IsSimpleType("f64", "double"))))));
}
TEST(AstVisitorTest, DocComment) {
IR ir = IrFromCc({R"(
/// Doc comment
///
/// * with three slashes
struct DocCommentSlashes {};
//! Doc comment
//!
//! * with slashes and bang
struct DocCommentBang {};
/** Multiline comment
* with two stars */
struct MultilineCommentTwoStars {};
// Line comment
//
// * with two slashes
struct LineComment {};
/* Multiline comment
* with one star */
struct MultilineOneStar {};
)"},
{});
EXPECT_THAT(
ir.items,
ElementsAre(VariantWith<Record>(AllOf(
IdentifierIs("DocCommentSlashes"),
DocCommentIs("Doc comment\n\n * with three slashes"))),
VariantWith<Record>(AllOf(
IdentifierIs("DocCommentBang"),
DocCommentIs("Doc comment\n\n * with slashes and bang"))),
// TODO(forster): The bullet point is not retained in this
// case. Instead we get the space at the end. Not sure if this
// can be fixed easily...
VariantWith<Record>(AllOf(
IdentifierIs("MultilineCommentTwoStars"),
DocCommentIs("Multiline comment\n\n with two stars "))),
VariantWith<Record>(AllOf(
IdentifierIs("LineComment"),
DocCommentIs("Line comment\n\n * with two slashes"))),
// TODO(forster): The bullet point is not retained in this
// case. Instead we get the space at the end. Not sure if this
// can be fixed easily...
VariantWith<Record>(AllOf(
IdentifierIs("MultilineOneStar"),
DocCommentIs("Multiline comment\n\n with one star ")))));
}
} // namespace
} // namespace rs_bindings_from_cc