Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 1 addition & 11 deletions src/core/IronPython/Runtime/AsyncGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,17 +103,7 @@ public object asend(object? value) {
/// </remarks>
[LightThrowing]
public object athrow(object? type, object? value, object? traceback) {
// Validate shape (mirrors PythonGenerator/PythonCoroutine.throw).
if (type is Exception || type is PythonExceptions.BaseException) {
if (value is not null)
return LightExceptions.Throw(PythonOps.TypeError("instance exception may not have a separate value"));
} else if (type is PythonType pt && typeof(PythonExceptions.BaseException).IsAssignableFrom(pt.UnderlyingSystemType)) {
// ok — class form
} else {
return LightExceptions.Throw(PythonOps.TypeError(
"exceptions must be classes or instances deriving from BaseException, not {0}",
PythonOps.GetPythonTypeName(type)));
}
if (PythonOps.ValidateThrowArguments(type, value) is Exception error) return LightExceptions.Throw(error);

Exception ex = PythonOps.MakeExceptionForGenerator(DefaultContext.Default, type, value, traceback, cause: null);
_started = true;
Expand Down
14 changes: 2 additions & 12 deletions src/core/IronPython/Runtime/Coroutine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,18 +97,8 @@ public object send(object? value) {
/// </remarks>
[LightThrowing]
public object @throw(object? type, object? value, object? traceback) {
// Validate the shape of (type, value, traceback) up front. Mirrors PythonGenerator.@throw
// so we don't mutate any state on bad input.
if (type is Exception || type is PythonExceptions.BaseException) {
if (value is not null)
return LightExceptions.Throw(PythonOps.TypeError("instance exception may not have a separate value"));
} else if (type is PythonType pt && typeof(PythonExceptions.BaseException).IsAssignableFrom(pt.UnderlyingSystemType)) {
// ok — class form, MakeExceptionForGenerator will construct.
} else {
return LightExceptions.Throw(PythonOps.TypeError(
"exceptions must be classes or instances deriving from BaseException, not {0}",
PythonOps.GetPythonTypeName(type)));
}
// Validate up front so we don't mutate any state on bad input.
if (PythonOps.ValidateThrowArguments(type, value) is Exception error) return LightExceptions.Throw(error);

// Construct the actual exception object. DefaultContext.Default suffices here — throw()'s
// CodeContext role is limited to resolving class-form exception construction, and the
Expand Down
50 changes: 30 additions & 20 deletions src/core/IronPython/Runtime/Exceptions/PythonExceptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@

namespace IronPython.Runtime.Exceptions {
/// <summary>
/// Implementation of the Python exceptions module and the IronPython/CLR exception mapping
/// Implementation of the Python exceptions module and the IronPython/CLR exception mapping
/// mechanism. The exception module is the parent module for all Python exception classes
/// and therefore is built-in to IronPython.dll instead of IronPython.Modules.dll.
///
///
/// The exception mapping mechanism is exposed as internal surface area available to only
/// IronPython / IronPython.Modules.dll. The actual exceptions themselves are all public.
///
///
/// Because the oddity of the built-in exception types all sharing the same physical layout
/// (see also PythonExceptions.BaseException) some classes are defined as classes w/ their
/// proper name and some classes are defined as PythonType fields. When a class is defined
Expand Down Expand Up @@ -324,20 +324,20 @@ internal static class Errno {
if error[i] < '0' or error[i] > '9':
error = error[:i]
break

if not error:
continue

errNo = OSError(0, justName, None, int(error)).errno
if errNo == 22 or errNo >= 10000:
continue
allErrors.append((justName, error))
l = toError.get(errNo)

l = toError.get(errNo)
if l is None:
toError[errNo] = l = []
l.append(justName)

for name, err in allErrors:
print('internal const int %s = %s;' % (name, err))

Expand Down Expand Up @@ -771,21 +771,31 @@ internal static BaseException CreatePythonThrowable(PythonType type, params obje
#nullable enable

internal static BaseException CreateBaseExceptionForRaise(CodeContext/*!*/ context, PythonType/*!*/ type, object? value) {
object? pyEx;
object? pyEx = CallForRaise(context, type, value);

if (pyEx is BaseException be) return be;

throw PythonOps.TypeError($"calling {PythonOps.ToString(type)} should have returned an instance of BaseException, not {PythonOps.ToString(DynamicHelpers.GetPythonType(pyEx))}");
}

internal static Exception CreateClrExceptionForRaise(CodeContext/*!*/ context, PythonType/*!*/ type, object? value) {
object? clrEx = CallForRaise(context, type, value);

if (clrEx is Exception ex) return ex;

throw PythonOps.TypeError($"calling {PythonOps.ToString(type)} should have returned an instance of Exception, not {PythonOps.ToString(DynamicHelpers.GetPythonType(clrEx))}");
}

private static object? CallForRaise(CodeContext/*!*/ context, PythonType/*!*/ type, object? value) {
if (PythonOps.IsInstance(value, type)) {
pyEx = value;
return value;
} else if (value is PythonTuple pt) {
pyEx = PythonCalls.Call(context, type, pt.ToArray());
return PythonCalls.Call(context, type, pt.ToArray());
} else if (value != null) {
pyEx = PythonCalls.Call(context, type, value);
return PythonCalls.Call(context, type, value);
} else {
pyEx = PythonCalls.Call(context, type);
return PythonCalls.Call(context, type);
}

if (pyEx is BaseException be) return be;

throw PythonOps.TypeError($"calling {PythonOps.ToString(type)} should have returned an instance of BaseException, not {PythonOps.ToString(DynamicHelpers.GetPythonType(pyEx))}");
}

/// <summary>
Expand Down Expand Up @@ -1002,7 +1012,7 @@ public static PythonType CreateSubType(PythonContext/*!*/ context, PythonType ba

/// <summary>
/// Creates a PythonType for a built-in module, where the type may inherit
/// from multiple bases. These types are mutable like normal user types.
/// from multiple bases. These types are mutable like normal user types.
/// </summary>
[PythonHidden]
public static PythonType CreateSubType(PythonContext/*!*/ context, PythonType[] baseTypes, Type underlyingType, string name, string module, string documentation, Func<string, Exception, Exception> exceptionMaker) {
Expand All @@ -1015,16 +1025,16 @@ public static PythonType CreateSubType(PythonContext/*!*/ context, PythonType[]

/// <summary>
/// Creates a new type for a built-in exception which derives from another Python
/// type. . These types are built-in and immutable like any other normal type. For
/// example StandardError.x = 3 is illegal. This isn't for module exceptions which
/// type. . These types are built-in and immutable like any other normal type. For
/// example StandardError.x = 3 is illegal. This isn't for module exceptions which
/// are like user defined types. thread.error.x = 3 is legal.
/// </summary>
private static PythonType CreateSubType(PythonType baseType, string name, Func<string, Exception, Exception> exceptionMaker) {
return new PythonType(baseType, name, exceptionMaker);
}

/// <summary>
/// Creates a new type for a built-in exception which is the root concrete type.
/// Creates a new type for a built-in exception which is the root concrete type.
/// </summary>
private static PythonType/*!*/ CreateSubType(PythonType/*!*/ baseType, Type/*!*/ concreteType, Func<string, Exception, Exception> exceptionMaker) {
Assert.NotNull(baseType, concreteType);
Expand Down
9 changes: 1 addition & 8 deletions src/core/IronPython/Runtime/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,7 @@ public object @throw(object? type, object? value, object? traceback) {
[LightThrowing]
private object @throw(object? type, object? value, object? traceback, bool finalizing) {
// Do argument validation outside of the generator's body and do not update the state on failure
if (type is Exception || type is PythonExceptions.BaseException) {
if (value is not null)
throw PythonOps.TypeError("instance exception may not have a separate value");
} else if (type is PythonType pt && typeof(PythonExceptions.BaseException).IsAssignableFrom(pt.UnderlyingSystemType)) {
// ok
} else {
throw PythonOps.TypeError("exceptions must be classes or instances deriving from BaseException, not {0}", PythonOps.GetPythonTypeName(type));
}
if (PythonOps.ValidateThrowArguments(type, value) is Exception error) return LightExceptions.Throw(error);

// Set fields which will then be used by CheckThrowable.
// We create the actual exception from inside the generator so that if the exception's __init__
Expand Down
16 changes: 16 additions & 0 deletions src/core/IronPython/Runtime/Operations/PythonOps.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2551,6 +2551,20 @@ internal static Exception MakeExceptionForGenerator(CodeContext/*!*/ context, ob
return e;
}

/// <summary>
/// Validates the arguments of generator/coroutine throw(). Returns the TypeError to raise, or null if valid.
/// </summary>
internal static Exception? ValidateThrowArguments(object? type, object? value) {
if (type is Exception || type is PythonExceptions.BaseException) {
if (value is not null) return TypeError("instance exception may not have a separate value");
} else if (type is PythonType pt && (typeof(PythonExceptions.BaseException).IsAssignableFrom(pt.UnderlyingSystemType) || typeof(Exception).IsAssignableFrom(pt.UnderlyingSystemType))) {
// ok
} else {
return TypeError("exceptions must be classes or instances deriving from BaseException, not {0}", GetPythonTypeName(type));
}
return null;
}

private static Exception MakeExceptionWorker(CodeContext/*!*/ context, object? type, object? value, object? traceback, object? cause, bool suppressContext, bool forRethrow) {
Exception throwable;

Expand All @@ -2560,6 +2574,8 @@ private static Exception MakeExceptionWorker(CodeContext/*!*/ context, object? t
if (type is Exception ex) {
// TODO: maybe add cause?
throwable = ex;
} else if (type is PythonType pt && typeof(Exception).IsAssignableFrom(pt.UnderlyingSystemType)) {
throwable = PythonExceptions.CreateClrExceptionForRaise(context, pt, value);
} else {
throwable = GetBaseException(context, type, value).CreateClrExceptionWithCause(causeException, contextException, suppressContext);
}
Expand Down
28 changes: 28 additions & 0 deletions tests/suite/test_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1079,4 +1079,32 @@ def f():

self.assertRaises(error, f)

@skipUnlessIronPython()
def test_ipy3_gh1816(self):
import System

with self.assertRaises(System.InvalidOperationException):
raise System.InvalidOperationException

with self.assertRaises(SystemError):
raise System.InvalidOperationException

class MyException(System.Exception): pass

with self.assertRaises(MyException):
raise MyException

with self.assertRaises(TypeError): # constructor requires arguments
raise System.Reflection.ReflectionTypeLoadException

def gen():
try:
yield
except System.InvalidOperationException as e:
yield e.Message

g = gen()
next(g)
self.assertEqual(g.throw(System.InvalidOperationException, "abc"), "abc")

run_test(__name__)
Loading