Ответ 1
По моему честному мнению, теоретически утверждение может "потерпеть неудачу" (практически я так не думаю).
Как?
Примечание. Ниже приведены только мои "мнения" на основе некоторых исследований, которые я ранее сделал в SSCLI.
- Может возникнуть InvalidProgramException. Это, по общему признанию, крайне маловероятно, но, тем не менее, теоретически возможно (например, некоторая внутренняя ошибка CLR может привести к тому, что бросающийся объект станет недоступным!!!!).
- Если CLR не найдет достаточно памяти для обработки действия "повторного броска", вместо этого вместо него будет выбрано исключение OutOfMemoryException (внутренняя логика повторного броска CLR требует выделения некоторой памяти, если она не имеет отношения к "предварительно выделенным" исключениям, например OutOfMemoryException).
- Если среда CLR запущена под каким-либо другим хостом (например, SQL-сервер или даже ваш собственный), и хост решает прекратить поток повторного броска исключения (на основе некоторой внутренней логики) ThreadAbortException (известный как прерывание отбойного потока в этом случае). Хотя, я не уверен, что Assert даже выполнит в этом случае.
- Пользовательский хост, возможно, применил политику эскалации к CLR (ICLRPolicyManager:: SetActionOnFailure). В этом случае, если вы имеете дело с OutOfMemoryException, политика эскалации может привести к возникновению ThreadAbortException (снова грубый поток прерывается. Не уверен, что произойдет, если политика диктует нормальный поток прерывания).
- Хотя @Alois Kraus поясняет, что исключение "нормального" прерывания потока невозможно, из исследования SSCLI я все еще сомневаюсь, что может произойти (нормальное) ThreadAbortException.
Edit:
Как я уже говорил, утверждение может терпеть неудачу теоретически, но практически маловероятно. Поэтому для этого очень сложно разработать POC.
Чтобы предоставить больше "доказательств", следуют фрагменты кода SSCLI для обработки инструкции rethow
IL, которые подтверждают мои вышеуказанные баллы.
Предупреждение. Коммерческая среда CLR может сильно отличаться от SSCLI.
-
InvalidProgramException:
if (throwable != NULL) { ... } else { // This can only be the result of bad IL (or some internal EE failure). RealCOMPlusThrow(kInvalidProgramException, (UINT)IDS_EE_RETHROW_NOT_ALLOWED); }
-
Отказоустойчивость:
if (pThread->IsRudeAbortInitiated()) { // Nobody should be able to swallow rude thread abort. throwable = CLRException::GetPreallocatedRudeThreadAbortException(); }
Это означает, что если был запущен "грубый откат нити", любое исключение будет изменено на исключение из строгой отмены прерывания.
-
Теперь самое интересное,
OutOfMemoryException
. Поскольку команда rethrow IL по существу перебрасывает один и тот же объект Exception (т.е.object.ReferenceEquals
возвращает true), кажется невозможным, что OutOfMemoryException может возникать при повторном броске. Однако, следующий код SSCLI показывает, что это возможно:// Always save the current object in the handle so on rethrow we can reuse it. This is important as it // contains stack trace info. // // Note: we use SafeSetLastThrownObject, which will try to set the throwable and if there are any problems, // it will set the throwable to something appropiate (like OOM exception) and return the new // exception. Thus, the user exception object can be replaced here. throwable = pThread->SafeSetLastThrownObject(throwable);
SafeSetLastThrownObject
вызывает SetLastThrownObject, и если он терпит неудачу, возникаетOutOfMemoryException
. Вот фрагмент отSetLastThrownObject
(добавлены мои комментарии)... if (m_LastThrownObjectHandle != NULL) { // We'll somtimes use a handle for a preallocated exception object. We should never, ever destroy one of // these handles... they'll be destroyed when the Runtime shuts down. if (!CLRException::IsPreallocatedExceptionHandle(m_LastThrownObjectHandle)) { //Destroys the GC handle only but not the throwable object itself DestroyHandle(m_LastThrownObjectHandle); } } ... //This step can fail if there is no space left for a new handle m_LastThrownObjectHandle = GetDomain()->CreateHandle(throwable);
Над фрагментами кода показано, что дескриптор GC дескриптора объекта уничтожается (т.е. освобождает слот в таблице GC), а затем создается новый дескриптор. Поскольку слот только что был выпущен, новое создание дескриптора никогда не завершится до тех пор, пока не пройдет курс в очень редком сценарии нового потока, который будет запланирован только в нужное время и будет потреблять все доступные GC-ручки.
Кроме того, все исключения (в том числе rethrows) поднимаются через RaiseException win api. Код, который ловит это исключение для подготовки соответствующего управляемого исключения, сам может поднять OutOfMemoryException
.