| 1 | //===-- TestClangASTImporter.cpp ------------------------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "gtest/gtest.h" |
| 10 | |
| 11 | #include "Plugins/ExpressionParser/Clang/ClangASTImporter.h" |
| 12 | #include "Plugins/ExpressionParser/Clang/ClangASTMetadata.h" |
| 13 | #include "Plugins/ExpressionParser/Clang/ClangUtil.h" |
| 14 | #include "Plugins/SymbolFile/DWARF/DWARFASTParserClang.h" |
| 15 | #include "Plugins/TypeSystem/Clang/TypeSystemClang.h" |
| 16 | #include "TestingSupport/SubsystemRAII.h" |
| 17 | #include "TestingSupport/Symbol/ClangTestUtils.h" |
| 18 | #include "lldb/Core/Declaration.h" |
| 19 | #include "lldb/Host/FileSystem.h" |
| 20 | #include "lldb/Host/HostInfo.h" |
| 21 | #include "clang/AST/DeclCXX.h" |
| 22 | |
| 23 | using namespace clang; |
| 24 | using namespace lldb; |
| 25 | using namespace lldb_private; |
| 26 | |
| 27 | class TestClangASTImporter : public testing::Test { |
| 28 | public: |
| 29 | SubsystemRAII<FileSystem, HostInfo> subsystems; |
| 30 | }; |
| 31 | |
| 32 | TEST_F(TestClangASTImporter, CanImportInvalidType) { |
| 33 | ClangASTImporter importer; |
| 34 | EXPECT_FALSE(importer.CanImport(CompilerType())); |
| 35 | } |
| 36 | |
| 37 | TEST_F(TestClangASTImporter, ImportInvalidType) { |
| 38 | ClangASTImporter importer; |
| 39 | EXPECT_FALSE(importer.Import(CompilerType())); |
| 40 | } |
| 41 | |
| 42 | TEST_F(TestClangASTImporter, CopyDeclTagDecl) { |
| 43 | // Tests that the ClangASTImporter::CopyDecl can copy TagDecls. |
| 44 | clang_utils::SourceASTWithRecord source; |
| 45 | |
| 46 | auto holder = |
| 47 | std::make_unique<clang_utils::TypeSystemClangHolder>(args: "target ast" ); |
| 48 | auto *target_ast = holder->GetAST(); |
| 49 | |
| 50 | ClangASTImporter importer; |
| 51 | clang::Decl *imported = |
| 52 | importer.CopyDecl(&target_ast->getASTContext(), source.record_decl); |
| 53 | ASSERT_NE(nullptr, imported); |
| 54 | |
| 55 | // Check that we got the correct decl by just comparing their qualified name. |
| 56 | clang::TagDecl *imported_tag_decl = llvm::cast<clang::TagDecl>(Val: imported); |
| 57 | EXPECT_EQ(source.record_decl->getQualifiedNameAsString(), |
| 58 | imported_tag_decl->getQualifiedNameAsString()); |
| 59 | // We did a minimal import of the tag decl. |
| 60 | EXPECT_TRUE(imported_tag_decl->hasExternalLexicalStorage()); |
| 61 | |
| 62 | // Check that origin was set for the imported declaration. |
| 63 | ClangASTImporter::DeclOrigin origin = importer.GetDeclOrigin(decl: imported); |
| 64 | EXPECT_TRUE(origin.Valid()); |
| 65 | EXPECT_EQ(origin.ctx, &source.ast->getASTContext()); |
| 66 | EXPECT_EQ(origin.decl, source.record_decl); |
| 67 | } |
| 68 | |
| 69 | TEST_F(TestClangASTImporter, CopyTypeTagDecl) { |
| 70 | // Tests that the ClangASTImporter::CopyType can copy TagDecls types. |
| 71 | clang_utils::SourceASTWithRecord source; |
| 72 | |
| 73 | auto holder = |
| 74 | std::make_unique<clang_utils::TypeSystemClangHolder>(args: "target ast" ); |
| 75 | auto *target_ast = holder->GetAST(); |
| 76 | |
| 77 | ClangASTImporter importer; |
| 78 | CompilerType imported = importer.CopyType(dst&: *target_ast, src_type: source.record_type); |
| 79 | ASSERT_TRUE(imported.IsValid()); |
| 80 | |
| 81 | // Check that we got the correct decl by just comparing their qualified name. |
| 82 | clang::TagDecl *imported_tag_decl = ClangUtil::GetAsTagDecl(type: imported); |
| 83 | EXPECT_EQ(source.record_decl->getQualifiedNameAsString(), |
| 84 | imported_tag_decl->getQualifiedNameAsString()); |
| 85 | // We did a minimal import of the tag decl. |
| 86 | EXPECT_TRUE(imported_tag_decl->hasExternalLexicalStorage()); |
| 87 | |
| 88 | // Check that origin was set for the imported declaration. |
| 89 | ClangASTImporter::DeclOrigin origin = |
| 90 | importer.GetDeclOrigin(imported_tag_decl); |
| 91 | EXPECT_TRUE(origin.Valid()); |
| 92 | EXPECT_EQ(origin.ctx, &source.ast->getASTContext()); |
| 93 | EXPECT_EQ(origin.decl, source.record_decl); |
| 94 | } |
| 95 | |
| 96 | TEST_F(TestClangASTImporter, CompleteFwdDeclWithOtherOrigin) { |
| 97 | // Create an AST with a full type that is defined. |
| 98 | clang_utils::SourceASTWithRecord source_with_definition; |
| 99 | |
| 100 | // Create an AST with a type thst is only a forward declaration with the |
| 101 | // same name as the one in the other source. |
| 102 | auto holder = |
| 103 | std::make_unique<clang_utils::TypeSystemClangHolder>(args: "ast" ); |
| 104 | auto *fwd_decl_source = holder->GetAST(); |
| 105 | CompilerType fwd_decl_type = clang_utils::createRecord( |
| 106 | ast&: *fwd_decl_source, name: source_with_definition.record_decl->getName()); |
| 107 | |
| 108 | // Create a target and import the forward decl. |
| 109 | auto target_holder = |
| 110 | std::make_unique<clang_utils::TypeSystemClangHolder>(args: "target ast" ); |
| 111 | auto *target = target_holder->GetAST(); |
| 112 | ClangASTImporter importer; |
| 113 | CompilerType imported = importer.CopyType(dst&: *target, src_type: fwd_decl_type); |
| 114 | ASSERT_TRUE(imported.IsValid()); |
| 115 | EXPECT_FALSE(imported.IsDefined()); |
| 116 | |
| 117 | // Now complete the forward decl with the definition from the other source. |
| 118 | // This should define the decl and give it the fields of the other origin. |
| 119 | clang::TagDecl *imported_tag_decl = ClangUtil::GetAsTagDecl(type: imported); |
| 120 | importer.CompleteTagDeclWithOrigin(imported_tag_decl, |
| 121 | source_with_definition.record_decl); |
| 122 | ASSERT_TRUE(imported.IsValid()); |
| 123 | EXPECT_TRUE(imported.IsDefined()); |
| 124 | EXPECT_EQ(1U, imported.GetNumFields()); |
| 125 | } |
| 126 | |
| 127 | TEST_F(TestClangASTImporter, DeportDeclTagDecl) { |
| 128 | // Tests that the ClangASTImporter::DeportDecl completely copies TagDecls. |
| 129 | clang_utils::SourceASTWithRecord source; |
| 130 | |
| 131 | auto holder = |
| 132 | std::make_unique<clang_utils::TypeSystemClangHolder>(args: "target ast" ); |
| 133 | auto *target_ast = holder->GetAST(); |
| 134 | |
| 135 | ClangASTImporter importer; |
| 136 | clang::Decl *imported = |
| 137 | importer.DeportDecl(&target_ast->getASTContext(), source.record_decl); |
| 138 | ASSERT_NE(nullptr, imported); |
| 139 | |
| 140 | // Check that we got the correct decl by just comparing their qualified name. |
| 141 | clang::TagDecl *imported_tag_decl = llvm::cast<clang::TagDecl>(Val: imported); |
| 142 | EXPECT_EQ(source.record_decl->getQualifiedNameAsString(), |
| 143 | imported_tag_decl->getQualifiedNameAsString()); |
| 144 | // The record should be completed as we deported it. |
| 145 | EXPECT_FALSE(imported_tag_decl->hasExternalLexicalStorage()); |
| 146 | |
| 147 | // Deporting doesn't update the origin map. |
| 148 | EXPECT_FALSE(importer.GetDeclOrigin(imported_tag_decl).Valid()); |
| 149 | } |
| 150 | |
| 151 | TEST_F(TestClangASTImporter, DeportTypeTagDecl) { |
| 152 | // Tests that the ClangASTImporter::CopyType can deport TagDecl types. |
| 153 | clang_utils::SourceASTWithRecord source; |
| 154 | |
| 155 | auto holder = |
| 156 | std::make_unique<clang_utils::TypeSystemClangHolder>(args: "target ast" ); |
| 157 | auto *target_ast = holder->GetAST(); |
| 158 | |
| 159 | ClangASTImporter importer; |
| 160 | CompilerType imported = importer.DeportType(dst&: *target_ast, src_type: source.record_type); |
| 161 | ASSERT_TRUE(imported.IsValid()); |
| 162 | |
| 163 | // Check that we got the correct decl by just comparing their qualified name. |
| 164 | clang::TagDecl *imported_tag_decl = ClangUtil::GetAsTagDecl(type: imported); |
| 165 | EXPECT_EQ(source.record_decl->getQualifiedNameAsString(), |
| 166 | imported_tag_decl->getQualifiedNameAsString()); |
| 167 | // The record should be completed as we deported it. |
| 168 | EXPECT_FALSE(imported_tag_decl->hasExternalLexicalStorage()); |
| 169 | |
| 170 | // Deporting doesn't update the origin map. |
| 171 | EXPECT_FALSE(importer.GetDeclOrigin(imported_tag_decl).Valid()); |
| 172 | } |
| 173 | |
| 174 | TEST_F(TestClangASTImporter, MetadataPropagation) { |
| 175 | // Tests that AST metadata is propagated when copying declarations. |
| 176 | |
| 177 | clang_utils::SourceASTWithRecord source; |
| 178 | |
| 179 | const lldb::user_id_t metadata = 123456; |
| 180 | source.ast->SetMetadataAsUserID(source.record_decl, metadata); |
| 181 | |
| 182 | auto holder = |
| 183 | std::make_unique<clang_utils::TypeSystemClangHolder>(args: "target ast" ); |
| 184 | auto *target_ast = holder->GetAST(); |
| 185 | |
| 186 | ClangASTImporter importer; |
| 187 | clang::Decl *imported = |
| 188 | importer.CopyDecl(&target_ast->getASTContext(), source.record_decl); |
| 189 | ASSERT_NE(nullptr, imported); |
| 190 | |
| 191 | // Check that we got the same Metadata. |
| 192 | ASSERT_NE(std::nullopt, importer.GetDeclMetadata(imported)); |
| 193 | EXPECT_EQ(metadata, importer.GetDeclMetadata(imported)->GetUserID()); |
| 194 | } |
| 195 | |
| 196 | TEST_F(TestClangASTImporter, MetadataPropagationIndirectImport) { |
| 197 | // Tests that AST metadata is propagated when copying declarations when |
| 198 | // importing one declaration into a temporary context and then to the |
| 199 | // actual destination context. |
| 200 | |
| 201 | clang_utils::SourceASTWithRecord source; |
| 202 | |
| 203 | const lldb::user_id_t metadata = 123456; |
| 204 | source.ast->SetMetadataAsUserID(source.record_decl, metadata); |
| 205 | |
| 206 | auto tmp_holder = |
| 207 | std::make_unique<clang_utils::TypeSystemClangHolder>(args: "tmp ast" ); |
| 208 | auto *temporary_ast = tmp_holder->GetAST(); |
| 209 | |
| 210 | ClangASTImporter importer; |
| 211 | clang::Decl *temporary_imported = |
| 212 | importer.CopyDecl(&temporary_ast->getASTContext(), source.record_decl); |
| 213 | ASSERT_NE(nullptr, temporary_imported); |
| 214 | |
| 215 | auto holder = |
| 216 | std::make_unique<clang_utils::TypeSystemClangHolder>(args: "target ast" ); |
| 217 | auto *target_ast = holder->GetAST(); |
| 218 | clang::Decl *imported = |
| 219 | importer.CopyDecl(dst_ctx: &target_ast->getASTContext(), decl: temporary_imported); |
| 220 | ASSERT_NE(nullptr, imported); |
| 221 | |
| 222 | // Check that we got the same Metadata. |
| 223 | ASSERT_NE(std::nullopt, importer.GetDeclMetadata(imported)); |
| 224 | EXPECT_EQ(metadata, importer.GetDeclMetadata(imported)->GetUserID()); |
| 225 | } |
| 226 | |
| 227 | TEST_F(TestClangASTImporter, MetadataPropagationAfterCopying) { |
| 228 | // Tests that AST metadata is propagated when copying declarations even |
| 229 | // when the metadata was set after the declaration has already been copied. |
| 230 | |
| 231 | clang_utils::SourceASTWithRecord source; |
| 232 | const lldb::user_id_t metadata = 123456; |
| 233 | |
| 234 | auto holder = |
| 235 | std::make_unique<clang_utils::TypeSystemClangHolder>(args: "target ast" ); |
| 236 | auto *target_ast = holder->GetAST(); |
| 237 | |
| 238 | ClangASTImporter importer; |
| 239 | clang::Decl *imported = |
| 240 | importer.CopyDecl(&target_ast->getASTContext(), source.record_decl); |
| 241 | ASSERT_NE(nullptr, imported); |
| 242 | |
| 243 | // The TagDecl has been imported. Now set the metadata of the source and |
| 244 | // make sure the imported one will directly see it. |
| 245 | source.ast->SetMetadataAsUserID(source.record_decl, metadata); |
| 246 | |
| 247 | // Check that we got the same Metadata. |
| 248 | ASSERT_NE(std::nullopt, importer.GetDeclMetadata(imported)); |
| 249 | EXPECT_EQ(metadata, importer.GetDeclMetadata(imported)->GetUserID()); |
| 250 | } |
| 251 | |
| 252 | TEST_F(TestClangASTImporter, RecordLayout) { |
| 253 | // Test that it is possible to register RecordDecl layouts and then later |
| 254 | // correctly retrieve them. |
| 255 | |
| 256 | clang_utils::SourceASTWithRecord source; |
| 257 | |
| 258 | ClangASTImporter importer; |
| 259 | ClangASTImporter::LayoutInfo layout_info; |
| 260 | layout_info.bit_size = 15; |
| 261 | layout_info.alignment = 2; |
| 262 | layout_info.field_offsets[source.field_decl] = 1; |
| 263 | importer.SetRecordLayout(decl: source.record_decl, layout: layout_info); |
| 264 | |
| 265 | uint64_t bit_size; |
| 266 | uint64_t alignment; |
| 267 | llvm::DenseMap<const clang::FieldDecl *, uint64_t> field_offsets; |
| 268 | llvm::DenseMap<const clang::CXXRecordDecl *, clang::CharUnits> base_offsets; |
| 269 | llvm::DenseMap<const clang::CXXRecordDecl *, clang::CharUnits> vbase_offsets; |
| 270 | importer.LayoutRecordType(record_decl: source.record_decl, bit_size, alignment, |
| 271 | field_offsets, base_offsets, vbase_offsets); |
| 272 | |
| 273 | EXPECT_EQ(15U, bit_size); |
| 274 | EXPECT_EQ(2U, alignment); |
| 275 | EXPECT_EQ(1U, field_offsets.size()); |
| 276 | EXPECT_EQ(1U, field_offsets[source.field_decl]); |
| 277 | EXPECT_EQ(0U, base_offsets.size()); |
| 278 | EXPECT_EQ(0U, vbase_offsets.size()); |
| 279 | } |
| 280 | |
| 281 | TEST_F(TestClangASTImporter, RecordLayoutFromOrigin) { |
| 282 | // Tests that we can retrieve the layout of a record that has |
| 283 | // an origin with an already existing LayoutInfo. We expect |
| 284 | // the layout to be retrieved from the ClangASTImporter of the |
| 285 | // origin decl. |
| 286 | |
| 287 | clang_utils::SourceASTWithRecord source; |
| 288 | |
| 289 | auto *dwarf_parser = |
| 290 | static_cast<DWARFASTParserClang *>(source.ast->GetDWARFParser()); |
| 291 | auto &importer = dwarf_parser->GetClangASTImporter(); |
| 292 | |
| 293 | // Set the layout for the origin decl in the origin ClangASTImporter. |
| 294 | ClangASTImporter::LayoutInfo layout_info; |
| 295 | layout_info.bit_size = 32; |
| 296 | layout_info.alignment = 16; |
| 297 | layout_info.field_offsets[source.field_decl] = 1; |
| 298 | importer.SetRecordLayout(decl: source.record_decl, layout: layout_info); |
| 299 | |
| 300 | auto holder = |
| 301 | std::make_unique<clang_utils::TypeSystemClangHolder>(args: "target ast" ); |
| 302 | auto *target_ast = holder->GetAST(); |
| 303 | |
| 304 | // Import the decl into a new TypeSystemClang. |
| 305 | CompilerType imported = importer.CopyType(dst&: *target_ast, src_type: source.record_type); |
| 306 | ASSERT_TRUE(imported.IsValid()); |
| 307 | |
| 308 | auto *imported_decl = cast<CXXRecordDecl>(Val: ClangUtil::GetAsTagDecl(type: imported)); |
| 309 | ClangASTImporter::DeclOrigin origin = importer.GetDeclOrigin(imported_decl); |
| 310 | ASSERT_TRUE(origin.Valid()); |
| 311 | ASSERT_EQ(origin.decl, source.record_decl); |
| 312 | |
| 313 | uint64_t bit_size; |
| 314 | uint64_t alignment; |
| 315 | llvm::DenseMap<const clang::FieldDecl *, uint64_t> field_offsets; |
| 316 | llvm::DenseMap<const clang::CXXRecordDecl *, clang::CharUnits> base_offsets; |
| 317 | llvm::DenseMap<const clang::CXXRecordDecl *, clang::CharUnits> vbase_offsets; |
| 318 | |
| 319 | // Make sure we correctly read out the layout (despite not Having |
| 320 | // called SetRecordLayout on the new TypeSystem's ClangASTImporter). |
| 321 | auto success = |
| 322 | importer.LayoutRecordType(imported_decl, bit_size, alignment, |
| 323 | field_offsets, base_offsets, vbase_offsets); |
| 324 | EXPECT_TRUE(success); |
| 325 | |
| 326 | EXPECT_EQ(32U, bit_size); |
| 327 | EXPECT_EQ(16U, alignment); |
| 328 | EXPECT_EQ(1U, field_offsets.size()); |
| 329 | EXPECT_EQ(1U, field_offsets[*imported_decl->field_begin()]); |
| 330 | EXPECT_EQ(0U, base_offsets.size()); |
| 331 | EXPECT_EQ(0U, vbase_offsets.size()); |
| 332 | } |
| 333 | |