Table of Contents
There are two kinds of exception processors catchers and handlers. Exception support in the language could be oriented on one of them or on the both.
Handlers process exception by calling handler as function, this function is could do many interesting things, for example it could ask user to increase disk quota for process if limitation were hit during saving the file. POSIX signals or Dylan handler could be likely examples of this.
Example 1. Handler example
/// source method method someMethod() { try { WriteFile(params) } handle (e: QuotaExceededException) { if(AskUserForMoreQuota()) { retry } else { throw e } } }
Catchers are kinds of exception processor that require stack to be collapsed before control could be transferred to them. Most OO languages like Java, C++ have only exception catchers. A disadvantage of this variant is that program has no chance to fix problem.
Example 2. Catcher example
to callingMethod(data:bytes):int { try { return calledMethod(data) } catch (e is MyException) { performHandling(e) } }
These two strategies are not mutually exclusive, and they could live together. Usually it is implemented with last handler throwing an exception that is being processed.
Exceptions do not need to be primitive construct, as they could be expressed using other constructs.
Exception handling could be understood as syntactic sugar over normal invocation. There could be optimizations involved in it, by they are not significant to semantics.
Handlers could be considered as "in" parameter of following type handler. This could be passed as first argument to all methods. Handlers logically form linked list
Example 3. Expanded handler example
/// compiled code method someMethod(cur:HalerBase) { newh = new SomeSpecificContextHandler(cur) WriteFile(newh,params) } /// base class in system library class BaseHandler { val protected Next:Handler maker BaseHandler(n:Hander) { Next = n } to Handle(e:Exception) { throw Exception } } /// class implemeting handler class SomeSpecificContextHandler { extends BaseHandler val ctx : CallContext to Handle(e:Exception) { if(e is QuotaExceededException) { if(ctx.AskUserForMoreQuota()) { return } else { throw e } } Next.Handle(e) } }
Support for catchers could be implemented as invisible out parameter like it were done in IBM's SOM for OS/2. So they do not directly contradict capability security too.
Example 4. Expanded catcher example
to calledMethod(out e:Exception, data:bytes):int to callingMethod(out callersE:Exception, data:bytes):int { var e:Exception = null rc = calledMethod(e,data) if(e != null) { goto catchers } // perform normal actions here return rc catchers: if(e is MyException){ performHandling(e) } else { callersE = e return 0 // any value will work } }
In most langauges there is little attention paid to security of exceptions. In Java catchable runtime exception could travel freely. In CLI all catchable exception could do so. In Dylan [1], handlers could be installed for any exception.
Implemented in this way, such syntactic sugar raises interesting security issues. Handler ideology provide communication channel between stack frames with intermediate layers in stack. This could be unexpected communication channel. Frame that is deeper in stack, will could get authority that were given only to frame that higher on stack.
Intermediate frames in stack could easily block communication by installing new blocking handler that will throw all exceptions rather then handle it or simply consume it, but blocking should be exlicitly inserted and communications will be available by default which is not so good security feature.
There is less risk in catchers as they are one-time message passing rather then continuous channel, but they could still leak secrets like passwords to upper level handlers.
Also there is kind of exceptions that could be happening at any time, like NullPointerException or ArrayIndexOutOfBoundException. These exceptions are interesting only to limited class of clients and unlikely will be handled in code, but they provide valuable debugging information that helps quickly locate and fix bugs.
Exceptions should be introduced w/o introducing security risks. Both catchers and handlers are very convenient means of program engineering. But their security risk should be eliminated or significantly reduced.
Currently suggested solution is to require that handlers should be explicitly declared. Because of it passing of handlers to inner components will not implicitly cross layers. (It should be also possible to reactivate handlers explicitly as objects for complete solution)
Example 5. Exception declaration
to WriteData(data:ISequence[Byte]):int \ notifies QuotaExcededException \ throws IOException
In this example methods only handlers for QuotaExcededException and its subclasses will go inside. Other handlers will stop propagating.
Declaration of catchable exceptions is difficult to introduce in reasonable way, as people will surely start to work around unreasonable policy. It is not yet clear if lists of exceptions that are declared for handlers and catchers should be the same or not. Also it there is an unclear situation with base classes for exceptions.
Current variant is generic UnhandledException that could be thrown from any place. If exception is not handled explicitly by caller and is not declared in signature, it is wrapped as UnhandledException and could be later unwrapped by logging layer with unwrapper capability, such capability should be granted only explicitly. Callers will see just opaque UnhandledException instance. Also wrapped exceptions are unavailable to anyone except logging layer. Declared exceptions could be caught and processed or redeclared in interfaces.
For example if caller of WriteData() method will not handle IOExcepiton and does not declare in throws, IOException will be wrapped in UnhandledException. So caller of caller of WriteData() will not be able to learn why called method have been failed. But if caller of WriteData() will declare IOExcepiton in throws clause, caller of caller will receive IOException as it have been thrown by WriteData().
Please note that even this variant is not completly secure as attacking component will be able to pass infromation to upper context with response timing and some other ways.