From c754d24b162ecd504f3c4bdd8632045dd0398768 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Thu, 27 Nov 2025 05:05:04 +0900 Subject: feat(nuldoc): Djot to Markdown --- .../phperkaigi-2023-unused-token-quiz-3.dj | 289 --------------------- .../phperkaigi-2023-unused-token-quiz-3.md | 281 ++++++++++++++++++++ 2 files changed, 281 insertions(+), 289 deletions(-) delete mode 100644 services/nuldoc/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.dj create mode 100644 services/nuldoc/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.md (limited to 'services/nuldoc/content/posts/2023-01-10') diff --git a/services/nuldoc/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.dj b/services/nuldoc/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.dj deleted file mode 100644 index 9cbb15b..0000000 --- a/services/nuldoc/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.dj +++ /dev/null @@ -1,289 +0,0 @@ ---- -[article] -uuid = "89722cfb-7f4b-4e96-80bc-e0096e5eeef6" -title = "PHPerKaigi 2023: ボツになったトークン問題 その 3" -description = "来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、ボツになった問題を公開する (その 3)。" -tags = [ - "php", - "phperkaigi", -] - -[[article.revisions]] -date = "2023-01-10" -remark = "公開" ---- -{#intro} -# はじめに - -2023 年 3 月 23 日から 25 日にかけて開催予定 (記事執筆時点) の [PHPerKaigi 2023](https://phperkaigi.jp/2023/) において、 -昨年と同様に、弊社 [デジタルサーカス株式会社](https://www.dgcircus.com/) からトークン問題を出題予定である。 - -昨年のトークン問題の記事はこちら: [PHPerKaigi 2022 トークン問題の解説](/posts/2022-04-09/phperkaigi-2022-tokens/) - -すでに 2023 年用の問題は作成済みであるが、その制作過程の中でいくつかボツ問ができた。 -せっかくなので、PHPerKaigi 開催を待つ間に紹介しようと思う。 - -10 月から 2 月まで、毎月 1 記事ずつ公開していく予定 (忘れていなければ → 忘れていたので 12 月公開予定だった記事を今書いている)。 - -* その 1 はこちら: [PHPerKaigi 2023: ボツになったトークン問題 その 1](/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/) -* その 2 はこちら: [PHPerKaigi 2023: ボツになったトークン問題 その 2](/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/) - -{#quiz} -# 問題 - -注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。 - -```php -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==')` が得られる。 - -トークンは PHP の式になっていて、評価すると `Hello, World!` という文字列になる。PHPer チャレンジのトークンには空白を含められないという制約があるが、こういった形でトークンにすれば回避できる。 - -{#commentary} -# 解説 - -{#summary} -## 概要 - -例外が発生した行数にデータをエンコードし、それを `catch` で捕まえて表示している。 - -{#chain-of-exceptions} -## 例外オブジェクトの連鎖 - -[`Exception`](https://www.php.net/class.Exception) や [`Error`](https://www.php.net/class.Error) には `$previous` というプロパティがあり、コンストラクタの第3引数から渡すことができる。主に 2つの用法がある: - -* エラーを処理している途中に起こった別のエラーに、元のエラー情報を含める -* 内部エラーをラップして作られたエラーに、内部エラーの情報を含める - -このうち 1つ目のケースは、 `finally` 節の中でエラーを投げると PHP 処理系が勝手に `$previous` を設定してくれる。 - -```php -getMessage() . PHP_EOL; - // => Error 2 - echo $e->getPrevious()->getMessage() . PHP_EOL; - // => Error 1 -} -``` - -この知識を元に、トークンの出力部を解析してみる。 - -{#output} -## 出力部の解析 - -出力部をコメントや改行を追加して再掲する: - -```php -getPrevious()) { - printf('%c', $e->getLine() + 23); - } - echo "\n"; -} -``` - -出力をおこなう `catch` 節を見てみると、 `Throwable::getPrevious()` を呼び出してエラーチェインを辿り、 `Throwable::getLine()` でエラーが発生した行数を取得している。その行数に `23` なるマジックナンバーを足し、フォーマット指定子 `%c` で出力している。 - -フォーマット指定子 `%c` は、整数を ASCII コード[^ras-syndrome] と見做して印字する。トークン `#base64_decode('SGVsbG8sIFdvcmxkIQ==')` の `b` であれば、ASCII コード `98` なので、75 行目で発生したエラー、 - -```php -1, 20 => 0 / 0, -``` - -によって表現されている。エラーを起こす方法はいろいろと考えられるが、今回はゼロ除算を使った。 - -それでは、エラーチェインを作る箇所、関数 `f()` を見ていく。 - -[^ras-syndrome]: RAS syndrome - -{#data-construction} -## データ構成部の解析 - -`f()` の定義を再掲する (エラーオブジェクトの行数を利用しているので、一部分だけ抜き出すと値が変わることに注意): - -```php -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 -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==')` が得られる。 + +トークンは PHP の式になっていて、評価すると `Hello, World!` という文字列になる。PHPer チャレンジのトークンには空白を含められないという制約があるが、こういった形でトークンにすれば回避できる。 + +# 解説 {#commentary} + +## 概要 {#summary} + +例外が発生した行数にデータをエンコードし、それを `catch` で捕まえて表示している。 + +## 例外オブジェクトの連鎖 {#chain-of-exceptions} + +[`Exception`](https://www.php.net/class.Exception) や [`Error`](https://www.php.net/class.Error) には `$previous` というプロパティがあり、コンストラクタの第3引数から渡すことができる。主に 2つの用法がある: + +* エラーを処理している途中に起こった別のエラーに、元のエラー情報を含める +* 内部エラーをラップして作られたエラーに、内部エラーの情報を含める + +このうち 1つ目のケースは、 `finally` 節の中でエラーを投げると PHP 処理系が勝手に `$previous` を設定してくれる。 + +```php +getMessage() . PHP_EOL; + // => Error 2 + echo $e->getPrevious()->getMessage() . PHP_EOL; + // => Error 1 +} +``` + +この知識を元に、トークンの出力部を解析してみる。 + +## 出力部の解析 {#output} + +出力部をコメントや改行を追加して再掲する: + +```php +getPrevious()) { + printf('%c', $e->getLine() + 23); + } + echo "\n"; +} +``` + +出力をおこなう `catch` 節を見てみると、 `Throwable::getPrevious()` を呼び出してエラーチェインを辿り、 `Throwable::getLine()` でエラーが発生した行数を取得している。その行数に `23` なるマジックナンバーを足し、フォーマット指定子 `%c` で出力している。 + +フォーマット指定子 `%c` は、整数を ASCII コード[^ras-syndrome] と見做して印字する。トークン `#base64_decode('SGVsbG8sIFdvcmxkIQ==')` の `b` であれば、ASCII コード `98` なので、75 行目で発生したエラー、 + +```php +1, 20 => 0 / 0, +``` + +によって表現されている。エラーを起こす方法はいろいろと考えられるが、今回はゼロ除算を使った。 + +それでは、エラーチェインを作る箇所、関数 `f()` を見ていく。 + +[^ras-syndrome]: RAS syndrome + +## データ構成部の解析 {#data-construction} + +`f()` の定義を再掲する (エラーオブジェクトの行数を利用しているので、一部分だけ抜き出すと値が変わることに注意): + +```php +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 +