any_internal_impl.h revision 9ed0cab99f18acb3570a35e9408f24355f6b8324
1// Copyright 2014 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// Internal implementation of brillo::Any class.
6
7#ifndef LIBCHROMEOS_BRILLO_ANY_INTERNAL_IMPL_H_
8#define LIBCHROMEOS_BRILLO_ANY_INTERNAL_IMPL_H_
9
10#include <type_traits>
11#include <typeinfo>
12#include <utility>
13
14#include <base/logging.h>
15#include <brillo/dbus/data_serialization.h>
16#include <brillo/type_name_undecorate.h>
17
18namespace brillo {
19
20namespace internal_details {
21
22// An extension to std::is_convertible to allow conversion from an enum to
23// an integral type which std::is_convertible does not indicate as supported.
24template <typename From, typename To>
25struct IsConvertible
26    : public std::integral_constant<
27          bool,
28          std::is_convertible<From, To>::value ||
29              (std::is_enum<From>::value && std::is_integral<To>::value)> {};
30
31// TryConvert is a helper function that does a safe compile-time conditional
32// type cast between data types that may not be always convertible.
33// From and To are the source and destination types.
34// The function returns true if conversion was possible/successful.
35template <typename From, typename To>
36inline typename std::enable_if<IsConvertible<From, To>::value, bool>::type
37TryConvert(const From& in, To* out) {
38  *out = static_cast<To>(in);
39  return true;
40}
41template <typename From, typename To>
42inline typename std::enable_if<!IsConvertible<From, To>::value, bool>::type
43TryConvert(const From& in, To* out) {
44  return false;
45}
46
47//////////////////////////////////////////////////////////////////////////////
48// Provide a way to compare values of unspecified types without compiler errors
49// when no operator==() is provided for a given type. This is important to
50// allow Any class to have operator==(), yet still allowing arbitrary types
51// (not necessarily comparable) to be placed inside Any without resulting in
52// compile-time error.
53//
54// We achieve this in two ways. First, we provide a IsEqualityComparable<T>
55// class that can be used in compile-time conditions to determine if there is
56// operator==() defined that takes values of type T (or which can be implicitly
57// converted to type T). Secondly, this allows us to specialize a helper
58// compare function EqCompare<T>(v1, v2) to use operator==() for types that
59// are comparable, and just return false for those that are not.
60//
61// IsEqualityComparableHelper<T> is a helper class for implementing an
62// an STL-compatible IsEqualityComparable<T> containing a Boolean member |value|
63// which evaluates to true for comparable types and false otherwise.
64template<typename T>
65struct IsEqualityComparableHelper {
66  struct IntWrapper {
67    // A special structure that provides a constructor that takes an int.
68    // This way, an int argument passed to a function will be favored over
69    // IntWrapper when both overloads are provided.
70    // Also this constructor must NOT be explicit.
71    // NOLINTNEXTLINE(runtime/explicit)
72    IntWrapper(int dummy) {}  // do nothing
73  };
74
75  // Here is an obscure trick to determine if a type U has operator==().
76  // We are providing two function prototypes for TriggerFunction. One that
77  // takes an argument of type IntWrapper (which is implicitly convertible from
78  // an int), and returns an std::false_type. This is a fall-back mechanism.
79  template<typename U>
80  static std::false_type TriggerFunction(IntWrapper dummy);
81
82  // The second overload of TriggerFunction takes an int (explicitly) and
83  // returns std::true_type. If both overloads are available, this one will be
84  // chosen when referencing it as TriggerFunction(0), since it is a better
85  // (more specific) match.
86  //
87  // However this overload is available only for types that support operator==.
88  // This is achieved by employing SFINAE mechanism inside a template function
89  // overload that refers to operator==() for two values of types U&. This is
90  // used inside decltype(), so no actual code is executed. If the types
91  // are not comparable, reference to "==" would fail and the compiler will
92  // simply ignore this overload due to SFIANE.
93  //
94  // The final little trick used here is the reliance on operator comma inside
95  // the decltype() expression. The result of the expression is always
96  // std::true_type(). The expression on the left of comma is just evaluated and
97  // discarded. If it evaluates successfully (i.e. the type has operator==), the
98  // return value of the function is set to be std::true_value. If it fails,
99  // the whole function prototype is discarded and is not available in the
100  // IsEqualityComparableHelper<T> class.
101  //
102  // Here we use std::declval<U&>() to make sure we have operator==() that takes
103  // lvalue references to type U which is not necessarily default-constructible.
104  template<typename U>
105  static decltype((std::declval<U&>() == std::declval<U&>()), std::true_type())
106  TriggerFunction(int dummy);
107
108  // Finally, use the return type of the overload of TriggerFunction that
109  // matches the argument (int) to be aliased to type |type|. If T is
110  // comparable, there will be two overloads and the more specific (int) will
111  // be chosen which returns std::true_value. If the type is non-comparable,
112  // there will be only one version of TriggerFunction available which
113  // returns std::false_value.
114  using type = decltype(TriggerFunction<T>(0));
115};
116
117// IsEqualityComparable<T> is simply a class that derives from either
118// std::true_value, if type T is comparable, or from std::false_value, if the
119// type is non-comparable. We just use |type| alias from
120// IsEqualityComparableHelper<T> as the base class.
121template<typename T>
122struct IsEqualityComparable : IsEqualityComparableHelper<T>::type {};
123
124// EqCompare() overload for non-comparable types. Always returns false.
125template<typename T>
126inline typename std::enable_if<!IsEqualityComparable<T>::value, bool>::type
127EqCompare(const T& v1, const T& v2) {
128  return false;
129}
130
131// EqCompare overload for comparable types. Calls operator==(v1, v2) to compare.
132template<typename T>
133inline typename std::enable_if<IsEqualityComparable<T>::value, bool>::type
134EqCompare(const T& v1, const T& v2) {
135  return (v1 == v2);
136}
137
138//////////////////////////////////////////////////////////////////////////////
139
140class Buffer;  // Forward declaration of data buffer container.
141
142// Abstract base class for contained variant data.
143struct Data {
144  virtual ~Data() {}
145  // Returns the type information for the contained data.
146  virtual const std::type_info& GetType() const = 0;
147  // Copies the contained data to the output |buffer|.
148  virtual void CopyTo(Buffer* buffer) const = 0;
149  // Moves the contained data to the output |buffer|.
150  virtual void MoveTo(Buffer* buffer) = 0;
151  // Checks if the contained data is an integer type (not necessarily an 'int').
152  virtual bool IsConvertibleToInteger() const = 0;
153  // Gets the contained integral value as an integer.
154  virtual intmax_t GetAsInteger() const = 0;
155  // Writes the contained value to the D-Bus message buffer.
156  virtual void AppendToDBusMessage(dbus::MessageWriter* writer) const = 0;
157  // Compares if the two data containers have objects of the same value.
158  virtual bool CompareEqual(const Data* other_data) const = 0;
159};
160
161// Concrete implementation of variant data of type T.
162template<typename T>
163struct TypedData : public Data {
164  explicit TypedData(const T& value) : value_(value) {}
165  // NOLINTNEXTLINE(build/c++11)
166  explicit TypedData(T&& value) : value_(std::move(value)) {}
167
168  const std::type_info& GetType() const override { return typeid(T); }
169  void CopyTo(Buffer* buffer) const override;
170  void MoveTo(Buffer* buffer) override;
171  bool IsConvertibleToInteger() const override {
172    return std::is_integral<T>::value || std::is_enum<T>::value;
173  }
174  intmax_t GetAsInteger() const override {
175    intmax_t int_val = 0;
176    bool converted = TryConvert(value_, &int_val);
177    CHECK(converted) << "Unable to convert value of type '"
178                     << GetUndecoratedTypeName<T>() << "' to integer";
179    return int_val;
180  }
181
182  template<typename U>
183  static typename std::enable_if<dbus_utils::IsTypeSupported<U>::value>::type
184  AppendValueHelper(dbus::MessageWriter* writer, const U& value) {
185    brillo::dbus_utils::AppendValueToWriterAsVariant(writer, value);
186  }
187  template<typename U>
188  static typename std::enable_if<!dbus_utils::IsTypeSupported<U>::value>::type
189  AppendValueHelper(dbus::MessageWriter* writer, const U& value) {
190    LOG(FATAL) << "Type '" << GetUndecoratedTypeName<U>()
191               << "' is not supported by D-Bus";
192  }
193
194  void AppendToDBusMessage(dbus::MessageWriter* writer) const override {
195    return AppendValueHelper(writer, value_);
196  }
197
198  bool CompareEqual(const Data* other_data) const override {
199    return EqCompare<T>(value_,
200                        static_cast<const TypedData<T>*>(other_data)->value_);
201  }
202
203  // Special methods to copy/move data of the same type
204  // without reallocating the buffer.
205  void FastAssign(const T& source) { value_ = source; }
206  // NOLINTNEXTLINE(build/c++11)
207  void FastAssign(T&& source) { value_ = std::move(source); }
208
209  T value_;
210};
211
212// Buffer class that stores the contained variant data.
213// To improve performance and reduce memory fragmentation, small variants
214// are stored in pre-allocated memory buffers that are part of the Any class.
215// If the memory requirements are larger than the set limit or the type is
216// non-trivially copyable, then the contained class is allocated in a separate
217// memory block and the pointer to that memory is contained within this memory
218// buffer class.
219class Buffer final {
220 public:
221  enum StorageType { kExternal, kContained };
222  Buffer() : external_ptr_(nullptr), storage_(kExternal) {}
223  ~Buffer() { Clear(); }
224
225  Buffer(const Buffer& rhs) : Buffer() { rhs.CopyTo(this); }
226  // NOLINTNEXTLINE(build/c++11)
227  Buffer(Buffer&& rhs) : Buffer() { rhs.MoveTo(this); }
228  Buffer& operator=(const Buffer& rhs) {
229    rhs.CopyTo(this);
230    return *this;
231  }
232  // NOLINTNEXTLINE(build/c++11)
233  Buffer& operator=(Buffer&& rhs) {
234    rhs.MoveTo(this);
235    return *this;
236  }
237
238  // Returns the underlying pointer to contained data. Uses either the pointer
239  // or the raw data depending on |storage_| type.
240  inline Data* GetDataPtr() {
241    return (storage_ == kExternal) ? external_ptr_
242                                   : reinterpret_cast<Data*>(contained_buffer_);
243  }
244  inline const Data* GetDataPtr() const {
245    return (storage_ == kExternal)
246               ? external_ptr_
247               : reinterpret_cast<const Data*>(contained_buffer_);
248  }
249
250  // Destroys the contained object (and frees memory if needed).
251  void Clear() {
252    Data* data = GetDataPtr();
253    if (storage_ == kExternal) {
254      delete data;
255    } else {
256      // Call the destructor manually, since the object was constructed inline
257      // in the pre-allocated buffer. We still need to call the destructor
258      // to free any associated resources, but we can't call delete |data| here.
259      data->~Data();
260    }
261    external_ptr_ = nullptr;
262    storage_ = kExternal;
263  }
264
265  // Stores a value of type T.
266  template<typename T>
267  void Assign(T&& value) {  // NOLINT(build/c++11)
268    using Type = typename std::decay<T>::type;
269    using DataType = TypedData<Type>;
270    Data* ptr = GetDataPtr();
271    if (ptr && ptr->GetType() == typeid(Type)) {
272      // We assign the data to the variant container, which already
273      // has the data of the same type. Do fast copy/move with no memory
274      // reallocation.
275      DataType* typed_ptr = static_cast<DataType*>(ptr);
276      // NOLINTNEXTLINE(build/c++11)
277      typed_ptr->FastAssign(std::forward<T>(value));
278    } else {
279      Clear();
280      // TODO(avakulenko): [see crbug.com/379833]
281      // Unfortunately, GCC doesn't support std::is_trivially_copyable<T> yet,
282      // so using std::is_trivial instead, which is a bit more restrictive.
283      // Once GCC has support for is_trivially_copyable, update the following.
284      if (!std::is_trivial<Type>::value ||
285          sizeof(DataType) > sizeof(contained_buffer_)) {
286        // If it is too big or not trivially copyable, allocate it separately.
287        // NOLINTNEXTLINE(build/c++11)
288        external_ptr_ = new DataType(std::forward<T>(value));
289        storage_ = kExternal;
290      } else {
291        // Otherwise just use the pre-allocated buffer.
292        DataType* address = reinterpret_cast<DataType*>(contained_buffer_);
293        // Make sure we still call the copy/move constructor.
294        // Call the constructor manually by using placement 'new'.
295        // NOLINTNEXTLINE(build/c++11)
296        new (address) DataType(std::forward<T>(value));
297        storage_ = kContained;
298      }
299    }
300  }
301
302  // Helper methods to retrieve a reference to contained data.
303  // These assume that type checking has already been performed by Any
304  // so the type cast is valid and will succeed.
305  template<typename T>
306  const T& GetData() const {
307    using DataType = internal_details::TypedData<typename std::decay<T>::type>;
308    return static_cast<const DataType*>(GetDataPtr())->value_;
309  }
310  template<typename T>
311  T& GetData() {
312    using DataType = internal_details::TypedData<typename std::decay<T>::type>;
313    return static_cast<DataType*>(GetDataPtr())->value_;
314  }
315
316  // Returns true if the buffer has no contained data.
317  bool IsEmpty() const {
318    return (storage_ == kExternal && external_ptr_ == nullptr);
319  }
320
321  // Copies the data from the current buffer into the |destination|.
322  void CopyTo(Buffer* destination) const {
323    if (IsEmpty()) {
324      destination->Clear();
325    } else {
326      GetDataPtr()->CopyTo(destination);
327    }
328  }
329
330  // Moves the data from the current buffer into the |destination|.
331  void MoveTo(Buffer* destination) {
332    if (IsEmpty()) {
333      destination->Clear();
334    } else {
335      if (storage_ == kExternal) {
336        destination->Clear();
337        destination->storage_ = kExternal;
338        destination->external_ptr_ = external_ptr_;
339        external_ptr_ = nullptr;
340      } else {
341        GetDataPtr()->MoveTo(destination);
342      }
343    }
344  }
345
346  union {
347    // |external_ptr_| is a pointer to a larger object allocated in
348    // a separate memory block.
349    Data* external_ptr_;
350    // |contained_buffer_| is a pre-allocated buffer for smaller/simple objects.
351    // Pre-allocate enough memory to store objects as big as "double".
352    unsigned char contained_buffer_[sizeof(TypedData<double>)];
353  };
354  // Depending on a value of |storage_|, either |external_ptr_| or
355  // |contained_buffer_| above is used to get a pointer to memory containing
356  // the variant data.
357  StorageType storage_;  // Declare after the union to eliminate member padding.
358};
359
360template <typename T>
361void TypedData<T>::CopyTo(Buffer* buffer) const {
362  buffer->Assign(value_);
363}
364template <typename T>
365void TypedData<T>::MoveTo(Buffer* buffer) {
366  buffer->Assign(std::move(value_));
367}
368
369}  // namespace internal_details
370
371}  // namespace brillo
372
373#endif  // LIBCHROMEOS_BRILLO_ANY_INTERNAL_IMPL_H_
374