GTIRB  v2.1.0
GrammaTech Intermediate Representation for Binaries: C++ API
Symbol.hpp
Go to the documentation of this file.
1 //===- Symbol.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_SYMBOL_H
16 #define GTIRB_SYMBOL_H
17 
18 #include <gtirb/Addr.hpp>
19 #include <gtirb/CodeBlock.hpp>
20 #include <gtirb/DataBlock.hpp>
21 #include <gtirb/Node.hpp>
22 #include <gtirb/ProxyBlock.hpp>
23 #include <functional>
24 #include <optional>
25 #include <type_traits>
26 #include <variant>
27 
30 
31 namespace gtirb {
32 namespace proto {
33 class Symbol;
34 }
35 
36 template <class T> class ErrorOr;
37 class Module; // Forward declared for the backpointer.
38 class SymbolObserver;
39 
43 class GTIRB_EXPORT_API Symbol : public Node {
44  template <typename... Ts> struct TypeList {};
45 
46  // SFINAE overload handling a Callable with a void return type.
47  template <typename Callable, typename Ty, typename CommonRetTy>
48  auto visit_impl_help(Callable&& Visitor) const
49  -> std::enable_if_t<std::is_void_v<CommonRetTy>, bool> {
50  static_assert(std::is_invocable_v<Callable, Ty*>,
51  "Visitor must contain an overloaded function call operator "
52  "for each of the types in supported_types");
53  if (Node* const* Ptr = std::get_if<Node*>(&Payload)) {
54  if (Ty* Obj = dyn_cast_or_null<Ty>(*Ptr)) {
55  std::invoke(Visitor, Obj);
56  return true;
57  }
58  }
59  return false;
60  }
61 
62  // SFINAE overload handling a Callable with a non-void return type.
63  template <typename Callable, typename Ty, typename CommonRetTy>
64  bool
65  visit_impl_help(Callable&& Visitor,
66  std::enable_if_t<!std::is_void_v<CommonRetTy>,
67  std::optional<CommonRetTy>>& Ret) const {
68  static_assert(std::is_invocable_v<Callable, Ty*>,
69  "Visitor must contain an overloaded function call operator "
70  "for each of the types in supported_types");
71  if (Node* const* Ptr = std::get_if<Node*>(&Payload)) {
72  if (Ty* Obj = dyn_cast_or_null<Ty>(*Ptr)) {
73  Ret = std::invoke(Visitor, Obj);
74  return true;
75  }
76  }
77  return false;
78  }
79 
80  // Helper type traits used to determine whether all of the Callable object's
81  // return types agree. Note that the return types do not need to be the same,
82  // but do need to be implicitly convertible to the same common type. e.g.,
83  // this class is a valid Callable.
84  // struct Visitor {
85  // int operator()(Block*);
86  // long operator()(DataBlock*);
87  // };
88  template <typename AlwaysVoid, typename Callable,
89  template <typename...> typename TypeList, typename... Types>
90  struct common_return_type_impl : std::false_type {};
91 
92  template <typename Callable, template <typename...> typename TypeList,
93  typename... Types>
94  struct common_return_type_impl<
95  std::void_t<
96  std::common_type_t<std::invoke_result_t<Callable, Types*>...>>,
97  Callable, TypeList, Types...> : std::true_type {
98  using type = std::common_type_t<std::invoke_result_t<Callable, Types*>...>;
99  };
100 
101  template <typename Callable, template <typename...> typename TypeList,
102  typename... Types>
103  using common_return_type_t =
104  typename common_return_type_impl<void, Callable, TypeList,
105  Types...>::type;
106 
107  template <typename Callable, template <typename...> typename TypeList,
108  typename... Types>
109  static constexpr bool common_return_type_v =
110  common_return_type_impl<void, Callable, TypeList, Types...>::value;
111 
112  // Helper function to unpack all of the types in the type list and attempt to
113  // visit Callable once per type. Verifies that the Callable objects all share
114  // a compatible return type.
115  //
116  // SFINAE overload handling a Callable where there is a common, non-void
117  // return type. Returns a std::optional<common_type>.
118  template <typename Callable, template <typename...> typename TypeList,
119  typename... Types>
120  auto visit_impl(Callable&& Visitor, TypeList<Types...>) const
121  -> std::enable_if_t<
122  !std::is_void_v<common_return_type_t<Callable, TypeList, Types...>>,
123  std::optional<common_return_type_t<Callable, TypeList, Types...>>> {
124  // If this assertion fails, the return values from the Callable object are
125  // not compatible enough. This can happen if they return incompatible types
126  // or if there is not an overload for each referent type.
127  static_assert(common_return_type_v<Callable, TypeList, Types...>,
128  "incompatible return types for the Callable object");
129  // Instantiate a call to the Visitor once for each of the listed types, but
130  // only issue the call at runtime if the Referent can be dynamically cast to
131  // the given type. In this way, the Visitor needs to be able to handle any
132  // of the supported types, but will only be called once for the concrete
133  // type of the Referent.
134  //
135  // If you get an error about there being no matching overloaded function for
136  // the call to visit_impl_help, that is most likely because there are one
137  // or more overloads missing for each referent type.
138  std::optional<common_return_type_t<Callable, TypeList, Types...>> Res;
139  (... ||
140  visit_impl_help<Callable, Types, typename decltype(Res)::value_type>(
141  std::forward<Callable>(Visitor), Res));
142  return Res;
143  }
144 
145  // SFINAE overload handling a Callable where every return type is void.
146  // Returns void.
147  template <typename Callable, template <typename...> typename TypeList,
148  typename... Types>
149  auto visit_impl(Callable&& Visitor, TypeList<Types...>) const
150  -> std::enable_if_t<
151  std::is_void_v<common_return_type_t<Callable, TypeList, Types...>>,
152  void> {
153  // Call each of the overloads on Callable, but there is no value to be
154  // returned from any of the calls.
155  (... ||
156  visit_impl_help<Callable, Types, void>(std::forward<Callable>(Visitor)));
157  }
158 
159  // Helper function that determines whether the passed NodeTy is the same as
160  // any of the types in the Types... list.
161  template <typename NodeTy, template <typename...> typename TypeList,
162  typename... Types>
163  static constexpr bool is_supported_type_impl(TypeList<Types...>) {
164  return (... || std::is_same_v<NodeTy, Types>);
165  }
166 
167 public:
168  // Helper function that determines whether the passed NodeTy is one of the
169  // supported referent types.
170  template <typename NodeTy> static constexpr bool is_supported_type() {
171  return is_supported_type_impl<NodeTy>(
172  std::decay_t<supported_referent_types>{});
173  }
174 
176  using supported_referent_types = TypeList<CodeBlock, DataBlock, ProxyBlock>;
177 
213  template <typename Callable> auto visit(Callable&& Visitor) const {
214  return visit_impl(std::forward<Callable>(Visitor),
215  std::decay_t<supported_referent_types>{});
216  }
217 
221  static Symbol* Create(Context& C) { return C.Create<Symbol>(C); }
222 
231  static Symbol* Create(Context& C, const std::string& Name,
232  bool AtEnd = false) {
233  return C.Create<Symbol>(C, Name, AtEnd);
234  }
235 
245  static Symbol* Create(Context& C, Addr X, const std::string& Name,
246  bool AtEnd = false) {
247  return C.Create<Symbol>(C, X, Name, AtEnd);
248  }
249 
259  template <typename NodeTy>
260  static Symbol* Create(Context& C, NodeTy* Referent, const std::string& Name,
261  bool AtEnd = false) {
262  static_assert(is_supported_type<NodeTy>(), "unsupported referent type");
263  return C.Create<Symbol>(C, Referent, Name, AtEnd);
264  }
265 
267  Module* getModule() { return Parent; }
269  const Module* getModule() const { return Parent; }
270 
274  std::optional<Addr> getAddress() const;
275 
279  const std::string& getName() const { return Name; }
280 
287  template <typename NodeTy> NodeTy* getReferent() {
288  if (Node* const* Ptr = std::get_if<Node*>(&Payload))
289  return dyn_cast_or_null<NodeTy>(*Ptr);
290  return nullptr;
291  }
292 
299  template <typename NodeTy> const NodeTy* getReferent() const {
300  if (Node* const* Ptr = std::get_if<Node*>(&Payload))
301  return dyn_cast_or_null<NodeTy>(*Ptr);
302  return nullptr;
303  }
304 
308  bool hasReferent() const { return std::holds_alternative<Node*>(Payload); }
309 
311  void setName(const std::string& N);
312 
318  template <typename NodeTy>
319  std::enable_if_t<is_supported_type<NodeTy>()> setReferent(NodeTy* N) {
320  setReferentFromNode(N);
321  }
322 
324  void setAddress(Addr A);
325 
330  bool getAtEnd() const { return AtEnd; }
331 
336  void setAtEnd(bool AE) { AtEnd = AE; }
337 
339  static bool classof(const Node* N) { return N->getKind() == Kind::Symbol; }
341 
342 private:
343  Symbol(Context& C) : Node(C, Kind::Symbol) {}
344  Symbol(Context& C, const std::string& N, bool AE)
345  : Node(C, Kind::Symbol), Name(N), AtEnd(AE) {}
346  Symbol(Context& C, const std::string& N, bool AE, const UUID& U)
347  : Node(C, Kind::Symbol, U), Name(N), AtEnd(AE) {}
348  Symbol(Context& C, Addr X, const std::string& N, bool AE)
349  : Node(C, Kind::Symbol), Payload(X), Name(N), AtEnd(AE) {}
350  template <typename NodeTy>
351  Symbol(Context& C, NodeTy* R, const std::string& N, bool AE)
352  : Node(C, Kind::Symbol), Payload(R), Name(N), AtEnd(AE) {
353  if (!R) {
354  Payload = std::monostate{};
355  }
356  }
357 
358  static Symbol* Create(Context& C, const std::string& Name, bool AtEnd,
359  const UUID& U) {
360  return C.Create<Symbol>(C, Name, AtEnd, U);
361  }
362 
363  void setParent(Module* M, SymbolObserver* O) {
364  Parent = M;
365  Observer = O;
366  }
367 
368  void setReferentFromNode(Node* N);
369 
371  using MessageType = proto::Symbol;
372 
378  void toProtobuf(MessageType* Message) const;
379 
386  static ErrorOr<Symbol*> fromProtobuf(Context& C, const MessageType& Message);
387 
388  // Present for testing purposes only.
389  void save(std::ostream& Out) const;
390 
391  // Present for testing purposes only.
392  static Symbol* load(Context& C, std::istream& In);
393 
394  Module* Parent{nullptr};
395  SymbolObserver* Observer{nullptr};
396  std::variant<std::monostate, Addr, Node*> Payload;
397  std::string Name;
398  bool AtEnd = false;
399 
400  friend class Context; // Allow Context to construct Symbols.
401  friend class Module; // Allow Module to call setModule, Create, etc.
402  // Allows serializaton from Module via containerToProtobuf.
403  template <typename T> friend typename T::MessageType toProtobuf(const T&);
404  friend class SerializationTestHarness; // Testing support.
405 };
406 
411 
413 public:
414  virtual ~SymbolObserver() = default;
415 
425  virtual ChangeStatus nameChange(Symbol* S, const std::string& OldName,
426  const std::string& NewName) = 0;
427 
437  virtual ChangeStatus
438  referentChange(Symbol* S,
439  std::variant<std::monostate, Addr, Node*> OldReferent,
440  std::variant<std::monostate, Addr, Node*> NewReferent) = 0;
441 };
442 
443 inline void Symbol::setName(const std::string& N) {
444  if (Observer) {
445  std::string OldName(N);
446  std::swap(Name, OldName);
447  [[maybe_unused]] ChangeStatus Status =
448  Observer->nameChange(this, OldName, Name);
449  assert(Status != ChangeStatus::Rejected &&
450  "recovering from rejected name change is unsupported");
451  } else {
452  Name = N;
453  }
454 }
455 
456 inline void Symbol::setAddress(Addr A) {
457  if (Observer) {
458  std::variant<std::monostate, Addr, Node*> OldValue = Payload;
459  Payload = A;
460  [[maybe_unused]] ChangeStatus Status =
461  Observer->referentChange(this, OldValue, Payload);
462  assert(Status != ChangeStatus::Rejected &&
463  "recovering from rejected address change is unsupported");
464  } else {
465  Payload = A;
466  }
467 }
468 
469 inline void Symbol::setReferentFromNode(Node* N) {
470  std::variant<std::monostate, Addr, Node*> OldValue = Payload;
471  if (N) {
472  Payload = N;
473  } else {
474  Payload = std::monostate{};
475  }
476  if (Observer) {
477  [[maybe_unused]] ChangeStatus Status =
478  Observer->referentChange(this, OldValue, Payload);
479  assert(Status != ChangeStatus::Rejected &&
480  "recovering from rejected referent change is unsupported");
481  }
482 }
483 
484 } // namespace gtirb
485 
486 #endif // GTIRB_SYMBOL_H
gtirb::Symbol::Create
static Symbol * Create(Context &C, NodeTy *Referent, const std::string &Name, bool AtEnd=false)
Create a Symbol object.
Definition: Symbol.hpp:260
gtirb::Symbol::setReferent
std::enable_if_t< is_supported_type< NodeTy >)> setReferent(NodeTy *N)
Set the referent of a symbol.
Definition: Symbol.hpp:319
gtirb::Symbol::Create
static Symbol * Create(Context &C, Addr X, const std::string &Name, bool AtEnd=false)
Create a Symbol object.
Definition: Symbol.hpp:245
gtirb::Context::Create
NodeTy * Create(Args &&... TheArgs)
Create an object of type T.
Definition: Context.hpp:126
gtirb::Node
Represents the base of the Node class hierarchy.
Definition: Node.hpp:39
gtirb::SymbolObserver::referentChange
virtual ChangeStatus referentChange(Symbol *S, std::variant< std::monostate, Addr, Node * > OldReferent, std::variant< std::monostate, Addr, Node * > NewReferent)=0
Notify parent when the Symbol's referent (Node or Addr) changes.
gtirb::UUID
boost::uuids::uuid UUID
Represents a universally unique identifier used to identify Node objects across serialization boundar...
Definition: Context.hpp:36
gtirb::SymbolObserver
Interface for notifying observers when the Symbol is updated.
Definition: Symbol.hpp:412
gtirb::Addr
A special class to store an Effective Address.
Definition: Addr.hpp:37
gtirb::ChangeStatus::Rejected
@ Rejected
gtirb::Symbol::visit
auto visit(Callable &&Visitor) const
Visits the symbol's referent, if one is present, by concrete referent type.
Definition: Symbol.hpp:213
gtirb::Context
The context under which GTIRB operations occur.
Definition: Context.hpp:63
gtirb::Symbol::getModule
Module * getModule()
Get the Module this symbol belongs to.
Definition: Symbol.hpp:267
Node.hpp
Class gtirb::Node.
gtirb::Symbol::getReferent
NodeTy * getReferent()
Get the referent to which this symbol refers.
Definition: Symbol.hpp:287
DataBlock.hpp
Class gtirb::DataBlock.
gtirb::SymAttribute::S
@ S
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::Symbol::setAtEnd
void setAtEnd(bool AE)
sets whether or not this symbol is pointing to the end of the referent rather than at the beginning.
Definition: Symbol.hpp:336
gtirb::Symbol::setAddress
void setAddress(Addr A)
Set the address of a symbol.
Definition: Symbol.hpp:456
gtirb
Main namespace for the GTIRB API.
Definition: Addr.hpp:28
gtirb::Symbol::Create
static Symbol * Create(Context &C)
Create an unitialized Symbol object.
Definition: Symbol.hpp:221
gtirb::Symbol::hasReferent
bool hasReferent() const
Check if this symbol has a referent.
Definition: Symbol.hpp:308
gtirb::Symbol::getAtEnd
bool getAtEnd() const
If true, this symbol is pointing to the end of the referent rather than at the beginning.
Definition: Symbol.hpp:330
gtirb::Symbol::getReferent
const NodeTy * getReferent() const
Get the referent to which this symbol refers.
Definition: Symbol.hpp:299
gtirb::Symbol::getName
const std::string & getName() const
Get the name.
Definition: Symbol.hpp:279
gtirb::Symbol::supported_referent_types
TypeList< CodeBlock, DataBlock, ProxyBlock > supported_referent_types
The list of supported referent types.
Definition: Symbol.hpp:176
gtirb::Symbol::getModule
const Module * getModule() const
Get the Module this symbol belongs to.
Definition: Symbol.hpp:269
ProxyBlock.hpp
Class gtirb::ProxyBlock.
std
Definition: Addr.hpp:359
gtirb::Module
Represents a single binary (library or executable).
Definition: Module.hpp:107
gtirb::SymbolObserver::nameChange
virtual ChangeStatus nameChange(Symbol *S, const std::string &OldName, const std::string &NewName)=0
Notify parent when the Symbol's name changes.
CodeBlock.hpp
Class gtirb::CodeBlock.
Addr.hpp
Class gtirb::Addr and related functions.
gtirb::Symbol::is_supported_type
static constexpr bool is_supported_type()
Definition: Symbol.hpp:170
gtirb::ChangeStatus
ChangeStatus
Definition: Observer.hpp:19
gtirb::Symbol::Create
static Symbol * Create(Context &C, const std::string &Name, bool AtEnd=false)
Create a Symbol object.
Definition: Symbol.hpp:231
gtirb::Symbol
Represents a Symbol, which maps a name to an object in the IR.
Definition: Symbol.hpp:43
gtirb::Symbol::setName
void setName(const std::string &N)
Set the name of a symbol.
Definition: Symbol.hpp:443