From d30dfc89bf1b673b2fdc0638766b930adaec228c Mon Sep 17 00:00:00 2001
From: nsfisis
<?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==') が得られる。
@@ -247,20 +248,22 @@
このうち 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
+}
+ この知識を元に、トークンの出力部を解析してみる。 @@ -273,15 +276,17 @@ 出力部をコメントや改行を追加して再掲する:
-<?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 で出力している。
@@ -291,7 +296,9 @@
フォーマット指定子 %c は、整数を ASCII コード と見做して印字する。トークン #base64_decode('SGVsbG8sIFdvcmxkIQ==') の b であれば、ASCII コード 98 なので、75 行目で発生したエラー、
1, 20 => 0 / 0,
+ 1, 20 => 0 / 0,
+
によって表現されている。エラーを起こす方法はいろいろと考えられるが、今回はゼロ除算を使った。
@@ -308,38 +315,44 @@
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 / 3 で 37 が渡されることがわかる。そこから 1 ずつ減らして再帰呼び出ししていき、0 より小さくなったら f() を引数なしで呼び出す。引数の数が足りないと呼び出しに失敗するので、再帰はここで止まる。
--
cgit v1.2.3-70-g09d2