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 LIBBRILLO_BRILLO_ANY_INTERNAL_IMPL_H_
8 #define LIBBRILLO_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
18 namespace brillo {
19
20 namespace 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.
24 template <typename From, typename To>
25 struct 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.
35 template <typename From, typename To>
36 inline typename std::enable_if<IsConvertible<From, To>::value, bool>::type
TryConvert(const From & in,To * out)37 TryConvert(const From& in, To* out) {
38 *out = static_cast<To>(in);
39 return true;
40 }
41 template <typename From, typename To>
42 inline typename std::enable_if<!IsConvertible<From, To>::value, bool>::type
TryConvert(const From &,To *)43 TryConvert(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.
64 template<typename T>
65 struct 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 // NOLINT: Allow implicit conversion from int.
IntWrapperIsEqualityComparableHelper::IntWrapper73 IntWrapper(int /* dummy */) {} // do nothing, NOLINT
74 };
75
76 // Here is an obscure trick to determine if a type U has operator==().
77 // We are providing two function prototypes for TriggerFunction. One that
78 // takes an argument of type IntWrapper (which is implicitly convertible from
79 // an int), and returns an std::false_type. This is a fall-back mechanism.
80 template<typename U>
81 static std::false_type TriggerFunction(IntWrapper dummy);
82
83 // The second overload of TriggerFunction takes an int (explicitly) and
84 // returns std::true_type. If both overloads are available, this one will be
85 // chosen when referencing it as TriggerFunction(0), since it is a better
86 // (more specific) match.
87 //
88 // However this overload is available only for types that support operator==.
89 // This is achieved by employing SFINAE mechanism inside a template function
90 // overload that refers to operator==() for two values of types U&. This is
91 // used inside decltype(), so no actual code is executed. If the types
92 // are not comparable, reference to "==" would fail and the compiler will
93 // simply ignore this overload due to SFIANE.
94 //
95 // The final little trick used here is the reliance on operator comma inside
96 // the decltype() expression. The result of the expression is always
97 // std::true_type(). The expression on the left of comma is just evaluated and
98 // discarded. If it evaluates successfully (i.e. the type has operator==), the
99 // return value of the function is set to be std::true_value. If it fails,
100 // the whole function prototype is discarded and is not available in the
101 // IsEqualityComparableHelper<T> class.
102 //
103 // Here we use std::declval<U&>() to make sure we have operator==() that takes
104 // lvalue references to type U which is not necessarily default-constructible.
105 template<typename U>
106 static decltype((std::declval<U&>() == std::declval<U&>()), std::true_type())
107 TriggerFunction(int dummy);
108
109 // Finally, use the return type of the overload of TriggerFunction that
110 // matches the argument (int) to be aliased to type |type|. If T is
111 // comparable, there will be two overloads and the more specific (int) will
112 // be chosen which returns std::true_value. If the type is non-comparable,
113 // there will be only one version of TriggerFunction available which
114 // returns std::false_value.
115 using type = decltype(TriggerFunction<T>(0));
116 };
117
118 // IsEqualityComparable<T> is simply a class that derives from either
119 // std::true_value, if type T is comparable, or from std::false_value, if the
120 // type is non-comparable. We just use |type| alias from
121 // IsEqualityComparableHelper<T> as the base class.
122 template<typename T>
123 struct IsEqualityComparable : IsEqualityComparableHelper<T>::type {};
124
125 // EqCompare() overload for non-comparable types. Always returns false.
126 template<typename T>
127 inline typename std::enable_if<!IsEqualityComparable<T>::value, bool>::type
EqCompare(const T &,const T &)128 EqCompare(const T& /* v1 */, const T& /* v2 */) {
129 return false;
130 }
131
132 // EqCompare overload for comparable types. Calls operator==(v1, v2) to compare.
133 template<typename T>
134 inline typename std::enable_if<IsEqualityComparable<T>::value, bool>::type
EqCompare(const T & v1,const T & v2)135 EqCompare(const T& v1, const T& v2) {
136 return (v1 == v2);
137 }
138
139 //////////////////////////////////////////////////////////////////////////////
140
141 class Buffer; // Forward declaration of data buffer container.
142
143 // Abstract base class for contained variant data.
144 struct Data {
~DataData145 virtual ~Data() {}
146 // Returns the type tag (name) for the contained data.
147 virtual const char* GetTypeTag() const = 0;
148 // Copies the contained data to the output |buffer|.
149 virtual void CopyTo(Buffer* buffer) const = 0;
150 // Moves the contained data to the output |buffer|.
151 virtual void MoveTo(Buffer* buffer) = 0;
152 // Checks if the contained data is an integer type (not necessarily an 'int').
153 virtual bool IsConvertibleToInteger() const = 0;
154 // Gets the contained integral value as an integer.
155 virtual intmax_t GetAsInteger() const = 0;
156 // Writes the contained value to the D-Bus message buffer.
157 virtual void AppendToDBusMessage(dbus::MessageWriter* writer) const = 0;
158 // Compares if the two data containers have objects of the same value.
159 virtual bool CompareEqual(const Data* other_data) const = 0;
160 };
161
162 // Concrete implementation of variant data of type T.
163 template<typename T>
164 struct TypedData : public Data {
TypedDataTypedData165 explicit TypedData(const T& value) : value_(value) {}
166 // NOLINTNEXTLINE(build/c++11)
TypedDataTypedData167 explicit TypedData(T&& value) : value_(std::move(value)) {}
168
GetTypeTagTypedData169 const char* GetTypeTag() const override { return brillo::GetTypeTag<T>(); }
170 void CopyTo(Buffer* buffer) const override;
171 void MoveTo(Buffer* buffer) override;
IsConvertibleToIntegerTypedData172 bool IsConvertibleToInteger() const override {
173 return std::is_integral<T>::value || std::is_enum<T>::value;
174 }
GetAsIntegerTypedData175 intmax_t GetAsInteger() const override {
176 intmax_t int_val = 0;
177 bool converted = TryConvert(value_, &int_val);
178 CHECK(converted) << "Unable to convert value of type '"
179 << GetUndecoratedTypeName<T>() << "' to integer";
180 return int_val;
181 }
182
183 template<typename U>
184 static typename std::enable_if<dbus_utils::IsTypeSupported<U>::value>::type
AppendValueHelperTypedData185 AppendValueHelper(dbus::MessageWriter* writer, const U& value) {
186 brillo::dbus_utils::AppendValueToWriterAsVariant(writer, value);
187 }
188 template<typename U>
189 static typename std::enable_if<!dbus_utils::IsTypeSupported<U>::value>::type
AppendValueHelperTypedData190 AppendValueHelper(dbus::MessageWriter* /* writer */, const U& /* value */) {
191 LOG(FATAL) << "Type '" << GetUndecoratedTypeName<U>()
192 << "' is not supported by D-Bus";
193 }
194
AppendToDBusMessageTypedData195 void AppendToDBusMessage(dbus::MessageWriter* writer) const override {
196 return AppendValueHelper(writer, value_);
197 }
198
CompareEqualTypedData199 bool CompareEqual(const Data* other_data) const override {
200 return EqCompare<T>(value_,
201 static_cast<const TypedData<T>*>(other_data)->value_);
202 }
203
204 // Special methods to copy/move data of the same type
205 // without reallocating the buffer.
FastAssignTypedData206 void FastAssign(const T& source) { value_ = source; }
207 // NOLINTNEXTLINE(build/c++11)
FastAssignTypedData208 void FastAssign(T&& source) { value_ = std::move(source); }
209
210 T value_;
211 };
212
213 // Buffer class that stores the contained variant data.
214 // To improve performance and reduce memory fragmentation, small variants
215 // are stored in pre-allocated memory buffers that are part of the Any class.
216 // If the memory requirements are larger than the set limit or the type is
217 // non-trivially copyable, then the contained class is allocated in a separate
218 // memory block and the pointer to that memory is contained within this memory
219 // buffer class.
220 class Buffer final {
221 public:
222 enum StorageType { kExternal, kContained };
Buffer()223 Buffer() : external_ptr_(nullptr), storage_(kExternal) {}
~Buffer()224 ~Buffer() { Clear(); }
225
Buffer(const Buffer & rhs)226 Buffer(const Buffer& rhs) : Buffer() { rhs.CopyTo(this); }
227 // NOLINTNEXTLINE(build/c++11)
Buffer(Buffer && rhs)228 Buffer(Buffer&& rhs) : Buffer() { rhs.MoveTo(this); }
229 Buffer& operator=(const Buffer& rhs) {
230 rhs.CopyTo(this);
231 return *this;
232 }
233 // NOLINTNEXTLINE(build/c++11)
234 Buffer& operator=(Buffer&& rhs) {
235 rhs.MoveTo(this);
236 return *this;
237 }
238
239 // Returns the underlying pointer to contained data. Uses either the pointer
240 // or the raw data depending on |storage_| type.
GetDataPtr()241 inline Data* GetDataPtr() {
242 return (storage_ == kExternal) ? external_ptr_
243 : reinterpret_cast<Data*>(contained_buffer_);
244 }
GetDataPtr()245 inline const Data* GetDataPtr() const {
246 return (storage_ == kExternal)
247 ? external_ptr_
248 : reinterpret_cast<const Data*>(contained_buffer_);
249 }
250
251 // Destroys the contained object (and frees memory if needed).
Clear()252 void Clear() {
253 Data* data = GetDataPtr();
254 if (storage_ == kExternal) {
255 delete data;
256 } else {
257 // Call the destructor manually, since the object was constructed inline
258 // in the pre-allocated buffer. We still need to call the destructor
259 // to free any associated resources, but we can't call delete |data| here.
260 data->~Data();
261 }
262 external_ptr_ = nullptr;
263 storage_ = kExternal;
264 }
265
266 // Stores a value of type T.
267 template<typename T>
Assign(T && value)268 void Assign(T&& value) { // NOLINT(build/c++11)
269 using Type = typename std::decay<T>::type;
270 using DataType = TypedData<Type>;
271 Data* ptr = GetDataPtr();
272 if (ptr && strcmp(ptr->GetTypeTag(), GetTypeTag<Type>()) == 0) {
273 // We assign the data to the variant container, which already
274 // has the data of the same type. Do fast copy/move with no memory
275 // reallocation.
276 DataType* typed_ptr = static_cast<DataType*>(ptr);
277 // NOLINTNEXTLINE(build/c++11)
278 typed_ptr->FastAssign(std::forward<T>(value));
279 } else {
280 Clear();
281 // TODO(avakulenko): [see crbug.com/379833]
282 // Unfortunately, GCC doesn't support std::is_trivially_copyable<T> yet,
283 // so using std::is_trivial instead, which is a bit more restrictive.
284 // Once GCC has support for is_trivially_copyable, update the following.
285 if (!std::is_trivial<Type>::value ||
286 sizeof(DataType) > sizeof(contained_buffer_)) {
287 // If it is too big or not trivially copyable, allocate it separately.
288 // NOLINTNEXTLINE(build/c++11)
289 external_ptr_ = new DataType(std::forward<T>(value));
290 storage_ = kExternal;
291 } else {
292 // Otherwise just use the pre-allocated buffer.
293 DataType* address = reinterpret_cast<DataType*>(contained_buffer_);
294 // Make sure we still call the copy/move constructor.
295 // Call the constructor manually by using placement 'new'.
296 // NOLINTNEXTLINE(build/c++11)
297 new (address) DataType(std::forward<T>(value));
298 storage_ = kContained;
299 }
300 }
301 }
302
303 // Helper methods to retrieve a reference to contained data.
304 // These assume that type checking has already been performed by Any
305 // so the type cast is valid and will succeed.
306 template<typename T>
GetData()307 const T& GetData() const {
308 using DataType = internal_details::TypedData<typename std::decay<T>::type>;
309 return static_cast<const DataType*>(GetDataPtr())->value_;
310 }
311 template<typename T>
GetData()312 T& GetData() {
313 using DataType = internal_details::TypedData<typename std::decay<T>::type>;
314 return static_cast<DataType*>(GetDataPtr())->value_;
315 }
316
317 // Returns true if the buffer has no contained data.
IsEmpty()318 bool IsEmpty() const {
319 return (storage_ == kExternal && external_ptr_ == nullptr);
320 }
321
322 // Copies the data from the current buffer into the |destination|.
CopyTo(Buffer * destination)323 void CopyTo(Buffer* destination) const {
324 if (IsEmpty()) {
325 destination->Clear();
326 } else {
327 GetDataPtr()->CopyTo(destination);
328 }
329 }
330
331 // Moves the data from the current buffer into the |destination|.
MoveTo(Buffer * destination)332 void MoveTo(Buffer* destination) {
333 if (IsEmpty()) {
334 destination->Clear();
335 } else {
336 if (storage_ == kExternal) {
337 destination->Clear();
338 destination->storage_ = kExternal;
339 destination->external_ptr_ = external_ptr_;
340 external_ptr_ = nullptr;
341 } else {
342 GetDataPtr()->MoveTo(destination);
343 }
344 }
345 }
346
347 union {
348 // |external_ptr_| is a pointer to a larger object allocated in
349 // a separate memory block.
350 Data* external_ptr_;
351 // |contained_buffer_| is a pre-allocated buffer for smaller/simple objects.
352 // Pre-allocate enough memory to store objects as big as "double".
353 unsigned char contained_buffer_[sizeof(TypedData<double>)];
354 };
355 // Depending on a value of |storage_|, either |external_ptr_| or
356 // |contained_buffer_| above is used to get a pointer to memory containing
357 // the variant data.
358 StorageType storage_; // Declare after the union to eliminate member padding.
359 };
360
361 template <typename T>
CopyTo(Buffer * buffer)362 void TypedData<T>::CopyTo(Buffer* buffer) const {
363 buffer->Assign(value_);
364 }
365 template <typename T>
MoveTo(Buffer * buffer)366 void TypedData<T>::MoveTo(Buffer* buffer) {
367 buffer->Assign(std::move(value_));
368 }
369
370 } // namespace internal_details
371
372 } // namespace brillo
373
374 #endif // LIBBRILLO_BRILLO_ANY_INTERNAL_IMPL_H_
375