// 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 LIBBRILLO_BRILLO_ANY_INTERNAL_IMPL_H_ #define LIBBRILLO_BRILLO_ANY_INTERNAL_IMPL_H_ #include #include #include #include #include #include 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 struct IsConvertible : public std::integral_constant< bool, std::is_convertible::value || (std::is_enum::value && std::is_integral::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 inline typename std::enable_if::value, bool>::type TryConvert(const From& in, To* out) { *out = static_cast(in); return true; } template inline typename std::enable_if::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 // 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(v1, v2) to use operator==() for types that // are comparable, and just return false for those that are not. // // IsEqualityComparableHelper is a helper class for implementing an // an STL-compatible IsEqualityComparable containing a Boolean member |value| // which evaluates to true for comparable types and false otherwise. template 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) // NOLINT: Allow implicit conversion from int. IntWrapper(int /* dummy */) {} // do nothing, NOLINT }; // 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 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 class. // // Here we use std::declval() to make sure we have operator==() that takes // lvalue references to type U which is not necessarily default-constructible. template static decltype((std::declval() == std::declval()), 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(0)); }; // IsEqualityComparable 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 as the base class. template struct IsEqualityComparable : IsEqualityComparableHelper::type {}; // EqCompare() overload for non-comparable types. Always returns false. template inline typename std::enable_if::value, bool>::type EqCompare(const T& /* v1 */, const T& /* v2 */) { return false; } // EqCompare overload for comparable types. Calls operator==(v1, v2) to compare. template inline typename std::enable_if::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 tag (name) for the contained data. virtual const char* GetTypeTag() 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 struct TypedData : public Data { explicit TypedData(const T& value) : value_(value) {} // NOLINTNEXTLINE(build/c++11) explicit TypedData(T&& value) : value_(std::move(value)) {} const char* GetTypeTag() const override { return brillo::GetTypeTag(); } void CopyTo(Buffer* buffer) const override; void MoveTo(Buffer* buffer) override; bool IsConvertibleToInteger() const override { return std::is_integral::value || std::is_enum::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() << "' to integer"; return int_val; } template static typename std::enable_if::value>::type AppendValueHelper(dbus::MessageWriter* writer, const U& value) { brillo::dbus_utils::AppendValueToWriterAsVariant(writer, value); } template static typename std::enable_if::value>::type AppendValueHelper(dbus::MessageWriter* /* writer */, const U& /* value */) { LOG(FATAL) << "Type '" << GetUndecoratedTypeName() << "' 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(value_, static_cast*>(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(contained_buffer_); } inline const Data* GetDataPtr() const { return (storage_ == kExternal) ? external_ptr_ : reinterpret_cast(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 void Assign(T&& value) { // NOLINT(build/c++11) using Type = typename std::decay::type; using DataType = TypedData; Data* ptr = GetDataPtr(); if (ptr && strcmp(ptr->GetTypeTag(), GetTypeTag()) == 0) { // 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(ptr); // NOLINTNEXTLINE(build/c++11) typed_ptr->FastAssign(std::forward(value)); } else { Clear(); // TODO(avakulenko): [see crbug.com/379833] // Unfortunately, GCC doesn't support std::is_trivially_copyable 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::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(value)); storage_ = kExternal; } else { // Otherwise just use the pre-allocated buffer. DataType* address = reinterpret_cast(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(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 const T& GetData() const { using DataType = internal_details::TypedData::type>; return static_cast(GetDataPtr())->value_; } template T& GetData() { using DataType = internal_details::TypedData::type>; return static_cast(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)]; }; // 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 void TypedData::CopyTo(Buffer* buffer) const { buffer->Assign(value_); } template void TypedData::MoveTo(Buffer* buffer) { buffer->Assign(std::move(value_)); } } // namespace internal_details } // namespace brillo #endif // LIBBRILLO_BRILLO_ANY_INTERNAL_IMPL_H_