Search code examples
c++gccboostandroid-ndkboost-spirit-qi

Simple boost::spirit grammar SIGSEGV on Android


The parser

        namespace qi = boost::spirit::qi;

        template<typename T>
        class action
        {
        public:
            action(std::vector<std::shared_ptr<part>>& parts) : m_parts{ parts } {}

            void operator()(const std::vector<char>& cc, qi::unused_type, qi::unused_type) const
            {
                std::string s(cc.begin(), cc.end());
                if (s.length() > 0) {
                    auto p = new T(s);
                    m_parts.push_back(std::shared_ptr<part>(p));
                }
            }
        private:
            std::vector<std::shared_ptr<part>>& m_parts;
        };

        std::vector<std::shared_ptr<part>> parse(const std::string& source) {
            namespace ascii = boost::spirit::ascii;

            using ascii::char_;
            using qi::lit;

            std::vector<std::shared_ptr<part>> parts;

            auto prop_g = lit("{{=")
                >> *char_(' ')
                >> (*(char_ - char_("} ")))[action<property_part>(parts)]
                >> *char_(' ')
                >> "}}"
                ;
            auto text_g = (+(char_ - '{'))[action<text_part>(parts)];
            auto g = -text_g >> +(prop_g >> text_g);

            qi::parse(source.begin(), source.end(), g);
            return parts;
        }

causes a fault on the qi::parse call when testing on a Kitkat device. The fault happens before any semantic action is called. The same code works on Xcode 6/iOS 8.4 and VS 2015. We're using Boost 1.59.

We could replace Spirit with Bison implying an extra build step, or use Clang with the Android NDK, getting us off the beaten path.

Can this fault be fixed with build configuration or are there other options we could explore?


Solution

  • Of course errors can be fixed. You do not show the grammar See Update You show a lot of parser expressions with unknowns, though, so there's no way we could even start to reason about your code.

    One thing that does stand out is the (ab)use of auto, though.

    A quick google should point you at ~6 questions on SO that warn about this.

    You can not use auto with Spirit's expression templates. In all but the very very trivial cases (unparameterized stateless terminals) this leads directly to Undefined Behaviour

    Try using

    • qi::rule<> to contain/group the expressions
    • boost::spirit::copy or boost::proto::deep_copy

    UPDATE

    To the edited question: indeed the grammar crashed due to undefined behaviour (if this ever "appeared to work", you were just (un)lucky!).

    Here's a fixed version, as a bonus I removed the action<> thing replacing it with existing Phoenix mechanisms and attribute propagation.

    You may want to study the changes side-by-side to find out exactly what I changed.

    Live On Coliru

    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    
    struct part {
        part(std::string s="") : _value(std::move(s)) {}
        virtual ~part() { }
        virtual void do_print(std::ostream& os) const = 0;
      protected:
        std::string _value;
    };
    
    struct property_part : part { 
        using part::part; 
        void do_print(std::ostream& os) const { os << "{{=" << _value << "}}"; }
    };
    struct text_part     : part { 
        using part::part; 
        void do_print(std::ostream& os) const { os << "'" << _value << "'"; }
    };
    
    std::vector<std::shared_ptr<part>> parse(const std::string& source) {
        namespace qi = boost::spirit::qi;
        namespace px = boost::phoenix;
        using boost::spirit::ascii::char_;
        using qi::lit;
    
        typedef std::shared_ptr<part> pptr;
        qi::rule<std::string::const_iterator, pptr()> prop_g, text_g;
    
        // this is ok: purely stateless expression template
        prop_g = lit("{{=")
            >> *char_(' ')
            >> qi::as_string [ +~char_("} ") ] [ qi::_val = px::construct<pptr>(px::new_<property_part>(qi::_1)) ]
            >> *char_(' ')
            >> "}}"
            ;
    
        text_g = qi::as_string [ +~char_('{') ] [ qi::_val = px::construct<pptr>(px::new_<text_part>(qi::_1)) ];
    
        std::vector<pptr> parts;
        qi::parse(source.begin(), source.end(), -text_g >> +(prop_g >> text_g), parts);
    
        return parts;
    }
    
    int main() {
        auto result = parse("My book is about {{= this-is-a-(bogus)-property-part }} else entirely {{=byebye}}");
        assert(result.size() == 4);
    
        for(auto item : result)
            item->do_print(std::cout);
    }
    

    Prints

    'My book is about '{{=this-is-a-(bogus)-property-part}}' else entirely '{{=byebye}}