aboutsummaryrefslogtreecommitdiffhomepage
path: root/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.xml
diff options
context:
space:
mode:
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.xml218
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">&lt;?php
- try {
- f(g() / __LINE__);
- } catch (Throwable $e) {
- while ($e = $e-&gt;getPrevious()) printf('%c', $e-&gt;getLine() + 23);
- echo "\n";
- }
- function f(int $i) {
- if ($i &lt; 0) f();
- try {
- match ($i) {
- 0 =&gt; 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 =&gt; 0 / 0,
- 14 =&gt; 0 / 0,
- 37 =&gt; 0 / 0,
+ 15, 36 => 0 / 0,
+ 14 => 0 / 0,
+ 37 => 0 / 0,
@@ -63,16 +65,16 @@
- 6 =&gt; 0 / 0,
+ 6 => 0 / 0,
- 5 =&gt; 0 / 0,
+ 5 => 0 / 0,
- 22 =&gt; 0 / 0,
+ 22 => 0 / 0,
- 34, 35 =&gt; 0 / 0,
+ 34, 35 => 0 / 0,
@@ -81,10 +83,10 @@
- 25 =&gt; 0 / 0,
- 17, 21 =&gt; 0 / 0,
+ 25 => 0 / 0,
+ 17, 21 => 0 / 0,
- 24, 32 =&gt; 0 / 0,
+ 24, 32 => 0 / 0,
@@ -92,12 +94,12 @@
- 33 =&gt; 0 / 0,
+ 33 => 0 / 0,
- 16 =&gt; 0 / 0,
+ 16 => 0 / 0,
- 18 =&gt; 0 / 0,
+ 18 => 0 / 0,
@@ -106,37 +108,37 @@
- 7 =&gt; 0 / 0,
+ 7 => 0 / 0,
- 2 =&gt; 0 / 0,
- 1, 20 =&gt; 0 / 0,
- 10, 28 =&gt; 0 / 0,
- 8, 12, 26 =&gt; 0 / 0,
- 4, 9, 13 =&gt; 0 / 0,
+ 2 => 0 / 0,
+ 1, 20 => 0 / 0,
+ 10, 28 => 0 / 0,
+ 8, 12, 26 => 0 / 0,
+ 4, 9, 13 => 0 / 0,
- 31 =&gt; 0 / 0,
+ 31 => 0 / 0,
- 29 =&gt; 0 / 0,
+ 29 => 0 / 0,
- 11 =&gt; 0 / 0,
+ 11 => 0 / 0,
- 3, 19, 23 =&gt; 0 / 0,
+ 3, 19, 23 => 0 / 0,
- 27 =&gt; 0 / 0,
+ 27 => 0 / 0,
- 30 =&gt; 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">&lt;?php
-
- try {
- try {
- throw new Exception("Error 1");
- } finally {
- throw new Exception("Error 2");
- }
- } catch (Exception $e) {
- echo $e-&gt;getMessage() . PHP_EOL;
- // =&gt; Error 2
- echo $e-&gt;getPrevious()-&gt;getMessage() . PHP_EOL;
- // =&gt; 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">&lt;?php
- try {
- f(g() / __LINE__);
- } catch (Throwable $e) {
- while ($e = $e-&gt;getPrevious()) {
- printf('%c', $e-&gt;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 =&gt; 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 &lt; 0) f();
- try {
- match ($i) {
- 0 =&gt; 0 / 0, // 12 行目
-
-
-
- 15, 36 =&gt; 0 / 0,
- 14 =&gt; 0 / 0,
- 37 =&gt; 0 / 0,
-
- // (略)
-
- 30 =&gt; 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">&lt;?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">