--- title: 16.2.7 tags: The Racket Guide slideOptions: theme: white slideNumber: 'c/t' center: false transition: 'none' keyboard: true width: '93%' height: '100%' --- <style> /* basic design */ .reveal h1, .reveal h2, .reveal h3, .reveal h4, .reveal h5, .reveal h6, .reveal section, .reveal table, .reveal li, .reveal blockquote, .reveal th, .reveal td, .reveal p { font-family: 'Meiryo UI', 'Source Sans Pro', Helvetica, sans-serif, 'Helvetica Neue', 'Helvetica', 'Arial', 'Hiragino Sans', 'ヒラギノ角ゴシック', YuGothic, 'Yu Gothic'; text-align: left; line-height: 1.8; letter-spacing: normal; text-shadow: none; word-wrap: break-word; color: #444; } .reveal h1, .reveal h2, .reveal h3, .reveal h4, .reveal h5 {font-weight: normal;}, .reveal h6 {font-weight: bold;} .reveal h1, .reveal h2, .reveal h3 {color: #2980b9;} .reveal th {background: #DDD;} .reveal section img {background:none; border:none; box-shadow:none; max-width: 95%; max-height: 95%;} .reveal blockquote {width: 90%; padding: 0.5vw 3.0vw;} .reveal table {margin: 1.0vw auto;} .reveal code {line-height: 1.2;} .reveal p, .reveal li {padding: 0vw; margin: 0vw;} .reveal .box {margin: -0.5vw 1.5vw 2.0vw -1.5vw; padding: 0.5vw 1.5vw 0.5vw 1.5vw; background: #EEE; border-radius: 1.5vw;} /* table design */ .reveal table {background: #f5f5f5;} .reveal th {background: #444; color: #fff;} .reveal td {position: relative; transition: all 300ms;} .reveal tbody:hover td { color: transparent; text-shadow: 0 0 3px #aaa;} .reveal tbody:hover tr:hover td {color: #444; text-shadow: 0 1px 0 #fff;} /* blockquote design */ .reveal blockquote { width: 90%; padding: 0.5vw 0 0.5vw 6.0vw; font-style: italic; background: #f5f5f5; } .reveal blockquote:before{ position: absolute; top: 0.1vw; left: 1vw; content: "\f10d"; font-family: FontAwesome; color: #2980b9; font-size: 3.0vw; } /* font size */ .reveal h1 {font-size: 5.0vw;} .reveal h2 {font-size: 3.0vw;} .reveal h3 {font-size: 2.8vw;} .reveal h4 {font-size: 2.6vw;} .reveal h5 {font-size: 1.8vw;} .reveal h6 {font-size: 2.2vw;} .reveal section, .reveal table, .reveal li, .reveal blockquote, .reveal th, .reveal td, .reveal p {font-size: 2.2vw;} .reveal code {font-size: 1.6vw;} /* new color */ .red {color: #EE6557;} .blue {color: #16A6B6;} /* split slide */ #right {left: -18.33%; text-align: left; float: left; width: 50%; z-index: -10;} #left {left: 31.25%; text-align: left; float: left; width: 50%; z-index: -10;} </style> <style> /* specific design */ .reveal h1 { margin: 0% -100%; padding: 2% 100% 4% 100%; color: #fff; background: #fffa5e; /* fallback for old browsers */ background: linear-gradient(-45deg, #f7f439, #54ffa4, #23A6D5, #238ed5); background-size: 200% 200%; animation: Gradient 60s ease infinite; } @keyframes Gradient { 0% {background-position: 0% 50%} 50% {background-position: 100% 50%} 100% {background-position: 0% 50%} } .reveal h2 { text-align: center; margin: -5% -50% 2% -50%; padding: 3% 10% 1% 10%; color: #fff; background: #fffa5e; /* fallback for old browsers */ background: -webkit-linear-gradient(to right, #c74646, #fffa5e); /* Chrome 10-25, Safari 5.1-6 */ background: linear-gradient(to right, #c74646, #fffa5e); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */ } .reveal h4 { text-align: center; } </style> --- ## 16.2.7 コードインスペクタと構文の乱れ モジュールには、同じモジュール内での使用のみを目的とした定義が含まれていることが多く、provideではエクスポートされません。しかし、モジュール内で定義されたマクロを使用すると、エクスポートされていない識別子の参照に展開されることがあります。一般に、このような識別子は展開された式から抽出して別のコンテキストで使用してはいけません。別のコンテキストで識別子を使用すると、マクロのモジュールの不変性が損なわれる可能性があるからです。 たとえば、次のモジュールでは、unchecked-go の使用に展開される go マクロがエクスポートされます。 ```ocaml "m.rkt" #lang racket (provide go) (define (unchecked-go n x) ; to avoid disaster, n must be a number (+ n 17)) (define-syntax (go stx) (syntax-case stx () [(_ x) #'(unchecked-go 8 x)])) ``` unchecked-goへの参照を(go 'a)の展開から抽出してしまうと、(unchecked-go #f 'a)という新しい式に挿入されてしまう可能性があり、最悪の事態を招きます。datum->syntax手続きも同様に、マクロの展開に識別子への参照が含まれていなくても、移植されていない識別子への参照を構築するために使用できます。 結局のところ、モジュールのプライベートバインディングを保護するには、 current-code-inspector パラメータを設定して現在のコードインスペクタを変更する必要があります。これは、コードインスペクタが module->namespace などの関数を通じてモジュールの内部状態へのアクセスを制御するからです。また、現在のコードインスペクターは、racket/unsafe/ops などの安全でないモジュールのエクスポートへのアクセスを制御します。 コードインスペクタは datum->syntax を通じてバインディングへのアクセスをある程度制限しますが(「信頼できるコードと信頼できないコードのためのコードインスペクタ」を参照)、コードインスペクタは expand や local-expand の使用を制御することはできません。そのモジュールのバインディングやインポートの悪用を防ぐために、マクロはその結果のシンタックスオブジェクトのsyntax-protectによるシンタックステイントを有効にしなければなりません。 --- ## 16.2.7.1 マクロの結果に対するsyntax-protectの使用 インポートされていない識別子の悪用を防ぐために、前述の例の go マクロは syntax-protect を使用してその展開を明示的に保護する必要があります。 ```ocaml (define-syntax (go stx) (syntax-case stx () [(_ x) (syntax-protect #'(unchecked-go 8 x))])) ``` syntax-protect 関数は、go の結果から抽出されるすべての構文オブジェクトを汚染されたものにします。マクロ展開ツールは汚染された識別子を拒否するので、(go 'a) の展開から unchecked-go を抽出しようとすると、新しい式の構築に使用できない (または、少なくともマクロ展開ツールが受け入れられない) 識別子が生成されます。syntax-rule、syntax-id-rule、および define-yntax-rule フォームは、展開結果を自動的に保護します。 より正確には、syntax-protect は、構文オブジェクトを染料パックで武装します。構文オブジェクトが武装されると、syntax-eはその結果のすべての構文オブジェクトを汚染する。同様に、datum->syntaxはその第一引数が武装されると、その結果を汚します。最後に、引用された構文オブジェクトのいずれかの部分が武装された場合、対応する部分は結果の構文定数で汚染されます。 もちろん、マクロ展開ツール自体も、構文オブジェクトの染料パックを解除して、式やその部分式をさらに展開できるようにする必要があります。構文オブジェクトが染料パックで武装されている場合、染料パックには、染料パックを解除するために使用できる関連するインスペクタがあります。(syntax-protect stx)関数の呼び出しは、実際には(syntax-arm stx #f #t)の短縮形で、適切なインスペクタを使ってstxを武装させます。エキスパンダーは、展開やコンパイルを行う前に、すべての式に対して syntax-disarm とそのインスペクタを使用します。 マクロ展開ツールが構文変換ツールの入力から出力にプロパティをコピーするのと同じように (「構文オブジェクトのプロパティ」を参照)、展開ツールは変換ツールの入力から出力に色素パックをコピーします。先ほどの例を踏まえて ```ocaml "n.rkt" #lang racket (require "m.rkt") (provide go-more) (define y 'hello) (define-syntax (go-more stx) (syntax-protect #'(go y))) ``` (go-more)の展開では、(go y)の中の未展開のyへの参照が導入され、展開結果が武装されているため、yを展開から抽出することができません。goがその結果にsyntax-protectを使用しなかったとしても(おそらく、結局unchecked-goを保護する必要がないため)、(go y)の色素パックは最終的な展開(unchecked-go 8 y)に伝播されます。マクロ展開部は、syntax-rearm を使用して、染料パックを変換部の入力から出力に伝搬させます。 --- ## 16.2.7.2 染色モード マクロの実装者は、結果を汚染することなく、マクロの結果の限定的なデストラクションを許可することを意図する場合があります。たとえば、次のようなdefine-like-yマクロがあるとします。 ```ocaml "q.rkt" #lang racket (provide define-like-y) (define y 'hello) (define-syntax (define-like-y stx) (syntax-case stx () [(_ id) (syntax-protect #'(define-values (id) y))])) ``` 誰かが内部定義でマクロを使うことができます。 ```ocaml (let () (define-like-y x) x) ``` q.rkt "モジュールの実装者は、このようなdefine-like-yの使用を意図していたと思われます。しかし、内部の定義をletrecのバインディングに変換するためには、define-like-yで生成されたdefineフォームを分解しなければなりません。これは通常、バインディングxとyへの参照の両方を汚染することになります。 しかし、syntax-protectはdefine-valuesで始まる構文リストを特別に扱うので、define-like-yの内部使用は許可されています。この場合、式全体を武装させるのではなく、構文リストの個々の要素を武装させ、染料パックをリストの2番目の要素に押し込んで、定義された識別子に添付させます。したがって、展開結果(define-values (x) y)のdefine-values, x, yは個々に武装され、定義を分解してletrecに変換することができます。 syntax-protectと同様に、エキスパンダーは、define-valuesで始まる変換結果を、染料パックをリスト要素に押し込むことで、後処理します。その結果、define-like-yは、define-valuesの代わりにdefineを使用する(define id y)を生成するように実装できました。この場合、define フォーム全体が最初は染料パックで武装されていますが、define フォームが define-values に拡張されると、染料パックはパーツに移動されます。 マクロ展開ツールは、define-syntaxes で始まる syntax-list の結果を、define-values で始まる結果と同じように扱います。begin で始まるシンタックス リストの結果は、シンタックス リストの 2 番目の要素が他のすべての要素と同様に扱われることを除いて、同様に扱われます (つまり、その内容ではなく、即時の要素が武装されます)。さらに、マクロがネストした定義値形式を含む begin 形式を生成した場合、マクロ展開ツールはこの特別な処理を再帰的に適用します。 dye packs のデフォルトの適用は、マクロ変換の結果として得られる構文オブジェクトに 'taint-mode' プロパティ (See Syntax Object Properties) を付けることで上書きできます。プロパティ値が「不透明」の場合、構文オブジェクトが武装され、その部分は武装されません。プロパティ値が 'transparent' の場合、シンタックス オブジェクトのパーツが武装されます。プロパティ値が'transparent-binding'の場合、シンタックスオブジェクトのパーツと第2部のサブパーツ(define-valuesやdefine-syntaxesの場合)が武装される。transparentおよびtransparent-bindingモードでは、パーツでの再帰的なプロパティチェックが行われるため、武装はトランスフォーマーの結果に任意の深さで押し込むことができます。 --- ## 16.2.7.3 汚れとコードインスペクタ 特権を持つことが意図されているツール(デバッグトランスフォーマーなど)は、展開されたプログラムの染料パックを解除しなければなりません。特権はコードインスペクターによって与えられます。各染料パックにはインスペクタが記録されており、十分に強力なインスペクタを使用して、シンタックスオブジェクトを解除することができます。 モジュールが宣言されると、宣言はcurrent-code-inspectorパラメータの現在の値を捕捉する。キャプチャされたインスペクタは、モジュール内で定義されたマクロトランスフォーマによってシンタックスプロテクションが適用されるときに使用されます。ツールは、モジュールのインスペクタと同じかスーパーインスペクタであるインスペクタをsyntax-disarmに与えることで、結果として生じるシンタックスオブジェクトを解除することができます。信頼されていないコードは、current-code-inspectorをより弱いインスペクタに設定した後、最終的に実行されます(デバッグツールなどの信頼されたコードがロードされた後)。 これは、生成マクロが、生成マクロを含むモジュールの保護レベルではなく、生成モジュールの保護レベルを必要とする構文オブジェクトを埋め込む可能性があるためです。この問題を回避するには、(variable-reference->module-declaration-inspector (#%variable-reference)) としてアクセス可能なモジュールの宣言時インスペクタを使用し、それを使って syntax-protect のバリエーションを定義します。 たとえば、go マクロがマクロで実装されているとします。 ```ocaml #lang racket (provide def-go) (define (unchecked-go n x) (+ n 17)) (define-syntax (def-go stx) (syntax-case stx () [(_ go) (syntax-protect #'(define-syntax (go stx) (syntax-case stx () [(_ x) (syntax-protect #'(unchecked-go 8 x))])))])) ``` def-goが他のモジュール内でgoを定義するために使用され、goを定義するモジュールがdef-goを定義するモジュールと異なる保護レベルにある場合、生成されたマクロのsyntax-protectの使用は正しくありません。unchecked-goの使用は、go-definingモジュールではなくdef-go-definingモジュールのレベルで保護されるべきです。 解決策は、代わりにgo-syntax-protectを定義して使用することです。 ```ocaml #lang racket (provide def-go) (define (unchecked-go n x) (+ n 17)) (define-for-syntax go-syntax-protect (let ([insp (variable-reference->module-declaration-inspector (#%variable-reference))]) (lambda (stx) (syntax-arm stx insp)))) (define-syntax (def-go stx) (syntax-case stx () [(_ go) (syntax-protect #'(define-syntax (go stx) (syntax-case stx () [(_ x) (go-syntax-protect #'(unchecked-go 8 x))])))])) ``` --- ## 16.2.7.4 保護されたエクスポート モジュールは、あるモジュール、つまりエクスポートするモジュールと同じ信頼レベルにある他のモジュールにバインディングをエクスポートして、信頼されていないモジュールからのアクセスを防ぐ必要がある場合があります。そのようなエクスポートにはprotect-out形式のprofileを使うべきです。たとえば、ffi/unsafe は、安全でないバインディングをすべてこの意味でのprotectedとしてエクスポートします。 エクスポートしたモジュールと同じように強力なコードインスペクタを搭載したモジュールだけが、エクスポートしたモジュールの保護されたバインディングを使うことができます。dynamic-require のような操作は、current-code-inspector で決められた現在のコードインスペクタに応じてアクセスが許可されます。 モジュールが保護されたバインディングを再輸出するとき、protect-out を再び使う必要はありません。アクセスは常に、保護されたバインディングを最初に定義したモジュールのコードインスペクタによって決定されます。