1 //===- unittests/Frontend/ASTUnitTest.cpp - ASTUnit tests -----------------===// 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 <fstream> 10 11 #include "clang/Basic/FileManager.h" 12 #include "clang/Frontend/ASTUnit.h" 13 #include "clang/Frontend/CompilerInstance.h" 14 #include "clang/Frontend/CompilerInvocation.h" 15 #include "clang/Frontend/PCHContainerOperations.h" 16 #include "clang/Lex/HeaderSearch.h" 17 #include "llvm/Support/FileSystem.h" 18 #include "llvm/Support/Path.h" 19 #include "llvm/Support/ToolOutputFile.h" 20 #include "llvm/Support/VirtualFileSystem.h" 21 #include "gtest/gtest.h" 22 23 using namespace llvm; 24 using namespace clang; 25 26 namespace { 27 28 class ASTUnitTest : public ::testing::Test { 29 protected: 30 int FD; 31 llvm::SmallString<256> InputFileName; 32 std::unique_ptr<ToolOutputFile> input_file; 33 IntrusiveRefCntPtr<DiagnosticsEngine> Diags; 34 std::shared_ptr<CompilerInvocation> CInvok; 35 std::shared_ptr<PCHContainerOperations> PCHContainerOps; 36 37 std::unique_ptr<ASTUnit> createASTUnit(bool isVolatile) { 38 EXPECT_FALSE(llvm::sys::fs::createTemporaryFile("ast-unit", "cpp", FD, 39 InputFileName)); 40 input_file = std::make_unique<ToolOutputFile>(InputFileName, FD); 41 input_file->os() << ""; 42 43 const char *Args[] = {"clang", "-xc++", InputFileName.c_str()}; 44 45 auto VFS = llvm::vfs::getRealFileSystem(); 46 Diags = CompilerInstance::createDiagnostics(*VFS, new DiagnosticOptions()); 47 48 CreateInvocationOptions CIOpts; 49 CIOpts.Diags = Diags; 50 CIOpts.VFS = VFS; 51 CInvok = createInvocation(Args, std::move(CIOpts)); 52 53 if (!CInvok) 54 return nullptr; 55 56 FileManager *FileMgr = new FileManager(FileSystemOptions(), VFS); 57 PCHContainerOps = std::make_shared<PCHContainerOperations>(); 58 59 return ASTUnit::LoadFromCompilerInvocation( 60 CInvok, PCHContainerOps, Diags, FileMgr, false, CaptureDiagsKind::None, 61 0, TU_Complete, false, false, isVolatile); 62 } 63 }; 64 65 TEST_F(ASTUnitTest, SaveLoadPreservesLangOptionsInPrintingPolicy) { 66 // Check that the printing policy is restored with the correct language 67 // options when loading an ASTUnit from a file. To this end, an ASTUnit 68 // for a C++ translation unit is set up and written to a temporary file. 69 70 // By default `UseVoidForZeroParams` is true for non-C++ language options, 71 // thus we can check this field after loading the ASTUnit to deduce whether 72 // the correct (C++) language options were used when setting up the printing 73 // policy. 74 75 { 76 PrintingPolicy PolicyWithDefaultLangOpt(LangOptions{}); 77 EXPECT_TRUE(PolicyWithDefaultLangOpt.UseVoidForZeroParams); 78 } 79 80 std::unique_ptr<ASTUnit> AST = createASTUnit(false); 81 82 if (!AST) 83 FAIL() << "failed to create ASTUnit"; 84 85 EXPECT_FALSE(AST->getASTContext().getPrintingPolicy().UseVoidForZeroParams); 86 87 llvm::SmallString<256> ASTFileName; 88 ASSERT_FALSE( 89 llvm::sys::fs::createTemporaryFile("ast-unit", "ast", FD, ASTFileName)); 90 ToolOutputFile ast_file(ASTFileName, FD); 91 AST->Save(ASTFileName.str()); 92 93 EXPECT_TRUE(llvm::sys::fs::exists(ASTFileName)); 94 auto HSOpts = std::make_shared<HeaderSearchOptions>(); 95 96 std::unique_ptr<ASTUnit> AU = ASTUnit::LoadFromASTFile( 97 ASTFileName, PCHContainerOps->getRawReader(), ASTUnit::LoadEverything, 98 Diags, FileSystemOptions(), HSOpts); 99 100 if (!AU) 101 FAIL() << "failed to load ASTUnit"; 102 103 EXPECT_FALSE(AU->getASTContext().getPrintingPolicy().UseVoidForZeroParams); 104 } 105 106 TEST_F(ASTUnitTest, GetBufferForFileMemoryMapping) { 107 std::unique_ptr<ASTUnit> AST = createASTUnit(true); 108 109 if (!AST) 110 FAIL() << "failed to create ASTUnit"; 111 112 std::unique_ptr<llvm::MemoryBuffer> memoryBuffer = 113 AST->getBufferForFile(InputFileName); 114 115 EXPECT_NE(memoryBuffer->getBufferKind(), 116 llvm::MemoryBuffer::MemoryBuffer_MMap); 117 } 118 119 TEST_F(ASTUnitTest, ModuleTextualHeader) { 120 llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFs = 121 new llvm::vfs::InMemoryFileSystem(); 122 InMemoryFs->addFile("test.cpp", 0, llvm::MemoryBuffer::getMemBuffer(R"cpp( 123 #include "Textual.h" 124 void foo() {} 125 )cpp")); 126 InMemoryFs->addFile("m.modulemap", 0, llvm::MemoryBuffer::getMemBuffer(R"cpp( 127 module M { 128 module Textual { 129 textual header "Textual.h" 130 } 131 } 132 )cpp")); 133 InMemoryFs->addFile("Textual.h", 0, llvm::MemoryBuffer::getMemBuffer(R"cpp( 134 void foo(); 135 )cpp")); 136 137 const char *Args[] = {"clang", "test.cpp", "-fmodule-map-file=m.modulemap", 138 "-fmodule-name=M"}; 139 Diags = 140 CompilerInstance::createDiagnostics(*InMemoryFs, new DiagnosticOptions()); 141 CreateInvocationOptions CIOpts; 142 CIOpts.Diags = Diags; 143 CInvok = createInvocation(Args, std::move(CIOpts)); 144 ASSERT_TRUE(CInvok); 145 146 FileManager *FileMgr = new FileManager(FileSystemOptions(), InMemoryFs); 147 PCHContainerOps = std::make_shared<PCHContainerOperations>(); 148 149 auto AU = ASTUnit::LoadFromCompilerInvocation( 150 CInvok, PCHContainerOps, Diags, FileMgr, false, CaptureDiagsKind::None, 1, 151 TU_Complete, false, false, false); 152 ASSERT_TRUE(AU); 153 auto File = AU->getFileManager().getFileRef("Textual.h", false, false); 154 ASSERT_TRUE(bool(File)); 155 // Verify that we do not crash here. 156 EXPECT_TRUE( 157 AU->getPreprocessor().getHeaderSearchInfo().getExistingFileInfo(*File)); 158 } 159 160 TEST_F(ASTUnitTest, LoadFromCommandLineEarlyError) { 161 EXPECT_FALSE( 162 llvm::sys::fs::createTemporaryFile("ast-unit", "c", FD, InputFileName)); 163 input_file = std::make_unique<ToolOutputFile>(InputFileName, FD); 164 input_file->os() << ""; 165 166 const char *Args[] = {"clang", "-target", "foobar", InputFileName.c_str()}; 167 168 auto Diags = CompilerInstance::createDiagnostics( 169 *llvm::vfs::getRealFileSystem(), new DiagnosticOptions()); 170 auto PCHContainerOps = std::make_shared<PCHContainerOperations>(); 171 std::unique_ptr<clang::ASTUnit> ErrUnit; 172 173 std::unique_ptr<ASTUnit> AST = ASTUnit::LoadFromCommandLine( 174 &Args[0], &Args[4], PCHContainerOps, Diags, "", false, "", false, 175 CaptureDiagsKind::All, {}, true, 0, TU_Complete, false, false, false, 176 SkipFunctionBodiesScope::None, false, true, false, false, std::nullopt, 177 &ErrUnit, nullptr); 178 179 ASSERT_EQ(AST, nullptr); 180 ASSERT_NE(ErrUnit, nullptr); 181 ASSERT_TRUE(Diags->hasErrorOccurred()); 182 ASSERT_NE(ErrUnit->stored_diag_size(), 0U); 183 } 184 185 TEST_F(ASTUnitTest, LoadFromCommandLineWorkingDirectory) { 186 EXPECT_FALSE( 187 llvm::sys::fs::createTemporaryFile("bar", "c", FD, InputFileName)); 188 auto Input = std::make_unique<ToolOutputFile>(InputFileName, FD); 189 Input->os() << ""; 190 191 SmallString<128> WorkingDir; 192 ASSERT_FALSE(sys::fs::createUniqueDirectory("foo", WorkingDir)); 193 const char *Args[] = {"clang", "-working-directory", WorkingDir.c_str(), 194 InputFileName.c_str()}; 195 196 auto Diags = CompilerInstance::createDiagnostics( 197 *llvm::vfs::getRealFileSystem(), new DiagnosticOptions()); 198 auto PCHContainerOps = std::make_shared<PCHContainerOperations>(); 199 std::unique_ptr<clang::ASTUnit> ErrUnit; 200 201 std::unique_ptr<ASTUnit> AST = ASTUnit::LoadFromCommandLine( 202 &Args[0], &Args[4], PCHContainerOps, Diags, "", false, "", false, 203 CaptureDiagsKind::All, {}, true, 0, TU_Complete, false, false, false, 204 SkipFunctionBodiesScope::None, false, true, false, false, std::nullopt, 205 &ErrUnit, nullptr); 206 207 ASSERT_NE(AST, nullptr); 208 ASSERT_FALSE(Diags->hasErrorOccurred()); 209 210 // Make sure '-working-directory' sets both the FileSystemOpts and underlying 211 // VFS working directory. 212 const auto &FM = AST->getFileManager(); 213 const auto &VFS = FM.getVirtualFileSystem(); 214 ASSERT_EQ(*VFS.getCurrentWorkingDirectory(), WorkingDir.str()); 215 ASSERT_EQ(FM.getFileSystemOpts().WorkingDir, WorkingDir.str()); 216 } 217 218 } // anonymous namespace 219