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