From 64d50106f0e58e1bcd513a0c461c9d81d2cfe009 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 18 Feb 2020 11:26:31 +0100 Subject: [PATCH] Auto-rewrite associative DSL expression trees ... if depth > log2(leaves) * 2 to reduce recursion and thereby avoid stack overflows. refs #7827 --- lib/config/expression.cpp | 3 + lib/config/expression.hpp | 112 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 109 insertions(+), 6 deletions(-) diff --git a/lib/config/expression.cpp b/lib/config/expression.cpp index 09b860cde..c38045aaf 100644 --- a/lib/config/expression.cpp +++ b/lib/config/expression.cpp @@ -16,12 +16,15 @@ #include "base/defer.hpp" #include #include +#include using namespace icinga; boost::signals2::signal Expression::OnBreakpoint; boost::thread_specific_ptr l_InBreakpointHandler; +const long double AssociativeExpression::m_Log2 = logl(2); + Expression::~Expression() { } diff --git a/lib/config/expression.hpp b/lib/config/expression.hpp index 644548d28..356f2651f 100644 --- a/lib/config/expression.hpp +++ b/lib/config/expression.hpp @@ -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 +#include +#include #include +#include +#include +#include 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 + void RebalanceLeaves() + { + uintmax_t count = 0, depth = 0, depthBuf = 0; + StatLeaves(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> leaves; + leaves.reserve(count); + HarvestLeaves(leaves); + + auto split (leaves.begin() + leaves.size() / 2u); + m_Operand1 = AssembleLeaves(leaves.begin(), split); + m_Operand2 = AssembleLeaves(split, leaves.end()); + } + } + +private: + static const long double m_Log2; + + template + 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(op)); + + if (branch == nullptr) { + count += 1u; + } else { + branch->template StatLeaves(count, depth, depthBuf); + } + } + + --depthBuf; + } + + template + void HarvestLeaves(std::vector>& leaves) + { + for (auto op : {&m_Operand1, &m_Operand2}) { + auto branch (dynamic_cast(op->get())); + + if (branch == nullptr) { + leaves.emplace_back(std::move(*op)); + } else { + branch->template HarvestLeaves(leaves); + } + } + } + + template + std::unique_ptr 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(new Branch( + AssembleLeaves(std::move(begin), split), + AssembleLeaves(split, std::move(end)) + )); + } + } +}; + +class LogicalAndExpression final : public AssociativeExpression { public: LogicalAndExpression(std::unique_ptr operand1, std::unique_ptr operand2, const DebugInfo& debugInfo = DebugInfo()) - : BinaryExpression(std::move(operand1), std::move(operand2), debugInfo) - { } + : AssociativeExpression(std::move(operand1), std::move(operand2), debugInfo) + { + RebalanceLeaves(); + } protected: ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; }; -class LogicalOrExpression final : public BinaryExpression +class LogicalOrExpression final : public AssociativeExpression { public: LogicalOrExpression(std::unique_ptr operand1, std::unique_ptr operand2, const DebugInfo& debugInfo = DebugInfo()) - : BinaryExpression(std::move(operand1), std::move(operand2), debugInfo) - { } + : AssociativeExpression(std::move(operand1), std::move(operand2), debugInfo) + { + RebalanceLeaves(); + } protected: ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override;