Skip to content

Commit

Permalink
fix Issue 13450 - Add WindowsException and wenforce to std.exception
Browse files Browse the repository at this point in the history
Also expose ErrnoException's errno field as a documented property
for consistency.
  • Loading branch information
CyberShadow committed Sep 11, 2014
1 parent 8b53ef9 commit b1418c9
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 37 deletions.
5 changes: 3 additions & 2 deletions std/exception.d
Expand Up @@ -1389,10 +1389,11 @@ unittest //more alias this opCast
*/
class ErrnoException : Exception
{
uint errno; // operating system error code
final @property uint errno() { return _errno; } /// Operating system error code.
private uint _errno;
this(string msg, string file = null, size_t line = 0) @trusted
{
errno = .errno;
_errno = .errno;
version (linux)
{
char[1024] buf = void;
Expand Down
151 changes: 116 additions & 35 deletions std/windows/syserror.d
Expand Up @@ -14,68 +14,149 @@
* http://www.boost.org/LICENSE_1_0.txt)
*/
module std.windows.syserror;

version (StdDdoc)
{
private
{
alias DWORD = uint;
enum LANG_NEUTRAL = 0, SUBLANG_DEFAULT = 1;
}

/// Query the text for a Windows error code (as returned by $(LINK2
/// http://msdn.microsoft.com/en-us/library/windows/desktop/ms679360.aspx,
/// $(D GetLastError))) as a D string.
string sysErrorString(
DWORD errCode,
// MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT) is the user's default language
int langId = LANG_NEUTRAL,
int subLangId = SUBLANG_DEFAULT) @trusted;

/*********************
* Thrown if errors that set $(LINK2
* http://msdn.microsoft.com/en-us/library/windows/desktop/ms679360.aspx,
* $(D GetLastError)) occur.
*/
class WindowsException : Exception
{
private alias DWORD = int;
final @property DWORD code(); /// $(D GetLastError)'s return value.
@disable this(int dummy);
}

/++
If $(D !!value) is true, $(D value) is returned. Otherwise,
$(D new WindowsException(GetLastError(), msg)) is thrown.
$(D WindowsException) assumes that the last operation set
$(D GetLastError()) appropriately.
Example:
--------------------
wenforce(DeleteFileA("junk.tmp"), "DeleteFile failed");
--------------------
+/
T wenforce(T, string file = __FILE__, size_t line = __LINE__)
(T value, lazy string msg = null);
}
else:

version (Windows):

import std.windows.charset;
import std.array : appender;
import std.format : formattedWrite;
import core.sys.windows.windows;

// MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT) is the user's default language
string sysErrorString(
uint errCode,
DWORD errCode,
// MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT) is the user's default language
int langId = LANG_NEUTRAL,
int subLangId = SUBLANG_DEFAULT) @trusted
{
wchar* pWideMessage;
auto buf = appender!string();

if (!putSysError(errCode, buf, MAKELANGID(langId, subLangId)))
{
throw new Exception(
"failed getting error string for WinAPI error code: " ~
sysErrorString(GetLastError()));
}

DWORD length = FormatMessageW(
return buf.data;
}

bool putSysError(Writer)(DWORD code, Writer w, /*WORD*/int langId = 0)
{
wchar *lpMsgBuf = null;
auto res = FormatMessageW(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
null,
errCode,
MAKELANGID(langId, subLangId),
cast(LPWSTR)&pWideMessage,
code,
0,
cast(LPWSTR)&lpMsgBuf,
0,
null);
scope(exit) if (lpMsgBuf) LocalFree(lpMsgBuf);

if(length == 0)
if (lpMsgBuf)
{
throw new Exception(
"failed getting error string for WinAPI error code: " ~
sysErrorString(GetLastError()));
import std.string : strip;
w.put(lpMsgBuf[0..res].strip());
return true;
}
else
return false;
}


scope(exit) LocalFree(cast(HLOCAL)pWideMessage);
class WindowsException : Exception
{
import core.sys.windows.windows;

/* Remove \r\n from error string */
if (length >= 2)
length -= 2;
final @property DWORD code() { return _code; } /// $(D GetLastError)'s return value.
private DWORD _code;

static int wideToNarrow(wchar[] wide, char[] narrow) nothrow
this(DWORD code, string str=null, string file = null, size_t line = 0) @trusted
{
return WideCharToMultiByte(
CP_UTF8,
0, // No WC_COMPOSITECHECK, as system error messages are precomposed
wide.ptr,
cast(int)wide.length,
narrow.ptr,
cast(int)narrow.length,
null,
null);
}
_code = code;

auto buf = appender!string();

auto wideMessage = pWideMessage[0 .. length];
if (str)
{
buf.put(str);
buf.put(": ");
}

int requiredCodeUnits = wideToNarrow(wideMessage, null);
auto success = putSysError(code, buf);
formattedWrite(buf, success ? " (error %d)" : "Error %d", code);

// If FormatMessage with FORMAT_MESSAGE_FROM_SYSTEM succeeds,
// there's no reason for the returned UTF-16 to be invalid.
assert(requiredCodeUnits > 0);
super(buf.data, file, line);
}
}

auto message = new char[requiredCodeUnits];
auto writtenLength = wideToNarrow(wideMessage, message);

assert(writtenLength > 0); // Ditto
T wenforce(T)(T value, lazy string msg = null,
string file = __FILE__, size_t line = __LINE__)
{
if (!value)
throw new WindowsException(GetLastError(), msg, file, line);
return value;
}

version(Windows)
unittest
{
import std.exception;
import std.string;

return cast(immutable)message[0 .. writtenLength];
auto e = collectException!WindowsException(
DeleteFileA("unexisting.txt").wenforce("DeleteFile")
);
assert(e.code == ERROR_FILE_NOT_FOUND);
assert(e.msg.startsWith("DeleteFile: "));
// can't test the entire message, as it depends on Windows locale
assert(e.msg.endsWith(" (error 2)"));
}

0 comments on commit b1418c9

Please sign in to comment.