mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-18 14:46:39 +00:00
903 lines
27 KiB
C++
903 lines
27 KiB
C++
//===--- PreambleTests.cpp --------------------------------------*- C++ -*-===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "Annotations.h"
|
|
#include "Compiler.h"
|
|
#include "Config.h"
|
|
#include "Diagnostics.h"
|
|
#include "Headers.h"
|
|
#include "Hover.h"
|
|
#include "ParsedAST.h"
|
|
#include "Preamble.h"
|
|
#include "Protocol.h"
|
|
#include "SourceCode.h"
|
|
#include "TestFS.h"
|
|
#include "TestTU.h"
|
|
#include "XRefs.h"
|
|
#include "support/Context.h"
|
|
#include "clang/Basic/SourceManager.h"
|
|
#include "clang/Format/Format.h"
|
|
#include "clang/Frontend/FrontendActions.h"
|
|
#include "clang/Frontend/PrecompiledPreamble.h"
|
|
#include "llvm/ADT/StringMap.h"
|
|
#include "llvm/ADT/StringRef.h"
|
|
#include "llvm/Support/Error.h"
|
|
#include "llvm/Support/MemoryBuffer.h"
|
|
#include "llvm/Support/ScopedPrinter.h"
|
|
#include "llvm/Support/VirtualFileSystem.h"
|
|
#include "llvm/Testing/Annotations/Annotations.h"
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest-matchers.h"
|
|
#include "gtest/gtest.h"
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
using testing::AllOf;
|
|
using testing::Contains;
|
|
using testing::ElementsAre;
|
|
using testing::Field;
|
|
using testing::IsEmpty;
|
|
using testing::Matcher;
|
|
using testing::MatchesRegex;
|
|
using testing::UnorderedElementsAre;
|
|
using testing::UnorderedElementsAreArray;
|
|
|
|
namespace clang {
|
|
namespace clangd {
|
|
namespace {
|
|
|
|
MATCHER_P2(Distance, File, D, "") {
|
|
return arg.first() == File && arg.second == D;
|
|
}
|
|
|
|
// Builds a preamble for BaselineContents, patches it for ModifiedContents and
|
|
// returns the includes in the patch.
|
|
IncludeStructure
|
|
collectPatchedIncludes(llvm::StringRef ModifiedContents,
|
|
llvm::StringRef BaselineContents,
|
|
llvm::StringRef MainFileName = "main.cpp") {
|
|
MockFS FS;
|
|
auto TU = TestTU::withCode(BaselineContents);
|
|
TU.Filename = MainFileName.str();
|
|
// ms-compatibility changes meaning of #import, make sure it is turned off.
|
|
TU.ExtraArgs = {"-fno-ms-compatibility"};
|
|
auto BaselinePreamble = TU.preamble();
|
|
// Create the patch.
|
|
TU.Code = ModifiedContents.str();
|
|
auto PI = TU.inputs(FS);
|
|
auto PP = PreamblePatch::createFullPatch(testPath(TU.Filename), PI,
|
|
*BaselinePreamble);
|
|
// Collect patch contents.
|
|
IgnoreDiagnostics Diags;
|
|
auto CI = buildCompilerInvocation(PI, Diags);
|
|
PP.apply(*CI);
|
|
// Run preprocessor over the modified contents with patched Invocation. We
|
|
// provide a preamble and trim contents to ensure only the implicit header
|
|
// introduced by the patch is parsed and nothing else.
|
|
// We don't run PP directly over the patch cotents to test production
|
|
// behaviour.
|
|
auto Bounds = Lexer::ComputePreamble(ModifiedContents, CI->getLangOpts());
|
|
auto Clang =
|
|
prepareCompilerInstance(std::move(CI), &BaselinePreamble->Preamble,
|
|
llvm::MemoryBuffer::getMemBufferCopy(
|
|
ModifiedContents.slice(0, Bounds.Size).str()),
|
|
PI.TFS->view(PI.CompileCommand.Directory), Diags);
|
|
PreprocessOnlyAction Action;
|
|
if (!Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0])) {
|
|
ADD_FAILURE() << "failed begin source file";
|
|
return {};
|
|
}
|
|
IncludeStructure Includes;
|
|
Includes.collect(*Clang);
|
|
if (llvm::Error Err = Action.Execute()) {
|
|
ADD_FAILURE() << "failed to execute action: " << std::move(Err);
|
|
return {};
|
|
}
|
|
Action.EndSourceFile();
|
|
return Includes;
|
|
}
|
|
|
|
// Check preamble lexing logic by building an empty preamble and patching it
|
|
// with all the contents.
|
|
TEST(PreamblePatchTest, IncludeParsing) {
|
|
// We expect any line with a point to show up in the patch.
|
|
llvm::StringRef Cases[] = {
|
|
// Only preamble
|
|
R"cpp(^#include "a.h")cpp",
|
|
// Both preamble and mainfile
|
|
R"cpp(
|
|
^#include "a.h"
|
|
garbage, finishes preamble
|
|
#include "a.h")cpp",
|
|
// Mixed directives
|
|
R"cpp(
|
|
^#include "a.h"
|
|
#pragma directive
|
|
// some comments
|
|
^#include_next <a.h>
|
|
#ifdef skipped
|
|
^#import "a.h"
|
|
#endif)cpp",
|
|
// Broken directives
|
|
R"cpp(
|
|
#include "a
|
|
^#include "a.h"
|
|
#include <b
|
|
^#include <b.h>)cpp",
|
|
// Directive is not part of preamble if it is not the token immediately
|
|
// followed by the hash (#).
|
|
R"cpp(
|
|
^#include "a.h"
|
|
#/**/include <b.h>)cpp",
|
|
};
|
|
|
|
for (const auto &Case : Cases) {
|
|
Annotations Test(Case);
|
|
const auto Code = Test.code();
|
|
SCOPED_TRACE(Code);
|
|
|
|
auto Includes =
|
|
collectPatchedIncludes(Code, /*BaselineContents=*/"").MainFileIncludes;
|
|
auto Points = Test.points();
|
|
ASSERT_EQ(Includes.size(), Points.size());
|
|
for (size_t I = 0, E = Includes.size(); I != E; ++I)
|
|
EXPECT_EQ(Includes[I].HashLine, Points[I].line);
|
|
}
|
|
}
|
|
|
|
TEST(PreamblePatchTest, ContainsNewIncludes) {
|
|
constexpr llvm::StringLiteral BaselineContents = R"cpp(
|
|
#include <a.h>
|
|
#include <b.h> // This will be removed
|
|
#include <c.h>
|
|
)cpp";
|
|
constexpr llvm::StringLiteral ModifiedContents = R"cpp(
|
|
#include <a.h>
|
|
#include <c.h> // This has changed a line.
|
|
#include <c.h> // This is a duplicate.
|
|
#include <d.h> // This is newly introduced.
|
|
)cpp";
|
|
auto Includes = collectPatchedIncludes(ModifiedContents, BaselineContents)
|
|
.MainFileIncludes;
|
|
EXPECT_THAT(Includes, ElementsAre(AllOf(Field(&Inclusion::Written, "<d.h>"),
|
|
Field(&Inclusion::HashLine, 4))));
|
|
}
|
|
|
|
TEST(PreamblePatchTest, MainFileIsEscaped) {
|
|
auto Includes = collectPatchedIncludes("#include <a.h>", "", "file\"name.cpp")
|
|
.MainFileIncludes;
|
|
EXPECT_THAT(Includes, ElementsAre(AllOf(Field(&Inclusion::Written, "<a.h>"),
|
|
Field(&Inclusion::HashLine, 0))));
|
|
}
|
|
|
|
TEST(PreamblePatchTest, PatchesPreambleIncludes) {
|
|
MockFS FS;
|
|
IgnoreDiagnostics Diags;
|
|
auto TU = TestTU::withCode(R"cpp(
|
|
#include "a.h" // IWYU pragma: keep
|
|
#include "c.h"
|
|
#ifdef FOO
|
|
#include "d.h"
|
|
#endif
|
|
)cpp");
|
|
TU.AdditionalFiles["a.h"] = "#include \"b.h\"";
|
|
TU.AdditionalFiles["b.h"] = "";
|
|
TU.AdditionalFiles["c.h"] = "";
|
|
auto PI = TU.inputs(FS);
|
|
auto BaselinePreamble = buildPreamble(
|
|
TU.Filename, *buildCompilerInvocation(PI, Diags), PI, true, nullptr);
|
|
// We drop c.h from modified and add a new header. Since the latter is patched
|
|
// we should only get a.h in preamble includes. d.h shouldn't be part of the
|
|
// preamble, as it's coming from a disabled region.
|
|
TU.Code = R"cpp(
|
|
#include "a.h"
|
|
#include "b.h"
|
|
#ifdef FOO
|
|
#include "d.h"
|
|
#endif
|
|
)cpp";
|
|
auto PP = PreamblePatch::createFullPatch(testPath(TU.Filename), TU.inputs(FS),
|
|
*BaselinePreamble);
|
|
// Only a.h should exists in the preamble, as c.h has been dropped and b.h was
|
|
// newly introduced.
|
|
EXPECT_THAT(
|
|
PP.preambleIncludes(),
|
|
ElementsAre(AllOf(
|
|
Field(&Inclusion::Written, "\"a.h\""),
|
|
Field(&Inclusion::Resolved, testPath("a.h")),
|
|
Field(&Inclusion::HeaderID, testing::Not(testing::Eq(std::nullopt))),
|
|
Field(&Inclusion::FileKind, SrcMgr::CharacteristicKind::C_User))));
|
|
}
|
|
|
|
std::optional<ParsedAST>
|
|
createPatchedAST(llvm::StringRef Baseline, llvm::StringRef Modified,
|
|
llvm::StringMap<std::string> AdditionalFiles = {}) {
|
|
auto TU = TestTU::withCode(Baseline);
|
|
TU.AdditionalFiles = std::move(AdditionalFiles);
|
|
auto BaselinePreamble = TU.preamble();
|
|
if (!BaselinePreamble) {
|
|
ADD_FAILURE() << "Failed to build baseline preamble";
|
|
return std::nullopt;
|
|
}
|
|
|
|
IgnoreDiagnostics Diags;
|
|
MockFS FS;
|
|
TU.Code = Modified.str();
|
|
auto CI = buildCompilerInvocation(TU.inputs(FS), Diags);
|
|
if (!CI) {
|
|
ADD_FAILURE() << "Failed to build compiler invocation";
|
|
return std::nullopt;
|
|
}
|
|
return ParsedAST::build(testPath(TU.Filename), TU.inputs(FS), std::move(CI),
|
|
{}, BaselinePreamble);
|
|
}
|
|
|
|
std::string getPreamblePatch(llvm::StringRef Baseline,
|
|
llvm::StringRef Modified) {
|
|
auto BaselinePreamble = TestTU::withCode(Baseline).preamble();
|
|
if (!BaselinePreamble) {
|
|
ADD_FAILURE() << "Failed to build baseline preamble";
|
|
return "";
|
|
}
|
|
MockFS FS;
|
|
auto TU = TestTU::withCode(Modified);
|
|
return PreamblePatch::createFullPatch(testPath("main.cpp"), TU.inputs(FS),
|
|
*BaselinePreamble)
|
|
.text()
|
|
.str();
|
|
}
|
|
|
|
TEST(PreamblePatchTest, IncludesArePreserved) {
|
|
llvm::StringLiteral Baseline = R"(//error-ok
|
|
#include <foo>
|
|
#include <bar>
|
|
)";
|
|
llvm::StringLiteral Modified = R"(//error-ok
|
|
#include <foo>
|
|
#include <bar>
|
|
#define FOO)";
|
|
|
|
auto Includes = createPatchedAST(Baseline, Modified.str())
|
|
->getIncludeStructure()
|
|
.MainFileIncludes;
|
|
EXPECT_TRUE(!Includes.empty());
|
|
EXPECT_EQ(Includes, TestTU::withCode(Baseline)
|
|
.build()
|
|
.getIncludeStructure()
|
|
.MainFileIncludes);
|
|
}
|
|
|
|
TEST(PreamblePatchTest, Define) {
|
|
// BAR should be defined while parsing the AST.
|
|
struct {
|
|
const char *const Contents;
|
|
const char *const ExpectedPatch;
|
|
} Cases[] = {
|
|
{
|
|
R"cpp(
|
|
#define BAR
|
|
[[BAR]])cpp",
|
|
R"cpp(#line 0 ".*main.cpp"
|
|
#undef BAR
|
|
#line 2
|
|
#define BAR
|
|
)cpp",
|
|
},
|
|
// multiline macro
|
|
{
|
|
R"cpp(
|
|
#define BAR \
|
|
|
|
[[BAR]])cpp",
|
|
R"cpp(#line 0 ".*main.cpp"
|
|
#undef BAR
|
|
#line 2
|
|
#define BAR
|
|
)cpp",
|
|
},
|
|
// multiline macro
|
|
{
|
|
R"cpp(
|
|
#define \
|
|
BAR
|
|
[[BAR]])cpp",
|
|
R"cpp(#line 0 ".*main.cpp"
|
|
#undef BAR
|
|
#line 3
|
|
#define BAR
|
|
)cpp",
|
|
},
|
|
};
|
|
|
|
for (const auto &Case : Cases) {
|
|
SCOPED_TRACE(Case.Contents);
|
|
llvm::Annotations Modified(Case.Contents);
|
|
EXPECT_THAT(getPreamblePatch("", Modified.code()),
|
|
MatchesRegex(Case.ExpectedPatch));
|
|
|
|
auto AST = createPatchedAST("", Modified.code());
|
|
ASSERT_TRUE(AST);
|
|
std::vector<llvm::Annotations::Range> MacroRefRanges;
|
|
for (auto &M : AST->getMacros().MacroRefs) {
|
|
for (auto &O : M.getSecond())
|
|
MacroRefRanges.push_back({O.StartOffset, O.EndOffset});
|
|
}
|
|
EXPECT_THAT(MacroRefRanges, Contains(Modified.range()));
|
|
}
|
|
}
|
|
|
|
TEST(PreamblePatchTest, OrderingPreserved) {
|
|
llvm::StringLiteral Baseline = "#define BAR(X) X";
|
|
Annotations Modified(R"cpp(
|
|
#define BAR(X, Y) X Y
|
|
#define BAR(X) X
|
|
[[BAR]](int y);
|
|
)cpp");
|
|
|
|
llvm::StringLiteral ExpectedPatch(R"cpp(#line 0 ".*main.cpp"
|
|
#undef BAR
|
|
#line 2
|
|
#define BAR\(X, Y\) X Y
|
|
#undef BAR
|
|
#line 3
|
|
#define BAR\(X\) X
|
|
)cpp");
|
|
EXPECT_THAT(getPreamblePatch(Baseline, Modified.code()),
|
|
MatchesRegex(ExpectedPatch.str()));
|
|
|
|
auto AST = createPatchedAST(Baseline, Modified.code());
|
|
ASSERT_TRUE(AST);
|
|
}
|
|
|
|
TEST(PreamblePatchTest, LocateMacroAtWorks) {
|
|
struct {
|
|
const char *const Baseline;
|
|
const char *const Modified;
|
|
} Cases[] = {
|
|
// Addition of new directive
|
|
{
|
|
"",
|
|
R"cpp(
|
|
#define $def^FOO
|
|
$use^FOO)cpp",
|
|
},
|
|
// Available inside preamble section
|
|
{
|
|
"",
|
|
R"cpp(
|
|
#define $def^FOO
|
|
#undef $use^FOO)cpp",
|
|
},
|
|
// Available after undef, as we don't patch those
|
|
{
|
|
"",
|
|
R"cpp(
|
|
#define $def^FOO
|
|
#undef FOO
|
|
$use^FOO)cpp",
|
|
},
|
|
// Identifier on a different line
|
|
{
|
|
"",
|
|
R"cpp(
|
|
#define \
|
|
$def^FOO
|
|
$use^FOO)cpp",
|
|
},
|
|
// In presence of comment tokens
|
|
{
|
|
"",
|
|
R"cpp(
|
|
#\
|
|
define /* FOO */\
|
|
/* FOO */ $def^FOO
|
|
$use^FOO)cpp",
|
|
},
|
|
// Moved around
|
|
{
|
|
"#define FOO",
|
|
R"cpp(
|
|
#define BAR
|
|
#define $def^FOO
|
|
$use^FOO)cpp",
|
|
},
|
|
};
|
|
for (const auto &Case : Cases) {
|
|
SCOPED_TRACE(Case.Modified);
|
|
llvm::Annotations Modified(Case.Modified);
|
|
auto AST = createPatchedAST(Case.Baseline, Modified.code());
|
|
ASSERT_TRUE(AST);
|
|
|
|
const auto &SM = AST->getSourceManager();
|
|
auto *MacroTok = AST->getTokens().spelledTokenContaining(
|
|
SM.getComposedLoc(SM.getMainFileID(), Modified.point("use")));
|
|
ASSERT_TRUE(MacroTok);
|
|
|
|
auto FoundMacro = locateMacroAt(*MacroTok, AST->getPreprocessor());
|
|
ASSERT_TRUE(FoundMacro);
|
|
EXPECT_THAT(FoundMacro->Name, "FOO");
|
|
|
|
auto MacroLoc = FoundMacro->NameLoc;
|
|
EXPECT_EQ(SM.getFileID(MacroLoc), SM.getMainFileID());
|
|
EXPECT_EQ(SM.getFileOffset(MacroLoc), Modified.point("def"));
|
|
}
|
|
}
|
|
|
|
TEST(PreamblePatchTest, LocateMacroAtDeletion) {
|
|
{
|
|
// We don't patch deleted define directives, make sure we don't crash.
|
|
llvm::StringLiteral Baseline = "#define FOO";
|
|
llvm::Annotations Modified("^FOO");
|
|
|
|
auto AST = createPatchedAST(Baseline, Modified.code());
|
|
ASSERT_TRUE(AST);
|
|
|
|
const auto &SM = AST->getSourceManager();
|
|
auto *MacroTok = AST->getTokens().spelledTokenContaining(
|
|
SM.getComposedLoc(SM.getMainFileID(), Modified.point()));
|
|
ASSERT_TRUE(MacroTok);
|
|
|
|
auto FoundMacro = locateMacroAt(*MacroTok, AST->getPreprocessor());
|
|
ASSERT_TRUE(FoundMacro);
|
|
EXPECT_THAT(FoundMacro->Name, "FOO");
|
|
auto HI =
|
|
getHover(*AST, offsetToPosition(Modified.code(), Modified.point()),
|
|
format::getLLVMStyle(), nullptr);
|
|
ASSERT_TRUE(HI);
|
|
EXPECT_THAT(HI->Definition, testing::IsEmpty());
|
|
}
|
|
|
|
{
|
|
// Offset is valid, but underlying text is different.
|
|
llvm::StringLiteral Baseline = "#define FOO";
|
|
Annotations Modified(R"cpp(#define BAR
|
|
^FOO")cpp");
|
|
|
|
auto AST = createPatchedAST(Baseline, Modified.code());
|
|
ASSERT_TRUE(AST);
|
|
|
|
auto HI = getHover(*AST, Modified.point(), format::getLLVMStyle(), nullptr);
|
|
ASSERT_TRUE(HI);
|
|
EXPECT_THAT(HI->Definition, "#define BAR");
|
|
}
|
|
}
|
|
|
|
MATCHER_P(referenceRangeIs, R, "") { return arg.Loc.range == R; }
|
|
|
|
TEST(PreamblePatchTest, RefsToMacros) {
|
|
struct {
|
|
const char *const Baseline;
|
|
const char *const Modified;
|
|
} Cases[] = {
|
|
// Newly added
|
|
{
|
|
"",
|
|
R"cpp(
|
|
#define ^FOO
|
|
^[[FOO]])cpp",
|
|
},
|
|
// Moved around
|
|
{
|
|
"#define FOO",
|
|
R"cpp(
|
|
#define BAR
|
|
#define ^FOO
|
|
^[[FOO]])cpp",
|
|
},
|
|
// Ref in preamble section
|
|
{
|
|
"",
|
|
R"cpp(
|
|
#define ^FOO
|
|
#undef ^FOO)cpp",
|
|
},
|
|
};
|
|
|
|
for (const auto &Case : Cases) {
|
|
Annotations Modified(Case.Modified);
|
|
auto AST = createPatchedAST("", Modified.code());
|
|
ASSERT_TRUE(AST);
|
|
|
|
const auto &SM = AST->getSourceManager();
|
|
std::vector<Matcher<ReferencesResult::Reference>> ExpectedLocations;
|
|
for (const auto &R : Modified.ranges())
|
|
ExpectedLocations.push_back(referenceRangeIs(R));
|
|
|
|
for (const auto &P : Modified.points()) {
|
|
auto *MacroTok =
|
|
AST->getTokens().spelledTokenContaining(SM.getComposedLoc(
|
|
SM.getMainFileID(),
|
|
llvm::cantFail(positionToOffset(Modified.code(), P))));
|
|
ASSERT_TRUE(MacroTok);
|
|
EXPECT_THAT(findReferences(*AST, P, 0).References,
|
|
testing::ElementsAreArray(ExpectedLocations));
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(TranslatePreamblePatchLocation, Simple) {
|
|
auto TU = TestTU::withHeaderCode(R"cpp(
|
|
#line 3 "main.cpp"
|
|
int foo();)cpp");
|
|
// Presumed line/col needs to be valid in the main file.
|
|
TU.Code = R"cpp(// line 1
|
|
// line 2
|
|
// line 3
|
|
// line 4)cpp";
|
|
TU.Filename = "main.cpp";
|
|
TU.HeaderFilename = "__preamble_patch__.h";
|
|
TU.ImplicitHeaderGuard = false;
|
|
|
|
auto AST = TU.build();
|
|
auto &SM = AST.getSourceManager();
|
|
auto &ND = findDecl(AST, "foo");
|
|
EXPECT_NE(SM.getFileID(ND.getLocation()), SM.getMainFileID());
|
|
|
|
auto TranslatedLoc = translatePreamblePatchLocation(ND.getLocation(), SM);
|
|
auto DecompLoc = SM.getDecomposedLoc(TranslatedLoc);
|
|
EXPECT_EQ(DecompLoc.first, SM.getMainFileID());
|
|
EXPECT_EQ(SM.getLineNumber(DecompLoc.first, DecompLoc.second), 3U);
|
|
}
|
|
|
|
TEST(PreamblePatch, ModifiedBounds) {
|
|
struct {
|
|
const char *const Baseline;
|
|
const char *const Modified;
|
|
} Cases[] = {
|
|
// Size increased
|
|
{
|
|
"",
|
|
R"cpp(
|
|
#define FOO
|
|
FOO)cpp",
|
|
},
|
|
// Stayed same
|
|
{"#define FOO", "#define BAR"},
|
|
// Got smaller
|
|
{
|
|
R"cpp(
|
|
#define FOO
|
|
#undef FOO)cpp",
|
|
"#define FOO"},
|
|
};
|
|
|
|
for (const auto &Case : Cases) {
|
|
auto TU = TestTU::withCode(Case.Baseline);
|
|
auto BaselinePreamble = TU.preamble();
|
|
ASSERT_TRUE(BaselinePreamble);
|
|
|
|
Annotations Modified(Case.Modified);
|
|
TU.Code = Modified.code().str();
|
|
MockFS FS;
|
|
auto PP = PreamblePatch::createFullPatch(testPath(TU.Filename),
|
|
TU.inputs(FS), *BaselinePreamble);
|
|
|
|
IgnoreDiagnostics Diags;
|
|
auto CI = buildCompilerInvocation(TU.inputs(FS), Diags);
|
|
ASSERT_TRUE(CI);
|
|
|
|
const auto ExpectedBounds =
|
|
Lexer::ComputePreamble(Case.Modified, CI->getLangOpts());
|
|
EXPECT_EQ(PP.modifiedBounds().Size, ExpectedBounds.Size);
|
|
EXPECT_EQ(PP.modifiedBounds().PreambleEndsAtStartOfLine,
|
|
ExpectedBounds.PreambleEndsAtStartOfLine);
|
|
}
|
|
}
|
|
|
|
TEST(PreamblePatch, MacroLoc) {
|
|
llvm::StringLiteral Baseline = "\n#define MACRO 12\nint num = MACRO;";
|
|
llvm::StringLiteral Modified = " \n#define MACRO 12\nint num = MACRO;";
|
|
auto AST = createPatchedAST(Baseline, Modified);
|
|
ASSERT_TRUE(AST);
|
|
}
|
|
|
|
TEST(PreamblePatch, NoopWhenNotRequested) {
|
|
llvm::StringLiteral Baseline = "#define M\nint num = M;";
|
|
llvm::StringLiteral Modified = "#define M\n#include <foo.h>\nint num = M;";
|
|
auto TU = TestTU::withCode(Baseline);
|
|
auto BaselinePreamble = TU.preamble();
|
|
ASSERT_TRUE(BaselinePreamble);
|
|
|
|
TU.Code = Modified.str();
|
|
MockFS FS;
|
|
auto PP = PreamblePatch::createMacroPatch(testPath(TU.Filename),
|
|
TU.inputs(FS), *BaselinePreamble);
|
|
EXPECT_TRUE(PP.text().empty());
|
|
}
|
|
|
|
::testing::Matcher<const Diag &>
|
|
withNote(::testing::Matcher<Note> NoteMatcher) {
|
|
return Field(&Diag::Notes, ElementsAre(NoteMatcher));
|
|
}
|
|
MATCHER_P(Diag, Range, "Diag at " + llvm::to_string(Range)) {
|
|
return arg.Range == Range;
|
|
}
|
|
MATCHER_P2(Diag, Range, Name,
|
|
"Diag at " + llvm::to_string(Range) + " = [" + Name + "]") {
|
|
return arg.Range == Range && arg.Name == Name;
|
|
}
|
|
|
|
TEST(PreamblePatch, DiagnosticsFromMainASTAreInRightPlace) {
|
|
{
|
|
Annotations Code("#define FOO");
|
|
// Check with removals from preamble.
|
|
Annotations NewCode("[[x]];/* error-ok */");
|
|
auto AST = createPatchedAST(Code.code(), NewCode.code());
|
|
EXPECT_THAT(AST->getDiagnostics(),
|
|
ElementsAre(Diag(NewCode.range(), "missing_type_specifier")));
|
|
}
|
|
{
|
|
// Check with additions to preamble.
|
|
Annotations Code("#define FOO");
|
|
Annotations NewCode(R"(
|
|
#define FOO
|
|
#define BAR
|
|
[[x]];/* error-ok */)");
|
|
auto AST = createPatchedAST(Code.code(), NewCode.code());
|
|
EXPECT_THAT(AST->getDiagnostics(),
|
|
ElementsAre(Diag(NewCode.range(), "missing_type_specifier")));
|
|
}
|
|
}
|
|
|
|
TEST(PreamblePatch, DiagnosticsToPreamble) {
|
|
Config Cfg;
|
|
Cfg.Diagnostics.UnusedIncludes = Config::IncludesPolicy::Strict;
|
|
Cfg.Diagnostics.MissingIncludes = Config::IncludesPolicy::Strict;
|
|
WithContextValue WithCfg(Config::Key, std::move(Cfg));
|
|
|
|
llvm::StringMap<std::string> AdditionalFiles;
|
|
AdditionalFiles["foo.h"] = "#pragma once";
|
|
AdditionalFiles["bar.h"] = "#pragma once";
|
|
{
|
|
Annotations Code(R"(
|
|
// Test comment
|
|
[[#include "foo.h"]])");
|
|
// Check with removals from preamble.
|
|
Annotations NewCode(R"([[# include "foo.h"]])");
|
|
auto AST = createPatchedAST(Code.code(), NewCode.code(), AdditionalFiles);
|
|
EXPECT_THAT(AST->getDiagnostics(),
|
|
ElementsAre(Diag(NewCode.range(), "unused-includes")));
|
|
}
|
|
{
|
|
// Check with additions to preamble.
|
|
Annotations Code(R"(
|
|
// Test comment
|
|
[[#include "foo.h"]])");
|
|
Annotations NewCode(R"(
|
|
$bar[[#include "bar.h"]]
|
|
// Test comment
|
|
$foo[[#include "foo.h"]])");
|
|
auto AST = createPatchedAST(Code.code(), NewCode.code(), AdditionalFiles);
|
|
EXPECT_THAT(
|
|
AST->getDiagnostics(),
|
|
UnorderedElementsAre(Diag(NewCode.range("bar"), "unused-includes"),
|
|
Diag(NewCode.range("foo"), "unused-includes")));
|
|
}
|
|
{
|
|
Annotations Code("#define [[FOO]] 1\n");
|
|
// Check ranges for notes.
|
|
// This also makes sure we don't generate missing-include diagnostics
|
|
// because macros are redefined in preamble-patch.
|
|
Annotations NewCode(R"(#define BARXYZ 1
|
|
#define $foo1[[FOO]] 1
|
|
void foo();
|
|
#define $foo2[[FOO]] 2)");
|
|
auto AST = createPatchedAST(Code.code(), NewCode.code(), AdditionalFiles);
|
|
EXPECT_THAT(
|
|
AST->getDiagnostics(),
|
|
ElementsAre(AllOf(Diag(NewCode.range("foo2"), "-Wmacro-redefined"),
|
|
withNote(Diag(NewCode.range("foo1"))))));
|
|
}
|
|
}
|
|
|
|
TEST(PreamblePatch, TranslatesDiagnosticsInPreamble) {
|
|
{
|
|
// Check with additions to preamble.
|
|
Annotations Code("#include [[<foo>]]");
|
|
Annotations NewCode(R"(
|
|
#define BAR
|
|
#include [[<foo>]])");
|
|
auto AST = createPatchedAST(Code.code(), NewCode.code());
|
|
EXPECT_THAT(AST->getDiagnostics(),
|
|
ElementsAre(Diag(NewCode.range(), "pp_file_not_found")));
|
|
}
|
|
{
|
|
// Check with removals from preamble.
|
|
Annotations Code(R"(
|
|
#define BAR
|
|
#include [[<foo>]])");
|
|
Annotations NewCode("#include [[<foo>]]");
|
|
auto AST = createPatchedAST(Code.code(), NewCode.code());
|
|
EXPECT_THAT(AST->getDiagnostics(),
|
|
ElementsAre(Diag(NewCode.range(), "pp_file_not_found")));
|
|
}
|
|
{
|
|
// Drop line with diags.
|
|
Annotations Code("#include [[<foo>]]");
|
|
Annotations NewCode("#define BAR\n#define BAZ\n");
|
|
auto AST = createPatchedAST(Code.code(), NewCode.code());
|
|
EXPECT_THAT(AST->getDiagnostics(), IsEmpty());
|
|
}
|
|
{
|
|
// Picks closest line in case of multiple alternatives.
|
|
Annotations Code("#include [[<foo>]]");
|
|
Annotations NewCode(R"(
|
|
#define BAR
|
|
#include [[<foo>]]
|
|
#define BAR
|
|
#include <foo>)");
|
|
auto AST = createPatchedAST(Code.code(), NewCode.code());
|
|
EXPECT_THAT(AST->getDiagnostics(),
|
|
ElementsAre(Diag(NewCode.range(), "pp_file_not_found")));
|
|
}
|
|
{
|
|
// Drop diag if line spelling has changed.
|
|
Annotations Code("#include [[<foo>]]");
|
|
Annotations NewCode(" # include <foo>");
|
|
auto AST = createPatchedAST(Code.code(), NewCode.code());
|
|
EXPECT_THAT(AST->getDiagnostics(), IsEmpty());
|
|
}
|
|
{
|
|
// Multiple lines.
|
|
Annotations Code(R"(
|
|
#define BAR
|
|
#include [[<fo\
|
|
o>]])");
|
|
Annotations NewCode(R"(#include [[<fo\
|
|
o>]])");
|
|
auto AST = createPatchedAST(Code.code(), NewCode.code());
|
|
EXPECT_THAT(AST->getDiagnostics(),
|
|
ElementsAre(Diag(NewCode.range(), "pp_file_not_found")));
|
|
}
|
|
{
|
|
// Multiple lines with change.
|
|
Annotations Code(R"(
|
|
#define BAR
|
|
#include <fox>
|
|
#include [[<fo\
|
|
o>]])");
|
|
Annotations NewCode(R"(#include <fo\
|
|
x>)");
|
|
auto AST = createPatchedAST(Code.code(), NewCode.code());
|
|
EXPECT_THAT(AST->getDiagnostics(), IsEmpty());
|
|
}
|
|
{
|
|
// Preserves notes.
|
|
Annotations Code(R"(
|
|
#define $note[[BAR]] 1
|
|
#define $main[[BAR]] 2)");
|
|
Annotations NewCode(R"(
|
|
#define BAZ 0
|
|
#define $note[[BAR]] 1
|
|
#define BAZ 0
|
|
#define $main[[BAR]] 2)");
|
|
auto AST = createPatchedAST(Code.code(), NewCode.code());
|
|
EXPECT_THAT(
|
|
AST->getDiagnostics(),
|
|
ElementsAre(AllOf(Diag(NewCode.range("main"), "-Wmacro-redefined"),
|
|
withNote(Diag(NewCode.range("note"))))));
|
|
}
|
|
{
|
|
// Preserves diag without note.
|
|
Annotations Code(R"(
|
|
#define $note[[BAR]] 1
|
|
#define $main[[BAR]] 2)");
|
|
Annotations NewCode(R"(
|
|
#define $main[[BAR]] 2)");
|
|
auto AST = createPatchedAST(Code.code(), NewCode.code());
|
|
EXPECT_THAT(
|
|
AST->getDiagnostics(),
|
|
ElementsAre(AllOf(Diag(NewCode.range("main"), "-Wmacro-redefined"),
|
|
Field(&Diag::Notes, IsEmpty()))));
|
|
}
|
|
{
|
|
// Make sure orphaned notes are not promoted to diags.
|
|
Annotations Code(R"(
|
|
#define $note[[BAR]] 1
|
|
#define $main[[BAR]] 2)");
|
|
Annotations NewCode(R"(
|
|
#define BAZ 0
|
|
#define BAR 1)");
|
|
auto AST = createPatchedAST(Code.code(), NewCode.code());
|
|
EXPECT_THAT(AST->getDiagnostics(), IsEmpty());
|
|
}
|
|
{
|
|
Annotations Code(R"(
|
|
#ifndef FOO
|
|
#define FOO
|
|
void foo();
|
|
#endif)");
|
|
// This code will emit a diagnostic for unterminated #ifndef (as stale
|
|
// preamble has the conditional but main file doesn't terminate it).
|
|
// We shouldn't emit any diagnotiscs (and shouldn't crash).
|
|
Annotations NewCode("");
|
|
auto AST = createPatchedAST(Code.code(), NewCode.code());
|
|
EXPECT_THAT(AST->getDiagnostics(), IsEmpty());
|
|
}
|
|
{
|
|
Annotations Code(R"(
|
|
#ifndef FOO
|
|
#define FOO
|
|
void foo();
|
|
#endif)");
|
|
// This code will emit a diagnostic for unterminated #ifndef (as stale
|
|
// preamble has the conditional but main file doesn't terminate it).
|
|
// We shouldn't emit any diagnotiscs (and shouldn't crash).
|
|
// FIXME: Patch/ignore diagnostics in such cases.
|
|
Annotations NewCode(R"(
|
|
i[[nt]] xyz;
|
|
)");
|
|
auto AST = createPatchedAST(Code.code(), NewCode.code());
|
|
EXPECT_THAT(
|
|
AST->getDiagnostics(),
|
|
ElementsAre(Diag(NewCode.range(), "pp_unterminated_conditional")));
|
|
}
|
|
}
|
|
|
|
MATCHER_P2(Mark, Range, Text, "") {
|
|
return std::tie(arg.Rng, arg.Trivia) == std::tie(Range, Text);
|
|
}
|
|
|
|
TEST(PreamblePatch, MacroAndMarkHandling) {
|
|
{
|
|
Annotations Code(R"cpp(
|
|
#ifndef FOO
|
|
#define FOO
|
|
// Some comments
|
|
#pragma mark XX
|
|
#define BAR
|
|
|
|
#endif)cpp");
|
|
Annotations NewCode(R"cpp(
|
|
#ifndef FOO
|
|
#define FOO
|
|
#define BAR
|
|
#pragma $x[[mark XX
|
|
]]
|
|
#pragma $y[[mark YY
|
|
]]
|
|
#define BAZ
|
|
|
|
#endif)cpp");
|
|
auto AST = createPatchedAST(Code.code(), NewCode.code());
|
|
EXPECT_THAT(AST->getMacros().Names.keys(),
|
|
UnorderedElementsAreArray({"FOO", "BAR", "BAZ"}));
|
|
EXPECT_THAT(AST->getMarks(),
|
|
UnorderedElementsAre(Mark(NewCode.range("x"), " XX"),
|
|
Mark(NewCode.range("y"), " YY")));
|
|
}
|
|
}
|
|
|
|
TEST(PreamblePatch, PatchFileEntry) {
|
|
Annotations Code(R"cpp(#define FOO)cpp");
|
|
Annotations NewCode(R"cpp(
|
|
#define BAR
|
|
#define FOO)cpp");
|
|
{
|
|
auto AST = createPatchedAST(Code.code(), Code.code());
|
|
EXPECT_EQ(
|
|
PreamblePatch::getPatchEntry(AST->tuPath(), AST->getSourceManager()),
|
|
nullptr);
|
|
}
|
|
{
|
|
auto AST = createPatchedAST(Code.code(), NewCode.code());
|
|
auto FE =
|
|
PreamblePatch::getPatchEntry(AST->tuPath(), AST->getSourceManager());
|
|
ASSERT_NE(FE, std::nullopt);
|
|
EXPECT_THAT(FE->getName().str(),
|
|
testing::EndsWith(PreamblePatch::HeaderName.str()));
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace clangd
|
|
} // namespace clang
|