[e-lang] Remaining semantic issues
Kevin Reid
kpreid at attglobal.net
Tue Jan 18 12:32:56 EST 2005
This is a list of bugs which I believe will require changes to Kernel-E
semantics or structure.
*** SuchThatPattern discards information
Non-primitive patterns such as map and quasi-patterns end up discarding
information about the failure of subpatterns.
This is already entered as a bug:
https://bugs.sieve.net/bugs/?func=detailbug&bug_id=125617&group_id=16380
I now think the right solution for fixing/replacing ? is something
analogous to the Guard protocol - exposing the ejector.
However, I can see no simple way to retrieve failure information from a
subpattern. Of the two means of 'invoking' patterns:
1. MatchBindExpr discards the failure exception, except in bindings
established by the pattern (of which there can be zero).
2. DefineExpr discards the distinction between match failure and
throw()n exceptions.
*** Failing patterns in kernel matchers / expansion of multiple matchers
https://bugs.sieve.net/bugs/?func=detailbug&bug_id=125573&group_id=16380
The Miranda implementations of __respondsTo and __getAllegedType assume
that if a matcher is present, it is a full delegate object that has the
corresponding methods itself (so as to correctly report the interface
as the semi-union of the two objects).
This results in problems when the matcher is intended to handle only
particular methods:
? help(require)
# problem: Prefix doesn't match: run
The straightforward solution is for the Miranda implementations to
treat a nonmatching matcher equivalently to no matcher. This is as good
as we can do for __getAllegedType.
For __respondsTo, it could match the matcher's pattern against [verb,
[null] * arity]. This would typically produce reasonable results, but
might be surprising in that the pattern is executed without the body.
A related problem which becomes more serious once the above is
resolved, especially if __respondsTo is implemented as I describe:
The current expansion of multiple matchers to a single Kernel-E matcher
node results in the kernel matcher having a pattern which always
matches (rather than failing if none of the pre-expansion patterns
match).
I see no clean solution to this other than allowing multiple matchers
in Kernel-E.
*** Exception information leakage
Existing bug:
https://bugs.sieve.net/bugs/?
func=detailbug&bug_id=125503&group_id=16380
This is my current proposal, partly derived from a discussion with Mark
Miller on July 19, 2004.
1. 'Ordinary' exceptions are sealed.
Any exceptions resulting from non-E code, throw(_), throw.eject(null,
_), or Kernel-E (e.g. 'no such method' or primitive patterns outside of
match-bind) are sealed by a vat-global sealer. The corresponding
unsealer is available via the privileged scope.
2. optEjector is used wherever it matters.
Ejectors follow capability rules, by explicitly specifying the
exceptional data-and-control-flow path. Therefore, the current pattern
of using 'optEjector' parameters is kept, and used everywhere that the
caller might want to distinguish particular exceptions, or get
information about an exception.
3. Sealers are used to distinguish exceptions across turns.
Since ejectors are useless across vat turns (and therefore for
inter-vat communication), where a client would otherwise pass an
ejector, it passes a new kind of object with the same interface:
def makeFailureBrand {
to run(label) :any {
def [sealer, unsealer] := <elib:sealing.Brand>(label)
def ejectoid implements PassByCopy {
to run(problem :Throwable) {
# SealedBoxThrowable is simply an object which is a Java
Throwable
# and can hold a promise for a SealedBox.
throw(makeSealedBoxThrowable(mySealer <- seal(problem)))
}
to run() {
failureSealer("unspecified problem")
}
}
def guard extends __makeGuard(guard) {
to coerce(specimen, optEjector) :any {
def box := SealedBoxThrowable.coerce(specimen, optEjector) \
.getValue()
# unseal/2 does not actually exist, but would use the ejector
# for both UnsealingExceptions and the argument not being
# a proper SealedBox (so its Java argument type must be
# Object).
return Throwable.coerce(unsealer.unseal(box, optEjector),
optEjector)
}
}
return [ejectoid, guard]
}
to inline(guard__Resolver) {
def [ejectoid, bind guard] := makeFailureBrand("inline")
return ejectoid
}
}
Example usage:
def [ejectoid, MyMissingKey] := makeFailureBrand("missing key")
when (farFlexMap <- fetch(key, ejectoid)) -> _(value) {
# ok
} catch problem :MyMissingKey {
# oops
}
(If it is still desirable that Throwables be DeepPassByCopy (a
constraint which I think is not particularly useful given these
proposed restrictions on exception-data flow), then encryption-based
sealers may be used instead.)
Since the 'ejectoid' (not a name I'm actually proposing) acts almost
exactly as an Ejector does (run/1 -> never returns), no changes are
needed to objects which accept an optEjector-style argument.
The inline/1 method (also in need of a better name) allows a shorthand
for not 'pre-declaring' the pair:
def fb := makeFailureBrand.inline
farFlexMap <- fetch(key, fb(def MyMissingKey))
4. Stack traces are not public.
From unprivileged code, exceptions do not appear to contain stack
traces, except perhaps pseudo-traces explicitly added by code
manipulating exceptions (e.g. a catch-and-rethrow with added
information).
--
Kevin Reid <http://homepage.mac.com/kpreid/>
More information about the e-lang
mailing list