diff options
8 files changed, 941 insertions, 813 deletions
diff --git a/content/posts/2021-10-02/ruby-then-keyword-and-case-in.xml b/content/posts/2021-10-02/ruby-then-keyword-and-case-in.xml index c3fd933..4176c67 100644 --- a/content/posts/2021-10-02/ruby-then-keyword-and-case-in.xml +++ b/content/posts/2021-10-02/ruby-then-keyword-and-case-in.xml @@ -63,11 +63,15 @@ end</programlisting> を書くことはない。なぜか。次のコードを実行してみるとわかる。</simpara> <programlisting language="ruby" linenumbering="unnumbered">if true puts 'Hello, World!' end</programlisting> <simpara>次のような構文エラーが出力される。</simpara> - <literallayout class="monospaced">20:1: syntax error, unexpected local variable or method, expecting `then' or ';' or '\n' - if true puts 'Hello, World!' end - ^~~~ - 20:1: syntax error, unexpected `end', expecting end-of-input - ...f true puts 'Hello, World!' end</literallayout> + <literallayout class="monospaced"> + <![CDATA[ + 20:1: syntax error, unexpected local variable or method, expecting `then' or ';' or '\n' + if true puts 'Hello, World!' end + ^~~~ + 20:1: syntax error, unexpected `end', expecting end-of-input + ...f true puts 'Hello, World!' end + ]]> + </literallayout> <simpara>二つ目のメッセージは無視して一つ目を読むと、<literal>then</literal> か <literal>;</literal> か改行が来るはずのところ変数だかメソッドだかが現れたことによりエラーとなっているようだ。</simpara> <simpara>ポイントは改行が <literal>then</literal> (や <literal>;</literal>) の代わりとなることである。<literal>true</literal> diff --git a/content/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line.xml b/content/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line.xml index 9ef6654..5cb5d93 100644 --- a/content/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line.xml +++ b/content/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line.xml @@ -71,80 +71,84 @@ <title>主な障害</title> <simpara>1行あたりの文字数など、適当に改行を挟めばいいだけではないのか?</simpara> <simpara>特に、C言語でこのような試みをおこなったことがあるかたならそう思うだろう。事実、Cでのこの制約はほとんど無意味に等しい。</simpara> - <programlisting language="c" linenumbering="unnumbered">#\ - i\ - n\ - c\ - l\ - u\ - d\ - e\ - <\ - s\ - t\ - d\ - i\ - o\ - .\ - h\ - >\ - /* - */ - i\ - n\ - t\ - /* - */ - m\ - a\ - i\ - n( - ){ - f\ - o\ - r( - i\ - n\ - t\ - /* - */ - i= - 1; - i< - 1\ - 0\ - 0; - i\ - +\ - +) - if - (i - %\ - 15 - == - 0) - p\ - r\ - i\ - n\ - t\ - f( - "\ - F\ - i\ - z\ - z\ - B\ - u\ - z\ - z\ - %\ - c\ - ", - 10 - ); + <programlisting language="c" linenumbering="unnumbered"> + <![CDATA[ + #\ + i\ + n\ + c\ + l\ + u\ + d\ + e\ + <\ + s\ + t\ + d\ + i\ + o\ + .\ + h\ + >\ + /* + */ + i\ + n\ + t\ + /* + */ + m\ + a\ + i\ + n( + ){ + f\ + o\ + r( + i\ + n\ + t\ + /* + */ + i= + 1; + i< + 1\ + 0\ + 0; + i\ + +\ + +) + if + (i + %\ + 15 + == + 0) + p\ + r\ + i\ + n\ + t\ + f( + "\ + F\ + i\ + z\ + z\ + B\ + u\ + z\ + z\ + %\ + c\ + ", + 10 + ); - /* あとは同じように普通のプログラムを変形するだけなので省略 */</programlisting> + /* あとは同じように普通のプログラムを変形するだけなので省略 */ + ]]> + </programlisting> <simpara>バックスラッシュを使った行継続がトークンを区切らない、というのがポイントだ。</simpara> <simpara>さて、PHP ではそもそもバックスラッシュを行継続に使うことができない。これにより、「3文字以上からなるトークンが一切使えない」という制約が課される。例えば、<literal>echo</literal> @@ -180,10 +184,14 @@ <simpara>また、2文字だと文字列がまともに書けないのも辛い。<literal>''</literal> だけで 2文字使うので、「1文字の文字列リテラル」というものを書くことができない。PHP では文字列リテラル中に生の改行が書けるので</simpara> -<programlisting language="php" linenumbering="unnumbered">$a -=' -a' -;;</programlisting> +<programlisting language="php" linenumbering="unnumbered"> + <![CDATA[ + $a + =' + a' + ;; + ]]> +</programlisting> <simpara>とすると <literal>$a</literal> は <literal>"\na"</literal> になるのだが、余計な改行が入ってしまう。</simpara> <simpara>これらの障害をどのように乗り越えるのか、次節から見ていく。</simpara> </section> @@ -192,11 +200,15 @@ a' <section xml:id="_普通の_fizzbuzz"> <title>普通の (?) fizzbuzz</title> <simpara>まずは普通に書くとしよう。</simpara> - <literallayout class="monospaced"><?php + <programlisting language="php" linenumbering="unnumbered"> + <![CDATA[ + <?php - for ($i = 1; $i < 100; $i++) { - echo (($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"; - }</literallayout> + for ($i = 1; $i < 100; $i++) { + echo (($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"; + } + ]]> + </programlisting> <simpara>素直に書いた fizzbuzz とは言い難いが、このくらいは普通だということにしておかないと、この先がやっていられないので許してほしい。</simpara> </section> @@ -205,14 +217,18 @@ a' <simpara><literal>for</literal> は、3文字もある長いキーワードである。こんなものは使えない。<literal>array_</literal> 系の関数を使って、適当に置き換えるとしよう。</simpara> - <programlisting language="php" linenumbering="unnumbered"><?php + <programlisting language="php" linenumbering="unnumbered"> + <![CDATA[ + <?php - $s = range(1, 100); - array_walk( - $s, - fn($i) => - printf((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"), - );</programlisting> + $s = range(1, 100); + array_walk( + $s, + fn($i) => + printf((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"), + ); + ]]> + </programlisting> <simpara><literal>array_walk</literal> や <literal>range</literal>、<literal>printf</literal> といった <literal>for</literal> よりも長いトークンが現れてしまったが、これは次節で直すことにする。なお、<literal>echo</literal> は文 (statement) であり式 (expression) ではないので、式である <literal>printf</literal> @@ -223,18 +239,22 @@ a' <simpara><literal>range</literal>、<literal>array_walk</literal>、<literal>printf</literal> は長すぎるのでどうにかせねばならない。ここで、PHP の可変関数を使う。可変関数とは、関数名が文字列として入った変数を経由して、関数を呼び出す機能である。</simpara> - <programlisting language="php" linenumbering="unnumbered"><?php + <programlisting language="php" linenumbering="unnumbered"> + <![CDATA[ + <?php - $r = 'range'; - $w = 'array_walk'; - $p = 'printf'; + $r = 'range'; + $w = 'array_walk'; + $p = 'printf'; - $s = $r(1, 100); - $w( - $s, - fn($i) => - $p((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"), - );</programlisting> + $s = $r(1, 100); + $w( + $s, + fn($i) => + $p((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"), + ); + ]]> + </programlisting> <simpara>これで関数を呼び出している所は短くなった。では、<literal>$r</literal> や <literal>$w</literal> や <literal>$p</literal>、また <literal>'Fizz'</literal> や <literal>'Buzz'</literal> はどうやって 1行2文字に収めるのか。次のテクニックへ移ろう。</simpara> @@ -252,23 +272,31 @@ a' <simpara>というルールがない場合、「未定義の定数が評価された場合、その定数の名前が値になる」という PHP 7.x までの仕様が利用できる。例えば、 <literal>Fizz</literal> という文字列が欲しければ、次のようにする。</simpara> -<programlisting language="php" linenumbering="unnumbered">$f -=F -.i -.z -.z -;;</programlisting> +<programlisting language="php" linenumbering="unnumbered"> + <![CDATA[ + $f + =F + .i + .z + .z + ;; + ]]> +</programlisting> <simpara>こうして簡単に文字列を作れる。なお、この仕様は 7.x 時点でも警告を受けるので、<literal>@</literal> 演算子を使って抑制してやるとよい。</simpara> -<programlisting language="php" linenumbering="unnumbered">$f -=@ -F. -@i -.# -@z -.# -@z -;;</programlisting> +<programlisting language="php" linenumbering="unnumbered"> + <![CDATA[ + $f + =@ + F. + @i + .# + @z + .# + @z + ;; + ]]> +</programlisting> <simpara>むしろ、このことがわかっていたからこそ PHP 8.x での動作を要件に課したところがある。</simpara> </section> @@ -278,56 +306,72 @@ F. <simpara>ずばり、文字列同士のビット演算を使う。PHP では、文字列同士でビット演算 (<literal>&</literal>、<literal>|</literal>、<literal>^</literal>) をした場合、文字列の各バイトごとに指定したビット演算がなされ、それを結合したものが演算結果となる。</simpara> -<programlisting language="php" linenumbering="unnumbered">$a = "12345"; -$b = "world"; +<programlisting language="php" linenumbering="unnumbered"> + <![CDATA[ + $a = "12345"; + $b = "world"; -// $a ^ $b は次のコードと同じ -$result = ''; -for ($i = 0; $i < min(strlen($a), strlen($b)); $i++) { -$result .= $a[$i] ^ $b[$i]; -} + // $a ^ $b は次のコードと同じ + $result = ''; + for ($i = 0; $i < min(strlen($a), strlen($b)); $i++) { + $result .= $a[$i] ^ $b[$i]; + } -echo $result; -// => F]AXQ</programlisting> + echo $result; + // => F]AXQ + ]]> +</programlisting> <simpara>これを踏まえ、次のコードを見てみよう。</simpara> -<programlisting language="php" linenumbering="unnumbered">$x = "x\nOm\n"; -$y = "\nk!\no"; -$r = $x ^ $y; -echo "$r\n";</programlisting> +<programlisting language="php" linenumbering="unnumbered"> + <![CDATA[ + $x = "x\nOm\n"; + $y = "\nk!\no"; + $r = $x ^ $y; + echo "$r\n"; + ]]> +</programlisting> <simpara>実行すると、<literal>range</literal> が表示される。さて、PHP では文字列リテラル中に生の改行を直接書いてもよいのだった (「主な障害」の節を参照のこと)。書きかえてみよう。</simpara> -<programlisting language="php" linenumbering="unnumbered">$x -='x -Om -'; -$y -=' -k! -o' -; +<programlisting language="php" linenumbering="unnumbered"> + <![CDATA[ + $x + ='x + Om + '; + $y + =' + k! + o' + ; -$r = $x ^ $y; -echo "$r\n";</programlisting> + $r = $x ^ $y; + echo "$r\n"; + ]]> +</programlisting> <simpara>さらに <literal>#</literal> を使って適当に調整すると、次のようになる。</simpara> -<programlisting language="php" linenumbering="unnumbered">$x -=# -'x -Om -'; -$y -=' -k! -o' -;# -$r -=# -$x -^# -$y -;# +<programlisting language="php" linenumbering="unnumbered"> + <![CDATA[ + $x + =# + 'x + Om + '; + $y + =' + k! + o' + ;# + $r + =# + $x + ^# + $y + ;# -echo "$r\n";</programlisting> + echo "$r\n"; + ]]> +</programlisting> <simpara>1行あたり2文字で、<literal>range</literal> という文字列を生成することに成功した。他の必要な文字列にも、同様の処理をほどこす。</simpara> <simpara>備考: <literal>Buzz</literal> 中にある小文字の <literal>u</literal> は、このロジックだと non-printable @@ -337,155 +381,159 @@ echo "$r\n";</programlisting> <section xml:id="_完成系"> <title>完成系</title> <simpara>完成したものがこちら。</simpara> - <programlisting language="php" linenumbering="unnumbered"><?php + <programlisting language="php" linenumbering="unnumbered"> + <![CDATA[ + <?php - $x - =# - 'i - S' - ;; - $y - =' - b! - '; - $c - =# - $x - ^# - $y - ;# - $x - =# - 'x - Om - '; - $y - =' - k! - o' - ;# - $r - =# - $x - ^# - $y - ;# - $x - =# - 'k - Sk - ~} - Ma - '; - $y - =' - x! - s! - k! - '; - $w - =# - $x - ^# - $y - ;# - $x - =# - 'z - Hd - G' - ;# - $y - =' - x! - ~! - '; - $p - =# - $x - ^# - $y - ;# - $x - =# - 'L - [p - '; - $y - =' - c! - '; - $f - =# - $x - ^# - $y - ;# - $x - =# - 'H - [p - '; - $y - =' - _! - '; - $b - =# - $x - ^# - $y - ;# - $b - [1 - ]= - $c - (# - 13 - *9 - ); - $s - =# - $r - (1 - ,( - 10 - ** - 2) - ); - $w - (# - $s - ,# - fn - (# - $i - )# - => - $p - (( - (# - $i - %3 - ?# - '' - :# - $f - ). - (# - $i - %5 - ?# - '' - :# - $b - )? - :# - $i - )# - .' - ') - );</programlisting> + $x + =# + 'i + S' + ;; + $y + =' + b! + '; + $c + =# + $x + ^# + $y + ;# + $x + =# + 'x + Om + '; + $y + =' + k! + o' + ;# + $r + =# + $x + ^# + $y + ;# + $x + =# + 'k + Sk + ~} + Ma + '; + $y + =' + x! + s! + k! + '; + $w + =# + $x + ^# + $y + ;# + $x + =# + 'z + Hd + G' + ;# + $y + =' + x! + ~! + '; + $p + =# + $x + ^# + $y + ;# + $x + =# + 'L + [p + '; + $y + =' + c! + '; + $f + =# + $x + ^# + $y + ;# + $x + =# + 'H + [p + '; + $y + =' + _! + '; + $b + =# + $x + ^# + $y + ;# + $b + [1 + ]= + $c + (# + 13 + *9 + ); + $s + =# + $r + (1 + ,( + 10 + ** + 2) + ); + $w + (# + $s + ,# + fn + (# + $i + )# + => + $p + (( + (# + $i + %3 + ?# + '' + :# + $f + ). + (# + $i + %5 + ?# + '' + :# + $b + )? + :# + $i + )# + .' + ') + ); + ]]> + </programlisting> </section> <section xml:id="_感想など"> <title>感想など</title> @@ -501,18 +549,22 @@ echo "$r\n";</programlisting> ではバックスラッシュによる行継続が使えないと書いたが、シェルでは使える (当然だが、呼び出されるシェルに依存する。Bash なら大丈夫だろう。知らんけど)。</simpara> - <programlisting language="php" linenumbering="unnumbered"><?php + <programlisting language="php" linenumbering="unnumbered"> + <![CDATA[ + <?php - printf(` - e\ - c\ - h\ - o\ - \ - 1\ - 2\ - 3\ - `);</programlisting> + printf(` + e\ + c\ + h\ + o\ + \ + 1\ + 2\ + 3\ + `); + ]]> + </programlisting> <simpara>なお、ここでは簡単のため出力に <literal>printf</literal> をそのまま使っているが、実際には <literal>printf</literal> という文字列を合成して可変関数で呼び出す。</simpara> <simpara>ただし、これでは</simpara> @@ -525,57 +577,73 @@ echo "$r\n";</programlisting> </blockquote> <simpara>に違反してしまう。スペースが使えないと引数とコマンドを区切れない。これは困った。</simpara> <simpara>もうこれ以上は不可能だと思っていたのだが、この記事の執筆中に解決する方法を思いついたので載せておく。</simpara> -<programlisting language="php" linenumbering="unnumbered"><?php +<programlisting language="php" linenumbering="unnumbered"> + <![CDATA[ + <?php -$c = 'chr'; + $c = 'chr'; -${ -'_ -'} -=# -$c -(# -32 -). -$c -(# -92 -); + ${ + '_ + '} + =# + $c + (# + 32 + ). + $c + (# + 92 + ); -printf(` -e\ -c\ -h\ -o\ -${ -'_ -'} -1\ -2\ -3\ -`);</programlisting> + printf(` + e\ + c\ + h\ + o\ + ${ + '_ + '} + 1\ + 2\ + 3\ + `); + ]]> +</programlisting> <simpara>先程と同じく、<literal>chr</literal> や <literal>printf</literal> を生成する部分は長くなるので省いた。</simpara> -<literallayout class="monospaced">${ -'_ -'}</literallayout> +<literallayout class="monospaced"> + <![CDATA[ + ${ + '_ + '} + ]]> +</literallayout> <simpara>は変数で、中にはスペースとエスケープが入っている (<literal>chr(32) . chr(92)</literal>)。シェルに渡されている文字列は次のようになる。</simpara> -<literallayout class="monospaced">e\ -c\ -h\ -o\ -\ -1\ -2\ -3\</literallayout> +<literallayout class="monospaced"> + <![CDATA[ + e\ + c\ + h\ + o\ + \ + 1\ + 2\ + 3\ + ]]> +</literallayout> <simpara>これは、前掲したコマンドと同じだ。かくして、スペースを陽に書かずにシェルをおおよそ自由に扱えるようになった。Fizzbuzz のワンライナーくらいすぐ書けるだろうから、あとはなんとかなるだろう (試してないけど)。</simpara> <simpara>ということでこれは別解ということにしておく。</simpara> <simpara>ちなみに、PHP 8.2 からは、この記法で Warning が出るようになるようだ。</simpara> -<literallayout class="monospaced">${ -'_ -'}</literallayout> +<literallayout class="monospaced"> + <![CDATA[ + ${ + '_ + '} + ]]> +</literallayout> <simpara>最新版で警告が出るというのも美しくないので、私としては本編の解法を推す。</simpara> </section> </article> diff --git a/content/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2.xml b/content/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2.xml index bc93868..f1df049 100644 --- a/content/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2.xml +++ b/content/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2.xml @@ -28,7 +28,8 @@ <section xml:id="_問題"> <title>問題</title> <simpara>注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。</simpara> - <programlisting language="php" linenumbering="unnumbered"><?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + <programlisting language="php" linenumbering="unnumbered"> + <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> @@ -37,13 +38,15 @@ <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> - <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?></programlisting> + <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + </programlisting> <simpara>"And Then There Were None" (そして誰もいなくなった) と名付けた作品。変則 quine (自分自身と同じソースコードを出力するプログラム) になっている。</simpara> </section> <section xml:id="_トークン入手方法"> <title>トークン入手方法</title> <simpara>実行してみると、次のような出力が得られる。</simpara> - <programlisting language="php" linenumbering="unnumbered"># + <programlisting language="php" linenumbering="unnumbered"> + # <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> @@ -52,9 +55,11 @@ <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> - <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?></programlisting> + <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + </programlisting> <simpara>1 行目を除き、先ほどのコードとほぼ同じものが出てきた。もう一度実行してみる。</simpara> -<programlisting language="php" linenumbering="unnumbered"># +<programlisting language="php" linenumbering="unnumbered"> + # W <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> @@ -63,34 +68,43 @@ W <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> -<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?></programlisting> + <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> +</programlisting> <simpara>今度は 2 行目が書き換えられた。すべての行が変化するまで繰り返すと次のようになる。</simpara> -<programlisting language="php" linenumbering="unnumbered"># -W -E -L -O -V -E -P -H -P</programlisting> +<programlisting language="php" linenumbering="unnumbered"> + # + W + E + L + O + V + E + P + H + P +</programlisting> <simpara>トークン「#WELOVEPHP」が手に入った。</simpara> </section> <section xml:id="_解説"> <title>解説</title> <simpara>一見すると同じ行が 10 行並んでいるだけなのにも関わらず、なぜそれぞれの行で出力が変わるのか。ソースコードをコピーして、適当なエディタに貼り付けるとわかりやすい。</simpara> <simpara>Vim で開くと次のようになる (1 行目を抜粋)。</simpara> - <programlisting language="php" linenumbering="unnumbered"><?php printf((isset($s)?fn($s)=>trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s='<200b><?php printf((isset($s)?fn($s)=>trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?></programlisting> + <programlisting language="php" linenumbering="unnumbered"> + <?php printf((isset($s)?fn($s)=>trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s='<200b><?php printf((isset($s)?fn($s)=>trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + </programlisting> <simpara><literal><200b></literal> と表示されているのは、Unicode の U+200b で、ゼロ幅スペースである。</simpara> <note> <simpara>エディタによっては、ゼロ幅スペースが見えないことがある。VSCode ではブラウザと同様に不可視だった。</simpara> </note> <simpara>文字列リテラルの中にゼロ幅スペースを仕込むことで、見た目を変えずに情報をエンコードすることが可能となる。</simpara> <simpara>続いて、トークンへの変換ロジックを解析する。注目すべきはこの部分だ。以下、ゼロ幅スペースは Vim での表示に合わせて <literal><200b></literal> と記載する。</simpara> - <programlisting language="php" linenumbering="unnumbered">fn($s)=>chr(strlen($s)/3)</programlisting> + <programlisting language="php" linenumbering="unnumbered"> + fn($s)=>chr(strlen($s)/3) + </programlisting> <simpara>PHP の <literal>strlen()</literal> は文字列のバイト数を返す。1 行目の <literal>$s</literal> は以下の内容となっており、</simpara> - <programlisting language="php" linenumbering="unnumbered">$s='<200b><?php printf((isset($s)?fn($s)=>trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'</programlisting> + <programlisting language="php" linenumbering="unnumbered"> + $s='<200b><?php printf((isset($s)?fn($s)=>trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>' + </programlisting> <simpara>このソースコードは UTF-8 で書かれているので、105 バイトになる。それを 3 で割ると 35 となり、これは <literal>#</literal> の ASCII コードと一致する。他の行も、同様にしてゼロ幅スペースを詰めることで文字列長を調整し、トークンをエンコードしている。</simpara> <simpara>デコード部以外の部分は、quine のための記述である。</simpara> </section> diff --git a/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.xml b/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.xml index 2a7be46..cd65047 100644 --- a/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.xml +++ b/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.xml @@ -35,24 +35,26 @@ <section xml:id="_問題"> <title>問題</title> <simpara>注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。</simpara> - <programlisting language="php" linenumbering="unnumbered"><?php - try { - f(g() / __LINE__); - } catch (Throwable $e) { - while ($e = $e->getPrevious()) printf('%c', $e->getLine() + 23); - echo "\n"; - } - function f(int $i) { - if ($i < 0) f(); - try { - match ($i) { - 0 => 0 / 0, + <programlisting language="php" linenumbering="unnumbered"> + <![CDATA[ + <?php + try { + f(g() / __LINE__); + } catch (Throwable $e) { + while ($e = $e->getPrevious()) printf('%c', $e->getLine() + 23); + echo "\n"; + } + function f(int $i) { + if ($i < 0) f(); + try { + match ($i) { + 0 => 0 / 0, - 15, 36 => 0 / 0, - 14 => 0 / 0, - 37 => 0 / 0, + 15, 36 => 0 / 0, + 14 => 0 / 0, + 37 => 0 / 0, @@ -63,16 +65,16 @@ - 6 => 0 / 0, + 6 => 0 / 0, - 5 => 0 / 0, + 5 => 0 / 0, - 22 => 0 / 0, + 22 => 0 / 0, - 34, 35 => 0 / 0, + 34, 35 => 0 / 0, @@ -81,10 +83,10 @@ - 25 => 0 / 0, - 17, 21 => 0 / 0, + 25 => 0 / 0, + 17, 21 => 0 / 0, - 24, 32 => 0 / 0, + 24, 32 => 0 / 0, @@ -92,12 +94,12 @@ - 33 => 0 / 0, + 33 => 0 / 0, - 16 => 0 / 0, + 16 => 0 / 0, - 18 => 0 / 0, + 18 => 0 / 0, @@ -106,37 +108,37 @@ - 7 => 0 / 0, + 7 => 0 / 0, - 2 => 0 / 0, - 1, 20 => 0 / 0, - 10, 28 => 0 / 0, - 8, 12, 26 => 0 / 0, - 4, 9, 13 => 0 / 0, + 2 => 0 / 0, + 1, 20 => 0 / 0, + 10, 28 => 0 / 0, + 8, 12, 26 => 0 / 0, + 4, 9, 13 => 0 / 0, - 31 => 0 / 0, + 31 => 0 / 0, - 29 => 0 / 0, + 29 => 0 / 0, - 11 => 0 / 0, + 11 => 0 / 0, - 3, 19, 23 => 0 / 0, + 3, 19, 23 => 0 / 0, - 27 => 0 / 0, + 27 => 0 / 0, - 30 => 0 / 0, - }; - } finally { - f($i - 1); - } - } + 30 => 0 / 0, + }; + } finally { + f($i - 1); + } + } @@ -144,9 +146,11 @@ - function g() { - return __LINE__; - }</programlisting> + function g() { + return __LINE__; + } + ]]> + </programlisting> <simpara>"Catchline" と名付けた作品。実行するとトークン <literal>#base64_decode('SGVsbG8sIFdvcmxkIQ==')</literal> が得られる。</simpara> <simpara>トークンは PHP の式になっていて、評価すると <literal>Hello, World!</literal> という文字列になる。PHPer チャレンジのトークンには空白を含められないという制約があるが、こういった形でトークンにすれば回避できる。</simpara> </section> @@ -168,70 +172,94 @@ </listitem> </itemizedlist> <simpara>このうち 1つ目のケースは、 <literal>finally</literal> 節の中でエラーを投げると PHP 処理系が勝手に <literal>$previous</literal> を設定してくれる。</simpara> - <programlisting language="php" linenumbering="unnumbered"><?php - - try { - try { - throw new Exception("Error 1"); - } finally { - throw new Exception("Error 2"); - } - } catch (Exception $e) { - echo $e->getMessage() . PHP_EOL; - // => Error 2 - echo $e->getPrevious()->getMessage() . PHP_EOL; - // => Error 1 - }</programlisting> + <programlisting language="php" linenumbering="unnumbered"> + <![CDATA[ + <?php + + try { + try { + throw new Exception("Error 1"); + } finally { + throw new Exception("Error 2"); + } + } catch (Exception $e) { + echo $e->getMessage() . PHP_EOL; + // => Error 2 + echo $e->getPrevious()->getMessage() . PHP_EOL; + // => Error 1 + } + ]]> + </programlisting> <simpara>この知識を元に、トークンの出力部を解析してみる。</simpara> </section> <section xml:id="_出力部の解析"> <title>出力部の解析</title> <simpara>出力部をコメントや改行を追加して再掲する:</simpara> - <programlisting language="php" linenumbering="unnumbered"><?php - try { - f(g() / __LINE__); - } catch (Throwable $e) { - while ($e = $e->getPrevious()) { - printf('%c', $e->getLine() + 23); - } - echo "\n"; - }</programlisting> + <programlisting language="php" linenumbering="unnumbered"> + <![CDATA[ + <?php + try { + f(g() / __LINE__); + } catch (Throwable $e) { + while ($e = $e->getPrevious()) { + printf('%c', $e->getLine() + 23); + } + echo "\n"; + } + ]]> + </programlisting> <simpara>出力をおこなう <literal>catch</literal> 節を見てみると、 <literal>Throwable::getPrevious()</literal> を呼び出してエラーチェインを辿り、 <literal>Throwable::getLine()</literal> でエラーが発生した行数を取得している。その行数に <literal>23</literal> なるマジックナンバーを足し、フォーマット指定子 <literal>%c</literal> で出力している。</simpara> <simpara>フォーマット指定子 <literal>%c</literal> は、整数を ASCII コード<footnote>RAS syndrome</footnote> と見做して印字する。トークン <literal>#base64_decode('SGVsbG8sIFdvcmxkIQ==')</literal> の <literal>b</literal> であれば、ASCII コード <literal>98</literal> なので、75 行目で発生したエラー、</simpara> - <programlisting language="php" linenumbering="unnumbered"> 1, 20 => 0 / 0,</programlisting> + <programlisting language="php" linenumbering="unnumbered"> + <![CDATA[ + 1, 20 => 0 / 0, + ]]> + </programlisting> <simpara>によって表現されている。エラーを起こす方法はいろいろと考えられるが、今回はゼロ除算を使った。</simpara> <simpara>それでは、エラーチェインを作る箇所、関数 <literal>f()</literal> を見ていく。</simpara> </section> <section xml:id="_データ構成部の解析"> <title>データ構成部の解析</title> <simpara><literal>f()</literal> の定義を再掲する (エラーオブジェクトの行数を利用しているので、一部分だけ抜き出すと値が変わることに注意):</simpara> - <programlisting language="php" linenumbering="unnumbered">function f(int $i) { - if ($i < 0) f(); - try { - match ($i) { - 0 => 0 / 0, // 12 行目 - - - - 15, 36 => 0 / 0, - 14 => 0 / 0, - 37 => 0 / 0, - - // (略) - - 30 => 0 / 0, // 97 行目 - }; - } finally { - f($i - 1); - } - }</programlisting> + <programlisting language="php" linenumbering="unnumbered"> + <![CDATA[ + function f(int $i) { + if ($i < 0) f(); + try { + match ($i) { + 0 => 0 / 0, // 12 行目 + + + + 15, 36 => 0 / 0, + 14 => 0 / 0, + 37 => 0 / 0, + + // (略) + + 30 => 0 / 0, // 97 行目 + }; + } finally { + f($i - 1); + } + } + ]]> + </programlisting> <simpara>前述のように、 <literal>finally</literal> 節でエラーを投げると PHP 処理系が <literal>$previous</literal> を設定する。ここでは、エラーを繋げるために <literal>f()</literal> を再帰呼び出ししている。最初に <literal>f()</literal> を呼び出している箇所を確認すると、</simpara> - <programlisting language="php" linenumbering="unnumbered"><?php - try { - f(g() / __LINE__); // 3 行目</programlisting> - <programlisting language="php" linenumbering="unnumbered">function g() { - return __LINE__; // 111 行目 - }</programlisting> + <programlisting language="php" linenumbering="unnumbered"> + <![CDATA[ + <?php + try { + f(g() / __LINE__); // 3 行目 + ]]> + </programlisting> + <programlisting language="php" linenumbering="unnumbered"> + <![CDATA[ + function g() { + return __LINE__; // 111 行目 + } + ]]> + </programlisting> <simpara><literal>f()</literal> には <literal>111 / 3</literal> で <literal>37</literal> が渡されることがわかる。そこから 1 ずつ減らして再帰呼び出ししていき、0 より小さくなったら <literal>f()</literal> を引数なしで呼び出す。引数の数が足りないと呼び出しに失敗するので、再帰はここで止まる。</simpara> <simpara>エラーチェインは、最後に発生したエラーを先頭とした単方向連結リストになっているので、順に</simpara> <orderedlist numeration="arabic"> diff --git a/public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html b/public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html index 3197780..dd6da27 100644 --- a/public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html +++ b/public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html @@ -110,10 +110,10 @@ end</code></pre> </p> <pre class="monospaced highlight"><code>20:1: syntax error, unexpected local variable or method, expecting `then' or ';' or '\n' - if true puts 'Hello, World!' end - ^~~~ - 20:1: syntax error, unexpected `end', expecting end-of-input - ...f true puts 'Hello, World!' end</code></pre> +if true puts 'Hello, World!' end + ^~~~ +20:1: syntax error, unexpected `end', expecting end-of-input +...f true puts 'Hello, World!' end</code></pre> <p> 二つ目のメッセージは無視して一つ目を読むと、<code>then</code>か<code>;</code>か改行が来るはずのところ変数だかメソッドだかが現れたことによりエラーとなっているようだ。 diff --git a/public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html b/public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html index 4d63296..480c2eb 100644 --- a/public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html +++ b/public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html @@ -126,79 +126,79 @@ </p> <pre class="highlight" language="c" linenumbering="unnumbered"><code>#\ - i\ - n\ - c\ - l\ - u\ - d\ - e\ - <\ - s\ - t\ - d\ - i\ - o\ - .\ - h\ - >\ - /* - */ - i\ - n\ - t\ - /* - */ - m\ - a\ - i\ - n( - ){ - f\ - o\ - r( - i\ - n\ - t\ - /* - */ - i= - 1; - i< - 1\ - 0\ - 0; - i\ - +\ - +) - if - (i - %\ - 15 - == - 0) - p\ - r\ - i\ - n\ - t\ - f( - "\ - F\ - i\ - z\ - z\ - B\ - u\ - z\ - z\ - %\ - c\ - ", - 10 - ); +i\ +n\ +c\ +l\ +u\ +d\ +e\ +<\ +s\ +t\ +d\ +i\ +o\ +.\ +h\ +>\ +/* +*/ +i\ +n\ +t\ +/* +*/ +m\ +a\ +i\ +n( +){ +f\ +o\ +r( +i\ +n\ +t\ +/* +*/ +i= +1; +i< +1\ +0\ +0; +i\ ++\ ++) +if +(i +%\ +15 +== +0) +p\ +r\ +i\ +n\ +t\ +f( +"\ +F\ +i\ +z\ +z\ +B\ +u\ +z\ +z\ +%\ +c\ +", +10 +); - /* あとは同じように普通のプログラムを変形するだけなので省略 */</code></pre> +/* あとは同じように普通のプログラムを変形するだけなので省略 */</code></pre> <p> バックスラッシュを使った行継続がトークンを区切らない、というのがポイントだ。 @@ -280,11 +280,11 @@ a' まずは普通に書くとしよう。 </p> - <pre class="monospaced highlight"><code><?php + <pre class="highlight" language="php" linenumbering="unnumbered"><code><?php - for ($i = 1; $i < 100; $i++) { - echo (($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"; - }</code></pre> +for ($i = 1; $i < 100; $i++) { + echo (($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"; +}</code></pre> <p> 素直に書いた fizzbuzz とは言い難いが、このくらいは普通だということにしておかないと、この先がやっていられないので許してほしい。 @@ -299,12 +299,12 @@ a' <pre class="highlight" language="php" linenumbering="unnumbered"><code><?php - $s = range(1, 100); - array_walk( +$s = range(1, 100); +array_walk( $s, fn($i) => - printf((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"), - );</code></pre> + printf((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"), +);</code></pre> <p> <code>array_walk</code>や<code>range</code>、<code>printf</code>といった<code>for</code>よりも長いトークンが現れてしまったが、これは次節で直すことにする。なお、<code>echo</code>は文 (statement) であり式 (expression) ではないので、式である<code>printf</code>に置き換えた。 @@ -319,16 +319,16 @@ a' <pre class="highlight" language="php" linenumbering="unnumbered"><code><?php - $r = 'range'; - $w = 'array_walk'; - $p = 'printf'; +$r = 'range'; +$w = 'array_walk'; +$p = 'printf'; - $s = $r(1, 100); - $w( +$s = $r(1, 100); +$w( $s, fn($i) => - $p((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"), - );</code></pre> + $p((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"), +);</code></pre> <p> これで関数を呼び出している所は短くなった。では、<code>$r</code>や<code>$w</code>や<code>$p</code>、また<code>'Fizz'</code>や<code>'Buzz'</code>はどうやって 1行2文字に収めるのか。次のテクニックへ移ろう。 @@ -470,153 +470,153 @@ echo "$r\n";</code></pre> <pre class="highlight" language="php" linenumbering="unnumbered"><code><?php - $x - =# - 'i - S' - ;; - $y - =' - b! - '; - $c - =# - $x - ^# - $y - ;# - $x - =# - 'x - Om - '; - $y - =' - k! - o' - ;# - $r - =# - $x - ^# - $y - ;# - $x - =# - 'k - Sk - ~} - Ma - '; - $y - =' - x! - s! - k! - '; - $w - =# - $x - ^# - $y - ;# - $x - =# - 'z - Hd - G' - ;# - $y - =' - x! - ~! - '; - $p - =# - $x - ^# - $y - ;# - $x - =# - 'L - [p - '; - $y - =' - c! - '; - $f - =# - $x - ^# - $y - ;# - $x - =# - 'H - [p - '; - $y - =' - _! - '; - $b - =# - $x - ^# - $y - ;# - $b - [1 - ]= - $c - (# - 13 - *9 - ); - $s - =# - $r - (1 - ,( - 10 - ** - 2) - ); - $w - (# - $s - ,# - fn - (# - $i - )# - => - $p - (( - (# - $i - %3 - ?# - '' - :# - $f - ). - (# - $i - %5 - ?# - '' - :# - $b - )? - :# - $i - )# - .' - ') - );</code></pre> +$x +=# +'i +S' +;; +$y +=' +b! +'; +$c +=# +$x +^# +$y +;# +$x +=# +'x +Om +'; +$y +=' +k! +o' +;# +$r +=# +$x +^# +$y +;# +$x +=# +'k +Sk +~} +Ma +'; +$y +=' +x! +s! +k! +'; +$w +=# +$x +^# +$y +;# +$x +=# +'z +Hd +G' +;# +$y +=' +x! +~! +'; +$p +=# +$x +^# +$y +;# +$x +=# +'L +[p +'; +$y +=' +c! +'; +$f +=# +$x +^# +$y +;# +$x +=# +'H +[p +'; +$y +=' +_! +'; +$b +=# +$x +^# +$y +;# +$b +[1 +]= +$c +(# +13 +*9 +); +$s +=# +$r +(1 +,( +10 +** +2) +); +$w +(# +$s +,# +fn +(# +$i +)# +=> +$p +(( +(# +$i +%3 +?# +'' +:# +$f +). +(# +$i +%5 +?# +'' +:# +$b +)? +:# +$i +)# +.' +') +);</code></pre> </section> <section id="section--_感想など"> @@ -638,16 +638,16 @@ echo "$r\n";</code></pre> <pre class="highlight" language="php" linenumbering="unnumbered"><code><?php - printf(` - e\ - c\ - h\ - o\ - \ - 1\ - 2\ - 3\ - `);</code></pre> +printf(` +e\ +c\ +h\ +o\ +\ +1\ +2\ +3\ +`);</code></pre> <p> なお、ここでは簡単のため出力に<code>printf</code>をそのまま使っているが、実際には<code>printf</code>という文字列を合成して可変関数で呼び出す。 diff --git a/public/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/index.html b/public/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/index.html index dcc0de8..be78585 100644 --- a/public/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/index.html +++ b/public/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/index.html @@ -72,7 +72,8 @@ 注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。 </p> - <pre class="highlight" language="php" linenumbering="unnumbered"><code><?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + <pre class="highlight" language="php" linenumbering="unnumbered"><code> + <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> @@ -81,7 +82,8 @@ <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> - <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?></code></pre> + <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + </code></pre> <p> "And Then There Were None" (そして誰もいなくなった) と名付けた作品。変則 quine (自分自身と同じソースコードを出力するプログラム) になっている。 @@ -94,7 +96,8 @@ 実行してみると、次のような出力が得られる。 </p> - <pre class="highlight" language="php" linenumbering="unnumbered"><code># + <pre class="highlight" language="php" linenumbering="unnumbered"><code> + # <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> @@ -103,13 +106,15 @@ <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> - <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?></code></pre> + <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + </code></pre> <p> 1 行目を除き、先ほどのコードとほぼ同じものが出てきた。もう一度実行してみる。 </p> - <pre class="highlight" language="php" linenumbering="unnumbered"><code># + <pre class="highlight" language="php" linenumbering="unnumbered"><code> + # W <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> @@ -118,22 +123,25 @@ W <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> -<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?></code></pre> + <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> +</code></pre> <p> 今度は 2 行目が書き換えられた。すべての行が変化するまで繰り返すと次のようになる。 </p> - <pre class="highlight" language="php" linenumbering="unnumbered"><code># -W -E -L -O -V -E -P -H -P</code></pre> + <pre class="highlight" language="php" linenumbering="unnumbered"><code> + # + W + E + L + O + V + E + P + H + P +</code></pre> <p> トークン「#WELOVEPHP」が手に入った。 @@ -150,7 +158,9 @@ P</code></pre> Vim で開くと次のようになる (1 行目を抜粋)。 </p> - <pre class="highlight" language="php" linenumbering="unnumbered"><code><?php printf((isset($s)?fn($s)=>trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s='<200b><?php printf((isset($s)?fn($s)=>trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?></code></pre> + <pre class="highlight" language="php" linenumbering="unnumbered"><code> + <?php printf((isset($s)?fn($s)=>trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s='<200b><?php printf((isset($s)?fn($s)=>trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + </code></pre> <p> <code><200b></code>と表示されているのは、Unicode の U+200b で、ゼロ幅スペースである。 @@ -175,13 +185,17 @@ P</code></pre> 続いて、トークンへの変換ロジックを解析する。注目すべきはこの部分だ。以下、ゼロ幅スペースは Vim での表示に合わせて<code><200b></code>と記載する。 </p> - <pre class="highlight" language="php" linenumbering="unnumbered"><code>fn($s)=>chr(strlen($s)/3)</code></pre> + <pre class="highlight" language="php" linenumbering="unnumbered"><code> + fn($s)=>chr(strlen($s)/3) + </code></pre> <p> PHP の<code>strlen()</code>は文字列のバイト数を返す。1 行目の<code>$s</code>は以下の内容となっており、 </p> - <pre class="highlight" language="php" linenumbering="unnumbered"><code>$s='<200b><?php printf((isset($s)?fn($s)=>trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'</code></pre> + <pre class="highlight" language="php" linenumbering="unnumbered"><code> + $s='<200b><?php printf((isset($s)?fn($s)=>trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>' + </code></pre> <p> このソースコードは UTF-8 で書かれているので、105 バイトになる。それを 3 で割ると 35 となり、これは<code>#</code>の ASCII コードと一致する。他の行も、同様にしてゼロ幅スペースを詰めることで文字列長を調整し、トークンをエンコードしている。 diff --git a/public/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html b/public/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html index 6896530..a7f86b3 100644 --- a/public/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html +++ b/public/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html @@ -83,23 +83,23 @@ </p> <pre class="highlight" language="php" linenumbering="unnumbered"><code><?php - try { - f(g() / __LINE__); - } catch (Throwable $e) { - while ($e = $e->getPrevious()) printf('%c', $e->getLine() + 23); - echo "\n"; - } - function f(int $i) { - if ($i < 0) f(); - try { +try { + f(g() / __LINE__); +} catch (Throwable $e) { + while ($e = $e->getPrevious()) printf('%c', $e->getLine() + 23); + echo "\n"; +} +function f(int $i) { + if ($i < 0) f(); + try { match ($i) { - 0 => 0 / 0, + 0 => 0 / 0, - 15, 36 => 0 / 0, - 14 => 0 / 0, - 37 => 0 / 0, + 15, 36 => 0 / 0, + 14 => 0 / 0, + 37 => 0 / 0, @@ -110,16 +110,16 @@ - 6 => 0 / 0, + 6 => 0 / 0, - 5 => 0 / 0, + 5 => 0 / 0, - 22 => 0 / 0, + 22 => 0 / 0, - 34, 35 => 0 / 0, + 34, 35 => 0 / 0, @@ -128,10 +128,10 @@ - 25 => 0 / 0, - 17, 21 => 0 / 0, + 25 => 0 / 0, + 17, 21 => 0 / 0, - 24, 32 => 0 / 0, + 24, 32 => 0 / 0, @@ -139,12 +139,12 @@ - 33 => 0 / 0, + 33 => 0 / 0, - 16 => 0 / 0, + 16 => 0 / 0, - 18 => 0 / 0, + 18 => 0 / 0, @@ -153,37 +153,37 @@ - 7 => 0 / 0, + 7 => 0 / 0, - 2 => 0 / 0, - 1, 20 => 0 / 0, - 10, 28 => 0 / 0, - 8, 12, 26 => 0 / 0, - 4, 9, 13 => 0 / 0, + 2 => 0 / 0, + 1, 20 => 0 / 0, + 10, 28 => 0 / 0, + 8, 12, 26 => 0 / 0, + 4, 9, 13 => 0 / 0, - 31 => 0 / 0, + 31 => 0 / 0, - 29 => 0 / 0, + 29 => 0 / 0, - 11 => 0 / 0, + 11 => 0 / 0, - 3, 19, 23 => 0 / 0, + 3, 19, 23 => 0 / 0, - 27 => 0 / 0, + 27 => 0 / 0, - 30 => 0 / 0, + 30 => 0 / 0, }; - } finally { + } finally { f($i - 1); - } - } + } +} @@ -191,9 +191,9 @@ - function g() { - return __LINE__; - }</code></pre> +function g() { + return __LINE__; +}</code></pre> <p> "Catchline" と名付けた作品。実行するとトークン<code>#base64_decode('SGVsbG8sIFdvcmxkIQ==')</code>が得られる。 @@ -239,18 +239,18 @@ <pre class="highlight" language="php" linenumbering="unnumbered"><code><?php - try { - try { - throw new Exception("Error 1"); - } finally { - throw new Exception("Error 2"); - } - } catch (Exception $e) { - echo $e->getMessage() . PHP_EOL; - // => Error 2 - echo $e->getPrevious()->getMessage() . PHP_EOL; - // => Error 1 - }</code></pre> +try { + try { + throw new Exception("Error 1"); + } finally { + throw new Exception("Error 2"); + } +} catch (Exception $e) { + echo $e->getMessage() . PHP_EOL; + // => Error 2 + echo $e->getPrevious()->getMessage() . PHP_EOL; + // => Error 1 +}</code></pre> <p> この知識を元に、トークンの出力部を解析してみる。 @@ -264,14 +264,14 @@ </p> <pre class="highlight" language="php" linenumbering="unnumbered"><code><?php - try { - f(g() / __LINE__); - } catch (Throwable $e) { - while ($e = $e->getPrevious()) { - printf('%c', $e->getLine() + 23); - } - echo "\n"; - }</code></pre> +try { + f(g() / __LINE__); +} catch (Throwable $e) { + while ($e = $e->getPrevious()) { + printf('%c', $e->getLine() + 23); + } + echo "\n"; +}</code></pre> <p> 出力をおこなう<code>catch</code>節を見てみると、<code>Throwable::getPrevious()</code>を呼び出してエラーチェインを辿り、<code>Throwable::getLine()</code>でエラーが発生した行数を取得している。その行数に<code>23</code>なるマジックナンバーを足し、フォーマット指定子<code>%c</code>で出力している。 @@ -281,7 +281,7 @@ フォーマット指定子<code>%c</code>は、整数を ASCII コード<span></span>と見做して印字する。トークン<code>#base64_decode('SGVsbG8sIFdvcmxkIQ==')</code>の<code>b</code>であれば、ASCII コード<code>98</code>なので、75 行目で発生したエラー、 </p> - <pre class="highlight" language="php" linenumbering="unnumbered"><code> 1, 20 => 0 / 0,</code></pre> + <pre class="highlight" language="php" linenumbering="unnumbered"><code>1, 20 => 0 / 0,</code></pre> <p> によって表現されている。エラーを起こす方法はいろいろと考えられるが、今回はゼロ除算を使った。 @@ -299,9 +299,9 @@ </p> <pre class="highlight" language="php" linenumbering="unnumbered"><code>function f(int $i) { - if ($i < 0) f(); - try { - match ($i) { + if ($i < 0) f(); + try { + match ($i) { 0 => 0 / 0, // 12 行目 @@ -313,23 +313,23 @@ // (略) 30 => 0 / 0, // 97 行目 - }; - } finally { - f($i - 1); - } - }</code></pre> + }; + } finally { + f($i - 1); + } +}</code></pre> <p> 前述のように、<code>finally</code>節でエラーを投げると PHP 処理系が<code>$previous</code>を設定する。ここでは、エラーを繋げるために<code>f()</code>を再帰呼び出ししている。最初に<code>f()</code>を呼び出している箇所を確認すると、 </p> <pre class="highlight" language="php" linenumbering="unnumbered"><code><?php - try { - f(g() / __LINE__); // 3 行目</code></pre> +try { + f(g() / __LINE__); // 3 行目</code></pre> <pre class="highlight" language="php" linenumbering="unnumbered"><code>function g() { - return __LINE__; // 111 行目 - }</code></pre> + return __LINE__; // 111 行目 +}</code></pre> <p> <code>f()</code>には<code>111 / 3</code>で<code>37</code>が渡されることがわかる。そこから 1 ずつ減らして再帰呼び出ししていき、0 より小さくなったら<code>f()</code>を引数なしで呼び出す。引数の数が足りないと呼び出しに失敗するので、再帰はここで止まる。 |
