Управление исключениями

Содержание

  1. Основные понятия
  2. Синтаксис
  3. Выброс и перехват исключения
  4. Связанные и несвязанные с исключением сообщения
  5. Исключения и отладчик ASL
  6. Предупреждения и исключения
  7. Регистрация исключений, отсылающих к разработчику

Основные понятия

Под управлением исключениями (exception handling) понимают механизм для обнаружения и обработки необычных, непредвиденных и исключительных состояний и событий. Предоставляется формальный способ отклонить поток управления на неспецифицированную секцию кода - обработчик исключения, готовый принять контроль за данной исключительной ситуацией.

В теории механизмы управления исключениями классифицируют по категориям возвратности и структурности.

Интерпретатор ASL реализует структурную безвозвратную модель управления исключениями.

Когда блок кода выбрасывает исключение, управление передается некоторой секции кода, которая еще находится в стеке вызовов. Термин стек вызовов обозначает последовательность вызванных и еще не завершившихся процедур, функций, и тел программ. Разматыванием стека называется процесс выталкивания значений из стека, необходимый для того, чтобы уничтожить локальные переменные и возвратить управление вызывающему блоку кода. Когда процедура, функция, или тело программы возвращает управление, происходит естественное разматывание стека. Когда же некий блок кода выбрасывает исключение, происходит разматывание стека в процессе поиска обработчика исключения.

Исключения ASL либо выбрасываются явными вызовами специальных встроенных процедур типа HALT или RAISE, либо возникают неявно, а в некоторых случаях и асинхронно, из встроенных процедур и функций, процедур и функций исполняемых библиотек или собственно из интерпретатора ASL в результате обнаружения ошибок, после которых нормальное продолжение исполнения программы невозможно. Примерами таких ошибок могут служить отказ блокировки или обращение к записи таблицы БД при неустановленной позиции. Каждое исключение относится к одному из предопределенных классов исключений:

Классы исключений - это обычные классы ASB-объектов. Все перечисленные выше классы исключений происходят от общего предка - класса Exc_Root, а сам класс Exc_Root является абстрактным, т.е. объектов класса Exc_Root не может быть в принципе.

Синтаксис

Синтаксис TRY-блока см. здесь. Пример:

TRY [TRANSACTION(TRANSACTION_SLAVE),
     MSG_SUPPRESSION(MSG_SUPPRESSION_ALL),
     TERM_BY_USER(TERM_BY_USER_IGNORE)] (* список атрибутов TRY-блока *)
 (* операторы защищенного кода *)
EXCEPT
ON Exc: Exc_Halt DO
 (* тело обработчика исключений класса Exc_Halt *)
ON Exc_Raise DO
 (* тело обработчика исключений класса Exc_Raise *)
EXCEPT ON e: Exc_Root DO (* если переменная-дескриптор исключения не требуется, то достаточно указать просто ELSE *)
 (* тело обработчика необработанных исключений произвольных классов *)
FINALLY
 (* код гарантированного завершения *)
END

В описании перехватываемого исключения указывается класс исключения: Exc_Root или его потомок. При выбросе исключения, интерпретатор будет искать подходящий обработчик, т.е. такой, что фактический класс выброшенного исключения совпадает с формальным классом исключения или происходит от него. Кроме того (необязательно) в описании может быть указано имя переменной предопределенного типа - дескриптора исключения. Поскольку класс исключения играет в обработчике двойную роль (не только формальный тип дескриптора исключения, но и критерий выбора обработчика), неопределенный тип в обработчике исключения недопустим.

Большая часть свойств класса исключения описана в базовом классе Exc_Root и поэтому доступна для исключений всех классов. Свойства Exc_Root.ProgramFile (имя модуля), Exc_Root.Row (номер строки) и Exc_Root.Column (номер колонки) указывают место выброса исключения. Свойство Exc_Root.Class содержит имя класса выброшенного исключения, Exc_Root.ErrorCode - числовой код исключения, а Exc_Root.Description - его текстовое описание. Прочие свойства (Exc_Root.HasDeferredMsg, Exc_Root.HasSuppressedMsg, Exc_Root.SuppressedMsgIsGarbled, Exc_Root.ShowSuppressedMsg) относятся к связанному с исключением сообщению (подробнее см. ниже). Исключения классов Exc_AcessDeny, Exc_NotAllowed, Exc_TwiceAccess, Exc_NotOccure к наследуемым от Exc_Root свойствам добавляют строковое свойство File - имя файла БД, при работе с которым было выброшено исключение. Исключения класса Exc_Object имеют дополнительное строковое свойство Exc_Object.Source - имя источника исключения. Обычно это имя приложения. Все свойства дескриптора исключения доступны только для чтения.

Ссылка на объект-дескриптор исключения передается в обработчик исключения. Обработчик может воспользоваться дескриптором исключения для выяснения обстоятельств, при которых было выброшено исключение, и/или для формирования реакции на исключение.

TRY
 (* защищенный код *) 
EXCEPT 
ON Exc: Exc_Raise DO
 IF Exc.ErrorCode = -1 THEN
  (* -1 - это "Отменить" в процедуре STDMSG *)
  (* см. полный список исключений *)
  STDMSG("Ответ неправильный!", INFORM);
 END;
END;

Выброс и перехват исключения

По отношению к интерпретатору ASL исключения классов Exc_Raise, Exc_Halt, Exc_Object считаются внешними, а исключения прочих классов - внутренними. Внутренние исключения выбрасываются по инициативе интерпретатора или исполняемых процедур и функций библиотеки ASL. Для всех классов внутренних исключений коды исключений уникальны, поэтому в данном руководстве обычно указывается только код внутреннего исключения без упоминания класса. Класс внутреннего исключения, а также его описание по коду можно узнать в полном списке исключений ASL. Внешние исключения классов Exc_Raise и Exc_Halt выбрасываются по инициативе программиста явным вызовом специальных функций RAISE и HALT, а внешние исключения класса Exc_Object - по инициативе объектов COM автоматизации.

Исключения класса Exc_PermanentChanged являются неперехватываемыми. Обработчик исключения класса Exc_PermanentChanged - синтаксически корректная, но бессмысленная конструкция, этот код никогда не получит управления.

Исключения класса Exc_TermByUser являются условно перехватываемыми. Способность обработчиков TRY-блока перехватывать такие исключения определяется значением атрибута TERM_BY_USER и относится только к данному TRY-блоку, т.е. не наследуется вложенными TRY-блоками. Возможные значения перечислены в наборе констант TERM_BY_USER. Значение TERM_BY_USER_ABORT указывает, что данный TRY-блок не способен перехватывать исключения класса Exc_TermByUser, такие исключения будут пробрасываться дальше по стеку. Значение атрибута TERM_BY_USER_THROW указывает, что данный TRY-блок способен перехватить исключение класса Exc_TermByUser, в том числе обработчиком класса Exc_Root и ELSE-обработчиком. Значение TERM_BY_USER_IGNORE запрещает выбрасывание исключений класса Exc_TermByUser в данном TRY-блоке и во вложенных TRY-блоках, а значит, о перехвате таких исключений речь идти не может. Следует особо отметить, что код гарантированного завершения выполняется в том числе и для необработанного исключения Exc_TermByUser в TRY-блоке, неспособном перехватывать такие исключения.

При входе в TRY-блок порождается новый уровень текущей транзакции (или подчиненная транзакция). В случае нормального выхода из TRY-блока уровень транзакции (или подчиненная транзакция) успешно завершается, код обработчиков игнорируется, выполняется код гарантированного завершения, после чего исполнение продолжается с точки за концом TRY-блока.

При выбрасывании исключения стек разматывается до ближайшего TRY-блока. Соответствующий TRY-блоку уровень транзакции (или подчиненная транзакция) откатывается, курсоры таблиц БД восстанавливаются. Если среди обработчиков TRY-блока нет подходящего, выполняется код гарантированного завершения, исключение пробрасывается следующему TRY-блоку. Если среди обработчиков TRY-блока найден подходящий, выполняется код обработчика. В случае благополучного выхода из обработчика исключение считается обработанным, после выполнения блока гарантированного завершения исполнение продолжится с точки за концом TRY-блока. Если обработчик исключения по каким-то причинам не может обработать исключение, он имеет право пробросить это исключение далее (охватывающему TRY-блоку) при помощи процедуры RAISE (вариант без параметров). Кроме того, код обработчика исключения может выбросить новое исключение. В обоих случаях, прежде чем разматывать стек до следующего TRY-блока, выполняется блок гарантированного завершения.

В блоке гарантированного завершения запрещены операторы EXIT, RETURN и HALT(0), в случае исполнения одного из них будет выброшено исключение 534. Проблема в том, что необработанное исключение после исполнения кода гарантированного завершения должно быть проброшено далее по стеку, а перечисленные операторы предполагают иной порядок работы.

Если исключение не было перехвачено в стеке программного комплекса, считается что программный комплекс завершился аварийно. В этом случае будет исполнен предопределенный обработчик исключений по умолчанию. Он проверит, связано ли с исключением отложенное сообщение (см. ниже). Если такое сообщение имеется, обработчик по умолчанию попытается воспроизвести его.

Нужно особо отметить, что HALT(0) не выбрасывает исключения. Считается, что HALT(0) - это нормальный возврат из CALL.

Связанные и несвязанные с исключением сообщения

Сообщения могут быть связаны или не связаны с исключением. Например, вызов STDMSG(..., STOP) выбрасывает исключение, соответствующее сообщение будет связано с исключением. С другой стороны, вызов STDMSG(..., INFORM) исключения не выбрасывает, т.е. сообщение не связано с исключением.

Если исключение выброшено внутри TRY-блока, оно еще может быть впоследствии обработано, поэтому вывод связанного с исключением сообщения по возможности откладывается. Если исключение будет обработано, тогда и сообщение показывать незачем. Флаговое свойство Exc_Root.HasDeferredMsg информирует программиста о наличии связанного с сообщением отложенного сообщения.

Если связанное с исключением сообщение содержит только кнопки отказа, то у пользователя нет никакого выбора, это не более чем информирование об ошибке, соответственно, показ такого сообщения можно отложить до момента, когда исключение приведет к аварийному завершению программы. А если не приведет, отложенное сообщение не будет показано. Т.о. связанные с исключением сообщения, содержащие только кнопки отказа, называются откладываемыми. Если же связанное с исключением сообщение содержит не только кнопки отказа, то отложить сообщение нельзя; именно в этой точке потока исполнения пользователь должен сделать выбор. Такие сообщения называются неоткладываемыми. Особым случаем является сообщение интерпретатора ASL о необработанном внутреннем исключении; помимо кнопки отказа "Отменить" оно содержит также кнопку "Поправить", однако считается откладываемым. Сообщения, несвязанные с исключением, не откладываются, ибо откладывать их бессмысленно.

Любое сообщение (связанное или несвязанное с исключением) может быть подавлено. Режим подавления сообщений в пределах защищенного кода TRY-блока задается атрибутом MSGSUPPRESSIONMODE. Возможные значения атрибута перечислены в наборе констант MSGSUPPRESS. Режим подавления сообщений наследуется вложенными TRY-блоками. Вложенные TRY-блоки не могут понизить режим подавления сообщений. Если в атрибуте TRY-блока будет задан более низкий режим, чем действует вне блока защищенного кода, установка вложенного TRY-блока будет проигнорирована. На время выполнения DBS-программы вне отладчика система автоматически устанавливает режим подавления сообщений MSGSUPPRESS_ALL, на время выполнения NTF-программы - режим MSGSUPPRESS_INFINITE.

Несвязанное с исключением сообщение в момент подавления исчезает бесследно. Связанное с исключением подавленное сообщение, как и связанное отложенное сообщение, хранится в объекте класса Exc_Root, чтобы иметь возможность воспроизвести его впоследствии. Флаговое свойство Exc_Root.HasSuppressedMsg информирует программиста о наличии связанного с исключением подавленного сообщения.

Связанное с исключением неоткладываемое (т.е. содержащее не только кнопку отказа) сообщение в момент подавления искажается; все элементы управления кроме кнопок отказа запрещаются. Только в таком виде (без выбора) оно может быть впоследствии показано пользователю, ибо момент выбора упущен. Флаговое свойство Exc_Root.SuppressedMsgIsGarbled информирует об искажении связанного с исключением подавленного сообщения.

Поднятый флаг Exc_Root.ShowSuppressedMsg интерпретируется как требование воспроизвести связанное с необработанным исключением подавленное сообщение в момент вылета необработанного исключения за пределы TRY-блока, т.е. после возможной смены режима подавления. Если на момент вылета необработанного исключения за пределы TRY-блока флаг Exc_Root.ShowSuppressedMsg поднят, будет предпринята попытка воспроизвести сообщение. Результат зависит от действующего режима подавления сообщений, возможно сообщение вновь будет подавлено.

Исключения и отладчик ASL

При выполнении программы под отладчиком в момент первичного выброса исключения исполнение программного комплекса приостанавливается, в строке статуса окна отладчика появляется сообщение об исключении. Тем же способом отладчик сообщает, что исключение полностью размотало стек и не было обработано, в результате чего программный комплекс завершается аварийно. Эта функция называется остановом отладчика по исключению.

Отладчик позволяет интерактивно запретить/разрешить останов для исключений указанных классов и кодов. По умолчанию запрещен останов для исключений класса Exc_Raise с кодами от -120 до -100 внутри TRY-блока и разрешен останов для всех прочих исключений.

Атрибут EXCEPTION_HANDLING TRY-блока и процедура SE_EXCEPTIONHANDLING позволяют программно запретить/разрешить останов отладчика во время первичного выброса исключения (любого, без подразделения по классам и кодам) в пределах текущего TRY-блока. Свойство TRY_BLOCK.EXCEPTION_HANDLING и процедура GE_EXCEPTIONHANDLING возвращают текущий режим останова отладчика по исключению.

Во время останова в блоке обработчика исключения отладчик позволяет посмотреть свойства дескриптора исключения.

Предупреждения и исключения

Добавление новых исключений в код интерпретатора и библиотек ASL может вести к несовместимости. Иногда объем необходимых исправлений ASL-модулей оказывается настолько велик или настолько неопределен, что вместо исключения на переходный период вводится предупреждение. В отличие от исключения предупреждение предназначено только для информирования программиста и не приводит к отклонению потока исполнения программы. Предполагается, что по завершении переходного периода предупреждение будет заменено исключением. В настоящее время применяется два способа информирования программиста о предупреждении.

  1. В инженерной копии проекта для программистов поднимается сообщение-предупреждение, позволяющее либо (по нажатию кнопки отказа) выбросить соответствующее исключение, либо продолжить выполнение программы. На сообщения-предупреждения распространяются обычные правила подавления сообщений. Если сообщение будет подавлено, автоматически сработает кнопка отказа, и будет выброшено исключение. В рабочей копии проекта или для непрограммиста в инженерной копии не будет ни сообщения, ни тем более исключения.
  2. Если не было выброшено соответствующее исключение, запись о предупреждении однократно прописывается в журнал ECN.

Как правило, применяются оба способа информирования программиста; в некоторых случаях может быть задействован только второй способ.

Регистрация исключений, отсылающих к разработчику

В версии 14.171.070 был добавлен элемент возвратной обработки исключений. Строковое свойство APP.OnCallAuthor содержит полное имя функции-регистратора исключений со стилем STOP_AND_CALL_AUTHOR, т.е. с отсылкой к разработчикам. Функция должна принимать в качестве параметра дескриптор исключения, а возвращать строку с новым текстом описания исключения.

PROCEDURE RegisterCallAuthorException(exc: Exc_Root): STRING[];

Функция будет вызвана процедурным оператором CALL в момент первичного выброса исключения при соблюдении следующих условий:

  1. Поток исполнения не находится внутри функции-регистратора или внутри отладочного выражения (в отладчике).
  2. В точке выброса действует режим EXCEPTION_HANDLING_ON реагирования исполнительной системы.
  3. Функция-регистратор существует и подходит под вышеприведенное описание.
  4. Программный комплекс - типа ASL, NTF или опция CALL/CallBP.
  5. Свойство OnCallAuthor содержит непустую строку - составной идентификатор, соответствующая функция существует и подходит под вышеприведенное описание.
  6. Исключение содержит отложенное сообщение (Exc_Root.HasDeferredMsg), либо подавленное (Exc_Root.HasSuppressedMsg) неискаженное (~Exc_Root.SuppressedMsgIsGarbled) сообщение.

Если функция завершится успешно, возвращенная ею строка переопределит текст описания выброшенного исключения (Exc_Root.Description) и текст связанного (отложенного или подавленного) сообщения об ошибке. Возвращаемая функцией строка, подобно параметру message процедуры STDMSG, может содержать переводы строк, задаваемые символом '|', символом CHR(10) или подстрокой CHR(13)+CHR(10), а с версии 14.180.010, - и тэги отчеркивания. Если функция-регистратор завершится необработанным исключением, оно будет перехвачено интерпретатором ASL и заменено исходным исключением. При любом завершении функции будет проброшено исходное исключение, либо с модифицированными описанием и сообщением, либо с исходными.