diff options
Diffstat (limited to 'content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.xml')
| -rw-r--r-- | content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.xml | 218 |
1 files changed, 123 insertions, 95 deletions
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"> |
