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.
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();
...
....
}