マクロツイーター

はてダから移行した記事の表示が崩れてますが、そのうちに直せればいいのに(えっ)

もう脆弱性なんて怖くない(※ただしLaTeXの意味で)(4)

(前回の続き)
補足1: 普通に実行される時

「保護」の機構は飽くまで「保護付完全展開」の時に意味をもつものなので、「普通に実行される時」、すなわち展開限定でない文脈ではマクロの動作に影響を与えない設計になっている。すなわち、展開限定文脈外では:

  • \protect\relax に等置されている。
  • \DeclareRobustCommand で定義したマクロは、同じ定義本体で \newcommand で定義したマクロと同じ動作を行う。

つまり、展開限定文脈外で \protect の入った \NabeAzzLike

\setcoutner{enumi}{42}
\NabeAzzLike{enumi}

のように使用すると、\protect のない場合と全く同じ動作をする。

ただし、このことは、「展開限定文脈外」と「展開限定文脈で保護付完全展開が行われた場合」でマクロが同じ動作を行うことを自動的に保証するものではない。例として次の(不完全な)\NabeAzzLike の定義を考えよう。

\def\NabeAzzLike#1{\protect\nabeazz{\arabic{#1}}}

保護付完全展開が入った場合は、\nabeazz が展開されないので、先に \arabic が展開されることになり、結果的に後で \nabeazz が実行される時にはその引数は(規定通り)10 進表記になっている。ところが、保護付完全展開が入らずに直接実行される場合は、\nabeazz の引数が \arabic{...} のままになってしまってこれでは正常に動作しない。すなわち、このマクロでは、保護付完全展開が入る時と入らない時で動作が異なっている。

補足2: \protected@edef

LaTeX において、「保護付完全展開」の最も典型的な実装が \protected@edef という内部命令である。この命令は \edef と同じ構文をとる((ただしプリミティブでなくマクロなので、\global 等の接頭辞プリミティブを付けられない。\global 付き(つまり \xdef)に相当するものとして \protected@xdef が用意されている。))が、本体を「完全展開」する代わりに「保護付完全展開」する点が異なる。この \protected@edef を用いて「保護付完全展開」の結果を確認することができる。((\protected@edef の展開では \protect を付けたままにしている。\protected@edef の展開が複数回行われた場合に保護したものが結局展開されてしまうのを防ぐためである。))

\makeatletter
\def\NabeAzzLike#1{%
  \expandafter\protect\expandafter\nabeazz\expandafter{\number\csname c@#1\endcsname}}
\setcounter{enumi}{42}
\protected@edef\result{\NabeAzzLike{enumi}}
\show\result %==> \protect \nabeazz {42}

また、(展開可能なトークンを含む)トークン列を \typeout で画面に表示させるときに行われる展開も実は「保護付完全展開」になっている。((中で \protected@write という内部命令(つまり保護付の \write)が使われている。))

\typeoput{\NabeAzzLike{enumi}}
%==>「\nabeazz{42}」と出力
補足3: 頑強と完全展開可能は違う

「頑強」とは保護付完全展開、つまり「保護されたトークンは展開しない完全展開」を施しても正しく動くということである。従って、\edef 等を用いて(保護付でない)完全展開を行ってしまうと、保護されたトークンも展開されてしまうので、もはや正しく動く保証はない。完全展開しても正しく動くためには(文字通り)「完全展開可能」でなければならない。

完全展開可能でない処理を中に含むマクロは決して完全展開可能にはならない。ところが、頑強でない(脆弱な)マクロは「保護」することで容易に頑強にすることができる。*1これが、LaTeX でわざわざ「保護付完全展開」や「頑強」という概念が導入された理由であろう。

補足4: e-TeX 拡張のエンジンレベルでの保護

についてはまた今度。

*1:前回に述べたような微妙な問題はあるが。