Asylo
error_space.h
Go to the documentation of this file.
1 /*
2  *
3  * Copyright 2017 Asylo authors
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  */
18 
19 #ifndef ASYLO_UTIL_ERROR_SPACE_H_
20 #define ASYLO_UTIL_ERROR_SPACE_H_
21 
22 #include <string>
23 #include <unordered_map>
24 
25 #include "absl/strings/str_format.h"
26 #include "asylo/platform/common/static_map.h"
27 #include "asylo/util/error_codes.h"
28 
29 // This file, along with the associated cc file, defines the basic
30 // infrastructure for error-spaces that could be used with the
31 // ::asylo::Status class. The files also provide implementation of the
32 // canonical Google error space.
33 
34 namespace asylo {
35 namespace error {
36 
37 // Name of the canonical error space.
38 static constexpr char kCanonicalErrorSpaceName[] =
39  "::asylo::error::GoogleErrorSpace";
40 
41 // Forward declaration of abstract class ErrorSpace. This class acts as the base
42 // class for all error-space implementations.
43 class ErrorSpace;
44 
45 /// \cond Internal
46 /// ErrorSpaceAdlTag is a zero-byte template struct that is used for invoking
47 /// the correct implementation of GetErrorSpace() and related methods.
48 template <typename EnumT>
49 struct ErrorSpaceAdlTag {
50  // Make sure that error spaces can only be associated with enum types.
51  static_assert(std::is_enum<EnumT>::value,
52  "Cannot associate an error space with a non-enum type");
53 };
54 
55 // The error_enum_traits class is used to determine whether the template
56 // arugument EnumT has an error-space associated with it, and to retrieve a
57 // singleton pointer to it, if one exists.
58 //
59 // This class uses some SFINAE techniques to determine the existence of an error
60 // space. While these techniques add some code complexity, it is expected that
61 // the added complexity is worth the sanity of compile-time error detection.
62 template <typename EnumT>
63 struct error_enum_traits {
64  private:
65  // TestErrorSpaceBinding is a function declaration that is used for statically
66  // determining if a particular enum has an error-space associated with it.
67  // Two function prototypes are defined, of which the compiler picks the most
68  // restrictive one that applies.
69  //
70  // The first prototype is a more restrictive one, as it applies only to enum
71  // types that have an error space associated with them (an enum type T is
72  // considered to have an error space associated with it if and only if
73  // ErrorSpace const *GetErrorSpace(ErrorSpaceAdlTag<T>) is defined).
74  //
75  // The second prototype applies to all types, and hence is less restrictive.
76  // The compiler will pick this prototype only if it is unable to pick the
77  // first prototype.
78 
79  // Restrictive prototype.
80  template <typename EnumU>
81  static auto TestErrorSpaceBinding(ErrorSpace const *space)
82  -> decltype(space = GetErrorSpace(ErrorSpaceAdlTag<EnumU>()),
83  std::true_type());
84 
85  // Non-restrictive prototype.
86  template <typename EnumU>
87  static auto TestErrorSpaceBinding(...) -> std::false_type;
88 
89  // Returns the error space associated with ErrorSpaceAdlTag template
90  // specialization Tag.
91  template <typename Tag>
92  static ErrorSpace const *get_error_space(Tag tag, std::true_type t) {
93  return GetErrorSpace(tag);
94  }
95 
96  // Dummy implementation designed to provide a meaningful compile-time error
97  // for types that do not have an error space associated them.
98  //
99  // Note that although this definition of get_error_space() is a valid
100  // candidate when TruthType is std::true_type, the compiler will choose the
101  // definition above because it is more restrictive.
102  template <typename Tag, typename TruthType>
103  static ErrorSpace const *get_error_space(Tag tag, TruthType truth_type) {
104  static_assert(TruthType::value,
105  "No error-space binding found for template parameter EnumT, "
106  "Make sure that GetErrorSpace(ErrorSpaceAdlTag<EnumT>) is "
107  "defined");
108  return nullptr;
109  }
110 
111  public:
112  using error_space_binding_type = decltype(TestErrorSpaceBinding<EnumT>(0));
113 
114  static ErrorSpace const *get_error_space() {
115  return get_error_space(ErrorSpaceAdlTag<EnumT>(),
116  error_space_binding_type());
117  }
118 };
119 /// \endcond
120 
121 /// All implementations of error spaces are derived from this abstract class.
122 /// \related GoogleErrorSpace
123 ///
124 /// At a conceptual level, an ErrorSpace provides a mechanism for classifying
125 /// error codes into distinct categories and mapping those errors to
126 /// human readable strings. It also provides a mechanism for converting error
127 /// codes from arbitrary error spaces to the Google canonical error space.
128 ///
129 /// At the implementation level, an error space consists of an error code enum
130 /// and an associated implementation of the abstract ErrorSpace interface. The
131 /// ErrorSpace interface declares three pure virtual methods. An ErrorSpace
132 /// interface implementation is bound to the error code enum via a compile-time
133 /// polymorphic function GetErrorSpace(), which can be used to retrieve a
134 /// singleton pointer to the error space associated with a particular enum.
135 ///
136 /// Thus, to implement a new error space, the implementer must provide three
137 /// components:
138 /// 1. An enum type that is not associated with any current ErrorSpace
139 /// implementation.
140 /// 2. An implementation of the ErrorSpace interface.
141 /// 3. An implementation of an appropriately-typed GetErrorSpace() function.
142 ///
143 /// The error-space library maintains a global map of singletons for all the
144 /// error-space implementations loaded into the current address space. This map
145 /// can be queried to retrieve singleton pointers associated with a given name
146 /// using the ErrorSpace::Find(const string &name) method. To enable seamless
147 /// bookkeeping of such singletons, the error-space infrastructure defines an
148 /// intermediate template class called ErrorSpaceImplementationHelper, which is
149 /// derived from the ErrorSpace abstract class. Any error-space implementation
150 /// derived from this class is automatically tracked in the
151 /// error-space singleton global map. The helper class also provides a
152 /// std::unordered_map-based implementation of the SpaceName() and String()
153 /// methods, and as a result, error-space implementations derived from this
154 /// class do not need to provide their own implementation of these methods. It
155 /// is strongly recommended that all error-space implementations be derived from
156 /// the ErrorSpaceImplementationHelper. While it is possible to correctly
157 /// implement an error space without deriving from this class, such an
158 /// implementation will have to be aware of the error-space infrastructure, and
159 /// consequently, will be fragile.
160 ///
161 /// Below is an example of implementing a new enum `Foo`, and associating it
162 /// with the ErrorSpace implementation `FooErrorSpace`.
163 ///
164 /// First, define the enum type.
165 /// ```
166 /// enum Foo {
167 /// OK = 0, // A value of 0 must always map to OK status in the error space.
168 /// ...
169 /// };
170 /// ```
171 /// Next implement the `FooErrorSpace` class by deriving it from
172 /// `ErrorSpaceImplementationHelper<FooErrorSpace>`.
173 /// ```
174 /// class FooErrorSpace : public ErrorSpaceImplementationHelper<FooErrorSpace> {
175 /// public:
176 /// using code_type = Foo; // Error-space implementation must define the
177 /// // code_type type-alias that aliases to the
178 /// // underlying enum.
179 /// // No need to provide implementation of ErrorSpace interface, as
180 /// // ErrorSpaceImplementationHelper<FooErrorSpace> provides such
181 /// // implementation.
182 ///
183 /// friend ErrorSpace const *GetErrorSpace(ErrorSpaceAdlTag<Foo> tag);
184 /// private:
185 ///
186 /// FooErrorSpace() : ErrorSpaceImplementationHelper<FooErrorSpace>{
187 /// "FooErrorSpace"} {
188 /// AddTranslationMapEntry(...);
189 /// ...
190 /// }
191 /// };
192 /// ```
193 /// Finally, bind the ErrorSpace implementation to the enum by defining
194 /// appropriate GetErrorSpace() function.
195 /// ```
196 /// ErrorSpace const *GetErrorSpace(ErrorSpaceAdlTag<Foo> tag) {
197 /// // Must return a singleton pointer of FooErrorSpace
198 /// ...
199 /// }
200 /// ```
201 /// See GoogleErrorSpace for an example implementation.
202 class ErrorSpace {
203  public:
204  ErrorSpace() = default;
205  ErrorSpace(const ErrorSpace &other) = delete;
206  virtual ~ErrorSpace() = default;
207  ErrorSpace &operator=(const ErrorSpace &other) = delete;
208 
209  /// Gets a name that uniquely identifies the error space.
210  /// \return A uniquely identifying name for the ErrorSpace.
211  virtual std::string SpaceName() const = 0;
212 
213  /// Gets a string that describes the error code within the space.
214  /// \param code The error code to interpret within the error space.
215  /// \return A description for the input code.
216  virtual std::string String(int code) const = 0;
217 
218  /// Converts `code` to an appropriate value in the GoogleError enum.
219  /// \param code The error code in this error space to convert to GoogleError.
220  /// \return The GoogleError interpretation of `code`.
221  virtual GoogleError GoogleErrorCode(int code) const = 0;
222 
223  /// Finds and returns an ErrorSpace singleton pointer whose SpaceName()
224  /// equals `name`.
225  /// \param name The name to search for.
226  /// \return A singleton pointer to an ErrorSpace on success, nullptr on
227  /// failure.
228  static ErrorSpace const *Find(const std::string &name);
229 };
230 
231 /// \cond Internal
232 namespace error_internal {
233 
234 // Namer object for the ErrorSpace base class. Used for creating an ErrorSpace
235 // static map.
236 struct ErrorSpaceNamer {
237  std::string operator()(const ErrorSpace &space) { return space.SpaceName(); }
238 };
239 
240 // Static map providing a mapping from error-space name string to an error-space
241 // singleton pointer.
242 class AsyloErrorSpaceStaticMap
243  : public ::asylo::StaticMap<AsyloErrorSpaceStaticMap, const ErrorSpace,
244  ErrorSpaceNamer> {};
245 
246 } // namespace error_internal
247 /// \endcond
248 
249 /// An intermediate template class that to help define an ErrorSpace subclass.
250 /// ErrorSpaceImplementationHelper automatically creates and inserts a singleton
251 /// instance of `ErrorSpaceT` into the global error-space singleton map. It is
252 /// customary to derive the class `ErrorSpaceT` from
253 /// `ErrorSpaceImplementationHelper<ErrorSpaceT>` to ensure correct management
254 /// of the map.
255 template <typename ErrorSpaceT>
256 class ErrorSpaceImplementationHelper : public ErrorSpace {
257  protected:
258  /// Constructs an ErrorSpaceImplementationHelper and registers it as
259  /// `space_name`.
260  ///
261  /// \param space_name The name that ErrorSpace::Find() will use to fetch the
262  /// singleton instance of this ErrorSpace.
263  /// \param default_error_string The result for String() for an unrecognized
264  /// error code.
266  const std::string &space_name,
267  const std::string &default_error_string = "Unrecognized Code")
269  // Passing the address of |inserter_| to DoNotOptimize() forces the compiler
270  // to instantiate the member variable.
271  DoNotOptimize(&inserter_);
272  }
273 
274  /// Adds an interpretation of an error code as both a string and GoogleError.
275  ///
276  /// \param code The error code to interpret.
277  /// \param error_string The interpretation String() will return for `code`.
278  /// \param google_error_code The most apt GoogleError to assign to `code`.
279  void AddTranslationMapEntry(int code, const std::string &error_string,
280  GoogleError google_error_code) {
281  CHECK(code_translation_map_
282  .emplace(code, std::pair<std::string, GoogleError>(
283  error_string, google_error_code))
284  .second)
285  << "Duplicate map key: " << code;
286  }
287 
288  std::string SpaceName() const override { return space_name_; }
289 
290  std::string String(int code) const override {
291  auto it = code_translation_map_.find(code);
292  if (it == code_translation_map_.cend()) {
293  if (code == 0) {
294  return "OK";
295  }
296  return absl::StrFormat("%s (%d)", default_error_string_, code);
297  }
298  return it->second.first;
299  }
300 
301  GoogleError GoogleErrorCode(int code) const override {
302  if (code == 0) {
303  // Error code value of zero must map to GoogleError::OK.
304  return GoogleError::OK;
305  }
306  auto it = code_translation_map_.find(code);
307  if (it == code_translation_map_.cend()) {
308  return GoogleError::UNKNOWN;
309  }
310  return it->second.second;
311  }
312 
313  private:
314  using InserterType = error_internal::AsyloErrorSpaceStaticMap::ValueInserter;
315  InserterType *DoNotOptimize(InserterType *inserter) { return inserter; }
316  static InserterType inserter_;
317  // Since ErrorSpace is used in trusted primitives layer where system calls may
318  // not be available, avoid usage of absl containers which may make system
319  // calls.
320  std::unordered_map<int, std::pair<std::string, GoogleError>>
321  code_translation_map_;
322  const std::string space_name_;
323  const std::string default_error_string_;
324 };
325 
326 /// \cond Internal
327 // Instantiate inserter_ with a singleton pointer of |ErrorSpaceT| so that the
328 // singleton pointer gets inserted into the global map.
329 template <typename ErrorSpaceT>
330 error_internal::AsyloErrorSpaceStaticMap::ValueInserter
331  ErrorSpaceImplementationHelper<ErrorSpaceT>::inserter_(
333 /// \endcond
334 
335 /// Binds the class GoogleErrorSpace to the #GoogleError enum.
337  ErrorSpaceAdlTag<::asylo::error::GoogleError> tag);
338 
339 /// The implementation of the ErrorSpace interface for the GoogleError canonical
340 /// error space.
343  public:
345 
346  GoogleErrorSpace(const GoogleErrorSpace &other) = delete;
347  virtual ~GoogleErrorSpace() = default;
348  GoogleErrorSpace &operator=(const GoogleErrorSpace &other) = delete;
349 
350  /// Gets the singleton instance of GoogleErrorSpace.
351  /// \return The one instance of GoogleErrorSpace.
352  static ErrorSpace const *GetInstance() {
353  static ErrorSpace const *instance = new GoogleErrorSpace();
354  return instance;
355  }
356 
357  private:
358  GoogleErrorSpace();
359 };
360 
361 } // namespace error
362 } // namespace asylo
363 
364 #endif // ASYLO_UTIL_ERROR_SPACE_H_
virtual ~GoogleErrorSpace()=default
virtual ~ErrorSpace()=default
ErrorSpaceImplementationHelper(const std::string &space_name, const std::string &default_error_string="Unrecognized Code")
Constructs an ErrorSpaceImplementationHelper and registers it as space_name.
Definition: error_space.h:265
virtual std::string String(int code) const =0
Gets a string that describes the error code within the space.
virtual GoogleError GoogleErrorCode(int code) const =0
Converts code to an appropriate value in the GoogleError enum.
All implementations of error spaces are derived from this abstract class.
Definition: error_space.h:202
GoogleErrorSpace & operator=(const GoogleErrorSpace &other)=delete
GoogleError GoogleErrorCode(int code) const override
Converts code to an appropriate value in the GoogleError enum.
Definition: error_space.h:301
ABSL_CONST_INIT const char kStatusMoveAssignmentMsg[]
void AddTranslationMapEntry(int code, const std::string &error_string, GoogleError google_error_code)
Adds an interpretation of an error code as both a string and GoogleError.
Definition: error_space.h:279
The implementation of the ErrorSpace interface for the GoogleError canonical error space...
Definition: error_space.h:341
ErrorSpace const * GetErrorSpace(ErrorSpaceAdlTag<::asylo::error::GoogleError > tag)
Binds the class GoogleErrorSpace to the #GoogleError enum.
static ErrorSpace const * GetInstance()
Gets the singleton instance of GoogleErrorSpace.
Definition: error_space.h:352
virtual std::string SpaceName() const =0
Gets a name that uniquely identifies the error space.
ErrorSpace(const ErrorSpace &other)=delete
GoogleErrorSpace(const GoogleErrorSpace &other)=delete
std::string String(int code) const override
Gets a string that describes the error code within the space.
Definition: error_space.h:290
ErrorSpace & operator=(const ErrorSpace &other)=delete
static ErrorSpace const * Find(const std::string &name)
Finds and returns an ErrorSpace singleton pointer whose SpaceName() equals name.
ErrorSpace const * GetErrorSpace(ErrorSpaceAdlTag< PosixError > tag)
Binds the PosixErrorSpace class to the PosixError enum.
std::string SpaceName() const override
Gets a name that uniquely identifies the error space.
Definition: error_space.h:288