From 4681e861efcdf9b0b73e3803e70712bc55162863 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sat, 8 Aug 2026 10:38:18 +0900 Subject: feat(slides): improve slide controller --- .../phperkaigi-2023-unused-token-quiz-3/index.html | 333 +++++++++++---------- 1 file changed, 170 insertions(+), 163 deletions(-) (limited to 'services/nuldoc/public/blog/posts/2023-01-10') diff --git a/services/nuldoc/public/blog/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html b/services/nuldoc/public/blog/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html index 69b1fd47..ef1f779c 100644 --- a/services/nuldoc/public/blog/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html +++ b/services/nuldoc/public/blog/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html @@ -15,7 +15,7 @@ PHPerKaigi 2023: ボツになったトークン問題 その 3|REPL: Rest-Eat-Program Loop - +
@@ -121,118 +121,119 @@ 注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。

-
<?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, -
-
-
-
-
-
-
-
-
-
-
6 => 0 / 0, -
-
5 => 0 / 0, -
-
22 => 0 / 0, -
-
-
-
-
34, 35 => 0 / 0, -
-
-
-
-
-
-
-
-
25 => 0 / 0, -
17, 21 => 0 / 0, -
-
24, 32 => 0 / 0, -
-
-
-
-
-
-
-
33 => 0 / 0, -
-
16 => 0 / 0, -
-
-
18 => 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, -
-
-
-
-
-
31 => 0 / 0, -
-
29 => 0 / 0, -
-
11 => 0 / 0, -
-
-
-
3, 19, 23 => 0 / 0, -
-
-
27 => 0 / 0, -
-
30 => 0 / 0, -
}; -
} finally { -
f($i - 1); -
} -
} -
-
-
-
-
-
-
-
function g() { -
return __LINE__; -
}
+
<?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,
+
+
+
+
+
+
+
+
+
+
+      6 => 0 / 0,
+
+      5 => 0 / 0,
+
+      22 => 0 / 0,
+
+
+
+
+      34, 35 => 0 / 0,
+
+
+
+
+
+
+
+
+      25 => 0 / 0,
+      17, 21 => 0 / 0,
+
+      24, 32 => 0 / 0,
+
+
+
+
+
+
+
+      33 => 0 / 0,
+
+      16 => 0 / 0,
+
+
+      18 => 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,
+
+
+
+
+
+      31 => 0 / 0,
+
+      29 => 0 / 0,
+
+      11 => 0 / 0,
+
+
+
+      3, 19, 23 => 0 / 0,
+
+
+      27 => 0 / 0,
+
+      30 => 0 / 0,
+    };
+  } finally {
+    f($i - 1);
+  }
+}
+
+
+
+
+
+
+
+function g() {
+  return __LINE__;
+}
+

“Catchline” と名付けた作品。実行するとトークン #base64_decode('SGVsbG8sIFdvcmxkIQ==') が得られる。 @@ -266,20 +267,21 @@ このうち 1つ目のケースは、 finally 節の中でエラーを投げると PHP 処理系が勝手に $previous を設定してくれる。

-
<?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 -
}
+
<?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
+}
+

この知識を元に、トークンの出力部を解析してみる。 @@ -291,15 +293,16 @@ 出力部をコメントや改行を追加して再掲する:

-
<?php -
try { -
f(g() / __LINE__); -
} catch (Throwable $e) { -
while ($e = $e->getPrevious()) { -
printf('%c', $e->getLine() + 23); -
} -
echo "\n"; -
}
+
<?php
+try {
+  f(g() / __LINE__);
+} catch (Throwable $e) {
+  while ($e = $e->getPrevious()) {
+    printf('%c', $e->getLine() + 23);
+  }
+  echo "\n";
+}
+

出力をおこなう catch 節を見てみると、 Throwable::getPrevious() を呼び出してエラーチェインを辿り、 Throwable::getLine() でエラーが発生した行数を取得している。その行数に 23 なるマジックナンバーを足し、フォーマット指定子 %c で出力している。 @@ -308,7 +311,8 @@ フォーマット指定子 %c は、整数を ASCII コード[1] と見做して印字する。トークン #base64_decode('SGVsbG8sIFdvcmxkIQ==')b であれば、ASCII コード 98 なので、75 行目で発生したエラー、

-
1, 20 => 0 / 0,
+
1, 20 => 0 / 0,
+

によって表現されている。エラーを起こす方法はいろいろと考えられるが、今回はゼロ除算を使った。 @@ -323,39 +327,42 @@ f() の定義を再掲する (エラーオブジェクトの行数を利用しているので、一部分だけ抜き出すと値が変わることに注意):

-
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); -
} -
}
+
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);
+  }
+}
+

前述のように、 finally 節でエラーを投げると PHP 処理系が $previous を設定する。ここでは、エラーを繋げるために f() を再帰呼び出ししている。最初に f() を呼び出している箇所を確認すると、

-
<?php -
try { -
f(g() / __LINE__); // 3 行目
+
<?php
+try {
+  f(g() / __LINE__); // 3 行目
+
-
function g() { -
return __LINE__; // 111 行目 -
}
+
function g() {
+  return __LINE__; // 111 行目
+}
+

f() には 111 / 337 が渡されることがわかる。そこから 1 ずつ減らして再帰呼び出ししていき、0 より小さくなったら f() を引数なしで呼び出す。引数の数が足りないと呼び出しに失敗するので、再帰はここで止まる。 -- cgit v1.3.1