From 56cf64f590cf67a10f60c438e15e4da696bbb9ad Mon Sep 17 00:00:00 2001 From: Gunnar Beutner Date: Thu, 5 Jul 2018 14:04:04 +0200 Subject: [PATCH] Improve error message for serializing objects with recursive references --- lib/base/serializer.cpp | 94 ++++++++++++++++++++++++++++++++++----- lib/base/serializer.hpp | 15 +++++++ lib/config/configitem.cpp | 9 ++++ 3 files changed, 108 insertions(+), 10 deletions(-) diff --git a/lib/base/serializer.cpp b/lib/base/serializer.cpp index 7296ec3ce..1bd8b0c8c 100644 --- a/lib/base/serializer.cpp +++ b/lib/base/serializer.cpp @@ -21,10 +21,68 @@ #include "base/type.hpp" #include "base/application.hpp" #include "base/objectlock.hpp" +#include "base/convert.hpp" +#include "base/exception.hpp" +#include +#include using namespace icinga; -static Array::Ptr SerializeArray(const Array::Ptr& input, int attributeTypes) +struct SerializeStackEntry +{ + String Name; + Value Val; +}; + +CircularReferenceError::CircularReferenceError(String message, std::vector path) + : m_Message(message), m_Path(path) +{ } + +const char *CircularReferenceError::what(void) const throw() +{ + return m_Message.CStr(); +} + +std::vector CircularReferenceError::GetPath() const +{ + return m_Path; +} + +struct SerializeStack +{ + std::deque Entries; + + inline void Push(const String& name, const Value& val) + { + Object::Ptr obj; + + if (val.IsObject()) + obj = val; + + if (obj) { + for (const auto& entry : Entries) { + if (entry.Val == obj) { + std::vector path; + for (const auto& entry : Entries) + path.push_back(entry.Name); + path.push_back(name); + BOOST_THROW_EXCEPTION(CircularReferenceError("Cannot serialize object which recursively refers to itself. Attribute path which leads to the cycle: " + boost::algorithm::join(path, " -> "), path)); + } + } + } + + Entries.push_back({ name, obj }); + } + + inline void Pop() + { + Entries.pop_back(); + } +}; + +static Value SerializeInternal(const Value& value, int attributeTypes, SerializeStack& stack); + +static Array::Ptr SerializeArray(const Array::Ptr& input, int attributeTypes, SerializeStack& stack) { ArrayData result; @@ -32,14 +90,19 @@ static Array::Ptr SerializeArray(const Array::Ptr& input, int attributeTypes) ObjectLock olock(input); + int index = 0; + for (const Value& value : input) { - result.emplace_back(Serialize(value, attributeTypes)); + stack.Push(Convert::ToString(index), value); + result.emplace_back(SerializeInternal(value, attributeTypes, stack)); + stack.Pop(); + index++; } return new Array(std::move(result)); } -static Dictionary::Ptr SerializeDictionary(const Dictionary::Ptr& input, int attributeTypes) +static Dictionary::Ptr SerializeDictionary(const Dictionary::Ptr& input, int attributeTypes, SerializeStack& stack) { DictionaryData result; @@ -48,13 +111,15 @@ static Dictionary::Ptr SerializeDictionary(const Dictionary::Ptr& input, int att ObjectLock olock(input); for (const Dictionary::Pair& kv : input) { - result.emplace_back(kv.first, Serialize(kv.second, attributeTypes)); + stack.Push(kv.first, kv.second); + result.emplace_back(kv.first, SerializeInternal(kv.second, attributeTypes, stack)); + stack.Pop(); } return new Dictionary(std::move(result)); } -static Object::Ptr SerializeObject(const Object::Ptr& input, int attributeTypes) +static Object::Ptr SerializeObject(const Object::Ptr& input, int attributeTypes, SerializeStack& stack) { Type::Ptr type = input->GetReflectionType(); @@ -73,7 +138,10 @@ static Object::Ptr SerializeObject(const Object::Ptr& input, int attributeTypes) if (strcmp(field.Name, "type") == 0) continue; - fields.emplace_back(field.Name, Serialize(input->GetField(i), attributeTypes)); + Value value = input->GetField(i); + stack.Push(field.Name, value); + fields.emplace_back(field.Name, SerializeInternal(input->GetField(i), attributeTypes, stack)); + stack.Pop(); } fields.emplace_back("type", type->GetName()); @@ -158,7 +226,7 @@ static Object::Ptr DeserializeObject(const Object::Ptr& object, const Dictionary return instance; } -Value icinga::Serialize(const Value& value, int attributeTypes) +static Value SerializeInternal(const Value& value, int attributeTypes, SerializeStack& stack) { if (!value.IsObject()) return value; @@ -168,14 +236,20 @@ Value icinga::Serialize(const Value& value, int attributeTypes) Array::Ptr array = dynamic_pointer_cast(input); if (array) - return SerializeArray(array, attributeTypes); + return SerializeArray(array, attributeTypes, stack); Dictionary::Ptr dict = dynamic_pointer_cast(input); if (dict) - return SerializeDictionary(dict, attributeTypes); + return SerializeDictionary(dict, attributeTypes, stack); - return SerializeObject(input, attributeTypes); + return SerializeObject(input, attributeTypes, stack); +} + +Value icinga::Serialize(const Value& value, int attributeTypes) +{ + SerializeStack stack; + return SerializeInternal(value, attributeTypes, stack); } Value icinga::Deserialize(const Value& value, bool safe_mode, int attributeTypes) diff --git a/lib/base/serializer.hpp b/lib/base/serializer.hpp index b5e8cd31a..85e7d454d 100644 --- a/lib/base/serializer.hpp +++ b/lib/base/serializer.hpp @@ -23,10 +23,25 @@ #include "base/i2-base.hpp" #include "base/type.hpp" #include "base/value.hpp" +#include "base/exception.hpp" namespace icinga { +class CircularReferenceError : virtual public user_error +{ +public: + CircularReferenceError(String message, std::vector path); + ~CircularReferenceError() throw() = default; + + const char *what(void) const throw() final; + std::vector GetPath() const; + +private: + String m_Message; + std::vector m_Path; +}; + Value Serialize(const Value& value, int attributeTypes = FAState); Value Deserialize(const Value& value, bool safe_mode = false, int attributeTypes = FAState); Value Deserialize(const Object::Ptr& object, const Value& value, bool safe_mode = false, int attributeTypes = FAState); diff --git a/lib/config/configitem.cpp b/lib/config/configitem.cpp index 4e4eacc29..e20612860 100644 --- a/lib/config/configitem.cpp +++ b/lib/config/configitem.cpp @@ -36,6 +36,7 @@ #include "base/json.hpp" #include "base/exception.hpp" #include "base/function.hpp" +#include #include #include @@ -289,6 +290,14 @@ ConfigObject::Ptr ConfigItem::Commit(bool discard) throw; } + Value serializedObject; + + try { + serializedObject = Serialize(dobj, FAConfig); + } catch (const CircularReferenceError& ex) { + BOOST_THROW_EXCEPTION(ValidationError(dobj, ex.GetPath(), "Circular references are not allowed")); + } + Dictionary::Ptr persistentItem = new Dictionary({ { "type", type->GetName() }, { "name", GetName() },