Auto-rewrite associative DSL expression trees

... if depth > log2(leaves) * 2 to reduce recursion
and thereby avoid stack overflows.

refs #7827
This commit is contained in:
Alexander A. Klimov 2020-02-18 11:26:31 +01:00
parent 511c81ab90
commit 64d50106f0
2 changed files with 109 additions and 6 deletions

View file

@ -16,12 +16,15 @@
#include "base/defer.hpp"
#include <boost/exception_ptr.hpp>
#include <boost/exception/errinfo_nested_exception.hpp>
#include <cmath>
using namespace icinga;
boost::signals2::signal<void (ScriptFrame&, ScriptError *ex, const DebugInfo&)> Expression::OnBreakpoint;
boost::thread_specific_ptr<bool> l_InBreakpointHandler;
const long double AssociativeExpression::m_Log2 = logl(2);
Expression::~Expression()
{ }

View file

@ -9,10 +9,17 @@
#include "base/dictionary.hpp"
#include "base/function.hpp"
#include "base/exception.hpp"
#include "base/logger.hpp"
#include "base/scriptframe.hpp"
#include "base/shared-object.hpp"
#include "base/convert.hpp"
#include <cmath>
#include <cstdint>
#include <iterator>
#include <map>
#include <memory>
#include <utility>
#include <vector>
namespace icinga
{
@ -562,23 +569,116 @@ protected:
ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override;
};
class LogicalAndExpression final : public BinaryExpression
class AssociativeExpression : public BinaryExpression
{
public:
using BinaryExpression::BinaryExpression;
protected:
template<class Branch>
void RebalanceLeaves()
{
uintmax_t count = 0, depth = 0, depthBuf = 0;
StatLeaves<Branch>(count, depth, depthBuf);
if (depth > logl(count) / m_Log2 * 2) {
{
Log msg (LogDebug, "config");
msg << "Detected associative expression tree with " << count << " leaves, but "
<< depth << " > log2(" << count << ") * 2 nesting levels. Rebalancing to log2(" << count << ") nesting levels.\n";
ShowCodeLocation(msg, m_DebugInfo);
}
std::vector<std::unique_ptr<Expression>> leaves;
leaves.reserve(count);
HarvestLeaves<Branch>(leaves);
auto split (leaves.begin() + leaves.size() / 2u);
m_Operand1 = AssembleLeaves<Branch>(leaves.begin(), split);
m_Operand2 = AssembleLeaves<Branch>(split, leaves.end());
}
}
private:
static const long double m_Log2;
template<class Branch>
void StatLeaves(uintmax_t& count, uintmax_t& depth, uintmax_t& depthBuf)
{
++depthBuf;
if (depth < depthBuf) {
depth = depthBuf;
}
for (auto op : {m_Operand1.get(), m_Operand2.get()}) {
auto branch (dynamic_cast<Branch*>(op));
if (branch == nullptr) {
count += 1u;
} else {
branch->template StatLeaves<Branch>(count, depth, depthBuf);
}
}
--depthBuf;
}
template<class Branch>
void HarvestLeaves(std::vector<std::unique_ptr<Expression>>& leaves)
{
for (auto op : {&m_Operand1, &m_Operand2}) {
auto branch (dynamic_cast<Branch*>(op->get()));
if (branch == nullptr) {
leaves.emplace_back(std::move(*op));
} else {
branch->template HarvestLeaves<Branch>(leaves);
}
}
}
template<class Branch, class Iter>
std::unique_ptr<Expression> AssembleLeaves(Iter begin, Iter end)
{
auto distance (std::distance(begin, end));
if (distance < 2u) {
return std::move(*begin);
} else {
auto split (begin + distance / 2u);
return std::unique_ptr<Branch>(new Branch(
AssembleLeaves<Branch, Iter>(std::move(begin), split),
AssembleLeaves<Branch, Iter>(split, std::move(end))
));
}
}
};
class LogicalAndExpression final : public AssociativeExpression
{
public:
LogicalAndExpression(std::unique_ptr<Expression> operand1, std::unique_ptr<Expression> operand2, const DebugInfo& debugInfo = DebugInfo())
: BinaryExpression(std::move(operand1), std::move(operand2), debugInfo)
{ }
: AssociativeExpression(std::move(operand1), std::move(operand2), debugInfo)
{
RebalanceLeaves<LogicalAndExpression>();
}
protected:
ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override;
};
class LogicalOrExpression final : public BinaryExpression
class LogicalOrExpression final : public AssociativeExpression
{
public:
LogicalOrExpression(std::unique_ptr<Expression> operand1, std::unique_ptr<Expression> operand2, const DebugInfo& debugInfo = DebugInfo())
: BinaryExpression(std::move(operand1), std::move(operand2), debugInfo)
{ }
: AssociativeExpression(std::move(operand1), std::move(operand2), debugInfo)
{
RebalanceLeaves<LogicalOrExpression>();
}
protected:
ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override;