racket/collects/scribblings/guide/certificates.scrbl
Matthew Flatt 6a93d0c531 macros chapter in guide
svn: r9927
2008-05-22 16:12:51 +00:00

252 lines
11 KiB
Racket

#lang scribble/doc
@(require scribble/manual
scribble/eval
"guide-utils.ss")
@title[#:tag "stx-certs" #:style 'quiet]{Syntax Certificates}
A use of a macro can expand into a use of an identifier that is not
exported from the module that binds the macro. In general, such an
identifier must not be extracted from the expanded expression and used
in a different context, because using the identifier in a different
context may break invariants of the macro's module.
For example, the following module exports a macro @scheme[go] that
expands to a use of @scheme[unchecked-go]:
@schemeblock[
(module m mzscheme
(provide go)
(define (unchecked-go n x)
(code:comment #, @t{to avoid disaster, @scheme[n] must be a number})
(+ n 17))
(define-syntax (go stx)
(syntax-case stx ()
[(_ x)
#'(unchecked-go 8 x)])))
]
If the reference to @scheme[unchecked-go] is extracted from the
expansion of @scheme[(go 'a)], then it might be inserted into a new
expression, @scheme[(unchecked-go #f 'a)], leading to disaster. The
@scheme[datum->syntax] procedure can be used similarly to construct
references to an unexported identifier, even when no macro expansion
includes a reference to the identifier.
To prevent such abuses of unexported identifiers, the expander rejects
references to unexported identifiers unless they appear in
@defterm{certified} syntax objects. The macro expander always
certifies a syntax object that is produced by a transformer. For
example, when @scheme[(go 'a)] is expanded to @scheme[(unchecked-go 8
'a)], a certificate is attached to the result @scheme[(unchecked-go 8
'a)]. Extracting just @scheme[unchecked-go] removes the identifier
from the certified expression, so that the reference is disallowed
when it is inserted into @scheme[(unchecked-go #f 'a)].
In addition to checking module references, the macro expander
disallows references to local bindings where the binding identifier is
less certified than the reference. Otherwise, the expansion of
@scheme[(go 'a)] could be wrapped with a local binding that redirects
@scheme[#%app] to @scheme[values], thus obtaining the value of
@scheme[unchecked-go]. Note that a capturing @scheme[#%app] would have
to be extracted from the expansion of @scheme[(go 'a)], since lexical
scope would prevent an arbitrary @scheme[#%app] from capturing. The
act of extracting @scheme[#%app] removes its certification, whereas
the @scheme[#%app] within the expansion is still certified; comparing
these certifications, the macro expander rejects the local-binding
reference, and @scheme[unchecked-go] remains protected.
In much the same way that the macro expander copies properties from a
syntax transformer's input to its output (see @refsecref["stxprops"]),
the expander copies certificates from a transformer's input to its
output. Building on the previous example,
@schemeblock[
(module n mzscheme
(require m)
(provide go-more)
(define y 'hello)
(define-syntax (go-more stx)
#'(go y)))
]
the expansion of @scheme[(go-more)] introduces a reference to the
unexported @scheme[y] in @scheme[(go y)], and a certificate allows the
reference to @scheme[y]. As @scheme[(go y)] is expanded to
@scheme[(unchecked-go 8 y)], the certificate that allows @scheme[y] is
copied over, in addition to the certificate that allows the reference
to @scheme[unchecked-go].
When a protected identifier becomes inaccessible by direct reference
(i.e., when the current code inspector is changed so that it does not
control the module's invocation; see @refsecref["modprotect"]), the
protected identifier is treated like an unexported identifier.
@;------------------------------------------------------------------------
@section[#:tag "stxinactivecerts"]{Certificate Propagation}
When the result of a macro expansion contains a @scheme[quote-syntax]
form, the macro expansion's certificate must be attached to the
resulting syntax object to support macro-generating macros. In
general, when the macro expander encounters @scheme[quote-syntax], it
attaches all certificates from enclosing expressions to the quoted
syntax constant. However, the certificates are attached to the syntax
constant as @defterm{inactive} certificates, and inactive certificates
do not count directly for certifying identifier access. Inactive
certificates become active when the macro expander certifies the
result of a macro expansion; at that time, the expander removes all
inactive certificates within the expansion result and attaches active
versions of the certificates to the overall expansion result.
For example, suppose that the @scheme[go] macro is implemented through
a macro:
@schemeblock[
(module m mzscheme
(provide def-go)
(define (unchecked-go n x)
(+ n 17))
(define-syntax (def-go stx)
(syntax-case stx ()
[(_ go)
#'(define-syntax (go stx)
(syntax-case stx ()
[(_ x)
#'(unchecked-go 8 x)]))])))
]
When @scheme[def-go] is used inside another module, the generated
macro should legally generate expressions that use
@scheme[unchecked-go], since @scheme[def-go] in @scheme[m] had
complete control over the generated macro.
@schemeblock[
(module n mzscheme
(require m)
(def-go go)
(go 10)) ; access to @scheme[unchecked-go] is allowed
]
This example works because the expansion of @scheme[(def-go go)] is
certified to access protected identifiers in @scheme[m], including
@scheme[unchecked-go]. Specifically, the certified expansion is a
definition of the macro @scheme[go], which includes a syntax-object
constant @scheme[unchecked-go]. Since the enclosing macro declaration
is certified, the @scheme[unchecked-go] syntax constant gets an
inactive certificate to access protected identifiers of
@scheme[m]. When @scheme[(go 10)] is expanded, the inactive
certificate on @scheme[unchecked-go] is activated for the macro result
@scheme[(unchecked-go 8 10)], and the access of @scheme[unchecked-go]
is allowed.
To see why @scheme[unchecked-go] as a syntax constant must be given an
inactive certificate instead of an active one, it's helpful to write
the @scheme[def-go] macro as follows:
@schemeblock[
(define-syntax (def-go stx)
(syntax-case stx ()
[(_ go)
#'(define-syntax (go stx)
(syntax-case stx ()
[(_ x)
(with-syntax ([ug (quote-syntax unchecked-go)])
#'(ug 8 x))]))]))
]
In this case, @scheme[unchecked-go] is clearly quoted as an immediate
syntax object in the expansion of @scheme[(def-go go)]. If this syntax
object were given an active certificate, then it would keep the
certificate---directly on the identifier @scheme[unchecked-go]---in
the result @scheme[(unchecked-go 8 10)]. Consequently, the
@scheme[unchecked-go] identifier could be extracted and used with its
certificate intact. Attaching an inactive certificate to
@scheme[unchecked-go] and activating it only for the complete result
@scheme[(unchecked-go 8 10)] ensures that @scheme[unchecked-go] is
used only in the way intended by the implementor of @scheme[def-go].
The @scheme[datum->syntax] procedure allows inactive certificates to
be transferred from one syntax object to another. Such transfers are
allowed because a macro transformer with access to the syntax object
could already wrap it with an arbitrary context before activating the
certificates. In practice, transferring inactive certificates is
useful mainly to macros that implement to new template forms, such as
@scheme[syntax/loc].
@;------------------------------------------------------------------------
@section{Internal Certificates}
In some cases, a macro implementor intends to allow limited
destructuring of a macro result without losing the result's
certificate. For example, given the following @scheme[define-like-y]
macro,
@schemeblock[
(module q mzscheme
(provide define-like-y)
(define y 'hello)
(define-syntax (define-like-y stx)
(syntax-case stx ()
[(_ id) #'(define-values (id) y)])))
]
someone may use the macro in an internal definition:
@schemeblock[
(let ()
(define-like-y x)
x)
]
The implementor of the @scheme[q] module most likely intended to allow
such uses of @scheme[define-like-y]. To convert an internal definition
into a @scheme[letrec] binding, however, the @scheme[define] form
produced by @scheme[define-like-y] must be deconstructed, which would
normally lose the certificate that allows the reference to @scheme[y].
The internal use of @scheme[define-like-y] is allowed because the
macro expander treats specially a transformer result that is a syntax
list beginning with @scheme[define-values]. In that case, instead of
attaching the certificate to the overall expression, the certificate
is instead attached to each individual element of the syntax list,
pushing the certificates into the second element of the list so that
they are attached to the defined identifiers. Thus, a certificate is
attached to @scheme[define-values], @scheme[x], and @scheme[y] in the
expansion result @scheme[(define-values (x) y)], and the definition
can be deconstructed for conversion to @scheme[letrec].
Just like the new certificate that is added to a transformer result,
old certificates from the input are similarly moved to syntax-list
elements when the result starts with @scheme[define-values]. Thus,
@scheme[define-like-y] could have been implemented to produce
@scheme[(define id y)], using @scheme[define] instead of
@scheme[define-values]. In that case, the certificate to allow
reference to @scheme[y] would be attached initially to the expansion
result @scheme[(define x y)], but as the @scheme[define] is expanded
to @scheme[define-values], the certificate would be moved to the
parts.
The macro expander treats syntax-list results starting with
@scheme[define-syntaxes] in the same way that it treats results
starting with @scheme[define-values]. Syntax-list results starting
with @scheme[begin] are treated similarly, except that the second
element of the syntax list is treated like all the other elements
(i.e., the certificate is attached to the element instead of its
content). Furthermore, the macro expander applies this special
handling recursively, in case a macro produces a @scheme[begin] form
that contains nested @scheme[define-values] forms.
The default application of certificates can be overridden by attaching
a @scheme['certify-mode] property (see @refsecref["stxprops"]) to the
result syntax object of a macro transformer. If the property value is
@scheme['opaque], then the certificate is attached to the syntax
object and not its parts. If the property value is
@scheme['transparent], then the certificate is attached to the syntax
object's parts. If the property value is
@scheme['transparent-binding], then the certificate is attached to the
syntax object's parts and to the sub-parts of the second part (as for
@scheme[define-values] and @scheme[define-syntaxes]). The
@scheme['transparent] and @scheme['transparent-binding] modes triggers
recursive property checking at the parts, so that the certificate can
be pushed arbitrarily deep into a transformer's result.