diff options
| author | nsfisis <nsfisis@gmail.com> | 2022-11-19 14:23:32 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2022-11-19 14:25:59 +0900 |
| commit | 6209453817da9922f28bac1bb1522c6d380630ab (patch) | |
| tree | 19e0699e751af387d549d6720ca215c8065b3c0c /content/posts/2022-04-09 | |
| parent | 0cafa073914b5e0b162b735a7f8445fb2aa8a604 (diff) | |
| download | blog.nsfisis.dev-6209453817da9922f28bac1bb1522c6d380630ab.tar.gz blog.nsfisis.dev-6209453817da9922f28bac1bb1522c6d380630ab.tar.zst blog.nsfisis.dev-6209453817da9922f28bac1bb1522c6d380630ab.zip | |
Hugo to Asciidoctor
Diffstat (limited to 'content/posts/2022-04-09')
| -rw-r--r-- | content/posts/2022-04-09/phperkaigi-2022-tokens.adoc (renamed from content/posts/2022-04-09/phperkaigi-2022-tokens.md) | 244 |
1 files changed, 126 insertions, 118 deletions
diff --git a/content/posts/2022-04-09/phperkaigi-2022-tokens.md b/content/posts/2022-04-09/phperkaigi-2022-tokens.adoc index 5640b8f..51d9a36 100644 --- a/content/posts/2022-04-09/phperkaigi-2022-tokens.md +++ b/content/posts/2022-04-09/phperkaigi-2022-tokens.adoc @@ -1,28 +1,24 @@ ---- -title: "PHPerKaigi 2022 トークン問題の解説" -date: 2022-04-09T21:50:19+09:00 -lastmod: 2022-04-16 -tags: ["conference", "php", "phperkaigi"] -summary: | - PHPerKaigi 2022 で私が作成した PHPer チャレンジ問題を解説する。 -changelog: - 2022-04-09: 公開 - 2022-04-16: 2問目、3問目の解説を追加、1問目に加筆 ---- - -# はじめに - -本日開始された [PHPerKaigi 2022](https://phperkaigi.jp/2022/) の PHPer チャレンジにおいて、弊社 [デジタルサーカス株式会社](https://www.dgcircus.com/) の問題を 3問作成した。この記事では、これらの問題の解説をおこなう。 += PHPerKaigi 2022 トークン問題の解説 +:tags: conference, php, phperkaigi +:description: PHPerKaigi 2022 で私が作成した PHPer チャレンジ問題を解説する。 +:revision-1: 2022-04-09 公開 +:revision-2: 2022-04-16 2問目、3問目の解説を追加、1問目に加筆 -リポジトリはこちら: https://github.com/nsfisis/PHPerKaigi2022-tokens +== はじめに +本日開始された https://phperkaigi.jp/2022/[PHPerKaigi 2022] の PHPer +チャレンジにおいて、弊社 +https://www.dgcircus.com/[デジタルサーカス株式会社] の問題を +3問作成した。この記事では、これらの問題の解説をおこなう。 +リポジトリはこちら: https://github.com/nsfisis/PHPerKaigi2022-tokens -# 第1問 brainf_ck.php +== 第1問 brainf_ck.php ソースコードはこちら。実行には PHP 8.1 以上が必要なので注意。 -```php +[source,php] +---- <?php declare(strict_types=0O1); @@ -92,29 +88,28 @@ $🐘([ $👉, $👎, $📝, $👈, $📝, ]); -``` +---- この問題は、単に適切なバージョンの PHP で動かせばトークンが得られる。 +=== 解説 -## 解説 - -### 絵文字 +==== 絵文字 -まず目につくのは大量の絵文字だろう。 -PHP は識別子に使用できる文字の範囲が広く、絵文字も使うことができる。 +まず目につくのは大量の絵文字だろう。 PHP +は識別子に使用できる文字の範囲が広く、絵文字も使うことができる。 +==== プログラム全体 -### プログラム全体 - -Brainf\*ck のインタプリタとプログラムになっている。 -Brainf\*ck とは、難解プログラミング言語のひとつであり、ここで説明するよりも Wikipedia の該当ページを読んだ方がよい。 +Brainf*ck のインタプリタとプログラムになっている。 Brainf*ck +とは、難解プログラミング言語のひとつであり、ここで説明するよりも +Wikipedia の該当ページを読んだ方がよい。 https://ja.wikipedia.org/wiki/Brainfuck -なお、brainf\*ck プログラムを普通の書き方で書くと、次のようになる。 +なお、brainf*ck プログラムを普通の書き方で書くと、次のようになる。 -``` +.... + + + + + + + + + + [ > + + + @@ -137,7 +132,7 @@ https://ja.wikipedia.org/wiki/Brainfuck > + + . > - . < . -``` +.... 実行結果はこちら: https://ideone.com/22VWmb @@ -151,48 +146,48 @@ https://ja.wikipedia.org/wiki/Brainfuck * `$🤡`: `[` * `$🎪`: `]` -`,` (入力) に対応する関数はない (このプログラムでは使わないので用意していない)。 +`,` (入力) に対応する関数はない +(このプログラムでは使わないので用意していない)。 なお、`$🐘` はいわゆる main 関数であり、プログラムの実行部分である。 +==== 絵文字の選択 -### 絵文字の選択 - -おおよそ意味に合致するよう選んでいるが、`$🤡` と `$🎪` は弊社デジタルサーカスにちなんでいる。 -また、`$🐘` は PHP のマスコットの象に由来する。 - +おおよそ意味に合致するよう選んでいるが、`$🤡` と `$🎪` +は弊社デジタルサーカスにちなんでいる。 また、`$🐘` は PHP +のマスコットの象に由来する。 -### strict_types +==== strict_types -`declare` 文の `strict_types` に指定できるのは、`0` か `1` の数値リテラルだが、 -`0x0` や `0b1` のような値も受け付ける。 -今回は、PHP 8.1 から追加された、`0O` または `0o` から始まる八進数リテラルを使った。 +`declare` 文の `strict_types` に指定できるのは、`0` か `1` +の数値リテラルだが、 `0x0` や `0b1` のような値も受け付ける。 今回は、PHP +8.1 から追加された、`0O` または `0o` から始まる八進数リテラルを使った。 - -### URL +==== URL ソースコードのライセンスを示したこの部分だが、 -```php +[source,php] +---- https://creativecommons.org/publicdomain/zero/1.0/ -``` - -完全に合法な PHP のコードである。 -`https:` 部分はラベル、`//` 以降は行コメントになっている。 +---- +完全に合法な PHP のコードである。 `https:` 部分はラベル、`//` +以降は行コメントになっている。 -### リテラルなしで数値を生成する +==== リテラルなしで数値を生成する ソースコード中に、ほとんど数値リテラルが書かれていないことにお気づきだろうか。 PHP では、型変換を利用することで任意の整数を作り出すことができる。 -```php +[source,php] +---- assert(0 === +!![]); assert(1 === +![]); assert(2 === ![]+![]); assert(3 === ![]+![]+![]); assert(10 === +(![].+!![])); -``` +---- `[]` に `!` を適用すると `true` が返ってくる。それに `+` を適用すると、`bool` から `int` ヘの型変換が走り、`1` が生成される。`10` @@ -201,32 +196,36 @@ assert(10 === +(![].+!![])); への型変換が走り、`10` が生まれる (コード量に頓着しないなら、`1` を 10 個足し合わせてももちろん 10 が作れる)。 -また、`error_reporting` に指定しているのは `-1` である。 -これは、`!` によって文字列を `false` にし、`+` によって `false` を `0` にし、さらにビット反転して `-1` にしている。 - +また、`error_reporting` に指定しているのは `-1` である。 これは、`!` +によって文字列を `false` にし、`+` によって `false` を `0` +にし、さらにビット反転して `-1` にしている。 -### `if` 文なしで条件分岐 +==== `if` 文なしで条件分岐 -三項演算子ないし `match` 式を使うことで、`if` を一切書かずに条件分岐ができる。 -また、`&&` / `||` も使えることがある。 -遅延評価が不要なケースでは、`[$t, $f][$cond]` のような形で分岐することもできる。 +三項演算子ないし `match` 式を使うことで、`if` +を一切書かずに条件分岐ができる。 また、`&&` / `||` も使えることがある。 +遅延評価が不要なケースでは、`[$t, $f][$cond]` +のような形で分岐することもできる。 -### `while`、`for` 文なしでループ +==== `while`、`for` 文なしでループ -不動点コンビネータを使って無名再帰する (詳しい説明は省略する。これらの単語で検索してほしい)。 -ここでは、一般に Z コンビネータとして知られるものを使った (`$z`)。 +不動点コンビネータを使って無名再帰する +(詳しい説明は省略する。これらの単語で検索してほしい)。 ここでは、一般に +Z コンビネータとして知られるものを使った (`$z`)。 -実際のところ、`$🤡` や `$🎪`、`$🐘` は、一度 Scheme (Lisp の一種) で書いてから PHP に翻訳する形で記述した。 +実際のところ、`$🤡` や `$🎪`、`$🐘` は、一度 Scheme (Lisp の一種) +で書いてから PHP に翻訳する形で記述した。 -なお、PHP は末尾再帰の最適化をおこなわない (少なくとも今のところは) ので、 -あまりに長い brainf\*ck プログラムを書くとスタックオーバーフローする。 +なお、PHP は末尾再帰の最適化をおこなわない (少なくとも今のところは) +ので、 あまりに長い brainf*ck +プログラムを書くとスタックオーバーフローする。 - -# 第2問 riddle.php +== 第2問 riddle.php ソースコードはこちら。実行には PHP 8.0 以上が必要なので注意。 -```php +[source,php] +---- <?php /********************************************************* @@ -261,77 +260,86 @@ foreach ($token as $x) { $x = implode("\n", str_split($x, length: 5)); echo "{$x}\n\n"; } -``` +---- さて、この問題はさきほどのように単純に実行しただけでは、謎のブロックが表示されるだけでトークンは得られない。 -トークンを得るためには、ソースコードを読み、定数 `N` を特定する必要がある。 +トークンを得るためには、ソースコードを読み、定数 `N` +を特定する必要がある。 ここでは、私の想定解を解説する。 - -## 読解 +=== 読解 まずはソースコードを読んでいく。 -```php +[source,php] +---- $token = [ // 略 ]; -``` +---- 数値からなる `$token` があり、各要素をループしている。 -```php +[source,php] +---- $x = $x ^ N; -``` +---- まずは排他的論理和 (xor) を取り、 -```php +[source,php] +---- $x = sprintf('%025b', $x); -``` +---- 二進数に変換して、 -```php +[source,php] +---- $x = str_replace(search: ['0', '1'], replace: [' ', '#'], subject: $x); -``` +---- 0 を空白に、1 を `#` にし、 -```php +[source,php] +---- $x = implode("\n", str_split($x, length: 5)); -``` +---- 5文字ごとに区切ったあと、改行で結合している。 - -## ヒント +=== ヒント 次に、ソースコードに書いてあるヒントを読んでいく。 -* `N` それ自体は、42 や 8128 といったような特別な意味を持たず、ランダムに決められている +* `N` それ自体は、42 や 8128 +といったような特別な意味を持たず、ランダムに決められている * `$token` の各要素は、1文字を表す * 1文字は 5x5 のセルからなる * 出力されるのは、完全な PHPer トークンである ここで、PHPer トークンは必ず `#` 記号から始まることを思いだすと、 -`$token` の最初の数字 `0x14B499C` は、変換の結果 `#` になるのではないかと予想される (なお、このことは、リポジトリの README ファイルに追加ヒントとして書かれている)。 - +`$token` の最初の数字 `0x14B499C` は、変換の結果 `#` +になるのではないかと予想される (なお、このことは、リポジトリの README +ファイルに追加ヒントとして書かれている)。 -## 解く +=== 解く -ここまでわかれば、あと一歩で解ける。すなわち、`0x14B499C` が `#` に変換されるような `N` を見つければよい。 +ここまでわかれば、あと一歩で解ける。すなわち、`0x14B499C` が `#` +に変換されるような `N` を見つければよい。 `N` は高々 -```php +[source,php] +---- assert(0 <= N && N <= 0b11111_11111_11111_11111_11111); -``` +---- なのでブルートフォースしてもよいが、ここではブルートフォースしない方法を紹介する。 -```php +[source,php] +---- <?php $x = 0x14B499C; @@ -348,11 +356,12 @@ assert($x === " # # \n" . "#####\n" . " # # "); -``` +---- この一連の変換に対する逆変換を考えると、次のようになる。 -```php +[source,php] +---- <?php $x = @@ -369,17 +378,16 @@ $x = bindec($x); $n = $x ^ 0x14B499C; echo "N = $n\n"; -``` +---- これを実行すると、`N` が得られる。 - - -# 第3問 toquine.php +== 第3問 toquine.php ソースコードはこちら。 -```php +[source,php] +---- <?php // License: https://creativecommons.org/publicdomain/zero/1.0/ @@ -409,49 +417,49 @@ $t = null.false; for ($i = 0; $i <= intdiv(__LINE__-035,6); ++$i) if (!isset($xs $t .= implode("\n", str_split(str_replace(['0','1'], [' ','##'], sprintf(chr(37) . '025b', $xs[$i])), 012)) . "\n\n"; $ws = array_map(fn($w) => implode(', ', $w), array_chunk(array_map(fn($x) => sprintf('0x' . chr(37) . '07X', $x), $xs), 10)); printf($s, $t, str_rot13("<<<'D'\n{$s}\nD"), implode(",\n", $ws)); -``` +---- コメントにもあるとおり、次のようにして実行すれば答えがでてくる。 -```shell-session +[source,shell-session] +---- $ php toquine.php | php | php | php | ... -``` +---- 実際にはもう少しパイプで繋げなければならない。 +=== 解説 -## 解説 - -### プログラム全体 +==== プログラム全体 コメントにもあるとおり、これは quine (風) のプログラムになっている。 -Quine とは、自分のソースコードをそっくりそのまま出力するようなプログラムのことである。 +Quine +とは、自分のソースコードをそっくりそのまま出力するようなプログラムのことである。 このプログラムは、実行すると自身とほとんど同じプログラムを出力する。 異なるのはトークンになっている部分のみである。 +==== トークン -### トークン +`$xs` がトークンに対応している。変換のロジックは `riddle.php` +とほぼ同じなので省略する。 -`$xs` がトークンに対応している。変換のロジックは `riddle.php` とほぼ同じなので省略する。 +==== 状態保持 +トークンの何文字目まで出力したかを、ソースコードを変えずに (quine +なので) 覚えておく必要がある。 +このプログラムでは、トークンが出力されるとソースコードがだんだんと長くなっていくのを利用して、`__LINE__` +から情報を取得している。 -### 状態保持 - -トークンの何文字目まで出力したかを、ソースコードを変えずに (quine なので) 覚えておく必要がある。 -このプログラムでは、トークンが出力されるとソースコードがだんだんと長くなっていくのを利用して、`__LINE__` から情報を取得している。 - - -### ROT 13 +==== ROT 13 Quine は、素朴に書くとプログラムの一部が 2回記述されてしまう。 -これがあまり美しくないので、`toquine.php` では、ROT 13 変換を使って難読化した。 +これがあまり美しくないので、`toquine.php` では、ROT 13 +変換を使って難読化した。 それにしてもなぜこんなものが標準ライブラリに……。 - - -# おわりに +== おわりに 解いていただいたみなさん、また、難易度調整につきあっていただいた社内のみなさん、ありがとうございました。 |
