1 | //===--- PreambleTests.cpp --------------------------------------*- C++ -*-===// |
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 "Annotations.h" |
10 | #include "Compiler.h" |
11 | #include "Config.h" |
12 | #include "Diagnostics.h" |
13 | #include "Headers.h" |
14 | #include "Hover.h" |
15 | #include "ParsedAST.h" |
16 | #include "Preamble.h" |
17 | #include "Protocol.h" |
18 | #include "SourceCode.h" |
19 | #include "TestFS.h" |
20 | #include "TestTU.h" |
21 | #include "XRefs.h" |
22 | #include "support/Context.h" |
23 | #include "clang/Basic/SourceManager.h" |
24 | #include "clang/Format/Format.h" |
25 | #include "clang/Frontend/FrontendActions.h" |
26 | #include "clang/Frontend/PrecompiledPreamble.h" |
27 | #include "llvm/ADT/StringMap.h" |
28 | #include "llvm/ADT/StringRef.h" |
29 | #include "llvm/Support/Error.h" |
30 | #include "llvm/Support/MemoryBuffer.h" |
31 | #include "llvm/Support/ScopedPrinter.h" |
32 | #include "llvm/Support/VirtualFileSystem.h" |
33 | #include "llvm/Testing/Annotations/Annotations.h" |
34 | #include "gmock/gmock.h" |
35 | #include "gtest/gtest-matchers.h" |
36 | #include "gtest/gtest.h" |
37 | #include <memory> |
38 | #include <optional> |
39 | #include <string> |
40 | #include <utility> |
41 | #include <vector> |
42 | |
43 | using testing::AllOf; |
44 | using testing::Contains; |
45 | using testing::ElementsAre; |
46 | using testing::Field; |
47 | using testing::IsEmpty; |
48 | using testing::Matcher; |
49 | using testing::MatchesRegex; |
50 | using testing::UnorderedElementsAre; |
51 | using testing::UnorderedElementsAreArray; |
52 | |
53 | namespace clang { |
54 | namespace clangd { |
55 | namespace { |
56 | |
57 | MATCHER_P2(Distance, File, D, "" ) { |
58 | return arg.first() == File && arg.second == D; |
59 | } |
60 | |
61 | // Builds a preamble for BaselineContents, patches it for ModifiedContents and |
62 | // returns the includes in the patch. |
63 | IncludeStructure |
64 | collectPatchedIncludes(llvm::StringRef ModifiedContents, |
65 | llvm::StringRef BaselineContents, |
66 | llvm::StringRef MainFileName = "main.cpp" ) { |
67 | MockFS FS; |
68 | auto TU = TestTU::withCode(Code: BaselineContents); |
69 | TU.Filename = MainFileName.str(); |
70 | // ms-compatibility changes meaning of #import, make sure it is turned off. |
71 | TU.ExtraArgs = {"-fno-ms-compatibility" }; |
72 | auto BaselinePreamble = TU.preamble(); |
73 | // Create the patch. |
74 | TU.Code = ModifiedContents.str(); |
75 | auto PI = TU.inputs(FS); |
76 | auto PP = PreamblePatch::createFullPatch(FileName: testPath(File: TU.Filename), Modified: PI, |
77 | Baseline: *BaselinePreamble); |
78 | // Collect patch contents. |
79 | IgnoreDiagnostics Diags; |
80 | auto CI = buildCompilerInvocation(Inputs: PI, D&: Diags); |
81 | PP.apply(CI&: *CI); |
82 | // Run preprocessor over the modified contents with patched Invocation. We |
83 | // provide a preamble and trim contents to ensure only the implicit header |
84 | // introduced by the patch is parsed and nothing else. |
85 | // We don't run PP directly over the patch cotents to test production |
86 | // behaviour. |
87 | auto Bounds = Lexer::ComputePreamble(Buffer: ModifiedContents, LangOpts: CI->getLangOpts()); |
88 | auto Clang = |
89 | prepareCompilerInstance(std::move(CI), &BaselinePreamble->Preamble, |
90 | MainFile: llvm::MemoryBuffer::getMemBufferCopy( |
91 | InputData: ModifiedContents.slice(Start: 0, End: Bounds.Size).str()), |
92 | PI.TFS->view(CWD: PI.CompileCommand.Directory), Diags); |
93 | PreprocessOnlyAction Action; |
94 | if (!Action.BeginSourceFile(CI&: *Clang, Input: Clang->getFrontendOpts().Inputs[0])) { |
95 | ADD_FAILURE() << "failed begin source file" ; |
96 | return {}; |
97 | } |
98 | IncludeStructure Includes; |
99 | Includes.collect(CI: *Clang); |
100 | if (llvm::Error Err = Action.Execute()) { |
101 | ADD_FAILURE() << "failed to execute action: " << std::move(Err); |
102 | return {}; |
103 | } |
104 | Action.EndSourceFile(); |
105 | return Includes; |
106 | } |
107 | |
108 | // Check preamble lexing logic by building an empty preamble and patching it |
109 | // with all the contents. |
110 | TEST(PreamblePatchTest, IncludeParsing) { |
111 | // We expect any line with a point to show up in the patch. |
112 | llvm::StringRef Cases[] = { |
113 | // Only preamble |
114 | R"cpp(^#include "a.h")cpp" , |
115 | // Both preamble and mainfile |
116 | R"cpp( |
117 | ^#include "a.h" |
118 | garbage, finishes preamble |
119 | #include "a.h")cpp" , |
120 | // Mixed directives |
121 | R"cpp( |
122 | ^#include "a.h" |
123 | #pragma directive |
124 | // some comments |
125 | ^#include_next <a.h> |
126 | #ifdef skipped |
127 | ^#import "a.h" |
128 | #endif)cpp" , |
129 | // Broken directives |
130 | R"cpp( |
131 | #include "a |
132 | ^#include "a.h" |
133 | #include <b |
134 | ^#include <b.h>)cpp" , |
135 | // Directive is not part of preamble if it is not the token immediately |
136 | // followed by the hash (#). |
137 | R"cpp( |
138 | ^#include "a.h" |
139 | #/**/include <b.h>)cpp" , |
140 | }; |
141 | |
142 | for (const auto &Case : Cases) { |
143 | Annotations Test(Case); |
144 | const auto Code = Test.code(); |
145 | SCOPED_TRACE(Code); |
146 | |
147 | auto Includes = |
148 | collectPatchedIncludes(ModifiedContents: Code, /*BaselineContents=*/"" ).MainFileIncludes; |
149 | auto Points = Test.points(); |
150 | ASSERT_EQ(Includes.size(), Points.size()); |
151 | for (size_t I = 0, E = Includes.size(); I != E; ++I) |
152 | EXPECT_EQ(Includes[I].HashLine, Points[I].line); |
153 | } |
154 | } |
155 | |
156 | TEST(PreamblePatchTest, ContainsNewIncludes) { |
157 | constexpr llvm::StringLiteral BaselineContents = R"cpp( |
158 | #include <a.h> |
159 | #include <b.h> // This will be removed |
160 | #include <c.h> |
161 | )cpp" ; |
162 | constexpr llvm::StringLiteral ModifiedContents = R"cpp( |
163 | #include <a.h> |
164 | #include <c.h> // This has changed a line. |
165 | #include <c.h> // This is a duplicate. |
166 | #include <d.h> // This is newly introduced. |
167 | )cpp" ; |
168 | auto Includes = collectPatchedIncludes(ModifiedContents, BaselineContents) |
169 | .MainFileIncludes; |
170 | EXPECT_THAT(Includes, ElementsAre(AllOf(Field(&Inclusion::Written, "<d.h>" ), |
171 | Field(&Inclusion::HashLine, 4)))); |
172 | } |
173 | |
174 | TEST(PreamblePatchTest, MainFileIsEscaped) { |
175 | auto Includes = collectPatchedIncludes(ModifiedContents: "#include <a.h>" , BaselineContents: "" , MainFileName: "file\"name.cpp" ) |
176 | .MainFileIncludes; |
177 | EXPECT_THAT(Includes, ElementsAre(AllOf(Field(&Inclusion::Written, "<a.h>" ), |
178 | Field(&Inclusion::HashLine, 0)))); |
179 | } |
180 | |
181 | TEST(PreamblePatchTest, PatchesPreambleIncludes) { |
182 | MockFS FS; |
183 | IgnoreDiagnostics Diags; |
184 | auto TU = TestTU::withCode(Code: R"cpp( |
185 | #include "a.h" // IWYU pragma: keep |
186 | #include "c.h" |
187 | #ifdef FOO |
188 | #include "d.h" |
189 | #endif |
190 | )cpp" ); |
191 | TU.AdditionalFiles["a.h" ] = "#include \"b.h\"" ; |
192 | TU.AdditionalFiles["b.h" ] = "" ; |
193 | TU.AdditionalFiles["c.h" ] = "" ; |
194 | auto PI = TU.inputs(FS); |
195 | auto BaselinePreamble = buildPreamble( |
196 | FileName: TU.Filename, CI: *buildCompilerInvocation(Inputs: PI, D&: Diags), Inputs: PI, StoreInMemory: true, PreambleCallback: nullptr); |
197 | // We drop c.h from modified and add a new header. Since the latter is patched |
198 | // we should only get a.h in preamble includes. d.h shouldn't be part of the |
199 | // preamble, as it's coming from a disabled region. |
200 | TU.Code = R"cpp( |
201 | #include "a.h" |
202 | #include "b.h" |
203 | #ifdef FOO |
204 | #include "d.h" |
205 | #endif |
206 | )cpp" ; |
207 | auto PP = PreamblePatch::createFullPatch(FileName: testPath(File: TU.Filename), Modified: TU.inputs(FS), |
208 | Baseline: *BaselinePreamble); |
209 | // Only a.h should exists in the preamble, as c.h has been dropped and b.h was |
210 | // newly introduced. |
211 | EXPECT_THAT( |
212 | PP.preambleIncludes(), |
213 | ElementsAre(AllOf( |
214 | Field(&Inclusion::Written, "\"a.h\"" ), |
215 | Field(&Inclusion::Resolved, testPath("a.h" )), |
216 | Field(&Inclusion::HeaderID, testing::Not(testing::Eq(std::nullopt))), |
217 | Field(&Inclusion::FileKind, SrcMgr::CharacteristicKind::C_User)))); |
218 | } |
219 | |
220 | std::optional<ParsedAST> |
221 | createPatchedAST(llvm::StringRef Baseline, llvm::StringRef Modified, |
222 | llvm::StringMap<std::string> AdditionalFiles = {}) { |
223 | auto TU = TestTU::withCode(Code: Baseline); |
224 | TU.AdditionalFiles = std::move(AdditionalFiles); |
225 | auto BaselinePreamble = TU.preamble(); |
226 | if (!BaselinePreamble) { |
227 | ADD_FAILURE() << "Failed to build baseline preamble" ; |
228 | return std::nullopt; |
229 | } |
230 | |
231 | IgnoreDiagnostics Diags; |
232 | MockFS FS; |
233 | TU.Code = Modified.str(); |
234 | auto CI = buildCompilerInvocation(Inputs: TU.inputs(FS), D&: Diags); |
235 | if (!CI) { |
236 | ADD_FAILURE() << "Failed to build compiler invocation" ; |
237 | return std::nullopt; |
238 | } |
239 | return ParsedAST::build(Filename: testPath(File: TU.Filename), Inputs: TU.inputs(FS), CI: std::move(CI), |
240 | CompilerInvocationDiags: {}, Preamble: BaselinePreamble); |
241 | } |
242 | |
243 | std::string getPreamblePatch(llvm::StringRef Baseline, |
244 | llvm::StringRef Modified) { |
245 | auto BaselinePreamble = TestTU::withCode(Code: Baseline).preamble(); |
246 | if (!BaselinePreamble) { |
247 | ADD_FAILURE() << "Failed to build baseline preamble" ; |
248 | return "" ; |
249 | } |
250 | MockFS FS; |
251 | auto TU = TestTU::withCode(Code: Modified); |
252 | return PreamblePatch::createFullPatch(FileName: testPath(File: "main.cpp" ), Modified: TU.inputs(FS), |
253 | Baseline: *BaselinePreamble) |
254 | .text() |
255 | .str(); |
256 | } |
257 | |
258 | TEST(PreamblePatchTest, IncludesArePreserved) { |
259 | llvm::StringLiteral Baseline = R"(//error-ok |
260 | #include <foo> |
261 | #include <bar> |
262 | )" ; |
263 | llvm::StringLiteral Modified = R"(//error-ok |
264 | #include <foo> |
265 | #include <bar> |
266 | #define FOO)" ; |
267 | |
268 | auto Includes = createPatchedAST(Baseline, Modified: Modified.str()) |
269 | ->getIncludeStructure() |
270 | .MainFileIncludes; |
271 | EXPECT_TRUE(!Includes.empty()); |
272 | EXPECT_EQ(Includes, TestTU::withCode(Baseline) |
273 | .build() |
274 | .getIncludeStructure() |
275 | .MainFileIncludes); |
276 | } |
277 | |
278 | TEST(PreamblePatchTest, Define) { |
279 | // BAR should be defined while parsing the AST. |
280 | struct { |
281 | const char *const Contents; |
282 | const char *const ExpectedPatch; |
283 | } Cases[] = { |
284 | { |
285 | .Contents: R"cpp( |
286 | #define BAR |
287 | [[BAR]])cpp" , |
288 | .ExpectedPatch: R"cpp(#line 0 ".*main.cpp" |
289 | #undef BAR |
290 | #line 2 |
291 | #define BAR |
292 | )cpp" , |
293 | }, |
294 | // multiline macro |
295 | { |
296 | .Contents: R"cpp( |
297 | #define BAR \ |
298 | |
299 | [[BAR]])cpp" , |
300 | .ExpectedPatch: R"cpp(#line 0 ".*main.cpp" |
301 | #undef BAR |
302 | #line 2 |
303 | #define BAR |
304 | )cpp" , |
305 | }, |
306 | // multiline macro |
307 | { |
308 | .Contents: R"cpp( |
309 | #define \ |
310 | BAR |
311 | [[BAR]])cpp" , |
312 | .ExpectedPatch: R"cpp(#line 0 ".*main.cpp" |
313 | #undef BAR |
314 | #line 3 |
315 | #define BAR |
316 | )cpp" , |
317 | }, |
318 | }; |
319 | |
320 | for (const auto &Case : Cases) { |
321 | SCOPED_TRACE(Case.Contents); |
322 | llvm::Annotations Modified(Case.Contents); |
323 | EXPECT_THAT(getPreamblePatch("" , Modified.code()), |
324 | MatchesRegex(Case.ExpectedPatch)); |
325 | |
326 | auto AST = createPatchedAST(Baseline: "" , Modified: Modified.code()); |
327 | ASSERT_TRUE(AST); |
328 | std::vector<llvm::Annotations::Range> MacroRefRanges; |
329 | for (auto &M : AST->getMacros().MacroRefs) { |
330 | for (auto &O : M.getSecond()) |
331 | MacroRefRanges.push_back(x: {.Begin: O.StartOffset, .End: O.EndOffset}); |
332 | } |
333 | EXPECT_THAT(MacroRefRanges, Contains(Modified.range())); |
334 | } |
335 | } |
336 | |
337 | TEST(PreamblePatchTest, OrderingPreserved) { |
338 | llvm::StringLiteral Baseline = "#define BAR(X) X" ; |
339 | Annotations Modified(R"cpp( |
340 | #define BAR(X, Y) X Y |
341 | #define BAR(X) X |
342 | [[BAR]](int y); |
343 | )cpp" ); |
344 | |
345 | llvm::StringLiteral ExpectedPatch(R"cpp(#line 0 ".*main.cpp" |
346 | #undef BAR |
347 | #line 2 |
348 | #define BAR\(X, Y\) X Y |
349 | #undef BAR |
350 | #line 3 |
351 | #define BAR\(X\) X |
352 | )cpp" ); |
353 | EXPECT_THAT(getPreamblePatch(Baseline, Modified.code()), |
354 | MatchesRegex(ExpectedPatch.str())); |
355 | |
356 | auto AST = createPatchedAST(Baseline, Modified: Modified.code()); |
357 | ASSERT_TRUE(AST); |
358 | } |
359 | |
360 | TEST(PreamblePatchTest, LocateMacroAtWorks) { |
361 | struct { |
362 | const char *const Baseline; |
363 | const char *const Modified; |
364 | } Cases[] = { |
365 | // Addition of new directive |
366 | { |
367 | .Baseline: "" , |
368 | .Modified: R"cpp( |
369 | #define $def^FOO |
370 | $use^FOO)cpp" , |
371 | }, |
372 | // Available inside preamble section |
373 | { |
374 | .Baseline: "" , |
375 | .Modified: R"cpp( |
376 | #define $def^FOO |
377 | #undef $use^FOO)cpp" , |
378 | }, |
379 | // Available after undef, as we don't patch those |
380 | { |
381 | .Baseline: "" , |
382 | .Modified: R"cpp( |
383 | #define $def^FOO |
384 | #undef FOO |
385 | $use^FOO)cpp" , |
386 | }, |
387 | // Identifier on a different line |
388 | { |
389 | .Baseline: "" , |
390 | .Modified: R"cpp( |
391 | #define \ |
392 | $def^FOO |
393 | $use^FOO)cpp" , |
394 | }, |
395 | // In presence of comment tokens |
396 | { |
397 | .Baseline: "" , |
398 | .Modified: R"cpp( |
399 | #\ |
400 | define /* FOO */\ |
401 | /* FOO */ $def^FOO |
402 | $use^FOO)cpp" , |
403 | }, |
404 | // Moved around |
405 | { |
406 | .Baseline: "#define FOO" , |
407 | .Modified: R"cpp( |
408 | #define BAR |
409 | #define $def^FOO |
410 | $use^FOO)cpp" , |
411 | }, |
412 | }; |
413 | for (const auto &Case : Cases) { |
414 | SCOPED_TRACE(Case.Modified); |
415 | llvm::Annotations Modified(Case.Modified); |
416 | auto AST = createPatchedAST(Baseline: Case.Baseline, Modified: Modified.code()); |
417 | ASSERT_TRUE(AST); |
418 | |
419 | const auto &SM = AST->getSourceManager(); |
420 | auto *MacroTok = AST->getTokens().spelledTokenContaining( |
421 | Loc: SM.getComposedLoc(FID: SM.getMainFileID(), Offset: Modified.point(Name: "use" ))); |
422 | ASSERT_TRUE(MacroTok); |
423 | |
424 | auto FoundMacro = locateMacroAt(SpelledTok: *MacroTok, PP&: AST->getPreprocessor()); |
425 | ASSERT_TRUE(FoundMacro); |
426 | EXPECT_THAT(FoundMacro->Name, "FOO" ); |
427 | |
428 | auto MacroLoc = FoundMacro->NameLoc; |
429 | EXPECT_EQ(SM.getFileID(MacroLoc), SM.getMainFileID()); |
430 | EXPECT_EQ(SM.getFileOffset(MacroLoc), Modified.point("def" )); |
431 | } |
432 | } |
433 | |
434 | TEST(PreamblePatchTest, LocateMacroAtDeletion) { |
435 | { |
436 | // We don't patch deleted define directives, make sure we don't crash. |
437 | llvm::StringLiteral Baseline = "#define FOO" ; |
438 | llvm::Annotations Modified("^FOO" ); |
439 | |
440 | auto AST = createPatchedAST(Baseline, Modified: Modified.code()); |
441 | ASSERT_TRUE(AST); |
442 | |
443 | const auto &SM = AST->getSourceManager(); |
444 | auto *MacroTok = AST->getTokens().spelledTokenContaining( |
445 | Loc: SM.getComposedLoc(FID: SM.getMainFileID(), Offset: Modified.point())); |
446 | ASSERT_TRUE(MacroTok); |
447 | |
448 | auto FoundMacro = locateMacroAt(SpelledTok: *MacroTok, PP&: AST->getPreprocessor()); |
449 | ASSERT_TRUE(FoundMacro); |
450 | EXPECT_THAT(FoundMacro->Name, "FOO" ); |
451 | auto HI = |
452 | getHover(AST&: *AST, Pos: offsetToPosition(Code: Modified.code(), Offset: Modified.point()), |
453 | Style: format::getLLVMStyle(), Index: nullptr); |
454 | ASSERT_TRUE(HI); |
455 | EXPECT_THAT(HI->Definition, testing::IsEmpty()); |
456 | } |
457 | |
458 | { |
459 | // Offset is valid, but underlying text is different. |
460 | llvm::StringLiteral Baseline = "#define FOO" ; |
461 | Annotations Modified(R"cpp(#define BAR |
462 | ^FOO")cpp" ); |
463 | |
464 | auto AST = createPatchedAST(Baseline, Modified: Modified.code()); |
465 | ASSERT_TRUE(AST); |
466 | |
467 | auto HI = getHover(AST&: *AST, Pos: Modified.point(), Style: format::getLLVMStyle(), Index: nullptr); |
468 | ASSERT_TRUE(HI); |
469 | EXPECT_THAT(HI->Definition, "#define BAR" ); |
470 | } |
471 | } |
472 | |
473 | MATCHER_P(referenceRangeIs, R, "" ) { return arg.Loc.range == R; } |
474 | |
475 | TEST(PreamblePatchTest, RefsToMacros) { |
476 | struct { |
477 | const char *const Baseline; |
478 | const char *const Modified; |
479 | } Cases[] = { |
480 | // Newly added |
481 | { |
482 | .Baseline: "" , |
483 | .Modified: R"cpp( |
484 | #define ^FOO |
485 | ^[[FOO]])cpp" , |
486 | }, |
487 | // Moved around |
488 | { |
489 | .Baseline: "#define FOO" , |
490 | .Modified: R"cpp( |
491 | #define BAR |
492 | #define ^FOO |
493 | ^[[FOO]])cpp" , |
494 | }, |
495 | // Ref in preamble section |
496 | { |
497 | .Baseline: "" , |
498 | .Modified: R"cpp( |
499 | #define ^FOO |
500 | #undef ^FOO)cpp" , |
501 | }, |
502 | }; |
503 | |
504 | for (const auto &Case : Cases) { |
505 | Annotations Modified(Case.Modified); |
506 | auto AST = createPatchedAST(Baseline: "" , Modified: Modified.code()); |
507 | ASSERT_TRUE(AST); |
508 | |
509 | const auto &SM = AST->getSourceManager(); |
510 | std::vector<Matcher<ReferencesResult::Reference>> ExpectedLocations; |
511 | for (const auto &R : Modified.ranges()) |
512 | ExpectedLocations.push_back(x: referenceRangeIs(gmock_p0: R)); |
513 | |
514 | for (const auto &P : Modified.points()) { |
515 | auto *MacroTok = |
516 | AST->getTokens().spelledTokenContaining(Loc: SM.getComposedLoc( |
517 | FID: SM.getMainFileID(), |
518 | Offset: llvm::cantFail(ValOrErr: positionToOffset(Code: Modified.code(), P)))); |
519 | ASSERT_TRUE(MacroTok); |
520 | EXPECT_THAT(findReferences(*AST, P, 0).References, |
521 | testing::ElementsAreArray(ExpectedLocations)); |
522 | } |
523 | } |
524 | } |
525 | |
526 | TEST(TranslatePreamblePatchLocation, Simple) { |
527 | auto TU = TestTU::withHeaderCode(HeaderCode: R"cpp( |
528 | #line 3 "main.cpp" |
529 | int foo();)cpp" ); |
530 | // Presumed line/col needs to be valid in the main file. |
531 | TU.Code = R"cpp(// line 1 |
532 | // line 2 |
533 | // line 3 |
534 | // line 4)cpp" ; |
535 | TU.Filename = "main.cpp" ; |
536 | TU.HeaderFilename = "__preamble_patch__.h" ; |
537 | TU.ImplicitHeaderGuard = false; |
538 | |
539 | auto AST = TU.build(); |
540 | auto &SM = AST.getSourceManager(); |
541 | auto &ND = findDecl(AST, QName: "foo" ); |
542 | EXPECT_NE(SM.getFileID(ND.getLocation()), SM.getMainFileID()); |
543 | |
544 | auto TranslatedLoc = translatePreamblePatchLocation(ND.getLocation(), SM); |
545 | auto DecompLoc = SM.getDecomposedLoc(Loc: TranslatedLoc); |
546 | EXPECT_EQ(DecompLoc.first, SM.getMainFileID()); |
547 | EXPECT_EQ(SM.getLineNumber(DecompLoc.first, DecompLoc.second), 3U); |
548 | } |
549 | |
550 | TEST(PreamblePatch, ModifiedBounds) { |
551 | struct { |
552 | const char *const Baseline; |
553 | const char *const Modified; |
554 | } Cases[] = { |
555 | // Size increased |
556 | { |
557 | .Baseline: "" , |
558 | .Modified: R"cpp( |
559 | #define FOO |
560 | FOO)cpp" , |
561 | }, |
562 | // Stayed same |
563 | {.Baseline: "#define FOO" , .Modified: "#define BAR" }, |
564 | // Got smaller |
565 | { |
566 | .Baseline: R"cpp( |
567 | #define FOO |
568 | #undef FOO)cpp" , |
569 | .Modified: "#define FOO" }, |
570 | }; |
571 | |
572 | for (const auto &Case : Cases) { |
573 | auto TU = TestTU::withCode(Code: Case.Baseline); |
574 | auto BaselinePreamble = TU.preamble(); |
575 | ASSERT_TRUE(BaselinePreamble); |
576 | |
577 | Annotations Modified(Case.Modified); |
578 | TU.Code = Modified.code().str(); |
579 | MockFS FS; |
580 | auto PP = PreamblePatch::createFullPatch(FileName: testPath(File: TU.Filename), |
581 | Modified: TU.inputs(FS), Baseline: *BaselinePreamble); |
582 | |
583 | IgnoreDiagnostics Diags; |
584 | auto CI = buildCompilerInvocation(Inputs: TU.inputs(FS), D&: Diags); |
585 | ASSERT_TRUE(CI); |
586 | |
587 | const auto ExpectedBounds = |
588 | Lexer::ComputePreamble(Buffer: Case.Modified, LangOpts: CI->getLangOpts()); |
589 | EXPECT_EQ(PP.modifiedBounds().Size, ExpectedBounds.Size); |
590 | EXPECT_EQ(PP.modifiedBounds().PreambleEndsAtStartOfLine, |
591 | ExpectedBounds.PreambleEndsAtStartOfLine); |
592 | } |
593 | } |
594 | |
595 | TEST(PreamblePatch, MacroLoc) { |
596 | llvm::StringLiteral Baseline = "\n#define MACRO 12\nint num = MACRO;" ; |
597 | llvm::StringLiteral Modified = " \n#define MACRO 12\nint num = MACRO;" ; |
598 | auto AST = createPatchedAST(Baseline, Modified); |
599 | ASSERT_TRUE(AST); |
600 | } |
601 | |
602 | TEST(PreamblePatch, NoopWhenNotRequested) { |
603 | llvm::StringLiteral Baseline = "#define M\nint num = M;" ; |
604 | llvm::StringLiteral Modified = "#define M\n#include <foo.h>\nint num = M;" ; |
605 | auto TU = TestTU::withCode(Code: Baseline); |
606 | auto BaselinePreamble = TU.preamble(); |
607 | ASSERT_TRUE(BaselinePreamble); |
608 | |
609 | TU.Code = Modified.str(); |
610 | MockFS FS; |
611 | auto PP = PreamblePatch::createMacroPatch(FileName: testPath(File: TU.Filename), |
612 | Modified: TU.inputs(FS), Baseline: *BaselinePreamble); |
613 | EXPECT_TRUE(PP.text().empty()); |
614 | } |
615 | |
616 | ::testing::Matcher<const Diag &> |
617 | withNote(::testing::Matcher<Note> NoteMatcher) { |
618 | return Field(field: &Diag::Notes, matcher: ElementsAre(matchers: NoteMatcher)); |
619 | } |
620 | MATCHER_P(Diag, Range, "Diag at " + llvm::to_string(Range)) { |
621 | return arg.Range == Range; |
622 | } |
623 | MATCHER_P2(Diag, Range, Name, |
624 | "Diag at " + llvm::to_string(Range) + " = [" + Name + "]" ) { |
625 | return arg.Range == Range && arg.Name == Name; |
626 | } |
627 | |
628 | TEST(PreamblePatch, DiagnosticsFromMainASTAreInRightPlace) { |
629 | { |
630 | Annotations Code("#define FOO" ); |
631 | // Check with removals from preamble. |
632 | Annotations NewCode("[[x]];/* error-ok */" ); |
633 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code()); |
634 | EXPECT_THAT(AST->getDiagnostics(), |
635 | ElementsAre(Diag(NewCode.range(), "missing_type_specifier" ))); |
636 | } |
637 | { |
638 | // Check with additions to preamble. |
639 | Annotations Code("#define FOO" ); |
640 | Annotations NewCode(R"( |
641 | #define FOO |
642 | #define BAR |
643 | [[x]];/* error-ok */)" ); |
644 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code()); |
645 | EXPECT_THAT(AST->getDiagnostics(), |
646 | ElementsAre(Diag(NewCode.range(), "missing_type_specifier" ))); |
647 | } |
648 | } |
649 | |
650 | TEST(PreamblePatch, DiagnosticsToPreamble) { |
651 | Config Cfg; |
652 | Cfg.Diagnostics.UnusedIncludes = Config::IncludesPolicy::Strict; |
653 | Cfg.Diagnostics.MissingIncludes = Config::IncludesPolicy::Strict; |
654 | WithContextValue WithCfg(Config::Key, std::move(Cfg)); |
655 | |
656 | llvm::StringMap<std::string> AdditionalFiles; |
657 | AdditionalFiles["foo.h" ] = "#pragma once" ; |
658 | AdditionalFiles["bar.h" ] = "#pragma once" ; |
659 | { |
660 | Annotations Code(R"( |
661 | // Test comment |
662 | [[#include "foo.h"]])" ); |
663 | // Check with removals from preamble. |
664 | Annotations NewCode(R"([[# include "foo.h"]])" ); |
665 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code(), AdditionalFiles); |
666 | EXPECT_THAT(AST->getDiagnostics(), |
667 | ElementsAre(Diag(NewCode.range(), "unused-includes" ))); |
668 | } |
669 | { |
670 | // Check with additions to preamble. |
671 | Annotations Code(R"( |
672 | // Test comment |
673 | [[#include "foo.h"]])" ); |
674 | Annotations NewCode(R"( |
675 | $bar[[#include "bar.h"]] |
676 | // Test comment |
677 | $foo[[#include "foo.h"]])" ); |
678 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code(), AdditionalFiles); |
679 | EXPECT_THAT( |
680 | AST->getDiagnostics(), |
681 | UnorderedElementsAre(Diag(NewCode.range("bar" ), "unused-includes" ), |
682 | Diag(NewCode.range("foo" ), "unused-includes" ))); |
683 | } |
684 | { |
685 | Annotations Code("#define [[FOO]] 1\n" ); |
686 | // Check ranges for notes. |
687 | // This also makes sure we don't generate missing-include diagnostics |
688 | // because macros are redefined in preamble-patch. |
689 | Annotations NewCode(R"(#define BARXYZ 1 |
690 | #define $foo1[[FOO]] 1 |
691 | void foo(); |
692 | #define $foo2[[FOO]] 2)" ); |
693 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code(), AdditionalFiles); |
694 | EXPECT_THAT( |
695 | AST->getDiagnostics(), |
696 | ElementsAre(AllOf(Diag(NewCode.range("foo2" ), "-Wmacro-redefined" ), |
697 | withNote(Diag(NewCode.range("foo1" )))))); |
698 | } |
699 | } |
700 | |
701 | TEST(PreamblePatch, TranslatesDiagnosticsInPreamble) { |
702 | { |
703 | // Check with additions to preamble. |
704 | Annotations Code("#include [[<foo>]]" ); |
705 | Annotations NewCode(R"( |
706 | #define BAR |
707 | #include [[<foo>]])" ); |
708 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code()); |
709 | EXPECT_THAT(AST->getDiagnostics(), |
710 | ElementsAre(Diag(NewCode.range(), "pp_file_not_found" ))); |
711 | } |
712 | { |
713 | // Check with removals from preamble. |
714 | Annotations Code(R"( |
715 | #define BAR |
716 | #include [[<foo>]])" ); |
717 | Annotations NewCode("#include [[<foo>]]" ); |
718 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code()); |
719 | EXPECT_THAT(AST->getDiagnostics(), |
720 | ElementsAre(Diag(NewCode.range(), "pp_file_not_found" ))); |
721 | } |
722 | { |
723 | // Drop line with diags. |
724 | Annotations Code("#include [[<foo>]]" ); |
725 | Annotations NewCode("#define BAR\n#define BAZ\n" ); |
726 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code()); |
727 | EXPECT_THAT(AST->getDiagnostics(), IsEmpty()); |
728 | } |
729 | { |
730 | // Picks closest line in case of multiple alternatives. |
731 | Annotations Code("#include [[<foo>]]" ); |
732 | Annotations NewCode(R"( |
733 | #define BAR |
734 | #include [[<foo>]] |
735 | #define BAR |
736 | #include <foo>)" ); |
737 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code()); |
738 | EXPECT_THAT(AST->getDiagnostics(), |
739 | ElementsAre(Diag(NewCode.range(), "pp_file_not_found" ))); |
740 | } |
741 | { |
742 | // Drop diag if line spelling has changed. |
743 | Annotations Code("#include [[<foo>]]" ); |
744 | Annotations NewCode(" # include <foo>" ); |
745 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code()); |
746 | EXPECT_THAT(AST->getDiagnostics(), IsEmpty()); |
747 | } |
748 | { |
749 | // Multiple lines. |
750 | Annotations Code(R"( |
751 | #define BAR |
752 | #include [[<fo\ |
753 | o>]])" ); |
754 | Annotations NewCode(R"(#include [[<fo\ |
755 | o>]])" ); |
756 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code()); |
757 | EXPECT_THAT(AST->getDiagnostics(), |
758 | ElementsAre(Diag(NewCode.range(), "pp_file_not_found" ))); |
759 | } |
760 | { |
761 | // Multiple lines with change. |
762 | Annotations Code(R"( |
763 | #define BAR |
764 | #include <fox> |
765 | #include [[<fo\ |
766 | o>]])" ); |
767 | Annotations NewCode(R"(#include <fo\ |
768 | x>)" ); |
769 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code()); |
770 | EXPECT_THAT(AST->getDiagnostics(), IsEmpty()); |
771 | } |
772 | { |
773 | // Preserves notes. |
774 | Annotations Code(R"( |
775 | #define $note[[BAR]] 1 |
776 | #define $main[[BAR]] 2)" ); |
777 | Annotations NewCode(R"( |
778 | #define BAZ 0 |
779 | #define $note[[BAR]] 1 |
780 | #define BAZ 0 |
781 | #define $main[[BAR]] 2)" ); |
782 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code()); |
783 | EXPECT_THAT( |
784 | AST->getDiagnostics(), |
785 | ElementsAre(AllOf(Diag(NewCode.range("main" ), "-Wmacro-redefined" ), |
786 | withNote(Diag(NewCode.range("note" )))))); |
787 | } |
788 | { |
789 | // Preserves diag without note. |
790 | Annotations Code(R"( |
791 | #define $note[[BAR]] 1 |
792 | #define $main[[BAR]] 2)" ); |
793 | Annotations NewCode(R"( |
794 | #define $main[[BAR]] 2)" ); |
795 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code()); |
796 | EXPECT_THAT( |
797 | AST->getDiagnostics(), |
798 | ElementsAre(AllOf(Diag(NewCode.range("main" ), "-Wmacro-redefined" ), |
799 | Field(&Diag::Notes, IsEmpty())))); |
800 | } |
801 | { |
802 | // Make sure orphaned notes are not promoted to diags. |
803 | Annotations Code(R"( |
804 | #define $note[[BAR]] 1 |
805 | #define $main[[BAR]] 2)" ); |
806 | Annotations NewCode(R"( |
807 | #define BAZ 0 |
808 | #define BAR 1)" ); |
809 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code()); |
810 | EXPECT_THAT(AST->getDiagnostics(), IsEmpty()); |
811 | } |
812 | { |
813 | Annotations Code(R"( |
814 | #ifndef FOO |
815 | #define FOO |
816 | void foo(); |
817 | #endif)" ); |
818 | // This code will emit a diagnostic for unterminated #ifndef (as stale |
819 | // preamble has the conditional but main file doesn't terminate it). |
820 | // We shouldn't emit any diagnotiscs (and shouldn't crash). |
821 | Annotations NewCode("" ); |
822 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code()); |
823 | EXPECT_THAT(AST->getDiagnostics(), IsEmpty()); |
824 | } |
825 | { |
826 | Annotations Code(R"( |
827 | #ifndef FOO |
828 | #define FOO |
829 | void foo(); |
830 | #endif)" ); |
831 | // This code will emit a diagnostic for unterminated #ifndef (as stale |
832 | // preamble has the conditional but main file doesn't terminate it). |
833 | // We shouldn't emit any diagnotiscs (and shouldn't crash). |
834 | // FIXME: Patch/ignore diagnostics in such cases. |
835 | Annotations NewCode(R"( |
836 | i[[nt]] xyz; |
837 | )" ); |
838 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code()); |
839 | EXPECT_THAT( |
840 | AST->getDiagnostics(), |
841 | ElementsAre(Diag(NewCode.range(), "pp_unterminated_conditional" ))); |
842 | } |
843 | } |
844 | |
845 | MATCHER_P2(Mark, Range, Text, "" ) { |
846 | return std::tie(arg.Rng, arg.Trivia) == std::tie(Range, Text); |
847 | } |
848 | |
849 | TEST(PreamblePatch, MacroAndMarkHandling) { |
850 | { |
851 | Annotations Code(R"cpp( |
852 | #ifndef FOO |
853 | #define FOO |
854 | // Some comments |
855 | #pragma mark XX |
856 | #define BAR |
857 | |
858 | #endif)cpp" ); |
859 | Annotations NewCode(R"cpp( |
860 | #ifndef FOO |
861 | #define FOO |
862 | #define BAR |
863 | #pragma $x[[mark XX |
864 | ]] |
865 | #pragma $y[[mark YY |
866 | ]] |
867 | #define BAZ |
868 | |
869 | #endif)cpp" ); |
870 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code()); |
871 | EXPECT_THAT(AST->getMacros().Names.keys(), |
872 | UnorderedElementsAreArray({"FOO" , "BAR" , "BAZ" })); |
873 | EXPECT_THAT(AST->getMarks(), |
874 | UnorderedElementsAre(Mark(NewCode.range("x" ), " XX" ), |
875 | Mark(NewCode.range("y" ), " YY" ))); |
876 | } |
877 | } |
878 | |
879 | TEST(PreamblePatch, PatchFileEntry) { |
880 | Annotations Code(R"cpp(#define FOO)cpp" ); |
881 | Annotations NewCode(R"cpp( |
882 | #define BAR |
883 | #define FOO)cpp" ); |
884 | { |
885 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: Code.code()); |
886 | EXPECT_EQ( |
887 | PreamblePatch::getPatchEntry(AST->tuPath(), AST->getSourceManager()), |
888 | nullptr); |
889 | } |
890 | { |
891 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code()); |
892 | auto FE = |
893 | PreamblePatch::getPatchEntry(MainFilePath: AST->tuPath(), SM: AST->getSourceManager()); |
894 | ASSERT_NE(FE, std::nullopt); |
895 | EXPECT_THAT(FE->getName().str(), |
896 | testing::EndsWith(PreamblePatch::HeaderName.str())); |
897 | } |
898 | } |
899 | |
900 | } // namespace |
901 | } // namespace clangd |
902 | } // namespace clang |
903 | |