c++segmentation-faultexprtk

segmentation fault only when including exprtk::collect_variables() in the code and calling it twice


When running and compiling a simple equation more than once using ExprTK, I encounter a segmentation fault or address boundary error. Surprisingly, the issue disappears when I exclude the exprtk::collect_variables() function, which doesn't make any sense since the function is just supposed to provide me a list with every variable in the equation.

#include "exprtk.h"
#include <iostream>

typedef exprtk::symbol_table<float> symbol_table_t;
typedef exprtk::expression<float> expression_t;
typedef exprtk::parser<float> parser_t;

parser_t parser;
expression_t expression;
symbol_table_t symbol_table;

float a = 5.0f;
std::string equation;

void f()
{
    symbol_table.clear();

    std::vector<std::string> varList;
    exprtk::collect_variables(equation, varList); // DELETE THIS LINE FOR THE SEGFAULT TO GO AWAY

    symbol_table.add_variable("a", a);

    parser.compile(equation, expression);

    std::cout << "value is " << expression.value() << std::endl;
}

int main()
{
    equation.reserve(16);
    equation = "a";

    expression.register_symbol_table(symbol_table);

    std::cout << "First call" << std::endl;
    f();
    std::cout << "Second call" << std::endl;
    f();

    return 0;
}

I really need the "collect_variables" function and I would appreciate if someone could tell me what is happening and how to I fix it.

Thanks in advance.

I tried to exclude the "symbol_table.clear()" call and the code works too, but it brings other issues.


Solution

  • Initially, from the ExprTk readme, in multiple places, we have the following statement:

    The  life-time of  objects registered  with or  created from  a
    specific symbol-table must span  at  least the lifetime  of the
    symbol  table  instance  and  all  compiled  expressions  which
    utilise  objects,  such  as  variables,  strings,  vectors  and
    functions of  that symbol-table,  otherwise the  result will be
    undefined behaviour.
    

    On the second call to f(), on the first line the symbol_table is cleared, which means the references to the variables etc that have been embedded in the expression are now pointing to invalid memory, any accesses to those nodes will cause UB.

    When the compile method is called in the second call to f() the expression instance's destructor is called, at which point it attempts to see if any nodes it is holding on to need to be destroyed, whilst doing this it accesses the nodes it got from the symbol table during the previous compilation call and that's why you are seeing the crash.

    Including or excluding the call to collect_variables has got nothing to do with the issue.

    Running your code using UBSAN and ASAN, the resulting diagnostics will correctly point to the issue you are observing.


    In order to resolve the crash, all you need to do is call release on the expression instance before calling clear on the symbol_table. As follows:

    void f()
    {
        expression.release();
        symbol_table.clear();
        ...
        ....
    }