From dae1b45d1f3bc805161606ca5815dac210358db7 Mon Sep 17 00:00:00 2001
From: nsfisis
Date: Tue, 10 Jan 2023 01:50:37 +0900
Subject: new post: phperkaigi-2023-unused-token-quiz-3
---
NOTE.md | 2 +-
.../phperkaigi-2023-unused-token-quiz-3.adoc | 279 +++++++++++++
lib/command.rb | 4 +-
public/posts/2021-03-05/my-first-post/index.html | 1 +
public/posts/2021-03-30/phperkaigi-2021/index.html | 1 +
.../index.html | 1 +
.../python-unbound-local-error/index.html | 1 +
.../ruby-detect-running-implementation/index.html | 1 +
.../ruby-then-keyword-and-case-in/index.html | 1 +
.../rust-where-are-primitive-types-from/index.html | 1 +
.../index.html | 1 +
.../vim-swap-order-of-selected-lines/index.html | 1 +
.../2022-04-09/phperkaigi-2022-tokens/index.html | 1 +
.../index.html | 1 +
public/posts/2022-05-01/phperkaigi-2022/index.html | 1 +
.../php-conference-okinawa-code-golf/index.html | 1 +
.../index.html | 1 +
.../index.html | 1 +
.../phperkaigi-2023-unused-token-quiz-1/index.html | 1 +
.../setup-server-for-this-site/index.html | 1 +
.../phperkaigi-2023-unused-token-quiz-2/index.html | 1 +
.../phperkaigi-2023-unused-token-quiz-3/index.html | 460 +++++++++++++++++++++
public/posts/index.html | 22 +-
public/tags/php/index.html | 16 +
public/tags/phperkaigi/index.html | 16 +
templates/document__post.html.erb | 9 +
26 files changed, 820 insertions(+), 6 deletions(-)
create mode 100644 content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.adoc
create mode 100644 public/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html
diff --git a/NOTE.md b/NOTE.md
index c8c6098..88c5e55 100644
--- a/NOTE.md
+++ b/NOTE.md
@@ -12,7 +12,7 @@ Create a new post.
```
$ mkdir -p content/posts/$(date +'%Y-%m-%d')
-$ touch content/posts/$(date +'%Y-%m-%d')/[TITLE].md
+$ touch content/posts/$(date +'%Y-%m-%d')/[TITLE].adoc
```
## TODO
diff --git a/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.adoc b/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.adoc
new file mode 100644
index 0000000..29a86a1
--- /dev/null
+++ b/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.adoc
@@ -0,0 +1,279 @@
+= PHPerKaigi 2023: ボツになったトークン問題 その 3
+:tags: php, phperkaigi
+:description: 来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、 \
+ ボツになった問題を公開する (その 3)。
+:revision-1: 2023-01-10 公開
+
+
+== はじめに
+
+2023 年 3 月 23 日から 25 日にかけて開催予定 (記事執筆時点) の https://phperkaigi.jp/2023/[PHPerKaigi 2023] において、
+昨年と同様に、弊社 https://www.dgcircus.com/[デジタルサーカス株式会社] からトークン問題を出題予定である。
+
+昨年のトークン問題の記事はこちら: link:/posts/2022-04-09/phperkaigi-2022-tokens/[PHPerKaigi 2022 トークン問題の解説]
+
+すでに 2023 年用の問題は作成済みであるが、その制作過程の中でいくつかボツ問ができた。せっかくなので、PHPerKaigi 開催を待つ間に紹介しようと思う。
+
+10 月から 2 月まで、毎月 1 記事ずつ公開していく予定 (忘れていなければ → 忘れていたので 12 月公開予定だった記事を今書いている)。
+
+* その 1 はこちら: link:/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/[PHPerKaigi 2023: ボツになったトークン問題 その 1]
+* その 2 はこちら: link:/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/[PHPerKaigi 2023: ボツになったトークン問題 その 2]
+
+
+== 問題
+
+注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。
+
+[source,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 チャレンジのトークンには空白を含められないという制約があるが、こういった形でトークンにすれば回避できる。
+
+== 解説
+
+=== 概要
+
+例外が発生した行数にデータをエンコードし、それを `catch` で捕まえて表示している。
+
+=== 例外オブジェクトの連鎖
+
+https://www.php.net/class.Exception[`Exception`] や https://www.php.net/class.Error[`Error`] には `$previous` というプロパティがあり、コンストラクタの第3引数から渡すことができる。主に 2つの用法がある:
+
+* エラーを処理している途中に起こった別のエラーに、元のエラー情報を含める
+* 内部エラーをラップして作られたエラーに、内部エラーの情報を含める
+
+このうち 1つ目のケースは、 `finally` 節の中でエラーを投げると PHP 処理系が勝手に `$previous` を設定してくれる。
+
+[source,php]
+----
+getMessage() . PHP_EOL;
+ // => Error 2
+ echo $e->getPrevious()->getMessage() . PHP_EOL;
+ // => Error 1
+}
+----
+
+この知識を元に、トークンの出力部を解析してみる。
+
+=== 出力部の解析
+
+出力部をコメントや改行を追加して再掲する:
+
+[source,php]
+----
+getPrevious()) {
+ printf('%c', $e->getLine() + 23);
+ }
+ echo "\n";
+}
+----
+
+出力をおこなう `catch` 節を見てみると、 `Throwable::getPrevious()` を呼び出してエラーチェインを辿り、 `Throwable::getLine()` でエラーが発生した行数を取得している。その行数に `23` なるマジックナンバーを足し、フォーマット指定子 `%c` で出力している。
+
+フォーマット指定子 `%c` は、整数を ASCII コードfootnote:[RAS syndrome] と見做して印字する。トークン `#base64_decode('SGVsbG8sIFdvcmxkIQ==')` の `b` であれば、ASCII コード `98` なので、75 行目で発生したエラー、
+
+```
+ 1, 20 => 0 / 0,
+```
+
+によって表現されている。エラーを起こす方法はいろいろと考えられるが、今回はゼロ除算を使った。
+
+それでは、エラーチェインを作る箇所、関数 `f()` を見ていく。
+
+=== データ構成部の解析
+
+`f()` の定義を再掲する (エラーオブジェクトの行数を利用しているので、一部分だけ抜き出すと値が変わることに注意):
+
+[source,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()` を呼び出している箇所を確認すると、
+
+[source,php]
+----
+
+
+