Search code examples
c++boostboost-spiritboost-spirit-qi

Segfault trying to use boost::spirit::qi parser in a class


I have been writing some parser code using boost::spirit and started getting a segfault.

I have simplified my code down as much as I can to make it easy to post, see below.

The segfault occurs during the pushback of the int when the parser calls back to addModule.

Valgrind claims that the vector v_modules is not initialised. Earlier in the code I can see that it IS initialised so I assume some memory trashing is occuring. I've tried rewriting it many time with this smaller testcase to no avail. Any help appreciated!

verilog.cpp:

#include "verilog.h"
#include <string>
#include <boost/spirit/include/qi.hpp>

Verilog::Verilog() {
  m_parser.verilog = this;
}
Verilog::~Verilog(){}

void Verilog::parse(string contents) {
  string::const_iterator iter = contents.begin();
  string::const_iterator end = contents.end();
  bool r = phrase_parse(iter,end,m_parser,boost::spirit::ascii::space);
}

void Verilog::addModule() {
  int new_mod = 1;
  v_modules.push_back(new_mod);
}

int main()
{
  Verilog* verilog = new Verilog();
  string contents = "hello";
  verilog->parse(contents);
}

verilog.h

#ifndef VERILOG_H
#define VERILOG_H

#include <iostream>
#include <string>
#include <boost/spirit/include/qi.hpp>
#include <boost/bind.hpp>

using namespace std;
namespace qi = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;

class Verilog
{

 public:
  Verilog();
  ~Verilog();
  void parse(string contents);
  void addModule() ;

  template <typename Iterator>
    struct verilog_parser : qi::grammar<Iterator, ascii::space_type>  
    {
    verilog_parser() : verilog_parser::base_type(module)
        {
          module = qi::eps[boost::bind(&Verilog::addModule, verilog)];
        }

      qi::rule<Iterator, ascii::space_type> module;

      Verilog* verilog;

    };

 private:
  std::vector<int>    v_modules;
  verilog_parser<string::const_iterator> m_parser;

};

#endif

Solution

  • You're using a boost::bind which results in a temporary function object that refers to whatever the verilog member points to during construction of the grammar.

    That's not gonna work.

    You need a phoenix lazy actor, and you better make it refer to this->verilog by _reference if you wanted it to pick up the changed value once you set it from within the Verilog constructor.

    To be honest, the code looks a bit clumsy. Why don't you use Spirit's attribute compatibility rules to automatically build vectors (or lists, sets, maps... etc) for you?

    Here's a fix:

    #include <boost/spirit/include/phoenix.hpp>
    namespace phx = boost::phoenix;
    
    // ... later
    
                module = qi::eps[phx::bind(&Verilog::addModule, phx::ref(verilog))];
    

    Note this still leaves the leaked Verilog instance in main. Why do you use new in modern C++?

    Integrating it:

    Live On Coliru

    #include <iostream>
    #include <string>
    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    #include <boost/bind.hpp>
    
    using namespace std;
    namespace qi = boost::spirit::qi;
    namespace ascii = boost::spirit::ascii;
    namespace phx = boost::phoenix;
    
    class Verilog {
    
      public:
        Verilog();
        ~Verilog();
        void parse(string contents);
        void addModule();
    
        template <typename Iterator> struct verilog_parser : qi::grammar<Iterator, ascii::space_type> {
            verilog_parser() : verilog_parser::base_type(module) {
                module = qi::eps[phx::bind(&Verilog::addModule, phx::ref(verilog))];
            }
    
            qi::rule<Iterator, ascii::space_type> module;
    
            Verilog *verilog;
        };
    
      private:
        std::vector<int> v_modules;
        verilog_parser<string::const_iterator> m_parser;
    };
    
    #include <string>
    #include <boost/spirit/include/qi.hpp>
    
    Verilog::Verilog() { m_parser.verilog = this; }
    Verilog::~Verilog() {}
    
    void Verilog::parse(string contents) {
        string::const_iterator iter = contents.begin();
        string::const_iterator end = contents.end();
        bool r = phrase_parse(iter, end, m_parser, boost::spirit::ascii::space);
    }
    
    void Verilog::addModule() {
        int new_mod = 1;
        v_modules.push_back(new_mod);
    }
    
    int main() {
        Verilog verilog;
        string contents = "hello";
        verilog.parse(contents);
    }