Echo Writes Code

windows_errors.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
#include "crucible/core/errors.hpp"

#include <type_traits>

#include <Windows.h>

#include "crucible/core/format.hpp"
#include "crucible/core/memory.hpp"
#include "crucible/core/result.hpp"
#include "crucible/core/unicode.hpp"
#include "crucible/core/utility.hpp"

namespace crucible
{
  windows_error::windows_error() noexcept :
    my_code { static_cast<std::uint32_t>(::GetLastError()) }
  {}

  windows_error::windows_error(std::uint32_t const code) noexcept :
    my_code { code }
  {}

  auto windows_error::code() const noexcept -> std::uint32_t
  {
    return my_code;
  }

  auto windows_error::message() const -> string
  {
    constexpr ::DWORD FLAGS {
      FORMAT_MESSAGE_ALLOCATE_BUFFER |
      FORMAT_MESSAGE_FROM_SYSTEM |
      FORMAT_MESSAGE_IGNORE_INSERTS |
      FORMAT_MESSAGE_MAX_WIDTH_MASK
    };

    constexpr ::DWORD LANGUAGE_ID { 0 };
    constexpr ::DWORD MINIMUM_SIZE { 0 };

    // If `::GetLastError()` happens to return `ERROR_SUCCESS`, we override the default message
    // because it sucks
    // See: https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
    if (my_code == ::ERROR_SUCCESS)
    {
      return "No further information";
    }

    ::LPVOID message_source { nullptr };
    ::LPWSTR raw_wide_message { nullptr };

    // Make sure we don't lose information going from DWORD -> std::uint32_t
    static_assert(sizeof(::DWORD) <= sizeof(std::uint32_t));
    static_assert(std::is_unsigned_v<::DWORD> == std::is_unsigned_v<std::uint32_t>);
    auto const code { static_cast<::DWORD>(my_code) };

    // I *promise* that there is no other way to do this... the API documentation specifically
    // says we need to perform this cast :(
    // See: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-formatmessagew
    auto dont_blame_me_blame_microsoft { reinterpret_cast<::LPWSTR>(&raw_wide_message) };

    // Using the one brought in by Windows.h just in case (as opposed to the one in <cstdarg>, which
    // is std::va_list)
    ::va_list *arguments { nullptr };

    ::SetLastError(::ERROR_SUCCESS);
    ::DWORD const code_units_written {
      ::FormatMessageW(
        FLAGS,
        message_source,
        code,
        LANGUAGE_ID,
        dont_blame_me_blame_microsoft,
        MINIMUM_SIZE,
        arguments)
    };

    if (code_units_written == 0) {
      return "Failed to format error message (Windows API error code: " + format_integral(my_code) + ")";
    }

    local_alloc_memory_guard memory_guard { raw_wide_message };

    ::DWORD const code_units_written_excluding_terminator { code_units_written - 1 };
    ffi_string<wchar_t> const wide_message { raw_wide_message, code_units_written_excluding_terminator };

    auto transcoding_result { bulk_transcode_wide_to_utf8(wide_message) };

    auto const handle_failure {
      [this](unicode_error const &) {
        return "Failed to transcode error message to UTF-8 (Windows API error code: " + format_integral(my_code) + ")";
      }
    };

    return std::move(transcoding_result).resolve(handle_failure, identity);
  }

  // Errors arising from calling a Windows API function

  auto create_directory_failed::describe() const -> string
  {
    return "::CreateDirectoryW() failed: " + windows_error::message();
  }

  auto create_file_failed::describe() const -> string
  {
    return "::CreateFileW() failed: " + windows_error::message();
  }

  auto delete_file_failed::describe() const -> string
  {
    return "::DeleteFileW() failed: " + windows_error::message();
  }

  auto find_first_file_failed::describe() const -> string
  {
    return "::FindFirstFileW() failed: " + windows_error::message();
  }

  auto find_next_file_failed::describe() const -> string
  {
    return "::FindNextFileW() failed: " + windows_error::message();
  }

  auto get_file_attributes_failed::describe() const -> string
  {
    return "::GetFileAttributesW() failed: " + windows_error::message();
  }

  auto get_file_size_ex_failed::describe() const -> string
  {
    return "::GetFileSizeEx() failed: " + windows_error::message();
  }

  auto multi_byte_to_wide_char_failed::describe() const -> string
  {
    return "::MultiByteToWideChar() failed: " + windows_error::message();
  }

  auto read_file_failed::describe() const -> string
  {
    return "::ReadFile() failed: " + windows_error::message();
  }

  auto remove_directory_failed::describe() const -> string
  {
    return "::RemoveDirectoryW() failed: " + windows_error::message();
  }

  auto SymInitialize_failed::describe() const -> string
  {
    return "::SymInitialize() failed: " + windows_error::message();
  }

  auto wide_char_to_multi_byte_failed::describe() const -> string
  {
    return "::WideCharToMultiByte() failed: " + windows_error::message();
  }

  auto write_file_failed::describe() const -> string
  {
    return "::WriteFile() failed: " + windows_error::message();
  }

  // Errors arising from a precondition or postcondition being violated

  auto multi_byte_to_wide_char_input_overflow::describe() const -> string
  {
    return "Input string is too large for ::MultiByteToWideChar()";
  }

  auto multi_byte_to_wide_char_output_overflow::describe() const -> string
  {
    return "Output string was truncated by ::MultiByteToWideChar()";
  }

  auto negative_file_size::describe() const -> string
  {
    return "File size was reported as a negative value";
  }

  auto wide_char_to_multi_byte_input_overflow::describe() const -> string
  {
    return "Input string is too large for ::WideCharToMultiByte()";
  }

  auto wide_char_to_multi_byte_output_overflow::describe() const -> string
  {
    return "Output string was truncated by ::WideCharToMultiByte()";
  }
}