Page MenuHomePhabricator

[clangd] Add support for missing includes analysis.

Authored by VitaNuo on Feb 7 2023, 6:41 AM.

Diff Detail

Event Timeline

There are a very large number of changes, so older changes are hidden. Show Older Changes
VitaNuo added inline comments.Thu, Feb 23, 8:46 AM

Ok should be done now.


Thanks. This might very well be the case, but this comment also seems to suggest some premature optimization (in a way). It totally makes sense to re-use what's re-usable, but this sort of refactoring really only makes sense once we have a clear use case (and get there :)


Ok, agreed. Storing symbols now. Having only unused include analysis on is a convincing use case.

VitaNuo updated this revision to Diff 499886.Thu, Feb 23, 9:07 AM

Upload once again.

VitaNuo updated this revision to Diff 499896.Thu, Feb 23, 9:23 AM

Merge upstream changes.

thanks! looks amazing, we're missing a little bit of test coverage though






what about just resolvedPath, if you'd rather keep the verb, i think get makes more sense than find. we're not really searching anything.


nit: you can directly return SymProvider.physical()->tryGetRealPathName(); (same for other 2 cases) and have an llvm_unreachable("Unknown symbol kind"); after the switch statement.


in this and the next case we need to trim <>"


same as above, either just symbolName or get


again you can just return here and below


getName is a StringRef, and unfortunately there are some platforms (like darwin) that don't support implicit conversion from stringrefs to std::string. so can you call .str() explicitly in the end?


i think for now this should be

if (Cfg.Diagnostics.MissingIncludes == Config::IncludesPolicy::Strict ||
  Cfg.Diagnostics.UnusedIncludes == Config::IncludesPolicy::Experiment) {

otherwise we'll run both legacy and new analysis for UnusedIncludes == Strict


this is pointing at the declaration inside b.h not to the reference inside the main file. are you sure this test passes?


can you also add a reference (and declaration) for std::vector, and have an IWYU private pragma in one of the headers to test code paths that spell verbatim and standard headers? also having some diagnostic suppressed via IgnoreHeaders is important to check


can you make one of these names qualified? e.g. namespace ns { struct Bar { void f(); }; }

VitaNuo updated this revision to Diff 502084.Fri, Mar 3, 2:44 AM
VitaNuo marked 23 inline comments as done.

Improve test coverage.

VitaNuo marked an inline comment as done.Fri, Mar 3, 2:47 AM

Thank you for all the thoughtful comments!


Ok, let's call it get. I do prefer verbs for methods, that's correct.


Yes, all the tests pass.
D is a Decl from the main file, otherwise it wouldn't have passed the safeguard if (!SM.isWrittenInMainFile(SM.getExpansionLoc(D->getLocation()))) continue; above.


Thank you for the great tips on improving test coverage!

In fact, I had to also introduce support for private pragmas, as they were not taken care of. Hopefully, the solution will make sense to you.

thanks, looks great!


we should respect the style configurations (sorry for missing this in previous iterations).

you can get the relevant style with: clang::format::getStyle, which has an IncludeStyle. in case the getStyle fails, we should fallback to clang::format::getLLVMStyle as we do in other places. you can get at the relevant VFS instance through sourcemanager.


you can directly use !Pragmas->isPrivate(Inc->Resolved) here, instead of getpublic


this check seems to be new. what's the reason for rejecting private providers? I can see that we might want to be conservative by not inserting private providers, but treating symbols as unsatisfied when a private provider is already included doesn't feel right. e.g. the code being analyzed might be allowed to depend on this private header, because it's also part of the library, or it's the public header that's exposing this private header. in such a scenario we shouldn't try to insert the public header again. is there a more concrete issue this code is trying to address?


nit: braces


this is passing because bool BDeclFound; is uninitialized above, if you set it to bool BDeclFound = false; you should see the test fail.

there's no declaration for b inside the main file, it's declared in b.h and referenced inside the main file. you still need to search for the decl (without the constraint of being written in main file), use it to build an include_cleaner::Symbol, and use a clangd::Annotation range for the range of the reference.

it might be easer to write this as:

const NamedDecl* B = nullptr;
for (...) {
 B = D;
// build expected diagnostic info based on B and check that it's equal to what we've produced

i think the example for std::vector is solid, and IWYU pragma private needs a little adjustment.


we should include private.h through some indirection (not public.h) to check IWYU pragma private spellings are respected.


name this range as bar instead of d?


could you add a comment here saying this shouldn't be diagnosed?

VitaNuo updated this revision to Diff 503012.Tue, Mar 7, 6:03 AM
VitaNuo marked 10 inline comments as done.

Address review comments.

Thanks for the comments!


Ok makes sense. No, I guess I was just confused, because I understood that you wanted a test that includes "private.h" with a diagnostic generated saying that "public.h" should be included instead, so I assumed that was expected behaviour. But that's not what you meant, so I misunderstood.


Didn't know there was a difference between uninitialized and false..

Thanks for the idea with ASSERT_TRUE(Decl). Please check out the new version.

kadircet added inline comments.Tue, Mar 7, 6:25 AM

creating a copy of LLVM style unnecessarily all the time is not really great, can you move this into the failure case instead?

also you can drop the clang:: here and elsewhere, as this code is already part of clang:: namespace.


as mentioned above we also need to make sure we're passing the relevant VFS instance inside the source manager, rather than using the real file system (as some clients rely on the VFS).



to be consistent with other places.


can you also elog this error? as it should be rare and when this goes wrong, having this mentioned in the logs are really useful for debugging (since the failure is actually outside of clangd, it usually means a malformed config file somewhere)


nit: instead of using a point, can you use a range here instead (i.e. [[b]])? afterwards you can have a FileRange pointing at both offsets, rather than relying on the length of the identifier.


rest of the code here doesn't really belong to the for loop, can you take them out?

VitaNuo updated this revision to Diff 503033.Tue, Mar 7, 7:11 AM
VitaNuo marked 6 inline comments as done.

Address review comments.

Thanks for the comments.

kadircet accepted this revision.Tue, Mar 7, 7:24 AM

thanks for bearing with me, let's ship it!


nit: this could be shorter with

auto FileStyle = format::getStyle(..);
if (!FileStyle) {
  FileStyle = format::getLLVMStyle();
tooling::HeaderIncludes HeaderIncludes(AST.tuPath(), Code, FileStyle->IncludeStyle);


size_t Start = llvm::cantFail(positionToOffset(MainFile.code(), Range.start));
size_t End = llvm::cantFail(positionToOffset(MainFile.code(), Range.end));

no need for EXPECT_FALSE(..takeError())s as llvm::cantFail will fail (no pun intended :P), static_casts are also redundant


it'd be better to ASSERT_TRUE(BDecl); right after the for loop, as rest of the code will crash (and even trigger undefined behavior because we're dereferencing nullptr in failure case).

difference between ASSERT_X and EXPECT_X macros are, the former will stop execution of the particular test (hence we'll never trigger a nullptr deref with ASSERT_TRUE), whereas the latter just prints the failure, but doesn't abort the execution of test (hence helps print multiple failures at once, when they're non fatal).

This revision is now accepted and ready to land.Tue, Mar 7, 7:24 AM
VitaNuo updated this revision to Diff 503043.Tue, Mar 7, 7:48 AM
VitaNuo marked 3 inline comments as done.

Address review comments.

VitaNuo updated this revision to Diff 503045.Tue, Mar 7, 7:57 AM

Rebase to main.

This revision was landed with ongoing or failed builds.Tue, Mar 7, 8:07 AM
This revision was automatically updated to reflect the committed changes.
PiotrZSL added a subscriber: PiotrZSL.EditedTue, Mar 7, 1:47 PM

This change broke regression, none-one read: "This revision was landed with ongoing or failed builds."
./ClangdTests.exe/IncludeCleaner/GenerateMissingHeaderDiags tests fails on windows

thakis added a subscriber: thakis.Tue, Mar 7, 6:28 PM

This breaks tests on windows:

Please take a look and revert for now if it takes a while to fix.

thakis added a comment.Tue, Mar 7, 7:14 PM

Oh, that was reported a while ago already. Reverted in 2eb5ac99a76dbbf8ac68c538211fabeaa5ac0bfd for now.

hokein added a subscriber: hokein.Wed, Mar 8, 2:52 AM
hokein added inline comments.

Looks like this filter doesn't work on windows (the / vs \ path separator might be the root cause here), I think a fix can be

  • change the check to return Header.endsWith("buzz.h")
  • or return Header == testPath("buzz.h", llvm::sys::path::Style::posix).

@VitaNuo Why did you recommit this again without any fix, breaking regression again.

VitaNuo reopened this revision.Wed, Mar 8, 4:20 AM
This revision is now accepted and ready to land.Wed, Mar 8, 4:20 AM
VitaNuo updated this revision to Diff 503315.Wed, Mar 8, 4:20 AM

Try to fix windows build.

This revision was landed with ongoing or failed builds.Wed, Mar 8, 4:31 AM
This revision was automatically updated to reflect the committed changes.
VitaNuo reopened this revision.Wed, Mar 8, 4:43 AM
This revision is now accepted and ready to land.Wed, Mar 8, 4:43 AM
VitaNuo updated this revision to Diff 503322.Wed, Mar 8, 4:43 AM

Try another approach.

VitaNuo updated this revision to Diff 503335.Wed, Mar 8, 5:29 AM

Fix formatting.

VitaNuo closed this revision.Mon, Mar 13, 2:57 AM