racket/collects/unstable/temp-c/scribblings/temp-c.scrbl

145 lines
7.3 KiB
Racket

#lang scribble/doc
@(require scribble/manual
scribble/bnf
scribble/eval
unstable/scribblings/utils
(for-label racket/base
racket/contract
racket/match
racket/list))
@(define our-eval (make-base-eval))
@title[#:tag "temp-c"]{Temporal Contracts: Explicit Contract Monitors}
@unstable[@author+email["Jay McCarthy" "jay@racket-lang.org"]]
@defmodule[unstable/temp-c]
The contract system implies the presence of a "monitoring system" that ensures that contracts are not violated. The @racketmodname[racket/contract] system compiles this monitoring system into checks on values that cross a contracted boundary. This module provides a facility to pass contract boundary crossing information to an explicit monitor for approval. This monitor may, for example, use state to enforce temporal constraints, such as a resource is locked before it is accessed.
@section{Warning! Experimental!}
This library is truly experimental and the interface is likely to
drastically change as we get more experience making use of temporal
contracts. In particular, the library comes with no advice about
designing temporal contracts, which are much more subtle than standard
contracts. This subtlety is compounded because, while temporal
contract violations have accurate blame information, we cannot yet
connect violations to sub-pieces of the temporal formula.
For example, applying @racket[f] to @racket["three"] when it is
contracted to only accept numbers will error by blaming the caller and
providing the explanation "expected a <number?>, received: "three"".
In contrast, applying @racket[g] to @racket["even"] and then to
@racket["odd"] when @racket[g] is contracted to accept strings on
every odd invocation, but numbers on every even invocation, will error
by blaming the second (odd) call, but will not provide any explanation
except "the monitor disallowed the call with arguments: "odd"".
Translating non-acceptance of an event trace by an automata into a
palatable user explanation is an open problem.
@section[#:tag "monitor"]{Monitors}
@defmodule[unstable/temp-c/monitor]
@(require (for-label unstable/temp-c/monitor))
@deftogether[[
@defstruct*[monitor ([label symbol?]) #:transparent]
@defstruct*[(monitor:proj monitor)
([label symbol?] [proj-label symbol?] [v any/c])
#:transparent]
@defstruct*[(monitor:call monitor)
([label symbol?] [proj-label symbol?] [f procedure?]
[app-label symbol?] [kws (listof keyword?)] [kw-args list?] [args list?])
#:transparent]
@defstruct*[(monitor:return monitor)
([label symbol?] [proj-label symbol?] [f procedure?]
[app-label symbol?] [kws (listof keyword?)] [kw-args list?] [args list?]
[rets list?])
#:transparent]
@defproc[(monitor/c [monitor-allows? (-> monitor? boolean?)]
[label symbol?]
[c contract?])
contract?]
]]{
@racket[monitor/c] creates a new contract around @racket[c] that uses @racket[monitor-allows?] to approve
contract boundary crossings. (@racket[c] approves positive crossings first.)
Whenever a value @racket[v] is projected by the result of @racket[monitor/c], @racket[monitor-allows?]
must approve a @racket[(monitor:proj label proj-label v)] structure, where @racket[proj-label] is a unique
symbol for this projection.
If @racket[monitor-allows?] approves and the value is not a function, then the value is returned.
If the value is a function, then a projection is returned, whenever it is called, @racket[monitor-allows?]
must approve a @racket[(monitor:call label proj-label v app-label kws kw-args args)] structure,
where @racket[app-label] is a unique symbol for this application and @racket[kws], @racket[kw-args], @racket[args]
are the arguments passed to the function.
Whenever it returns, @racket[monitor-allows?]
must approve a @racket[(monitor:return label proj-label v app-label kws kw-args args rets)] structure,
where @racket[ret] are the return values of the application.
The unique projection label allows explicitly monitored contracts to be useful when used in a first-class way
at different boundaries.
The unique application label allows explicitly monitored contracts to pair calls and returns when functions
return multiple times or never through the use of continuations.
}
Here is a short example that uses an explicit monitor to ensure that @racket[_malloc] and @racket[_free] are
used correctly.
@racketblock[
(define allocated (make-weak-hasheq))
(define memmon
(match-lambda
[(monitor:return 'malloc _ _ _ _ _ _ (list addr))
(hash-set! allocated addr #t)
#t]
[(monitor:call 'free _ _ _ _ _ (list addr))
(hash-has-key? allocated addr)]
[(monitor:return 'free _ _ _ _ _ (list addr) _)
(hash-remove! allocated addr)
#t]
[_
#t]))
(provide/contract
[malloc (monitor/c memmon 'malloc (-> number?))]
[free (monitor/c memmon 'free (-> number? void))])
]
@section[#:tag "dsl"]{Domain Specific Language}
@defmodule[unstable/temp-c/dsl]
@(require (for-label racket/match
racket/contract
unstable/temp-c/dsl
unstable/automata/re
unstable/automata/re-ext))
Constructing explicit monitors using only @racket[monitor/c] can be a bit onerous. This module provides some helpful tools for making the definition easier. It provides everything from @racketmodname[unstable/temp-c/monitor], as well as all bindings from @racketmodname[unstable/automata/re] and @racketmodname[unstable/automata/re-ext]. The latter provide a DSL for writing "dependent" regular expression machines over arbitrary @racketmodname[racket/match] patterns.
First, a few @racket[match] patterns are available to avoid specify all the details of monitored events (since most of the time the detailed options are unnecessary.)
@defform[(call n a ...)]{ A @racket[match] expander for call events to the labeled function @racket[n] with arguments @racket[a]. }
@defform[(ret n a ...)]{ A @racket[match] expander for return events to the labeled function @racket[n] with return values @racket[a]. }
@defform[(with-monitor contract-expr re-pat)]{ Defines a monitored contract where the structural portion of the contract is the @racket[contract-expr] (which may included embedded @racket[label] expressions) and where the temporal portion of the contract is the regular expression given by @racket[re-pat]. (Note: @racket[re-pat] is not a Racket expression that evaluates to a regular expression. It is a literal regular expression.) An optional @racket[#:concurrent] may be added between the contract and the regular expression to ensure that the machine is safe against race-conditions.}
@defform[(label id contract-expr)]{ Labels a portion of a structural contract inside of @racket[with-monitor] with the label @racket[id]. }
Here is a short example for @racket[_malloc] and @racket[_free]:
@racketblock[
(with-monitor
(cons/c (label 'malloc (-> addr?))
(label 'free (-> addr? void?)))
(complement
(seq (star _)
(dseq (call 'free addr)
(seq
(star (not (ret 'malloc (== addr))))
(call 'free (== addr)))))))
]