Clangd won't find all references when invoked from header

I’m using clangd inside emacs.
I’ve turned on background index to find references in the whole project.
When I run xref-find-references in one of the cpp files, all references, including the function prototype inside the header shows up.
But when I run it in the header file, only the function prototype shows up.

To see what’s going on, I’ve used clang-indexer to generate an index file.
It seems that the references in the header has Kind 9 whereas those in cpp has Kind 12, which seems to be the reason for the behavior.

Can anyone help me make clangd return all references even when invoked on a function prototype in the header??

--- !Refs                                             
ID:              38331D70E0F98E4A                     
References:                                           
  - Kind:            9                                
    Location:                                         
      FileURI:         'file:///media/hdd/proj/xxx/xxx/header.h
      Start:                                          
        Line:            19                           
        Column:          0                            
      End:                                            
        Line:            19                           
        Column:          18                           
  - Kind:            12                               
    Location:                                         
      FileURI:         'file:///media/hdd/proj/xxx/xxx/AAA.cpp
      Start:                                          
        Line:            398                          
        Column:          2                            
      End:                                            
        Line:            398                          
        Column:          20                           
  - Kind:            12                               
    Location:                                         
      FileURI:         'file:///media/hdd/proj/xxx/xxx/BBB.cpp
      Start:                                          
        Line:            399                          
        Column:          2                            
      End:                                            
        Line:            399                          
        Column:          20                           
  - Kind:            12                               
    Location:                                         
      FileURI:         'file:///media/hdd/proj/xxx/xxx/CCC.cpp
      Start:                                          
        Line:            427                          
        Column:          4                            
      End:                                            
        Line:            427                          
        Column:          2                            

The RefKind being 9 vs. 12 is expected, that just identifies the occurrence in the header file as being the declaration (9 is Declaration | Spelled from these flags) vs. the occurrences in the source files as being references (12 is Reference | Spelled).

It’s hard to diagnose the issue without being able to reproduce it, but a few theories:

  • The .h extension is ambiguous between C and C++, is clangd parsing the header file in C++ mode? In C mode function signatures are mangled differently and the same signature can produce a different ID.
  • Presumably, the header file does not have its own entry in compile_commands.json, so when you open it in the editor, clangd infers the compile command for it from another file. Which file is that? (Search for “inferred from” in the clangd logs.) Is it one of the source files that contains references to the function? If not, can you try adding a reference to the function in that source file, and see if find-refs on the reference in that source file finds the other references?
1 Like

I guess clangd is opening .h files as c, as the following log shows.

{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"languageId":"c", ...

Is there a way to force it to load it in c++ mode?

You can create a project config file (.clangd in the project root) containing:

If:
  PathMatch: .*\.h

CompileFlags:
  Add: [-xc++-header]

This instructs clangd to add -xc++-header to the compile command for all .h files in the project. If you have a mixture of C and C++ headers, you can make the PathMatch more specific to e.g. only apply to headers in a particular subdirectory.

1 Like

I made the config file, but nothing changed.
The languageID field still shows c.

I’m using clangd.dex file generated by clangd-indexer for debugging, and it only shows one symbol for the function of interest, and the Refs section lists both definition and references.
Does that mean it’s not the first problem?

I noticed a strange behavior.
If I open cpp file first, and find ref, it includes the definition in the header file.
But once I open the header file, find ref in cpp does not include the definition.
It seems than opening the header file does something so that clangd recognizes the symbol in the header file and cpp file as different symbol?

The indexer only parses source files listed in the compile_commands.json. So, if your header is only included from C++ source files, the indexer will never process the contents of the header file in C mode, and thus the C mangling of the symbol will never make it into the index.

But if clangd is parsing the header in C mode when you open the header directly in your editor, it will try to search for that C mangling.

1 Like

One thing to note:
The C headers are inside extern "C" block.

The index generated seems to say that it is the C symbol? (the Lang part)

--- !Symbol
ID:              AB2F2B1CD123EBF0
Name:            params_new
Scope:           ''
SymInfo:
  Kind:            Function
  Lang:            C
CanonicalDeclaration:
  FileURI:         'file:///media/xxxx/xxx.h
  Start:
    Line:            93
    Column:          7
  End:
    Line:            93
    Column:          17
References:      1
Flags:           9
Signature:       '()'
TemplateSpecializationArgs: ''
CompletionSnippetSuffix: '()'
Documentation:   ''
ReturnType:      Params
Type:            'c:$@S@Params'
IncludeHeaders:
  - Header:          'file:///media/xxxx/xxx.h
    References:      1

One more thing that’s strange is that the Reference part say there are only one ref, but Refs section for that symbol ID lists multiple entries.(with different Kind, as I reported in the beginning)

It’s hard to try and diagnose the issue based on bits and pieces of information like this.

If you’re able to share an example project that demonstrates the issue (preferably a minimal one), that would be ideal. In that case please feel free to file an issue at Issues · clangd/clangd · GitHub and attach it there.

1 Like

I found out the reason!

My cpp files were including C headers, wrapping it with extern "C".
So the index files were generated with C symbols alright.
But when I open the C header, the dynamic indexer will read it as C++ header, based on the cpp file which includes the header (in compile_commands.json)

When I put the following in .clangd, everything worked fine!

If:
  PathMatch: .*\.h

CompileFlags:
  Add: [-xc-header, -std=gnu17]

Another solution is to move extern "C" wrapper inside the header file.

#ifdef __cplusplus
extern "C"
{
#endif
/* code */
#ifdef __cplusplus
}
#endif

Thanks a lot for your help :slight_smile:

1 Like

Ah, so my first guess was right, just in reverse :laughing:

Glad you got it sorted!

1 Like