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