GTIRB  v2.3.1
GrammaTech Intermediate Representation for Binaries: C++ API
AuxData.hpp
Go to the documentation of this file.
1 //===- AuxData.hpp -----------------------------------------------*- C++-*-===//
2 //
3 // Copyright (C) 2020 GrammaTech, Inc.
4 //
5 // This code is licensed under the MIT license. See the LICENSE file in the
6 // project root for license terms.
7 //
8 // This project is sponsored by the Office of Naval Research, One Liberty
9 // Center, 875 N. Randolph Street, Arlington, VA 22203 under contract #
10 // N68335-17-C-0700. The content of the information does not necessarily
11 // reflect the position or policy of the Government and no official
12 // endorsement should be inferred.
13 //
14 //===----------------------------------------------------------------------===//
15 #ifndef GTIRB_AUXDATA_H
16 #define GTIRB_AUXDATA_H
17 
18 #include <gtirb/Addr.hpp>
19 #include <gtirb/Node.hpp>
20 #include <gtirb/Offset.hpp>
21 #include <boost/endian/conversion.hpp>
22 #include <deque>
23 #include <iostream>
24 #include <list>
25 #include <map>
26 #include <set>
27 #include <string>
28 #include <type_traits>
29 #include <typeinfo>
30 #include <unordered_map>
31 #include <unordered_set>
32 #include <variant>
33 #include <vector>
34 
39 
40 namespace gtirb {
41 namespace proto {
42 class AuxData;
43 } // namespace proto
44 class Context;
45 
101 
103 
110 template <class T> struct is_sequence : std::false_type {};
111 
113 template <class T> struct is_sequence<std::vector<T>> : std::true_type {};
114 template <class T> struct is_sequence<std::list<T>> : std::true_type {};
115 template <class T> struct is_sequence<std::deque<T>> : std::true_type {};
117 
123 template <class T> struct is_mapping : std::false_type {};
125 template <class T, class U>
126 struct is_mapping<std::map<T, U>> : std::true_type {};
127 template <class T, class U>
128 struct is_mapping<std::unordered_map<T, U>> : std::true_type {};
129 // Explicitly disable multimaps. Because they can contain multiple values for
130 // a given key, they can't be used interchangeably with maps.
131 template <class T, class U>
132 struct is_mapping<std::multimap<T, U>> : std::false_type {};
133 template <class T, class U>
134 struct is_mapping<std::unordered_multimap<T, U>> : std::false_type {};
136 
142 template <class T> struct is_set : std::false_type {};
144 template <class... Args> struct is_set<std::set<Args...>> : std::true_type {};
145 template <class... Args>
146 struct is_set<std::unordered_set<Args...>> : std::true_type {};
147 // Explicitly disable multisets. Because they can contain multiple equivalent
148 // values, they can't be used interchangeably with sets.
149 template <class... Args>
150 struct is_set<std::multiset<Args...>> : std::false_type {};
151 template <class... Args>
152 struct is_set<std::unordered_multiset<Args...>> : std::false_type {};
154 
160 template <class T> struct is_tuple : std::false_type {};
162 template <class... Args>
163 struct is_tuple<std::tuple<Args...>> : std::true_type {};
164 
165 template <class... Args>
166 struct is_tuple<std::pair<Args...>> : std::true_type {};
167 
168 // Utility class for serializing AuxData.
169 class ToByteRange {
170 public:
171  explicit ToByteRange(std::string& Bytes) : It(std::back_inserter(Bytes)) {}
172 
173  void write(std::byte Byte) { *It = static_cast<char>(Byte); }
174 
175 private:
176  std::back_insert_iterator<std::string> It;
177 };
178 
179 // Utility class for deserializing AuxData.
180 class FromByteRange {
181 public:
182  explicit FromByteRange(const std::string& Bytes)
183  : Curr(Bytes.begin()), End(Bytes.end()) {}
184 
185  bool read(std::byte& Byte) {
186  if (Curr == End)
187  return false;
188 
189  Byte = std::byte(*Curr);
190  ++Curr;
191  return true;
192  }
193 
194  uint64_t remainingBytesToRead() const {
195  return static_cast<uint64_t>(std::distance(Curr, End));
196  }
197 
198 private:
199  std::string::const_iterator Curr;
200  std::string::const_iterator End;
201 };
202 
204 
211 template <class T, class Enable = void> struct auxdata_traits {
216  static void toBytes(const T& Object, ToByteRange& TBR) = delete;
217 
224  static bool fromBytes(T& Object, FromByteRange& FBR) = delete;
225 
232  static std::string type_name() = delete;
233 };
234 
236 template <class... Ts> struct TypeId {};
237 
238 template <class T>
239 struct is_endian_type
240  : std::integral_constant<bool, std::is_class_v<T> ||
241  (std::is_integral_v<T> &&
242  !std::is_same_v<T, bool>)> {};
243 
244 template <typename T, typename Enable = void> struct default_serialization {};
245 
246 // Serialize and deserialize by copying the object representation directly.
247 template <typename T>
248 struct default_serialization<
249  T, typename std::enable_if_t<is_endian_type<T>::value ||
250  std::is_floating_point<T>::value ||
251  std::is_same<T, bool>::value>> {
252  static void toBytes(const T& object, ToByteRange& TBR) {
253  // Store as little-endian.
254  T ordered = object;
255 
256  if constexpr (!std::is_floating_point<T>::value &&
257  !std::is_same<T, bool>::value) {
258  // Do not reorder floating point or boolean values
259  boost::endian::conditional_reverse_inplace<boost::endian::order::little,
260  boost::endian::order::native>(
261  ordered);
262  }
263  auto srcBytes_begin = reinterpret_cast<const std::byte*>(&ordered);
264  auto srcBytes_end = reinterpret_cast<const std::byte*>(&ordered + 1);
265  std::for_each(srcBytes_begin, srcBytes_end, [&](auto b) { TBR.write(b); });
266  }
267 
268  static bool fromBytes(T& object, FromByteRange& FBR) {
269  auto dest_begin = reinterpret_cast<std::byte*>(&object);
270  auto dest_end = reinterpret_cast<std::byte*>(&object + 1);
271  bool Success = true;
272  std::for_each(dest_begin, dest_end, [&](auto& b) {
273  if (!FBR.read(b))
274  Success = false;
275  });
276  if (!Success) {
277  return false;
278  }
279 
280  // Data stored as little-endian.
281  if constexpr (!std::is_floating_point<T>::value &&
282  !std::is_same<T, bool>::value) {
283  boost::endian::conditional_reverse_inplace<boost::endian::order::little,
284  boost::endian::order::native>(
285  object);
286  }
287 
288  return true;
289  }
290 };
291 
292 template <>
293 struct auxdata_traits<std::byte> : default_serialization<std::byte> {
294  static std::string type_name() { return "byte"; }
295 
296  static void toBytes(std::byte object, ToByteRange& TBR) { TBR.write(object); }
297 
298  static bool fromBytes(std::byte& object, FromByteRange& FBR) {
299  return FBR.read(object);
300  }
301 };
302 
303 template <> struct auxdata_traits<Addr> : default_serialization<Addr> {
304  static std::string type_name() { return "Addr"; }
305 };
306 
307 template <> struct auxdata_traits<UUID> : default_serialization<UUID> {
308  static std::string type_name() { return "UUID"; }
309 };
310 
311 template <class T>
312 struct auxdata_traits<T, typename std::enable_if_t<std::is_integral<T>::value &&
313  std::is_signed<T>::value>>
314  : default_serialization<T> {
315  static std::string type_name() {
316  return "int" + std::to_string(8 * sizeof(T)) + "_t";
317  }
318 };
319 
320 template <class T>
321 struct auxdata_traits<T, typename std::enable_if_t<std::is_integral<T>::value &&
322  std::is_unsigned<T>::value>>
323  : default_serialization<T> {
324  static std::string type_name() {
325  return "uint" + std::to_string(8 * sizeof(T)) + "_t";
326  }
327 };
328 
329 template <> struct auxdata_traits<bool> : default_serialization<bool> {
330  static std::string type_name() { return "bool"; }
331 };
332 
333 template <> struct auxdata_traits<float> : default_serialization<float> {
334  static std::string type_name() { return "float"; }
335 };
336 
337 template <> struct auxdata_traits<double> : default_serialization<double> {
338  static std::string type_name() { return "double"; }
339 };
340 
341 template <> struct auxdata_traits<std::string> {
342  static std::string type_name() { return "string"; }
343 
344  static void toBytes(const std::string& Object, ToByteRange& TBR) {
345  auxdata_traits<uint64_t>::toBytes(Object.size(), TBR);
346  std::for_each(Object.begin(), Object.end(),
347  [&](auto& elt) { auxdata_traits<char>::toBytes(elt, TBR); });
348  }
349 
350  static bool fromBytes(std::string& Object, FromByteRange& FBR) {
351  uint64_t Count;
352  if (!auxdata_traits<uint64_t>::fromBytes(Count, FBR))
353  return false;
354 
355  if (Count > FBR.remainingBytesToRead())
356  return false;
357 
358  Object.resize(Count);
359  bool Success = true;
360  std::for_each(Object.begin(), Object.end(), [&](auto& elt) {
361  if (!auxdata_traits<char>::fromBytes(elt, FBR))
362  Success = false;
363  });
364 
365  return Success;
366  }
367 };
368 
369 template <> struct auxdata_traits<Offset> {
370  static std::string type_name() { return "Offset"; }
371 
372  static void toBytes(const Offset& Object, ToByteRange& TBR) {
373  auxdata_traits<UUID>::toBytes(Object.ElementId, TBR);
374  auxdata_traits<uint64_t>::toBytes(Object.Displacement, TBR);
375  }
376 
377  static bool fromBytes(Offset& Object, FromByteRange& FBR) {
378  if (!auxdata_traits<UUID>::fromBytes(Object.ElementId, FBR))
379  return false;
380  return auxdata_traits<uint64_t>::fromBytes(Object.Displacement, FBR);
381  }
382 };
383 
384 template <class T>
385 struct auxdata_traits<T, typename std::enable_if_t<is_sequence<T>::value>> {
386  static std::string type_name() {
387  return "sequence<" + TypeId<typename T::value_type>::value() + ">";
388  }
389 
390  static void toBytes(const T& Object, ToByteRange& TBR) {
391  auxdata_traits<uint64_t>::toBytes(Object.size(), TBR);
392  std::for_each(Object.begin(), Object.end(), [&](const auto& Elt) {
393  auxdata_traits<typename T::value_type>::toBytes(Elt, TBR);
394  });
395  }
396 
397  static bool fromBytes(T& Object, FromByteRange& FBR) {
398  uint64_t Count;
399  if (!auxdata_traits<uint64_t>::fromBytes(Count, FBR))
400  return false;
401 
402  if (Count > FBR.remainingBytesToRead())
403  return false;
404 
405  Object.resize(Count);
406  bool Success = true;
407  std::for_each(Object.begin(), Object.end(), [&](auto& Elt) {
408  if (!auxdata_traits<typename T::value_type>::fromBytes(Elt, FBR))
409  Success = false;
410  });
411 
412  return Success;
413  }
414 };
415 
416 template <class T>
417 struct auxdata_traits<T, typename std::enable_if_t<is_set<T>::value>> {
418  static std::string type_name() {
419  return "set<" + TypeId<typename T::value_type>::value() + ">";
420  }
421 
422  static void toBytes(const T& Object, ToByteRange& TBR) {
423  auxdata_traits<uint64_t>::toBytes(Object.size(), TBR);
424  for (const auto& Elt : Object)
426  }
427 
428  static bool fromBytes(T& Object, FromByteRange& FBR) {
429  uint64_t Count;
430  if (!auxdata_traits<uint64_t>::fromBytes(Count, FBR))
431  return false;
432 
433  if (Count > FBR.remainingBytesToRead())
434  return false;
435 
436  for (uint64_t i = 0; i < Count; i++) {
437  typename T::value_type V;
438  if (!auxdata_traits<decltype(V)>::fromBytes(V, FBR))
439  return false;
440  Object.emplace(std::move(V));
441  }
442 
443  return true;
444  }
445 };
446 
447 template <class T>
448 struct auxdata_traits<T, typename std::enable_if_t<is_mapping<T>::value>> {
449  static std::string type_name() {
450  return "mapping<" +
451  TypeId<typename T::key_type, typename T::mapped_type>::value() + ">";
452  }
453 
454  static void toBytes(const T& Object, ToByteRange& TBR) {
455  auxdata_traits<uint64_t>::toBytes(Object.size(), TBR);
456  std::for_each(Object.begin(), Object.end(), [&](const auto& Elt) {
457  auxdata_traits<typename T::key_type>::toBytes(Elt.first, TBR);
458  auxdata_traits<typename T::mapped_type>::toBytes(Elt.second, TBR);
459  });
460  }
461 
462  static bool fromBytes(T& Object, FromByteRange& FBR) {
463  uint64_t Count;
464  if (!auxdata_traits<uint64_t>::fromBytes(Count, FBR))
465  return false;
466 
467  if (Count > FBR.remainingBytesToRead())
468  return false;
469 
470  for (uint64_t i = 0; i < Count; i++) {
471  typename T::key_type K;
472  if (!auxdata_traits<decltype(K)>::fromBytes(K, FBR))
473  return false;
474  typename T::mapped_type V;
475  if (!auxdata_traits<decltype(V)>::fromBytes(V, FBR))
476  return false;
477  Object.emplace(std::move(K), std::move(V));
478  }
479  return true;
480  }
481 };
482 
487 template <class... Args> struct auxdata_traits<std::variant<Args...>> {
488  using T = std::variant<Args...>;
489 
490  static std::string type_name() {
491  return "variant<" + TypeId<Args...>::value() + ">";
492  }
493 
494  template <uint64_t I = 0>
495  static std::optional<std::variant<Args...>> expand_type(uint64_t i) {
496  if constexpr (I >= sizeof...(Args)) {
497  return std::nullopt;
498  } else {
499  return i == 0 ? std::variant<Args...>{std::in_place_index<I>,
500  std::variant_alternative_t<I, T>{}}
501  : expand_type<I + 1>(i - 1);
502  }
503  };
504 
505  static void toBytes(const T& Object, ToByteRange& TBR) {
506  uint64_t Index = Object.index();
508  std::visit(
509  [TBR](auto&& arg) mutable {
510  auxdata_traits<typename std::remove_const<
511  typename std::remove_reference<decltype(arg)>::type>::type>::
512  toBytes(arg, TBR);
513  },
514  Object);
515  }
516 
517  static bool fromBytes(T& Object, FromByteRange& FBR) {
518  uint64_t Index;
519  if (!auxdata_traits<uint64_t>::fromBytes(Index, FBR))
520  return false;
521  if (Index > FBR.remainingBytesToRead())
522  return false;
523 
524  auto maybeV = expand_type(Index);
525  if (!maybeV) {
526  return false;
527  }
528  auto V = *maybeV;
529  bool res_code = false;
530  std::visit(
531  [&res_code, &FBR](auto&& arg) mutable {
532  typename std::remove_reference<decltype(arg)>::type Val;
533  res_code = auxdata_traits<typename std::remove_reference<decltype(
534  arg)>::type>::fromBytes(Val, FBR);
535  if (!res_code)
536  return;
537  arg = Val;
538  },
539  V);
540  if (!res_code)
541  return false;
542  Object = V;
543  return true;
544  }
545 };
547 
549 template <class T> struct tuple_traits {};
550 template <class... Ts> struct tuple_traits<std::tuple<Ts...>> {
551  using Tuple = std::tuple<Ts...>;
552 
553  static std::string type_name() {
554  return "tuple<" + TypeId<Ts...>::value() + ">";
555  }
556 };
557 
558 template <class... Ts> struct tuple_traits<std::pair<Ts...>> {
559  using Tuple = std::tuple<Ts...>;
560 
561  static std::string type_name() {
562  return "tuple<" + TypeId<Ts...>::value() + ">";
563  }
564 };
565 
566 template <class Func, size_t... Is>
567 constexpr void static_for(Func&& f, std::integer_sequence<size_t, Is...>) {
568  (f(std::integral_constant<size_t, Is>{}), ...);
569 }
570 
571 template <class T>
572 struct auxdata_traits<T, typename std::enable_if_t<is_tuple<T>::value>>
573  : tuple_traits<T> {
574  static void toBytes(const T& Object, ToByteRange& TBR) {
575  static_for(
576  [&](auto i) {
577  const auto& F = std::get<i>(Object);
578  auxdata_traits<std::remove_cv_t<
579  std::remove_reference_t<decltype(F)>>>::toBytes(F, TBR);
580  },
581  std::make_index_sequence<std::tuple_size<T>::value>{});
582  }
583 
584  static bool fromBytes(T& Object, FromByteRange& FBR) {
585  bool Success = true;
586  static_for(
587  [&](auto i) {
588  auto& F = std::get<i>(Object);
589  if (!auxdata_traits<std::remove_cv_t<
590  std::remove_reference_t<decltype(F)>>>::fromBytes(F, FBR))
591  Success = false;
592  },
593  std::make_index_sequence<std::tuple_size<T>::value>{});
594 
595  return Success;
596  }
597 };
599 
601 template <class T> struct TypeId<T> {
602  static std::string value() { return auxdata_traits<T>::type_name(); }
603 };
604 
605 template <class T, class... Ts> struct TypeId<T, Ts...> {
606  static std::string value() {
607  return auxdata_traits<T>::type_name() + "," + TypeId<Ts...>::value();
608  }
609 };
611 
613 class GTIRB_EXPORT_API AuxData {
614 public:
617  struct SerializedForm {
618  std::string RawBytes;
619  std::string ProtobufType;
620  };
621 
623  AuxData() = default;
624 
625  virtual ~AuxData() = default;
626 
639  const SerializedForm& rawData() const { return this->SF; }
640 
643  static constexpr std::size_t UNREGISTERED_API_TYPE_ID = 0;
644 
646  virtual std::size_t getApiTypeId() const { return UNREGISTERED_API_TYPE_ID; }
647 
648 protected:
650  using MessageType = proto::AuxData;
651 
656  static void fromProtobuf(AuxData& Result, const MessageType& Message);
657 
661  virtual void toProtobuf(MessageType* Message) const {
662  toProtobuf(Message, this->SF);
663  }
664 
665  // This version of protobuf accepts a SerializedForm object to
666  // serialize rather than serializing AuxData's SerializedForm
667  // member. This is used by AuxDataImpl to serialize a typed AuxData
668  // object. We structure it this way to avoid having the AuxDataImpl
669  // template (which is instantiated in client code) access
670  // MessageType fields directly, which would introduce
671  // dllimport/dllexport issues on Windows.
672  void toProtobuf(MessageType* Message,
673  const SerializedForm& SFToSerialize) const;
674 
675  // Utility function for the AuxDataImpl template that allows us to
676  // check the embedded type name in the serialized protobuf against an
677  // expected typename without exposing the MessageType to clients.
678  static bool checkAuxDataMessageType(const AuxData::MessageType& Message,
679  const std::string& ExpectedName);
680 
681  // Present for testing purposes only.
682  void save(std::ostream& Out) const;
683 
684  // Present for testing purposes only.
685  static std::unique_ptr<AuxData>
686  load(std::istream& In, std::unique_ptr<AuxData> (*FPPtr)(const MessageType&));
687 
688 private:
689  SerializedForm SF;
690 
691  friend class AuxDataContainer; // Friend to enable fromProtobuf.
692  // Enables serialization by AuxDataContainer via containerToProtobuf.
693  template <typename T> friend typename T::MessageType toProtobuf(const T&);
694  friend class SerializationTestHarness; // Testing support.
695 };
697 
699 template <class Schema> class AuxDataImpl : public AuxData {
700 public:
701  AuxDataImpl() = default;
702  AuxDataImpl(typename Schema::Type&& Val) : Object(std::move(Val)){};
703 
705  static std::size_t staticGetApiTypeId() {
706  return typeid(typename Schema::Type).hash_code();
707  }
708 
710  virtual std::size_t getApiTypeId() const override {
711  return staticGetApiTypeId();
712  }
713 
714  const typename Schema::Type* get() const { return &Object; }
715 
716 private:
717  static std::unique_ptr<AuxData> fromProtobuf(const MessageType& Message) {
718  // Check if the serialized type isn't compatible with the type
719  // we're trying to deserialize to.
720  if (!checkAuxDataMessageType(
722  return nullptr;
723  }
724 
725  // Note: Do not access Message's contents here. That would introduce
726  // dllexport/dllimport problems on Windows. Call the base class's
727  // fromProtobuf function and then unserialize from its
728  // SerializedForm structure.
729  auto TypedAuxData = std::make_unique<AuxDataImpl<Schema>>();
730  AuxData::fromProtobuf(*TypedAuxData, Message);
731  FromByteRange FBR(TypedAuxData->rawData().RawBytes);
732  if (!auxdata_traits<typename Schema::Type>::fromBytes(TypedAuxData->Object,
733  FBR))
734  return nullptr;
735  return TypedAuxData;
736  }
737 
741  virtual void toProtobuf(MessageType* Message) const override {
742  // Note: Do not edit Message's contents here. That would introduce
743  // dllexport/dllimport problems on Windows. Store to a
744  // SerializedForm, and then call the base class's toProtobuf
745  // function.
746  AuxData::SerializedForm TypedSF;
747  TypedSF.ProtobufType = auxdata_traits<typename Schema::Type>::type_name();
748  ToByteRange TBR(TypedSF.RawBytes);
750  AuxData::toProtobuf(Message, TypedSF);
751  }
752 
753  // Present for testing purposes only.
754  void save(std::ostream& Out) const { AuxData::save(Out); }
755 
756  // Present for testing purposes only.
757  static std::unique_ptr<AuxDataImpl> load([[maybe_unused]] Context& C,
758  std::istream& In) {
759  return std::unique_ptr<AuxDataImpl>{
760  static_cast<AuxDataImpl*>(AuxData::load(In, fromProtobuf).release())};
761  }
762 
763  typename Schema::Type Object;
764 
765  friend class AuxDataContainer; // Friend to enable to/fromProtobuf.
766  friend class SerializationTestHarness; // Testing support.
767 };
769 
770 // (end \defgroup AUXDATA_GROUP)
771 
772 } // namespace gtirb
773 
774 #endif // GTIRB_AUXDATA_H
gtirb::is_mapping
Trait class that identifies whether T is a mapping container type.
Definition: AuxData.hpp:123
gtirb::is_sequence
Trait class that identifies whether T is a sequential container type.
Definition: AuxData.hpp:110
gtirb::auxdata_traits::toBytes
static void toBytes(const T &Object, ToByteRange &TBR)=delete
Serialize an object to a sequence of bytes.
gtirb::UUID
boost::uuids::uuid UUID
Represents a universally unique identifier used to identify Node objects across serialization boundar...
Definition: Context.hpp:37
Node.hpp
Class gtirb::Node.
gtirb::is_tuple
Trait class that identifies whether T is tuple-like.
Definition: AuxData.hpp:160
gtirb::auxdata_traits::type_name
static std::string type_name()=delete
String representation of the serialized type of T.
GTIRB_EXPORT_API
#define GTIRB_EXPORT_API
This macro controls the visibility of exported symbols (i.e. classes) in shared libraries....
Definition: Export.hpp:52
gtirb
Main namespace for the GTIRB API.
Definition: Addr.hpp:28
Offset.hpp
gtirb::auxdata_traits::fromBytes
static bool fromBytes(T &Object, FromByteRange &FBR)=delete
Deserialize an object from a sequence of bytes.
std
Definition: Addr.hpp:359
gtirb::is_set
Trait class that identifies whether T is a set container type.
Definition: AuxData.hpp:142
gtirb::auxdata_traits
Provides type information and serialization functions for types which can be stored in AuxData.
Definition: AuxData.hpp:211
Addr.hpp
Class gtirb::Addr and related functions.