Trouble using TextDiagnosticPrinter

classic Classic list List threaded Threaded
5 messages Options
Reply | Threaded
Open this post in threaded view
|

Trouble using TextDiagnosticPrinter

Manas via cfe-dev
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<MyVisitor> {
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<DiagnosticOptions> 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
   
    

_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
Reply | Threaded
Open this post in threaded view
|

Re: Trouble using TextDiagnosticPrinter

Manas via cfe-dev
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

On Wed, Oct 14, 2020 at 12:17 PM Billy O'Mahony via cfe-dev <[hidden email]> wrote:
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<MyVisitor> {
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<DiagnosticOptions> 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
   
    
_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev

_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
Reply | Threaded
Open this post in threaded view
|

Re: Trouble using TextDiagnosticPrinter

Manas via cfe-dev
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:
*
https://github.com/banach-space/clang-tutor/blob/master/tools/CodeStyleCheckerMain.cpp#L45-L47
With clang::tooling, DiagnosticsEngine and DiagnosticConsumer are set
and managed for you behind the scenes (i.e. you shouldn't need to do
anything extra). If you check a backtrace in your debugger, you'll
notice that the driver within libclangFrontend manages that for you
(i.e. creates a CompilerInstance, which contains DiagnosticEngine, which
contains the default DiagnosticConsumer -> more or less ;-) ).

However, if you _are_ interested in the diagnostics APIs, then like Ray
has highlighted - managing the resources is often the root cause of most
issues. These APIs are quite complex though and this can take a fair bit
of trial and error. Having said that, do upload your code on GitHub and
I can take a look if you want.

-Andrzej

On 14/10/2020 20:33, Ray Zhang via cfe-dev wrote:

> Hi Billy,
>
> I've also run into this issue but I skirted around it by creating a
> FixItRewriter
> <https://clang.llvm.org/doxygen/classclang_1_1FixItRewriter.html> 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
>
> On Wed, Oct 14, 2020 at 12:17 PM Billy O'Mahony via cfe-dev
> <[hidden email] <mailto:[hidden email]>> wrote:
>
>     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<MyVisitor> {
>     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<DiagnosticOptions> 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
>
>
>     _______________________________________________
>     cfe-dev mailing list
>     [hidden email] <mailto:[hidden email]>
>     https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
>
>
> _______________________________________________
> cfe-dev mailing list
> [hidden email]
> https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
>
_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
Reply | Threaded
Open this post in threaded view
|

Re: Trouble using TextDiagnosticPrinter

Manas via cfe-dev
  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.

On Thu, 15 Oct 2020 at 09:51, Andrzej Warzynski via cfe-dev <[hidden email]> wrote:
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:
*
https://github.com/banach-space/clang-tutor/blob/master/tools/CodeStyleCheckerMain.cpp#L45-L47
With clang::tooling, DiagnosticsEngine and DiagnosticConsumer are set
and managed for you behind the scenes (i.e. you shouldn't need to do
anything extra). If you check a backtrace in your debugger, you'll
notice that the driver within libclangFrontend manages that for you
(i.e. creates a CompilerInstance, which contains DiagnosticEngine, which
contains the default DiagnosticConsumer -> more or less ;-) ).

However, if you _are_ interested in the diagnostics APIs, then like Ray
has highlighted - managing the resources is often the root cause of most
issues. These APIs are quite complex though and this can take a fair bit
of trial and error. Having said that, do upload your code on GitHub and
I can take a look if you want.

-Andrzej

On 14/10/2020 20:33, Ray Zhang via cfe-dev wrote:
> Hi Billy,
>
> I've also run into this issue but I skirted around it by creating a
> FixItRewriter
> <https://clang.llvm.org/doxygen/classclang_1_1FixItRewriter.html> 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
>
> On Wed, Oct 14, 2020 at 12:17 PM Billy O'Mahony via cfe-dev
> <[hidden email] <mailto:[hidden email]>> wrote:
>
>     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<MyVisitor> {
>     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<DiagnosticOptions> 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
>
>
>     _______________________________________________
>     cfe-dev mailing list
>     [hidden email] <mailto:[hidden email]>
>     https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
>
>
> _______________________________________________
> cfe-dev mailing list
> [hidden email]
> https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
>
_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev

_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
Reply | Threaded
Open this post in threaded view
|

Re: Trouble using TextDiagnosticPrinter

Manas via cfe-dev
In reply to this post by Manas via cfe-dev
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.

On Wed, 14 Oct 2020 at 20:34, Ray Zhang <[hidden email]> wrote:
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

On Wed, Oct 14, 2020 at 12:17 PM Billy O'Mahony via cfe-dev <[hidden email]> wrote:
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<MyVisitor> {
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<DiagnosticOptions> 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
   
    
_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev

_______________________________________________
cfe-dev mailing list
[hidden email]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev