summaryrefslogtreecommitdiff
path: root/brillo/any_internal_impl.h
diff options
context:
space:
mode:
Diffstat (limited to 'brillo/any_internal_impl.h')
-rw-r--r--brillo/any_internal_impl.h373
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_