GTIRB  v2.2.0
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 <variant>
32 #include <vector>
33 
38 
39 namespace gtirb {
40 namespace proto {
41 class AuxData;
42 } // namespace proto
43 class Context;
44 
100 
102 
109 template <class T> struct is_sequence : std::false_type {};
110 
112 template <class T> struct is_sequence<std::vector<T>> : std::true_type {};
113 template <class T> struct is_sequence<std::list<T>> : std::true_type {};
114 template <class T> struct is_sequence<std::deque<T>> : std::true_type {};
116 
122 template <class T> struct is_mapping : std::false_type {};
124 template <class T, class U>
125 struct is_mapping<std::map<T, U>> : std::true_type {};
126 template <class T, class U>
127 struct is_mapping<std::unordered_map<T, U>> : std::true_type {};
128 // Explicitly disable multimaps. Because they can contain multiple values for
129 // a given key, they can't be used interchangeably with maps.
130 template <class T, class U>
131 struct is_mapping<std::multimap<T, U>> : std::false_type {};
132 template <class T, class U>
133 struct is_mapping<std::unordered_multimap<T, U>> : std::false_type {};
134 
135 template <class T> struct is_tuple : std::false_type {};
136 template <class... Args>
137 struct is_tuple<std::tuple<Args...>> : std::true_type {};
138 
139 template <class... Args>
140 struct is_tuple<std::pair<Args...>> : std::true_type {};
141 
142 // Utility class for serializing AuxData.
143 class ToByteRange {
144 public:
145  explicit ToByteRange(std::string& Bytes) : It(std::back_inserter(Bytes)) {}
146 
147  void write(std::byte Byte) { *It = static_cast<char>(Byte); }
148 
149 private:
150  std::back_insert_iterator<std::string> It;
151 };
152 
153 // Utility class for deserializing AuxData.
154 class FromByteRange {
155 public:
156  explicit FromByteRange(const std::string& Bytes)
157  : Curr(Bytes.begin()), End(Bytes.end()) {}
158 
159  bool read(std::byte& Byte) {
160  if (Curr == End)
161  return false;
162 
163  Byte = std::byte(*Curr);
164  ++Curr;
165  return true;
166  }
167 
168  uint64_t remainingBytesToRead() const {
169  return static_cast<uint64_t>(std::distance(Curr, End));
170  }
171 
172 private:
173  std::string::const_iterator Curr;
174  std::string::const_iterator End;
175 };
176 
178 
185 template <class T, class Enable = void> struct auxdata_traits {
190  static void toBytes(const T& Object, ToByteRange& TBR) = delete;
191 
198  static bool fromBytes(T& Object, FromByteRange& FBR) = delete;
199 
206  static std::string type_name() = delete;
207 };
208 
210 template <class... Ts> struct TypeId {};
211 
212 template <class T>
213 struct is_endian_type
214  : std::integral_constant<bool, std::is_class_v<T> ||
215  (std::is_integral_v<T> &&
216  !std::is_same_v<T, bool>)> {};
217 
218 template <typename T, typename Enable = void> struct default_serialization {};
219 
220 // Serialize and deserialize by copying the object representation directly.
221 template <typename T>
222 struct default_serialization<
223  T, typename std::enable_if_t<is_endian_type<T>::value ||
224  std::is_floating_point<T>::value ||
225  std::is_same<T, bool>::value>> {
226  static void toBytes(const T& object, ToByteRange& TBR) {
227  // Store as little-endian.
228  T ordered = object;
229 
230  if constexpr (!std::is_floating_point<T>::value &&
231  !std::is_same<T, bool>::value) {
232  // Do not reorder floating point or boolean values
233  boost::endian::conditional_reverse_inplace<boost::endian::order::little,
234  boost::endian::order::native>(
235  ordered);
236  }
237  auto srcBytes_begin = reinterpret_cast<const std::byte*>(&ordered);
238  auto srcBytes_end = reinterpret_cast<const std::byte*>(&ordered + 1);
239  std::for_each(srcBytes_begin, srcBytes_end, [&](auto b) { TBR.write(b); });
240  }
241 
242  static bool fromBytes(T& object, FromByteRange& FBR) {
243  auto dest_begin = reinterpret_cast<std::byte*>(&object);
244  auto dest_end = reinterpret_cast<std::byte*>(&object + 1);
245  bool Success = true;
246  std::for_each(dest_begin, dest_end, [&](auto& b) {
247  if (!FBR.read(b))
248  Success = false;
249  });
250  if (!Success) {
251  return false;
252  }
253 
254  // Data stored as little-endian.
255  if constexpr (!std::is_floating_point<T>::value &&
256  !std::is_same<T, bool>::value) {
257  boost::endian::conditional_reverse_inplace<boost::endian::order::little,
258  boost::endian::order::native>(
259  object);
260  }
261 
262  return true;
263  }
264 };
265 
266 template <>
267 struct auxdata_traits<std::byte> : default_serialization<std::byte> {
268  static std::string type_name() { return "byte"; }
269 
270  static void toBytes(std::byte object, ToByteRange& TBR) { TBR.write(object); }
271 
272  static bool fromBytes(std::byte& object, FromByteRange& FBR) {
273  return FBR.read(object);
274  }
275 };
276 
277 template <> struct auxdata_traits<Addr> : default_serialization<Addr> {
278  static std::string type_name() { return "Addr"; }
279 };
280 
281 template <> struct auxdata_traits<UUID> : default_serialization<UUID> {
282  static std::string type_name() { return "UUID"; }
283 };
284 
285 template <class T>
286 struct auxdata_traits<T, typename std::enable_if_t<std::is_integral<T>::value &&
287  std::is_signed<T>::value>>
288  : default_serialization<T> {
289  static std::string type_name() {
290  return "int" + std::to_string(8 * sizeof(T)) + "_t";
291  }
292 };
293 
294 template <class T>
295 struct auxdata_traits<T, typename std::enable_if_t<std::is_integral<T>::value &&
296  std::is_unsigned<T>::value>>
297  : default_serialization<T> {
298  static std::string type_name() {
299  return "uint" + std::to_string(8 * sizeof(T)) + "_t";
300  }
301 };
302 
303 template <> struct auxdata_traits<bool> : default_serialization<bool> {
304  static std::string type_name() { return "bool"; }
305 };
306 
307 template <> struct auxdata_traits<float> : default_serialization<float> {
308  static std::string type_name() { return "float"; }
309 };
310 
311 template <> struct auxdata_traits<double> : default_serialization<double> {
312  static std::string type_name() { return "double"; }
313 };
314 
315 template <> struct auxdata_traits<std::string> {
316  static std::string type_name() { return "string"; }
317 
318  static void toBytes(const std::string& Object, ToByteRange& TBR) {
319  auxdata_traits<uint64_t>::toBytes(Object.size(), TBR);
320  std::for_each(Object.begin(), Object.end(),
321  [&](auto& elt) { auxdata_traits<char>::toBytes(elt, TBR); });
322  }
323 
324  static bool fromBytes(std::string& Object, FromByteRange& FBR) {
325  uint64_t Count;
326  if (!auxdata_traits<uint64_t>::fromBytes(Count, FBR))
327  return false;
328 
329  if (Count > FBR.remainingBytesToRead())
330  return false;
331 
332  Object.resize(Count);
333  bool Success = true;
334  std::for_each(Object.begin(), Object.end(), [&](auto& elt) {
335  if (!auxdata_traits<char>::fromBytes(elt, FBR))
336  Success = false;
337  });
338 
339  return Success;
340  }
341 };
342 
343 template <> struct auxdata_traits<Offset> {
344  static std::string type_name() { return "Offset"; }
345 
346  static void toBytes(const Offset& Object, ToByteRange& TBR) {
347  auxdata_traits<UUID>::toBytes(Object.ElementId, TBR);
348  auxdata_traits<uint64_t>::toBytes(Object.Displacement, TBR);
349  }
350 
351  static bool fromBytes(Offset& Object, FromByteRange& FBR) {
352  if (!auxdata_traits<UUID>::fromBytes(Object.ElementId, FBR))
353  return false;
354  return auxdata_traits<uint64_t>::fromBytes(Object.Displacement, FBR);
355  }
356 };
357 
358 template <class T>
359 struct auxdata_traits<T, typename std::enable_if_t<is_sequence<T>::value>> {
360  static std::string type_name() {
361  return "sequence<" + TypeId<typename T::value_type>::value() + ">";
362  }
363 
364  static void toBytes(const T& Object, ToByteRange& TBR) {
365  auxdata_traits<uint64_t>::toBytes(Object.size(), TBR);
366  std::for_each(Object.begin(), Object.end(), [&](const auto& Elt) {
367  auxdata_traits<typename T::value_type>::toBytes(Elt, TBR);
368  });
369  }
370 
371  static bool fromBytes(T& Object, FromByteRange& FBR) {
372  uint64_t Count;
373  if (!auxdata_traits<uint64_t>::fromBytes(Count, FBR))
374  return false;
375 
376  if (Count > FBR.remainingBytesToRead())
377  return false;
378 
379  Object.resize(Count);
380  bool Success = true;
381  std::for_each(Object.begin(), Object.end(), [&](auto& Elt) {
382  if (!auxdata_traits<typename T::value_type>::fromBytes(Elt, FBR))
383  Success = false;
384  });
385 
386  return Success;
387  }
388 };
389 
390 template <class... Args> struct auxdata_traits<std::set<Args...>> {
391  using T = std::set<Args...>;
392 
393  static std::string type_name() {
394  return "set<" + TypeId<typename T::value_type>::value() + ">";
395  }
396 
397  static void toBytes(const T& Object, ToByteRange& TBR) {
398  auxdata_traits<uint64_t>::toBytes(Object.size(), TBR);
399  for (const auto& Elt : Object)
401  }
402 
403  static bool fromBytes(T& Object, FromByteRange& FBR) {
404  uint64_t Count;
405  if (!auxdata_traits<uint64_t>::fromBytes(Count, FBR))
406  return false;
407 
408  if (Count > FBR.remainingBytesToRead())
409  return false;
410 
411  for (uint64_t i = 0; i < Count; i++) {
412  typename T::value_type V;
413  if (!auxdata_traits<decltype(V)>::fromBytes(V, FBR))
414  return false;
415  Object.emplace(std::move(V));
416  }
417 
418  return true;
419  }
420 };
421 
422 template <class T>
423 struct auxdata_traits<T, typename std::enable_if_t<is_mapping<T>::value>> {
424  static std::string type_name() {
425  return "mapping<" +
426  TypeId<typename T::key_type, typename T::mapped_type>::value() + ">";
427  }
428 
429  static void toBytes(const T& Object, ToByteRange& TBR) {
430  auxdata_traits<uint64_t>::toBytes(Object.size(), TBR);
431  std::for_each(Object.begin(), Object.end(), [&](const auto& Elt) {
432  auxdata_traits<typename T::key_type>::toBytes(Elt.first, TBR);
433  auxdata_traits<typename T::mapped_type>::toBytes(Elt.second, TBR);
434  });
435  }
436 
437  static bool fromBytes(T& Object, FromByteRange& FBR) {
438  uint64_t Count;
439  if (!auxdata_traits<uint64_t>::fromBytes(Count, FBR))
440  return false;
441 
442  if (Count > FBR.remainingBytesToRead())
443  return false;
444 
445  for (uint64_t i = 0; i < Count; i++) {
446  typename T::key_type K;
447  if (!auxdata_traits<decltype(K)>::fromBytes(K, FBR))
448  return false;
449  typename T::mapped_type V;
450  if (!auxdata_traits<decltype(V)>::fromBytes(V, FBR))
451  return false;
452  Object.emplace(std::move(K), std::move(V));
453  }
454  return true;
455  }
456 };
457 
462 template <class... Args> struct auxdata_traits<std::variant<Args...>> {
463  using T = std::variant<Args...>;
464 
465  static std::string type_name() {
466  return "variant<" + TypeId<Args...>::value() + ">";
467  }
468 
469  template <uint64_t I = 0>
470  static std::optional<std::variant<Args...>> expand_type(uint64_t i) {
471  if constexpr (I >= sizeof...(Args)) {
472  return std::nullopt;
473  } else {
474  return i == 0 ? std::variant<Args...>{std::in_place_index<I>,
475  std::variant_alternative_t<I, T>{}}
476  : expand_type<I + 1>(i - 1);
477  }
478  };
479 
480  static void toBytes(const T& Object, ToByteRange& TBR) {
481  uint64_t Index = Object.index();
483  std::visit(
484  [TBR](auto&& arg) mutable {
485  auxdata_traits<typename std::remove_const<
486  typename std::remove_reference<decltype(arg)>::type>::type>::
487  toBytes(arg, TBR);
488  },
489  Object);
490  }
491 
492  static bool fromBytes(T& Object, FromByteRange& FBR) {
493  uint64_t Index;
494  if (!auxdata_traits<uint64_t>::fromBytes(Index, FBR))
495  return false;
496  if (Index > FBR.remainingBytesToRead())
497  return false;
498 
499  auto maybeV = expand_type(Index);
500  if (!maybeV) {
501  return false;
502  }
503  auto V = *maybeV;
504  bool res_code = false;
505  std::visit(
506  [&res_code, &FBR](auto&& arg) mutable {
507  typename std::remove_reference<decltype(arg)>::type Val;
508  res_code = auxdata_traits<typename std::remove_reference<decltype(
509  arg)>::type>::fromBytes(Val, FBR);
510  if (!res_code)
511  return;
512  arg = Val;
513  },
514  V);
515  if (!res_code)
516  return false;
517  Object = V;
518  return true;
519  }
520 };
522 
524 template <class T> struct tuple_traits {};
525 template <class... Ts> struct tuple_traits<std::tuple<Ts...>> {
526  using Tuple = std::tuple<Ts...>;
527 
528  static std::string type_name() {
529  return "tuple<" + TypeId<Ts...>::value() + ">";
530  }
531 };
532 
533 template <class... Ts> struct tuple_traits<std::pair<Ts...>> {
534  using Tuple = std::tuple<Ts...>;
535 
536  static std::string type_name() {
537  return "tuple<" + TypeId<Ts...>::value() + ">";
538  }
539 };
540 
541 template <class Func, size_t... Is>
542 constexpr void static_for(Func&& f, std::integer_sequence<size_t, Is...>) {
543  (f(std::integral_constant<size_t, Is>{}), ...);
544 }
545 
546 template <class T>
547 struct auxdata_traits<T, typename std::enable_if_t<is_tuple<T>::value>>
548  : tuple_traits<T> {
549  static void toBytes(const T& Object, ToByteRange& TBR) {
550  static_for(
551  [&](auto i) {
552  const auto& F = std::get<i>(Object);
553  auxdata_traits<std::remove_cv_t<
554  std::remove_reference_t<decltype(F)>>>::toBytes(F, TBR);
555  },
556  std::make_index_sequence<std::tuple_size<T>::value>{});
557  }
558 
559  static bool fromBytes(T& Object, FromByteRange& FBR) {
560  bool Success = true;
561  static_for(
562  [&](auto i) {
563  auto& F = std::get<i>(Object);
564  if (!auxdata_traits<std::remove_cv_t<
565  std::remove_reference_t<decltype(F)>>>::fromBytes(F, FBR))
566  Success = false;
567  },
568  std::make_index_sequence<std::tuple_size<T>::value>{});
569 
570  return Success;
571  }
572 };
574 
576 template <class T> struct TypeId<T> {
577  static std::string value() { return auxdata_traits<T>::type_name(); }
578 };
579 
580 template <class T, class... Ts> struct TypeId<T, Ts...> {
581  static std::string value() {
582  return auxdata_traits<T>::type_name() + "," + TypeId<Ts...>::value();
583  }
584 };
586 
588 class GTIRB_EXPORT_API AuxData {
589 public:
592  struct SerializedForm {
593  std::string RawBytes;
594  std::string ProtobufType;
595  };
596 
598  AuxData() = default;
599 
600  virtual ~AuxData() = default;
601 
614  const SerializedForm& rawData() const { return this->SF; }
615 
618  static constexpr std::size_t UNREGISTERED_API_TYPE_ID = 0;
619 
621  virtual std::size_t getApiTypeId() const { return UNREGISTERED_API_TYPE_ID; }
622 
623 protected:
625  using MessageType = proto::AuxData;
626 
631  static void fromProtobuf(AuxData& Result, const MessageType& Message);
632 
636  virtual void toProtobuf(MessageType* Message) const {
637  toProtobuf(Message, this->SF);
638  }
639 
640  // This version of protobuf accepts a SerializedForm object to
641  // serialize rather than serializing AuxData's SerializedForm
642  // member. This is used by AuxDataImpl to serialize a typed AuxData
643  // object. We structure it this way to avoid having the AuxDataImpl
644  // template (which is instantiated in client code) access
645  // MessageType fields directly, which would introduce
646  // dllimport/dllexport issues on Windows.
647  void toProtobuf(MessageType* Message,
648  const SerializedForm& SFToSerialize) const;
649 
650  // Utility function for the AuxDataImpl template that allows us to
651  // check the embedded type name in the serialized protobuf against an
652  // expected typename without exposing the MessageType to clients.
653  static bool checkAuxDataMessageType(const AuxData::MessageType& Message,
654  const std::string& ExpectedName);
655 
656  // Present for testing purposes only.
657  void save(std::ostream& Out) const;
658 
659  // Present for testing purposes only.
660  static std::unique_ptr<AuxData>
661  load(std::istream& In, std::unique_ptr<AuxData> (*FPPtr)(const MessageType&));
662 
663 private:
664  SerializedForm SF;
665 
666  friend class AuxDataContainer; // Friend to enable fromProtobuf.
667  // Enables serialization by AuxDataContainer via containerToProtobuf.
668  template <typename T> friend typename T::MessageType toProtobuf(const T&);
669  friend class SerializationTestHarness; // Testing support.
670 };
672 
674 template <class Schema> class AuxDataImpl : public AuxData {
675 public:
676  AuxDataImpl() = default;
677  AuxDataImpl(typename Schema::Type&& Val) : Object(std::move(Val)){};
678 
680  static std::size_t staticGetApiTypeId() {
681  return typeid(typename Schema::Type).hash_code();
682  }
683 
685  virtual std::size_t getApiTypeId() const override {
686  return staticGetApiTypeId();
687  }
688 
689  const typename Schema::Type* get() const { return &Object; }
690 
691 private:
692  static std::unique_ptr<AuxData> fromProtobuf(const MessageType& Message) {
693  // Check if the serialized type isn't compatible with the type
694  // we're trying to deserialize to.
695  if (!checkAuxDataMessageType(
697  return nullptr;
698  }
699 
700  // Note: Do not access Message's contents here. That would introduce
701  // dllexport/dllimport problems on Windows. Call the base class's
702  // fromProtobuf function and then unserialize from its
703  // SerializedForm structure.
704  auto TypedAuxData = std::make_unique<AuxDataImpl<Schema>>();
705  AuxData::fromProtobuf(*TypedAuxData, Message);
706  FromByteRange FBR(TypedAuxData->rawData().RawBytes);
707  if (!auxdata_traits<typename Schema::Type>::fromBytes(TypedAuxData->Object,
708  FBR))
709  return nullptr;
710  return TypedAuxData;
711  }
712 
716  virtual void toProtobuf(MessageType* Message) const override {
717  // Note: Do not edit Message's contents here. That would introduce
718  // dllexport/dllimport problems on Windows. Store to a
719  // SerializedForm, and then call the base class's toProtobuf
720  // function.
721  AuxData::SerializedForm TypedSF;
722  TypedSF.ProtobufType = auxdata_traits<typename Schema::Type>::type_name();
723  ToByteRange TBR(TypedSF.RawBytes);
725  AuxData::toProtobuf(Message, TypedSF);
726  }
727 
728  // Present for testing purposes only.
729  void save(std::ostream& Out) const { AuxData::save(Out); }
730 
731  // Present for testing purposes only.
732  static std::unique_ptr<AuxDataImpl> load([[maybe_unused]] Context& C,
733  std::istream& In) {
734  return std::unique_ptr<AuxDataImpl>{
735  static_cast<AuxDataImpl*>(AuxData::load(In, fromProtobuf).release())};
736  }
737 
738  typename Schema::Type Object;
739 
740  friend class AuxDataContainer; // Friend to enable to/fromProtobuf.
741  friend class SerializationTestHarness; // Testing support.
742 };
744 
745 // (end \defgroup AUXDATA_GROUP)
746 
747 } // namespace gtirb
748 
749 #endif // GTIRB_AUXDATA_H
gtirb::is_mapping
Trait class that identifies whether T is a mapping container type.
Definition: AuxData.hpp:122
gtirb::is_sequence
Trait class that identifies whether T is a sequential container type.
Definition: AuxData.hpp:109
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:36
Node.hpp
Class gtirb::Node.
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::auxdata_traits
Provides type information and serialization functions for types which can be stored in AuxData.
Definition: AuxData.hpp:185
Addr.hpp
Class gtirb::Addr and related functions.