diff options
Diffstat (limited to 'brillo/any_internal_impl.h')
-rw-r--r-- | brillo/any_internal_impl.h | 373 |
1 files changed, 373 insertions, 0 deletions
diff --git a/brillo/any_internal_impl.h b/brillo/any_internal_impl.h new file mode 100644 index 0000000..932f0ee --- /dev/null +++ b/brillo/any_internal_impl.h @@ -0,0 +1,373 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Internal implementation of brillo::Any class. + +#ifndef LIBCHROMEOS_BRILLO_ANY_INTERNAL_IMPL_H_ +#define LIBCHROMEOS_BRILLO_ANY_INTERNAL_IMPL_H_ + +#include <type_traits> +#include <typeinfo> +#include <utility> + +#include <base/logging.h> +#include <brillo/dbus/data_serialization.h> +#include <brillo/type_name_undecorate.h> + +namespace brillo { + +namespace internal_details { + +// An extension to std::is_convertible to allow conversion from an enum to +// an integral type which std::is_convertible does not indicate as supported. +template <typename From, typename To> +struct IsConvertible + : public std::integral_constant< + bool, + std::is_convertible<From, To>::value || + (std::is_enum<From>::value && std::is_integral<To>::value)> {}; + +// TryConvert is a helper function that does a safe compile-time conditional +// type cast between data types that may not be always convertible. +// From and To are the source and destination types. +// The function returns true if conversion was possible/successful. +template <typename From, typename To> +inline typename std::enable_if<IsConvertible<From, To>::value, bool>::type +TryConvert(const From& in, To* out) { + *out = static_cast<To>(in); + return true; +} +template <typename From, typename To> +inline typename std::enable_if<!IsConvertible<From, To>::value, bool>::type +TryConvert(const From& in, To* out) { + return false; +} + +////////////////////////////////////////////////////////////////////////////// +// Provide a way to compare values of unspecified types without compiler errors +// when no operator==() is provided for a given type. This is important to +// allow Any class to have operator==(), yet still allowing arbitrary types +// (not necessarily comparable) to be placed inside Any without resulting in +// compile-time error. +// +// We achieve this in two ways. First, we provide a IsEqualityComparable<T> +// class that can be used in compile-time conditions to determine if there is +// operator==() defined that takes values of type T (or which can be implicitly +// converted to type T). Secondly, this allows us to specialize a helper +// compare function EqCompare<T>(v1, v2) to use operator==() for types that +// are comparable, and just return false for those that are not. +// +// IsEqualityComparableHelper<T> is a helper class for implementing an +// an STL-compatible IsEqualityComparable<T> containing a Boolean member |value| +// which evaluates to true for comparable types and false otherwise. +template<typename T> +struct IsEqualityComparableHelper { + struct IntWrapper { + // A special structure that provides a constructor that takes an int. + // This way, an int argument passed to a function will be favored over + // IntWrapper when both overloads are provided. + // Also this constructor must NOT be explicit. + // NOLINTNEXTLINE(runtime/explicit) + IntWrapper(int dummy) {} // do nothing + }; + + // Here is an obscure trick to determine if a type U has operator==(). + // We are providing two function prototypes for TriggerFunction. One that + // takes an argument of type IntWrapper (which is implicitly convertible from + // an int), and returns an std::false_type. This is a fall-back mechanism. + template<typename U> + static std::false_type TriggerFunction(IntWrapper dummy); + + // The second overload of TriggerFunction takes an int (explicitly) and + // returns std::true_type. If both overloads are available, this one will be + // chosen when referencing it as TriggerFunction(0), since it is a better + // (more specific) match. + // + // However this overload is available only for types that support operator==. + // This is achieved by employing SFINAE mechanism inside a template function + // overload that refers to operator==() for two values of types U&. This is + // used inside decltype(), so no actual code is executed. If the types + // are not comparable, reference to "==" would fail and the compiler will + // simply ignore this overload due to SFIANE. + // + // The final little trick used here is the reliance on operator comma inside + // the decltype() expression. The result of the expression is always + // std::true_type(). The expression on the left of comma is just evaluated and + // discarded. If it evaluates successfully (i.e. the type has operator==), the + // return value of the function is set to be std::true_value. If it fails, + // the whole function prototype is discarded and is not available in the + // IsEqualityComparableHelper<T> class. + // + // Here we use std::declval<U&>() to make sure we have operator==() that takes + // lvalue references to type U which is not necessarily default-constructible. + template<typename U> + static decltype((std::declval<U&>() == std::declval<U&>()), std::true_type()) + TriggerFunction(int dummy); + + // Finally, use the return type of the overload of TriggerFunction that + // matches the argument (int) to be aliased to type |type|. If T is + // comparable, there will be two overloads and the more specific (int) will + // be chosen which returns std::true_value. If the type is non-comparable, + // there will be only one version of TriggerFunction available which + // returns std::false_value. + using type = decltype(TriggerFunction<T>(0)); +}; + +// IsEqualityComparable<T> is simply a class that derives from either +// std::true_value, if type T is comparable, or from std::false_value, if the +// type is non-comparable. We just use |type| alias from +// IsEqualityComparableHelper<T> as the base class. +template<typename T> +struct IsEqualityComparable : IsEqualityComparableHelper<T>::type {}; + +// EqCompare() overload for non-comparable types. Always returns false. +template<typename T> +inline typename std::enable_if<!IsEqualityComparable<T>::value, bool>::type +EqCompare(const T& v1, const T& v2) { + return false; +} + +// EqCompare overload for comparable types. Calls operator==(v1, v2) to compare. +template<typename T> +inline typename std::enable_if<IsEqualityComparable<T>::value, bool>::type +EqCompare(const T& v1, const T& v2) { + return (v1 == v2); +} + +////////////////////////////////////////////////////////////////////////////// + +class Buffer; // Forward declaration of data buffer container. + +// Abstract base class for contained variant data. +struct Data { + virtual ~Data() {} + // Returns the type information for the contained data. + virtual const std::type_info& GetType() const = 0; + // Copies the contained data to the output |buffer|. + virtual void CopyTo(Buffer* buffer) const = 0; + // Moves the contained data to the output |buffer|. + virtual void MoveTo(Buffer* buffer) = 0; + // Checks if the contained data is an integer type (not necessarily an 'int'). + virtual bool IsConvertibleToInteger() const = 0; + // Gets the contained integral value as an integer. + virtual intmax_t GetAsInteger() const = 0; + // Writes the contained value to the D-Bus message buffer. + virtual void AppendToDBusMessage(dbus::MessageWriter* writer) const = 0; + // Compares if the two data containers have objects of the same value. + virtual bool CompareEqual(const Data* other_data) const = 0; +}; + +// Concrete implementation of variant data of type T. +template<typename T> +struct TypedData : public Data { + explicit TypedData(const T& value) : value_(value) {} + // NOLINTNEXTLINE(build/c++11) + explicit TypedData(T&& value) : value_(std::move(value)) {} + + const std::type_info& GetType() const override { return typeid(T); } + void CopyTo(Buffer* buffer) const override; + void MoveTo(Buffer* buffer) override; + bool IsConvertibleToInteger() const override { + return std::is_integral<T>::value || std::is_enum<T>::value; + } + intmax_t GetAsInteger() const override { + intmax_t int_val = 0; + bool converted = TryConvert(value_, &int_val); + CHECK(converted) << "Unable to convert value of type '" + << GetUndecoratedTypeName<T>() << "' to integer"; + return int_val; + } + + template<typename U> + static typename std::enable_if<dbus_utils::IsTypeSupported<U>::value>::type + AppendValueHelper(dbus::MessageWriter* writer, const U& value) { + brillo::dbus_utils::AppendValueToWriterAsVariant(writer, value); + } + template<typename U> + static typename std::enable_if<!dbus_utils::IsTypeSupported<U>::value>::type + AppendValueHelper(dbus::MessageWriter* writer, const U& value) { + LOG(FATAL) << "Type '" << GetUndecoratedTypeName<U>() + << "' is not supported by D-Bus"; + } + + void AppendToDBusMessage(dbus::MessageWriter* writer) const override { + return AppendValueHelper(writer, value_); + } + + bool CompareEqual(const Data* other_data) const override { + return EqCompare<T>(value_, + static_cast<const TypedData<T>*>(other_data)->value_); + } + + // Special methods to copy/move data of the same type + // without reallocating the buffer. + void FastAssign(const T& source) { value_ = source; } + // NOLINTNEXTLINE(build/c++11) + void FastAssign(T&& source) { value_ = std::move(source); } + + T value_; +}; + +// Buffer class that stores the contained variant data. +// To improve performance and reduce memory fragmentation, small variants +// are stored in pre-allocated memory buffers that are part of the Any class. +// If the memory requirements are larger than the set limit or the type is +// non-trivially copyable, then the contained class is allocated in a separate +// memory block and the pointer to that memory is contained within this memory +// buffer class. +class Buffer final { + public: + enum StorageType { kExternal, kContained }; + Buffer() : external_ptr_(nullptr), storage_(kExternal) {} + ~Buffer() { Clear(); } + + Buffer(const Buffer& rhs) : Buffer() { rhs.CopyTo(this); } + // NOLINTNEXTLINE(build/c++11) + Buffer(Buffer&& rhs) : Buffer() { rhs.MoveTo(this); } + Buffer& operator=(const Buffer& rhs) { + rhs.CopyTo(this); + return *this; + } + // NOLINTNEXTLINE(build/c++11) + Buffer& operator=(Buffer&& rhs) { + rhs.MoveTo(this); + return *this; + } + + // Returns the underlying pointer to contained data. Uses either the pointer + // or the raw data depending on |storage_| type. + inline Data* GetDataPtr() { + return (storage_ == kExternal) ? external_ptr_ + : reinterpret_cast<Data*>(contained_buffer_); + } + inline const Data* GetDataPtr() const { + return (storage_ == kExternal) + ? external_ptr_ + : reinterpret_cast<const Data*>(contained_buffer_); + } + + // Destroys the contained object (and frees memory if needed). + void Clear() { + Data* data = GetDataPtr(); + if (storage_ == kExternal) { + delete data; + } else { + // Call the destructor manually, since the object was constructed inline + // in the pre-allocated buffer. We still need to call the destructor + // to free any associated resources, but we can't call delete |data| here. + data->~Data(); + } + external_ptr_ = nullptr; + storage_ = kExternal; + } + + // Stores a value of type T. + template<typename T> + void Assign(T&& value) { // NOLINT(build/c++11) + using Type = typename std::decay<T>::type; + using DataType = TypedData<Type>; + Data* ptr = GetDataPtr(); + if (ptr && ptr->GetType() == typeid(Type)) { + // We assign the data to the variant container, which already + // has the data of the same type. Do fast copy/move with no memory + // reallocation. + DataType* typed_ptr = static_cast<DataType*>(ptr); + // NOLINTNEXTLINE(build/c++11) + typed_ptr->FastAssign(std::forward<T>(value)); + } else { + Clear(); + // TODO(avakulenko): [see crbug.com/379833] + // Unfortunately, GCC doesn't support std::is_trivially_copyable<T> yet, + // so using std::is_trivial instead, which is a bit more restrictive. + // Once GCC has support for is_trivially_copyable, update the following. + if (!std::is_trivial<Type>::value || + sizeof(DataType) > sizeof(contained_buffer_)) { + // If it is too big or not trivially copyable, allocate it separately. + // NOLINTNEXTLINE(build/c++11) + external_ptr_ = new DataType(std::forward<T>(value)); + storage_ = kExternal; + } else { + // Otherwise just use the pre-allocated buffer. + DataType* address = reinterpret_cast<DataType*>(contained_buffer_); + // Make sure we still call the copy/move constructor. + // Call the constructor manually by using placement 'new'. + // NOLINTNEXTLINE(build/c++11) + new (address) DataType(std::forward<T>(value)); + storage_ = kContained; + } + } + } + + // Helper methods to retrieve a reference to contained data. + // These assume that type checking has already been performed by Any + // so the type cast is valid and will succeed. + template<typename T> + const T& GetData() const { + using DataType = internal_details::TypedData<typename std::decay<T>::type>; + return static_cast<const DataType*>(GetDataPtr())->value_; + } + template<typename T> + T& GetData() { + using DataType = internal_details::TypedData<typename std::decay<T>::type>; + return static_cast<DataType*>(GetDataPtr())->value_; + } + + // Returns true if the buffer has no contained data. + bool IsEmpty() const { + return (storage_ == kExternal && external_ptr_ == nullptr); + } + + // Copies the data from the current buffer into the |destination|. + void CopyTo(Buffer* destination) const { + if (IsEmpty()) { + destination->Clear(); + } else { + GetDataPtr()->CopyTo(destination); + } + } + + // Moves the data from the current buffer into the |destination|. + void MoveTo(Buffer* destination) { + if (IsEmpty()) { + destination->Clear(); + } else { + if (storage_ == kExternal) { + destination->Clear(); + destination->storage_ = kExternal; + destination->external_ptr_ = external_ptr_; + external_ptr_ = nullptr; + } else { + GetDataPtr()->MoveTo(destination); + } + } + } + + union { + // |external_ptr_| is a pointer to a larger object allocated in + // a separate memory block. + Data* external_ptr_; + // |contained_buffer_| is a pre-allocated buffer for smaller/simple objects. + // Pre-allocate enough memory to store objects as big as "double". + unsigned char contained_buffer_[sizeof(TypedData<double>)]; + }; + // Depending on a value of |storage_|, either |external_ptr_| or + // |contained_buffer_| above is used to get a pointer to memory containing + // the variant data. + StorageType storage_; // Declare after the union to eliminate member padding. +}; + +template <typename T> +void TypedData<T>::CopyTo(Buffer* buffer) const { + buffer->Assign(value_); +} +template <typename T> +void TypedData<T>::MoveTo(Buffer* buffer) { + buffer->Assign(std::move(value_)); +} + +} // namespace internal_details + +} // namespace brillo + +#endif // LIBCHROMEOS_BRILLO_ANY_INTERNAL_IMPL_H_ |