Trouble using TextDiagnosticPrinter

Hi,

After much hacking I managed to write a DiagnosticConsumer that would emit some error messages for me. Kudos to Andrzej for his help.

I was expecting that I would more or less automatically get filename, line number and some source location carets added and some nice coloured text on my console. So for that I think I might need to use TextDiagnosticPrinter? However when I try to setClient that in my ASTContext’s diagEngine it segfaults when a diagnostic is emitted.

I’ve tried looking in the clang/tools files for inspiration from other FrontendAction style tools but they either don’t use TextDiagnosticPrinter or else they use some other clang infra like clang/Rewrite/Core/Rewriter.h.

This really feels like it should be super simple but I’m finding it very frustrating. My tool is actually doing useful things to spot project-specific code defects but now adding something simple like neat error messages is turning into a total quagmire.

Thanks,
Billy.

class MyDiagnosticConsumer : public clang::DiagnosticConsumer {
public:
void HandleDiagnostic(clang::DiagnosticsEngine::Level DiagLevel, const clang::Diagnostic& Info) override {
llvm::SmallVector<char, 512> message;
Info.FormatDiagnostic(message);
llvm::errs() << message << ‘\n’;
cout << “Hello HandleDiagnostic!” << endl;
}
};

class MyVisitor : public RecursiveASTVisitor {
public:
explicit MyVisitor(ASTContext *context)
: mContext(context) {}

bool VisitFunctionDecl(FunctionDecl* fnDecl) {
// let’s just issue an error on every function decl!

auto& diagEngine = mContext->getDiagnostics();
const auto ID = diagEngine.getCustomDiagID(clang::DiagnosticsEngine::Error,
“%0 declared? You insensitive clod!”);
auto Builder = diagEngine.Report(fnDecl->getLocation(), ID);
Builder.AddString(fnDecl->getNameAsString());
Builder.AddSourceRange(clang::CharSourceRange::getCharRange(call->getSourceRange()));
Builder.setForceEmit(); // <<< without this MyDiagnosticConsumer::HandleDiagnostic is never called !!

return true;
}

private:
ASTContext *mContext;
};

class MyConsumer : public clang::ASTConsumer {
public:
explicit MyConsumer(ASTContext *Context) : Visitor(Context) {

DiagnosticsEngine &diagEngine = Context->getDiagnostics();

// if I set my own DiagnosticsConsumer here it works (but no line/file info).
diagEngine.setClient(new MyDiagnosticConsumer(), /ShouldOwnClient=/true);

---- OR ----

// if I set a TextDiagnosticPrinter it crashes later on
// At the point where I otherwise get an error like ‘…/include/bla.h’ file not found.
IntrusiveRefCntPtr DiagOpts = new DiagnosticOptions();
TextDiagnosticPrinter *DiagClient =
new TextDiagnosticPrinter(llvm::errs(), &*DiagOpts);
diagEngine.setClient(DiagClient, true); // true => shouldOwnClient
}

virtual void HandleTranslationUnit(clang::ASTContext &Context) {
auto Decls = Context.getTranslationUnitDecl()->decls();
auto &SM = Context.getSourceManager();
for (auto &Decl : Decls) {
const auto& FileID = SM.getFileID(Decl->getLocation());
if (FileID != SM.getMainFileID()) {
// Skip decls coming via #incl
continue;
}

Visitor.TraverseDecl(Decl);
}
}

private:
MyVisitor Visitor;
};

Program received signal SIGSEGV, Segmentation fault.
clang::DiagnosticRenderer::emitDiagnostic (this=0x0, Loc=…, Level=clang::DiagnosticsEngine::Fatal, Message=…, Ranges=…,
FixItHints=…, D=…) at /home/bomahony/llvm/tools/clang/lib/Frontend/DiagnosticRenderer.cpp:95
95 beginDiagnostic(D, Level);
(gdb) bt
#0 clang::DiagnosticRenderer::emitDiagnostic (this=0x0, Loc=…, Level=clang::DiagnosticsEngine::Fatal, Message=…, Ranges=…,
FixItHints=…, D=…) at /home/bomahony/llvm/tools/clang/lib/Frontend/DiagnosticRenderer.cpp:95
#1 0x00000000084ce036 in clang::TextDiagnosticPrinter::HandleDiagnostic (this=0x9b22430, Level=clang::DiagnosticsEngine::Fatal, Info=…)
at /home/bomahony/llvm/tools/clang/lib/Frontend/TextDiagnosticPrinter.cpp:152

Hi Billy,

I’ve also run into this issue but I skirted around it by creating a FixItRewriter which is actually a DiagnosticConsumer which the diagnostics engine DOESN’T own. Unsure if this is the same case that you’re running into, but when I was experiencing segfaults I found it to be an issue with lifetime management for diagnostic consumers. YMMV since the issue is within the FixItRewriter impl itself, but the class stores a pointer to the current diagnostic engine’s client before setting the instance of FixItRewriter as our own. If we tell the engine to own the FixItRewriter, then the previous consumer is destroyed, and our FixItRewriter is then pointing to a destroyed resource. Can you try creating a std::unique_ptr for your own diagnostics consumer and tell the engine not to own it? You’d have to take care of the storage lifetime in this case.

Also, one more thing is, when the Builder object gets destroyed it performs an Emit(), and if you previously Emit then it won’t do a double-write. At the closing curly brace of your VisitFunctionDecl, it may be a good idea to check which consumer you are currently using. I did a GDB backtrace in my situation at the end-curly-brace and found that the diagnostics consumer used to emit the message was actually not the one I thought I was using.

Since I didn’t use the Visitor pattern but rather the Matcher pattern, my scope of how the two use cases differ are limited. Hope you find the bug!

Best,
Ray

Billy,

Are you more interested in:
   * understanding the Diagnostics API within Clang? or
   * writing a tool that can emit diagnostics?

The former will be much easier and straightforward if you use the clang::tooling API. Please, see example here:

Hi Andrzej,

success!

The trick was that I needed to add the setForceEmit() call and only that. But I’d already got sidetracked with rolling my own DiagConsumer. When I removed all that it worked perfectly (but I did have to leave in the setForceEmit() - not sure why - maybe my FEAction or ASTConsumer classes are not doing some finalization calls that. I’ll have to look closely at the CSC example again.

Thanks for your help and clang-tutor. I will undoubtedly be referring to clang-tutor again in the future.

Hi Ray,

thanks, I finally did notice that the ASTContext’s DiagEngine already did have a DiagConsumer registered at that one did work fine. But I had to add a Builder.setForceEmit call. Of course by the time I had tried that I had already added my own DiagConsumer so I was already well down a dead-end path!

BTW I had already tried the matcher pattern for my analyzer but I found a problem with the matchers traversal order (which was important to my application as it keeps track of the order in which certain things occur within the functions being analysed). https://bugs.llvm.org/show_bug.cgi?id=46423 - there hasn’t been any activity on it but some of the developers had sketched out potential fixes so maybe it has been addressed in the meantime.

Cheers,
Billy.