diff options
Diffstat (limited to 'lib/config')
-rw-r--r-- | lib/config/CMakeLists.txt | 47 | ||||
-rw-r--r-- | lib/config/activationcontext.cpp | 61 | ||||
-rw-r--r-- | lib/config/activationcontext.hpp | 46 | ||||
-rw-r--r-- | lib/config/applyrule-targeted.cpp | 266 | ||||
-rw-r--r-- | lib/config/applyrule.cpp | 189 | ||||
-rw-r--r-- | lib/config/applyrule.hpp | 126 | ||||
-rw-r--r-- | lib/config/config_lexer.ll | 253 | ||||
-rw-r--r-- | lib/config/config_parser.yy | 1243 | ||||
-rw-r--r-- | lib/config/configcompiler.cpp | 364 | ||||
-rw-r--r-- | lib/config/configcompiler.hpp | 161 | ||||
-rw-r--r-- | lib/config/configcompilercontext.cpp | 57 | ||||
-rw-r--r-- | lib/config/configcompilercontext.hpp | 42 | ||||
-rw-r--r-- | lib/config/configfragment.hpp | 26 | ||||
-rw-r--r-- | lib/config/configitem.cpp | 849 | ||||
-rw-r--r-- | lib/config/configitem.hpp | 106 | ||||
-rw-r--r-- | lib/config/configitembuilder.cpp | 120 | ||||
-rw-r--r-- | lib/config/configitembuilder.hpp | 58 | ||||
-rw-r--r-- | lib/config/expression.cpp | 1068 | ||||
-rw-r--r-- | lib/config/expression.hpp | 986 | ||||
-rw-r--r-- | lib/config/i2-config.hpp | 16 | ||||
-rw-r--r-- | lib/config/objectrule.cpp | 18 | ||||
-rw-r--r-- | lib/config/objectrule.hpp | 33 | ||||
-rw-r--r-- | lib/config/vmops.hpp | 274 |
23 files changed, 6409 insertions, 0 deletions
diff --git a/lib/config/CMakeLists.txt b/lib/config/CMakeLists.txt new file mode 100644 index 0000000..80b8c2c --- /dev/null +++ b/lib/config/CMakeLists.txt @@ -0,0 +1,47 @@ +# Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ + +find_package(BISON 2.3.0 REQUIRED) +find_package(FLEX 2.5.31 REQUIRED) + +bison_target(config_parser config_parser.yy ${CMAKE_CURRENT_BINARY_DIR}/config_parser.cc) +set_property(SOURCE ${CMAKE_CURRENT_BINARY_DIR}/config_parser.cc PROPERTY EXCLUDE_UNITY_BUILD TRUE) + +flex_target(config_lexer config_lexer.ll ${CMAKE_CURRENT_BINARY_DIR}/config_lexer.cc) +set_property(SOURCE ${CMAKE_CURRENT_BINARY_DIR}/config_lexer.cc PROPERTY EXCLUDE_UNITY_BUILD TRUE) + +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set_property(SOURCE ${CMAKE_CURRENT_BINARY_DIR}/config_parser.cc PROPERTY COMPILE_FLAGS "-Wno-deprecated-register -Wno-parentheses-equality -Wno-unused-function") + set_property(SOURCE ${CMAKE_CURRENT_BINARY_DIR}/config_lexer.cc PROPERTY COMPILE_FLAGS "-Wno-deprecated-register -Wno-null-conversion") +endif() + +add_flex_bison_dependency(config_lexer config_parser) + +include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) + +set(config_SOURCES + i2-config.hpp + activationcontext.cpp activationcontext.hpp + applyrule.cpp applyrule-targeted.cpp applyrule.hpp + configcompiler.cpp configcompiler.hpp + configcompilercontext.cpp configcompilercontext.hpp + configfragment.hpp + configitem.cpp configitem.hpp + configitembuilder.cpp configitembuilder.hpp + expression.cpp expression.hpp + objectrule.cpp objectrule.hpp + vmops.hpp + ${FLEX_config_lexer_OUTPUTS} ${BISON_config_parser_OUTPUTS} +) + +if(ICINGA2_UNITY_BUILD) + mkunity_target(config config config_SOURCES) +endif() + +add_library(config OBJECT ${config_SOURCES}) + +add_dependencies(config base) + +set_target_properties ( + config PROPERTIES + FOLDER Lib +) diff --git a/lib/config/activationcontext.cpp b/lib/config/activationcontext.cpp new file mode 100644 index 0000000..d050875 --- /dev/null +++ b/lib/config/activationcontext.cpp @@ -0,0 +1,61 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "config/activationcontext.hpp" +#include "base/exception.hpp" + +using namespace icinga; + +boost::thread_specific_ptr<std::stack<ActivationContext::Ptr> > ActivationContext::m_ActivationStack; + +std::stack<ActivationContext::Ptr>& ActivationContext::GetActivationStack() +{ + std::stack<ActivationContext::Ptr> *actx = m_ActivationStack.get(); + + if (!actx) { + actx = new std::stack<ActivationContext::Ptr>(); + m_ActivationStack.reset(actx); + } + + return *actx; +} + +void ActivationContext::PushContext(const ActivationContext::Ptr& context) +{ + GetActivationStack().push(context); +} + +void ActivationContext::PopContext() +{ + ASSERT(!GetActivationStack().empty()); + GetActivationStack().pop(); +} + +ActivationContext::Ptr ActivationContext::GetCurrentContext() +{ + std::stack<ActivationContext::Ptr>& astack = GetActivationStack(); + + if (astack.empty()) + BOOST_THROW_EXCEPTION(std::runtime_error("Objects may not be created outside of an activation context.")); + + return astack.top(); +} + +ActivationScope::ActivationScope(ActivationContext::Ptr context) + : m_Context(std::move(context)) +{ + if (!m_Context) + m_Context = new ActivationContext(); + + ActivationContext::PushContext(m_Context); +} + +ActivationScope::~ActivationScope() +{ + ActivationContext::PopContext(); +} + +ActivationContext::Ptr ActivationScope::GetContext() const +{ + return m_Context; +} + diff --git a/lib/config/activationcontext.hpp b/lib/config/activationcontext.hpp new file mode 100644 index 0000000..3fe5d09 --- /dev/null +++ b/lib/config/activationcontext.hpp @@ -0,0 +1,46 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef ACTIVATIONCONTEXT_H +#define ACTIVATIONCONTEXT_H + +#include "config/i2-config.hpp" +#include "base/object.hpp" +#include <boost/thread/tss.hpp> +#include <stack> + +namespace icinga +{ + +class ActivationContext final : public Object +{ +public: + DECLARE_PTR_TYPEDEFS(ActivationContext); + + static ActivationContext::Ptr GetCurrentContext(); + +private: + static void PushContext(const ActivationContext::Ptr& context); + static void PopContext(); + + static std::stack<ActivationContext::Ptr>& GetActivationStack(); + + static boost::thread_specific_ptr<std::stack<ActivationContext::Ptr> > m_ActivationStack; + + friend class ActivationScope; +}; + +class ActivationScope +{ +public: + ActivationScope(ActivationContext::Ptr context = nullptr); + ~ActivationScope(); + + ActivationContext::Ptr GetContext() const; + +private: + ActivationContext::Ptr m_Context; +}; + +} + +#endif /* ACTIVATIONCONTEXT_H */ diff --git a/lib/config/applyrule-targeted.cpp b/lib/config/applyrule-targeted.cpp new file mode 100644 index 0000000..c5bfe20 --- /dev/null +++ b/lib/config/applyrule-targeted.cpp @@ -0,0 +1,266 @@ +/* Icinga 2 | (c) 2022 Icinga GmbH | GPLv2+ */ + +#include "base/string.hpp" +#include "config/applyrule.hpp" +#include "config/expression.hpp" +#include <utility> +#include <vector> + +using namespace icinga; + +/** + * @returns All ApplyRules targeting only specific parent objects including the given host. (See AddTargetedRule().) + */ +const std::set<ApplyRule::Ptr>& ApplyRule::GetTargetedHostRules(const Type::Ptr& sourceType, const String& host) +{ + auto perSourceType (m_Rules.find(sourceType.get())); + + if (perSourceType != m_Rules.end()) { + auto perHost (perSourceType->second.Targeted.find(host)); + + if (perHost != perSourceType->second.Targeted.end()) { + return perHost->second.ForHost; + } + } + + static const std::set<ApplyRule::Ptr> noRules; + return noRules; +} + +/** + * @returns All ApplyRules targeting only specific parent objects including the given service. (See AddTargetedRule().) + */ +const std::set<ApplyRule::Ptr>& ApplyRule::GetTargetedServiceRules(const Type::Ptr& sourceType, const String& host, const String& service) +{ + auto perSourceType (m_Rules.find(sourceType.get())); + + if (perSourceType != m_Rules.end()) { + auto perHost (perSourceType->second.Targeted.find(host)); + + if (perHost != perSourceType->second.Targeted.end()) { + auto perService (perHost->second.ForServices.find(service)); + + if (perService != perHost->second.ForServices.end()) { + return perService->second; + } + } + } + + static const std::set<ApplyRule::Ptr> noRules; + return noRules; +} + +/** + * If the given ApplyRule targets only specific parent objects, add it to the respective "index". + * + * - The above means for apply T "N" to Host: assign where host.name == "H" [ || host.name == "h" ... ] + * - For apply T "N" to Service it means: assign where host.name == "H" && service.name == "S" [ || host.name == "h" && service.name == "s" ... ] + * + * The order of operands of || && == doesn't matter. + * + * @returns Whether the rule has been added to the "index". + */ +bool ApplyRule::AddTargetedRule(const ApplyRule::Ptr& rule, const String& targetType, ApplyRule::PerSourceType& rules) +{ + if (targetType == "Host") { + std::vector<const String *> hosts; + + if (GetTargetHosts(rule->m_Filter.get(), hosts)) { + for (auto host : hosts) { + rules.Targeted[*host].ForHost.emplace(rule); + } + + return true; + } + } else if (targetType == "Service") { + std::vector<std::pair<const String *, const String *>> services; + + if (GetTargetServices(rule->m_Filter.get(), services)) { + for (auto service : services) { + rules.Targeted[*service.first].ForServices[*service.second].emplace(rule); + } + + return true; + } + } + + return false; +} + +/** + * If the given assign filter is like the following, extract the host names ("H", "h", ...) into the vector: + * + * host.name == "H" [ || host.name == "h" ... ] + * + * The order of operands of || == doesn't matter. + * + * @returns Whether the given assign filter is like above. + */ +bool ApplyRule::GetTargetHosts(Expression* assignFilter, std::vector<const String *>& hosts, const Dictionary::Ptr& constants) +{ + auto lor (dynamic_cast<LogicalOrExpression*>(assignFilter)); + + if (lor) { + return GetTargetHosts(lor->GetOperand1().get(), hosts, constants) + && GetTargetHosts(lor->GetOperand2().get(), hosts, constants); + } + + auto name (GetComparedName(assignFilter, "host", constants)); + + if (name) { + hosts.emplace_back(name); + return true; + } + + return false; +} + +/** + * If the given assign filter is like the following, extract the host+service names ("H"+"S", "h"+"s", ...) into the vector: + * + * host.name == "H" && service.name == "S" [ || host.name == "h" && service.name == "s" ... ] + * + * The order of operands of || && == doesn't matter. + * + * @returns Whether the given assign filter is like above. + */ +bool ApplyRule::GetTargetServices(Expression* assignFilter, std::vector<std::pair<const String *, const String *>>& services, const Dictionary::Ptr& constants) +{ + auto lor (dynamic_cast<LogicalOrExpression*>(assignFilter)); + + if (lor) { + return GetTargetServices(lor->GetOperand1().get(), services, constants) + && GetTargetServices(lor->GetOperand2().get(), services, constants); + } + + auto service (GetTargetService(assignFilter, constants)); + + if (service.first) { + services.emplace_back(service); + return true; + } + + return false; +} + +/** + * If the given filter is like the following, extract the host+service names ("H"+"S"): + * + * host.name == "H" && service.name == "S" + * + * The order of operands of && == doesn't matter. + * + * @returns {host, service} on success and {nullptr, nullptr} on failure. + */ +std::pair<const String *, const String *> ApplyRule::GetTargetService(Expression* assignFilter, const Dictionary::Ptr& constants) +{ + auto land (dynamic_cast<LogicalAndExpression*>(assignFilter)); + + if (!land) { + return {nullptr, nullptr}; + } + + auto op1 (land->GetOperand1().get()); + auto op2 (land->GetOperand2().get()); + auto host (GetComparedName(op1, "host", constants)); + + if (!host) { + std::swap(op1, op2); + host = GetComparedName(op1, "host", constants); + } + + if (host) { + auto service (GetComparedName(op2, "service", constants)); + + if (service) { + return {host, service}; + } + } + + return {nullptr, nullptr}; +} + +/** + * If the given filter is like the following, extract the object name ("N"): + * + * $lcType$.name == "N" + * + * The order of operands of == doesn't matter. + * + * @returns The object name on success and nullptr on failure. + */ +const String * ApplyRule::GetComparedName(Expression* assignFilter, const char * lcType, const Dictionary::Ptr& constants) +{ + auto eq (dynamic_cast<EqualExpression*>(assignFilter)); + + if (!eq) { + return nullptr; + } + + auto op1 (eq->GetOperand1().get()); + auto op2 (eq->GetOperand2().get()); + + if (IsNameIndexer(op1, lcType, constants)) { + return GetConstString(op2, constants); + } + + if (IsNameIndexer(op2, lcType, constants)) { + return GetConstString(op1, constants); + } + + return nullptr; +} + +/** + * @returns Whether the given expression is like $lcType$.name. + */ +bool ApplyRule::IsNameIndexer(Expression* exp, const char * lcType, const Dictionary::Ptr& constants) +{ + auto ixr (dynamic_cast<IndexerExpression*>(exp)); + + if (!ixr) { + return false; + } + + auto var (dynamic_cast<VariableExpression*>(ixr->GetOperand1().get())); + + if (!var || var->GetVariable() != lcType) { + return false; + } + + auto val (GetConstString(ixr->GetOperand2().get(), constants)); + + return val && *val == "name"; +} + +/** + * @returns If the given expression is a constant string, its address. nullptr on failure. + */ +const String * ApplyRule::GetConstString(Expression* exp, const Dictionary::Ptr& constants) +{ + auto cnst (GetConst(exp, constants)); + + return cnst && cnst->IsString() ? &cnst->Get<String>() : nullptr; +} + +/** + * @returns If the given expression is a constant, its address. nullptr on failure. + */ +const Value * ApplyRule::GetConst(Expression* exp, const Dictionary::Ptr& constants) +{ + auto lit (dynamic_cast<LiteralExpression*>(exp)); + + if (lit) { + return &lit->GetValue(); + } + + if (constants) { + auto var (dynamic_cast<VariableExpression*>(exp)); + + if (var) { + return constants->GetRef(var->GetVariable()); + } + } + + return nullptr; +} diff --git a/lib/config/applyrule.cpp b/lib/config/applyrule.cpp new file mode 100644 index 0000000..8739971 --- /dev/null +++ b/lib/config/applyrule.cpp @@ -0,0 +1,189 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "config/applyrule.hpp" +#include "base/logger.hpp" +#include <set> +#include <unordered_set> + +using namespace icinga; + +ApplyRule::RuleMap ApplyRule::m_Rules; +ApplyRule::TypeMap ApplyRule::m_Types; + +ApplyRule::ApplyRule(String name, Expression::Ptr expression, + Expression::Ptr filter, String package, String fkvar, String fvvar, Expression::Ptr fterm, + bool ignoreOnError, DebugInfo di, Dictionary::Ptr scope) + : m_Name(std::move(name)), m_Expression(std::move(expression)), m_Filter(std::move(filter)), m_Package(std::move(package)), m_FKVar(std::move(fkvar)), + m_FVVar(std::move(fvvar)), m_FTerm(std::move(fterm)), m_IgnoreOnError(ignoreOnError), m_DebugInfo(std::move(di)), m_Scope(std::move(scope)), m_HasMatches(false) +{ } + +String ApplyRule::GetName() const +{ + return m_Name; +} + +Expression::Ptr ApplyRule::GetExpression() const +{ + return m_Expression; +} + +Expression::Ptr ApplyRule::GetFilter() const +{ + return m_Filter; +} + +String ApplyRule::GetPackage() const +{ + return m_Package; +} + +Expression::Ptr ApplyRule::GetFTerm() const +{ + return m_FTerm; +} + +bool ApplyRule::GetIgnoreOnError() const +{ + return m_IgnoreOnError; +} + +const DebugInfo& ApplyRule::GetDebugInfo() const +{ + return m_DebugInfo; +} + +Dictionary::Ptr ApplyRule::GetScope() const +{ + return m_Scope; +} + +void ApplyRule::AddRule(const String& sourceType, const String& targetType, const String& name, + const Expression::Ptr& expression, const Expression::Ptr& filter, const String& package, const String& fkvar, + const String& fvvar, const Expression::Ptr& fterm, bool ignoreOnError, const DebugInfo& di, const Dictionary::Ptr& scope) +{ + auto actualTargetType (&targetType); + + if (*actualTargetType == "") { + auto& targetTypes (GetTargetTypes(sourceType)); + + if (targetTypes.size() == 1u) { + actualTargetType = &targetTypes[0]; + } + } + + ApplyRule::Ptr rule = new ApplyRule(name, expression, filter, package, fkvar, fvvar, fterm, ignoreOnError, di, scope); + auto& rules (m_Rules[Type::GetByName(sourceType).get()]); + + if (!AddTargetedRule(rule, *actualTargetType, rules)) { + rules.Regular[Type::GetByName(*actualTargetType).get()].emplace_back(std::move(rule)); + } +} + +bool ApplyRule::EvaluateFilter(ScriptFrame& frame) const +{ + return Convert::ToBool(m_Filter->Evaluate(frame)); +} + +void ApplyRule::RegisterType(const String& sourceType, const std::vector<String>& targetTypes) +{ + m_Types[sourceType] = targetTypes; +} + +bool ApplyRule::IsValidSourceType(const String& sourceType) +{ + return m_Types.find(sourceType) != m_Types.end(); +} + +bool ApplyRule::IsValidTargetType(const String& sourceType, const String& targetType) +{ + auto it = m_Types.find(sourceType); + + if (it == m_Types.end()) + return false; + + if (it->second.size() == 1 && targetType == "") + return true; + + for (const String& type : it->second) { + if (type == targetType) + return true; + } + + return false; +} + +const std::vector<String>& ApplyRule::GetTargetTypes(const String& sourceType) +{ + auto it = m_Types.find(sourceType); + + if (it == m_Types.end()) { + static const std::vector<String> noTypes; + return noTypes; + } + + return it->second; +} + +void ApplyRule::AddMatch() +{ + m_HasMatches.store(true, std::memory_order_relaxed); +} + +bool ApplyRule::HasMatches() const +{ + return m_HasMatches.load(std::memory_order_relaxed); +} + +const std::vector<ApplyRule::Ptr>& ApplyRule::GetRules(const Type::Ptr& sourceType, const Type::Ptr& targetType) +{ + auto perSourceType (m_Rules.find(sourceType.get())); + + if (perSourceType != m_Rules.end()) { + auto perTargetType (perSourceType->second.Regular.find(targetType.get())); + + if (perTargetType != perSourceType->second.Regular.end()) { + return perTargetType->second; + } + } + + static const std::vector<ApplyRule::Ptr> noRules; + return noRules; +} + +void ApplyRule::CheckMatches(bool silent) +{ + for (auto& perSourceType : m_Rules) { + for (auto& perTargetType : perSourceType.second.Regular) { + for (auto& rule : perTargetType.second) { + CheckMatches(rule, perSourceType.first, silent); + } + } + + std::unordered_set<ApplyRule*> targeted; + + for (auto& perHost : perSourceType.second.Targeted) { + for (auto& rule : perHost.second.ForHost) { + targeted.emplace(rule.get()); + } + + for (auto& perService : perHost.second.ForServices) { + for (auto& rule : perService.second) { + targeted.emplace(rule.get()); + } + } + } + + for (auto rule : targeted) { + CheckMatches(rule, perSourceType.first, silent); + } + } +} + +void ApplyRule::CheckMatches(const ApplyRule::Ptr& rule, Type* sourceType, bool silent) +{ + if (!rule->HasMatches() && !silent) { + Log(LogWarning, "ApplyRule") + << "Apply rule '" << rule->GetName() << "' (" << rule->GetDebugInfo() << ") for type '" + << sourceType->GetName() << "' does not match anywhere!"; + } +} diff --git a/lib/config/applyrule.hpp b/lib/config/applyrule.hpp new file mode 100644 index 0000000..cf9b6e5 --- /dev/null +++ b/lib/config/applyrule.hpp @@ -0,0 +1,126 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef APPLYRULE_H +#define APPLYRULE_H + +#include "config/i2-config.hpp" +#include "config/expression.hpp" +#include "base/debuginfo.hpp" +#include "base/shared-object.hpp" +#include "base/type.hpp" +#include <unordered_map> +#include <atomic> + +namespace icinga +{ + +/** + * @ingroup config + */ +class ApplyRule : public SharedObject +{ +public: + DECLARE_PTR_TYPEDEFS(ApplyRule); + + struct PerHost + { + std::set<ApplyRule::Ptr> ForHost; + std::unordered_map<String /* service */, std::set<ApplyRule::Ptr>> ForServices; + }; + + struct PerSourceType + { + std::unordered_map<Type* /* target type */, std::vector<ApplyRule::Ptr>> Regular; + std::unordered_map<String /* host */, PerHost> Targeted; + }; + + /* + * m_Rules[T::TypeInstance.get()].Targeted["H"].ForHost + * contains all apply rules like apply T "x" to Host { ... } + * which target only specific hosts incl. "H", e.g. via + * assign where host.name == "H" || host.name == "h". + * + * m_Rules[T::TypeInstance.get()].Targeted["H"].ForServices["S"] + * contains all apply rules like apply T "x" to Service { ... } + * which target only specific services on specific hosts, + * e.g. via assign where host.name == "H" && service.name == "S". + * + * m_Rules[T::TypeInstance.get()].Regular[C::TypeInstance.get()] + * contains all other apply rules like apply T "x" to C { ... }. + */ + typedef std::unordered_map<Type* /* source type */, PerSourceType> RuleMap; + + typedef std::map<String, std::vector<String> > TypeMap; + + String GetName() const; + Expression::Ptr GetExpression() const; + Expression::Ptr GetFilter() const; + String GetPackage() const; + + inline const String& GetFKVar() const noexcept + { + return m_FKVar; + } + + inline const String& GetFVVar() const noexcept + { + return m_FVVar; + } + + Expression::Ptr GetFTerm() const; + bool GetIgnoreOnError() const; + const DebugInfo& GetDebugInfo() const; + Dictionary::Ptr GetScope() const; + void AddMatch(); + bool HasMatches() const; + + bool EvaluateFilter(ScriptFrame& frame) const; + + static void AddRule(const String& sourceType, const String& targetType, const String& name, const Expression::Ptr& expression, + const Expression::Ptr& filter, const String& package, const String& fkvar, const String& fvvar, const Expression::Ptr& fterm, + bool ignoreOnError, const DebugInfo& di, const Dictionary::Ptr& scope); + static const std::vector<ApplyRule::Ptr>& GetRules(const Type::Ptr& sourceType, const Type::Ptr& targetType); + static const std::set<ApplyRule::Ptr>& GetTargetedHostRules(const Type::Ptr& sourceType, const String& host); + static const std::set<ApplyRule::Ptr>& GetTargetedServiceRules(const Type::Ptr& sourceType, const String& host, const String& service); + static bool GetTargetHosts(Expression* assignFilter, std::vector<const String *>& hosts, const Dictionary::Ptr& constants = nullptr); + static bool GetTargetServices(Expression* assignFilter, std::vector<std::pair<const String *, const String *>>& services, const Dictionary::Ptr& constants = nullptr); + + static void RegisterType(const String& sourceType, const std::vector<String>& targetTypes); + static bool IsValidSourceType(const String& sourceType); + static bool IsValidTargetType(const String& sourceType, const String& targetType); + static const std::vector<String>& GetTargetTypes(const String& sourceType); + + static void CheckMatches(bool silent); + static void CheckMatches(const ApplyRule::Ptr& rule, Type* sourceType, bool silent); + +private: + String m_Name; + Expression::Ptr m_Expression; + Expression::Ptr m_Filter; + String m_Package; + String m_FKVar; + String m_FVVar; + Expression::Ptr m_FTerm; + bool m_IgnoreOnError; + DebugInfo m_DebugInfo; + Dictionary::Ptr m_Scope; + std::atomic<bool> m_HasMatches; + + static TypeMap m_Types; + static RuleMap m_Rules; + + static bool AddTargetedRule(const ApplyRule::Ptr& rule, const String& targetType, PerSourceType& rules); + static std::pair<const String *, const String *> GetTargetService(Expression* assignFilter, const Dictionary::Ptr& constants); + static const String * GetComparedName(Expression* assignFilter, const char * lcType, const Dictionary::Ptr& constants); + static bool IsNameIndexer(Expression* exp, const char * lcType, const Dictionary::Ptr& constants); + static const String * GetConstString(Expression* exp, const Dictionary::Ptr& constants); + static const Value * GetConst(Expression* exp, const Dictionary::Ptr& constants); + + ApplyRule(String name, Expression::Ptr expression, + Expression::Ptr filter, String package, String fkvar, String fvvar, Expression::Ptr fterm, + bool ignoreOnError, DebugInfo di, Dictionary::Ptr scope); +}; + +} + +#endif /* APPLYRULE_H */ diff --git a/lib/config/config_lexer.ll b/lib/config/config_lexer.ll new file mode 100644 index 0000000..abfdaff --- /dev/null +++ b/lib/config/config_lexer.ll @@ -0,0 +1,253 @@ +%{ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "config/configcompiler.hpp" +#include "config/expression.hpp" +#include "base/exception.hpp" +#include <utility> + +using namespace icinga; + +#include "config/config_parser.hh" +#include <sstream> + +#define YYLTYPE icinga::CompilerDebugInfo + +#define YY_EXTRA_TYPE ConfigCompiler * +#define YY_USER_ACTION \ +do { \ + yylloc->Path = yyextra->GetPath(); \ + yylloc->FirstLine = yylineno; \ + yylloc->FirstColumn = yycolumn; \ + yylloc->LastLine = yylineno; \ + yylloc->LastColumn = yycolumn + yyleng - 1; \ + yycolumn += yyleng; \ +} while (0); + +#define YY_INPUT(buf, result, max_size) \ +do { \ + result = yyextra->ReadInput(buf, max_size); \ +} while (0) +%} + +%option reentrant noyywrap yylineno +%option bison-bridge bison-locations +%option never-interactive nounistd +%option noinput nounput + +%x C_COMMENT +%x STRING +%x HEREDOC + +%% +\" { + yyextra->m_LexBuffer.Clear(); + + yyextra->m_LocationBegin = *yylloc; + + BEGIN(STRING); + } + +<STRING>\" { + BEGIN(INITIAL); + + yylloc->FirstLine = yyextra->m_LocationBegin.FirstLine; + yylloc->FirstColumn = yyextra->m_LocationBegin.FirstColumn; + + yylval->text = new String(std::move(yyextra->m_LexBuffer)); + + return T_STRING; + } + +<STRING>\n { + BOOST_THROW_EXCEPTION(ScriptError("Unterminated string literal", DebugInfoRange(yyextra->m_LocationBegin, *yylloc))); + } + +<STRING>\\[0-7]{1,3} { + /* octal escape sequence */ + int result; + + (void) sscanf(yytext + 1, "%o", &result); + + if (result > 0xff) { + /* error, constant is out-of-bounds */ + BOOST_THROW_EXCEPTION(ScriptError("Constant is out of bounds: " + String(yytext), *yylloc)); + } + + yyextra->m_LexBuffer += static_cast<char>(result); + } + +<STRING>\\[0-9]+ { + /* generate error - bad escape sequence; something + * like '\48' or '\0777777' + */ + BOOST_THROW_EXCEPTION(ScriptError("Bad escape sequence found: " + String(yytext), *yylloc)); + } +<STRING>\\n { yyextra->m_LexBuffer += '\n'; } +<STRING>\\\\ { yyextra->m_LexBuffer += '\\'; } +<STRING>\\\" { yyextra->m_LexBuffer += '"'; } +<STRING>\\t { yyextra->m_LexBuffer += '\t'; } +<STRING>\\r { yyextra->m_LexBuffer += '\r'; } +<STRING>\\b { yyextra->m_LexBuffer += '\b'; } +<STRING>\\f { yyextra->m_LexBuffer += '\f'; } +<STRING>\\\n { yyextra->m_LexBuffer += yytext[1]; } +<STRING>\\. { + BOOST_THROW_EXCEPTION(ScriptError("Bad escape sequence found: " + String(yytext), *yylloc)); + } + +<STRING>[^\\\n\"]+ { + char *yptr = yytext; + + while (*yptr) + yyextra->m_LexBuffer += *yptr++; + } + +<STRING><<EOF>> { + BOOST_THROW_EXCEPTION(ScriptError("End-of-file while in string literal", DebugInfoRange(yyextra->m_LocationBegin, *yylloc))); + } + +\{\{\{ { + yyextra->m_LexBuffer.Clear(); + + yyextra->m_LocationBegin = *yylloc; + + BEGIN(HEREDOC); + } + +<HEREDOC><<EOF>> { + BOOST_THROW_EXCEPTION(ScriptError("End-of-file while in string literal", DebugInfoRange(yyextra->m_LocationBegin, *yylloc))); + } + +<HEREDOC>\}\}\} { + BEGIN(INITIAL); + + yylloc->FirstLine = yyextra->m_LocationBegin.FirstLine; + yylloc->FirstColumn = yyextra->m_LocationBegin.FirstColumn; + + yylval->text = new String(std::move(yyextra->m_LexBuffer)); + + return T_STRING; + } + +<HEREDOC>(.|\n) { yyextra->m_LexBuffer += yytext[0]; } + +<INITIAL>{ +"/*" BEGIN(C_COMMENT); +} + +<C_COMMENT>{ +"*/" BEGIN(INITIAL); +[^*] /* ignore comment */ +"*" /* ignore star */ +} + +<C_COMMENT><<EOF>> { + BOOST_THROW_EXCEPTION(ScriptError("End-of-file while in comment", *yylloc)); + } + + +\/\/[^\n]* /* ignore C++-style comments */ +#[^\n]* /* ignore shell-style comments */ +[ \t] /* ignore whitespace */ + +<INITIAL>{ +object return T_OBJECT; +template return T_TEMPLATE; +include return T_INCLUDE; +include_recursive return T_INCLUDE_RECURSIVE; +include_zones return T_INCLUDE_ZONES; +library return T_LIBRARY; +null return T_NULL; +true { yylval->boolean = 1; return T_BOOLEAN; } +false { yylval->boolean = 0; return T_BOOLEAN; } +const return T_CONST; +var return T_VAR; +this return T_THIS; +globals return T_GLOBALS; +locals return T_LOCALS; +use return T_USE; +using return T_USING; +apply return T_APPLY; +default return T_DEFAULT; +to return T_TO; +where return T_WHERE; +import return T_IMPORT; +assign return T_ASSIGN; +ignore return T_IGNORE; +function return T_FUNCTION; +return return T_RETURN; +break return T_BREAK; +continue return T_CONTINUE; +for return T_FOR; +if return T_IF; +else return T_ELSE; +while return T_WHILE; +throw return T_THROW; +try return T_TRY; +except return T_EXCEPT; +ignore_on_error return T_IGNORE_ON_ERROR; +current_filename return T_CURRENT_FILENAME; +current_line return T_CURRENT_LINE; +debugger return T_DEBUGGER; +namespace return T_NAMESPACE; +=\> return T_FOLLOWS; +\<\< return T_SHIFT_LEFT; +\>\> return T_SHIFT_RIGHT; +\<= return T_LESS_THAN_OR_EQUAL; +\>= return T_GREATER_THAN_OR_EQUAL; +== return T_EQUAL; +!= return T_NOT_EQUAL; +!in return T_NOT_IN; +in return T_IN; +&& return T_LOGICAL_AND; +\|\| return T_LOGICAL_OR; +\{\{ return T_NULLARY_LAMBDA_BEGIN; +\}\} return T_NULLARY_LAMBDA_END; +[a-zA-Z_][a-zA-Z0-9\_]* { yylval->text = new String(yytext); return T_IDENTIFIER; } +@[a-zA-Z_][a-zA-Z0-9\_]* { yylval->text = new String(yytext + 1); return T_IDENTIFIER; } +\<[^ \>]*\> { yytext[yyleng-1] = '\0'; yylval->text = new String(yytext + 1); return T_STRING_ANGLE; } +[0-9]+(\.[0-9]+)?ms { yylval->num = strtod(yytext, NULL) / 1000; return T_NUMBER; } +[0-9]+(\.[0-9]+)?d { yylval->num = strtod(yytext, NULL) * 60 * 60 * 24; return T_NUMBER; } +[0-9]+(\.[0-9]+)?h { yylval->num = strtod(yytext, NULL) * 60 * 60; return T_NUMBER; } +[0-9]+(\.[0-9]+)?m { yylval->num = strtod(yytext, NULL) * 60; return T_NUMBER; } +[0-9]+(\.[0-9]+)?s { yylval->num = strtod(yytext, NULL); return T_NUMBER; } +[0-9]+(\.[0-9]+)? { yylval->num = strtod(yytext, NULL); return T_NUMBER; } += { yylval->csop = OpSetLiteral; return T_SET; } +\+= { yylval->csop = OpSetAdd; return T_SET_ADD; } +-= { yylval->csop = OpSetSubtract; return T_SET_SUBTRACT; } +\*= { yylval->csop = OpSetMultiply; return T_SET_MULTIPLY; } +\/= { yylval->csop = OpSetDivide; return T_SET_DIVIDE; } +\%= { yylval->csop = OpSetModulo; return T_SET_MODULO; } +\^= { yylval->csop = OpSetXor; return T_SET_XOR; } +\&= { yylval->csop = OpSetBinaryAnd; return T_SET_BINARY_AND; } +\|= { yylval->csop = OpSetBinaryOr; return T_SET_BINARY_OR; } +\+ return T_PLUS; +\- return T_MINUS; +\* return T_MULTIPLY; +\/ return T_DIVIDE_OP; +\% return T_MODULO; +\^ return T_XOR; +\& return T_BINARY_AND; +\| return T_BINARY_OR; +\< return T_LESS_THAN; +\> return T_GREATER_THAN; +} + +\( { yyextra->m_IgnoreNewlines.push(true); return '('; } +\) { yyextra->m_IgnoreNewlines.pop(); return ')'; } +[\r\n]+ { yycolumn -= strlen(yytext) - 1; if (!yyextra->m_IgnoreNewlines.top()) { return T_NEWLINE; } } +<<EOF>> { if (!yyextra->m_Eof) { yyextra->m_Eof = true; return T_NEWLINE; } else { yyterminate(); } } +. return yytext[0]; + +%% + +void ConfigCompiler::InitializeScanner() +{ + yylex_init(&m_Scanner); + yyset_extra(this, m_Scanner); +} + +void ConfigCompiler::DestroyScanner() +{ + yylex_destroy(m_Scanner); +} diff --git a/lib/config/config_parser.yy b/lib/config/config_parser.yy new file mode 100644 index 0000000..939681e --- /dev/null +++ b/lib/config/config_parser.yy @@ -0,0 +1,1243 @@ +%{ +#define YYDEBUG 1 + +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "config/i2-config.hpp" +#include "config/configcompiler.hpp" +#include "config/expression.hpp" +#include "config/applyrule.hpp" +#include "config/objectrule.hpp" +#include "base/value.hpp" +#include "base/utility.hpp" +#include "base/exception.hpp" +#include "base/configtype.hpp" +#include "base/exception.hpp" +#include <sstream> +#include <stack> + +#define YYLTYPE icinga::CompilerDebugInfo +#define YYERROR_VERBOSE + +#define YYLLOC_DEFAULT(Current, Rhs, N) \ +do { \ + if (N) { \ + (Current).Path = YYRHSLOC(Rhs, 1).Path; \ + (Current).FirstLine = YYRHSLOC(Rhs, 1).FirstLine; \ + (Current).FirstColumn = YYRHSLOC(Rhs, 1).FirstColumn; \ + (Current).LastLine = YYRHSLOC(Rhs, N).LastLine; \ + (Current).LastColumn = YYRHSLOC(Rhs, N).LastColumn; \ + } else { \ + (Current).Path = YYRHSLOC(Rhs, 0).Path; \ + (Current).FirstLine = (Current).LastLine = \ + YYRHSLOC(Rhs, 0).LastLine; \ + (Current).FirstColumn = (Current).LastColumn = \ + YYRHSLOC(Rhs, 0).LastColumn; \ + } \ +} while (0) + +#define YY_LOCATION_PRINT(file, loc) \ +do { \ + std::ostringstream msgbuf; \ + msgbuf << loc; \ + std::string str = msgbuf.str(); \ + fputs(str.c_str(), file); \ +} while (0) + +#define YYINITDEPTH 10000 + +using namespace icinga; + +template<typename T> +static void MakeRBinaryOp(Expression** result, Expression *left, Expression *right, const DebugInfo& diLeft, const DebugInfo& diRight) +{ + *result = new T(std::unique_ptr<Expression>(left), std::unique_ptr<Expression>(right), DebugInfoRange(diLeft, diRight)); +} + +%} + +%pure-parser + +%locations +%defines +%error-verbose +%glr-parser + +%parse-param { std::vector<std::pair<std::unique_ptr<Expression>, EItemInfo> > *llist } +%parse-param { ConfigCompiler *context } +%lex-param { void *scanner } + +%union { + String *text; + double num; + bool boolean; + icinga::Expression *expr; + icinga::DictExpression *dexpr; + CombinedSetOp csop; + std::vector<String> *slist; + std::vector<std::pair<std::unique_ptr<Expression>, EItemInfo> > *llist; + std::vector<std::unique_ptr<Expression> > *elist; + std::vector<std::pair<std::unique_ptr<Expression>, std::unique_ptr<Expression> > > *ebranchlist; + std::pair<std::unique_ptr<Expression>, std::unique_ptr<Expression> > *ebranch; + std::pair<String, std::unique_ptr<Expression> > *cvitem; + std::map<String, std::unique_ptr<Expression> > *cvlist; + icinga::ScopeSpecifier scope; +} + +%token T_NEWLINE "new-line" +%token <text> T_STRING +%token <text> T_STRING_ANGLE +%token <num> T_NUMBER +%token <boolean> T_BOOLEAN +%token T_NULL +%token <text> T_IDENTIFIER + +%token <csop> T_SET "= (T_SET)" +%token <csop> T_SET_ADD "+= (T_SET_ADD)" +%token <csop> T_SET_SUBTRACT "-= (T_SET_SUBTRACT)" +%token <csop> T_SET_MULTIPLY "*= (T_SET_MULTIPLY)" +%token <csop> T_SET_DIVIDE "/= (T_SET_DIVIDE)" +%token <csop> T_SET_MODULO "%= (T_SET_MODULO)" +%token <csop> T_SET_XOR "^= (T_SET_XOR)" +%token <csop> T_SET_BINARY_AND "&= (T_SET_BINARY_AND)" +%token <csop> T_SET_BINARY_OR "|= (T_SET_BINARY_OR)" + +%token T_SHIFT_LEFT "<< (T_SHIFT_LEFT)" +%token T_SHIFT_RIGHT ">> (T_SHIFT_RIGHT)" +%token T_EQUAL "== (T_EQUAL)" +%token T_NOT_EQUAL "!= (T_NOT_EQUAL)" +%token T_IN "in (T_IN)" +%token T_NOT_IN "!in (T_NOT_IN)" +%token T_LOGICAL_AND "&& (T_LOGICAL_AND)" +%token T_LOGICAL_OR "|| (T_LOGICAL_OR)" +%token T_LESS_THAN_OR_EQUAL "<= (T_LESS_THAN_OR_EQUAL)" +%token T_GREATER_THAN_OR_EQUAL ">= (T_GREATER_THAN_OR_EQUAL)" +%token T_PLUS "+ (T_PLUS)" +%token T_MINUS "- (T_MINUS)" +%token T_MULTIPLY "* (T_MULTIPLY)" +%token T_DIVIDE_OP "/ (T_DIVIDE_OP)" +%token T_MODULO "% (T_MODULO)" +%token T_XOR "^ (T_XOR)" +%token T_BINARY_AND "& (T_BINARY_AND)" +%token T_BINARY_OR "| (T_BINARY_OR)" +%token T_LESS_THAN "< (T_LESS_THAN)" +%token T_GREATER_THAN "> (T_GREATER_THAN)" + +%token T_VAR "var (T_VAR)" +%token T_GLOBALS "globals (T_GLOBALS)" +%token T_LOCALS "locals (T_LOCALS)" +%token T_CONST "const (T_CONST)" +%token T_DEFAULT "default (T_DEFAULT)" +%token T_IGNORE_ON_ERROR "ignore_on_error (T_IGNORE_ON_ERROR)" +%token T_CURRENT_FILENAME "current_filename (T_CURRENT_FILENAME)" +%token T_CURRENT_LINE "current_line (T_CURRENT_LINE)" +%token T_DEBUGGER "debugger (T_DEBUGGER)" +%token T_NAMESPACE "namespace (T_NAMESPACE)" +%token T_USE "use (T_USE)" +%token T_USING "using (T_USING)" +%token T_OBJECT "object (T_OBJECT)" +%token T_TEMPLATE "template (T_TEMPLATE)" +%token T_INCLUDE "include (T_INCLUDE)" +%token T_INCLUDE_RECURSIVE "include_recursive (T_INCLUDE_RECURSIVE)" +%token T_INCLUDE_ZONES "include_zones (T_INCLUDE_ZONES)" +%token T_LIBRARY "library (T_LIBRARY)" +%token T_APPLY "apply (T_APPLY)" +%token T_TO "to (T_TO)" +%token T_WHERE "where (T_WHERE)" +%token T_IMPORT "import (T_IMPORT)" +%token T_ASSIGN "assign (T_ASSIGN)" +%token T_IGNORE "ignore (T_IGNORE)" +%token T_FUNCTION "function (T_FUNCTION)" +%token T_RETURN "return (T_RETURN)" +%token T_BREAK "break (T_BREAK)" +%token T_CONTINUE "continue (T_CONTINUE)" +%token T_FOR "for (T_FOR)" +%token T_IF "if (T_IF)" +%token T_ELSE "else (T_ELSE)" +%token T_WHILE "while (T_WHILE)" +%token T_THROW "throw (T_THROW)" +%token T_TRY "try (T_TRY)" +%token T_EXCEPT "except (T_EXCEPT)" +%token T_FOLLOWS "=> (T_FOLLOWS)" +%token T_NULLARY_LAMBDA_BEGIN "{{ (T_NULLARY_LAMBDA_BEGIN)" +%token T_NULLARY_LAMBDA_END "}} (T_NULLARY_LAMBDA_END)" + +%type <text> identifier +%type <elist> rterm_items +%type <elist> rterm_items_inner +%type <slist> identifier_items +%type <slist> identifier_items_inner +%type <csop> combined_set_op +%type <llist> statements +%type <llist> lterm_items +%type <llist> lterm_items_inner +%type <expr> rterm +%type <expr> rterm_array +%type <dexpr> rterm_dict +%type <dexpr> rterm_scope_require_side_effect +%type <dexpr> rterm_scope +%type <ebranchlist> else_if_branches +%type <ebranch> else_if_branch +%type <expr> rterm_side_effect +%type <expr> rterm_no_side_effect +%type <expr> rterm_no_side_effect_no_dict +%type <expr> lterm +%type <expr> object +%type <expr> apply +%type <expr> optional_rterm +%type <text> target_type_specifier +%type <boolean> default_specifier +%type <boolean> ignore_specifier +%type <cvlist> use_specifier +%type <cvlist> use_specifier_items +%type <cvitem> use_specifier_item +%type <num> object_declaration + +%right T_FOLLOWS +%right T_INCLUDE T_INCLUDE_RECURSIVE T_INCLUDE_ZONES T_OBJECT T_TEMPLATE T_APPLY T_IMPORT T_ASSIGN T_IGNORE T_WHERE +%right T_FUNCTION T_FOR +%left T_SET T_SET_ADD T_SET_SUBTRACT T_SET_MULTIPLY T_SET_DIVIDE T_SET_MODULO T_SET_XOR T_SET_BINARY_AND T_SET_BINARY_OR +%right '?' ':' +%left T_LOGICAL_OR +%left T_LOGICAL_AND +%left T_RETURN T_BREAK T_CONTINUE +%left T_IDENTIFIER +%left T_BINARY_OR +%left T_XOR +%left T_BINARY_AND +%nonassoc T_EQUAL T_NOT_EQUAL +%left T_IN T_NOT_IN +%nonassoc T_LESS_THAN T_LESS_THAN_OR_EQUAL T_GREATER_THAN T_GREATER_THAN_OR_EQUAL +%left T_SHIFT_LEFT T_SHIFT_RIGHT +%left T_PLUS T_MINUS +%left T_MULTIPLY T_DIVIDE_OP T_MODULO +%left UNARY_MINUS UNARY_PLUS +%right REF_OP DEREF_OP +%right '!' '~' +%left '.' '(' '[' +%left T_VAR T_THIS T_GLOBALS T_LOCALS +%right ';' ',' +%right T_NEWLINE +%{ + +int yylex(YYSTYPE *lvalp, YYLTYPE *llocp, void *scanner); + +extern int yydebug; + +void yyerror(const YYLTYPE *locp, std::vector<std::pair<std::unique_ptr<Expression>, EItemInfo> > *, ConfigCompiler *context, const char *err) +{ + bool incomplete = context && context->m_Eof && (context->m_OpenBraces > 0); + BOOST_THROW_EXCEPTION(ScriptError(err, *locp, incomplete)); +} + +int yyparse(std::vector<std::pair<std::unique_ptr<Expression>, EItemInfo> > *llist, ConfigCompiler *context); + +static void BeginFlowControlBlock(ConfigCompiler *compiler, int allowedTypes, bool inherit) +{ + if (inherit) + allowedTypes |= compiler->m_FlowControlInfo.top(); + + compiler->m_FlowControlInfo.push(allowedTypes); +} + +static void EndFlowControlBlock(ConfigCompiler *compiler) +{ + compiler->m_FlowControlInfo.pop(); +} + +static void UseFlowControl(ConfigCompiler *compiler, FlowControlType type, const CompilerDebugInfo& location) +{ + int fci = compiler->m_FlowControlInfo.top(); + + if ((type & fci) != type) + BOOST_THROW_EXCEPTION(ScriptError("Invalid flow control statement.", location)); +} + +std::unique_ptr<Expression> ConfigCompiler::Compile() +{ + std::vector<std::pair<std::unique_ptr<Expression>, EItemInfo> > llist; + + //yydebug = 1; + + m_IgnoreNewlines.push(false); + BeginFlowControlBlock(this, 0, false); + + if (yyparse(&llist, this) != 0) + return NULL; + + EndFlowControlBlock(this); + m_IgnoreNewlines.pop(); + + std::vector<std::unique_ptr<Expression> > dlist; + decltype(llist.size()) num = 0; + for (auto& litem : llist) { + if (!litem.second.SideEffect && num != llist.size() - 1) { + yyerror(&litem.second.DebugInfo, NULL, NULL, "Value computed is not used."); + } + dlist.emplace_back(std::move(litem.first)); + num++; + } + + std::unique_ptr<DictExpression> expr{new DictExpression(std::move(dlist))}; + expr->MakeInline(); + return std::move(expr); +} + +#define scanner (context->GetScanner()) + +%} + +%% +script: statements + { + llist->swap(*$1); + delete $1; + } + ; + +statements: optional_newlines lterm_items + { + $$ = $2; + } + ; + +lterm_items: /* empty */ + { + $$ = new std::vector<std::pair<std::unique_ptr<Expression>, EItemInfo> >(); + } + | lterm_items_inner + | lterm_items_inner sep + ; + +lterm_items_inner: lterm %dprec 2 + { + $$ = new std::vector<std::pair<std::unique_ptr<Expression>, EItemInfo> >(); + $$->emplace_back(std::unique_ptr<Expression>($1), EItemInfo{true, @1}); + } + | rterm_no_side_effect + { + $$ = new std::vector<std::pair<std::unique_ptr<Expression>, EItemInfo> >(); + $$->emplace_back(std::unique_ptr<Expression>($1), EItemInfo{false, @1}); + } + | lterm_items_inner sep lterm %dprec 1 + { + if ($1) + $$ = $1; + else + $$ = new std::vector<std::pair<std::unique_ptr<Expression>, EItemInfo> >(); + + if ($3) { + $$->emplace_back(std::unique_ptr<Expression>($3), EItemInfo{true, @3}); + } + } + | lterm_items_inner sep rterm_no_side_effect %dprec 1 + { + if ($1) + $$ = $1; + else + $$ = new std::vector<std::pair<std::unique_ptr<Expression>, EItemInfo> >(); + + if ($3) { + $$->emplace_back(std::unique_ptr<Expression>($3), EItemInfo{false, @3}); + } + } + ; + +identifier: T_IDENTIFIER + | T_STRING + ; + +object: + { + context->m_ObjectAssign.push(true); + context->m_SeenAssign.push(false); + context->m_SeenIgnore.push(false); + context->m_Assign.push(0); + context->m_Ignore.push(0); + } + object_declaration rterm optional_rterm use_specifier default_specifier ignore_specifier + { + BeginFlowControlBlock(context, FlowControlReturn, false); + } + rterm_scope_require_side_effect + { + EndFlowControlBlock(context); + + context->m_ObjectAssign.pop(); + + bool abstract = $2; + bool defaultTmpl = $6; + + if (!abstract && defaultTmpl) + BOOST_THROW_EXCEPTION(ScriptError("'default' keyword is invalid for object definitions", @6)); + + bool seen_assign = context->m_SeenAssign.top(); + context->m_SeenAssign.pop(); + + bool seen_ignore = context->m_SeenIgnore.top(); + context->m_SeenIgnore.pop(); + + std::unique_ptr<Expression> ignore{std::move(context->m_Ignore.top())}; + context->m_Ignore.pop(); + + std::unique_ptr<Expression> assign{std::move(context->m_Assign.top())}; + context->m_Assign.pop(); + + std::unique_ptr<Expression> filter; + + if (seen_assign) { + if (ignore) { + std::unique_ptr<Expression> rex{new LogicalNegateExpression(std::move(ignore), DebugInfoRange(@2, @5))}; + + filter.reset(new LogicalAndExpression(std::move(assign), std::move(rex), DebugInfoRange(@2, @5))); + } else + filter.swap(assign); + } else if (seen_ignore) { + BOOST_THROW_EXCEPTION(ScriptError("object rule 'ignore where' cannot be used without 'assign where'", DebugInfoRange(@2, @4))); + } + + $$ = new ObjectExpression(abstract, std::unique_ptr<Expression>($3), std::unique_ptr<Expression>($4), + std::move(filter), context->GetZone(), context->GetPackage(), std::move(*$5), $6, $7, + std::unique_ptr<Expression>($9), DebugInfoRange(@2, @7)); + delete $5; + } + ; + +object_declaration: T_OBJECT + { + $$ = false; + } + | T_TEMPLATE + { + $$ = true; + } + ; + +identifier_items: /* empty */ + { + $$ = new std::vector<String>(); + } + | identifier_items_inner + | identifier_items_inner ',' + ; + +identifier_items_inner: identifier + { + $$ = new std::vector<String>(); + $$->emplace_back(std::move(*$1)); + delete $1; + } + | identifier_items_inner ',' identifier + { + if ($1) + $$ = $1; + else + $$ = new std::vector<String>(); + + $$->emplace_back(std::move(*$3)); + delete $3; + } + ; + +combined_set_op: T_SET + | T_SET_ADD + | T_SET_SUBTRACT + | T_SET_MULTIPLY + | T_SET_DIVIDE + | T_SET_MODULO + | T_SET_XOR + | T_SET_BINARY_AND + | T_SET_BINARY_OR + ; + +optional_var: /* empty */ + | T_VAR + ; + +lterm: T_LIBRARY rterm + { + $$ = new LibraryExpression(std::unique_ptr<Expression>($2), @$); + } + | rterm combined_set_op rterm + { + $$ = new SetExpression(std::unique_ptr<Expression>($1), $2, std::unique_ptr<Expression>($3), @$); + } + | T_INCLUDE rterm + { + $$ = new IncludeExpression(Utility::DirName(context->GetPath()), std::unique_ptr<Expression>($2), NULL, NULL, IncludeRegular, false, context->GetZone(), context->GetPackage(), @$); + } + | T_INCLUDE T_STRING_ANGLE + { + $$ = new IncludeExpression(Utility::DirName(context->GetPath()), MakeLiteral(std::move(*$2)), NULL, NULL, IncludeRegular, true, context->GetZone(), context->GetPackage(), @$); + delete $2; + } + | T_INCLUDE_RECURSIVE rterm + { + $$ = new IncludeExpression(Utility::DirName(context->GetPath()), std::unique_ptr<Expression>($2), MakeLiteral("*.conf"), NULL, IncludeRecursive, false, context->GetZone(), context->GetPackage(), @$); + } + | T_INCLUDE_RECURSIVE rterm ',' rterm + { + $$ = new IncludeExpression(Utility::DirName(context->GetPath()), std::unique_ptr<Expression>($2), std::unique_ptr<Expression>($4), NULL, IncludeRecursive, false, context->GetZone(), context->GetPackage(), @$); + } + | T_INCLUDE_ZONES rterm ',' rterm + { + $$ = new IncludeExpression(Utility::DirName(context->GetPath()), std::unique_ptr<Expression>($4), MakeLiteral("*.conf"), std::unique_ptr<Expression>($2), IncludeZones, false, context->GetZone(), context->GetPackage(), @$); + } + | T_INCLUDE_ZONES rterm ',' rterm ',' rterm + { + $$ = new IncludeExpression(Utility::DirName(context->GetPath()), std::unique_ptr<Expression>($4), std::unique_ptr<Expression>($6), std::unique_ptr<Expression>($2), IncludeZones, false, context->GetZone(), context->GetPackage(), @$); + } + | T_IMPORT rterm + { + $$ = new ImportExpression(std::unique_ptr<Expression>($2), @$); + } + | T_ASSIGN T_WHERE + { + BeginFlowControlBlock(context, FlowControlReturn, false); + } + rterm_scope %dprec 2 + { + EndFlowControlBlock(context); + + if ((context->m_Apply.empty() || !context->m_Apply.top()) && (context->m_ObjectAssign.empty() || !context->m_ObjectAssign.top())) + BOOST_THROW_EXCEPTION(ScriptError("'assign' keyword not valid in this context.", @$)); + + context->m_SeenAssign.top() = true; + + if (context->m_Assign.top()) + context->m_Assign.top() = new LogicalOrExpression(std::unique_ptr<Expression>(context->m_Assign.top()), std::unique_ptr<Expression>($4), @$); + else + context->m_Assign.top() = $4; + + $$ = MakeLiteralRaw(); + } + | T_ASSIGN T_WHERE rterm %dprec 1 + { + ASSERT(!dynamic_cast<DictExpression *>($3)); + + if ((context->m_Apply.empty() || !context->m_Apply.top()) && (context->m_ObjectAssign.empty() || !context->m_ObjectAssign.top())) + BOOST_THROW_EXCEPTION(ScriptError("'assign' keyword not valid in this context.", @$)); + + context->m_SeenAssign.top() = true; + + if (context->m_Assign.top()) + context->m_Assign.top() = new LogicalOrExpression(std::unique_ptr<Expression>(context->m_Assign.top()), std::unique_ptr<Expression>($3), @$); + else + context->m_Assign.top() = $3; + + $$ = MakeLiteralRaw(); + } + | T_IGNORE T_WHERE + { + BeginFlowControlBlock(context, FlowControlReturn, false); + } + rterm_scope %dprec 2 + { + EndFlowControlBlock(context); + + if ((context->m_Apply.empty() || !context->m_Apply.top()) && (context->m_ObjectAssign.empty() || !context->m_ObjectAssign.top())) + BOOST_THROW_EXCEPTION(ScriptError("'ignore' keyword not valid in this context.", @$)); + + context->m_SeenIgnore.top() = true; + + if (context->m_Ignore.top()) + context->m_Ignore.top() = new LogicalOrExpression(std::unique_ptr<Expression>(context->m_Ignore.top()), std::unique_ptr<Expression>($4), @$); + else + context->m_Ignore.top() = $4; + + $$ = MakeLiteralRaw(); + } + | T_IGNORE T_WHERE rterm %dprec 1 + { + ASSERT(!dynamic_cast<DictExpression *>($3)); + + if ((context->m_Apply.empty() || !context->m_Apply.top()) && (context->m_ObjectAssign.empty() || !context->m_ObjectAssign.top())) + BOOST_THROW_EXCEPTION(ScriptError("'ignore' keyword not valid in this context.", @$)); + + context->m_SeenIgnore.top() = true; + + if (context->m_Ignore.top()) + context->m_Ignore.top() = new LogicalOrExpression(std::unique_ptr<Expression>(context->m_Ignore.top()), std::unique_ptr<Expression>($3), @$); + else + context->m_Ignore.top() = $3; + + $$ = MakeLiteralRaw(); + } + | T_RETURN optional_rterm + { + UseFlowControl(context, FlowControlReturn, @$); + $$ = new ReturnExpression(std::unique_ptr<Expression>($2), @$); + } + | T_BREAK + { + UseFlowControl(context, FlowControlBreak, @$); + $$ = new BreakExpression(@$); + } + | T_CONTINUE + { + UseFlowControl(context, FlowControlContinue, @$); + $$ = new ContinueExpression(@$); + } + | T_DEBUGGER + { + $$ = new BreakpointExpression(@$); + } + | T_NAMESPACE rterm + { + BeginFlowControlBlock(context, FlowControlReturn, false); + } + rterm_scope_require_side_effect + { + EndFlowControlBlock(context); + + std::unique_ptr<Expression> expr{$2}; + BindToScope(expr, ScopeGlobal); + $$ = new SetExpression(std::move(expr), OpSetLiteral, std::unique_ptr<Expression>(new NamespaceExpression(std::unique_ptr<Expression>($4), @$)), @$); + } + | T_USING rterm + { + Expression::Ptr expr{$2}; + context->AddImport(std::move(expr)); + $$ = MakeLiteralRaw(); + } + | apply + | object + | T_FOR '(' optional_var identifier T_FOLLOWS optional_var identifier T_IN rterm ')' + { + BeginFlowControlBlock(context, FlowControlContinue | FlowControlBreak, true); + } + rterm_scope_require_side_effect + { + EndFlowControlBlock(context); + + $$ = new ForExpression(std::move(*$4), std::move(*$7), std::unique_ptr<Expression>($9), std::unique_ptr<Expression>($12), @$); + delete $4; + delete $7; + } + | T_FOR '(' optional_var identifier T_IN rterm ')' + { + BeginFlowControlBlock(context, FlowControlContinue | FlowControlBreak, true); + } + rterm_scope_require_side_effect + { + EndFlowControlBlock(context); + + $$ = new ForExpression(std::move(*$4), "", std::unique_ptr<Expression>($6), std::unique_ptr<Expression>($9), @$); + delete $4; + } + | T_FUNCTION identifier '(' identifier_items ')' use_specifier + { + BeginFlowControlBlock(context, FlowControlReturn, false); + } + rterm_scope + { + EndFlowControlBlock(context); + + std::unique_ptr<FunctionExpression> fexpr{new FunctionExpression(*$2, std::move(*$4), std::move(*$6), std::unique_ptr<Expression>($8), @$)}; + delete $4; + delete $6; + + $$ = new SetExpression(MakeIndexer(ScopeThis, std::move(*$2)), OpSetLiteral, std::move(fexpr), @$); + delete $2; + } + | T_CONST T_IDENTIFIER T_SET rterm + { + $$ = new SetConstExpression(std::move(*$2), std::unique_ptr<Expression>($4), @$); + delete $2; + } + | T_VAR rterm + { + std::unique_ptr<Expression> expr{$2}; + BindToScope(expr, ScopeLocal); + $$ = new SetExpression(std::move(expr), OpSetLiteral, MakeLiteral(), @$); + } + | T_VAR rterm combined_set_op rterm + { + std::unique_ptr<Expression> expr{$2}; + BindToScope(expr, ScopeLocal); + $$ = new SetExpression(std::move(expr), $3, std::unique_ptr<Expression>($4), @$); + } + | T_WHILE '(' rterm ')' + { + BeginFlowControlBlock(context, FlowControlContinue | FlowControlBreak, true); + } + rterm_scope + { + EndFlowControlBlock(context); + + $$ = new WhileExpression(std::unique_ptr<Expression>($3), std::unique_ptr<Expression>($6), @$); + } + | T_THROW rterm + { + $$ = new ThrowExpression(std::unique_ptr<Expression>($2), false, @$); + } + | T_TRY rterm_scope T_EXCEPT rterm_scope + { + $$ = new TryExceptExpression(std::unique_ptr<Expression>($2), std::unique_ptr<Expression>($4), @$); + } + | rterm_side_effect + ; + +rterm_items: /* empty */ + { + $$ = new std::vector<std::unique_ptr<Expression> >(); + } + | rterm_items_inner + | rterm_items_inner ',' optional_newlines + | rterm_items_inner newlines + ; + +rterm_items_inner: rterm + { + $$ = new std::vector<std::unique_ptr<Expression> >(); + $$->emplace_back($1); + } + | rterm_items_inner ',' optional_newlines rterm + { + $$ = $1; + $$->emplace_back($4); + } + ; + +rterm_array: '[' + { + context->m_OpenBraces++; + } + optional_newlines rterm_items ']' + { + context->m_OpenBraces--; + $$ = new ArrayExpression(std::move(*$4), @$); + delete $4; + } + ; + +rterm_dict: '{' + { + BeginFlowControlBlock(context, 0, false); + context->m_IgnoreNewlines.push(false); + context->m_OpenBraces++; + } + statements '}' + { + EndFlowControlBlock(context); + context->m_OpenBraces--; + context->m_IgnoreNewlines.pop(); + std::vector<std::unique_ptr<Expression> > dlist; + for (auto& litem : *$3) { + if (!litem.second.SideEffect) + yyerror(&litem.second.DebugInfo, NULL, NULL, "Value computed is not used."); + dlist.emplace_back(std::move(litem.first)); + } + delete $3; + $$ = new DictExpression(std::move(dlist), @$); + } + ; + +rterm_scope_require_side_effect: '{' + { + context->m_IgnoreNewlines.push(false); + context->m_OpenBraces++; + } + statements '}' + { + context->m_OpenBraces--; + context->m_IgnoreNewlines.pop(); + std::vector<std::unique_ptr<Expression> > dlist; + for (auto& litem : *$3) { + if (!litem.second.SideEffect) + yyerror(&litem.second.DebugInfo, NULL, NULL, "Value computed is not used."); + dlist.emplace_back(std::move(litem.first)); + } + delete $3; + $$ = new DictExpression(std::move(dlist), @$); + $$->MakeInline(); + } + ; + +rterm_scope: '{' + { + context->m_IgnoreNewlines.push(false); + context->m_OpenBraces++; + } + statements '}' + { + context->m_OpenBraces--; + context->m_IgnoreNewlines.pop(); + std::vector<std::unique_ptr<Expression> > dlist; + decltype($3->size()) num = 0; + for (auto& litem : *$3) { + if (!litem.second.SideEffect && num != $3->size() - 1) + yyerror(&litem.second.DebugInfo, NULL, NULL, "Value computed is not used."); + dlist.emplace_back(std::move(litem.first)); + num++; + } + delete $3; + $$ = new DictExpression(std::move(dlist), @$); + $$->MakeInline(); + } + ; + +else_if_branch: T_ELSE T_IF '(' rterm ')' rterm_scope + { + $$ = new std::pair<std::unique_ptr<Expression>, std::unique_ptr<Expression> >(std::unique_ptr<Expression>($4), std::unique_ptr<Expression>($6)); + } + ; + +else_if_branches: /* empty */ + { + $$ = new std::vector<std::pair<std::unique_ptr<Expression>, std::unique_ptr<Expression> > >(); + } + | else_if_branches else_if_branch + { + $$ = $1; + $$->emplace_back(std::move(*$2)); + delete $2; + } + ; + +rterm_side_effect: rterm '(' rterm_items ')' + { + $$ = new FunctionCallExpression(std::unique_ptr<Expression>($1), std::move(*$3), @$); + delete $3; + } + | T_IF '(' rterm ')' rterm_scope else_if_branches + { + std::vector<std::pair<std::unique_ptr<Expression>, std::unique_ptr<Expression> > > ebranches; + $6->swap(ebranches); + delete $6; + + std::unique_ptr<Expression> afalse; + + for (int i = ebranches.size() - 1; i >= 0; i--) { + auto& ebranch = ebranches[i]; + afalse.reset(new ConditionalExpression(std::move(ebranch.first), std::move(ebranch.second), std::move(afalse), @6)); + } + + $$ = new ConditionalExpression(std::unique_ptr<Expression>($3), std::unique_ptr<Expression>($5), std::move(afalse), @$); + } + | T_IF '(' rterm ')' rterm_scope else_if_branches T_ELSE rterm_scope + { + std::vector<std::pair<std::unique_ptr<Expression>, std::unique_ptr<Expression> > > ebranches; + $6->swap(ebranches); + delete $6; + + $8->MakeInline(); + + std::unique_ptr<Expression> afalse{$8}; + + for (int i = ebranches.size() - 1; i >= 0; i--) { + auto& ebranch = ebranches[i]; + afalse.reset(new ConditionalExpression(std::move(ebranch.first), std::move(ebranch.second), std::move(afalse), @6)); + } + + $$ = new ConditionalExpression(std::unique_ptr<Expression>($3), std::unique_ptr<Expression>($5), std::move(afalse), @$); + } + | rterm '?' rterm ':' rterm + { + $$ = new ConditionalExpression(std::unique_ptr<Expression>($1), std::unique_ptr<Expression>($3), std::unique_ptr<Expression>($5), @$); + } + ; + +rterm_no_side_effect_no_dict: T_STRING + { + $$ = MakeLiteralRaw(std::move(*$1)); + delete $1; + } + | T_NUMBER + { + $$ = MakeLiteralRaw($1); + } + | T_BOOLEAN + { + $$ = MakeLiteralRaw($1); + } + | T_NULL + { + $$ = MakeLiteralRaw(); + } + | rterm '.' T_IDENTIFIER %dprec 2 + { + $$ = new IndexerExpression(std::unique_ptr<Expression>($1), MakeLiteral(std::move(*$3)), @$); + delete $3; + } + | rterm '[' rterm ']' + { + $$ = new IndexerExpression(std::unique_ptr<Expression>($1), std::unique_ptr<Expression>($3), @$); + } + | T_IDENTIFIER + { + $$ = new VariableExpression(std::move(*$1), context->GetImports(), @1); + delete $1; + } + | T_MULTIPLY rterm %prec DEREF_OP + { + $$ = new DerefExpression(std::unique_ptr<Expression>($2), @$); + } + | T_BINARY_AND rterm %prec REF_OP + { + $$ = new RefExpression(std::unique_ptr<Expression>($2), @$); + } + | '!' rterm + { + $$ = new LogicalNegateExpression(std::unique_ptr<Expression>($2), @$); + } + | '~' rterm + { + $$ = new NegateExpression(std::unique_ptr<Expression>($2), @$); + } + | T_PLUS rterm %prec UNARY_PLUS + { + $$ = $2; + } + | T_MINUS rterm %prec UNARY_MINUS + { + $$ = new SubtractExpression(MakeLiteral(0), std::unique_ptr<Expression>($2), @$); + } + | T_THIS + { + $$ = new GetScopeExpression(ScopeThis); + } + | T_GLOBALS + { + $$ = new GetScopeExpression(ScopeGlobal); + } + | T_LOCALS + { + $$ = new GetScopeExpression(ScopeLocal); + } + | T_CURRENT_FILENAME + { + $$ = MakeLiteralRaw(@$.Path); + } + | T_CURRENT_LINE + { + $$ = MakeLiteralRaw(@$.FirstLine); + } + | identifier T_FOLLOWS + { + BeginFlowControlBlock(context, FlowControlReturn, false); + } + rterm_scope %dprec 2 + { + EndFlowControlBlock(context); + + std::vector<String> args; + args.emplace_back(std::move(*$1)); + delete $1; + + $$ = new FunctionExpression("<anonymous>", std::move(args), {}, std::unique_ptr<Expression>($4), @$); + } + | identifier T_FOLLOWS rterm %dprec 1 + { + ASSERT(!dynamic_cast<DictExpression *>($3)); + + std::vector<String> args; + args.emplace_back(std::move(*$1)); + delete $1; + + $$ = new FunctionExpression("<anonymous>", std::move(args), {}, std::unique_ptr<Expression>($3), @$); + } + | '(' identifier_items ')' use_specifier T_FOLLOWS + { + BeginFlowControlBlock(context, FlowControlReturn, false); + } + rterm_scope %dprec 2 + { + EndFlowControlBlock(context); + + $$ = new FunctionExpression("<anonymous>", std::move(*$2), std::move(*$4), std::unique_ptr<Expression>($7), @$); + delete $2; + delete $4; + } + | '(' identifier_items ')' use_specifier T_FOLLOWS rterm %dprec 1 + { + ASSERT(!dynamic_cast<DictExpression *>($6)); + + $$ = new FunctionExpression("<anonymous>", std::move(*$2), std::move(*$4), std::unique_ptr<Expression>($6), @$); + delete $2; + delete $4; + } + | rterm_array + | '(' + { + context->m_OpenBraces++; + } + rterm ')' + { + context->m_OpenBraces--; + $$ = $3; + } + | rterm T_LOGICAL_OR rterm { MakeRBinaryOp<LogicalOrExpression>(&$$, $1, $3, @1, @3); } + | rterm T_LOGICAL_AND rterm { MakeRBinaryOp<LogicalAndExpression>(&$$, $1, $3, @1, @3); } + | rterm T_BINARY_OR rterm { MakeRBinaryOp<BinaryOrExpression>(&$$, $1, $3, @1, @3); } + | rterm T_BINARY_AND rterm { MakeRBinaryOp<BinaryAndExpression>(&$$, $1, $3, @1, @3); } + | rterm T_IN rterm { MakeRBinaryOp<InExpression>(&$$, $1, $3, @1, @3); } + | rterm T_NOT_IN rterm { MakeRBinaryOp<NotInExpression>(&$$, $1, $3, @1, @3); } + | rterm T_EQUAL rterm { MakeRBinaryOp<EqualExpression>(&$$, $1, $3, @1, @3); } + | rterm T_NOT_EQUAL rterm { MakeRBinaryOp<NotEqualExpression>(&$$, $1, $3, @1, @3); } + | rterm T_LESS_THAN rterm { MakeRBinaryOp<LessThanExpression>(&$$, $1, $3, @1, @3); } + | rterm T_LESS_THAN_OR_EQUAL rterm { MakeRBinaryOp<LessThanOrEqualExpression>(&$$, $1, $3, @1, @3); } + | rterm T_GREATER_THAN rterm { MakeRBinaryOp<GreaterThanExpression>(&$$, $1, $3, @1, @3); } + | rterm T_GREATER_THAN_OR_EQUAL rterm { MakeRBinaryOp<GreaterThanOrEqualExpression>(&$$, $1, $3, @1, @3); } + | rterm T_SHIFT_LEFT rterm { MakeRBinaryOp<ShiftLeftExpression>(&$$, $1, $3, @1, @3); } + | rterm T_SHIFT_RIGHT rterm { MakeRBinaryOp<ShiftRightExpression>(&$$, $1, $3, @1, @3); } + | rterm T_PLUS rterm { MakeRBinaryOp<AddExpression>(&$$, $1, $3, @1, @3); } + | rterm T_MINUS rterm { MakeRBinaryOp<SubtractExpression>(&$$, $1, $3, @1, @3); } + | rterm T_MULTIPLY rterm { MakeRBinaryOp<MultiplyExpression>(&$$, $1, $3, @1, @3); } + | rterm T_DIVIDE_OP rterm { MakeRBinaryOp<DivideExpression>(&$$, $1, $3, @1, @3); } + | rterm T_MODULO rterm { MakeRBinaryOp<ModuloExpression>(&$$, $1, $3, @1, @3); } + | rterm T_XOR rterm { MakeRBinaryOp<XorExpression>(&$$, $1, $3, @1, @3); } + | T_FUNCTION '(' identifier_items ')' use_specifier + { + BeginFlowControlBlock(context, FlowControlReturn, false); + } + rterm_scope + { + EndFlowControlBlock(context); + + $$ = new FunctionExpression("<anonymous>", std::move(*$3), std::move(*$5), std::unique_ptr<Expression>($7), @$); + delete $3; + delete $5; + } + | T_NULLARY_LAMBDA_BEGIN + { + BeginFlowControlBlock(context, FlowControlReturn, false); + } + statements T_NULLARY_LAMBDA_END + { + EndFlowControlBlock(context); + + std::vector<std::unique_ptr<Expression> > dlist; + decltype(dlist.size()) num = 0; + for (auto& litem : *$3) { + if (!litem.second.SideEffect && num != $3->size() - 1) + yyerror(&litem.second.DebugInfo, NULL, NULL, "Value computed is not used."); + dlist.emplace_back(std::move(litem.first)); + num++; + } + delete $3; + std::unique_ptr<DictExpression> aexpr{new DictExpression(std::move(dlist), @$)}; + aexpr->MakeInline(); + + $$ = new FunctionExpression("<anonymous>", {}, {}, std::move(aexpr), @$); + } + ; + +rterm_no_side_effect: + rterm_no_side_effect_no_dict %dprec 1 + | rterm_dict %dprec 2 + { + std::unique_ptr<Expression> expr{$1}; + BindToScope(expr, ScopeThis); + $$ = expr.release(); + } + ; + +rterm: + rterm_side_effect %dprec 2 + | rterm_no_side_effect %dprec 1 + ; + +target_type_specifier: /* empty */ + { + $$ = new String(); + } + | T_TO identifier + { + $$ = $2; + } + ; + +default_specifier: /* empty */ + { + $$ = false; + } + | T_DEFAULT + { + $$ = true; + } + ; + +ignore_specifier: /* empty */ + { + $$ = false; + } + | T_IGNORE_ON_ERROR + { + $$ = true; + } + ; + +use_specifier: /* empty */ + { + $$ = new std::map<String, std::unique_ptr<Expression> >(); + } + | T_USE '(' use_specifier_items ')' + { + $$ = $3; + } + ; + +use_specifier_items: use_specifier_item + { + $$ = new std::map<String, std::unique_ptr<Expression> >(); + $$->emplace(std::move(*$1)); + delete $1; + } + | use_specifier_items ',' use_specifier_item + { + $$ = $1; + $$->emplace(std::move(*$3)); + delete $3; + } + ; + +use_specifier_item: identifier + { + std::unique_ptr<Expression> var (new VariableExpression(*$1, context->GetImports(), @1)); + $$ = new std::pair<String, std::unique_ptr<Expression> >(std::move(*$1), std::move(var)); + delete $1; + } + | identifier T_SET rterm + { + $$ = new std::pair<String, std::unique_ptr<Expression> >(std::move(*$1), std::unique_ptr<Expression>($3)); + delete $1; + } + ; + +apply_for_specifier: /* empty */ + | T_FOR '(' optional_var identifier T_FOLLOWS optional_var identifier T_IN rterm ')' + { + context->m_FKVar.top() = std::move(*$4); + delete $4; + + context->m_FVVar.top() = std::move(*$7); + delete $7; + + context->m_FTerm.top() = $9; + } + | T_FOR '(' optional_var identifier T_IN rterm ')' + { + context->m_FKVar.top() = std::move(*$4); + delete $4; + + context->m_FVVar.top() = ""; + + context->m_FTerm.top() = $6; + } + ; + +optional_rterm: /* empty */ + { + $$ = MakeLiteralRaw(); + } + | rterm + ; + +apply: + { + context->m_Apply.push(true); + context->m_SeenAssign.push(false); + context->m_SeenIgnore.push(false); + context->m_Assign.push(NULL); + context->m_Ignore.push(NULL); + context->m_FKVar.push(""); + context->m_FVVar.push(""); + context->m_FTerm.push(NULL); + } + T_APPLY identifier optional_rterm apply_for_specifier target_type_specifier use_specifier ignore_specifier + { + BeginFlowControlBlock(context, FlowControlReturn, false); + } + rterm_scope_require_side_effect + { + EndFlowControlBlock(context); + + context->m_Apply.pop(); + + String type = std::move(*$3); + delete $3; + String target = std::move(*$6); + delete $6; + + if (!ApplyRule::IsValidSourceType(type)) + BOOST_THROW_EXCEPTION(ScriptError("'apply' cannot be used with type '" + type + "'", @3)); + + if (!ApplyRule::IsValidTargetType(type, target)) { + if (target == "") { + auto& types (ApplyRule::GetTargetTypes(type)); + String typeNames; + + for (std::vector<String>::size_type i = 0; i < types.size(); i++) { + if (typeNames != "") { + if (i == types.size() - 1) + typeNames += " or "; + else + typeNames += ", "; + } + + typeNames += "'" + types[i] + "'"; + } + + BOOST_THROW_EXCEPTION(ScriptError("'apply' target type is ambiguous (can be one of " + typeNames + "): use 'to' to specify a type", DebugInfoRange(@2, @3))); + } else + BOOST_THROW_EXCEPTION(ScriptError("'apply' target type '" + target + "' is invalid", @6)); + } + + bool seen_assign = context->m_SeenAssign.top(); + context->m_SeenAssign.pop(); + + // assign && !ignore + if (!seen_assign && !context->m_FTerm.top()) + BOOST_THROW_EXCEPTION(ScriptError("'apply' is missing 'assign'/'for'", DebugInfoRange(@2, @3))); + + std::unique_ptr<Expression> ignore{context->m_Ignore.top()}; + context->m_Ignore.pop(); + + std::unique_ptr<Expression> assign; + + if (!seen_assign) + assign = MakeLiteral(true); + else + assign.reset(context->m_Assign.top()); + + context->m_Assign.pop(); + + std::unique_ptr<Expression> filter; + + if (ignore) { + std::unique_ptr<Expression>rex{new LogicalNegateExpression(std::move(ignore), DebugInfoRange(@2, @5))}; + + filter.reset(new LogicalAndExpression(std::move(assign), std::move(rex), DebugInfoRange(@2, @5))); + } else + filter.swap(assign); + + String fkvar = std::move(context->m_FKVar.top()); + context->m_FKVar.pop(); + + String fvvar = std::move(context->m_FVVar.top()); + context->m_FVVar.pop(); + + std::unique_ptr<Expression> fterm{context->m_FTerm.top()}; + context->m_FTerm.pop(); + + $$ = new ApplyExpression(std::move(type), std::move(target), std::unique_ptr<Expression>($4), std::move(filter), context->GetPackage(), std::move(fkvar), std::move(fvvar), std::move(fterm), std::move(*$7), $8, std::unique_ptr<Expression>($10), DebugInfoRange(@2, @8)); + delete $7; + } + ; + +newlines: T_NEWLINE + | T_NEWLINE newlines + ; + +optional_newlines: /* empty */ + | newlines + ; + +/* required separator */ +sep: ',' optional_newlines + | ';' optional_newlines + | newlines + ; + +%% diff --git a/lib/config/configcompiler.cpp b/lib/config/configcompiler.cpp new file mode 100644 index 0000000..62f02ba --- /dev/null +++ b/lib/config/configcompiler.cpp @@ -0,0 +1,364 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "config/configcompiler.hpp" +#include "config/configitem.hpp" +#include "base/logger.hpp" +#include "base/utility.hpp" +#include "base/loader.hpp" +#include "base/context.hpp" +#include "base/exception.hpp" +#include <fstream> + +using namespace icinga; + +std::vector<String> ConfigCompiler::m_IncludeSearchDirs; +std::mutex ConfigCompiler::m_ZoneDirsMutex; +std::map<String, std::vector<ZoneFragment> > ConfigCompiler::m_ZoneDirs; + +/** + * Constructor for the ConfigCompiler class. + * + * @param path The path of the configuration file (or another name that + * identifies the source of the configuration text). + * @param input Input stream for the configuration file. + * @param zone The zone. + */ +ConfigCompiler::ConfigCompiler(String path, std::istream *input, + String zone, String package) + : m_Path(std::move(path)), m_Input(input), m_Zone(std::move(zone)), + m_Package(std::move(package)), m_Eof(false), m_OpenBraces(0) +{ + InitializeScanner(); +} + +/** + * Destructor for the ConfigCompiler class. + */ +ConfigCompiler::~ConfigCompiler() +{ + DestroyScanner(); +} + +/** + * Reads data from the input stream. Used internally by the lexer. + * + * @param buffer Where to store data. + * @param max_size The maximum number of bytes to read from the stream. + * @returns The actual number of bytes read. + */ +size_t ConfigCompiler::ReadInput(char *buffer, size_t max_size) +{ + m_Input->read(buffer, max_size); + return static_cast<size_t>(m_Input->gcount()); +} + +/** + * Retrieves the scanner object. + * + * @returns The scanner object. + */ +void *ConfigCompiler::GetScanner() const +{ + return m_Scanner; +} + +/** + * Retrieves the path for the input file. + * + * @returns The path. + */ +const char *ConfigCompiler::GetPath() const +{ + return m_Path.CStr(); +} + +void ConfigCompiler::SetZone(const String& zone) +{ + m_Zone = zone; +} + +String ConfigCompiler::GetZone() const +{ + return m_Zone; +} + +void ConfigCompiler::SetPackage(const String& package) +{ + m_Package = package; +} + +String ConfigCompiler::GetPackage() const +{ + return m_Package; +} + +void ConfigCompiler::CollectIncludes(std::vector<std::unique_ptr<Expression> >& expressions, + const String& file, const String& zone, const String& package) +{ + try { + expressions.emplace_back(CompileFile(file, zone, package)); + } catch (const std::exception& ex) { + Log(LogWarning, "ConfigCompiler") + << "Cannot compile file '" + << file << "': " << DiagnosticInformation(ex); + } +} + +/** + * Handles an include directive. + * + * @param relativeBath The path this include is relative to. + * @param path The path from the include directive. + * @param search Whether to search global include dirs. + * @param debuginfo Debug information. + */ +std::unique_ptr<Expression> ConfigCompiler::HandleInclude(const String& relativeBase, const String& path, + bool search, const String& zone, const String& package, const DebugInfo& debuginfo) +{ + String upath; + + if (search || (IsAbsolutePath(path))) + upath = path; + else + upath = relativeBase + "/" + path; + + String includePath = upath; + + if (search) { + for (const String& dir : m_IncludeSearchDirs) { + String spath = dir + "/" + path; + + if (Utility::PathExists(spath)) { + includePath = spath; + break; + } + } + } + + std::vector<std::unique_ptr<Expression> > expressions; + auto funcCallback = [&expressions, zone, package](const String& file) { CollectIncludes(expressions, file, zone, package); }; + + if (!Utility::Glob(includePath, funcCallback, GlobFile) && includePath.FindFirstOf("*?") == String::NPos) { + std::ostringstream msgbuf; + msgbuf << "Include file '" + path + "' does not exist"; + BOOST_THROW_EXCEPTION(ScriptError(msgbuf.str(), debuginfo)); + } + + std::unique_ptr<DictExpression> expr{new DictExpression(std::move(expressions))}; + expr->MakeInline(); + return std::move(expr); +} + +/** + * Handles recursive includes. + * + * @param relativeBase The path this include is relative to. + * @param path The directory path. + * @param pattern The file pattern. + * @param debuginfo Debug information. + */ +std::unique_ptr<Expression> ConfigCompiler::HandleIncludeRecursive(const String& relativeBase, const String& path, + const String& pattern, const String& zone, const String& package, const DebugInfo&) +{ + String ppath; + + if (IsAbsolutePath(path)) + ppath = path; + else + ppath = relativeBase + "/" + path; + + std::vector<std::unique_ptr<Expression> > expressions; + Utility::GlobRecursive(ppath, pattern, [&expressions, zone, package](const String& file) { + CollectIncludes(expressions, file, zone, package); + }, GlobFile); + + std::unique_ptr<DictExpression> dict{new DictExpression(std::move(expressions))}; + dict->MakeInline(); + return std::move(dict); +} + +void ConfigCompiler::HandleIncludeZone(const String& relativeBase, const String& tag, const String& path, const String& pattern, const String& package, std::vector<std::unique_ptr<Expression> >& expressions) +{ + String zoneName = Utility::BaseName(path); + + String ppath; + + if (IsAbsolutePath(path)) + ppath = path; + else + ppath = relativeBase + "/" + path; + + RegisterZoneDir(tag, ppath, zoneName); + + Utility::GlobRecursive(ppath, pattern, [&expressions, zoneName, package](const String& file) { + CollectIncludes(expressions, file, zoneName, package); + }, GlobFile); +} + +/** + * Handles zone includes. + * + * @param relativeBase The path this include is relative to. + * @param tag The tag name. + * @param path The directory path. + * @param pattern The file pattern. + * @param debuginfo Debug information. + */ +std::unique_ptr<Expression> ConfigCompiler::HandleIncludeZones(const String& relativeBase, const String& tag, + const String& path, const String& pattern, const String& package, const DebugInfo&) +{ + String ppath; + String newRelativeBase = relativeBase; + + if (IsAbsolutePath(path)) + ppath = path; + else { + ppath = relativeBase + "/" + path; + newRelativeBase = "."; + } + + std::vector<std::unique_ptr<Expression> > expressions; + Utility::Glob(ppath + "/*", [newRelativeBase, tag, pattern, package, &expressions](const String& path) { + HandleIncludeZone(newRelativeBase, tag, path, pattern, package, expressions); + }, GlobDirectory); + + return std::unique_ptr<Expression>(new DictExpression(std::move(expressions))); +} + +/** + * Compiles a stream. + * + * @param path A name identifying the stream. + * @param stream The input stream. + * @returns Configuration items. + */ +std::unique_ptr<Expression> ConfigCompiler::CompileStream(const String& path, + std::istream *stream, const String& zone, const String& package) +{ + CONTEXT("Compiling configuration stream with name '" << path << "'"); + + stream->exceptions(std::istream::badbit); + + ConfigCompiler ctx(path, stream, zone, package); + + try { + return ctx.Compile(); + } catch (const ScriptError& ex) { + return std::unique_ptr<Expression>(new ThrowExpression(MakeLiteral(ex.what()), ex.IsIncompleteExpression(), ex.GetDebugInfo())); + } catch (const std::exception& ex) { + return std::unique_ptr<Expression>(new ThrowExpression(MakeLiteral(DiagnosticInformation(ex)), false)); + } +} + +/** + * Compiles a file. + * + * @param path The path. + * @returns Configuration items. + */ +std::unique_ptr<Expression> ConfigCompiler::CompileFile(const String& path, const String& zone, + const String& package) +{ + CONTEXT("Compiling configuration file '" << path << "'"); + + std::ifstream stream(path.CStr(), std::ifstream::in); + + if (!stream) + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("std::ifstream::open") + << boost::errinfo_errno(errno) + << boost::errinfo_file_name(path)); + + Log(LogNotice, "ConfigCompiler") + << "Compiling config file: " << path; + + return CompileStream(path, &stream, zone, package); +} + +/** + * Compiles a snippet of text. + * + * @param path A name identifying the text. + * @param text The text. + * @returns Configuration items. + */ +std::unique_ptr<Expression> ConfigCompiler::CompileText(const String& path, const String& text, + const String& zone, const String& package) +{ + std::stringstream stream(text); + return CompileStream(path, &stream, zone, package); +} + +/** + * Adds a directory to the list of include search dirs. + * + * @param dir The new dir. + */ +void ConfigCompiler::AddIncludeSearchDir(const String& dir) +{ + Log(LogInformation, "ConfigCompiler") + << "Adding include search dir: " << dir; + + m_IncludeSearchDirs.push_back(dir); +} + +std::vector<ZoneFragment> ConfigCompiler::GetZoneDirs(const String& zone) +{ + std::unique_lock<std::mutex> lock(m_ZoneDirsMutex); + auto it = m_ZoneDirs.find(zone); + if (it == m_ZoneDirs.end()) + return std::vector<ZoneFragment>(); + else + return it->second; +} + +void ConfigCompiler::RegisterZoneDir(const String& tag, const String& ppath, const String& zoneName) +{ + ZoneFragment zf; + zf.Tag = tag; + zf.Path = ppath; + + std::unique_lock<std::mutex> lock(m_ZoneDirsMutex); + m_ZoneDirs[zoneName].push_back(zf); +} + +bool ConfigCompiler::HasZoneConfigAuthority(const String& zoneName) +{ + std::vector<ZoneFragment> zoneDirs = m_ZoneDirs[zoneName]; + + bool empty = zoneDirs.empty(); + + if (!empty) { + std::vector<String> paths; + paths.reserve(zoneDirs.size()); + + for (const ZoneFragment& zf : zoneDirs) { + paths.push_back(zf.Path); + } + + Log(LogNotice, "ConfigCompiler") + << "Registered authoritative config directories for zone '" << zoneName << "': " << Utility::NaturalJoin(paths); + } + + return !empty; +} + + +bool ConfigCompiler::IsAbsolutePath(const String& path) +{ +#ifndef _WIN32 + return (path.GetLength() > 0 && path[0] == '/'); +#else /* _WIN32 */ + return !PathIsRelative(path.CStr()); +#endif /* _WIN32 */ +} + +void ConfigCompiler::AddImport(const Expression::Ptr& import) +{ + m_Imports.push_back(import); +} + +std::vector<Expression::Ptr> ConfigCompiler::GetImports() const +{ + return m_Imports; +} diff --git a/lib/config/configcompiler.hpp b/lib/config/configcompiler.hpp new file mode 100644 index 0000000..fe00bed --- /dev/null +++ b/lib/config/configcompiler.hpp @@ -0,0 +1,161 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef CONFIGCOMPILER_H +#define CONFIGCOMPILER_H + +#include "config/i2-config.hpp" +#include "config/expression.hpp" +#include "base/debuginfo.hpp" +#include "base/registry.hpp" +#include "base/initialize.hpp" +#include "base/singleton.hpp" +#include "base/string.hpp" +#include <future> +#include <iostream> +#include <stack> + +typedef union YYSTYPE YYSTYPE; +typedef void *yyscan_t; + +namespace icinga +{ + +struct CompilerDebugInfo +{ + const char *Path; + + int FirstLine; + int FirstColumn; + + int LastLine; + int LastColumn; + + operator DebugInfo() const + { + DebugInfo di; + di.Path = Path; + di.FirstLine = FirstLine; + di.FirstColumn = FirstColumn; + di.LastLine = LastLine; + di.LastColumn = LastColumn; + return di; + } +}; + +struct EItemInfo +{ + bool SideEffect; + CompilerDebugInfo DebugInfo; +}; + +enum FlowControlType +{ + FlowControlReturn = 1, + FlowControlContinue = 2, + FlowControlBreak = 4 +}; + +struct ZoneFragment +{ + String Tag; + String Path; +}; + +/** + * The configuration compiler can be used to compile a configuration file + * into a number of configuration items. + * + * @ingroup config + */ +class ConfigCompiler +{ +public: + explicit ConfigCompiler(String path, std::istream *input, + String zone = String(), String package = String()); + virtual ~ConfigCompiler(); + + std::unique_ptr<Expression> Compile(); + + static std::unique_ptr<Expression>CompileStream(const String& path, std::istream *stream, + const String& zone = String(), const String& package = String()); + static std::unique_ptr<Expression>CompileFile(const String& path, const String& zone = String(), + const String& package = String()); + static std::unique_ptr<Expression>CompileText(const String& path, const String& text, + const String& zone = String(), const String& package = String()); + + static void AddIncludeSearchDir(const String& dir); + + const char *GetPath() const; + + void SetZone(const String& zone); + String GetZone() const; + + void SetPackage(const String& package); + String GetPackage() const; + + void AddImport(const Expression::Ptr& import); + std::vector<Expression::Ptr> GetImports() const; + + static void CollectIncludes(std::vector<std::unique_ptr<Expression> >& expressions, + const String& file, const String& zone, const String& package); + + static std::unique_ptr<Expression> HandleInclude(const String& relativeBase, const String& path, bool search, + const String& zone, const String& package, const DebugInfo& debuginfo = DebugInfo()); + static std::unique_ptr<Expression> HandleIncludeRecursive(const String& relativeBase, const String& path, + const String& pattern, const String& zone, const String& package, const DebugInfo& debuginfo = DebugInfo()); + static std::unique_ptr<Expression> HandleIncludeZones(const String& relativeBase, const String& tag, + const String& path, const String& pattern, const String& package, const DebugInfo& debuginfo = DebugInfo()); + + size_t ReadInput(char *buffer, size_t max_bytes); + void *GetScanner() const; + + static std::vector<ZoneFragment> GetZoneDirs(const String& zone); + static void RegisterZoneDir(const String& tag, const String& ppath, const String& zoneName); + + static bool HasZoneConfigAuthority(const String& zoneName); + +private: + std::promise<Expression::Ptr> m_Promise; + + String m_Path; + std::istream *m_Input; + String m_Zone; + String m_Package; + std::vector<Expression::Ptr> m_Imports; + + void *m_Scanner; + + static std::vector<String> m_IncludeSearchDirs; + static std::mutex m_ZoneDirsMutex; + static std::map<String, std::vector<ZoneFragment> > m_ZoneDirs; + + void InitializeScanner(); + void DestroyScanner(); + + static void HandleIncludeZone(const String& relativeBase, const String& tag, const String& path, const String& pattern, const String& package, std::vector<std::unique_ptr<Expression> >& expressions); + + static bool IsAbsolutePath(const String& path); + +public: + bool m_Eof; + int m_OpenBraces; + + String m_LexBuffer; + CompilerDebugInfo m_LocationBegin; + + std::stack<bool> m_IgnoreNewlines; + std::stack<bool> m_Apply; + std::stack<bool> m_ObjectAssign; + std::stack<bool> m_SeenAssign; + std::stack<bool> m_SeenIgnore; + std::stack<Expression *> m_Assign; + std::stack<Expression *> m_Ignore; + std::stack<String> m_FKVar; + std::stack<String> m_FVVar; + std::stack<Expression *> m_FTerm; + std::stack<int> m_FlowControlInfo; +}; + +} + +#endif /* CONFIGCOMPILER_H */ diff --git a/lib/config/configcompilercontext.cpp b/lib/config/configcompilercontext.cpp new file mode 100644 index 0000000..0161181 --- /dev/null +++ b/lib/config/configcompilercontext.cpp @@ -0,0 +1,57 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "config/configcompilercontext.hpp" +#include "base/singleton.hpp" +#include "base/json.hpp" +#include "base/netstring.hpp" +#include "base/exception.hpp" +#include "base/application.hpp" +#include "base/utility.hpp" + +using namespace icinga; + +ConfigCompilerContext *ConfigCompilerContext::GetInstance() +{ + return Singleton<ConfigCompilerContext>::GetInstance(); +} + +void ConfigCompilerContext::OpenObjectsFile(const String& filename) +{ + try { + m_ObjectsFP = std::make_unique<AtomicFile>(filename, 0600); + } catch (const std::exception& ex) { + Log(LogCritical, "cli", "Could not create temporary objects file: " + DiagnosticInformation(ex, false)); + Application::Exit(1); + } +} + +void ConfigCompilerContext::WriteObject(const Dictionary::Ptr& object) +{ + if (!m_ObjectsFP) + return; + + String json = JsonEncode(object); + + { + std::unique_lock<std::mutex> lock(m_Mutex); + NetString::WriteStringToStream(*m_ObjectsFP, json); + } +} + +void ConfigCompilerContext::CancelObjectsFile() +{ + if (!m_ObjectsFP) + return; + + m_ObjectsFP.reset(nullptr); +} + +void ConfigCompilerContext::FinishObjectsFile() +{ + if (!m_ObjectsFP) + return; + + m_ObjectsFP->Commit(); + m_ObjectsFP.reset(nullptr); +} + diff --git a/lib/config/configcompilercontext.hpp b/lib/config/configcompilercontext.hpp new file mode 100644 index 0000000..c3d5317 --- /dev/null +++ b/lib/config/configcompilercontext.hpp @@ -0,0 +1,42 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef CONFIGCOMPILERCONTEXT_H +#define CONFIGCOMPILERCONTEXT_H + +#include "config/i2-config.hpp" +#include "base/atomic-file.hpp" +#include "base/dictionary.hpp" +#include <fstream> +#include <memory> +#include <mutex> + +namespace icinga +{ + +/* + * @ingroup config + */ +class ConfigCompilerContext +{ +public: + void OpenObjectsFile(const String& filename); + void WriteObject(const Dictionary::Ptr& object); + void CancelObjectsFile(); + void FinishObjectsFile(); + + inline bool IsOpen() const noexcept + { + return (bool)m_ObjectsFP; + } + + static ConfigCompilerContext *GetInstance(); + +private: + std::unique_ptr<AtomicFile> m_ObjectsFP; + + mutable std::mutex m_Mutex; +}; + +} + +#endif /* CONFIGCOMPILERCONTEXT_H */ diff --git a/lib/config/configfragment.hpp b/lib/config/configfragment.hpp new file mode 100644 index 0000000..883aef8 --- /dev/null +++ b/lib/config/configfragment.hpp @@ -0,0 +1,26 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef CONFIGFRAGMENT_H +#define CONFIGFRAGMENT_H + +#include "config/configcompiler.hpp" +#include "base/initialize.hpp" +#include "base/debug.hpp" +#include "base/exception.hpp" +#include "base/application.hpp" + +/* Ensure that the priority is lower than the basic namespace initialization in scriptframe.cpp. */ +#define REGISTER_CONFIG_FRAGMENT(name, fragment) \ + INITIALIZE_ONCE_WITH_PRIORITY([]() { \ + std::unique_ptr<icinga::Expression> expression = icinga::ConfigCompiler::CompileText(name, fragment); \ + VERIFY(expression); \ + try { \ + icinga::ScriptFrame frame(true); \ + expression->Evaluate(frame); \ + } catch (const std::exception& ex) { \ + std::cerr << icinga::DiagnosticInformation(ex) << std::endl; \ + icinga::Application::Exit(1); \ + } \ + }, icinga::InitializePriority::EvaluateConfigFragments) + +#endif /* CONFIGFRAGMENT_H */ diff --git a/lib/config/configitem.cpp b/lib/config/configitem.cpp new file mode 100644 index 0000000..9dc0f1a --- /dev/null +++ b/lib/config/configitem.cpp @@ -0,0 +1,849 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "config/configitem.hpp" +#include "config/configcompilercontext.hpp" +#include "config/applyrule.hpp" +#include "config/objectrule.hpp" +#include "config/configcompiler.hpp" +#include "base/application.hpp" +#include "base/configtype.hpp" +#include "base/objectlock.hpp" +#include "base/convert.hpp" +#include "base/logger.hpp" +#include "base/debug.hpp" +#include "base/workqueue.hpp" +#include "base/exception.hpp" +#include "base/stdiostream.hpp" +#include "base/netstring.hpp" +#include "base/serializer.hpp" +#include "base/json.hpp" +#include "base/exception.hpp" +#include "base/function.hpp" +#include "base/utility.hpp" +#include <boost/algorithm/string/join.hpp> +#include <atomic> +#include <sstream> +#include <fstream> +#include <algorithm> +#include <random> +#include <unordered_map> + +using namespace icinga; + +std::mutex ConfigItem::m_Mutex; +ConfigItem::TypeMap ConfigItem::m_Items; +ConfigItem::TypeMap ConfigItem::m_DefaultTemplates; +ConfigItem::ItemList ConfigItem::m_UnnamedItems; +ConfigItem::IgnoredItemList ConfigItem::m_IgnoredItems; + +REGISTER_FUNCTION(Internal, run_with_activation_context, &ConfigItem::RunWithActivationContext, "func"); + +/** + * Constructor for the ConfigItem class. + * + * @param type The object type. + * @param name The name of the item. + * @param unit The unit of the item. + * @param abstract Whether the item is a template. + * @param exprl Expression list for the item. + * @param debuginfo Debug information. + */ +ConfigItem::ConfigItem(Type::Ptr type, String name, + bool abstract, Expression::Ptr exprl, + Expression::Ptr filter, bool defaultTmpl, bool ignoreOnError, + DebugInfo debuginfo, Dictionary::Ptr scope, + String zone, String package) + : m_Type(std::move(type)), m_Name(std::move(name)), m_Abstract(abstract), + m_Expression(std::move(exprl)), m_Filter(std::move(filter)), + m_DefaultTmpl(defaultTmpl), m_IgnoreOnError(ignoreOnError), + m_DebugInfo(std::move(debuginfo)), m_Scope(std::move(scope)), m_Zone(std::move(zone)), + m_Package(std::move(package)) +{ +} + +/** + * Retrieves the type of the configuration item. + * + * @returns The type. + */ +Type::Ptr ConfigItem::GetType() const +{ + return m_Type; +} + +/** + * Retrieves the name of the configuration item. + * + * @returns The name. + */ +String ConfigItem::GetName() const +{ + return m_Name; +} + +/** + * Checks whether the item is abstract. + * + * @returns true if the item is abstract, false otherwise. + */ +bool ConfigItem::IsAbstract() const +{ + return m_Abstract; +} + +bool ConfigItem::IsDefaultTemplate() const +{ + return m_DefaultTmpl; +} + +bool ConfigItem::IsIgnoreOnError() const +{ + return m_IgnoreOnError; +} + +/** + * Retrieves the debug information for the configuration item. + * + * @returns The debug information. + */ +DebugInfo ConfigItem::GetDebugInfo() const +{ + return m_DebugInfo; +} + +Dictionary::Ptr ConfigItem::GetScope() const +{ + return m_Scope; +} + +ConfigObject::Ptr ConfigItem::GetObject() const +{ + return m_Object; +} + +/** + * Retrieves the expression list for the configuration item. + * + * @returns The expression list. + */ +Expression::Ptr ConfigItem::GetExpression() const +{ + return m_Expression; +} + +/** +* Retrieves the object filter for the configuration item. +* +* @returns The filter expression. +*/ +Expression::Ptr ConfigItem::GetFilter() const +{ + return m_Filter; +} + +class DefaultValidationUtils final : public ValidationUtils +{ +public: + bool ValidateName(const String& type, const String& name) const override + { + ConfigItem::Ptr item = ConfigItem::GetByTypeAndName(Type::GetByName(type), name); + + if (!item || item->IsAbstract()) + return false; + + return true; + } +}; + +/** + * Commits the configuration item by creating a ConfigObject + * object. + * + * @returns The ConfigObject that was created/updated. + */ +ConfigObject::Ptr ConfigItem::Commit(bool discard) +{ + Type::Ptr type = GetType(); + +#ifdef I2_DEBUG + Log(LogDebug, "ConfigItem") + << "Commit called for ConfigItem Type=" << type->GetName() << ", Name=" << GetName(); +#endif /* I2_DEBUG */ + + /* Make sure the type is valid. */ + if (!type || !ConfigObject::TypeInstance->IsAssignableFrom(type)) + BOOST_THROW_EXCEPTION(ScriptError("Type '" + type->GetName() + "' does not exist.", m_DebugInfo)); + + if (IsAbstract()) + return nullptr; + + ConfigObject::Ptr dobj = static_pointer_cast<ConfigObject>(type->Instantiate(std::vector<Value>())); + + dobj->SetDebugInfo(m_DebugInfo); + dobj->SetZoneName(m_Zone); + dobj->SetPackage(m_Package); + dobj->SetName(m_Name); + + DebugHint debugHints; + + ScriptFrame frame(true, dobj); + if (m_Scope) + m_Scope->CopyTo(frame.Locals); + try { + m_Expression->Evaluate(frame, &debugHints); + } catch (const std::exception& ex) { + if (m_IgnoreOnError) { + Log(LogNotice, "ConfigObject") + << "Ignoring config object '" << m_Name << "' of type '" << type->GetName() << "' due to errors: " << DiagnosticInformation(ex); + + { + std::unique_lock<std::mutex> lock(m_Mutex); + m_IgnoredItems.push_back(m_DebugInfo.Path); + } + + return nullptr; + } + + throw; + } + + if (discard) + m_Expression.reset(); + + String item_name; + String short_name = dobj->GetShortName(); + + if (!short_name.IsEmpty()) { + item_name = short_name; + dobj->SetName(short_name); + } else + item_name = m_Name; + + String name = item_name; + + auto *nc = dynamic_cast<NameComposer *>(type.get()); + + if (nc) { + if (name.IsEmpty()) + BOOST_THROW_EXCEPTION(ScriptError("Object name must not be empty.", m_DebugInfo)); + + name = nc->MakeName(name, dobj); + + if (name.IsEmpty()) + BOOST_THROW_EXCEPTION(std::runtime_error("Could not determine name for object")); + } + + if (name != item_name) + dobj->SetShortName(item_name); + + dobj->SetName(name); + + Dictionary::Ptr dhint = debugHints.ToDictionary(); + + try { + DefaultValidationUtils utils; + dobj->Validate(FAConfig, utils); + } catch (ValidationError& ex) { + if (m_IgnoreOnError) { + Log(LogNotice, "ConfigObject") + << "Ignoring config object '" << m_Name << "' of type '" << type->GetName() << "' due to errors: " << DiagnosticInformation(ex); + + { + std::unique_lock<std::mutex> lock(m_Mutex); + m_IgnoredItems.push_back(m_DebugInfo.Path); + } + + return nullptr; + } + + ex.SetDebugHint(dhint); + throw; + } + + try { + dobj->OnConfigLoaded(); + } catch (const std::exception& ex) { + if (m_IgnoreOnError) { + Log(LogNotice, "ConfigObject") + << "Ignoring config object '" << m_Name << "' of type '" << m_Type->GetName() << "' due to errors: " << DiagnosticInformation(ex); + + { + std::unique_lock<std::mutex> lock(m_Mutex); + m_IgnoredItems.push_back(m_DebugInfo.Path); + } + + return nullptr; + } + + throw; + } + + Value serializedObject; + + try { + if (ConfigCompilerContext::GetInstance()->IsOpen()) { + serializedObject = Serialize(dobj, FAConfig); + } else { + AssertNoCircularReferences(dobj); + } + } catch (const CircularReferenceError& ex) { + BOOST_THROW_EXCEPTION(ValidationError(dobj, ex.GetPath(), "Circular references are not allowed")); + } + + if (ConfigCompilerContext::GetInstance()->IsOpen()) { + Dictionary::Ptr persistentItem = new Dictionary({ + { "type", type->GetName() }, + { "name", GetName() }, + { "properties", serializedObject }, + { "debug_hints", dhint }, + { "debug_info", new Array({ + m_DebugInfo.Path, + m_DebugInfo.FirstLine, + m_DebugInfo.FirstColumn, + m_DebugInfo.LastLine, + m_DebugInfo.LastColumn, + }) } + }); + + ConfigCompilerContext::GetInstance()->WriteObject(persistentItem); + } + + dhint.reset(); + + dobj->Register(); + + m_Object = dobj; + + return dobj; +} + +/** + * Registers the configuration item. + */ +void ConfigItem::Register() +{ + m_ActivationContext = ActivationContext::GetCurrentContext(); + + std::unique_lock<std::mutex> lock(m_Mutex); + + /* If this is a non-abstract object with a composite name + * we register it in m_UnnamedItems instead of m_Items. */ + if (!m_Abstract && dynamic_cast<NameComposer *>(m_Type.get())) + m_UnnamedItems.emplace_back(this); + else { + auto& items = m_Items[m_Type]; + + auto it = items.find(m_Name); + + if (it != items.end()) { + std::ostringstream msgbuf; + msgbuf << "A configuration item of type '" << m_Type->GetName() + << "' and name '" << GetName() << "' already exists (" + << it->second->GetDebugInfo() << "), new declaration: " << GetDebugInfo(); + BOOST_THROW_EXCEPTION(ScriptError(msgbuf.str())); + } + + m_Items[m_Type][m_Name] = this; + + if (m_DefaultTmpl) + m_DefaultTemplates[m_Type][m_Name] = this; + } +} + +/** + * Unregisters the configuration item. + */ +void ConfigItem::Unregister() +{ + if (m_Object) { + m_Object->Unregister(); + m_Object.reset(); + } + + std::unique_lock<std::mutex> lock(m_Mutex); + m_UnnamedItems.erase(std::remove(m_UnnamedItems.begin(), m_UnnamedItems.end(), this), m_UnnamedItems.end()); + m_Items[m_Type].erase(m_Name); + m_DefaultTemplates[m_Type].erase(m_Name); +} + +/** + * Retrieves a configuration item by type and name. + * + * @param type The type of the ConfigItem that is to be looked up. + * @param name The name of the ConfigItem that is to be looked up. + * @returns The configuration item. + */ +ConfigItem::Ptr ConfigItem::GetByTypeAndName(const Type::Ptr& type, const String& name) +{ + std::unique_lock<std::mutex> lock(m_Mutex); + + auto it = m_Items.find(type); + + if (it == m_Items.end()) + return nullptr; + + auto it2 = it->second.find(name); + + if (it2 == it->second.end()) + return nullptr; + + return it2->second; +} + +bool ConfigItem::CommitNewItems(const ActivationContext::Ptr& context, WorkQueue& upq, std::vector<ConfigItem::Ptr>& newItems) +{ + typedef std::pair<ConfigItem::Ptr, bool> ItemPair; + std::unordered_map<Type*, std::vector<ItemPair>> itemsByType; + std::vector<ItemPair>::size_type total = 0; + + { + std::unique_lock<std::mutex> lock(m_Mutex); + + for (const TypeMap::value_type& kv : m_Items) { + std::vector<ItemPair> items; + + for (const ItemMap::value_type& kv2 : kv.second) { + if (kv2.second->m_Abstract || kv2.second->m_Object) + continue; + + if (kv2.second->m_ActivationContext != context) + continue; + + items.emplace_back(kv2.second, false); + } + + if (!items.empty()) { + total += items.size(); + itemsByType.emplace(kv.first.get(), std::move(items)); + } + } + + ItemList newUnnamedItems; + + for (const ConfigItem::Ptr& item : m_UnnamedItems) { + if (item->m_ActivationContext != context) { + newUnnamedItems.push_back(item); + continue; + } + + if (item->m_Abstract || item->m_Object) + continue; + + itemsByType[item->m_Type.get()].emplace_back(item, true); + ++total; + } + + m_UnnamedItems.swap(newUnnamedItems); + } + + if (!total) + return true; + + // Shuffle all items to evenly distribute them over the threads of the workqueue. This increases perfomance + // noticably in environments with lots of objects and available threads. + for (auto& kv : itemsByType) { + std::shuffle(std::begin(kv.second), std::end(kv.second), std::default_random_engine{}); + } + +#ifdef I2_DEBUG + Log(LogDebug, "configitem") + << "Committing " << total << " new items."; +#endif /* I2_DEBUG */ + + std::set<Type::Ptr> types; + std::set<Type::Ptr> completed_types; + int itemsCount {0}; + + for (const Type::Ptr& type : Type::GetAllTypes()) { + if (ConfigObject::TypeInstance->IsAssignableFrom(type)) + types.insert(type); + } + + while (types.size() != completed_types.size()) { + for (const Type::Ptr& type : types) { + if (completed_types.find(type) != completed_types.end()) + continue; + + bool unresolved_dep = false; + + /* skip this type (for now) if there are unresolved load dependencies */ + for (auto pLoadDep : type->GetLoadDependencies()) { + if (types.find(pLoadDep) != types.end() && completed_types.find(pLoadDep) == completed_types.end()) { + unresolved_dep = true; + break; + } + } + + if (unresolved_dep) + continue; + + std::atomic<int> committed_items(0); + std::mutex newItemsMutex; + + { + auto items (itemsByType.find(type.get())); + + if (items != itemsByType.end()) { + upq.ParallelFor(items->second, [&committed_items, &newItems, &newItemsMutex](const ItemPair& ip) { + const ConfigItem::Ptr& item = ip.first; + + if (!item->Commit(ip.second)) { + if (item->IsIgnoreOnError()) { + item->Unregister(); + } + + return; + } + + committed_items++; + + std::unique_lock<std::mutex> lock(newItemsMutex); + newItems.emplace_back(item); + }); + + upq.Join(); + } + } + + itemsCount += committed_items; + + completed_types.insert(type); + +#ifdef I2_DEBUG + if (committed_items > 0) + Log(LogDebug, "configitem") + << "Committed " << committed_items << " items of type '" << type->GetName() << "'."; +#endif /* I2_DEBUG */ + + if (upq.HasExceptions()) + return false; + } + } + +#ifdef I2_DEBUG + Log(LogDebug, "configitem") + << "Committed " << itemsCount << " items."; +#endif /* I2_DEBUG */ + + completed_types.clear(); + + while (types.size() != completed_types.size()) { + for (const Type::Ptr& type : types) { + if (completed_types.find(type) != completed_types.end()) + continue; + + bool unresolved_dep = false; + + /* skip this type (for now) if there are unresolved load dependencies */ + for (auto pLoadDep : type->GetLoadDependencies()) { + if (types.find(pLoadDep) != types.end() && completed_types.find(pLoadDep) == completed_types.end()) { + unresolved_dep = true; + break; + } + } + + if (unresolved_dep) + continue; + + std::atomic<int> notified_items(0); + + { + auto items (itemsByType.find(type.get())); + + if (items != itemsByType.end()) { + upq.ParallelFor(items->second, [¬ified_items](const ItemPair& ip) { + const ConfigItem::Ptr& item = ip.first; + + if (!item->m_Object) + return; + + try { + item->m_Object->OnAllConfigLoaded(); + notified_items++; + } catch (const std::exception& ex) { + if (!item->m_IgnoreOnError) + throw; + + Log(LogNotice, "ConfigObject") + << "Ignoring config object '" << item->m_Name << "' of type '" << item->m_Type->GetName() << "' due to errors: " << DiagnosticInformation(ex); + + item->Unregister(); + + { + std::unique_lock<std::mutex> lock(item->m_Mutex); + item->m_IgnoredItems.push_back(item->m_DebugInfo.Path); + } + } + }); + + upq.Join(); + } + } + + completed_types.insert(type); + +#ifdef I2_DEBUG + if (notified_items > 0) + Log(LogDebug, "configitem") + << "Sent OnAllConfigLoaded to " << notified_items << " items of type '" << type->GetName() << "'."; +#endif /* I2_DEBUG */ + + if (upq.HasExceptions()) + return false; + + notified_items = 0; + for (auto loadDep : type->GetLoadDependencies()) { + auto items (itemsByType.find(loadDep)); + + if (items != itemsByType.end()) { + upq.ParallelFor(items->second, [&type, ¬ified_items](const ItemPair& ip) { + const ConfigItem::Ptr& item = ip.first; + + if (!item->m_Object) + return; + + ActivationScope ascope(item->m_ActivationContext); + item->m_Object->CreateChildObjects(type); + notified_items++; + }); + } + } + + upq.Join(); + +#ifdef I2_DEBUG + if (notified_items > 0) + Log(LogDebug, "configitem") + << "Sent CreateChildObjects to " << notified_items << " items of type '" << type->GetName() << "'."; +#endif /* I2_DEBUG */ + + if (upq.HasExceptions()) + return false; + + // Make sure to activate any additionally generated items + if (!CommitNewItems(context, upq, newItems)) + return false; + } + } + + return true; +} + +bool ConfigItem::CommitItems(const ActivationContext::Ptr& context, WorkQueue& upq, std::vector<ConfigItem::Ptr>& newItems, bool silent) +{ + if (!silent) + Log(LogInformation, "ConfigItem", "Committing config item(s)."); + + if (!CommitNewItems(context, upq, newItems)) { + upq.ReportExceptions("config"); + + for (const ConfigItem::Ptr& item : newItems) { + item->Unregister(); + } + + return false; + } + + ApplyRule::CheckMatches(silent); + + if (!silent) { + /* log stats for external parsers */ + typedef std::map<Type::Ptr, int> ItemCountMap; + ItemCountMap itemCounts; + for (const ConfigItem::Ptr& item : newItems) { + if (!item->m_Object) + continue; + + itemCounts[item->m_Object->GetReflectionType()]++; + } + + for (const ItemCountMap::value_type& kv : itemCounts) { + Log(LogInformation, "ConfigItem") + << "Instantiated " << kv.second << " " << (kv.second != 1 ? kv.first->GetPluralName() : kv.first->GetName()) << "."; + } + } + + return true; +} + +/** + * ActivateItems activates new config items. + * + * @param newItems Vector of items to be activated + * @param runtimeCreated Whether the objects were created by a runtime object + * @param mainConfigActivation Whether this is the call for activating the main configuration during startup + * @param withModAttrs Whether this call shall read the modified attributes file + * @param cookie Cookie for preventing message loops + * @return Whether the config activation was successful (in case of errors, exceptions are thrown) + */ +bool ConfigItem::ActivateItems(const std::vector<ConfigItem::Ptr>& newItems, bool runtimeCreated, + bool mainConfigActivation, bool withModAttrs, const Value& cookie) +{ + static std::mutex mtx; + std::unique_lock<std::mutex> lock(mtx); + + if (withModAttrs) { + /* restore modified attributes */ + if (Utility::PathExists(Configuration::ModAttrPath)) { + std::unique_ptr<Expression> expression = ConfigCompiler::CompileFile(Configuration::ModAttrPath); + + if (expression) { + try { + ScriptFrame frame(true); + expression->Evaluate(frame); + } catch (const std::exception& ex) { + Log(LogCritical, "config", DiagnosticInformation(ex)); + } + } + } + } + + for (const ConfigItem::Ptr& item : newItems) { + if (!item->m_Object) + continue; + + ConfigObject::Ptr object = item->m_Object; + + if (object->IsActive()) + continue; + +#ifdef I2_DEBUG + Log(LogDebug, "ConfigItem") + << "Setting 'active' to true for object '" << object->GetName() << "' of type '" << object->GetReflectionType()->GetName() << "'"; +#endif /* I2_DEBUG */ + + object->PreActivate(); + } + + if (mainConfigActivation) + Log(LogInformation, "ConfigItem", "Triggering Start signal for config items"); + + /* Activate objects in priority order. */ + std::vector<Type::Ptr> types = Type::GetAllTypes(); + + std::sort(types.begin(), types.end(), [](const Type::Ptr& a, const Type::Ptr& b) { + if (a->GetActivationPriority() < b->GetActivationPriority()) + return true; + return false; + }); + + /* Find the last logger type to be activated. */ + Type::Ptr lastLoggerType = nullptr; + for (const Type::Ptr& type : types) { + if (Logger::TypeInstance->IsAssignableFrom(type)) { + lastLoggerType = type; + } + } + + for (const Type::Ptr& type : types) { + for (const ConfigItem::Ptr& item : newItems) { + if (!item->m_Object) + continue; + + ConfigObject::Ptr object = item->m_Object; + Type::Ptr objectType = object->GetReflectionType(); + + if (objectType != type) + continue; + +#ifdef I2_DEBUG + Log(LogDebug, "ConfigItem") + << "Activating object '" << object->GetName() << "' of type '" + << objectType->GetName() << "' with priority " + << objectType->GetActivationPriority(); +#endif /* I2_DEBUG */ + + object->Activate(runtimeCreated, cookie); + } + + if (mainConfigActivation && type == lastLoggerType) { + /* Disable early logging configuration once the last logger type was activated. */ + Logger::DisableEarlyLogging(); + } + } + + if (mainConfigActivation) + Log(LogInformation, "ConfigItem", "Activated all objects."); + + return true; +} + +bool ConfigItem::RunWithActivationContext(const Function::Ptr& function) +{ + ActivationScope scope; + + if (!function) + BOOST_THROW_EXCEPTION(ScriptError("'function' argument must not be null.")); + + function->Invoke(); + + WorkQueue upq(25000, Configuration::Concurrency); + upq.SetName("ConfigItem::RunWithActivationContext"); + + std::vector<ConfigItem::Ptr> newItems; + + if (!CommitItems(scope.GetContext(), upq, newItems, true)) + return false; + + if (!ActivateItems(newItems, false, false)) + return false; + + return true; +} + +std::vector<ConfigItem::Ptr> ConfigItem::GetItems(const Type::Ptr& type) +{ + std::vector<ConfigItem::Ptr> items; + + std::unique_lock<std::mutex> lock(m_Mutex); + + auto it = m_Items.find(type); + + if (it == m_Items.end()) + return items; + + items.reserve(it->second.size()); + + for (const ItemMap::value_type& kv : it->second) { + items.push_back(kv.second); + } + + return items; +} + +std::vector<ConfigItem::Ptr> ConfigItem::GetDefaultTemplates(const Type::Ptr& type) +{ + std::vector<ConfigItem::Ptr> items; + + std::unique_lock<std::mutex> lock(m_Mutex); + + auto it = m_DefaultTemplates.find(type); + + if (it == m_DefaultTemplates.end()) + return items; + + items.reserve(it->second.size()); + + for (const ItemMap::value_type& kv : it->second) { + items.push_back(kv.second); + } + + return items; +} + +void ConfigItem::RemoveIgnoredItems(const String& allowedConfigPath) +{ + std::unique_lock<std::mutex> lock(m_Mutex); + + for (const String& path : m_IgnoredItems) { + if (path.Find(allowedConfigPath) == String::NPos) + continue; + + Log(LogNotice, "ConfigItem") + << "Removing ignored item path '" << path << "'."; + + (void) unlink(path.CStr()); + } + + m_IgnoredItems.clear(); +} diff --git a/lib/config/configitem.hpp b/lib/config/configitem.hpp new file mode 100644 index 0000000..b99cd08 --- /dev/null +++ b/lib/config/configitem.hpp @@ -0,0 +1,106 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef CONFIGITEM_H +#define CONFIGITEM_H + +#include "config/i2-config.hpp" +#include "config/expression.hpp" +#include "config/activationcontext.hpp" +#include "base/configobject.hpp" +#include "base/workqueue.hpp" + +namespace icinga +{ + + +/** + * A configuration item. Non-abstract configuration items can be used to + * create configuration objects at runtime. + * + * @ingroup config + */ +class ConfigItem final : public Object { +public: + DECLARE_PTR_TYPEDEFS(ConfigItem); + + ConfigItem(Type::Ptr type, String name, bool abstract, + Expression::Ptr exprl, + Expression::Ptr filter, + bool defaultTmpl, bool ignoreOnError, DebugInfo debuginfo, + Dictionary::Ptr scope, String zone, + String package); + + Type::Ptr GetType() const; + String GetName() const; + bool IsAbstract() const; + bool IsDefaultTemplate() const; + bool IsIgnoreOnError() const; + + std::vector<ConfigItem::Ptr> GetParents() const; + + Expression::Ptr GetExpression() const; + Expression::Ptr GetFilter() const; + + void Register(); + void Unregister(); + + DebugInfo GetDebugInfo() const; + Dictionary::Ptr GetScope() const; + + ConfigObject::Ptr GetObject() const; + + static ConfigItem::Ptr GetByTypeAndName(const Type::Ptr& type, + const String& name); + + static bool CommitItems(const ActivationContext::Ptr& context, WorkQueue& upq, std::vector<ConfigItem::Ptr>& newItems, bool silent = false); + static bool ActivateItems(const std::vector<ConfigItem::Ptr>& newItems, bool runtimeCreated = false, + bool mainConfigActivation = false, bool withModAttrs = false, const Value& cookie = Empty); + + static bool RunWithActivationContext(const Function::Ptr& function); + + static std::vector<ConfigItem::Ptr> GetItems(const Type::Ptr& type); + static std::vector<ConfigItem::Ptr> GetDefaultTemplates(const Type::Ptr& type); + + static void RemoveIgnoredItems(const String& allowedConfigPath); + +private: + Type::Ptr m_Type; /**< The object type. */ + String m_Name; /**< The name. */ + bool m_Abstract; /**< Whether this is a template. */ + + Expression::Ptr m_Expression; + Expression::Ptr m_Filter; + bool m_DefaultTmpl; + bool m_IgnoreOnError; + DebugInfo m_DebugInfo; /**< Debug information. */ + Dictionary::Ptr m_Scope; /**< variable scope. */ + String m_Zone; /**< The zone. */ + String m_Package; + ActivationContext::Ptr m_ActivationContext; + + ConfigObject::Ptr m_Object; + + static std::mutex m_Mutex; + + typedef std::map<String, ConfigItem::Ptr> ItemMap; + typedef std::map<Type::Ptr, ItemMap> TypeMap; + static TypeMap m_Items; /**< All registered configuration items. */ + static TypeMap m_DefaultTemplates; + + typedef std::vector<ConfigItem::Ptr> ItemList; + static ItemList m_UnnamedItems; + + typedef std::vector<String> IgnoredItemList; + static IgnoredItemList m_IgnoredItems; + + static ConfigItem::Ptr GetObjectUnlocked(const String& type, + const String& name); + + ConfigObject::Ptr Commit(bool discard = true); + + static bool CommitNewItems(const ActivationContext::Ptr& context, WorkQueue& upq, std::vector<ConfigItem::Ptr>& newItems); +}; + +} + +#endif /* CONFIGITEM_H */ diff --git a/lib/config/configitembuilder.cpp b/lib/config/configitembuilder.cpp new file mode 100644 index 0000000..f7a3ead --- /dev/null +++ b/lib/config/configitembuilder.cpp @@ -0,0 +1,120 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "config/configitembuilder.hpp" +#include "base/configtype.hpp" +#include <sstream> + +using namespace icinga; + +ConfigItemBuilder::ConfigItemBuilder(const DebugInfo& debugInfo) + : m_Abstract(false), m_DefaultTmpl(false), m_IgnoreOnError(false) +{ + m_DebugInfo = debugInfo; +} + +void ConfigItemBuilder::SetType(const Type::Ptr& type) +{ + ASSERT(type); + m_Type = type; +} + +void ConfigItemBuilder::SetName(const String& name) +{ + m_Name = name; +} + +void ConfigItemBuilder::SetAbstract(bool abstract) +{ + m_Abstract = abstract; +} + +void ConfigItemBuilder::SetScope(const Dictionary::Ptr& scope) +{ + m_Scope = scope; +} + +void ConfigItemBuilder::SetZone(const String& zone) +{ + m_Zone = zone; +} + +void ConfigItemBuilder::SetPackage(const String& package) +{ + m_Package = package; +} + +void ConfigItemBuilder::AddExpression(Expression *expr) +{ + m_Expressions.emplace_back(expr); +} + +void ConfigItemBuilder::SetFilter(const Expression::Ptr& filter) +{ + m_Filter = filter; +} + +void ConfigItemBuilder::SetDefaultTemplate(bool defaultTmpl) +{ + m_DefaultTmpl = defaultTmpl; +} + +void ConfigItemBuilder::SetIgnoreOnError(bool ignoreOnError) +{ + m_IgnoreOnError = ignoreOnError; +} + +ConfigItem::Ptr ConfigItemBuilder::Compile() +{ + if (!m_Type) { + std::ostringstream msgbuf; + msgbuf << "The type of an object must be specified"; + BOOST_THROW_EXCEPTION(ScriptError(msgbuf.str(), m_DebugInfo)); + } + + auto *ctype = dynamic_cast<ConfigType *>(m_Type.get()); + + if (!ctype) { + std::ostringstream msgbuf; + msgbuf << "The type '" + m_Type->GetName() + "' cannot be used for config objects"; + BOOST_THROW_EXCEPTION(ScriptError(msgbuf.str(), m_DebugInfo)); + } + + if (m_Name.FindFirstOf("!") != String::NPos) { + std::ostringstream msgbuf; + msgbuf << "Name for object '" << m_Name << "' of type '" << m_Type->GetName() << "' is invalid: Object names may not contain '!'"; + BOOST_THROW_EXCEPTION(ScriptError(msgbuf.str(), m_DebugInfo)); + } + + std::vector<std::unique_ptr<Expression> > exprs; + + Array::Ptr templateArray = new Array({ m_Name }); + + exprs.emplace_back(new SetExpression(MakeIndexer(ScopeThis, "templates"), OpSetAdd, + std::unique_ptr<LiteralExpression>(new LiteralExpression(templateArray)), m_DebugInfo)); + +#ifdef I2_DEBUG + if (!m_Abstract) { + bool foundDefaultImport = false; + + for (const std::unique_ptr<Expression>& expr : m_Expressions) { + if (dynamic_cast<ImportDefaultTemplatesExpression *>(expr.get())) { + foundDefaultImport = true; + break; + } + } + + ASSERT(foundDefaultImport); + } +#endif /* I2_DEBUG */ + + auto *dexpr = new DictExpression(std::move(m_Expressions), m_DebugInfo); + dexpr->MakeInline(); + exprs.emplace_back(dexpr); + + auto exprl = new DictExpression(std::move(exprs), m_DebugInfo); + exprl->MakeInline(); + + return new ConfigItem(m_Type, m_Name, m_Abstract, exprl, m_Filter, + m_DefaultTmpl, m_IgnoreOnError, m_DebugInfo, m_Scope, m_Zone, m_Package); +} + diff --git a/lib/config/configitembuilder.hpp b/lib/config/configitembuilder.hpp new file mode 100644 index 0000000..9d2e339 --- /dev/null +++ b/lib/config/configitembuilder.hpp @@ -0,0 +1,58 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef CONFIGITEMBUILDER_H +#define CONFIGITEMBUILDER_H + +#include "config/expression.hpp" +#include "config/configitem.hpp" +#include "base/debuginfo.hpp" +#include "base/object.hpp" + +namespace icinga +{ + +/** + * Config item builder. Used to dynamically build configuration objects + * at runtime. + * + * @ingroup config + */ +class ConfigItemBuilder final +{ +public: + DECLARE_PTR_TYPEDEFS(ConfigItemBuilder); + + ConfigItemBuilder() = default; + explicit ConfigItemBuilder(const DebugInfo& debugInfo); + + void SetType(const Type::Ptr& type); + void SetName(const String& name); + void SetAbstract(bool abstract); + void SetScope(const Dictionary::Ptr& scope); + void SetZone(const String& zone); + void SetPackage(const String& package); + void SetDefaultTemplate(bool defaultTmpl); + void SetIgnoreOnError(bool ignoreOnError); + + void AddExpression(Expression *expr); + void SetFilter(const Expression::Ptr& filter); + + ConfigItem::Ptr Compile(); + +private: + Type::Ptr m_Type; /**< The object type. */ + String m_Name; /**< The name. */ + bool m_Abstract{false}; /**< Whether the item is abstract. */ + std::vector<std::unique_ptr<Expression> > m_Expressions; /**< Expressions for this item. */ + Expression::Ptr m_Filter; /**< Filter expression. */ + DebugInfo m_DebugInfo; /**< Debug information. */ + Dictionary::Ptr m_Scope; /**< variable scope. */ + String m_Zone; /**< The zone. */ + String m_Package; /**< The package name. */ + bool m_DefaultTmpl{false}; + bool m_IgnoreOnError{false}; /**< Whether the object should be ignored when an error occurs in one of the expressions. */ +}; + +} + +#endif /* CONFIGITEMBUILDER */ diff --git a/lib/config/expression.cpp b/lib/config/expression.cpp new file mode 100644 index 0000000..a8e9986 --- /dev/null +++ b/lib/config/expression.cpp @@ -0,0 +1,1068 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "config/expression.hpp" +#include "config/configitem.hpp" +#include "config/configcompiler.hpp" +#include "config/vmops.hpp" +#include "base/array.hpp" +#include "base/json.hpp" +#include "base/object.hpp" +#include "base/logger.hpp" +#include "base/exception.hpp" +#include "base/scriptglobal.hpp" +#include "base/loader.hpp" +#include "base/reference.hpp" +#include "base/namespace.hpp" +#include "base/defer.hpp" +#include <boost/exception_ptr.hpp> +#include <boost/exception/errinfo_nested_exception.hpp> + +using namespace icinga; + +boost::signals2::signal<void (ScriptFrame&, ScriptError *ex, const DebugInfo&)> Expression::OnBreakpoint; +boost::thread_specific_ptr<bool> l_InBreakpointHandler; + +Expression::~Expression() +{ } + +void Expression::ScriptBreakpoint(ScriptFrame& frame, ScriptError *ex, const DebugInfo& di) +{ + bool *inHandler = l_InBreakpointHandler.get(); + if (!inHandler || !*inHandler) { + inHandler = new bool(true); + l_InBreakpointHandler.reset(inHandler); + OnBreakpoint(frame, ex, di); + *inHandler = false; + } +} + +ExpressionResult Expression::Evaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + try { +#ifdef I2_DEBUG +/* std::ostringstream msgbuf; + ShowCodeLocation(msgbuf, GetDebugInfo(), false); + Log(LogDebug, "Expression") + << "Executing:\n" << msgbuf.str();*/ +#endif /* I2_DEBUG */ + + frame.IncreaseStackDepth(); + + Defer decreaseStackDepth([&frame]{ + frame.DecreaseStackDepth(); + }); + + ExpressionResult result = DoEvaluate(frame, dhint); + return result; + } catch (ScriptError& ex) { + ScriptBreakpoint(frame, &ex, GetDebugInfo()); + throw; + } catch (const std::exception& ex) { + BOOST_THROW_EXCEPTION(ScriptError("Error while evaluating expression: " + String(ex.what()), GetDebugInfo()) + << boost::errinfo_nested_exception(boost::current_exception())); + } +} + +bool Expression::GetReference(ScriptFrame& frame, bool init_dict, Value *parent, String *index, DebugHint **dhint) const +{ + return false; +} + +const DebugInfo& Expression::GetDebugInfo() const +{ + static DebugInfo debugInfo; + return debugInfo; +} + +std::unique_ptr<Expression> icinga::MakeIndexer(ScopeSpecifier scopeSpec, const String& index) +{ + std::unique_ptr<Expression> scope{new GetScopeExpression(scopeSpec)}; + return std::unique_ptr<Expression>(new IndexerExpression(std::move(scope), MakeLiteral(index))); +} + +void DictExpression::MakeInline() +{ + m_Inline = true; +} + +LiteralExpression::LiteralExpression(Value value) + : m_Value(std::move(value)) +{ } + +ExpressionResult LiteralExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + return m_Value; +} + +const DebugInfo& DebuggableExpression::GetDebugInfo() const +{ + return m_DebugInfo; +} + +VariableExpression::VariableExpression(String variable, std::vector<Expression::Ptr> imports, const DebugInfo& debugInfo) + : DebuggableExpression(debugInfo), m_Variable(std::move(variable)), m_Imports(std::move(imports)) +{ + m_Imports.push_back(MakeIndexer(ScopeGlobal, "System").release()); + m_Imports.push_back(new IndexerExpression(MakeIndexer(ScopeGlobal, "System"), MakeLiteral("Configuration"))); + m_Imports.push_back(MakeIndexer(ScopeGlobal, "Types").release()); + m_Imports.push_back(MakeIndexer(ScopeGlobal, "Icinga").release()); +} + +ExpressionResult VariableExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + Value value; + + if (frame.Locals && frame.Locals->Get(m_Variable, &value)) + return value; + else if (frame.Self.IsObject() && frame.Locals != frame.Self.Get<Object::Ptr>() && frame.Self.Get<Object::Ptr>()->GetOwnField(m_Variable, &value)) + return value; + else if (VMOps::FindVarImport(frame, m_Imports, m_Variable, &value, m_DebugInfo)) + return value; + else + return ScriptGlobal::Get(m_Variable); +} + +bool VariableExpression::GetReference(ScriptFrame& frame, bool init_dict, Value *parent, String *index, DebugHint **dhint) const +{ + *index = m_Variable; + + if (frame.Locals && frame.Locals->Contains(m_Variable)) { + *parent = frame.Locals; + + if (dhint) + *dhint = nullptr; + } else if (frame.Self.IsObject() && frame.Locals != frame.Self.Get<Object::Ptr>() && frame.Self.Get<Object::Ptr>()->HasOwnField(m_Variable)) { + *parent = frame.Self; + + if (dhint && *dhint) + *dhint = new DebugHint((*dhint)->GetChild(m_Variable)); + } else if (VMOps::FindVarImportRef(frame, m_Imports, m_Variable, parent, m_DebugInfo)) { + return true; + } else if (ScriptGlobal::Exists(m_Variable)) { + *parent = ScriptGlobal::GetGlobals(); + + if (dhint) + *dhint = nullptr; + } else + *parent = frame.Self; + + return true; +} + +ExpressionResult RefExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + Value parent; + String index; + + if (!m_Operand->GetReference(frame, false, &parent, &index, &dhint)) + BOOST_THROW_EXCEPTION(ScriptError("Cannot obtain reference for expression.", m_DebugInfo)); + + if (!parent.IsObject()) + BOOST_THROW_EXCEPTION(ScriptError("Cannot obtain reference for expression because parent is not an object.", m_DebugInfo)); + + return new Reference(parent, index); +} + +ExpressionResult DerefExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + ExpressionResult operand = m_Operand->Evaluate(frame); + CHECK_RESULT(operand); + + Object::Ptr obj = operand.GetValue(); + Reference::Ptr ref = dynamic_pointer_cast<Reference>(obj); + + if (!ref) + BOOST_THROW_EXCEPTION(ScriptError("Invalid reference specified.", GetDebugInfo())); + + return ref->Get(); +} + +bool DerefExpression::GetReference(ScriptFrame& frame, bool init_dict, Value *parent, String *index, DebugHint **dhint) const +{ + ExpressionResult operand = m_Operand->Evaluate(frame); + if (operand.GetCode() != ResultOK) + return false; + + Reference::Ptr ref = operand.GetValue(); + + *parent = ref->GetParent(); + *index = ref->GetIndex(); + return true; +} + +ExpressionResult NegateExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + ExpressionResult operand = m_Operand->Evaluate(frame); + CHECK_RESULT(operand); + + return ~(long)operand.GetValue(); +} + +ExpressionResult LogicalNegateExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + ExpressionResult operand = m_Operand->Evaluate(frame); + CHECK_RESULT(operand); + + return !operand.GetValue().ToBool(); +} + +ExpressionResult AddExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + ExpressionResult operand1 = m_Operand1->Evaluate(frame); + CHECK_RESULT(operand1); + + ExpressionResult operand2 = m_Operand2->Evaluate(frame); + CHECK_RESULT(operand2); + + return operand1.GetValue() + operand2.GetValue(); +} + +ExpressionResult SubtractExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + ExpressionResult operand1 = m_Operand1->Evaluate(frame); + CHECK_RESULT(operand1); + + ExpressionResult operand2 = m_Operand2->Evaluate(frame); + CHECK_RESULT(operand2); + + return operand1.GetValue() - operand2.GetValue(); +} + +ExpressionResult MultiplyExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + ExpressionResult operand1 = m_Operand1->Evaluate(frame); + CHECK_RESULT(operand1); + + ExpressionResult operand2 = m_Operand2->Evaluate(frame); + CHECK_RESULT(operand2); + + return operand1.GetValue() * operand2.GetValue(); +} + +ExpressionResult DivideExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + ExpressionResult operand1 = m_Operand1->Evaluate(frame); + CHECK_RESULT(operand1); + + ExpressionResult operand2 = m_Operand2->Evaluate(frame); + CHECK_RESULT(operand2); + + return operand1.GetValue() / operand2.GetValue(); +} + +ExpressionResult ModuloExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + ExpressionResult operand1 = m_Operand1->Evaluate(frame); + CHECK_RESULT(operand1); + + ExpressionResult operand2 = m_Operand2->Evaluate(frame); + CHECK_RESULT(operand2); + + return operand1.GetValue() % operand2.GetValue(); +} + +ExpressionResult XorExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + ExpressionResult operand1 = m_Operand1->Evaluate(frame); + CHECK_RESULT(operand1); + + ExpressionResult operand2 = m_Operand2->Evaluate(frame); + CHECK_RESULT(operand2); + + return operand1.GetValue() ^ operand2.GetValue(); +} + +ExpressionResult BinaryAndExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + ExpressionResult operand1 = m_Operand1->Evaluate(frame); + CHECK_RESULT(operand1); + + ExpressionResult operand2 = m_Operand2->Evaluate(frame); + CHECK_RESULT(operand2); + + return operand1.GetValue() & operand2.GetValue(); +} + +ExpressionResult BinaryOrExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + ExpressionResult operand1 = m_Operand1->Evaluate(frame); + CHECK_RESULT(operand1); + + ExpressionResult operand2 = m_Operand2->Evaluate(frame); + CHECK_RESULT(operand2); + + return operand1.GetValue() | operand2.GetValue(); +} + +ExpressionResult ShiftLeftExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + ExpressionResult operand1 = m_Operand1->Evaluate(frame); + CHECK_RESULT(operand1); + + ExpressionResult operand2 = m_Operand2->Evaluate(frame); + CHECK_RESULT(operand2); + + return operand1.GetValue() << operand2.GetValue(); +} + +ExpressionResult ShiftRightExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + ExpressionResult operand1 = m_Operand1->Evaluate(frame); + CHECK_RESULT(operand1); + + ExpressionResult operand2 = m_Operand2->Evaluate(frame); + CHECK_RESULT(operand2); + + return operand1.GetValue() >> operand2.GetValue(); +} + +ExpressionResult EqualExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + ExpressionResult operand1 = m_Operand1->Evaluate(frame); + CHECK_RESULT(operand1); + + ExpressionResult operand2 = m_Operand2->Evaluate(frame); + CHECK_RESULT(operand2); + + return operand1.GetValue() == operand2.GetValue(); +} + +ExpressionResult NotEqualExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + ExpressionResult operand1 = m_Operand1->Evaluate(frame); + CHECK_RESULT(operand1); + + ExpressionResult operand2 = m_Operand2->Evaluate(frame); + CHECK_RESULT(operand2); + + return operand1.GetValue() != operand2.GetValue(); +} + +ExpressionResult LessThanExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + ExpressionResult operand1 = m_Operand1->Evaluate(frame); + CHECK_RESULT(operand1); + + ExpressionResult operand2 = m_Operand2->Evaluate(frame); + CHECK_RESULT(operand2); + + return operand1.GetValue() < operand2.GetValue(); +} + +ExpressionResult GreaterThanExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + ExpressionResult operand1 = m_Operand1->Evaluate(frame); + CHECK_RESULT(operand1); + + ExpressionResult operand2 = m_Operand2->Evaluate(frame); + CHECK_RESULT(operand2); + + return operand1.GetValue() > operand2.GetValue(); +} + +ExpressionResult LessThanOrEqualExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + ExpressionResult operand1 = m_Operand1->Evaluate(frame); + CHECK_RESULT(operand1); + + ExpressionResult operand2 = m_Operand2->Evaluate(frame); + CHECK_RESULT(operand2); + + return operand1.GetValue() <= operand2.GetValue(); +} + +ExpressionResult GreaterThanOrEqualExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + ExpressionResult operand1 = m_Operand1->Evaluate(frame); + CHECK_RESULT(operand1); + + ExpressionResult operand2 = m_Operand2->Evaluate(frame); + CHECK_RESULT(operand2); + + return operand1.GetValue() >= operand2.GetValue(); +} + +ExpressionResult InExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + ExpressionResult operand2 = m_Operand2->Evaluate(frame); + CHECK_RESULT(operand2); + + if (operand2.GetValue().IsEmpty()) + return false; + else if (!operand2.GetValue().IsObjectType<Array>()) + BOOST_THROW_EXCEPTION(ScriptError("Invalid right side argument for 'in' operator: " + JsonEncode(operand2.GetValue()), m_DebugInfo)); + + ExpressionResult operand1 = m_Operand1->Evaluate(frame); + CHECK_RESULT(operand1) + + Array::Ptr arr = operand2.GetValue(); + return arr->Contains(operand1.GetValue()); +} + +ExpressionResult NotInExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + ExpressionResult operand2 = m_Operand2->Evaluate(frame); + CHECK_RESULT(operand2); + + if (operand2.GetValue().IsEmpty()) + return true; + else if (!operand2.GetValue().IsObjectType<Array>()) + BOOST_THROW_EXCEPTION(ScriptError("Invalid right side argument for 'in' operator: " + JsonEncode(operand2.GetValue()), m_DebugInfo)); + + ExpressionResult operand1 = m_Operand1->Evaluate(frame); + CHECK_RESULT(operand1); + + Array::Ptr arr = operand2.GetValue(); + return !arr->Contains(operand1.GetValue()); +} + +ExpressionResult LogicalAndExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + ExpressionResult operand1 = m_Operand1->Evaluate(frame); + CHECK_RESULT(operand1); + + if (!operand1.GetValue().ToBool()) + return operand1; + else { + ExpressionResult operand2 = m_Operand2->Evaluate(frame); + CHECK_RESULT(operand2); + + return operand2.GetValue(); + } +} + +ExpressionResult LogicalOrExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + ExpressionResult operand1 = m_Operand1->Evaluate(frame); + CHECK_RESULT(operand1); + + if (operand1.GetValue().ToBool()) + return operand1; + else { + ExpressionResult operand2 = m_Operand2->Evaluate(frame); + CHECK_RESULT(operand2); + + return operand2.GetValue(); + } +} + +ExpressionResult FunctionCallExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + Value self, vfunc; + String index; + + if (m_FName->GetReference(frame, false, &self, &index)) + vfunc = VMOps::GetField(self, index, frame.Sandboxed, m_DebugInfo); + else { + ExpressionResult vfuncres = m_FName->Evaluate(frame); + CHECK_RESULT(vfuncres); + + vfunc = vfuncres.GetValue(); + } + + if (vfunc.IsObjectType<Type>()) { + std::vector<Value> arguments; + arguments.reserve(m_Args.size()); + for (const auto& arg : m_Args) { + ExpressionResult argres = arg->Evaluate(frame); + CHECK_RESULT(argres); + + arguments.push_back(argres.GetValue()); + } + + return VMOps::ConstructorCall(vfunc, arguments, m_DebugInfo); + } + + if (!vfunc.IsObjectType<Function>()) + BOOST_THROW_EXCEPTION(ScriptError("Argument is not a callable object.", m_DebugInfo)); + + Function::Ptr func = vfunc; + + if (!func->IsSideEffectFree() && frame.Sandboxed) + BOOST_THROW_EXCEPTION(ScriptError("Function is not marked as safe for sandbox mode.", m_DebugInfo)); + + std::vector<Value> arguments; + arguments.reserve(m_Args.size()); + for (const auto& arg : m_Args) { + ExpressionResult argres = arg->Evaluate(frame); + CHECK_RESULT(argres); + + arguments.push_back(argres.GetValue()); + } + + return VMOps::FunctionCall(frame, self, func, arguments); +} + +ExpressionResult ArrayExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + ArrayData result; + result.reserve(m_Expressions.size()); + + for (const auto& aexpr : m_Expressions) { + ExpressionResult element = aexpr->Evaluate(frame); + CHECK_RESULT(element); + + result.push_back(element.GetValue()); + } + + return new Array(std::move(result)); +} + +ExpressionResult DictExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + Value self; + + if (!m_Inline) { + self = frame.Self; + frame.Self = new Dictionary(); + } + + Value result; + + try { + for (const auto& aexpr : m_Expressions) { + ExpressionResult element = aexpr->Evaluate(frame, m_Inline ? dhint : nullptr); + CHECK_RESULT(element); + result = element.GetValue(); + } + } catch (...) { + if (!m_Inline) + std::swap(self, frame.Self); + throw; + } + + if (m_Inline) + return result; + else { + std::swap(self, frame.Self); + return self; + } +} + +ExpressionResult GetScopeExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + if (m_ScopeSpec == ScopeLocal) + return frame.Locals; + else if (m_ScopeSpec == ScopeThis) + return frame.Self; + else if (m_ScopeSpec == ScopeGlobal) + return ScriptGlobal::GetGlobals(); + else + VERIFY(!"Invalid scope."); +} + +static inline +void WarnOnImplicitlySetGlobalVar(const std::unique_ptr<Expression>& setLhs, const Value& setLhsParent, CombinedSetOp setOp, const DebugInfo& debug) +{ + auto var (dynamic_cast<VariableExpression*>(setLhs.get())); + + if (var && setLhsParent.IsObject()) { + auto ns (dynamic_pointer_cast<Namespace>(setLhsParent.Get<Object::Ptr>())); + + if (ns && ns == ScriptGlobal::GetGlobals() && debug.Path.GetLength()) { + const char *opStr = nullptr; + + switch (setOp) { + case OpSetLiteral: + opStr = "="; + break; + case OpSetAdd: + opStr = "+="; + break; + case OpSetSubtract: + opStr = "-="; + break; + case OpSetMultiply: + opStr = "*="; + break; + case OpSetDivide: + opStr = "/="; + break; + case OpSetModulo: + opStr = "%="; + break; + case OpSetXor: + opStr = "^="; + break; + case OpSetBinaryAnd: + opStr = "&="; + break; + case OpSetBinaryOr: + opStr = "|="; + break; + default: + VERIFY(!"Invalid opcode."); + } + + auto varName (var->GetVariable()); + + Log(LogWarning, "config") + << "Global variable '" << varName << "' has been set implicitly via '" << varName << ' ' << opStr << " ...' " << debug << "." + " Please set it explicitly via 'globals." << varName << ' ' << opStr << " ...' instead."; + } + } +} + +ExpressionResult SetExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + if (frame.Sandboxed) + BOOST_THROW_EXCEPTION(ScriptError("Assignments are not allowed in sandbox mode.", m_DebugInfo)); + + DebugHint *psdhint = dhint; + + Value parent; + String index; + + if (!m_Operand1->GetReference(frame, true, &parent, &index, &psdhint)) + BOOST_THROW_EXCEPTION(ScriptError("Expression cannot be assigned to.", m_DebugInfo)); + + ExpressionResult operand2 = m_Operand2->Evaluate(frame, dhint); + CHECK_RESULT(operand2); + + if (m_Op != OpSetLiteral) { + Value object = VMOps::GetField(parent, index, frame.Sandboxed, m_DebugInfo); + + switch (m_Op) { + case OpSetAdd: + operand2 = object + operand2; + break; + case OpSetSubtract: + operand2 = object - operand2; + break; + case OpSetMultiply: + operand2 = object * operand2; + break; + case OpSetDivide: + operand2 = object / operand2; + break; + case OpSetModulo: + operand2 = object % operand2; + break; + case OpSetXor: + operand2 = object ^ operand2; + break; + case OpSetBinaryAnd: + operand2 = object & operand2; + break; + case OpSetBinaryOr: + operand2 = object | operand2; + break; + default: + VERIFY(!"Invalid opcode."); + } + } + + VMOps::SetField(parent, index, operand2.GetValue(), m_OverrideFrozen, m_DebugInfo); + + if (psdhint) { + psdhint->AddMessage("=", m_DebugInfo); + + if (psdhint != dhint) + delete psdhint; + } + + WarnOnImplicitlySetGlobalVar(m_Operand1, parent, m_Op, m_DebugInfo); + + return Empty; +} + +void SetExpression::SetOverrideFrozen() +{ + m_OverrideFrozen = true; +} + +ExpressionResult SetConstExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + auto globals = ScriptGlobal::GetGlobals(); + + ExpressionResult operandres = m_Operand->Evaluate(frame); + CHECK_RESULT(operandres); + Value operand = operandres.GetValue(); + + globals->Set(m_Name, operand, true); + + return Empty; +} + +ExpressionResult ConditionalExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + ExpressionResult condition = m_Condition->Evaluate(frame, dhint); + CHECK_RESULT(condition); + + if (condition.GetValue().ToBool()) + return m_TrueBranch->Evaluate(frame, dhint); + else if (m_FalseBranch) + return m_FalseBranch->Evaluate(frame, dhint); + + return Empty; +} + +ExpressionResult WhileExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + if (frame.Sandboxed) + BOOST_THROW_EXCEPTION(ScriptError("While loops are not allowed in sandbox mode.", m_DebugInfo)); + + for (;;) { + ExpressionResult condition = m_Condition->Evaluate(frame, dhint); + CHECK_RESULT(condition); + + if (!condition.GetValue().ToBool()) + break; + + ExpressionResult loop_body = m_LoopBody->Evaluate(frame, dhint); + CHECK_RESULT_LOOP(loop_body); + } + + return Empty; +} + +ExpressionResult ReturnExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + ExpressionResult operand = m_Operand->Evaluate(frame); + CHECK_RESULT(operand); + + return ExpressionResult(operand.GetValue(), ResultReturn); +} + +ExpressionResult BreakExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + return ExpressionResult(Empty, ResultBreak); +} + +ExpressionResult ContinueExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + return ExpressionResult(Empty, ResultContinue); +} + +ExpressionResult IndexerExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + ExpressionResult operand1 = m_Operand1->Evaluate(frame, dhint); + CHECK_RESULT(operand1); + + ExpressionResult operand2 = m_Operand2->Evaluate(frame, dhint); + CHECK_RESULT(operand2); + + return VMOps::GetField(operand1.GetValue(), operand2.GetValue(), frame.Sandboxed, m_DebugInfo); +} + +bool IndexerExpression::GetReference(ScriptFrame& frame, bool init_dict, Value *parent, String *index, DebugHint **dhint) const +{ + Value vparent; + String vindex; + DebugHint *psdhint = nullptr; + bool free_psd = false; + + if (dhint) + psdhint = *dhint; + + if (frame.Sandboxed) + init_dict = false; + + if (m_Operand1->GetReference(frame, init_dict, &vparent, &vindex, &psdhint)) { + if (init_dict) { + Value old_value; + bool has_field = true; + + if (vparent.IsObject()) { + Object::Ptr oparent = vparent; + has_field = oparent->HasOwnField(vindex); + } + + if (has_field) + old_value = VMOps::GetField(vparent, vindex, frame.Sandboxed, m_Operand1->GetDebugInfo()); + + if (old_value.IsEmpty() && !old_value.IsString()) + VMOps::SetField(vparent, vindex, new Dictionary(), m_OverrideFrozen, m_Operand1->GetDebugInfo()); + } + + *parent = VMOps::GetField(vparent, vindex, frame.Sandboxed, m_DebugInfo); + free_psd = true; + } else { + ExpressionResult operand1 = m_Operand1->Evaluate(frame); + *parent = operand1.GetValue(); + } + + ExpressionResult operand2 = m_Operand2->Evaluate(frame); + *index = operand2.GetValue(); + + if (dhint) { + if (psdhint) + *dhint = new DebugHint(psdhint->GetChild(*index)); + else + *dhint = nullptr; + } + + if (free_psd) + delete psdhint; + + return true; +} + +void IndexerExpression::SetOverrideFrozen() +{ + m_OverrideFrozen = true; +} + +void icinga::BindToScope(std::unique_ptr<Expression>& expr, ScopeSpecifier scopeSpec) +{ + auto *dexpr = dynamic_cast<DictExpression *>(expr.get()); + + if (dexpr) { + for (auto& expr : dexpr->m_Expressions) + BindToScope(expr, scopeSpec); + + return; + } + + auto *aexpr = dynamic_cast<SetExpression *>(expr.get()); + + if (aexpr) { + BindToScope(aexpr->m_Operand1, scopeSpec); + + return; + } + + auto *iexpr = dynamic_cast<IndexerExpression *>(expr.get()); + + if (iexpr) { + BindToScope(iexpr->m_Operand1, scopeSpec); + return; + } + + auto *lexpr = dynamic_cast<LiteralExpression *>(expr.get()); + + if (lexpr && lexpr->GetValue().IsString()) { + std::unique_ptr<Expression> scope{new GetScopeExpression(scopeSpec)}; + expr.reset(new IndexerExpression(std::move(scope), std::move(expr), lexpr->GetDebugInfo())); + } + + auto *vexpr = dynamic_cast<VariableExpression *>(expr.get()); + + if (vexpr) { + std::unique_ptr<Expression> scope{new GetScopeExpression(scopeSpec)}; + expr.reset(new IndexerExpression(std::move(scope), MakeLiteral(vexpr->GetVariable()), vexpr->GetDebugInfo())); + } +} + +ExpressionResult ThrowExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + ExpressionResult messageres = m_Message->Evaluate(frame); + CHECK_RESULT(messageres); + Value message = messageres.GetValue(); + BOOST_THROW_EXCEPTION(ScriptError(message, m_DebugInfo, m_IncompleteExpr)); +} + +ExpressionResult ImportExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + if (frame.Sandboxed) + BOOST_THROW_EXCEPTION(ScriptError("Imports are not allowed in sandbox mode.", m_DebugInfo)); + + String type = VMOps::GetField(frame.Self, "type", frame.Sandboxed, m_DebugInfo); + ExpressionResult nameres = m_Name->Evaluate(frame); + CHECK_RESULT(nameres); + Value name = nameres.GetValue(); + + if (!name.IsString()) + BOOST_THROW_EXCEPTION(ScriptError("Template/object name must be a string", m_DebugInfo)); + + ConfigItem::Ptr item = ConfigItem::GetByTypeAndName(Type::GetByName(type), name); + + if (!item) + BOOST_THROW_EXCEPTION(ScriptError("Import references unknown template: '" + name + "'", m_DebugInfo)); + + Dictionary::Ptr scope = item->GetScope(); + + if (scope) + scope->CopyTo(frame.Locals); + + ExpressionResult result = item->GetExpression()->Evaluate(frame, dhint); + CHECK_RESULT(result); + + return Empty; +} + +ExpressionResult ImportDefaultTemplatesExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + if (frame.Sandboxed) + BOOST_THROW_EXCEPTION(ScriptError("Imports are not allowed in sandbox mode.", m_DebugInfo)); + + String type = VMOps::GetField(frame.Self, "type", frame.Sandboxed, m_DebugInfo); + Type::Ptr ptype = Type::GetByName(type); + + for (const ConfigItem::Ptr& item : ConfigItem::GetDefaultTemplates(ptype)) { + Dictionary::Ptr scope = item->GetScope(); + + if (scope) + scope->CopyTo(frame.Locals); + + ExpressionResult result = item->GetExpression()->Evaluate(frame, dhint); + CHECK_RESULT(result); + } + + return Empty; +} + +ExpressionResult FunctionExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + return VMOps::NewFunction(frame, m_Name, m_Args, m_ClosedVars, m_Expression); +} + +ExpressionResult ApplyExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + if (frame.Sandboxed) + BOOST_THROW_EXCEPTION(ScriptError("Apply rules are not allowed in sandbox mode.", m_DebugInfo)); + + ExpressionResult nameres = m_Name->Evaluate(frame); + CHECK_RESULT(nameres); + + return VMOps::NewApply(frame, m_Type, m_Target, nameres.GetValue(), m_Filter, + m_Package, m_FKVar, m_FVVar, m_FTerm, m_ClosedVars, m_IgnoreOnError, m_Expression, m_DebugInfo); +} + +ExpressionResult NamespaceExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + Namespace::Ptr ns = new Namespace(true); + + ScriptFrame innerFrame(true, ns); + ExpressionResult result = m_Expression->Evaluate(innerFrame); + CHECK_RESULT(result); + + return ns; +} + +ExpressionResult ObjectExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + if (frame.Sandboxed) + BOOST_THROW_EXCEPTION(ScriptError("Object definitions are not allowed in sandbox mode.", m_DebugInfo)); + + ExpressionResult typeres = m_Type->Evaluate(frame, dhint); + CHECK_RESULT(typeres); + Type::Ptr type = typeres.GetValue(); + + String name; + + if (m_Name) { + ExpressionResult nameres = m_Name->Evaluate(frame, dhint); + CHECK_RESULT(nameres); + + name = nameres.GetValue(); + } + + return VMOps::NewObject(frame, m_Abstract, type, name, m_Filter, m_Zone, + m_Package, m_DefaultTmpl, m_IgnoreOnError, m_ClosedVars, m_Expression, m_DebugInfo); +} + +ExpressionResult ForExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + if (frame.Sandboxed) + BOOST_THROW_EXCEPTION(ScriptError("For loops are not allowed in sandbox mode.", m_DebugInfo)); + + ExpressionResult valueres = m_Value->Evaluate(frame, dhint); + CHECK_RESULT(valueres); + + return VMOps::For(frame, m_FKVar, m_FVVar, valueres.GetValue(), m_Expression, m_DebugInfo); +} + +ExpressionResult LibraryExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + if (frame.Sandboxed) + BOOST_THROW_EXCEPTION(ScriptError("Loading libraries is not allowed in sandbox mode.", m_DebugInfo)); + + ExpressionResult libres = m_Operand->Evaluate(frame, dhint); + CHECK_RESULT(libres); + + Log(LogNotice, "config") + << "Ignoring explicit load request for library \"" << libres << "\"."; + + return Empty; +} + +ExpressionResult IncludeExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + if (frame.Sandboxed) + BOOST_THROW_EXCEPTION(ScriptError("Includes are not allowed in sandbox mode.", m_DebugInfo)); + + std::unique_ptr<Expression> expr; + String name, path, pattern; + + switch (m_Type) { + case IncludeRegular: + { + ExpressionResult pathres = m_Path->Evaluate(frame, dhint); + CHECK_RESULT(pathres); + path = pathres.GetValue(); + } + + expr = ConfigCompiler::HandleInclude(m_RelativeBase, path, m_SearchIncludes, m_Zone, m_Package, m_DebugInfo); + break; + + case IncludeRecursive: + { + ExpressionResult pathres = m_Path->Evaluate(frame, dhint); + CHECK_RESULT(pathres); + path = pathres.GetValue(); + } + + { + ExpressionResult patternres = m_Pattern->Evaluate(frame, dhint); + CHECK_RESULT(patternres); + pattern = patternres.GetValue(); + } + + expr = ConfigCompiler::HandleIncludeRecursive(m_RelativeBase, path, pattern, m_Zone, m_Package, m_DebugInfo); + break; + + case IncludeZones: + { + ExpressionResult nameres = m_Name->Evaluate(frame, dhint); + CHECK_RESULT(nameres); + name = nameres.GetValue(); + } + + { + ExpressionResult pathres = m_Path->Evaluate(frame, dhint); + CHECK_RESULT(pathres); + path = pathres.GetValue(); + } + + { + ExpressionResult patternres = m_Pattern->Evaluate(frame, dhint); + CHECK_RESULT(patternres); + pattern = patternres.GetValue(); + } + + expr = ConfigCompiler::HandleIncludeZones(m_RelativeBase, name, path, pattern, m_Package, m_DebugInfo); + break; + } + + ExpressionResult res(Empty); + + try { + res = expr->Evaluate(frame, dhint); + } catch (const std::exception&) { + throw; + } + + return res; +} + +ExpressionResult BreakpointExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + ScriptBreakpoint(frame, nullptr, GetDebugInfo()); + + return Empty; +} + +ExpressionResult TryExceptExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const +{ + try { + ExpressionResult tryResult = m_TryBody->Evaluate(frame, dhint); + CHECK_RESULT(tryResult); + } catch (const std::exception&) { + ExpressionResult exceptResult = m_ExceptBody->Evaluate(frame, dhint); + CHECK_RESULT(exceptResult); + } + + return Empty; +} + diff --git a/lib/config/expression.hpp b/lib/config/expression.hpp new file mode 100644 index 0000000..644548d --- /dev/null +++ b/lib/config/expression.hpp @@ -0,0 +1,986 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef EXPRESSION_H +#define EXPRESSION_H + +#include "config/i2-config.hpp" +#include "base/debuginfo.hpp" +#include "base/array.hpp" +#include "base/dictionary.hpp" +#include "base/function.hpp" +#include "base/exception.hpp" +#include "base/scriptframe.hpp" +#include "base/shared-object.hpp" +#include "base/convert.hpp" +#include <map> + +namespace icinga +{ + +struct DebugHint +{ +public: + DebugHint(Dictionary::Ptr hints = nullptr) + : m_Hints(std::move(hints)) + { } + + DebugHint(Dictionary::Ptr&& hints) + : m_Hints(std::move(hints)) + { } + + void AddMessage(const String& message, const DebugInfo& di) + { + GetMessages()->Add(new Array({ message, di.Path, di.FirstLine, di.FirstColumn, di.LastLine, di.LastColumn })); + } + + DebugHint GetChild(const String& name) + { + const Dictionary::Ptr& children = GetChildren(); + + Value vchild; + Dictionary::Ptr child; + + if (!children->Get(name, &vchild)) { + child = new Dictionary(); + children->Set(name, child); + } else + child = vchild; + + return DebugHint(child); + } + + Dictionary::Ptr ToDictionary() const + { + return m_Hints; + } + +private: + Dictionary::Ptr m_Hints; + Array::Ptr m_Messages; + Dictionary::Ptr m_Children; + + const Array::Ptr& GetMessages() + { + if (m_Messages) + return m_Messages; + + if (!m_Hints) + m_Hints = new Dictionary(); + + Value vmessages; + + if (!m_Hints->Get("messages", &vmessages)) { + m_Messages = new Array(); + m_Hints->Set("messages", m_Messages); + } else + m_Messages = vmessages; + + return m_Messages; + } + + const Dictionary::Ptr& GetChildren() + { + if (m_Children) + return m_Children; + + if (!m_Hints) + m_Hints = new Dictionary(); + + Value vchildren; + + if (!m_Hints->Get("properties", &vchildren)) { + m_Children = new Dictionary(); + m_Hints->Set("properties", m_Children); + } else + m_Children = vchildren; + + return m_Children; + } +}; + +enum CombinedSetOp +{ + OpSetLiteral, + OpSetAdd, + OpSetSubtract, + OpSetMultiply, + OpSetDivide, + OpSetModulo, + OpSetXor, + OpSetBinaryAnd, + OpSetBinaryOr +}; + +enum ScopeSpecifier +{ + ScopeLocal, + ScopeThis, + ScopeGlobal +}; + +typedef std::map<String, String> DefinitionMap; + +/** + * @ingroup config + */ +enum ExpressionResultCode +{ + ResultOK, + ResultReturn, + ResultContinue, + ResultBreak +}; + +/** + * @ingroup config + */ +struct ExpressionResult +{ +public: + template<typename T> + ExpressionResult(T value, ExpressionResultCode code = ResultOK) + : m_Value(std::move(value)), m_Code(code) + { } + + operator const Value&() const + { + return m_Value; + } + + const Value& GetValue() const + { + return m_Value; + } + + ExpressionResultCode GetCode() const + { + return m_Code; + } + +private: + Value m_Value; + ExpressionResultCode m_Code; +}; + +#define CHECK_RESULT(res) \ + do { \ + if (res.GetCode() != ResultOK) \ + return res; \ + } while (0); + +#define CHECK_RESULT_LOOP(res) \ + if (res.GetCode() == ResultReturn) \ + return res; \ + if (res.GetCode() == ResultContinue) \ + continue; \ + if (res.GetCode() == ResultBreak) \ + break; \ + +/** + * @ingroup config + */ +class Expression : public SharedObject +{ +public: + DECLARE_PTR_TYPEDEFS(Expression); + + Expression() = default; + Expression(const Expression&) = delete; + virtual ~Expression(); + + Expression& operator=(const Expression&) = delete; + + ExpressionResult Evaluate(ScriptFrame& frame, DebugHint *dhint = nullptr) const; + virtual bool GetReference(ScriptFrame& frame, bool init_dict, Value *parent, String *index, DebugHint **dhint = nullptr) const; + virtual const DebugInfo& GetDebugInfo() const; + + virtual ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const = 0; + + static boost::signals2::signal<void (ScriptFrame& frame, ScriptError *ex, const DebugInfo& di)> OnBreakpoint; + + static void ScriptBreakpoint(ScriptFrame& frame, ScriptError *ex, const DebugInfo& di); +}; + +std::unique_ptr<Expression> MakeIndexer(ScopeSpecifier scopeSpec, const String& index); + +class OwnedExpression final : public Expression +{ +public: + OwnedExpression(Expression::Ptr expression) + : m_Expression(std::move(expression)) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override + { + return m_Expression->DoEvaluate(frame, dhint); + } + + const DebugInfo& GetDebugInfo() const override + { + return m_Expression->GetDebugInfo(); + } + +private: + Expression::Ptr m_Expression; +}; + +class LiteralExpression final : public Expression +{ +public: + LiteralExpression(Value value = Value()); + + const Value& GetValue() const + { + return m_Value; + } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; + +private: + Value m_Value; +}; + +inline LiteralExpression *MakeLiteralRaw(const Value& literal = Value()) +{ + return new LiteralExpression(literal); +} + +inline std::unique_ptr<LiteralExpression> MakeLiteral(const Value& literal = Value()) +{ + return std::unique_ptr<LiteralExpression>(MakeLiteralRaw(literal)); +} + +class DebuggableExpression : public Expression +{ +public: + DebuggableExpression(DebugInfo debugInfo = DebugInfo()) + : m_DebugInfo(std::move(debugInfo)) + { } + +protected: + const DebugInfo& GetDebugInfo() const final; + + DebugInfo m_DebugInfo; +}; + +class UnaryExpression : public DebuggableExpression +{ +public: + UnaryExpression(std::unique_ptr<Expression> operand, const DebugInfo& debugInfo = DebugInfo()) + : DebuggableExpression(debugInfo), m_Operand(std::move(operand)) + { } + +protected: + std::unique_ptr<Expression> m_Operand; +}; + +class BinaryExpression : public DebuggableExpression +{ +public: + BinaryExpression(std::unique_ptr<Expression> operand1, std::unique_ptr<Expression> operand2, const DebugInfo& debugInfo = DebugInfo()) + : DebuggableExpression(debugInfo), m_Operand1(std::move(operand1)), m_Operand2(std::move(operand2)) + { } + + inline const std::unique_ptr<Expression>& GetOperand1() const noexcept + { + return m_Operand1; + } + + inline const std::unique_ptr<Expression>& GetOperand2() const noexcept + { + return m_Operand2; + } + +protected: + std::unique_ptr<Expression> m_Operand1; + std::unique_ptr<Expression> m_Operand2; +}; + +class VariableExpression final : public DebuggableExpression +{ +public: + VariableExpression(String variable, std::vector<Expression::Ptr> imports, const DebugInfo& debugInfo = DebugInfo()); + + inline const String& GetVariable() const + { + return m_Variable; + } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; + bool GetReference(ScriptFrame& frame, bool init_dict, Value *parent, String *index, DebugHint **dhint) const override; + +private: + String m_Variable; + std::vector<Expression::Ptr> m_Imports; + + friend void BindToScope(std::unique_ptr<Expression>& expr, ScopeSpecifier scopeSpec); +}; + +class DerefExpression final : public UnaryExpression +{ +public: + DerefExpression(std::unique_ptr<Expression> operand, const DebugInfo& debugInfo = DebugInfo()) + : UnaryExpression(std::move(operand), debugInfo) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; + bool GetReference(ScriptFrame& frame, bool init_dict, Value *parent, String *index, DebugHint **dhint) const override; +}; + +class RefExpression final : public UnaryExpression +{ +public: + RefExpression(std::unique_ptr<Expression> operand, const DebugInfo& debugInfo = DebugInfo()) + : UnaryExpression(std::move(operand), debugInfo) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; +}; + +class NegateExpression final : public UnaryExpression +{ +public: + NegateExpression(std::unique_ptr<Expression> operand, const DebugInfo& debugInfo = DebugInfo()) + : UnaryExpression(std::move(operand), debugInfo) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; +}; + +class LogicalNegateExpression final : public UnaryExpression +{ +public: + LogicalNegateExpression(std::unique_ptr<Expression> operand, const DebugInfo& debugInfo = DebugInfo()) + : UnaryExpression(std::move(operand), debugInfo) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; +}; + +class AddExpression final : public BinaryExpression +{ +public: + AddExpression(std::unique_ptr<Expression> operand1, std::unique_ptr<Expression> operand2, const DebugInfo& debugInfo = DebugInfo()) + : BinaryExpression(std::move(operand1), std::move(operand2), debugInfo) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; +}; + +class SubtractExpression final : public BinaryExpression +{ +public: + SubtractExpression(std::unique_ptr<Expression> operand1, std::unique_ptr<Expression> operand2, const DebugInfo& debugInfo = DebugInfo()) + : BinaryExpression(std::move(operand1), std::move(operand2), debugInfo) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; +}; + +class MultiplyExpression final : public BinaryExpression +{ +public: + MultiplyExpression(std::unique_ptr<Expression> operand1, std::unique_ptr<Expression> operand2, const DebugInfo& debugInfo = DebugInfo()) + : BinaryExpression(std::move(operand1), std::move(operand2), debugInfo) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; +}; + +class DivideExpression final : public BinaryExpression +{ +public: + DivideExpression(std::unique_ptr<Expression> operand1, std::unique_ptr<Expression> operand2, const DebugInfo& debugInfo = DebugInfo()) + : BinaryExpression(std::move(operand1), std::move(operand2), debugInfo) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; +}; + +class ModuloExpression final : public BinaryExpression +{ +public: + ModuloExpression(std::unique_ptr<Expression> operand1, std::unique_ptr<Expression> operand2, const DebugInfo& debugInfo = DebugInfo()) + : BinaryExpression(std::move(operand1), std::move(operand2), debugInfo) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; +}; + +class XorExpression final : public BinaryExpression +{ +public: + XorExpression(std::unique_ptr<Expression> operand1, std::unique_ptr<Expression> operand2, const DebugInfo& debugInfo = DebugInfo()) + : BinaryExpression(std::move(operand1), std::move(operand2), debugInfo) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; +}; + +class BinaryAndExpression final : public BinaryExpression +{ +public: + BinaryAndExpression(std::unique_ptr<Expression> operand1, std::unique_ptr<Expression> operand2, const DebugInfo& debugInfo = DebugInfo()) + : BinaryExpression(std::move(operand1), std::move(operand2), debugInfo) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; +}; + +class BinaryOrExpression final : public BinaryExpression +{ +public: + BinaryOrExpression(std::unique_ptr<Expression> operand1, std::unique_ptr<Expression> operand2, const DebugInfo& debugInfo = DebugInfo()) + : BinaryExpression(std::move(operand1), std::move(operand2), debugInfo) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; +}; + +class ShiftLeftExpression final : public BinaryExpression +{ +public: + ShiftLeftExpression(std::unique_ptr<Expression> operand1, std::unique_ptr<Expression> operand2, const DebugInfo& debugInfo = DebugInfo()) + : BinaryExpression(std::move(operand1), std::move(operand2), debugInfo) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; +}; + +class ShiftRightExpression final : public BinaryExpression +{ +public: + ShiftRightExpression(std::unique_ptr<Expression> operand1, std::unique_ptr<Expression> operand2, const DebugInfo& debugInfo = DebugInfo()) + : BinaryExpression(std::move(operand1), std::move(operand2), debugInfo) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; +}; + +class EqualExpression final : public BinaryExpression +{ +public: + EqualExpression(std::unique_ptr<Expression> operand1, std::unique_ptr<Expression> operand2, const DebugInfo& debugInfo = DebugInfo()) + : BinaryExpression(std::move(operand1), std::move(operand2), debugInfo) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; +}; + +class NotEqualExpression final : public BinaryExpression +{ +public: + NotEqualExpression(std::unique_ptr<Expression> operand1, std::unique_ptr<Expression> operand2, const DebugInfo& debugInfo = DebugInfo()) + : BinaryExpression(std::move(operand1), std::move(operand2), debugInfo) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; +}; + +class LessThanExpression final : public BinaryExpression +{ +public: + LessThanExpression(std::unique_ptr<Expression> operand1, std::unique_ptr<Expression> operand2, const DebugInfo& debugInfo = DebugInfo()) + : BinaryExpression(std::move(operand1), std::move(operand2), debugInfo) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; +}; + +class GreaterThanExpression final : public BinaryExpression +{ +public: + GreaterThanExpression(std::unique_ptr<Expression> operand1, std::unique_ptr<Expression> operand2, const DebugInfo& debugInfo = DebugInfo()) + : BinaryExpression(std::move(operand1), std::move(operand2), debugInfo) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; +}; + +class LessThanOrEqualExpression final : public BinaryExpression +{ +public: + LessThanOrEqualExpression(std::unique_ptr<Expression> operand1, std::unique_ptr<Expression> operand2, const DebugInfo& debugInfo = DebugInfo()) + : BinaryExpression(std::move(operand1), std::move(operand2), debugInfo) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; +}; + +class GreaterThanOrEqualExpression final : public BinaryExpression +{ +public: + GreaterThanOrEqualExpression(std::unique_ptr<Expression> operand1, std::unique_ptr<Expression> operand2, const DebugInfo& debugInfo = DebugInfo()) + : BinaryExpression(std::move(operand1), std::move(operand2), debugInfo) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; +}; + +class InExpression final : public BinaryExpression +{ +public: + InExpression(std::unique_ptr<Expression> operand1, std::unique_ptr<Expression> operand2, const DebugInfo& debugInfo = DebugInfo()) + : BinaryExpression(std::move(operand1), std::move(operand2), debugInfo) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; +}; + +class NotInExpression final : public BinaryExpression +{ +public: + NotInExpression(std::unique_ptr<Expression> operand1, std::unique_ptr<Expression> operand2, const DebugInfo& debugInfo = DebugInfo()) + : BinaryExpression(std::move(operand1), std::move(operand2), debugInfo) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; +}; + +class LogicalAndExpression final : public BinaryExpression +{ +public: + LogicalAndExpression(std::unique_ptr<Expression> operand1, std::unique_ptr<Expression> operand2, const DebugInfo& debugInfo = DebugInfo()) + : BinaryExpression(std::move(operand1), std::move(operand2), debugInfo) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; +}; + +class LogicalOrExpression final : public BinaryExpression +{ +public: + LogicalOrExpression(std::unique_ptr<Expression> operand1, std::unique_ptr<Expression> operand2, const DebugInfo& debugInfo = DebugInfo()) + : BinaryExpression(std::move(operand1), std::move(operand2), debugInfo) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; +}; + +class FunctionCallExpression final : public DebuggableExpression +{ +public: + FunctionCallExpression(std::unique_ptr<Expression> fname, std::vector<std::unique_ptr<Expression> >&& args, const DebugInfo& debugInfo = DebugInfo()) + : DebuggableExpression(debugInfo), m_FName(std::move(fname)), m_Args(std::move(args)) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; + +public: + std::unique_ptr<Expression> m_FName; + std::vector<std::unique_ptr<Expression> > m_Args; +}; + +class ArrayExpression final : public DebuggableExpression +{ +public: + ArrayExpression(std::vector<std::unique_ptr<Expression > >&& expressions, const DebugInfo& debugInfo = DebugInfo()) + : DebuggableExpression(debugInfo), m_Expressions(std::move(expressions)) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; + +private: + std::vector<std::unique_ptr<Expression> > m_Expressions; +}; + +class DictExpression final : public DebuggableExpression +{ +public: + DictExpression(std::vector<std::unique_ptr<Expression> >&& expressions = {}, const DebugInfo& debugInfo = DebugInfo()) + : DebuggableExpression(debugInfo), m_Expressions(std::move(expressions)) + { } + + void MakeInline(); + + inline const std::vector<std::unique_ptr<Expression>>& GetExpressions() const noexcept + { + return m_Expressions; + } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; + +private: + std::vector<std::unique_ptr<Expression> > m_Expressions; + bool m_Inline{false}; + + friend void BindToScope(std::unique_ptr<Expression>& expr, ScopeSpecifier scopeSpec); +}; + +class SetConstExpression final : public UnaryExpression +{ +public: + SetConstExpression(const String& name, std::unique_ptr<Expression> operand, const DebugInfo& debugInfo = DebugInfo()) + : UnaryExpression(std::move(operand), debugInfo), m_Name(name) + { } + +protected: + String m_Name; + + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; +}; + +class SetExpression final : public BinaryExpression +{ +public: + SetExpression(std::unique_ptr<Expression> operand1, CombinedSetOp op, std::unique_ptr<Expression> operand2, const DebugInfo& debugInfo = DebugInfo()) + : BinaryExpression(std::move(operand1), std::move(operand2), debugInfo), m_Op(op) + { } + + void SetOverrideFrozen(); + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; + +private: + CombinedSetOp m_Op; + bool m_OverrideFrozen{false}; + + friend void BindToScope(std::unique_ptr<Expression>& expr, ScopeSpecifier scopeSpec); +}; + +class ConditionalExpression final : public DebuggableExpression +{ +public: + ConditionalExpression(std::unique_ptr<Expression> condition, std::unique_ptr<Expression> true_branch, std::unique_ptr<Expression> false_branch, const DebugInfo& debugInfo = DebugInfo()) + : DebuggableExpression(debugInfo), m_Condition(std::move(condition)), m_TrueBranch(std::move(true_branch)), m_FalseBranch(std::move(false_branch)) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; + +private: + std::unique_ptr<Expression> m_Condition; + std::unique_ptr<Expression> m_TrueBranch; + std::unique_ptr<Expression> m_FalseBranch; +}; + +class WhileExpression final : public DebuggableExpression +{ +public: + WhileExpression(std::unique_ptr<Expression> condition, std::unique_ptr<Expression> loop_body, const DebugInfo& debugInfo = DebugInfo()) + : DebuggableExpression(debugInfo), m_Condition(std::move(condition)), m_LoopBody(std::move(loop_body)) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; + +private: + std::unique_ptr<Expression> m_Condition; + std::unique_ptr<Expression> m_LoopBody; +}; + + +class ReturnExpression final : public UnaryExpression +{ +public: + ReturnExpression(std::unique_ptr<Expression> expression, const DebugInfo& debugInfo = DebugInfo()) + : UnaryExpression(std::move(expression), debugInfo) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; +}; + +class BreakExpression final : public DebuggableExpression +{ +public: + BreakExpression(const DebugInfo& debugInfo = DebugInfo()) + : DebuggableExpression(debugInfo) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; +}; + +class ContinueExpression final : public DebuggableExpression +{ +public: + ContinueExpression(const DebugInfo& debugInfo = DebugInfo()) + : DebuggableExpression(debugInfo) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; +}; + +class GetScopeExpression final : public Expression +{ +public: + GetScopeExpression(ScopeSpecifier scopeSpec) + : m_ScopeSpec(scopeSpec) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; + +private: + ScopeSpecifier m_ScopeSpec; +}; + +class IndexerExpression final : public BinaryExpression +{ +public: + IndexerExpression(std::unique_ptr<Expression> operand1, std::unique_ptr<Expression> operand2, const DebugInfo& debugInfo = DebugInfo()) + : BinaryExpression(std::move(operand1), std::move(operand2), debugInfo) + { } + + void SetOverrideFrozen(); + +protected: + bool m_OverrideFrozen{false}; + + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; + bool GetReference(ScriptFrame& frame, bool init_dict, Value *parent, String *index, DebugHint **dhint) const override; + + friend void BindToScope(std::unique_ptr<Expression>& expr, ScopeSpecifier scopeSpec); +}; + +void BindToScope(std::unique_ptr<Expression>& expr, ScopeSpecifier scopeSpec); + +class ThrowExpression final : public DebuggableExpression +{ +public: + ThrowExpression(std::unique_ptr<Expression> message, bool incompleteExpr, const DebugInfo& debugInfo = DebugInfo()) + : DebuggableExpression(debugInfo), m_Message(std::move(message)), m_IncompleteExpr(incompleteExpr) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; + +private: + std::unique_ptr<Expression> m_Message; + bool m_IncompleteExpr; +}; + +class ImportExpression final : public DebuggableExpression +{ +public: + ImportExpression(std::unique_ptr<Expression> name, const DebugInfo& debugInfo = DebugInfo()) + : DebuggableExpression(debugInfo), m_Name(std::move(name)) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; + +private: + std::unique_ptr<Expression> m_Name; +}; + +class ImportDefaultTemplatesExpression final : public DebuggableExpression +{ +public: + ImportDefaultTemplatesExpression(const DebugInfo& debugInfo = DebugInfo()) + : DebuggableExpression(debugInfo) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; +}; + +class FunctionExpression final : public DebuggableExpression +{ +public: + FunctionExpression(String name, std::vector<String> args, + std::map<String, std::unique_ptr<Expression> >&& closedVars, std::unique_ptr<Expression> expression, const DebugInfo& debugInfo = DebugInfo()) + : DebuggableExpression(debugInfo), m_Name(std::move(name)), m_Args(std::move(args)), m_ClosedVars(std::move(closedVars)), m_Expression(expression.release()) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; + +private: + String m_Name; + std::vector<String> m_Args; + std::map<String, std::unique_ptr<Expression> > m_ClosedVars; + Expression::Ptr m_Expression; +}; + +class ApplyExpression final : public DebuggableExpression +{ +public: + ApplyExpression(String type, String target, std::unique_ptr<Expression> name, + std::unique_ptr<Expression> filter, String package, String fkvar, String fvvar, + std::unique_ptr<Expression> fterm, std::map<String, std::unique_ptr<Expression> >&& closedVars, bool ignoreOnError, + std::unique_ptr<Expression> expression, const DebugInfo& debugInfo = DebugInfo()) + : DebuggableExpression(debugInfo), m_Type(std::move(type)), m_Target(std::move(target)), + m_Name(std::move(name)), m_Filter(filter.release()), m_Package(std::move(package)), m_FKVar(std::move(fkvar)), m_FVVar(std::move(fvvar)), + m_FTerm(fterm.release()), m_IgnoreOnError(ignoreOnError), m_ClosedVars(std::move(closedVars)), + m_Expression(expression.release()) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; + +private: + String m_Type; + String m_Target; + std::unique_ptr<Expression> m_Name; + Expression::Ptr m_Filter; + String m_Package; + String m_FKVar; + String m_FVVar; + Expression::Ptr m_FTerm; + bool m_IgnoreOnError; + std::map<String, std::unique_ptr<Expression> > m_ClosedVars; + Expression::Ptr m_Expression; +}; + +class NamespaceExpression final : public DebuggableExpression +{ +public: + NamespaceExpression(std::unique_ptr<Expression> expression, const DebugInfo& debugInfo = DebugInfo()) + : DebuggableExpression(debugInfo), m_Expression(expression.release()) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; + +private: + Expression::Ptr m_Expression; +}; + +class ObjectExpression final : public DebuggableExpression +{ +public: + ObjectExpression(bool abstract, std::unique_ptr<Expression> type, std::unique_ptr<Expression> name, std::unique_ptr<Expression> filter, + String zone, String package, std::map<String, std::unique_ptr<Expression> >&& closedVars, + bool defaultTmpl, bool ignoreOnError, std::unique_ptr<Expression> expression, const DebugInfo& debugInfo = DebugInfo()) + : DebuggableExpression(debugInfo), m_Abstract(abstract), m_Type(std::move(type)), + m_Name(std::move(name)), m_Filter(filter.release()), m_Zone(std::move(zone)), m_Package(std::move(package)), m_DefaultTmpl(defaultTmpl), + m_IgnoreOnError(ignoreOnError), m_ClosedVars(std::move(closedVars)), m_Expression(expression.release()) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; + +private: + bool m_Abstract; + std::unique_ptr<Expression> m_Type; + std::unique_ptr<Expression> m_Name; + Expression::Ptr m_Filter; + String m_Zone; + String m_Package; + bool m_DefaultTmpl; + bool m_IgnoreOnError; + std::map<String, std::unique_ptr<Expression> > m_ClosedVars; + Expression::Ptr m_Expression; +}; + +class ForExpression final : public DebuggableExpression +{ +public: + ForExpression(String fkvar, String fvvar, std::unique_ptr<Expression> value, std::unique_ptr<Expression> expression, const DebugInfo& debugInfo = DebugInfo()) + : DebuggableExpression(debugInfo), m_FKVar(std::move(fkvar)), m_FVVar(std::move(fvvar)), m_Value(std::move(value)), m_Expression(std::move(expression)) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; + +private: + String m_FKVar; + String m_FVVar; + std::unique_ptr<Expression> m_Value; + std::unique_ptr<Expression> m_Expression; +}; + +class LibraryExpression final : public UnaryExpression +{ +public: + LibraryExpression(std::unique_ptr<Expression> expression, const DebugInfo& debugInfo = DebugInfo()) + : UnaryExpression(std::move(expression), debugInfo) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; +}; + +enum IncludeType +{ + IncludeRegular, + IncludeRecursive, + IncludeZones +}; + +class IncludeExpression final : public DebuggableExpression +{ +public: + IncludeExpression(String relativeBase, std::unique_ptr<Expression> path, std::unique_ptr<Expression> pattern, std::unique_ptr<Expression> name, + IncludeType type, bool searchIncludes, String zone, String package, const DebugInfo& debugInfo = DebugInfo()) + : DebuggableExpression(debugInfo), m_RelativeBase(std::move(relativeBase)), m_Path(std::move(path)), m_Pattern(std::move(pattern)), + m_Name(std::move(name)), m_Type(type), m_SearchIncludes(searchIncludes), m_Zone(std::move(zone)), m_Package(std::move(package)) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; + +private: + String m_RelativeBase; + std::unique_ptr<Expression> m_Path; + std::unique_ptr<Expression> m_Pattern; + std::unique_ptr<Expression> m_Name; + IncludeType m_Type; + bool m_SearchIncludes; + String m_Zone; + String m_Package; +}; + +class BreakpointExpression final : public DebuggableExpression +{ +public: + BreakpointExpression(const DebugInfo& debugInfo = DebugInfo()) + : DebuggableExpression(debugInfo) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; +}; + +class TryExceptExpression final : public DebuggableExpression +{ +public: + TryExceptExpression(std::unique_ptr<Expression> tryBody, std::unique_ptr<Expression> exceptBody, const DebugInfo& debugInfo = DebugInfo()) + : DebuggableExpression(debugInfo), m_TryBody(std::move(tryBody)), m_ExceptBody(std::move(exceptBody)) + { } + +protected: + ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; + +private: + std::unique_ptr<Expression> m_TryBody; + std::unique_ptr<Expression> m_ExceptBody; +}; + +} + +#endif /* EXPRESSION_H */ diff --git a/lib/config/i2-config.hpp b/lib/config/i2-config.hpp new file mode 100644 index 0000000..8c26287 --- /dev/null +++ b/lib/config/i2-config.hpp @@ -0,0 +1,16 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef I2CONFIG_H +#define I2CONFIG_H + +/** + * @defgroup config Configuration library + * + * The configuration library implements a compiler for Icinga 2's configuration + * format. It also provides functionality for creating configuration objects + * at runtime. + */ + +#include "base/i2-base.hpp" + +#endif /* I2CONFIG_H */ diff --git a/lib/config/objectrule.cpp b/lib/config/objectrule.cpp new file mode 100644 index 0000000..6a74a40 --- /dev/null +++ b/lib/config/objectrule.cpp @@ -0,0 +1,18 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "config/objectrule.hpp" +#include <set> + +using namespace icinga; + +ObjectRule::TypeSet ObjectRule::m_Types; + +void ObjectRule::RegisterType(const String& sourceType) +{ + m_Types.insert(sourceType); +} + +bool ObjectRule::IsValidSourceType(const String& sourceType) +{ + return m_Types.find(sourceType) != m_Types.end(); +} diff --git a/lib/config/objectrule.hpp b/lib/config/objectrule.hpp new file mode 100644 index 0000000..d093c9f --- /dev/null +++ b/lib/config/objectrule.hpp @@ -0,0 +1,33 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef OBJECTRULE_H +#define OBJECTRULE_H + +#include "config/i2-config.hpp" +#include "config/expression.hpp" +#include "base/debuginfo.hpp" +#include <set> + +namespace icinga +{ + +/** + * @ingroup config + */ +class ObjectRule +{ +public: + typedef std::set<String> TypeSet; + + static void RegisterType(const String& sourceType); + static bool IsValidSourceType(const String& sourceType); + +private: + ObjectRule(); + + static TypeSet m_Types; +}; + +} + +#endif /* OBJECTRULE_H */ diff --git a/lib/config/vmops.hpp b/lib/config/vmops.hpp new file mode 100644 index 0000000..ea30983 --- /dev/null +++ b/lib/config/vmops.hpp @@ -0,0 +1,274 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#ifndef VMOPS_H +#define VMOPS_H + +#include "config/i2-config.hpp" +#include "config/expression.hpp" +#include "config/configitembuilder.hpp" +#include "config/applyrule.hpp" +#include "config/objectrule.hpp" +#include "base/debuginfo.hpp" +#include "base/array.hpp" +#include "base/dictionary.hpp" +#include "base/namespace.hpp" +#include "base/function.hpp" +#include "base/scriptglobal.hpp" +#include "base/exception.hpp" +#include "base/convert.hpp" +#include "base/objectlock.hpp" +#include <map> +#include <vector> + +namespace icinga +{ + +class VMOps +{ +public: + static inline bool FindVarImportRef(ScriptFrame& frame, const std::vector<Expression::Ptr>& imports, const String& name, Value *result, const DebugInfo& debugInfo = DebugInfo()) + { + for (const auto& import : imports) { + ExpressionResult res = import->Evaluate(frame); + Object::Ptr obj = res.GetValue(); + if (obj->HasOwnField(name)) { + *result = obj; + return true; + } + } + + return false; + } + + static inline bool FindVarImport(ScriptFrame& frame, const std::vector<Expression::Ptr>& imports, const String& name, Value *result, const DebugInfo& debugInfo = DebugInfo()) + { + Value parent; + + if (FindVarImportRef(frame, imports, name, &parent, debugInfo)) { + *result = GetField(parent, name, frame.Sandboxed, debugInfo); + return true; + } + + return false; + } + + static inline Value ConstructorCall(const Type::Ptr& type, const std::vector<Value>& args, const DebugInfo& debugInfo = DebugInfo()) + { + if (type->GetName() == "String") { + if (args.empty()) + return ""; + else if (args.size() == 1) + return Convert::ToString(args[0]); + else + BOOST_THROW_EXCEPTION(ScriptError("Too many arguments for constructor.")); + } else if (type->GetName() == "Number") { + if (args.empty()) + return 0; + else if (args.size() == 1) + return Convert::ToDouble(args[0]); + else + BOOST_THROW_EXCEPTION(ScriptError("Too many arguments for constructor.")); + } else if (type->GetName() == "Boolean") { + if (args.empty()) + return 0; + else if (args.size() == 1) + return Convert::ToBool(args[0]); + else + BOOST_THROW_EXCEPTION(ScriptError("Too many arguments for constructor.")); + } else if (args.size() == 1 && type->IsAssignableFrom(args[0].GetReflectionType())) + return args[0]; + else + return type->Instantiate(args); + } + + static inline Value FunctionCall(ScriptFrame& frame, const Value& self, const Function::Ptr& func, const std::vector<Value>& arguments) + { + if (!self.IsEmpty() || self.IsString()) + return func->InvokeThis(self, arguments); + else + return func->Invoke(arguments); + + } + + static inline Value NewFunction(ScriptFrame& frame, const String& name, const std::vector<String>& argNames, + const std::map<String, std::unique_ptr<Expression> >& closedVars, const Expression::Ptr& expression) + { + auto evaluatedClosedVars = EvaluateClosedVars(frame, closedVars); + + auto wrapper = [argNames, evaluatedClosedVars, expression](const std::vector<Value>& arguments) -> Value { + if (arguments.size() < argNames.size()) + BOOST_THROW_EXCEPTION(std::invalid_argument("Too few arguments for function")); + + ScriptFrame *frame = ScriptFrame::GetCurrentFrame(); + + frame->Locals = new Dictionary(); + + if (evaluatedClosedVars) + evaluatedClosedVars->CopyTo(frame->Locals); + + for (std::vector<Value>::size_type i = 0; i < std::min(arguments.size(), argNames.size()); i++) + frame->Locals->Set(argNames[i], arguments[i]); + + return expression->Evaluate(*frame); + }; + + return new Function(name, wrapper, argNames); + } + + static inline Value NewApply(ScriptFrame& frame, const String& type, const String& target, const String& name, const Expression::Ptr& filter, + const String& package, const String& fkvar, const String& fvvar, const Expression::Ptr& fterm, const std::map<String, std::unique_ptr<Expression> >& closedVars, + bool ignoreOnError, const Expression::Ptr& expression, const DebugInfo& debugInfo = DebugInfo()) + { + ApplyRule::AddRule(type, target, name, expression, filter, package, fkvar, + fvvar, fterm, ignoreOnError, debugInfo, EvaluateClosedVars(frame, closedVars)); + + return Empty; + } + + static inline Value NewObject(ScriptFrame& frame, bool abstract, const Type::Ptr& type, const String& name, const Expression::Ptr& filter, + const String& zone, const String& package, bool defaultTmpl, bool ignoreOnError, const std::map<String, std::unique_ptr<Expression> >& closedVars, const Expression::Ptr& expression, const DebugInfo& debugInfo = DebugInfo()) + { + ConfigItemBuilder item{debugInfo}; + + String checkName = name; + + if (!abstract) { + auto *nc = dynamic_cast<NameComposer *>(type.get()); + + if (nc) + checkName = nc->MakeName(name, nullptr); + } + + if (!checkName.IsEmpty()) { + ConfigItem::Ptr oldItem = ConfigItem::GetByTypeAndName(type, checkName); + + if (oldItem) { + std::ostringstream msgbuf; + msgbuf << "Object '" << name << "' of type '" << type->GetName() << "' re-defined: " << debugInfo << "; previous definition: " << oldItem->GetDebugInfo(); + BOOST_THROW_EXCEPTION(ScriptError(msgbuf.str(), debugInfo)); + } + } + + if (filter && !ObjectRule::IsValidSourceType(type->GetName())) { + std::ostringstream msgbuf; + msgbuf << "Object '" << name << "' of type '" << type->GetName() << "' must not have 'assign where' and 'ignore where' rules: " << debugInfo; + BOOST_THROW_EXCEPTION(ScriptError(msgbuf.str(), debugInfo)); + } + + item.SetType(type); + item.SetName(name); + + if (!abstract) + item.AddExpression(new ImportDefaultTemplatesExpression()); + + item.AddExpression(new OwnedExpression(expression)); + item.SetAbstract(abstract); + item.SetScope(EvaluateClosedVars(frame, closedVars)); + item.SetZone(zone); + item.SetPackage(package); + item.SetFilter(filter); + item.SetDefaultTemplate(defaultTmpl); + item.SetIgnoreOnError(ignoreOnError); + item.Compile()->Register(); + + return Empty; + } + + static inline ExpressionResult For(ScriptFrame& frame, const String& fkvar, const String& fvvar, const Value& value, const std::unique_ptr<Expression>& expression, const DebugInfo& debugInfo = DebugInfo()) + { + if (value.IsObjectType<Array>()) { + if (!fvvar.IsEmpty()) + BOOST_THROW_EXCEPTION(ScriptError("Cannot use dictionary iterator for array.", debugInfo)); + + Array::Ptr arr = value; + + for (Array::SizeType i = 0; i < arr->GetLength(); i++) { + frame.Locals->Set(fkvar, arr->Get(i)); + ExpressionResult res = expression->Evaluate(frame); + CHECK_RESULT_LOOP(res); + } + } else if (value.IsObjectType<Dictionary>()) { + if (fvvar.IsEmpty()) + BOOST_THROW_EXCEPTION(ScriptError("Cannot use array iterator for dictionary.", debugInfo)); + + Dictionary::Ptr dict = value; + std::vector<String> keys; + + { + ObjectLock olock(dict); + for (const Dictionary::Pair& kv : dict) { + keys.push_back(kv.first); + } + } + + for (const String& key : keys) { + frame.Locals->Set(fkvar, key); + frame.Locals->Set(fvvar, dict->Get(key)); + ExpressionResult res = expression->Evaluate(frame); + CHECK_RESULT_LOOP(res); + } + } else if (value.IsObjectType<Namespace>()) { + if (fvvar.IsEmpty()) + BOOST_THROW_EXCEPTION(ScriptError("Cannot use array iterator for namespace.", debugInfo)); + + Namespace::Ptr ns = value; + std::vector<String> keys; + + { + ObjectLock olock(ns); + for (const Namespace::Pair& kv : ns) { + keys.push_back(kv.first); + } + } + + for (const String& key : keys) { + frame.Locals->Set(fkvar, key); + frame.Locals->Set(fvvar, ns->Get(key)); + ExpressionResult res = expression->Evaluate(frame); + CHECK_RESULT_LOOP(res); + } + } else + BOOST_THROW_EXCEPTION(ScriptError("Invalid type in for expression: " + value.GetTypeName(), debugInfo)); + + return Empty; + } + + static inline Value GetField(const Value& context, const String& field, bool sandboxed = false, const DebugInfo& debugInfo = DebugInfo()) + { + if (BOOST_UNLIKELY(context.IsEmpty() && !context.IsString())) + return Empty; + + if (BOOST_UNLIKELY(!context.IsObject())) + return GetPrototypeField(context, field, true, debugInfo); + + Object::Ptr object = context; + + return object->GetFieldByName(field, sandboxed, debugInfo); + } + + static inline void SetField(const Object::Ptr& context, const String& field, const Value& value, bool overrideFrozen, const DebugInfo& debugInfo = DebugInfo()) + { + if (!context) + BOOST_THROW_EXCEPTION(ScriptError("Cannot set field '" + field + "' on a value that is not an object.", debugInfo)); + + return context->SetFieldByName(field, value, overrideFrozen, debugInfo); + } + +private: + static inline Dictionary::Ptr EvaluateClosedVars(ScriptFrame& frame, const std::map<String, std::unique_ptr<Expression> >& closedVars) + { + if (closedVars.empty()) + return nullptr; + + DictionaryData locals; + + for (const auto& cvar : closedVars) + locals.emplace_back(cvar.first, cvar.second->Evaluate(frame)); + + return new Dictionary(std::move(locals)); + } +}; + +} + +#endif /* VMOPS_H */ |