Коллеги, в ядре имеются средства для "защищенного" вызова процедур:
Код:
TYPE TryHandler = PROCEDURE (a, b, c: INTEGER)
The type of procedure that can be called with a local trap handler by means of Try.
PROCEDURE Try (h: TryHandler; a, b, c: INTEGER)
Calls the procedure h with parameters a, b, and c with a local trap handler.
If a trap occurs inside h a trap window is displayed and h is terminated but the execution is continued as if Try proceeded normally.
Модуль HostWindows много использовал эти средства, поскольку оконная система должна быть защищена от авостов в клиентских процедурах: иначе "посыпется" оконная система, когда какой-нибудь вид даст сбой, например, во время отработки нажатий.
Очевидны недостатки:
1. чтобы пользоваться Try, приходится забыть о контроле типов и пользоваться SYSTEM.VAL для приведения типов:
Код:
Kernel.Try(P, SYSTEM.VAL(INTEGER, SomePtr), 0, 0)
PROCEDURE P (a, b, c: INTEGER);
VAR ptr: SomePtr;
BEGIN ... ptr := SYSTEM.VAL(SomePtr, a)...
END P;
2. Параметров всегда три: если не нужны - передавать абы-чего, если нужно больше - изворачиваться.
Я предлагаю
решение, которое устранит эти недостатки; оно опробовано в экспериментальном Kernel в составе Tyler:
Код:
MODULE Kernel;
TYPE SafeAction* = ABSTRACT RECORD
trapped-: BOOLEAN
END;
PROCEDURE (VAR a: SafeAction) Do*, NEW, ABSTRACT;
PROCEDURE Do* (VAR a: SafeAction);
END Kernel.
MODULE Client;
TYPE SafeHandler = RECORD (Kernel.SafeAction)
str: ARRAY OF CHAR;
ptr: SomePtr
END;
PROCEDURE (VAR h: SafeHandler) Do;
BEGIN
PerformSomeRiskyActivity(h.str);
IF h.ptr # NIL THEN AnotherRiskFactor(ptr) END
END Do;
PROCEDURE P;
VAR h: SafeHandler;
BEGIN
h.str := "This is a safe handler";
h.ptr := something;
Kernel.Do(h);
IF h.trapped THEN Beda() ELSE Ura() END
END P;
END Client.
Таким образом, решаются обе проблемы Kernel.Try.
Перечислю
достоинства:
1. высокоуровневая проверка типов, повышается безопасность и надежность программирования;
2. бОльшая гибкость в передаче параметров;
3. возможность анализировать результат защищенного вызова: поле .trapped;
4. SafeActions объявлены и реализованы как записи (а не указатели), поэтому они могут располагаться в автоматической памяти (стеке), не требуют дорогостоящих обращений к менеджеру памяти, не нуждаются в сборке мусора; при этом допускают размещение и в динамической, и в статической памяти.
Use case: В проекте Тайлер в модуле HostWindows я повсеместно заменил Kernel.Try на Kernel.Do/Kernel.SafeAction, чем существенно ускорил отладку своих правок в HostWindows.
А затем оказалось, что SafeAction гораздо шире может применяться, чем как в HostWindows - на стыке хостовой небезопасной системы и ББ. Поскольку я изменил реализацию окон, процедура открытия окна Windows.Open стала зависеть от вызовов расширений. Т.о. если в расширении происходит авост, "валится" и оконная система - нарушаются инварианты внутренних структур данных. Оказалось удобным небезопасную (зависящую от расширений) часть процедуры Open выделить в TYPE SafeOpen = RECORD (Kernel.SafeAction), и тем самым удалось гарантировать сохранность внутренних инвариантов модуля Windows. Без Kernel.SafeAction это потребовало бы использовать Kernel.Try и SYSTEM.VAL, что нежелательно для высокоуровневого модуля Windows. При этом у SafeOpen множество полей - по сути, параметров процедуры Do.
Реализация Kernel.Do практически не отличается от текущей реализации Kernel.Try.
ПРЕДЛАГАЮ:(1) включить приведенные Kernel.SafeAction и Kernel.Do в состав ядра;
(2) объявить Kernel.Try и Kernel.TryHandler устаревшими и неспешно избавиться от них через пару-тройку лет.
P.S.
Мои эксперименты проводил под Linux
P.P.S.
Для одного системного инструмента - трассировщика сообщений - хотелось вызывать защищенную процедуру собственно с сообщением, т.е. передавать ей VAR-параметром запись, размещенную в стеке. Сделал так:
Код:
MODULE Kernel;
TYPE
SafeRecAction* = ABSTRACT RECORD (SafeAction)
prep: BOOLEAN; (* DoRec => Do; this flags to Do that it was called *)
adr, typedesc: INTEGER; (* address of VAR rec: ANYREC on stack; *)
END;
PROCEDURE (VAR a: SafeRecAction) DoRec* (VAR rec: ANYREC), NEW, ABSTRACT;
PROCEDURE DoRec* (VAR a: SafeRecAction; VAR rec: ANYREC);
END Kernel.
Реализация DoRec Полагается на Do:
Код:
PROCEDURE DoRec* (VAR a: SafeRecAction; VAR rec: ANYREC);
BEGIN a.adr := S.ADR(rec); a.typedesc := S.TYP(rec); a.prep := TRUE; Do(a)
END DoRec;
Т.е. DoRec просто "шаманит" с адресами, гарантируя клиентам безопасность.
Прилагаю реализацию Do. Зеленым - то, что взято из Try, бирюсовым - то, что добавлено.
(Вы добросовестно дочитали до конца мой лонгрид? Весьма вам признателен
)