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().spelledTokenAt( |
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().spelledTokenAt( |
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 = AST->getTokens().spelledTokenAt(Loc: SM.getComposedLoc( |
516 | FID: SM.getMainFileID(), |
517 | Offset: llvm::cantFail(ValOrErr: positionToOffset(Code: Modified.code(), P)))); |
518 | ASSERT_TRUE(MacroTok); |
519 | EXPECT_THAT(findReferences(*AST, P, 0).References, |
520 | testing::ElementsAreArray(ExpectedLocations)); |
521 | } |
522 | } |
523 | } |
524 | |
525 | TEST(TranslatePreamblePatchLocation, Simple) { |
526 | auto TU = TestTU::withHeaderCode(HeaderCode: R"cpp( |
527 | #line 3 "main.cpp" |
528 | int foo();)cpp" ); |
529 | // Presumed line/col needs to be valid in the main file. |
530 | TU.Code = R"cpp(// line 1 |
531 | // line 2 |
532 | // line 3 |
533 | // line 4)cpp" ; |
534 | TU.Filename = "main.cpp" ; |
535 | TU.HeaderFilename = "__preamble_patch__.h" ; |
536 | TU.ImplicitHeaderGuard = false; |
537 | |
538 | auto AST = TU.build(); |
539 | auto &SM = AST.getSourceManager(); |
540 | auto &ND = findDecl(AST, QName: "foo" ); |
541 | EXPECT_NE(SM.getFileID(ND.getLocation()), SM.getMainFileID()); |
542 | |
543 | auto TranslatedLoc = translatePreamblePatchLocation(ND.getLocation(), SM); |
544 | auto DecompLoc = SM.getDecomposedLoc(Loc: TranslatedLoc); |
545 | EXPECT_EQ(DecompLoc.first, SM.getMainFileID()); |
546 | EXPECT_EQ(SM.getLineNumber(DecompLoc.first, DecompLoc.second), 3U); |
547 | } |
548 | |
549 | TEST(PreamblePatch, ModifiedBounds) { |
550 | struct { |
551 | const char *const Baseline; |
552 | const char *const Modified; |
553 | } Cases[] = { |
554 | // Size increased |
555 | { |
556 | .Baseline: "" , |
557 | .Modified: R"cpp( |
558 | #define FOO |
559 | FOO)cpp" , |
560 | }, |
561 | // Stayed same |
562 | {.Baseline: "#define FOO" , .Modified: "#define BAR" }, |
563 | // Got smaller |
564 | { |
565 | .Baseline: R"cpp( |
566 | #define FOO |
567 | #undef FOO)cpp" , |
568 | .Modified: "#define FOO" }, |
569 | }; |
570 | |
571 | for (const auto &Case : Cases) { |
572 | auto TU = TestTU::withCode(Code: Case.Baseline); |
573 | auto BaselinePreamble = TU.preamble(); |
574 | ASSERT_TRUE(BaselinePreamble); |
575 | |
576 | Annotations Modified(Case.Modified); |
577 | TU.Code = Modified.code().str(); |
578 | MockFS FS; |
579 | auto PP = PreamblePatch::createFullPatch(FileName: testPath(File: TU.Filename), |
580 | Modified: TU.inputs(FS), Baseline: *BaselinePreamble); |
581 | |
582 | IgnoreDiagnostics Diags; |
583 | auto CI = buildCompilerInvocation(Inputs: TU.inputs(FS), D&: Diags); |
584 | ASSERT_TRUE(CI); |
585 | |
586 | const auto ExpectedBounds = |
587 | Lexer::ComputePreamble(Buffer: Case.Modified, LangOpts: CI->getLangOpts()); |
588 | EXPECT_EQ(PP.modifiedBounds().Size, ExpectedBounds.Size); |
589 | EXPECT_EQ(PP.modifiedBounds().PreambleEndsAtStartOfLine, |
590 | ExpectedBounds.PreambleEndsAtStartOfLine); |
591 | } |
592 | } |
593 | |
594 | TEST(PreamblePatch, MacroLoc) { |
595 | llvm::StringLiteral Baseline = "\n#define MACRO 12\nint num = MACRO;" ; |
596 | llvm::StringLiteral Modified = " \n#define MACRO 12\nint num = MACRO;" ; |
597 | auto AST = createPatchedAST(Baseline, Modified); |
598 | ASSERT_TRUE(AST); |
599 | } |
600 | |
601 | TEST(PreamblePatch, NoopWhenNotRequested) { |
602 | llvm::StringLiteral Baseline = "#define M\nint num = M;" ; |
603 | llvm::StringLiteral Modified = "#define M\n#include <foo.h>\nint num = M;" ; |
604 | auto TU = TestTU::withCode(Code: Baseline); |
605 | auto BaselinePreamble = TU.preamble(); |
606 | ASSERT_TRUE(BaselinePreamble); |
607 | |
608 | TU.Code = Modified.str(); |
609 | MockFS FS; |
610 | auto PP = PreamblePatch::createMacroPatch(FileName: testPath(File: TU.Filename), |
611 | Modified: TU.inputs(FS), Baseline: *BaselinePreamble); |
612 | EXPECT_TRUE(PP.text().empty()); |
613 | } |
614 | |
615 | ::testing::Matcher<const Diag &> |
616 | withNote(::testing::Matcher<Note> NoteMatcher) { |
617 | return Field(field: &Diag::Notes, matcher: ElementsAre(matchers: NoteMatcher)); |
618 | } |
619 | MATCHER_P(Diag, Range, "Diag at " + llvm::to_string(Range)) { |
620 | return arg.Range == Range; |
621 | } |
622 | MATCHER_P2(Diag, Range, Name, |
623 | "Diag at " + llvm::to_string(Range) + " = [" + Name + "]" ) { |
624 | return arg.Range == Range && arg.Name == Name; |
625 | } |
626 | |
627 | TEST(PreamblePatch, DiagnosticsFromMainASTAreInRightPlace) { |
628 | { |
629 | Annotations Code("#define FOO" ); |
630 | // Check with removals from preamble. |
631 | Annotations NewCode("[[x]];/* error-ok */" ); |
632 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code()); |
633 | EXPECT_THAT(AST->getDiagnostics(), |
634 | ElementsAre(Diag(NewCode.range(), "missing_type_specifier" ))); |
635 | } |
636 | { |
637 | // Check with additions to preamble. |
638 | Annotations Code("#define FOO" ); |
639 | Annotations NewCode(R"( |
640 | #define FOO |
641 | #define BAR |
642 | [[x]];/* error-ok */)" ); |
643 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code()); |
644 | EXPECT_THAT(AST->getDiagnostics(), |
645 | ElementsAre(Diag(NewCode.range(), "missing_type_specifier" ))); |
646 | } |
647 | } |
648 | |
649 | TEST(PreamblePatch, DiagnosticsToPreamble) { |
650 | Config Cfg; |
651 | Cfg.Diagnostics.UnusedIncludes = Config::IncludesPolicy::Strict; |
652 | Cfg.Diagnostics.MissingIncludes = Config::IncludesPolicy::Strict; |
653 | WithContextValue WithCfg(Config::Key, std::move(Cfg)); |
654 | |
655 | llvm::StringMap<std::string> AdditionalFiles; |
656 | AdditionalFiles["foo.h" ] = "#pragma once" ; |
657 | AdditionalFiles["bar.h" ] = "#pragma once" ; |
658 | { |
659 | Annotations Code(R"( |
660 | // Test comment |
661 | [[#include "foo.h"]])" ); |
662 | // Check with removals from preamble. |
663 | Annotations NewCode(R"([[# include "foo.h"]])" ); |
664 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code(), AdditionalFiles); |
665 | EXPECT_THAT(AST->getDiagnostics(), |
666 | ElementsAre(Diag(NewCode.range(), "unused-includes" ))); |
667 | } |
668 | { |
669 | // Check with additions to preamble. |
670 | Annotations Code(R"( |
671 | // Test comment |
672 | [[#include "foo.h"]])" ); |
673 | Annotations NewCode(R"( |
674 | $bar[[#include "bar.h"]] |
675 | // Test comment |
676 | $foo[[#include "foo.h"]])" ); |
677 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code(), AdditionalFiles); |
678 | EXPECT_THAT( |
679 | AST->getDiagnostics(), |
680 | UnorderedElementsAre(Diag(NewCode.range("bar" ), "unused-includes" ), |
681 | Diag(NewCode.range("foo" ), "unused-includes" ))); |
682 | } |
683 | { |
684 | Annotations Code("#define [[FOO]] 1\n" ); |
685 | // Check ranges for notes. |
686 | // This also makes sure we don't generate missing-include diagnostics |
687 | // because macros are redefined in preamble-patch. |
688 | Annotations NewCode(R"(#define BARXYZ 1 |
689 | #define $foo1[[FOO]] 1 |
690 | void foo(); |
691 | #define $foo2[[FOO]] 2)" ); |
692 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code(), AdditionalFiles); |
693 | EXPECT_THAT( |
694 | AST->getDiagnostics(), |
695 | ElementsAre(AllOf(Diag(NewCode.range("foo2" ), "-Wmacro-redefined" ), |
696 | withNote(Diag(NewCode.range("foo1" )))))); |
697 | } |
698 | } |
699 | |
700 | TEST(PreamblePatch, TranslatesDiagnosticsInPreamble) { |
701 | { |
702 | // Check with additions to preamble. |
703 | Annotations Code("#include [[<foo>]]" ); |
704 | Annotations NewCode(R"( |
705 | #define BAR |
706 | #include [[<foo>]])" ); |
707 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code()); |
708 | EXPECT_THAT(AST->getDiagnostics(), |
709 | ElementsAre(Diag(NewCode.range(), "pp_file_not_found" ))); |
710 | } |
711 | { |
712 | // Check with removals from preamble. |
713 | Annotations Code(R"( |
714 | #define BAR |
715 | #include [[<foo>]])" ); |
716 | Annotations NewCode("#include [[<foo>]]" ); |
717 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code()); |
718 | EXPECT_THAT(AST->getDiagnostics(), |
719 | ElementsAre(Diag(NewCode.range(), "pp_file_not_found" ))); |
720 | } |
721 | { |
722 | // Drop line with diags. |
723 | Annotations Code("#include [[<foo>]]" ); |
724 | Annotations NewCode("#define BAR\n#define BAZ\n" ); |
725 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code()); |
726 | EXPECT_THAT(AST->getDiagnostics(), IsEmpty()); |
727 | } |
728 | { |
729 | // Picks closest line in case of multiple alternatives. |
730 | Annotations Code("#include [[<foo>]]" ); |
731 | Annotations NewCode(R"( |
732 | #define BAR |
733 | #include [[<foo>]] |
734 | #define BAR |
735 | #include <foo>)" ); |
736 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code()); |
737 | EXPECT_THAT(AST->getDiagnostics(), |
738 | ElementsAre(Diag(NewCode.range(), "pp_file_not_found" ))); |
739 | } |
740 | { |
741 | // Drop diag if line spelling has changed. |
742 | Annotations Code("#include [[<foo>]]" ); |
743 | Annotations NewCode(" # include <foo>" ); |
744 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code()); |
745 | EXPECT_THAT(AST->getDiagnostics(), IsEmpty()); |
746 | } |
747 | { |
748 | // Multiple lines. |
749 | Annotations Code(R"( |
750 | #define BAR |
751 | #include [[<fo\ |
752 | o>]])" ); |
753 | Annotations NewCode(R"(#include [[<fo\ |
754 | o>]])" ); |
755 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code()); |
756 | EXPECT_THAT(AST->getDiagnostics(), |
757 | ElementsAre(Diag(NewCode.range(), "pp_file_not_found" ))); |
758 | } |
759 | { |
760 | // Multiple lines with change. |
761 | Annotations Code(R"( |
762 | #define BAR |
763 | #include <fox> |
764 | #include [[<fo\ |
765 | o>]])" ); |
766 | Annotations NewCode(R"(#include <fo\ |
767 | x>)" ); |
768 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code()); |
769 | EXPECT_THAT(AST->getDiagnostics(), IsEmpty()); |
770 | } |
771 | { |
772 | // Preserves notes. |
773 | Annotations Code(R"( |
774 | #define $note[[BAR]] 1 |
775 | #define $main[[BAR]] 2)" ); |
776 | Annotations NewCode(R"( |
777 | #define BAZ 0 |
778 | #define $note[[BAR]] 1 |
779 | #define BAZ 0 |
780 | #define $main[[BAR]] 2)" ); |
781 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code()); |
782 | EXPECT_THAT( |
783 | AST->getDiagnostics(), |
784 | ElementsAre(AllOf(Diag(NewCode.range("main" ), "-Wmacro-redefined" ), |
785 | withNote(Diag(NewCode.range("note" )))))); |
786 | } |
787 | { |
788 | // Preserves diag without note. |
789 | Annotations Code(R"( |
790 | #define $note[[BAR]] 1 |
791 | #define $main[[BAR]] 2)" ); |
792 | Annotations NewCode(R"( |
793 | #define $main[[BAR]] 2)" ); |
794 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code()); |
795 | EXPECT_THAT( |
796 | AST->getDiagnostics(), |
797 | ElementsAre(AllOf(Diag(NewCode.range("main" ), "-Wmacro-redefined" ), |
798 | Field(&Diag::Notes, IsEmpty())))); |
799 | } |
800 | { |
801 | // Make sure orphaned notes are not promoted to diags. |
802 | Annotations Code(R"( |
803 | #define $note[[BAR]] 1 |
804 | #define $main[[BAR]] 2)" ); |
805 | Annotations NewCode(R"( |
806 | #define BAZ 0 |
807 | #define BAR 1)" ); |
808 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code()); |
809 | EXPECT_THAT(AST->getDiagnostics(), IsEmpty()); |
810 | } |
811 | { |
812 | Annotations Code(R"( |
813 | #ifndef FOO |
814 | #define FOO |
815 | void foo(); |
816 | #endif)" ); |
817 | // This code will emit a diagnostic for unterminated #ifndef (as stale |
818 | // preamble has the conditional but main file doesn't terminate it). |
819 | // We shouldn't emit any diagnotiscs (and shouldn't crash). |
820 | Annotations NewCode("" ); |
821 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code()); |
822 | EXPECT_THAT(AST->getDiagnostics(), IsEmpty()); |
823 | } |
824 | { |
825 | Annotations Code(R"( |
826 | #ifndef FOO |
827 | #define FOO |
828 | void foo(); |
829 | #endif)" ); |
830 | // This code will emit a diagnostic for unterminated #ifndef (as stale |
831 | // preamble has the conditional but main file doesn't terminate it). |
832 | // We shouldn't emit any diagnotiscs (and shouldn't crash). |
833 | // FIXME: Patch/ignore diagnostics in such cases. |
834 | Annotations NewCode(R"( |
835 | i[[nt]] xyz; |
836 | )" ); |
837 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code()); |
838 | EXPECT_THAT( |
839 | AST->getDiagnostics(), |
840 | ElementsAre(Diag(NewCode.range(), "pp_unterminated_conditional" ))); |
841 | } |
842 | } |
843 | |
844 | MATCHER_P2(Mark, Range, Text, "" ) { |
845 | return std::tie(arg.Rng, arg.Trivia) == std::tie(Range, Text); |
846 | } |
847 | |
848 | TEST(PreamblePatch, MacroAndMarkHandling) { |
849 | { |
850 | Annotations Code(R"cpp( |
851 | #ifndef FOO |
852 | #define FOO |
853 | // Some comments |
854 | #pragma mark XX |
855 | #define BAR |
856 | |
857 | #endif)cpp" ); |
858 | Annotations NewCode(R"cpp( |
859 | #ifndef FOO |
860 | #define FOO |
861 | #define BAR |
862 | #pragma $x[[mark XX |
863 | ]] |
864 | #pragma $y[[mark YY |
865 | ]] |
866 | #define BAZ |
867 | |
868 | #endif)cpp" ); |
869 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code()); |
870 | EXPECT_THAT(AST->getMacros().Names.keys(), |
871 | UnorderedElementsAreArray({"FOO" , "BAR" , "BAZ" })); |
872 | EXPECT_THAT(AST->getMarks(), |
873 | UnorderedElementsAre(Mark(NewCode.range("x" ), " XX" ), |
874 | Mark(NewCode.range("y" ), " YY" ))); |
875 | } |
876 | } |
877 | |
878 | TEST(PreamblePatch, PatchFileEntry) { |
879 | Annotations Code(R"cpp(#define FOO)cpp" ); |
880 | Annotations NewCode(R"cpp( |
881 | #define BAR |
882 | #define FOO)cpp" ); |
883 | { |
884 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: Code.code()); |
885 | EXPECT_EQ( |
886 | PreamblePatch::getPatchEntry(AST->tuPath(), AST->getSourceManager()), |
887 | nullptr); |
888 | } |
889 | { |
890 | auto AST = createPatchedAST(Baseline: Code.code(), Modified: NewCode.code()); |
891 | auto FE = |
892 | PreamblePatch::getPatchEntry(MainFilePath: AST->tuPath(), SM: AST->getSourceManager()); |
893 | ASSERT_NE(FE, std::nullopt); |
894 | EXPECT_THAT(FE->getName().str(), |
895 | testing::EndsWith(PreamblePatch::HeaderName.str())); |
896 | } |
897 | } |
898 | |
899 | } // namespace |
900 | } // namespace clangd |
901 | } // namespace clang |
902 | |