Search code examples
c++shared-ptrantlr4

How do I solve this std::bad_cast with from antlrcpp::Any?


I'm trying to parse an expression with ANTLR4. Therefor I'm using the visitor pattern and creating an abstract syntax tree.

The result can be an expression or something other (which I removed from this example code). The visitor expects a return type of antlrcpp::Any so the elements of the abstract syntax tree are converted toantlrcpp::Any.

But I'm getting a std::bad_cast if I'm trying to convert the result back from antlrcpp::Any to std::shared_ptr<Expression>.

#include <iostream>
#include <string>
#include <memory>

#include "antlr4-runtime.h"
#include "Parser/SimpleLangLexer.h"
#include "Parser/SimpleLangParser.h"
#include "Parser/SimpleLangBaseVisitor.h"

struct Expression;

struct Node {
    virtual ~Node() { }
};

struct Expression: public Node {
    int constValue;
    Expression(int constValue) : constValue(constValue) { }
    virtual ~Expression() override { }
};

struct AstVisitor: public SimpleLangBaseVisitor {

    virtual antlrcpp::Any visitTopLevelElement(SimpleLangParser::TopLevelElementContext *ctx) override {
        std::shared_ptr<Expression> expression = ctx->expression()->accept(this);
        return std::dynamic_pointer_cast<Node>(expression);
    }

    virtual antlrcpp::Any visitIntExpr(SimpleLangParser::IntExprContext *ctx) override {

        std::string strInteger = ctx->INTEGER()->getSymbol()->getText();
        int constValue = std::stoi(strInteger);

        auto intExpr = std::make_shared<Expression>(constValue);
        return intExpr;
    }
};

int main() {

    std::string line = "123;";
    antlr4::ANTLRInputStream input(line);
    SimpleLangLexer lexer(&input);
    antlr4::CommonTokenStream tokens(&lexer);
    SimpleLangParser parser(&tokens);

    antlr4::tree::ParseTree *tree = parser.start();
    // Prints:
    // > (start (topLevelElement (expression 123) ;) <EOF>)
    std::cout << std::endl << tree->toStringTree(&parser) << std::endl;

    AstVisitor astVisitor;
    antlrcpp::Any result = tree->accept(&astVisitor);

    // Prints:
    // > Dn
    // std::cout << result.get_typeinfo().name() << std::endl;

    // Error:
    // > terminate called after throwing an instance of 'std::bad_cast'
    //     what():  std::bad_cast
    std::shared_ptr<Node> node = result;
    std::shared_ptr<Expression> iexpr = std::dynamic_pointer_cast<Expression>(node);

    // Expected:
    // > 123
    std::cout << iexpr->constValue << std::endl;
    return 0;
}

Implementation of antlrcpp::Any

My grammar:

grammar SimpleLang;

start
    : topLevelElement* EOF
    ;

topLevelElement
    : expression ';'
    ;

expression
    : IDENTIFIER #identifierExpr
    | INTEGER #intExpr
    ;

IDENTIFIER
    : [_a-zA-Z][_a-zA-Z0-9]*
    ;

INTEGER
    : [0-9]+
    ;

WS: [ \r\n\t] -> skip;

Note: I greatly reduced my code for this question and I hope it makes still sense.


Solution

  • antlrcpp::Any result = tree->accept(&astVisitor);
    // Error:
    // > terminate called after throwing an instance of 'std::bad_cast'
    //     what():  std::bad_cast
    
    std::shared_ptr<Node> node = tree->accept(result);
    

    here you call tree->accept first with a pointer to an astVisitor.

    Then you take the result of that, and pass it to tree->accept, which probably expects a pointer to SimpleLangBaseVisitor. But the first tree->accept call returned an Any that probably doesn't contain a pointer to a SimpleLangBaseVisitor.

    So it throws an error.


    As an aside, that Any you are using is missing std::typeinfo const& get_typeinfo() const. If you had that, you could do some get_typeinfo().name() printf debugging to get a clue what is going wrong.

    If you have access to the source code add:

    struct Base {
      virtual ~Base();
      virtual Base* clone() const = 0;
      std::typeinfo const& get_typeinfo() const = 0;
    };
    
    template<typename T>
    struct Derived : Base {
      // ...
      std::typeinfo const& get_typeinfo() const override {
        return typeid(T);
      }
    };
    

    and to Any itself:

    std::typeinfo const& get_typeinfo() const {
      if (!_ptr) return typeid(std::nullptr_t);
      return ptr->get_typeinfo();
    }
    

    once you have that, std::cerr << some_any.get_typeinfo().name() << std::endl; will solve many "why am I getting a bad_cast?!" pains. But it does require that both your code, and the code generating the Any, be built with this modified Any.