Skip to content

Commit 9162b30

Browse files
authored
Improve exception messages when Windows fails to load an extension module (#1467)
1 parent 16cb4b6 commit 9162b30

3 files changed

Lines changed: 32 additions & 5 deletions

File tree

Src/IronPython.Modules/_ctypes/_ctypes.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,8 @@ public static int CopyComPointer(object src, object dest) {
164164
throw new NotImplementedException("CopyComPointer");
165165
}
166166

167+
#nullable enable
168+
167169
public static string FormatError() {
168170
return FormatError(get_last_error());
169171
}
@@ -172,6 +174,15 @@ public static string FormatError(int errorCode) {
172174
return new Win32Exception(errorCode).Message;
173175
}
174176

177+
private static string FormatError(int errorCode, string? fileName) {
178+
string msg = FormatError(errorCode);
179+
// error codes: https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes
180+
if (errorCode is not (< 0 or >= 8200 or 34 or 106 or 317 or 718)) {
181+
msg = msg.Replace("%1", $"'{fileName}'");
182+
}
183+
return msg;
184+
}
185+
175186
[SupportedOSPlatform("windows"), PythonHidden(PlatformsAttribute.PlatformFamily.Unix)]
176187
public static void FreeLibrary(int handle) {
177188
FreeLibrary(new IntPtr(handle));
@@ -187,13 +198,17 @@ public static void FreeLibrary(IntPtr handle) {
187198
NativeFunctions.FreeLibrary(handle);
188199
}
189200

190-
#nullable enable
191-
192201
private static object LoadDLL(string? library, int mode) {
193202
if (library is not null && library.IndexOf((char)0) != -1) throw PythonOps.ValueError("embedded null byte");
194203
IntPtr res = NativeFunctions.LoadDLL(library, mode);
195204
if (res == IntPtr.Zero) {
196-
throw PythonOps.OSError($"cannot load library {library}");
205+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
206+
int code = NativeFunctions.GetLastError();
207+
string msg = FormatError(code, library);
208+
throw PythonExceptions.CreateThrowable(PythonExceptions.OSError, 0, msg, null, code);
209+
}
210+
211+
throw PythonOps.OSError("cannot load library '{0}'", library);
197212
}
198213

199214
return res.ToPython();

Tests/modules/type_related/test_ctypes.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
from array import array
1111
import sys
1212
import gc
13+
import unittest
1314

14-
from iptest import IronPythonTestCase, is_cli, big, myint, run_test
15+
from iptest import IronPythonTestCase, is_posix, is_cli, big, myint, run_test
1516

1617
class CTypesTest(IronPythonTestCase):
1718
export_error_msg = "Existing exports of data: object cannot be re-sized" if is_cli else "cannot resize an array that is exporting buffers"
@@ -219,4 +220,15 @@ class TestU(Structure):
219220
self.assertEqual(TestU(-(1 << 64)).x, 0)
220221
self.assertEqual(TestU(-(1 << 64) - 1).x, 0x7fffffffffffffff)
221222

223+
@unittest.skipIf(is_posix, 'Windows specific test')
224+
def test_loadlibrary_error(self):
225+
with self.assertRaises(OSError) as cm:
226+
windll.LoadLibrary(__file__)
227+
228+
self.assertEqual(cm.exception.errno, 8)
229+
self.assertEqual(cm.exception.winerror, 193)
230+
self.assertIn(" is not a valid Win32 application", cm.exception.strerror)
231+
self.assertIsNone(cm.exception.filename)
232+
self.assertIsNone(cm.exception.filename2)
233+
222234
run_test(__name__)

WhatsNewInPython30.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ Changes To Exceptions
102102
- [x] [PEP 3110][]: Catching exceptions. You must now use `except SomeException as variable` instead of `except SomeException, variable`. Moreover, the variable is explicitly deleted when the `except` block is left.
103103
- [x] [PEP 3134][]: Exception chaining. There are two cases: implicit chaining and explicit chaining. Implicit chaining happens when an exception is raised in an `except` or `finally` handler block. This usually happens due to a bug in the handler block; we call this a secondary exception. In this case, the original exception (that was being handled) is saved as the `__context__` attribute of the secondary exception. Explicit chaining is invoked with this syntax: `raise SecondaryException() from primary_exception` (where `primary_exception` is any expression that produces an exception object, probably an exception that was previously caught). In this case, the primary exception is stored on the `__cause__` attribute of the secondary exception. The traceback printed when an unhandled exception occurs walks the chain of `__cause__` and `__context__` attributes and prints a separate traceback for each component of the chain, with the primary exception at the top. (Java users may recognize this behavior.)
104104
- [x] [PEP 3134][]: Exception objects now store their traceback as the `__traceback__` attribute. This means that an exception object now contains all the information pertaining to an exception, and there are fewer reasons to use `sys.exc_info()` (though the latter is not removed).
105-
- [ ] A few exception messages are improved when Windows fails to load an extension module. For example, error code 193 is now `%1 is not a valid Win32 application`. Strings now deal with non-English locales.
105+
- [x] A few exception messages are improved when Windows fails to load an extension module. For example, error code 193 is now `%1 is not a valid Win32 application`. Strings now deal with non-English locales.
106106

107107
Operators And Special Methods
108108
===============

0 commit comments

Comments
 (0)