1//===--- DiagnosticsTests.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 "../clang-tidy/ClangTidyOptions.h"
10#include "Annotations.h"
11#include "Config.h"
12#include "Diagnostics.h"
13#include "Feature.h"
14#include "FeatureModule.h"
15#include "ParsedAST.h"
16#include "Protocol.h"
17#include "TestFS.h"
18#include "TestIndex.h"
19#include "TestTU.h"
20#include "TidyProvider.h"
21#include "index/MemIndex.h"
22#include "index/Ref.h"
23#include "index/Relation.h"
24#include "index/Symbol.h"
25#include "support/Context.h"
26#include "support/Path.h"
27#include "clang/AST/Decl.h"
28#include "clang/Basic/Diagnostic.h"
29#include "clang/Basic/DiagnosticSema.h"
30#include "clang/Basic/LLVM.h"
31#include "clang/Basic/Specifiers.h"
32#include "llvm/ADT/ArrayRef.h"
33#include "llvm/ADT/StringRef.h"
34#include "llvm/Support/JSON.h"
35#include "llvm/Support/ScopedPrinter.h"
36#include "llvm/Support/TargetSelect.h"
37#include "llvm/Testing/Support/SupportHelpers.h"
38#include "gmock/gmock.h"
39#include "gtest/gtest.h"
40#include <cstddef>
41#include <memory>
42#include <optional>
43#include <string>
44#include <utility>
45#include <vector>
46
47namespace clang {
48namespace clangd {
49namespace {
50
51using ::testing::_;
52using ::testing::AllOf;
53using ::testing::Contains;
54using ::testing::Each;
55using ::testing::ElementsAre;
56using ::testing::Field;
57using ::testing::IsEmpty;
58using ::testing::Not;
59using ::testing::Pair;
60using ::testing::SizeIs;
61using ::testing::UnorderedElementsAre;
62
63::testing::Matcher<const Diag &> withFix(::testing::Matcher<Fix> FixMatcher) {
64 return Field(field: &Diag::Fixes, matcher: ElementsAre(matchers: FixMatcher));
65}
66
67::testing::Matcher<const Diag &> withFix(::testing::Matcher<Fix> FixMatcher1,
68 ::testing::Matcher<Fix> FixMatcher2) {
69 return Field(field: &Diag::Fixes, matcher: UnorderedElementsAre(matchers: FixMatcher1, matchers: FixMatcher2));
70}
71
72::testing::Matcher<const Diag &> withID(unsigned ID) {
73 return Field(field: &Diag::ID, matcher: ID);
74}
75::testing::Matcher<const Diag &>
76withNote(::testing::Matcher<Note> NoteMatcher) {
77 return Field(field: &Diag::Notes, matcher: ElementsAre(matchers: NoteMatcher));
78}
79
80::testing::Matcher<const Diag &>
81withNote(::testing::Matcher<Note> NoteMatcher1,
82 ::testing::Matcher<Note> NoteMatcher2) {
83 return Field(field: &Diag::Notes, matcher: UnorderedElementsAre(matchers: NoteMatcher1, matchers: NoteMatcher2));
84}
85
86::testing::Matcher<const Diag &>
87withTag(::testing::Matcher<DiagnosticTag> TagMatcher) {
88 return Field(field: &Diag::Tags, matcher: Contains(matcher: TagMatcher));
89}
90
91MATCHER_P(hasRange, Range, "") { return arg.Range == Range; }
92
93MATCHER_P2(Diag, Range, Message,
94 "Diag at " + llvm::to_string(Range) + " = [" + Message + "]") {
95 return arg.Range == Range && arg.Message == Message;
96}
97
98MATCHER_P3(Fix, Range, Replacement, Message,
99 "Fix " + llvm::to_string(Range) + " => " +
100 ::testing::PrintToString(Replacement) + " = [" + Message + "]") {
101 return arg.Message == Message && arg.Edits.size() == 1 &&
102 arg.Edits[0].range == Range && arg.Edits[0].newText == Replacement;
103}
104
105MATCHER_P(fixMessage, Message, "") { return arg.Message == Message; }
106
107MATCHER_P(equalToLSPDiag, LSPDiag,
108 "LSP diagnostic " + llvm::to_string(LSPDiag)) {
109 if (toJSON(arg) != toJSON(LSPDiag)) {
110 *result_listener << llvm::formatv("expected:\n{0:2}\ngot\n{1:2}",
111 toJSON(LSPDiag), toJSON(arg))
112 .str();
113 return false;
114 }
115 return true;
116}
117
118MATCHER_P(diagSource, S, "") { return arg.Source == S; }
119MATCHER_P(diagName, N, "") { return arg.Name == N; }
120MATCHER_P(diagSeverity, S, "") { return arg.Severity == S; }
121
122MATCHER_P(equalToFix, Fix, "LSP fix " + llvm::to_string(Fix)) {
123 if (arg.Message != Fix.Message)
124 return false;
125 if (arg.Edits.size() != Fix.Edits.size())
126 return false;
127 for (std::size_t I = 0; I < arg.Edits.size(); ++I) {
128 if (arg.Edits[I].range != Fix.Edits[I].range ||
129 arg.Edits[I].newText != Fix.Edits[I].newText)
130 return false;
131 }
132 return true;
133}
134
135// Helper function to make tests shorter.
136Position pos(int Line, int Character) {
137 Position Res;
138 Res.line = Line;
139 Res.character = Character;
140 return Res;
141}
142
143// Normally returns the provided diagnostics matcher.
144// If clang-tidy checks are not linked in, returns a matcher for no diagnostics!
145// This is intended for tests where the diagnostics come from clang-tidy checks.
146// We don't #ifdef each individual test as it's intrusive and we want to ensure
147// that as much of the test is still compiled an run as possible.
148::testing::Matcher<std::vector<clangd::Diag>>
149ifTidyChecks(::testing::Matcher<std::vector<clangd::Diag>> M) {
150 if (!CLANGD_TIDY_CHECKS)
151 return IsEmpty();
152 return M;
153}
154
155TEST(DiagnosticsTest, DiagnosticRanges) {
156 // Check we report correct ranges, including various edge-cases.
157 Annotations Test(R"cpp(
158 // error-ok
159 #define ID(X) X
160 namespace test{};
161 void $decl[[foo]]();
162 int main() {
163 struct Container { int* begin(); int* end(); } *container;
164 for (auto i : $insertstar[[]]$range[[container]]) {
165 }
166
167 $typo[[go\
168o]]();
169 foo()$semicolon[[]]//with comments
170 $unk[[unknown]]();
171 double $type[[bar]] = "foo";
172 struct Foo { int x; }; Foo a;
173 a.$nomember[[y]];
174 test::$nomembernamespace[[test]];
175 $macro[[ID($macroarg[[fod]])]]();
176 }
177 )cpp");
178 auto TU = TestTU::withCode(Code: Test.code());
179 EXPECT_THAT(
180 TU.build().getDiagnostics(),
181 ElementsAre(
182 // Make sure the whole token is highlighted.
183 AllOf(Diag(Test.range("range"),
184 "invalid range expression of type 'struct Container *'; "
185 "did you mean to dereference it with '*'?"),
186 withFix(Fix(Test.range("insertstar"), "*", "insert '*'"))),
187 // This range spans lines.
188 AllOf(Diag(Test.range("typo"),
189 "use of undeclared identifier 'goo'; did you mean 'foo'?"),
190 diagSource(Diag::Clang), diagName("undeclared_var_use_suggest"),
191 withFix(
192 Fix(Test.range("typo"), "foo", "change 'go\\…' to 'foo'")),
193 // This is a pretty normal range.
194 withNote(Diag(Test.range("decl"), "'foo' declared here"))),
195 // This range is zero-width and insertion. Therefore make sure we are
196 // not expanding it into other tokens. Since we are not going to
197 // replace those.
198 AllOf(Diag(Test.range("semicolon"), "expected ';' after expression"),
199 withFix(Fix(Test.range("semicolon"), ";", "insert ';'"))),
200 // This range isn't provided by clang, we expand to the token.
201 Diag(Test.range("unk"), "use of undeclared identifier 'unknown'"),
202 Diag(Test.range("type"),
203 "cannot initialize a variable of type 'double' with an lvalue "
204 "of type 'const char[4]'"),
205 Diag(Test.range("nomember"), "no member named 'y' in 'Foo'"),
206 Diag(Test.range("nomembernamespace"),
207 "no member named 'test' in namespace 'test'"),
208 AllOf(Diag(Test.range("macro"),
209 "use of undeclared identifier 'fod'; did you mean 'foo'?"),
210 withFix(Fix(Test.range("macroarg"), "foo",
211 "change 'fod' to 'foo'")))));
212}
213
214// Verify that the -Wswitch case-not-covered diagnostic range covers the
215// whole expression. This is important because the "populate-switch" tweak
216// fires for the full expression range (see tweaks/PopulateSwitchTests.cpp).
217// The quickfix flow only works end-to-end if the tweak can be triggered on
218// the diagnostic's range.
219TEST(DiagnosticsTest, WSwitch) {
220 Annotations Test(R"cpp(
221 enum A { X };
222 struct B { A a; };
223 void foo(B b) {
224 switch ([[b.a]]) {}
225 }
226 )cpp");
227 auto TU = TestTU::withCode(Code: Test.code());
228 TU.ExtraArgs = {"-Wswitch"};
229 EXPECT_THAT(TU.build().getDiagnostics(),
230 ElementsAre(Diag(Test.range(),
231 "enumeration value 'X' not handled in switch")));
232}
233
234TEST(DiagnosticsTest, FlagsMatter) {
235 Annotations Test("[[void]] main() {} // error-ok");
236 auto TU = TestTU::withCode(Code: Test.code());
237 EXPECT_THAT(TU.build().getDiagnostics(),
238 ElementsAre(AllOf(Diag(Test.range(), "'main' must return 'int'"),
239 withFix(Fix(Test.range(), "int",
240 "change 'void' to 'int'")))));
241 // Same code built as C gets different diagnostics.
242 TU.Filename = "Plain.c";
243 EXPECT_THAT(
244 TU.build().getDiagnostics(),
245 ElementsAre(AllOf(
246 Diag(Test.range(), "return type of 'main' is not 'int'"),
247 withFix(Fix(Test.range(), "int", "change return type to 'int'")))));
248}
249
250TEST(DiagnosticsTest, DiagnosticPreamble) {
251 Annotations Test(R"cpp(
252 #include $[["not-found.h"]] // error-ok
253 )cpp");
254
255 auto TU = TestTU::withCode(Code: Test.code());
256 EXPECT_THAT(TU.build().getDiagnostics(),
257 ElementsAre(::testing::AllOf(
258 Diag(Test.range(), "'not-found.h' file not found"),
259 diagSource(Diag::Clang), diagName("pp_file_not_found"))));
260}
261
262TEST(DiagnosticsTest, DeduplicatedClangTidyDiagnostics) {
263 Annotations Test(R"cpp(
264 float foo = [[0.1f]];
265 )cpp");
266 auto TU = TestTU::withCode(Code: Test.code());
267 // Enable alias clang-tidy checks, these check emits the same diagnostics
268 // (except the check name).
269 TU.ClangTidyProvider = addTidyChecks(Checks: "readability-uppercase-literal-suffix,"
270 "hicpp-uppercase-literal-suffix");
271 // Verify that we filter out the duplicated diagnostic message.
272 EXPECT_THAT(
273 TU.build().getDiagnostics(),
274 ifTidyChecks(UnorderedElementsAre(::testing::AllOf(
275 Diag(Test.range(),
276 "floating point literal has suffix 'f', which is not uppercase"),
277 diagSource(Diag::ClangTidy)))));
278
279 Test = Annotations(R"cpp(
280 template<typename T>
281 void func(T) {
282 float f = [[0.3f]];
283 }
284 void k() {
285 func(123);
286 func(2.0);
287 }
288 )cpp");
289 TU.Code = std::string(Test.code());
290 // The check doesn't handle template instantiations which ends up emitting
291 // duplicated messages, verify that we deduplicate them.
292 EXPECT_THAT(
293 TU.build().getDiagnostics(),
294 ifTidyChecks(UnorderedElementsAre(::testing::AllOf(
295 Diag(Test.range(),
296 "floating point literal has suffix 'f', which is not uppercase"),
297 diagSource(Diag::ClangTidy)))));
298}
299
300TEST(DiagnosticsTest, ClangTidy) {
301 Annotations Test(R"cpp(
302 #include $deprecated[["assert.h"]]
303
304 #define $macrodef[[SQUARE]](X) (X)*(X)
305 int $main[[main]]() {
306 int y = 4;
307 return SQUARE($macroarg[[++]]y);
308 return $doubled[[sizeof(sizeof(int))]];
309 }
310
311 // misc-no-recursion uses a custom traversal from the TUDecl
312 void foo();
313 void $bar[[bar]]() {
314 foo();
315 }
316 void $foo[[foo]]() {
317 bar();
318 }
319 )cpp");
320 auto TU = TestTU::withCode(Code: Test.code());
321 TU.HeaderFilename = "assert.h"; // Suppress "not found" error.
322 TU.ClangTidyProvider = addTidyChecks(Checks: "bugprone-sizeof-expression,"
323 "bugprone-macro-repeated-side-effects,"
324 "modernize-deprecated-headers,"
325 "modernize-use-trailing-return-type,"
326 "misc-no-recursion");
327 TU.ExtraArgs.push_back(x: "-Wno-unsequenced");
328 EXPECT_THAT(
329 TU.build().getDiagnostics(),
330 ifTidyChecks(UnorderedElementsAre(
331 AllOf(Diag(Test.range("deprecated"),
332 "inclusion of deprecated C++ header 'assert.h'; consider "
333 "using 'cassert' instead"),
334 diagSource(Diag::ClangTidy),
335 diagName("modernize-deprecated-headers"),
336 withFix(Fix(Test.range("deprecated"), "<cassert>",
337 "change '\"assert.h\"' to '<cassert>'"))),
338 Diag(Test.range("doubled"),
339 "suspicious usage of 'sizeof(sizeof(...))'"),
340 AllOf(Diag(Test.range("macroarg"),
341 "side effects in the 1st macro argument 'X' are "
342 "repeated in "
343 "macro expansion"),
344 diagSource(Diag::ClangTidy),
345 diagName("bugprone-macro-repeated-side-effects"),
346 withNote(Diag(Test.range("macrodef"),
347 "macro 'SQUARE' defined here"))),
348 AllOf(Diag(Test.range("main"),
349 "use a trailing return type for this function"),
350 diagSource(Diag::ClangTidy),
351 diagName("modernize-use-trailing-return-type"),
352 // Verify there's no "[check-name]" suffix in the message.
353 withFix(fixMessage(
354 "use a trailing return type for this function"))),
355 Diag(Test.range("foo"),
356 "function 'foo' is within a recursive call chain"),
357 Diag(Test.range("bar"),
358 "function 'bar' is within a recursive call chain"))));
359}
360
361TEST(DiagnosticsTest, ClangTidyEOF) {
362 // clang-format off
363 Annotations Test(R"cpp(
364 [[#]]include <b.h>
365 #include "a.h")cpp");
366 // clang-format on
367 auto TU = TestTU::withCode(Code: Test.code());
368 TU.ExtraArgs = {"-isystem."};
369 TU.AdditionalFiles["a.h"] = TU.AdditionalFiles["b.h"] = "";
370 TU.ClangTidyProvider = addTidyChecks(Checks: "llvm-include-order");
371 EXPECT_THAT(
372 TU.build().getDiagnostics(),
373 ifTidyChecks(Contains(
374 AllOf(Diag(Test.range(), "#includes are not sorted properly"),
375 diagSource(Diag::ClangTidy), diagName("llvm-include-order")))));
376}
377
378TEST(DiagnosticTest, TemplatesInHeaders) {
379 // Diagnostics from templates defined in headers are placed at the expansion.
380 Annotations Main(R"cpp(
381 Derived<int> [[y]]; // error-ok
382 )cpp");
383 Annotations Header(R"cpp(
384 template <typename T>
385 struct Derived : [[T]] {};
386 )cpp");
387 TestTU TU = TestTU::withCode(Code: Main.code());
388 TU.HeaderCode = Header.code().str();
389 EXPECT_THAT(
390 TU.build().getDiagnostics(),
391 ElementsAre(AllOf(
392 Diag(Main.range(), "in template: base specifier must name a class"),
393 withNote(Diag(Header.range(), "error occurred here"),
394 Diag(Main.range(), "in instantiation of template class "
395 "'Derived<int>' requested here")))));
396}
397
398TEST(DiagnosticTest, MakeUnique) {
399 // We usually miss diagnostics from header functions as we don't parse them.
400 // std::make_unique is an exception.
401 Annotations Main(R"cpp(
402 struct S { S(char*); };
403 auto x = std::[[make_unique]]<S>(42); // error-ok
404 )cpp");
405 TestTU TU = TestTU::withCode(Code: Main.code());
406 TU.HeaderCode = R"cpp(
407 namespace std {
408 // These mocks aren't quite right - we omit unique_ptr for simplicity.
409 // forward is included to show its body is not needed to get the diagnostic.
410 template <typename T> T&& forward(T& t);
411 template <typename T, typename... A> T* make_unique(A&&... args) {
412 return new T(std::forward<A>(args)...);
413 }
414 }
415 )cpp";
416 EXPECT_THAT(TU.build().getDiagnostics(),
417 UnorderedElementsAre(
418 Diag(Main.range(),
419 "in template: "
420 "no matching constructor for initialization of 'S'")));
421}
422
423TEST(DiagnosticTest, CoroutineInHeader) {
424 StringRef CoroutineH = R"cpp(
425namespace std {
426template <class Ret, typename... T>
427struct coroutine_traits { using promise_type = typename Ret::promise_type; };
428
429template <class Promise = void>
430struct coroutine_handle {
431 static coroutine_handle from_address(void *) noexcept;
432 static coroutine_handle from_promise(Promise &promise);
433 constexpr void* address() const noexcept;
434};
435template <>
436struct coroutine_handle<void> {
437 template <class PromiseType>
438 coroutine_handle(coroutine_handle<PromiseType>) noexcept;
439 static coroutine_handle from_address(void *);
440 constexpr void* address() const noexcept;
441};
442
443struct awaitable {
444 bool await_ready() noexcept { return false; }
445 void await_suspend(coroutine_handle<>) noexcept {}
446 void await_resume() noexcept {}
447};
448} // namespace std
449 )cpp";
450
451 StringRef Header = R"cpp(
452#include "coroutine.h"
453template <typename T> struct [[clang::coro_return_type]] Gen {
454 struct promise_type {
455 Gen<T> get_return_object() {
456 return {};
457 }
458 std::awaitable initial_suspend();
459 std::awaitable final_suspend() noexcept;
460 void unhandled_exception();
461 void return_value(T t);
462 };
463};
464
465Gen<int> foo_coro(int b) { co_return b; }
466 )cpp";
467 Annotations Main(R"cpp(
468// error-ok
469#include "header.hpp"
470Gen<int> $[[bar_coro]](int b) { return foo_coro(b); }
471 )cpp");
472 TestTU TU = TestTU::withCode(Code: Main.code());
473 TU.AdditionalFiles["coroutine.h"] = std::string(CoroutineH);
474 TU.AdditionalFiles["header.hpp"] = std::string(Header);
475 TU.ExtraArgs.push_back(x: "--std=c++20");
476 EXPECT_THAT(TU.build().getDiagnostics(), ElementsAre(hasRange(Main.range())));
477}
478
479TEST(DiagnosticTest, MakeShared) {
480 // We usually miss diagnostics from header functions as we don't parse them.
481 // std::make_shared is only parsed when --parse-forwarding-functions is set
482 Annotations Main(R"cpp(
483 struct S { S(char*); };
484 auto x = std::[[make_shared]]<S>(42); // error-ok
485 )cpp");
486 TestTU TU = TestTU::withCode(Code: Main.code());
487 TU.HeaderCode = R"cpp(
488 namespace std {
489 // These mocks aren't quite right - we omit shared_ptr for simplicity.
490 // forward is included to show its body is not needed to get the diagnostic.
491 template <typename T> T&& forward(T& t);
492 template <typename T, typename... A> T* make_shared(A&&... args) {
493 return new T(std::forward<A>(args)...);
494 }
495 }
496 )cpp";
497 TU.ParseOpts.PreambleParseForwardingFunctions = true;
498 EXPECT_THAT(TU.build().getDiagnostics(),
499 UnorderedElementsAre(
500 Diag(Main.range(),
501 "in template: "
502 "no matching constructor for initialization of 'S'")));
503}
504
505TEST(DiagnosticTest, NoMultipleDiagnosticInFlight) {
506 Annotations Main(R"cpp(
507 template <typename T> struct Foo {
508 T *begin();
509 T *end();
510 };
511 struct LabelInfo {
512 int a;
513 bool b;
514 };
515
516 void f() {
517 Foo<LabelInfo> label_info_map;
518 [[for]] (auto it = label_info_map.begin(); it != label_info_map.end(); ++it) {
519 auto S = *it;
520 }
521 }
522 )cpp");
523 TestTU TU = TestTU::withCode(Code: Main.code());
524 TU.ClangTidyProvider = addTidyChecks(Checks: "modernize-loop-convert");
525 EXPECT_THAT(
526 TU.build().getDiagnostics(),
527 ifTidyChecks(UnorderedElementsAre(::testing::AllOf(
528 Diag(Main.range(), "use range-based for loop instead"),
529 diagSource(Diag::ClangTidy), diagName("modernize-loop-convert")))));
530}
531
532TEST(DiagnosticTest, RespectsDiagnosticConfig) {
533 Annotations Main(R"cpp(
534 // error-ok
535 void x() {
536 [[unknown]]();
537 $ret[[return]] 42;
538 }
539 )cpp");
540 auto TU = TestTU::withCode(Code: Main.code());
541 EXPECT_THAT(
542 TU.build().getDiagnostics(),
543 ElementsAre(Diag(Main.range(), "use of undeclared identifier 'unknown'"),
544 Diag(Main.range("ret"),
545 "void function 'x' should not return a value")));
546 Config Cfg;
547 Cfg.Diagnostics.Suppress.insert(key: "return-mismatch");
548 WithContextValue WithCfg(Config::Key, std::move(Cfg));
549 EXPECT_THAT(TU.build().getDiagnostics(),
550 ElementsAre(Diag(Main.range(),
551 "use of undeclared identifier 'unknown'")));
552}
553
554TEST(DiagnosticTest, RespectsDiagnosticConfigInHeader) {
555 Annotations Header(R"cpp(
556 int x = "42"; // error-ok
557 )cpp");
558 Annotations Main(R"cpp(
559 #include "header.hpp"
560 )cpp");
561 auto TU = TestTU::withCode(Code: Main.code());
562 TU.AdditionalFiles["header.hpp"] = std::string(Header.code());
563 Config Cfg;
564 Cfg.Diagnostics.Suppress.insert(key: "init_conversion_failed");
565 WithContextValue WithCfg(Config::Key, std::move(Cfg));
566 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
567}
568
569TEST(DiagnosticTest, ClangTidySuppressionComment) {
570 Annotations Main(R"cpp(
571 int main() {
572 int i = 3;
573 double d = 8 / i; // NOLINT
574 // NOLINTNEXTLINE
575 double e = 8 / i;
576 #define BAD 8 / i
577 double f = BAD; // NOLINT
578 double g = [[8]] / i;
579 #define BAD2 BAD
580 double h = BAD2; // NOLINT
581 // NOLINTBEGIN
582 double x = BAD2;
583 double y = BAD2;
584 // NOLINTEND
585
586 // verify no crashes on unmatched nolints.
587 // NOLINTBEGIN
588 }
589 )cpp");
590 TestTU TU = TestTU::withCode(Code: Main.code());
591 TU.ClangTidyProvider = addTidyChecks(Checks: "bugprone-integer-division");
592 EXPECT_THAT(
593 TU.build().getDiagnostics(),
594 ifTidyChecks(UnorderedElementsAre(::testing::AllOf(
595 Diag(Main.range(), "result of integer division used in a floating "
596 "point context; possible loss of precision"),
597 diagSource(Diag::ClangTidy),
598 diagName("bugprone-integer-division")))));
599}
600
601TEST(DiagnosticTest, ClangTidySystemMacro) {
602 Annotations Main(R"cpp(
603 #include "user.h"
604 #include "system.h"
605 int i = 3;
606 double x = $inline[[8]] / i;
607 double y = $user[[DIVIDE_USER]](i);
608 double z = DIVIDE_SYS(i);
609 )cpp");
610
611 auto TU = TestTU::withCode(Code: Main.code());
612 TU.AdditionalFiles["user.h"] = R"cpp(
613 #define DIVIDE_USER(Y) 8/Y
614 )cpp";
615 TU.AdditionalFiles["system.h"] = R"cpp(
616 #pragma clang system_header
617 #define DIVIDE_SYS(Y) 8/Y
618 )cpp";
619
620 TU.ClangTidyProvider = addTidyChecks(Checks: "bugprone-integer-division");
621 std::string BadDivision = "result of integer division used in a floating "
622 "point context; possible loss of precision";
623
624 // Expect to see warning from user macros, but not system macros.
625 // This matches clang-tidy --system-headers=0 (the default).
626 EXPECT_THAT(TU.build().getDiagnostics(),
627 ifTidyChecks(
628 UnorderedElementsAre(Diag(Main.range("inline"), BadDivision),
629 Diag(Main.range("user"), BadDivision))));
630}
631
632TEST(DiagnosticTest, ClangTidyWarningAsError) {
633 Annotations Main(R"cpp(
634 int main() {
635 int i = 3;
636 double f = [[8]] / i; // error-ok
637 }
638 )cpp");
639 TestTU TU = TestTU::withCode(Code: Main.code());
640 TU.ClangTidyProvider =
641 addTidyChecks(Checks: "bugprone-integer-division", WarningsAsErrors: "bugprone-integer-division");
642 EXPECT_THAT(
643 TU.build().getDiagnostics(),
644 ifTidyChecks(UnorderedElementsAre(::testing::AllOf(
645 Diag(Main.range(), "result of integer division used in a floating "
646 "point context; possible loss of precision"),
647 diagSource(Diag::ClangTidy), diagName("bugprone-integer-division"),
648 diagSeverity(DiagnosticsEngine::Error)))));
649}
650
651TidyProvider addClangArgs(std::vector<llvm::StringRef> ExtraArgs,
652 llvm::StringRef Checks) {
653 return [ExtraArgs = std::move(ExtraArgs), Checks = Checks.str()](
654 tidy::ClangTidyOptions &Opts, llvm::StringRef) {
655 if (!Opts.ExtraArgs)
656 Opts.ExtraArgs.emplace();
657 for (llvm::StringRef Arg : ExtraArgs)
658 Opts.ExtraArgs->emplace_back(args&: Arg);
659 if (!Checks.empty())
660 Opts.Checks = Checks;
661 };
662}
663
664TEST(DiagnosticTest, ClangTidyEnablesClangWarning) {
665 Annotations Main(R"cpp( // error-ok
666 static void [[foo]]() {}
667 )cpp");
668 TestTU TU = TestTU::withCode(Code: Main.code());
669 // This is always emitted as a clang warning, not a clang-tidy diagnostic.
670 auto UnusedFooWarning =
671 AllOf(matchers: Diag(gmock_p0: Main.range(), gmock_p1: "unused function 'foo'"),
672 matchers: diagName(gmock_p0: "-Wunused-function"), matchers: diagSource(gmock_p0: Diag::Clang),
673 matchers: diagSeverity(gmock_p0: DiagnosticsEngine::Warning));
674
675 // Check the -Wunused warning isn't initially on.
676 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
677
678 // We enable warnings based on clang-tidy extra args, if the matching
679 // clang-diagnostic- is there.
680 TU.ClangTidyProvider =
681 addClangArgs(ExtraArgs: {"-Wunused"}, Checks: "clang-diagnostic-unused-function");
682 EXPECT_THAT(TU.build().getDiagnostics(), ElementsAre(UnusedFooWarning));
683
684 // clang-diagnostic-* is acceptable
685 TU.ClangTidyProvider = addClangArgs(ExtraArgs: {"-Wunused"}, Checks: "clang-diagnostic-*");
686 EXPECT_THAT(TU.build().getDiagnostics(), ElementsAre(UnusedFooWarning));
687 // And plain * (may turn on other checks too).
688 TU.ClangTidyProvider = addClangArgs(ExtraArgs: {"-Wunused"}, Checks: "*");
689 EXPECT_THAT(TU.build().getDiagnostics(), Contains(UnusedFooWarning));
690 // And we can explicitly exclude a category too.
691 TU.ClangTidyProvider = addClangArgs(
692 ExtraArgs: {"-Wunused"}, Checks: "clang-diagnostic-*,-clang-diagnostic-unused-function");
693 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
694
695 // Without the exact check specified, the warnings are not enabled.
696 TU.ClangTidyProvider = addClangArgs(ExtraArgs: {"-Wunused"}, Checks: "clang-diagnostic-unused");
697 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
698
699 // We don't respect other args.
700 TU.ClangTidyProvider = addClangArgs(ExtraArgs: {"-Wunused", "-Dfoo=bar"},
701 Checks: "clang-diagnostic-unused-function");
702 EXPECT_THAT(TU.build().getDiagnostics(), ElementsAre(UnusedFooWarning))
703 << "Not unused function 'bar'!";
704
705 // -Werror doesn't apply to warnings enabled by clang-tidy extra args.
706 TU.ExtraArgs = {"-Werror"};
707 TU.ClangTidyProvider =
708 addClangArgs(ExtraArgs: {"-Wunused"}, Checks: "clang-diagnostic-unused-function");
709 EXPECT_THAT(TU.build().getDiagnostics(),
710 ElementsAre(diagSeverity(DiagnosticsEngine::Warning)));
711
712 // But clang-tidy extra args won't *downgrade* errors to warnings either.
713 TU.ExtraArgs = {"-Wunused", "-Werror"};
714 TU.ClangTidyProvider =
715 addClangArgs(ExtraArgs: {"-Wunused"}, Checks: "clang-diagnostic-unused-function");
716 EXPECT_THAT(TU.build().getDiagnostics(),
717 ElementsAre(diagSeverity(DiagnosticsEngine::Error)));
718
719 // FIXME: we're erroneously downgrading the whole group, this should be Error.
720 TU.ExtraArgs = {"-Wunused-function", "-Werror"};
721 TU.ClangTidyProvider =
722 addClangArgs(ExtraArgs: {"-Wunused"}, Checks: "clang-diagnostic-unused-label");
723 EXPECT_THAT(TU.build().getDiagnostics(),
724 ElementsAre(diagSeverity(DiagnosticsEngine::Warning)));
725
726 // This looks silly, but it's the typical result if a warning is enabled by a
727 // high-level .clang-tidy file and disabled by a low-level one.
728 TU.ExtraArgs = {};
729 TU.ClangTidyProvider = addClangArgs(ExtraArgs: {"-Wunused", "-Wno-unused"},
730 Checks: "clang-diagnostic-unused-function");
731 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
732
733 // Overriding only works in the proper order.
734 TU.ClangTidyProvider =
735 addClangArgs(ExtraArgs: {"-Wunused"}, Checks: {"clang-diagnostic-unused-function"});
736 EXPECT_THAT(TU.build().getDiagnostics(), SizeIs(1));
737
738 // More specific vs less-specific: match clang behavior
739 TU.ClangTidyProvider = addClangArgs(ExtraArgs: {"-Wunused", "-Wno-unused-function"},
740 Checks: {"clang-diagnostic-unused-function"});
741 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
742 TU.ClangTidyProvider = addClangArgs(ExtraArgs: {"-Wunused-function", "-Wno-unused"},
743 Checks: {"clang-diagnostic-unused-function"});
744 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
745
746 // We do allow clang-tidy config to disable warnings from the compile
747 // command. It's unclear this is ideal, but it's hard to avoid.
748 TU.ExtraArgs = {"-Wunused"};
749 TU.ClangTidyProvider = addClangArgs(ExtraArgs: {"-Wno-unused"}, Checks: {});
750 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
751}
752
753TEST(DiagnosticTest, LongFixMessages) {
754 // We limit the size of printed code.
755 Annotations Source(R"cpp(
756 int main() {
757 // error-ok
758 int somereallyreallyreallyreallyreallyreallyreallyreallylongidentifier;
759 [[omereallyreallyreallyreallyreallyreallyreallyreallylongidentifier]]= 10;
760 }
761 )cpp");
762 TestTU TU = TestTU::withCode(Code: Source.code());
763 EXPECT_THAT(
764 TU.build().getDiagnostics(),
765 ElementsAre(withFix(Fix(
766 Source.range(),
767 "somereallyreallyreallyreallyreallyreallyreallyreallylongidentifier",
768 "change 'omereallyreallyreallyreallyreallyreallyreallyreall…' to "
769 "'somereallyreallyreallyreallyreallyreallyreallyreal…'"))));
770 // Only show changes up to a first newline.
771 Source = Annotations(R"cpp(
772 // error-ok
773 int main() {
774 int ident;
775 [[ide\
776n]] = 10; // error-ok
777 }
778 )cpp");
779 TU.Code = std::string(Source.code());
780 EXPECT_THAT(TU.build().getDiagnostics(),
781 ElementsAre(withFix(
782 Fix(Source.range(), "ident", "change 'ide\\…' to 'ident'"))));
783}
784
785TEST(DiagnosticTest, NewLineFixMessage) {
786 Annotations Source("int a;[[]]");
787 TestTU TU = TestTU::withCode(Code: Source.code());
788 TU.ExtraArgs = {"-Wnewline-eof"};
789 EXPECT_THAT(
790 TU.build().getDiagnostics(),
791 ElementsAre(withFix((Fix(Source.range(), "\n", "insert '\\n'")))));
792}
793
794TEST(DiagnosticTest, ClangTidySuppressionCommentTrumpsWarningAsError) {
795 Annotations Main(R"cpp(
796 int main() {
797 int i = 3;
798 double f = [[8]] / i; // NOLINT
799 }
800 )cpp");
801 TestTU TU = TestTU::withCode(Code: Main.code());
802 TU.ClangTidyProvider =
803 addTidyChecks(Checks: "bugprone-integer-division", WarningsAsErrors: "bugprone-integer-division");
804 EXPECT_THAT(TU.build().getDiagnostics(), UnorderedElementsAre());
805}
806
807TEST(DiagnosticTest, ClangTidyNoLiteralDataInMacroToken) {
808 Annotations Main(R"cpp(
809 #define SIGTERM 15
810 using pthread_t = int;
811 int pthread_kill(pthread_t thread, int sig);
812 int func() {
813 pthread_t thread;
814 return pthread_kill(thread, 0);
815 }
816 )cpp");
817 TestTU TU = TestTU::withCode(Code: Main.code());
818 TU.ClangTidyProvider = addTidyChecks(Checks: "bugprone-bad-signal-to-kill-thread");
819 EXPECT_THAT(TU.build().getDiagnostics(), UnorderedElementsAre()); // no-crash
820}
821
822TEST(DiagnosticTest, ElseAfterReturnRange) {
823 Annotations Main(R"cpp(
824 int foo(int cond) {
825 if (cond == 1) {
826 return 42;
827 } [[else]] if (cond == 2) {
828 return 43;
829 }
830 return 44;
831 }
832 )cpp");
833 TestTU TU = TestTU::withCode(Code: Main.code());
834 TU.ClangTidyProvider = addTidyChecks(Checks: "llvm-else-after-return");
835 EXPECT_THAT(TU.build().getDiagnostics(),
836 ifTidyChecks(ElementsAre(
837 Diag(Main.range(), "do not use 'else' after 'return'"))));
838}
839
840TEST(DiagnosticTest, ClangTidySelfContainedDiags) {
841 Annotations Main(R"cpp($MathHeader[[]]
842 struct Foo{
843 int A, B;
844 Foo()$Fix[[]] {
845 $A[[A = 1;]]
846 $B[[B = 1;]]
847 }
848 };
849 void InitVariables() {
850 float $C[[C]]$CFix[[]];
851 double $D[[D]]$DFix[[]];
852 }
853 )cpp");
854 TestTU TU = TestTU::withCode(Code: Main.code());
855 TU.ClangTidyProvider =
856 addTidyChecks(Checks: "cppcoreguidelines-prefer-member-initializer,"
857 "cppcoreguidelines-init-variables");
858 clangd::Fix ExpectedAFix;
859 ExpectedAFix.Message =
860 "'A' should be initialized in a member initializer of the constructor";
861 ExpectedAFix.Edits.push_back(Elt: TextEdit{.range: Main.range(Name: "Fix"), .newText: " : A(1)"});
862 ExpectedAFix.Edits.push_back(Elt: TextEdit{.range: Main.range(Name: "A"), .newText: ""});
863
864 // When invoking clang-tidy normally, this code would produce `, B(1)` as the
865 // fix the `B` member, as it would think its already included the ` : ` from
866 // the previous `A` fix.
867 clangd::Fix ExpectedBFix;
868 ExpectedBFix.Message =
869 "'B' should be initialized in a member initializer of the constructor";
870 ExpectedBFix.Edits.push_back(Elt: TextEdit{.range: Main.range(Name: "Fix"), .newText: " : B(1)"});
871 ExpectedBFix.Edits.push_back(Elt: TextEdit{.range: Main.range(Name: "B"), .newText: ""});
872
873 clangd::Fix ExpectedCFix;
874 ExpectedCFix.Message = "variable 'C' is not initialized";
875 ExpectedCFix.Edits.push_back(Elt: TextEdit{.range: Main.range(Name: "CFix"), .newText: " = NAN"});
876 ExpectedCFix.Edits.push_back(
877 Elt: TextEdit{.range: Main.range(Name: "MathHeader"), .newText: "#include <math.h>\n\n"});
878
879 // Again in clang-tidy only the include directive would be emitted for the
880 // first warning. However we need the include attaching for both warnings.
881 clangd::Fix ExpectedDFix;
882 ExpectedDFix.Message = "variable 'D' is not initialized";
883 ExpectedDFix.Edits.push_back(Elt: TextEdit{.range: Main.range(Name: "DFix"), .newText: " = NAN"});
884 ExpectedDFix.Edits.push_back(
885 Elt: TextEdit{.range: Main.range(Name: "MathHeader"), .newText: "#include <math.h>\n\n"});
886 EXPECT_THAT(
887 TU.build().getDiagnostics(),
888 ifTidyChecks(UnorderedElementsAre(
889 AllOf(Diag(Main.range("A"), "'A' should be initialized in a member "
890 "initializer of the constructor"),
891 withFix(equalToFix(ExpectedAFix))),
892 AllOf(Diag(Main.range("B"), "'B' should be initialized in a member "
893 "initializer of the constructor"),
894 withFix(equalToFix(ExpectedBFix))),
895 AllOf(Diag(Main.range("C"), "variable 'C' is not initialized"),
896 withFix(equalToFix(ExpectedCFix))),
897 AllOf(Diag(Main.range("D"), "variable 'D' is not initialized"),
898 withFix(equalToFix(ExpectedDFix))))));
899}
900
901TEST(DiagnosticTest, ClangTidySelfContainedDiagsFormatting) {
902 Annotations Main(R"cpp(
903 class Interface {
904 public:
905 virtual void Reset1() = 0;
906 virtual void Reset2() = 0;
907 };
908 class A : public Interface {
909 // This will be marked by clangd to use override instead of virtual
910 $virtual1[[virtual ]]void $Reset1[[Reset1]]()$override1[[]];
911 $virtual2[[virtual ]]/**/void $Reset2[[Reset2]]()$override2[[]];
912 };
913 )cpp");
914 TestTU TU = TestTU::withCode(Code: Main.code());
915 TU.ClangTidyProvider =
916 addTidyChecks(Checks: "cppcoreguidelines-explicit-virtual-functions,");
917 clangd::Fix const ExpectedFix1{
918 .Message: "prefer using 'override' or (rarely) 'final' "
919 "instead of 'virtual'",
920 .Edits: {TextEdit{.range: Main.range(Name: "override1"), .newText: " override"},
921 TextEdit{.range: Main.range(Name: "virtual1"), .newText: ""}},
922 .Annotations: {}};
923 clangd::Fix const ExpectedFix2{
924 .Message: "prefer using 'override' or (rarely) 'final' "
925 "instead of 'virtual'",
926 .Edits: {TextEdit{.range: Main.range(Name: "override2"), .newText: " override"},
927 TextEdit{.range: Main.range(Name: "virtual2"), .newText: ""}},
928 .Annotations: {}};
929 // Note that in the Fix we expect the "virtual" keyword and the following
930 // whitespace to be deleted
931 EXPECT_THAT(TU.build().getDiagnostics(),
932 ifTidyChecks(UnorderedElementsAre(
933 AllOf(Diag(Main.range("Reset1"),
934 "prefer using 'override' or (rarely) 'final' "
935 "instead of 'virtual'"),
936 withFix(equalToFix(ExpectedFix1))),
937 AllOf(Diag(Main.range("Reset2"),
938 "prefer using 'override' or (rarely) 'final' "
939 "instead of 'virtual'"),
940 withFix(equalToFix(ExpectedFix2))))));
941}
942
943TEST(DiagnosticsTest, Preprocessor) {
944 // This looks like a preamble, but there's an #else in the middle!
945 // Check that:
946 // - the #else doesn't generate diagnostics (we had this bug)
947 // - we get diagnostics from the taken branch
948 // - we get no diagnostics from the not taken branch
949 Annotations Test(R"cpp(
950 #ifndef FOO
951 #define FOO
952 int a = [[b]]; // error-ok
953 #else
954 int x = y;
955 #endif
956 )cpp");
957 EXPECT_THAT(
958 TestTU::withCode(Test.code()).build().getDiagnostics(),
959 ElementsAre(Diag(Test.range(), "use of undeclared identifier 'b'")));
960}
961
962TEST(DiagnosticsTest, IgnoreVerify) {
963 auto TU = TestTU::withCode(Code: R"cpp(
964 int a; // expected-error {{}}
965 )cpp");
966 TU.ExtraArgs.push_back(x: "-Xclang");
967 TU.ExtraArgs.push_back(x: "-verify");
968 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
969}
970
971TEST(DiagnosticTest, IgnoreBEFilelistOptions) {
972 auto TU = TestTU::withCode(Code: "");
973 TU.ExtraArgs.push_back(x: "-Xclang");
974 for (const auto *DisableOption :
975 {"-fsanitize-ignorelist=null", "-fprofile-list=null",
976 "-fxray-always-instrument=null", "-fxray-never-instrument=null",
977 "-fxray-attr-list=null"}) {
978 TU.ExtraArgs.push_back(x: DisableOption);
979 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
980 TU.ExtraArgs.pop_back();
981 }
982}
983
984// Recursive main-file include is diagnosed, and doesn't crash.
985TEST(DiagnosticsTest, RecursivePreamble) {
986 auto TU = TestTU::withCode(Code: R"cpp(
987 #include "foo.h" // error-ok
988 int symbol;
989 )cpp");
990 TU.Filename = "foo.h";
991 EXPECT_THAT(TU.build().getDiagnostics(),
992 ElementsAre(diagName("pp_including_mainfile_in_preamble")));
993 EXPECT_THAT(TU.build().getLocalTopLevelDecls(), SizeIs(1));
994}
995
996// Recursive main-file include with #pragma once guard is OK.
997TEST(DiagnosticsTest, RecursivePreamblePragmaOnce) {
998 auto TU = TestTU::withCode(Code: R"cpp(
999 #pragma once
1000 #include "foo.h"
1001 int symbol;
1002 )cpp");
1003 TU.Filename = "foo.h";
1004 EXPECT_THAT(TU.build().getDiagnostics(),
1005 Not(Contains(diagName("pp_including_mainfile_in_preamble"))));
1006 EXPECT_THAT(TU.build().getLocalTopLevelDecls(), SizeIs(1));
1007}
1008
1009// Recursive main-file include with #ifndef guard should be OK.
1010// However, it's not yet recognized (incomplete at end of preamble).
1011TEST(DiagnosticsTest, RecursivePreambleIfndefGuard) {
1012 auto TU = TestTU::withCode(Code: R"cpp(
1013 #ifndef FOO
1014 #define FOO
1015 #include "foo.h" // error-ok
1016 int symbol;
1017 #endif
1018 )cpp");
1019 TU.Filename = "foo.h";
1020 // FIXME: should be no errors here.
1021 EXPECT_THAT(TU.build().getDiagnostics(),
1022 ElementsAre(diagName("pp_including_mainfile_in_preamble")));
1023 EXPECT_THAT(TU.build().getLocalTopLevelDecls(), SizeIs(1));
1024}
1025
1026TEST(DiagnosticsTest, PreambleWithPragmaAssumeNonnull) {
1027 auto TU = TestTU::withCode(Code: R"cpp(
1028#pragma clang assume_nonnull begin
1029void foo(int *x);
1030#pragma clang assume_nonnull end
1031)cpp");
1032 auto AST = TU.build();
1033 EXPECT_THAT(AST.getDiagnostics(), IsEmpty());
1034 const auto *X = cast<FunctionDecl>(Val: findDecl(AST, QName: "foo")).getParamDecl(i: 0);
1035 ASSERT_TRUE(X->getOriginalType()->getNullability() ==
1036 NullabilityKind::NonNull);
1037}
1038
1039TEST(DiagnosticsTest, PreambleHeaderWithBadPragmaAssumeNonnull) {
1040 Annotations Header(R"cpp(
1041#pragma clang assume_nonnull begin // error-ok
1042void foo(int *X);
1043)cpp");
1044 auto TU = TestTU::withCode(Code: R"cpp(
1045#include "foo.h" // unterminated assume_nonnull should not affect bar.
1046void bar(int *Y);
1047)cpp");
1048 TU.AdditionalFiles = {{"foo.h", std::string(Header.code())}};
1049 auto AST = TU.build();
1050 EXPECT_THAT(AST.getDiagnostics(),
1051 ElementsAre(diagName("pp_eof_in_assume_nonnull")));
1052 const auto *X = cast<FunctionDecl>(Val: findDecl(AST, QName: "foo")).getParamDecl(i: 0);
1053 ASSERT_TRUE(X->getOriginalType()->getNullability() ==
1054 NullabilityKind::NonNull);
1055 const auto *Y = cast<FunctionDecl>(Val: findDecl(AST, QName: "bar")).getParamDecl(i: 0);
1056 ASSERT_FALSE(Y->getOriginalType()->getNullability());
1057}
1058
1059TEST(DiagnosticsTest, InsideMacros) {
1060 Annotations Test(R"cpp(
1061 #define TEN 10
1062 #define RET(x) return x + 10
1063
1064 int* foo() {
1065 RET($foo[[0]]); // error-ok
1066 }
1067 int* bar() {
1068 return $bar[[TEN]];
1069 }
1070 )cpp");
1071 EXPECT_THAT(TestTU::withCode(Test.code()).build().getDiagnostics(),
1072 ElementsAre(Diag(Test.range("foo"),
1073 "cannot initialize return object of type "
1074 "'int *' with an rvalue of type 'int'"),
1075 Diag(Test.range("bar"),
1076 "cannot initialize return object of type "
1077 "'int *' with an rvalue of type 'int'")));
1078}
1079
1080TEST(DiagnosticsTest, NoFixItInMacro) {
1081 Annotations Test(R"cpp(
1082 #define Define(name) void name() {}
1083
1084 [[Define]](main) // error-ok
1085 )cpp");
1086 auto TU = TestTU::withCode(Code: Test.code());
1087 EXPECT_THAT(TU.build().getDiagnostics(),
1088 ElementsAre(AllOf(Diag(Test.range(), "'main' must return 'int'"),
1089 Not(withFix(_)))));
1090}
1091
1092TEST(DiagnosticsTest, PragmaSystemHeader) {
1093 Annotations Test("#pragma clang [[system_header]]\n");
1094 auto TU = TestTU::withCode(Code: Test.code());
1095 EXPECT_THAT(
1096 TU.build().getDiagnostics(),
1097 ElementsAre(AllOf(
1098 Diag(Test.range(), "#pragma system_header ignored in main file"))));
1099 TU.Filename = "TestTU.h";
1100 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
1101}
1102
1103TEST(ClangdTest, MSAsm) {
1104 // Parsing MS assembly tries to use the target MCAsmInfo, which we don't link.
1105 // We used to crash here. Now clang emits a diagnostic, which we filter out.
1106 llvm::InitializeAllTargetInfos(); // As in ClangdMain
1107 auto TU = TestTU::withCode(Code: "void fn() { __asm { cmp cl,64 } }");
1108 TU.ExtraArgs = {"-fms-extensions"};
1109 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
1110}
1111
1112TEST(DiagnosticsTest, ToLSP) {
1113 URIForFile MainFile =
1114 URIForFile::canonicalize(AbsPath: testPath(File: "foo/bar/main.cpp"), TUPath: "");
1115 URIForFile HeaderFile =
1116 URIForFile::canonicalize(AbsPath: testPath(File: "foo/bar/header.h"), TUPath: "");
1117
1118 clangd::Diag D;
1119 D.ID = clang::diag::err_undeclared_var_use;
1120 D.Tags = {DiagnosticTag::Unnecessary};
1121 D.Name = "undeclared_var_use";
1122 D.Source = clangd::Diag::Clang;
1123 D.Message = "something terrible happened";
1124 D.Range = {.start: pos(Line: 1, Character: 2), .end: pos(Line: 3, Character: 4)};
1125 D.InsideMainFile = true;
1126 D.Severity = DiagnosticsEngine::Error;
1127 D.File = "foo/bar/main.cpp";
1128 D.AbsFile = std::string(MainFile.file());
1129 D.OpaqueData["test"] = "bar";
1130
1131 clangd::Note NoteInMain;
1132 NoteInMain.Message = "declared somewhere in the main file";
1133 NoteInMain.Range = {.start: pos(Line: 5, Character: 6), .end: pos(Line: 7, Character: 8)};
1134 NoteInMain.Severity = DiagnosticsEngine::Remark;
1135 NoteInMain.File = "../foo/bar/main.cpp";
1136 NoteInMain.InsideMainFile = true;
1137 NoteInMain.AbsFile = std::string(MainFile.file());
1138
1139 D.Notes.push_back(x: NoteInMain);
1140
1141 clangd::Note NoteInHeader;
1142 NoteInHeader.Message = "declared somewhere in the header file";
1143 NoteInHeader.Range = {.start: pos(Line: 9, Character: 10), .end: pos(Line: 11, Character: 12)};
1144 NoteInHeader.Severity = DiagnosticsEngine::Note;
1145 NoteInHeader.File = "../foo/baz/header.h";
1146 NoteInHeader.InsideMainFile = false;
1147 NoteInHeader.AbsFile = std::string(HeaderFile.file());
1148 D.Notes.push_back(x: NoteInHeader);
1149
1150 clangd::Fix F;
1151 F.Message = "do something";
1152 D.Fixes.push_back(x: F);
1153
1154 // Diagnostics should turn into these:
1155 clangd::Diagnostic MainLSP;
1156 MainLSP.range = D.Range;
1157 MainLSP.severity = getSeverity(L: DiagnosticsEngine::Error);
1158 MainLSP.code = "undeclared_var_use";
1159 MainLSP.source = "clang";
1160 MainLSP.message =
1161 R"(Something terrible happened (fix available)
1162
1163main.cpp:6:7: remark: declared somewhere in the main file
1164
1165../foo/baz/header.h:10:11:
1166note: declared somewhere in the header file)";
1167 MainLSP.tags = {DiagnosticTag::Unnecessary};
1168 MainLSP.data = D.OpaqueData;
1169
1170 clangd::Diagnostic NoteInMainLSP;
1171 NoteInMainLSP.range = NoteInMain.Range;
1172 NoteInMainLSP.severity = getSeverity(L: DiagnosticsEngine::Remark);
1173 NoteInMainLSP.message = R"(Declared somewhere in the main file
1174
1175main.cpp:2:3: error: something terrible happened)";
1176
1177 ClangdDiagnosticOptions Opts;
1178 // Transform diagnostics and check the results.
1179 std::vector<std::pair<clangd::Diagnostic, std::vector<clangd::Fix>>> LSPDiags;
1180 toLSPDiags(D, File: MainFile, Opts,
1181 OutFn: [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix> Fixes) {
1182 LSPDiags.push_back(
1183 x: {std::move(LSPDiag),
1184 std::vector<clangd::Fix>(Fixes.begin(), Fixes.end())});
1185 });
1186
1187 EXPECT_THAT(
1188 LSPDiags,
1189 ElementsAre(Pair(equalToLSPDiag(MainLSP), ElementsAre(equalToFix(F))),
1190 Pair(equalToLSPDiag(NoteInMainLSP), IsEmpty())));
1191 EXPECT_EQ(LSPDiags[0].first.code, "undeclared_var_use");
1192 EXPECT_EQ(LSPDiags[0].first.source, "clang");
1193 EXPECT_EQ(LSPDiags[1].first.code, "");
1194 EXPECT_EQ(LSPDiags[1].first.source, "");
1195
1196 // Same thing, but don't flatten notes into the main list.
1197 LSPDiags.clear();
1198 Opts.EmitRelatedLocations = true;
1199 toLSPDiags(D, File: MainFile, Opts,
1200 OutFn: [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix> Fixes) {
1201 LSPDiags.push_back(
1202 x: {std::move(LSPDiag),
1203 std::vector<clangd::Fix>(Fixes.begin(), Fixes.end())});
1204 });
1205 MainLSP.message = "Something terrible happened (fix available)";
1206 DiagnosticRelatedInformation NoteInMainDRI;
1207 NoteInMainDRI.message = "Declared somewhere in the main file";
1208 NoteInMainDRI.location.range = NoteInMain.Range;
1209 NoteInMainDRI.location.uri = MainFile;
1210 MainLSP.relatedInformation = {NoteInMainDRI};
1211 DiagnosticRelatedInformation NoteInHeaderDRI;
1212 NoteInHeaderDRI.message = "Declared somewhere in the header file";
1213 NoteInHeaderDRI.location.range = NoteInHeader.Range;
1214 NoteInHeaderDRI.location.uri = HeaderFile;
1215 MainLSP.relatedInformation = {NoteInMainDRI, NoteInHeaderDRI};
1216 EXPECT_THAT(LSPDiags, ElementsAre(Pair(equalToLSPDiag(MainLSP),
1217 ElementsAre(equalToFix(F)))));
1218}
1219
1220struct SymbolWithHeader {
1221 std::string QName;
1222 std::string DeclaringFile;
1223 std::string IncludeHeader;
1224};
1225
1226std::unique_ptr<SymbolIndex>
1227buildIndexWithSymbol(llvm::ArrayRef<SymbolWithHeader> Syms) {
1228 SymbolSlab::Builder Slab;
1229 for (const auto &S : Syms) {
1230 Symbol Sym = cls(Name: S.QName);
1231 Sym.Flags |= Symbol::IndexedForCodeCompletion;
1232 Sym.CanonicalDeclaration.FileURI = S.DeclaringFile.c_str();
1233 Sym.Definition.FileURI = S.DeclaringFile.c_str();
1234 Sym.IncludeHeaders.emplace_back(Args: S.IncludeHeader, Args: 1, Args: Symbol::Include);
1235 Slab.insert(S: Sym);
1236 }
1237 return MemIndex::build(Symbols: std::move(Slab).build(), Refs: RefSlab(), Relations: RelationSlab());
1238}
1239
1240TEST(IncludeFixerTest, IncompleteType) {
1241 auto TU = TestTU::withHeaderCode(HeaderCode: "namespace ns { class X; } ns::X *x;");
1242 TU.ExtraArgs.push_back(x: "-std=c++20");
1243 auto Index = buildIndexWithSymbol(
1244 Syms: {SymbolWithHeader{.QName: "ns::X", .DeclaringFile: "unittest:///x.h", .IncludeHeader: "\"x.h\""}});
1245 TU.ExternalIndex = Index.get();
1246
1247 std::vector<std::pair<llvm::StringRef, llvm::StringRef>> Tests{
1248 {"incomplete_nested_name_spec", "[[ns::X::]]Nested n;"},
1249 {"incomplete_base_class", "class Y : [[ns::X]] {};"},
1250 {"incomplete_member_access", "auto i = x[[->]]f();"},
1251 {"incomplete_type", "auto& [[[]]m] = *x;"},
1252 {"init_incomplete_type",
1253 "struct C { static int f(ns::X&); }; int i = C::f([[{]]});"},
1254 {"bad_cast_incomplete", "auto a = [[static_cast]]<ns::X>(0);"},
1255 {"template_nontype_parm_incomplete", "template <ns::X [[foo]]> int a;"},
1256 {"typecheck_decl_incomplete_type", "ns::X [[var]];"},
1257 {"typecheck_incomplete_tag", "auto i = [[(*x)]]->f();"},
1258 {"typecheck_nonviable_condition_incomplete",
1259 "struct A { operator ns::X(); } a; const ns::X &[[b]] = a;"},
1260 {"invalid_incomplete_type_use", "auto var = [[ns::X()]];"},
1261 {"sizeof_alignof_incomplete_or_sizeless_type",
1262 "auto s = [[sizeof]](ns::X);"},
1263 {"for_range_incomplete_type", "void foo() { for (auto i : [[*]]x ) {} }"},
1264 {"func_def_incomplete_result", "ns::X [[func]] () {}"},
1265 {"field_incomplete_or_sizeless", "class M { ns::X [[member]]; };"},
1266 {"array_incomplete_or_sizeless_type", "auto s = [[(ns::X[]){}]];"},
1267 {"call_incomplete_return", "ns::X f(); auto fp = &f; auto z = [[fp()]];"},
1268 {"call_function_incomplete_return", "ns::X foo(); auto a = [[foo()]];"},
1269 {"call_incomplete_argument", "int m(ns::X); int i = m([[*x]]);"},
1270 {"switch_incomplete_class_type", "void a() { [[switch]](*x) {} }"},
1271 {"delete_incomplete_class_type", "void f() { [[delete]] *x; }"},
1272 {"-Wdelete-incomplete", "void f() { [[delete]] x; }"},
1273 {"dereference_incomplete_type",
1274 R"cpp(void f() { asm("" : "=r"([[*]]x)::); })cpp"},
1275 };
1276 for (auto Case : Tests) {
1277 Annotations Main(Case.second);
1278 TU.Code = Main.code().str() + "\n // error-ok";
1279 EXPECT_THAT(
1280 TU.build().getDiagnostics(),
1281 ElementsAre(AllOf(diagName(Case.first), hasRange(Main.range()),
1282 withFix(Fix(Range{}, "#include \"x.h\"\n",
1283 "Include \"x.h\" for symbol ns::X")))))
1284 << Case.second;
1285 }
1286}
1287
1288TEST(IncludeFixerTest, IncompleteEnum) {
1289 Symbol Sym = enm(Name: "X");
1290 Sym.Flags |= Symbol::IndexedForCodeCompletion;
1291 Sym.CanonicalDeclaration.FileURI = Sym.Definition.FileURI = "unittest:///x.h";
1292 Sym.IncludeHeaders.emplace_back(Args: "\"x.h\"", Args: 1, Args: Symbol::Include);
1293 SymbolSlab::Builder Slab;
1294 Slab.insert(S: Sym);
1295 auto Index =
1296 MemIndex::build(Symbols: std::move(Slab).build(), Refs: RefSlab(), Relations: RelationSlab());
1297
1298 TestTU TU;
1299 TU.ExternalIndex = Index.get();
1300 TU.ExtraArgs.push_back(x: "-std=c++20");
1301 TU.ExtraArgs.push_back(x: "-fno-ms-compatibility"); // else incomplete enum is OK
1302
1303 std::vector<std::pair<llvm::StringRef, llvm::StringRef>> Tests{
1304 {"incomplete_enum", "enum class X : int; using enum [[X]];"},
1305 {"underlying_type_of_incomplete_enum",
1306 "[[__underlying_type]](enum X) i;"},
1307 };
1308 for (auto Case : Tests) {
1309 Annotations Main(Case.second);
1310 TU.Code = Main.code().str() + "\n // error-ok";
1311 EXPECT_THAT(TU.build().getDiagnostics(),
1312 Contains(AllOf(diagName(Case.first), hasRange(Main.range()),
1313 withFix(Fix(Range{}, "#include \"x.h\"\n",
1314 "Include \"x.h\" for symbol X")))))
1315 << Case.second;
1316 }
1317}
1318
1319TEST(IncludeFixerTest, NoSuggestIncludeWhenNoDefinitionInHeader) {
1320 Annotations Test(R"cpp(// error-ok
1321$insert[[]]namespace ns {
1322 class X;
1323}
1324class Y : $base[[public ns::X]] {};
1325int main() {
1326 ns::X *x;
1327 x$access[[->]]f();
1328}
1329 )cpp");
1330 auto TU = TestTU::withCode(Code: Test.code());
1331 Symbol Sym = cls(Name: "ns::X");
1332 Sym.Flags |= Symbol::IndexedForCodeCompletion;
1333 Sym.CanonicalDeclaration.FileURI = "unittest:///x.h";
1334 Sym.Definition.FileURI = "unittest:///x.cc";
1335 Sym.IncludeHeaders.emplace_back(Args: "\"x.h\"", Args: 1, Args: Symbol::Include);
1336
1337 SymbolSlab::Builder Slab;
1338 Slab.insert(S: Sym);
1339 auto Index =
1340 MemIndex::build(Symbols: std::move(Slab).build(), Refs: RefSlab(), Relations: RelationSlab());
1341 TU.ExternalIndex = Index.get();
1342
1343 EXPECT_THAT(TU.build().getDiagnostics(),
1344 UnorderedElementsAre(
1345 Diag(Test.range("base"), "base class has incomplete type"),
1346 Diag(Test.range("access"),
1347 "member access into incomplete type 'ns::X'")));
1348}
1349
1350TEST(IncludeFixerTest, Typo) {
1351 Annotations Test(R"cpp(// error-ok
1352$insert[[]]namespace ns {
1353void foo() {
1354 $unqualified1[[X]] x;
1355 // No fix if the unresolved type is used as specifier. (ns::)X::Nested will be
1356 // considered the unresolved type.
1357 $unqualified2[[X]]::Nested n;
1358}
1359struct S : $base[[X]] {};
1360}
1361void bar() {
1362 ns::$qualified1[[X]] x; // ns:: is valid.
1363 ns::$qualified2[[X]](); // Error: no member in namespace
1364
1365 ::$global[[Global]] glob;
1366}
1367using Type = ns::$template[[Foo]]<int>;
1368 )cpp");
1369 auto TU = TestTU::withCode(Code: Test.code());
1370 auto Index = buildIndexWithSymbol(
1371 Syms: {SymbolWithHeader{.QName: "ns::X", .DeclaringFile: "unittest:///x.h", .IncludeHeader: "\"x.h\""},
1372 SymbolWithHeader{.QName: "Global", .DeclaringFile: "unittest:///global.h", .IncludeHeader: "\"global.h\""},
1373 SymbolWithHeader{.QName: "ns::Foo", .DeclaringFile: "unittest:///foo.h", .IncludeHeader: "\"foo.h\""}});
1374 TU.ExternalIndex = Index.get();
1375
1376 EXPECT_THAT(
1377 TU.build().getDiagnostics(),
1378 UnorderedElementsAre(
1379 AllOf(Diag(Test.range("unqualified1"), "unknown type name 'X'"),
1380 diagName("unknown_typename"),
1381 withFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
1382 "Include \"x.h\" for symbol ns::X"))),
1383 Diag(Test.range("unqualified2"), "use of undeclared identifier 'X'"),
1384 AllOf(Diag(Test.range("qualified1"),
1385 "no type named 'X' in namespace 'ns'"),
1386 diagName("typename_nested_not_found"),
1387 withFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
1388 "Include \"x.h\" for symbol ns::X"))),
1389 AllOf(Diag(Test.range("qualified2"),
1390 "no member named 'X' in namespace 'ns'"),
1391 diagName("no_member"),
1392 withFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
1393 "Include \"x.h\" for symbol ns::X"))),
1394 AllOf(Diag(Test.range("global"),
1395 "no type named 'Global' in the global namespace"),
1396 diagName("typename_nested_not_found"),
1397 withFix(Fix(Test.range("insert"), "#include \"global.h\"\n",
1398 "Include \"global.h\" for symbol Global"))),
1399 AllOf(Diag(Test.range("template"),
1400 "no template named 'Foo' in namespace 'ns'"),
1401 diagName("no_member_template"),
1402 withFix(Fix(Test.range("insert"), "#include \"foo.h\"\n",
1403 "Include \"foo.h\" for symbol ns::Foo"))),
1404 AllOf(Diag(Test.range("base"), "expected class name"),
1405 diagName("expected_class_name"),
1406 withFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
1407 "Include \"x.h\" for symbol ns::X")))));
1408}
1409
1410TEST(IncludeFixerTest, TypoInMacro) {
1411 auto TU = TestTU::withCode(Code: R"cpp(// error-ok
1412#define ID(T) T
1413X a1;
1414ID(X a2);
1415ns::X a3;
1416ID(ns::X a4);
1417namespace ns{};
1418ns::X a5;
1419ID(ns::X a6);
1420)cpp");
1421 auto Index = buildIndexWithSymbol(
1422 Syms: {SymbolWithHeader{.QName: "X", .DeclaringFile: "unittest:///x.h", .IncludeHeader: "\"x.h\""},
1423 SymbolWithHeader{.QName: "ns::X", .DeclaringFile: "unittest:///ns.h", .IncludeHeader: "\"x.h\""}});
1424 TU.ExternalIndex = Index.get();
1425 // FIXME: -fms-compatibility (which is default on windows) breaks the
1426 // ns::X cases when the namespace is undeclared. Find out why!
1427 TU.ExtraArgs = {"-fno-ms-compatibility"};
1428 EXPECT_THAT(TU.build().getDiagnostics(), Each(withFix(_)));
1429}
1430
1431TEST(IncludeFixerTest, MultipleMatchedSymbols) {
1432 Annotations Test(R"cpp(// error-ok
1433$insert[[]]namespace na {
1434namespace nb {
1435void foo() {
1436 $unqualified[[X]] x;
1437}
1438}
1439}
1440 )cpp");
1441 auto TU = TestTU::withCode(Code: Test.code());
1442 auto Index = buildIndexWithSymbol(
1443 Syms: {SymbolWithHeader{.QName: "na::X", .DeclaringFile: "unittest:///a.h", .IncludeHeader: "\"a.h\""},
1444 SymbolWithHeader{.QName: "na::nb::X", .DeclaringFile: "unittest:///b.h", .IncludeHeader: "\"b.h\""}});
1445 TU.ExternalIndex = Index.get();
1446
1447 EXPECT_THAT(TU.build().getDiagnostics(),
1448 UnorderedElementsAre(AllOf(
1449 Diag(Test.range("unqualified"), "unknown type name 'X'"),
1450 diagName("unknown_typename"),
1451 withFix(Fix(Test.range("insert"), "#include \"a.h\"\n",
1452 "Include \"a.h\" for symbol na::X"),
1453 Fix(Test.range("insert"), "#include \"b.h\"\n",
1454 "Include \"b.h\" for symbol na::nb::X")))));
1455}
1456
1457TEST(IncludeFixerTest, NoCrashMemberAccess) {
1458 Annotations Test(R"cpp(// error-ok
1459 struct X { int xyz; };
1460 void g() { X x; x.$[[xy]]; }
1461 )cpp");
1462 auto TU = TestTU::withCode(Code: Test.code());
1463 auto Index = buildIndexWithSymbol(
1464 Syms: SymbolWithHeader{.QName: "na::X", .DeclaringFile: "unittest:///a.h", .IncludeHeader: "\"a.h\""});
1465 TU.ExternalIndex = Index.get();
1466
1467 EXPECT_THAT(
1468 TU.build().getDiagnostics(),
1469 UnorderedElementsAre(Diag(Test.range(), "no member named 'xy' in 'X'")));
1470}
1471
1472TEST(IncludeFixerTest, UseCachedIndexResults) {
1473 // As index results for the identical request are cached, more than 5 fixes
1474 // are generated.
1475 Annotations Test(R"cpp(// error-ok
1476$insert[[]]void foo() {
1477 $x1[[X]] x;
1478 $x2[[X]] x;
1479 $x3[[X]] x;
1480 $x4[[X]] x;
1481 $x5[[X]] x;
1482 $x6[[X]] x;
1483 $x7[[X]] x;
1484}
1485
1486class X;
1487void bar(X *x) {
1488 x$a1[[->]]f();
1489 x$a2[[->]]f();
1490 x$a3[[->]]f();
1491 x$a4[[->]]f();
1492 x$a5[[->]]f();
1493 x$a6[[->]]f();
1494 x$a7[[->]]f();
1495}
1496 )cpp");
1497 auto TU = TestTU::withCode(Code: Test.code());
1498 auto Index =
1499 buildIndexWithSymbol(Syms: SymbolWithHeader{.QName: "X", .DeclaringFile: "unittest:///a.h", .IncludeHeader: "\"a.h\""});
1500 TU.ExternalIndex = Index.get();
1501
1502 auto Parsed = TU.build();
1503 for (const auto &D : Parsed.getDiagnostics()) {
1504 if (D.Fixes.size() != 1) {
1505 ADD_FAILURE() << "D.Fixes.size() != 1";
1506 continue;
1507 }
1508 EXPECT_EQ(D.Fixes[0].Message, std::string("Include \"a.h\" for symbol X"));
1509 }
1510}
1511
1512TEST(IncludeFixerTest, UnresolvedNameAsSpecifier) {
1513 Annotations Test(R"cpp(// error-ok
1514$insert[[]]namespace ns {
1515}
1516void g() { ns::$[[scope]]::X_Y(); }
1517 )cpp");
1518 TestTU TU;
1519 TU.Code = std::string(Test.code());
1520 // FIXME: Figure out why this is needed and remove it, PR43662.
1521 TU.ExtraArgs.push_back(x: "-fno-ms-compatibility");
1522 auto Index = buildIndexWithSymbol(
1523 Syms: SymbolWithHeader{.QName: "ns::scope::X_Y", .DeclaringFile: "unittest:///x.h", .IncludeHeader: "\"x.h\""});
1524 TU.ExternalIndex = Index.get();
1525
1526 EXPECT_THAT(
1527 TU.build().getDiagnostics(),
1528 UnorderedElementsAre(
1529 AllOf(Diag(Test.range(), "no member named 'scope' in namespace 'ns'"),
1530 diagName("no_member"),
1531 withFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
1532 "Include \"x.h\" for symbol ns::scope::X_Y")))));
1533}
1534
1535TEST(IncludeFixerTest, UnresolvedSpecifierWithSemaCorrection) {
1536 Annotations Test(R"cpp(// error-ok
1537$insert[[]]namespace clang {
1538void f() {
1539 // "clangd::" will be corrected to "clang::" by Sema.
1540 $q1[[clangd]]::$x[[X]] x;
1541 $q2[[clangd]]::$ns[[ns]]::Y y;
1542}
1543}
1544 )cpp");
1545 TestTU TU;
1546 TU.Code = std::string(Test.code());
1547 // FIXME: Figure out why this is needed and remove it, PR43662.
1548 TU.ExtraArgs.push_back(x: "-fno-ms-compatibility");
1549 auto Index = buildIndexWithSymbol(
1550 Syms: {SymbolWithHeader{.QName: "clang::clangd::X", .DeclaringFile: "unittest:///x.h", .IncludeHeader: "\"x.h\""},
1551 SymbolWithHeader{.QName: "clang::clangd::ns::Y", .DeclaringFile: "unittest:///y.h", .IncludeHeader: "\"y.h\""}});
1552 TU.ExternalIndex = Index.get();
1553
1554 EXPECT_THAT(
1555 TU.build().getDiagnostics(),
1556 UnorderedElementsAre(
1557 AllOf(Diag(Test.range("q1"), "use of undeclared identifier 'clangd'; "
1558 "did you mean 'clang'?"),
1559 diagName("undeclared_var_use_suggest"),
1560 withFix(_, // change clangd to clang
1561 Fix(Test.range("insert"), "#include \"x.h\"\n",
1562 "Include \"x.h\" for symbol clang::clangd::X"))),
1563 AllOf(Diag(Test.range("x"), "no type named 'X' in namespace 'clang'"),
1564 diagName("typename_nested_not_found"),
1565 withFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
1566 "Include \"x.h\" for symbol clang::clangd::X"))),
1567 AllOf(
1568 Diag(Test.range("q2"), "use of undeclared identifier 'clangd'; "
1569 "did you mean 'clang'?"),
1570 diagName("undeclared_var_use_suggest"),
1571 withFix(_, // change clangd to clang
1572 Fix(Test.range("insert"), "#include \"y.h\"\n",
1573 "Include \"y.h\" for symbol clang::clangd::ns::Y"))),
1574 AllOf(Diag(Test.range("ns"),
1575 "no member named 'ns' in namespace 'clang'"),
1576 diagName("no_member"),
1577 withFix(
1578 Fix(Test.range("insert"), "#include \"y.h\"\n",
1579 "Include \"y.h\" for symbol clang::clangd::ns::Y")))));
1580}
1581
1582TEST(IncludeFixerTest, SpecifiedScopeIsNamespaceAlias) {
1583 Annotations Test(R"cpp(// error-ok
1584$insert[[]]namespace a {}
1585namespace b = a;
1586namespace c {
1587 b::$[[X]] x;
1588}
1589 )cpp");
1590 auto TU = TestTU::withCode(Code: Test.code());
1591 auto Index = buildIndexWithSymbol(
1592 Syms: SymbolWithHeader{.QName: "a::X", .DeclaringFile: "unittest:///x.h", .IncludeHeader: "\"x.h\""});
1593 TU.ExternalIndex = Index.get();
1594
1595 EXPECT_THAT(TU.build().getDiagnostics(),
1596 UnorderedElementsAre(AllOf(
1597 Diag(Test.range(), "no type named 'X' in namespace 'a'"),
1598 diagName("typename_nested_not_found"),
1599 withFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
1600 "Include \"x.h\" for symbol a::X")))));
1601}
1602
1603TEST(IncludeFixerTest, NoCrashOnTemplateInstantiations) {
1604 Annotations Test(R"cpp(
1605 template <typename T> struct Templ {
1606 template <typename U>
1607 typename U::type operator=(const U &);
1608 };
1609
1610 struct A {
1611 Templ<char> s;
1612 A() { [[a]]; /*error-ok*/ } // crash if we compute scopes lazily.
1613 };
1614 )cpp");
1615
1616 auto TU = TestTU::withCode(Code: Test.code());
1617 auto Index = buildIndexWithSymbol(Syms: {});
1618 TU.ExternalIndex = Index.get();
1619
1620 EXPECT_THAT(
1621 TU.build().getDiagnostics(),
1622 ElementsAre(Diag(Test.range(), "use of undeclared identifier 'a'")));
1623}
1624
1625TEST(IncludeFixerTest, HeaderNamedInDiag) {
1626 Annotations Test(R"cpp(
1627 $insert[[]]int main() {
1628 [[printf]]("");
1629 }
1630 )cpp");
1631 auto TU = TestTU::withCode(Code: Test.code());
1632 TU.ExtraArgs = {"-xc", "-std=c99",
1633 "-Wno-error=implicit-function-declaration"};
1634 auto Index = buildIndexWithSymbol(Syms: {});
1635 TU.ExternalIndex = Index.get();
1636
1637 EXPECT_THAT(
1638 TU.build().getDiagnostics(),
1639 ElementsAre(AllOf(
1640 Diag(Test.range(), "call to undeclared library function 'printf' "
1641 "with type 'int (const char *, ...)'; ISO C99 "
1642 "and later do not support implicit function "
1643 "declarations"),
1644 withFix(Fix(Test.range("insert"), "#include <stdio.h>\n",
1645 "Include <stdio.h> for symbol printf")))));
1646
1647 TU.ExtraArgs = {"-xc", "-std=c89"};
1648 EXPECT_THAT(
1649 TU.build().getDiagnostics(),
1650 ElementsAre(AllOf(
1651 Diag(Test.range(), "implicitly declaring library function 'printf' "
1652 "with type 'int (const char *, ...)'"),
1653 withFix(Fix(Test.range("insert"), "#include <stdio.h>\n",
1654 "Include <stdio.h> for symbol printf")))));
1655}
1656
1657TEST(IncludeFixerTest, CImplicitFunctionDecl) {
1658 Annotations Test("void x() { [[foo]](); }");
1659 auto TU = TestTU::withCode(Code: Test.code());
1660 TU.Filename = "test.c";
1661 TU.ExtraArgs = {"-std=c99", "-Wno-error=implicit-function-declaration"};
1662
1663 Symbol Sym = func(Name: "foo");
1664 Sym.Flags |= Symbol::IndexedForCodeCompletion;
1665 Sym.CanonicalDeclaration.FileURI = "unittest:///foo.h";
1666 Sym.IncludeHeaders.emplace_back(Args: "\"foo.h\"", Args: 1, Args: Symbol::Include);
1667
1668 SymbolSlab::Builder Slab;
1669 Slab.insert(S: Sym);
1670 auto Index =
1671 MemIndex::build(Symbols: std::move(Slab).build(), Refs: RefSlab(), Relations: RelationSlab());
1672 TU.ExternalIndex = Index.get();
1673
1674 EXPECT_THAT(
1675 TU.build().getDiagnostics(),
1676 ElementsAre(AllOf(
1677 Diag(Test.range(),
1678 "call to undeclared function 'foo'; ISO C99 and later do not "
1679 "support implicit function declarations"),
1680 withFix(Fix(Range{}, "#include \"foo.h\"\n",
1681 "Include \"foo.h\" for symbol foo")))));
1682
1683 TU.ExtraArgs = {"-std=c89", "-Wall"};
1684 EXPECT_THAT(TU.build().getDiagnostics(),
1685 ElementsAre(AllOf(
1686 Diag(Test.range(), "implicit declaration of function 'foo'"),
1687 withFix(Fix(Range{}, "#include \"foo.h\"\n",
1688 "Include \"foo.h\" for symbol foo")))));
1689}
1690
1691TEST(DiagsInHeaders, DiagInsideHeader) {
1692 Annotations Main(R"cpp(
1693 #include [["a.h"]]
1694 void foo() {})cpp");
1695 Annotations Header("[[no_type_spec]]; // error-ok");
1696 TestTU TU = TestTU::withCode(Code: Main.code());
1697 TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};
1698 EXPECT_THAT(TU.build().getDiagnostics(),
1699 UnorderedElementsAre(AllOf(
1700 Diag(Main.range(), "in included file: a type specifier is "
1701 "required for all declarations"),
1702 withNote(Diag(Header.range(), "error occurred here")))));
1703}
1704
1705TEST(DiagsInHeaders, DiagInTransitiveInclude) {
1706 Annotations Main(R"cpp(
1707 #include [["a.h"]]
1708 void foo() {})cpp");
1709 TestTU TU = TestTU::withCode(Code: Main.code());
1710 TU.AdditionalFiles = {{"a.h", "#include \"b.h\""},
1711 {"b.h", "no_type_spec; // error-ok"}};
1712 EXPECT_THAT(TU.build().getDiagnostics(),
1713 UnorderedElementsAre(Diag(Main.range(),
1714 "in included file: a type specifier is "
1715 "required for all declarations")));
1716}
1717
1718TEST(DiagsInHeaders, DiagInMultipleHeaders) {
1719 Annotations Main(R"cpp(
1720 #include $a[["a.h"]]
1721 #include $b[["b.h"]]
1722 void foo() {})cpp");
1723 TestTU TU = TestTU::withCode(Code: Main.code());
1724 TU.AdditionalFiles = {{"a.h", "no_type_spec; // error-ok"},
1725 {"b.h", "no_type_spec; // error-ok"}};
1726 EXPECT_THAT(TU.build().getDiagnostics(),
1727 UnorderedElementsAre(
1728 Diag(Main.range("a"), "in included file: a type specifier is "
1729 "required for all declarations"),
1730 Diag(Main.range("b"), "in included file: a type specifier is "
1731 "required for all declarations")));
1732}
1733
1734TEST(DiagsInHeaders, PreferExpansionLocation) {
1735 Annotations Main(R"cpp(
1736 #include [["a.h"]]
1737 #include "b.h"
1738 void foo() {})cpp");
1739 TestTU TU = TestTU::withCode(Code: Main.code());
1740 TU.AdditionalFiles = {
1741 {"a.h", "#include \"b.h\"\n"},
1742 {"b.h", "#ifndef X\n#define X\nno_type_spec; // error-ok\n#endif"}};
1743 EXPECT_THAT(TU.build().getDiagnostics(),
1744 Contains(Diag(Main.range(), "in included file: a type specifier "
1745 "is required for all declarations")));
1746}
1747
1748TEST(DiagsInHeaders, PreferExpansionLocationMacros) {
1749 Annotations Main(R"cpp(
1750 #define X
1751 #include "a.h"
1752 #undef X
1753 #include [["b.h"]]
1754 void foo() {})cpp");
1755 TestTU TU = TestTU::withCode(Code: Main.code());
1756 TU.AdditionalFiles = {
1757 {"a.h", "#include \"c.h\"\n"},
1758 {"b.h", "#include \"c.h\"\n"},
1759 {"c.h", "#ifndef X\n#define X\nno_type_spec; // error-ok\n#endif"}};
1760 EXPECT_THAT(TU.build().getDiagnostics(),
1761 UnorderedElementsAre(Diag(Main.range(),
1762 "in included file: a type specifier is "
1763 "required for all declarations")));
1764}
1765
1766TEST(DiagsInHeaders, LimitDiagsOutsideMainFile) {
1767 Annotations Main(R"cpp(
1768 #include [["a.h"]]
1769 #include "b.h"
1770 void foo() {})cpp");
1771 TestTU TU = TestTU::withCode(Code: Main.code());
1772 TU.AdditionalFiles = {{"a.h", "#include \"c.h\"\n"},
1773 {"b.h", "#include \"c.h\"\n"},
1774 {"c.h", R"cpp(
1775 #ifndef X
1776 #define X
1777 no_type_spec_0; // error-ok
1778 no_type_spec_1;
1779 no_type_spec_2;
1780 no_type_spec_3;
1781 no_type_spec_4;
1782 no_type_spec_5;
1783 no_type_spec_6;
1784 no_type_spec_7;
1785 no_type_spec_8;
1786 no_type_spec_9;
1787 no_type_spec_10;
1788 #endif)cpp"}};
1789 EXPECT_THAT(TU.build().getDiagnostics(),
1790 UnorderedElementsAre(Diag(Main.range(),
1791 "in included file: a type specifier is "
1792 "required for all declarations")));
1793}
1794
1795TEST(DiagsInHeaders, OnlyErrorOrFatal) {
1796 Annotations Main(R"cpp(
1797 #include [["a.h"]]
1798 void foo() {})cpp");
1799 Annotations Header(R"cpp(
1800 [[no_type_spec]]; // error-ok
1801 int x = 5/0;)cpp");
1802 TestTU TU = TestTU::withCode(Code: Main.code());
1803 TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};
1804 EXPECT_THAT(TU.build().getDiagnostics(),
1805 UnorderedElementsAre(AllOf(
1806 Diag(Main.range(), "in included file: a type specifier is "
1807 "required for all declarations"),
1808 withNote(Diag(Header.range(), "error occurred here")))));
1809}
1810
1811TEST(DiagsInHeaders, OnlyDefaultErrorOrFatal) {
1812 Annotations Main(R"cpp(
1813 #include [["a.h"]] // get unused "foo" warning when building preamble.
1814 )cpp");
1815 Annotations Header(R"cpp(
1816 namespace { void foo() {} }
1817 void func() {foo();} ;)cpp");
1818 TestTU TU = TestTU::withCode(Code: Main.code());
1819 TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};
1820 // promote warnings to errors.
1821 TU.ExtraArgs = {"-Werror", "-Wunused"};
1822 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
1823}
1824
1825TEST(DiagsInHeaders, FromNonWrittenSources) {
1826 Annotations Main(R"cpp(
1827 #include [["a.h"]]
1828 void foo() {})cpp");
1829 Annotations Header(R"cpp(
1830 int x = 5/0;
1831 int b = [[FOO]]; // error-ok)cpp");
1832 TestTU TU = TestTU::withCode(Code: Main.code());
1833 TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};
1834 TU.ExtraArgs = {"-DFOO=NOOO"};
1835 EXPECT_THAT(TU.build().getDiagnostics(),
1836 UnorderedElementsAre(AllOf(
1837 Diag(Main.range(),
1838 "in included file: use of undeclared identifier 'NOOO'"),
1839 withNote(Diag(Header.range(), "error occurred here")))));
1840}
1841
1842TEST(DiagsInHeaders, ErrorFromMacroExpansion) {
1843 Annotations Main(R"cpp(
1844 void bar() {
1845 int fo; // error-ok
1846 #include [["a.h"]]
1847 })cpp");
1848 Annotations Header(R"cpp(
1849 #define X foo
1850 X;)cpp");
1851 TestTU TU = TestTU::withCode(Code: Main.code());
1852 TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};
1853 EXPECT_THAT(TU.build().getDiagnostics(),
1854 UnorderedElementsAre(
1855 Diag(Main.range(), "in included file: use of undeclared "
1856 "identifier 'foo'; did you mean 'fo'?")));
1857}
1858
1859TEST(DiagsInHeaders, ErrorFromMacroArgument) {
1860 Annotations Main(R"cpp(
1861 void bar() {
1862 int fo; // error-ok
1863 #include [["a.h"]]
1864 })cpp");
1865 Annotations Header(R"cpp(
1866 #define X(arg) arg
1867 X(foo);)cpp");
1868 TestTU TU = TestTU::withCode(Code: Main.code());
1869 TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};
1870 EXPECT_THAT(TU.build().getDiagnostics(),
1871 UnorderedElementsAre(
1872 Diag(Main.range(), "in included file: use of undeclared "
1873 "identifier 'foo'; did you mean 'fo'?")));
1874}
1875
1876TEST(IgnoreDiags, FromNonWrittenInclude) {
1877 TestTU TU;
1878 TU.ExtraArgs.push_back(x: "--include=a.h");
1879 TU.AdditionalFiles = {{"a.h", "void main();"}};
1880 // The diagnostic "main must return int" is from the header, we don't attempt
1881 // to render it in the main file as there is no written location there.
1882 EXPECT_THAT(TU.build().getDiagnostics(), UnorderedElementsAre());
1883}
1884
1885TEST(ToLSPDiag, RangeIsInMain) {
1886 ClangdDiagnosticOptions Opts;
1887 clangd::Diag D;
1888 D.Range = {.start: pos(Line: 1, Character: 2), .end: pos(Line: 3, Character: 4)};
1889 D.Notes.emplace_back();
1890 Note &N = D.Notes.back();
1891 N.Range = {.start: pos(Line: 2, Character: 3), .end: pos(Line: 3, Character: 4)};
1892
1893 D.InsideMainFile = true;
1894 N.InsideMainFile = false;
1895 toLSPDiags(D, File: {}, Opts,
1896 OutFn: [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {
1897 EXPECT_EQ(LSPDiag.range, D.Range);
1898 });
1899
1900 D.InsideMainFile = false;
1901 N.InsideMainFile = true;
1902 toLSPDiags(D, File: {}, Opts,
1903 OutFn: [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {
1904 EXPECT_EQ(LSPDiag.range, N.Range);
1905 });
1906}
1907
1908TEST(ParsedASTTest, ModuleSawDiag) {
1909 TestTU TU;
1910
1911 auto AST = TU.build();
1912 #if 0
1913 EXPECT_THAT(AST.getDiagnostics(),
1914 testing::Contains(Diag(Code.range(), KDiagMsg.str())));
1915 #endif
1916}
1917
1918TEST(Preamble, EndsOnNonEmptyLine) {
1919 TestTU TU;
1920 TU.ExtraArgs = {"-Wnewline-eof"};
1921
1922 {
1923 TU.Code = "#define FOO\n void bar();\n";
1924 auto AST = TU.build();
1925 EXPECT_THAT(AST.getDiagnostics(), IsEmpty());
1926 }
1927 {
1928 Annotations Code("#define FOO[[]]");
1929 TU.Code = Code.code().str();
1930 auto AST = TU.build();
1931 EXPECT_THAT(
1932 AST.getDiagnostics(),
1933 testing::Contains(Diag(Code.range(), "no newline at end of file")));
1934 }
1935}
1936
1937TEST(Diagnostics, Tags) {
1938 TestTU TU;
1939 TU.ExtraArgs = {"-Wunused", "-Wdeprecated"};
1940 Annotations Test(R"cpp(
1941 void bar() __attribute__((deprecated));
1942 void foo() {
1943 int $unused[[x]];
1944 $deprecated[[bar]]();
1945 })cpp");
1946 TU.Code = Test.code().str();
1947 EXPECT_THAT(TU.build().getDiagnostics(),
1948 UnorderedElementsAre(
1949 AllOf(Diag(Test.range("unused"), "unused variable 'x'"),
1950 withTag(DiagnosticTag::Unnecessary)),
1951 AllOf(Diag(Test.range("deprecated"), "'bar' is deprecated"),
1952 withTag(DiagnosticTag::Deprecated))));
1953
1954 Test = Annotations(R"cpp(
1955 $typedef[[typedef int INT]];
1956 )cpp");
1957 TU.Code = Test.code();
1958 TU.ClangTidyProvider = addTidyChecks(Checks: "modernize-use-using");
1959 EXPECT_THAT(
1960 TU.build().getDiagnostics(),
1961 ifTidyChecks(UnorderedElementsAre(
1962 AllOf(Diag(Test.range("typedef"), "use 'using' instead of 'typedef'"),
1963 withTag(DiagnosticTag::Deprecated)))));
1964}
1965
1966TEST(Diagnostics, DeprecatedDiagsAreHints) {
1967 ClangdDiagnosticOptions Opts;
1968 std::optional<clangd::Diagnostic> Diag;
1969 clangd::Diag D;
1970 D.Range = {.start: pos(Line: 1, Character: 2), .end: pos(Line: 3, Character: 4)};
1971 D.InsideMainFile = true;
1972
1973 // Downgrade warnings with deprecated tags to remark.
1974 D.Tags = {Deprecated};
1975 D.Severity = DiagnosticsEngine::Warning;
1976 toLSPDiags(D, File: {}, Opts,
1977 OutFn: [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {
1978 Diag = std::move(LSPDiag);
1979 });
1980 EXPECT_EQ(Diag->severity, getSeverity(DiagnosticsEngine::Remark));
1981 Diag.reset();
1982
1983 // Preserve errors.
1984 D.Severity = DiagnosticsEngine::Error;
1985 toLSPDiags(D, File: {}, Opts,
1986 OutFn: [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {
1987 Diag = std::move(LSPDiag);
1988 });
1989 EXPECT_EQ(Diag->severity, getSeverity(DiagnosticsEngine::Error));
1990 Diag.reset();
1991
1992 // No-op without tag.
1993 D.Tags = {};
1994 D.Severity = DiagnosticsEngine::Warning;
1995 toLSPDiags(D, File: {}, Opts,
1996 OutFn: [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {
1997 Diag = std::move(LSPDiag);
1998 });
1999 EXPECT_EQ(Diag->severity, getSeverity(DiagnosticsEngine::Warning));
2000}
2001
2002TEST(DiagnosticsTest, IncludeCleaner) {
2003 Annotations Test(R"cpp(
2004$fix[[ $diag[[#include "unused.h"]]
2005]]
2006 #include "used.h"
2007
2008 #include "ignore.h"
2009
2010 #include <system_header.h>
2011
2012 void foo() {
2013 used();
2014 }
2015 )cpp");
2016 TestTU TU;
2017 TU.Code = Test.code().str();
2018 TU.AdditionalFiles["unused.h"] = R"cpp(
2019 #pragma once
2020 void unused() {}
2021 )cpp";
2022 TU.AdditionalFiles["used.h"] = R"cpp(
2023 #pragma once
2024 void used() {}
2025 )cpp";
2026 TU.AdditionalFiles["ignore.h"] = R"cpp(
2027 #pragma once
2028 void ignore() {}
2029 )cpp";
2030 TU.AdditionalFiles["system/system_header.h"] = "";
2031 TU.ExtraArgs = {"-isystem" + testPath(File: "system")};
2032 Config Cfg;
2033 Cfg.Diagnostics.UnusedIncludes = Config::IncludesPolicy::Strict;
2034 // Set filtering.
2035 Cfg.Diagnostics.Includes.IgnoreHeader.emplace_back(
2036 args: [](llvm::StringRef Header) { return Header.ends_with(Suffix: "ignore.h"); });
2037 WithContextValue WithCfg(Config::Key, std::move(Cfg));
2038 auto AST = TU.build();
2039 EXPECT_THAT(
2040 AST.getDiagnostics(),
2041 Contains(AllOf(
2042 Diag(Test.range("diag"),
2043 "included header unused.h is not used directly"),
2044 withTag(DiagnosticTag::Unnecessary), diagSource(Diag::Clangd),
2045 withFix(Fix(Test.range("fix"), "", "remove #include directive")))));
2046 auto &Diag = AST.getDiagnostics().front();
2047 EXPECT_THAT(getDiagnosticDocURI(Diag.Source, Diag.ID, Diag.Name),
2048 llvm::ValueIs(Not(IsEmpty())));
2049 Cfg.Diagnostics.SuppressAll = true;
2050 WithContextValue SuppressAllWithCfg(Config::Key, std::move(Cfg));
2051 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
2052 Cfg.Diagnostics.SuppressAll = false;
2053 Cfg.Diagnostics.Suppress = {"unused-includes"};
2054 WithContextValue SuppressFilterWithCfg(Config::Key, std::move(Cfg));
2055 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
2056}
2057
2058TEST(DiagnosticsTest, FixItFromHeader) {
2059 llvm::StringLiteral Header(R"cpp(
2060 void foo(int *);
2061 void foo(int *, int);)cpp");
2062 Annotations Source(R"cpp(
2063 /*error-ok*/
2064 void bar() {
2065 int x;
2066 $diag[[foo]]($fix[[]]x, 1);
2067 })cpp");
2068 TestTU TU;
2069 TU.Code = Source.code().str();
2070 TU.HeaderCode = Header.str();
2071 EXPECT_THAT(
2072 TU.build().getDiagnostics(),
2073 UnorderedElementsAre(AllOf(
2074 Diag(Source.range("diag"), "no matching function for call to 'foo'"),
2075 withFix(Fix(Source.range("fix"), "&",
2076 "candidate function not viable: no known conversion from "
2077 "'int' to 'int *' for 1st argument; take the address of "
2078 "the argument with &")))));
2079}
2080
2081TEST(DiagnosticsTest, UnusedInHeader) {
2082 // Clang diagnoses unused static inline functions outside headers.
2083 auto TU = TestTU::withCode(Code: "static inline void foo(void) {}");
2084 TU.ExtraArgs.push_back(x: "-Wunused-function");
2085 TU.Filename = "test.c";
2086 EXPECT_THAT(TU.build().getDiagnostics(),
2087 ElementsAre(withID(diag::warn_unused_function)));
2088 // Sema should recognize a *.h file open in clangd as a header.
2089 // https://github.com/clangd/vscode-clangd/issues/360
2090 TU.Filename = "test.h";
2091 EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
2092}
2093
2094} // namespace
2095} // namespace clangd
2096} // namespace clang
2097

source code of clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp