Skip to content

Commit b2d114d

Browse files
committed
Auto-rewrite associative DSL expression trees
... if depth > log2(leaves) * 2 to reduce recursion and thereby avoid stack overflows. refs #7827
1 parent 008238f commit b2d114d

File tree

2 files changed

+109
-6
lines changed

2 files changed

+109
-6
lines changed

lib/config/expression.cpp

+3
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,15 @@
1616
#include "base/defer.hpp"
1717
#include <boost/exception_ptr.hpp>
1818
#include <boost/exception/errinfo_nested_exception.hpp>
19+
#include <cmath>
1920

2021
using namespace icinga;
2122

2223
boost::signals2::signal<void (ScriptFrame&, ScriptError *ex, const DebugInfo&)> Expression::OnBreakpoint;
2324
boost::thread_specific_ptr<bool> l_InBreakpointHandler;
2425

26+
const long double AssociativeExpression::m_Log2 = logl(2);
27+
2528
Expression::~Expression()
2629
{ }
2730

lib/config/expression.hpp

+106-6
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,17 @@
99
#include "base/dictionary.hpp"
1010
#include "base/function.hpp"
1111
#include "base/exception.hpp"
12+
#include "base/logger.hpp"
1213
#include "base/scriptframe.hpp"
1314
#include "base/shared-object.hpp"
1415
#include "base/convert.hpp"
16+
#include <cmath>
17+
#include <cstdint>
18+
#include <iterator>
1519
#include <map>
20+
#include <memory>
21+
#include <utility>
22+
#include <vector>
1623

1724
namespace icinga
1825
{
@@ -562,23 +569,116 @@ class NotInExpression final : public BinaryExpression
562569
ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override;
563570
};
564571

565-
class LogicalAndExpression final : public BinaryExpression
572+
class AssociativeExpression : public BinaryExpression
573+
{
574+
public:
575+
using BinaryExpression::BinaryExpression;
576+
577+
protected:
578+
template<class Branch>
579+
void RebalanceLeaves()
580+
{
581+
uintmax_t count = 0, depth = 0, depthBuf = 0;
582+
StatLeaves<Branch>(count, depth, depthBuf);
583+
584+
if (depth > logl(count) / m_Log2 * 2) {
585+
{
586+
Log msg (LogDebug, "config");
587+
588+
msg << "Detected associative expression tree with " << count << " leaves, but "
589+
<< depth << " > log2(" << count << ") * 2 nesting levels. Rebalancing to log2(" << count << ") nesting levels.\n";
590+
591+
ShowCodeLocation(msg, m_DebugInfo);
592+
}
593+
594+
std::vector<std::unique_ptr<Expression>> leaves;
595+
leaves.reserve(count);
596+
HarvestLeaves<Branch>(leaves);
597+
598+
auto split (leaves.begin() + leaves.size() / 2u);
599+
m_Operand1 = AssembleLeaves<Branch>(leaves.begin(), split);
600+
m_Operand2 = AssembleLeaves<Branch>(split, leaves.end());
601+
}
602+
}
603+
604+
private:
605+
static const long double m_Log2;
606+
607+
template<class Branch>
608+
void StatLeaves(uintmax_t& count, uintmax_t& depth, uintmax_t& depthBuf)
609+
{
610+
++depthBuf;
611+
612+
if (depth < depthBuf) {
613+
depth = depthBuf;
614+
}
615+
616+
for (auto op : {m_Operand1.get(), m_Operand2.get()}) {
617+
auto branch (dynamic_cast<Branch*>(op));
618+
619+
if (branch == nullptr) {
620+
count += 1u;
621+
} else {
622+
branch->template StatLeaves<Branch>(count, depth, depthBuf);
623+
}
624+
}
625+
626+
--depthBuf;
627+
}
628+
629+
template<class Branch>
630+
void HarvestLeaves(std::vector<std::unique_ptr<Expression>>& leaves)
631+
{
632+
for (auto op : {&m_Operand1, &m_Operand2}) {
633+
auto branch (dynamic_cast<Branch*>(op->get()));
634+
635+
if (branch == nullptr) {
636+
leaves.emplace_back(std::move(*op));
637+
} else {
638+
branch->template HarvestLeaves<Branch>(leaves);
639+
}
640+
}
641+
}
642+
643+
template<class Branch, class Iter>
644+
std::unique_ptr<Expression> AssembleLeaves(Iter begin, Iter end)
645+
{
646+
auto distance (std::distance(begin, end));
647+
648+
if (distance < 2u) {
649+
return std::move(*begin);
650+
} else {
651+
auto split (begin + distance / 2u);
652+
653+
return std::unique_ptr<Branch>(new Branch(
654+
AssembleLeaves<Branch, Iter>(std::move(begin), split),
655+
AssembleLeaves<Branch, Iter>(split, std::move(end))
656+
));
657+
}
658+
}
659+
};
660+
661+
class LogicalAndExpression final : public AssociativeExpression
566662
{
567663
public:
568664
LogicalAndExpression(std::unique_ptr<Expression> operand1, std::unique_ptr<Expression> operand2, const DebugInfo& debugInfo = DebugInfo())
569-
: BinaryExpression(std::move(operand1), std::move(operand2), debugInfo)
570-
{ }
665+
: AssociativeExpression(std::move(operand1), std::move(operand2), debugInfo)
666+
{
667+
RebalanceLeaves<LogicalAndExpression>();
668+
}
571669

572670
protected:
573671
ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override;
574672
};
575673

576-
class LogicalOrExpression final : public BinaryExpression
674+
class LogicalOrExpression final : public AssociativeExpression
577675
{
578676
public:
579677
LogicalOrExpression(std::unique_ptr<Expression> operand1, std::unique_ptr<Expression> operand2, const DebugInfo& debugInfo = DebugInfo())
580-
: BinaryExpression(std::move(operand1), std::move(operand2), debugInfo)
581-
{ }
678+
: AssociativeExpression(std::move(operand1), std::move(operand2), debugInfo)
679+
{
680+
RebalanceLeaves<LogicalOrExpression>();
681+
}
582682

583683
protected:
584684
ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override;

0 commit comments

Comments
 (0)