[RFC] Clang diagnostic for demangling failures

Problem

Clang produces mangled names that cannot be demangled with LLVM demangler APIs. This is causing bad UX in various debugging tools (e.g., lldb, pprof). There is a long-standing history of issues filed due to llvm-cxxfilt tool being unable to demangle names produced by clang (e.g., 43773, 73521, and more).

The root cause is that the codegen module in Clang and the LLVM demangling APIs are developed separately, and as a result they can diverge.

Background

Clang produces mangled names during code generation inside CodeGenModule, e.g., here.

Not all of the produced mangled names can be demangled by LLVM’s own llvm::demangle API.

However, it’s reasonable to expect that LLVM demangler can handle Clang-generated names in all cases.

E.g., some data from running large scale analysis on OSS projects to find this pattern:

  • llvm/llvm-project - 843 unique instances of this discrepancy (of those, 339 in llvm, 336 in mlir, 85 in clang and 20 in lldb).
  • absl libraries - 371 instances.
  • protobuf libraries - 129 instances.
  • gRPC libraries - 27 instances.
  • tensorflow - 709 instances.
  • filament - 122 instances.
  • HEIR - 89 instances.
  • Dawn - 193 instances.

Proposed solution

Introduce a diagnostic (remark) that would be emitted when a name cannot be demangled.

The usage would look like:

if (llvm::isMangledName(MangledName) && llvm::demangle(MangledName) == MangledName)
   Diags.Report(ND->getLocation(), diag::remark_name_cannot_be_demangled) << MangledName;

The advantages of introducing this as a remark vs. a warning:

  • Remark doesn’t break builds in -Werror mode.
  • The information is not user-actionable, so using a warning is contentious.

The diagnostic would have its own diagnostic group (e.g., DiagGroup<"demangler">), so that it can be easily enabled and disabled.

Since the diagnostic is not immediately user-actionable and quite noisy:

  • It will be off by default in the initial stage (we enable it with -R to test and fix failures).
  • The diagnostic will have information on how to report it in the diagnostic message.

Roadmap

We expect the diagnostic to be very noisy initially. Therefore, we propose a gradual rollout:

  1. Introduce the diagnostic and set it to off by default.
  2. Enable the diagnostic selectively for LLVM and internal projects to find the current demangling failures.
  3. Fix the problems in 2.
  4. Enable the diagnostic by default for RC builds.
  5. Collect and fix the problems reported by early RC adopters.
  6. Enable the diagnostic by default, since by this moment it’s not noisy.
  7. (assuming the above steps leave no long tails) Remove the diagnostic group, so that the diagnostic can not be disabled anymore. This step requires caution, since we don’t want to have users heavily impacted in case of demangler problems.

Alternatives considered (misc)

  • Adding a warning vs. a remark. The drawbacks of adding a warning are the opposite of the advantages of adding a remark mentioned above.
  • Adding an assertion instead of a diagnostic. This has been rejected, since mixing build configuration (assertion) with runtime configuration (compile flag to enable or disable it) is quite unusual.
  • Before generating the diagnostic, check that mangled names demangle to some expected shape (e.g., equal to the name that is mangled). This is not feasible, since mangled names don’t necessarily demangle to valid C++ (e.g., _ZZN1S1fEiiEd0_NKUlvE_clEv demangles to S::f(int,int)::'lambda'()::operator()() const).
2 Likes

I don’t want to bikeshed here (we can do that on the review), but NOT that :slight_smile:

This is, IMO the biggest reason here: Warnings are for something that the user can fix.

I’d prefer we do a pretty aggressive amount of ā€˜external’ projects before we enable this. So it should be ā€˜boost’ clean + . Boost at minimum, else I don’t have any good suggestions. Perhaps the format, that really popular jsonlibrary, and a few others?

I don’t think this should EVER happen. It should always be possible to disable these.

Sensible. I see value to SOMETHING that looks at the correctness of the demangling, but I also see that as orthogonal to this patch.

This seems like a thing we should have an assertion for (any case where we can’t demangle a thing we mangle is a bug in clang+demangle library - that’s what assertions are for)

Should check some large code bases with the assertion locally committed/enabled, fix a bunch of stuff, then commit the assertion upstream - and if there’s not enough +asserts coverage, maybe set some up if this is a valued invariant.

Thanks a lot for putting effort into this RFC, I think it looks like a great protection mechanism for divergence between mangler & demangler. I wish this was guaranteed by LLVM/clang development instead, but despite not being obvious to me, there must be reasons for keeping mangler and demangler isolated.

+1 to this. I think these should be remarks for sure.

I am also in favor of always keeping a knob. No matter how hard we try, there’ll be divergences inherently. We shouldn’t leave users with a chatty compilation when that happens (imagine a codebase where a ā€œcoreā€ header has some violation, all of a sudden your thousands of compilations start producing chatter, that’ll both confuse developers and hide ā€œusefulā€ information).

I am afraid this might hinder clang/llvm development. Imagine you’re trying to debug an issue, and as you reduce your reproducer all of a sudden you start hitting this assertion. Such workflows usually don’t involve a human in the loop (e.g. you trigger a creduce and don’t look at it for hours), so coming back to see your reducer isn’t making progress because it started hitting this assertion would be unfortunate.
I can see how a similar argument can be made for other assertions in the codebase, but I think usually assertions are likely to be triggered during development that can invalidate those assertions. Ensuring the developer with context will likely hit/fix the issue.
Whereas in here we have an inherently divergent development process, so there’ll be more chances of hitting spooky action at a distance.

This is entirely about catching internal compiler issues. Why do we want to surface it to users at all? I’m sure we can find 99% of the issues ourselves. Also, why burden users’ builds with the extra demangle calls? I agree with David that this seems appropriate for an assert.

Adding an assertion instead of a diagnostic. This has been rejected, since mixing build configuration (assertion) with runtime configuration (compile flag to enable or disable it) is quite unusual.

Why does it need a runtime flag for enabling/disabling?

E.g., some data from running large scale analysis on OSS projects to find this pattern:

I’d expect Clang’s LIT tests to have good coverage of the C++ constructs it can compile. Have you checked how many demangling problems those tests hit?

Roadmap

This is obviously the tricky part, since we seem to have many hundreds of missing demanglings.

But rolling out a diagnostic seems like starting at the wrong end. The important part is fixing the demanglings.

How about:

  1. We build a list of demangling failures (based on trying the LIT tests, self-hosting, and additional code if needed – I’d be happy to try Chromium)
  2. Fix those issues (fun times)
  3. Add the assert

(Again, I suppose that’s basically what David said.)

3 Likes

I agree this should be a runtime check, at least at some point. Every new commit should be expected to pass it, and the community living off of main branch with runtime-checks enabled builds should help catch these problems.

We would have to be careful with the word assert here, this is not to say that we should immediately bail out upon producing one of these manglings, otherwise it would be drip-feeding us these errors, which is a bad experience for us clang developers.

Thank you for all the comments! I’ll try to address the concerns I’ve seen raised in the thread, please let me know if something else/more needs to be addressed explicitly.

I’d prefer we do a pretty aggressive amount of ā€˜external’ projects before we enable this. So it should be ā€˜boost’ clean + . Boost at minimum, else I don’t have any good suggestions.

I have checked boost-1.84.0 (well, our internal import to be precise, with some local patches involved, but this shouldn’t make a difference in this case) and found no violations.

I don’t think this should EVER happen. It should always be possible to disable these.

I am also in favor of always keeping a knob. No matter how hard we try, there’ll be divergences inherently.

This is a fairly long-term part of the plan (assuming we get the codebase demangle-failure-free first), so I don’t think the knob/flag would go away any time soon realistically.
But I can see the point that even then (and probably as long as the mangling and demangling logic are developed separately), there will be divergencies.
The idea has been that we force the users to report those, but I can also the argument that

imagine a codebase where a ā€œcoreā€ header has some violation, all of a sudden your thousands of compilations start producing chatter, that’ll both confuse developers and hide ā€œusefulā€ information

Hence, we can get rid of the last item in the roadmap (to remove the diagnostic group).

Why does it need a runtime flag for enabling/disabling?

Not entirely sure if you’re asking why we need a flag or why the flag should be runtime.

Why we need a flag: there’s a giant number of violations at the moment.
Why it needs to be runtime: I guess it doesn’t. From a technical perspective, adding a build flag would work as well.

I’d expect Clang’s LIT tests to have good coverage of the C++ constructs it can compile.

That’s true. I will measure the number of violations in those.
LLVM itself (excl. tests) has 843 violations.

How about:

  1. We build a list of demangling failures (based on trying the LIT tests, self-hosting, and additional code if needed – I’d be happy to try Chromium)
  2. Fix those issues (fun times)
  3. Add the assert

I think the reasons we chose a diagnostic over an assert are roughly the following:

  • diagnostic (at remark level) won’t break compilations. An assertion most probably would.
    • We can’t afford to break compilations, since this problem is usually orthogonal to what the user/developer is actually doing.
    • Compilations might also be happening in automated mode (e.g., creduce), and it would be really unfortunate to break those.
  • we are hoping to get broader (interested) users to report future problems (divergences will keep happening, since the development of mangler and demangler will likely be out of sync for some time going forward). This way, as many as people as possible can test this to improve the LLVM demangler.
  • remarks seem to be occassionally used for things that aren’t immediately user-actionable (e.g., internal compilation args mismatch, or informing users about compilation progress).
  • remark that’s easy to silence should be fairly low-burden on the user.
  • remark/diagnostic allows to run the compiler once to find all the violations with corresponding locations. An assertion would abort the compilation every time it is triggered.

I agree that there are good arguments for an assertion (it’s definitely cleaner), but in order to get the codebase in a better state at the moment, a diagnostic might be a better fit. An assertion could be an item on the roadmap after fixing the discovered issues.

@AaronBallman what’s your take on this?

Some updated numbers on lit tests with the draft diagnostic enabled:

Failed Tests (5):
  Clang :: CodeGenCXX/PR28523.cpp
  Clang :: CodeGenCXX/mangle-fail.cpp
  Clang :: CodeGenOpenCLCXX/addrspace-of-this.clcpp 
  Clang :: SemaCXX/cxx1y-generic-lambdas-capturing.cpp 
  Clang :: SemaCXX/cxx1y-generic-lambdas.cpp 

These failed tests amount to about 25 demangling failures.

I don’t think this should be a warning, because it’s not about the users’ code, and users should never need to turn this on. It’s purely for clang developers.

So, I’d vote for this to be controlled by build-time NDEBUG, aborting by default with an assert failure in such builds. In order to avoid the issue of failures here blocking other bug-finding, I’d suggest an internal command line flag which can switch this to print a message to `llvm::errs instead of aborting.

Of course, we’ll want to fix all the failures we can find before enabling any such checking.

Overall, I love the idea of trying to catch mangling/demangling issues, so thank you for working on this!

Aside from the open questions about how to surface the information and to whom, my only real concern is compile-time overhead. I think we’d only want to pay the price of testing the demangler when we know the output is actually going somewhere a user can see.

+1 to this. The proposal is using a remark rather than a warning, but the underlying concern is the same – most users will never enable the remark to begin with. But also, attempting to silence the remark may cause concern for users because remarks are so infrequently encountered and it’s not well-known that -Rno- works the same way as -Wno-.

I think this is a reasonable approach, though I’d also be fine if there was a CMake configuration variable like LLVM_ENABLE_DEMANGLER_CHECKING or something so you could configure this functionality separately from an asserts build.

There’s a part of me that wonders if we should design this with distro maintainers in mind as the target audience. e.g., implement this in a way that’s enabled when distro maintainers want to build all their packages so that they get a nice report they can send to us that tells us a whole whack of problems at once. I think they’re the users best positioned to catch the mangling issues due to the volume of disparate code they compile. From that angle, an assertion or even stderr is more problematic; we might want a compiler flag that says ā€œemit violations to this output fileā€ and then dump some sort of structured text? (Piping to stderr would mean we’d get interleaved other output beyond just demangling failures.)

After some internal discussions, we have decided to put this work on hold.
It still remains an important problem, however. I have posted some stats for various open source projects in this thread above. Internally, we have around 177k violations (mangled names that can’t be demangled by the LLVM demangler).

In case someone in the community would like to pick up the effort, the draft PR has the code for the diagnostic. I will also add a comment to the codegen module mentioning the invariant that should hold in the future.

1 Like