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