From 98682c7a8792e7e79e487fea5024d25cee5aa310 Mon Sep 17 00:00:00 2001
From: nsfisis contained unnecessary whitespaces inside it
---
.../index.html | 12 +--
.../python-unbound-local-error/index.html | 18 ++--
.../ruby-detect-running-implementation/index.html | 12 +--
.../ruby-then-keyword-and-case-in/index.html | 68 +++++--------
.../rust-where-are-primitive-types-from/index.html | 36 +++----
.../index.html | 30 ++----
.../vim-swap-order-of-selected-lines/index.html | 24 ++---
.../2022-04-09/phperkaigi-2022-tokens/index.html | 76 +++++----------
.../index.html | 4 +-
.../php-conference-okinawa-code-golf/index.html | 10 +-
.../index.html | 102 +++++++-------------
.../phperkaigi-2023-unused-token-quiz-1/index.html | 52 ++++------
.../setup-server-for-this-site/index.html | 106 +++++++--------------
.../phperkaigi-2023-unused-token-quiz-2/index.html | 36 +++----
.../phperkaigi-2023-unused-token-quiz-3/index.html | 40 +++-----
15 files changed, 201 insertions(+), 425 deletions(-)
(limited to 'public')
diff --git a/public/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/index.html b/public/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/index.html
index ae44da9..ed91b45 100644
--- a/public/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/index.html
+++ b/public/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/index.html
@@ -56,8 +56,7 @@
タイトル落ち。まずはこのコードを見て欲しい。
- #include <iostream>
+ #include <iostream>
[[alignas]] [[alignof]] [[and]] [[and_eq]] [[asm]] [[auto]] [[bitand]]
[[bitor]] [[bool]] [[break]] [[case]] [[catch]] [[char]] [[char16_t]]
@@ -75,8 +74,7 @@
// [[using]]
int main() {
std::cout << "Hello, World!" << std::endl;
-}
-
+}
-@@ -126,10 +124,8 @@ std::cout << "Hello, World!" << std::endl; 上のコードでは
-[[using]]をコメントアウトしているが、これはusingキーワードのみ属性構文の中で意味を持つからであり、このコメントアウトを外すとコンパイルに失敗する。-+// using の例 -[[using foo: attr1, attr2]] int x; // [[foo::attr1, foo::attr2]] の糖衣構文-// using の例 +[[using foo: attr1, attr2]] int x; // [[foo::attr1, foo::attr2]] の糖衣構文C++17 の仕様も見てみる (正確には標準化前のドラフト)。 diff --git a/public/posts/2021-10-02/python-unbound-local-error/index.html b/public/posts/2021-10-02/python-unbound-local-error/index.html index 27c2d05..0516d6f 100644 --- a/public/posts/2021-10-02/python-unbound-local-error/index.html +++ b/public/posts/2021-10-02/python-unbound-local-error/index.html @@ -60,15 +60,13 @@ Python でクロージャを作ろうと、次のようなコードを書いた。
--def f(): ++f()def f(): x = 0 def g(): x += 1 g() -f()-関数
-gから 関数fのスコープ内で定義された変数xを参照し、それに 1 を足そうとしている。 これを実行するとx += 1の箇所でエラーが発生する。 @@ -84,8 +82,7 @@ f() local変数xが代入前に参照された、とある。これは、fのxを参照するのではなく、新しく別の変数をg内に作ってしまっているため。 前述のコードを宣言と代入を便宜上分けて書き直すと次のようになる。varを変数宣言のための構文として擬似的に利用している。-# 注: var は正しい Python の文法ではない。上記参照のこと ++g()# 注: var は正しい Python の文法ではない。上記参照のこと def f(): var x # f の local変数 'x' を宣言 x = 0 # x に 0 を代入 @@ -94,21 +91,18 @@ var x # g の local変数 'x' を宣言 # たまたま f にも同じ名前の変数があるが、それとは別の変数 x += 1 # x に 1 を加算 (x = x + 1 の糖衣構文) # 加算する前の値を参照しようとするが、まだ代入されていないためエラー -g()-当初の意図を表現するには、次のように書けばよい。
--def f(): ++g()def f(): x = 0 def g(): nonlocal x ## (*) x += 1 -g()--
(*)のように、nonlocalを追加する。これにより一つ外側のスコープ (gの一つ外側 =f) で定義されているxを探しに行くようになる。 diff --git a/public/posts/2021-10-02/ruby-detect-running-implementation/index.html b/public/posts/2021-10-02/ruby-detect-running-implementation/index.html index ed7ccd5..1163674 100644 --- a/public/posts/2021-10-02/ruby-detect-running-implementation/index.html +++ b/public/posts/2021-10-02/ruby-detect-running-implementation/index.html @@ -65,14 +65,12 @@ 上記ページの例から引用する:-$ ruby-1.9.1 -ve 'p RUBY_ENGINE' ++"jruby"$ ruby-1.9.1 -ve 'p RUBY_ENGINE' ruby 1.9.1p0 (2009-03-04 revision 22762) [x86_64-linux] "ruby" $ jruby -ve 'p RUBY_ENGINE' jruby 1.2.0 (ruby 1.8.6 patchlevel 287) (2009-03-16 rev 9419) [i386-java] -"jruby"-それぞれの処理系がどのような値を返すかだが、stack overflow に良い質問と回答があった。 @@ -192,12 +190,10 @@ jruby 1.2.0 (ruby 1.8.6 patchlevel 287) (2009-03-16 rev 9419) [i386-java] mruby 該当部分のソースより引用:
--diff --git a/public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html b/public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html index 9b1df47..ed8d9e8 100644 --- a/public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html +++ b/public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html @@ -65,20 +65,17 @@ 使われることは稀だが、Ruby では/* ++#define MRUBY_RUBY_ENGINE "mruby"/* * Ruby engine. */ -#define MRUBY_RUBY_ENGINE "mruby"-thenがキーワードになっている。次のように使う: --if cond then ++ endif cond then puts "Y" else puts "N" - end-このキーワードが現れうる場所はいくつかあり、
-if、unless、rescue、case構文がそれに当たる。 上記のように、何か条件を書いた後thenを置き、式がそこで終了していることを示すマーカーとして機能する。-# Example: ++end# Example: if x then a @@ -97,8 +94,7 @@ end case x when p then a -end-@@ -107,21 +103,17 @@ end 普通 Ruby のコードで thenを書くことはない。なぜか。次のコードを実行してみるとわかる。 --+if true puts 'Hello, World!' end-if true puts 'Hello, World!' end次のような構文エラーが出力される。
--20:1: syntax error, unexpected local variable or method, expecting `then' or ';' or '\n' ++ ...f true puts 'Hello, World!' end20:1: syntax error, unexpected local variable or method, expecting `then' or ';' or '\n' if true puts 'Hello, World!' end ^~~~ 20:1: syntax error, unexpected `end', expecting end-of-input - ...f true puts 'Hello, World!' end-二つ目のメッセージは無視して一つ目を読むと、
-thenか;か改行が来るはずのところ変数だかメソッドだかが現れたことによりエラーとなっているようだ。 @@ -131,10 +123,8 @@ end ポイントは改行がthen(や;) の代わりとなることである。trueの後に改行を入れてみる。-+if true -puts 'Hello, World!' end-if true +puts 'Hello, World!' end無事 Hello, World! と出力されるようになった。 @@ -147,27 +137,21 @@ puts 'Hello, World!' end なぜ
-thenや;や改行 (以下 「then等」) が必要なのだろうか。次の例を見てほしい:-+if a b end-if a b end-
thenも;も改行もないのでエラーになるが、これは条件式がどこまで続いているのかわからないためだ。 この例は二通りに解釈できる。--# a という変数かメソッドの評価結果が truthy なら b という変数かメソッドを評価 ++end# a という変数かメソッドの評価結果が truthy なら b という変数かメソッドを評価 if a then b -end--# a というメソッドに b という変数かメソッドの評価結果を渡して呼び出し、 ++end# a というメソッドに b という変数かメソッドの評価結果を渡して呼び出し、 # その結果が truthy なら何もしない if a(b) then -end--
then等はこの曖昧性を排除するためにあり、条件式はifからthen等までの間にある、ということを明確にする。 C系のif後に来る(/)や、Python の:、Rust/Go/Swift などの{も同じ役割を持つ。 @@ -188,8 +172,7 @@ end https://github.com/ruby/ruby/blob/221ca0f8281d39f0dfdfe13b2448875384bbf735/parse.y#L3961-L3986-+ ;p_case_body : keyword_in +-p_case_body : keyword_in { SET_LEX_STATE(EXPR_BEG|EXPR_LABEL); p->command_start = FALSE; @@ -214,17 +197,14 @@ end/*% %*/ /*% ripper: in!($4, $7, escape_Qundef($8)) %*/ } - ;簡略版:
--+p_case_body : keyword_in p_top_expr then compstmt p_cases -;-p_case_body : keyword_in p_top_expr then compstmt p_cases +;ここで、
-keyword_inは文字通りin、p_top_exprはいわゆるパターン、thenはthenキーワードのことではなく、この記事でthen等と呼んでいるもの、つまりthenキーワード、;、改行のいずれかである。 @@ -234,8 +214,7 @@ end これにより、case-whenによる従来の構文と同じように、then等をパターンの後ろに挿入すればよいことがわかった。つまり次の3通りのいずれかになる:-case x ++endcase x in 1 then a in 2 then b in 3 then c @@ -254,20 +233,17 @@ case x in 1; a in 2; b in 3; c -end-ところで、
-p_top_exprにはifによる guard clause が書けるので、その場合はif-thenと似たような見た目になる。-case x ++endcase x in 0 then a in n if n < 0 then b in n then c -end-diff --git a/public/posts/2021-10-02/rust-where-are-primitive-types-from/index.html b/public/posts/2021-10-02/rust-where-are-primitive-types-from/index.html index 8bcb923..5b1f86a 100644 --- a/public/posts/2021-10-02/rust-where-are-primitive-types-from/index.html +++ b/public/posts/2021-10-02/rust-where-are-primitive-types-from/index.html @@ -55,8 +55,7 @@ Rust において、プリミティブ型の名前は予約語でない。したがって、次のコードは合法である。 - -#![allow(non_camel_case_types)] ++struct str;#![allow(non_camel_case_types)] #![allow(dead_code)] struct bool; @@ -75,8 +74,7 @@ struct u128; struct usize; struct f32; struct f64; -struct str;-では、普段単に
-boolと書いたとき、このboolは一体どこから来ているのか。rustc のソースを追ってみた。 @@ -111,34 +109,29 @@ struct str;rustcはセルフホストされている (=rustc自身が Rust で書かれている) ので、boolやcharなどで適当に検索をかけてもノイズが多すぎて話にならない。 しかし、お誂え向きなことにi128/u128というコンパイラ自身が使うことがなさそうな型が存在するのでこれを使ってgit grepしてみる。-$ git grep "\bi128\b" | wc # i128 ++3563 23577 294659$ git grep "\bi128\b" | wc # i128 165 1069 15790 $ git grep "\bu128\b" | wc # u128 293 2127 26667 $ git grep "\bbool\b" | wc # cf. bool の結果 -3563 23577 294659-165 程度であれば探すことができそうだ。今回は、クレート名を見ておおよその当たりをつけた。
--$ git grep "\bi128\b" ++...$ git grep "\bi128\b" ... rustc_resolve/src/lib.rs: table.insert(sym::i128, Int(IntTy::I128)); -...--
rustc_resolveというのはいかにも名前解決を担いそうなクレート名である。該当箇所を見てみる。-/// Interns the names of the primitive types. ++}/// Interns the names of the primitive types. /// /// All other types are defined somewhere and possibly imported, but the primitive ones need /// special handling, since they have no place of origin. @@ -169,8 +162,7 @@ table.insert(sym::u64, Uint(UintTy::U64)); table.insert(sym::u128, Uint(UintTy::U128)); Self { primitive_types: table } } -}-これは初めに列挙したプリミティブ型の一覧と一致している。doc comment にも、 @@ -186,8 +178,7 @@ Self { primitive_types: table } とある。次はこの struct の使用箇所を追う。追うと言っても使われている箇所は次の一箇所しかない。なお説明に不要な箇所は大きく削っている。
--/// This resolves the identifier `ident` in the namespace `ns` in the current lexical scope. ++}/// This resolves the identifier `ident` in the namespace `ns` in the current lexical scope. /// (略) fn resolve_ident_in_lexical_scope( &mut self, @@ -207,8 +198,7 @@ return Some(LexicalScopeBinding::Item(binding)); } None -}-関数名や doc comment が示している通り、この関数は識別子 (identifier, ident) を現在のレキシカルスコープ内で解決 (resolve) する。
-if ns == TypeNSのブロック内では、primitive_type_table(上記のPrimitiveTypeTable::new()で作られた変数) に含まれている識別子 (bool、i32など) かどうか判定し、そうであればそれに紐づけられたプリミティブ型を返している。 @@ -226,15 +216,13 @@ None 動作がわかったところで、例として次のコードを考える。-#![allow(non_camel_case_types)] ++}#![allow(non_camel_case_types)] struct bool; fn main() { let _: bool = bool; -}-ここで
-main()のboolはstruct boolとして解決される。なぜなら、プリミティブ型の判定をする前にboolという名前の別の型が見つかるからだ。 diff --git a/public/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html b/public/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html index db28b1c..8e34b4d 100644 --- a/public/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html +++ b/public/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html @@ -112,30 +112,24 @@ https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L85-L86-+{"BufAdd", EVENT_BUFADD}, -{"BufCreate", EVENT_BUFADD},-{"BufAdd", EVENT_BUFADD}, +{"BufCreate", EVENT_BUFADD},https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L95-L97
--{"BufRead", EVENT_BUFREADPOST}, ++{"BufReadPost", EVENT_BUFREADPOST},{"BufRead", EVENT_BUFREADPOST}, {"BufReadCmd", EVENT_BUFREADCMD}, -{"BufReadPost", EVENT_BUFREADPOST},-https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L103-L105
--{"BufWrite", EVENT_BUFWRITEPRE}, ++{"BufWritePre", EVENT_BUFWRITEPRE},{"BufWrite", EVENT_BUFWRITEPRE}, {"BufWritePost", EVENT_BUFWRITEPOST}, -{"BufWritePre", EVENT_BUFWRITEPRE},-@@ -148,24 +142,20 @@ https://github.com/neovim/neovim/blob/71d4f5851f068eeb432af34850dddda8cc1c71e3/src/nvim/auevents.lua#L119-L124 - -aliases = { ++},aliases = { BufCreate = 'BufAdd', BufRead = 'BufReadPost', BufWrite = 'BufWritePre', FileEncoding = 'EncodingChanged', -},-ところで、上では取り上げなかった
-FileEncodingだが、これは:help FileEncodingにしっかりと書いてある。-*FileEncoding* ++to |EncodingChanged|.*FileEncoding* FileEncoding Obsolete. It still works and is equivalent -to |EncodingChanged|.-diff --git a/public/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html b/public/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html index 8a3b304..f4e2b71 100644 --- a/public/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html +++ b/public/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html @@ -115,11 +115,9 @@ なお、 :g/^/m0は全ての行を入れ替えるが、:N,Mg/^/mN-1とすることで N行目から M行目を処理範囲とするよう拡張できる。手でこれを入力するわけにはいかないので、次のようなコマンドを用意する。 --command! -bar -range=% ++\ <line1>,<line2>g/^/m<line1>-1command! -bar -range=% \ Reverse -\ <line1>,<line2>g/^/m<line1>-1-これは望みの動作をするが、実際に実行してみると全行がハイライトされてしまう。次節で詳細を述べる。 @@ -152,15 +150,13 @@ 前述した
-:Reverseコマンドの定義を少し変えて、次のようにする:-function! s:reverse_lines(from, to) abort ++ \ call <SID>reverse_lines(<line1>, <line2>)function! s:reverse_lines(from, to) abort execute printf("%d,%dg/^/m%d", a:from, a:to, a:from - 1) endfunction command! -bar -range=% \ Reverse - \ call <SID>reverse_lines(<line1>, <line2>)-実行しているコマンドが変わったわけではないが、関数呼び出しを経由するようにした。これだけで前述の問題が解決する。 @@ -211,11 +207,9 @@
- command! -bar -range=%
+ command! -bar -range=%
\ Reverse
- \ keeppatterns <line1>,<line2>g/^/m<line1>-1
-
+ \ keeppatterns <line1>,<line2>g/^/m<line1>-1
まさにこのための Exコマンド、:keeppatternsが存在する。:keeppatterns {command}のように使い、読んで字の如く、後ろに続く Exコマンドを「現在の検索パターンを保ったまま」実行する。はるかに分かりやすく意図を表現できる。
@@ -230,13 +224,11 @@
コピペ用再掲
-
-
" License: Public Domain
+
+ \ keeppatterns <line1>,<line2>g/^/m<line1>-1" License: Public Domain
command! -bar -range=%
\ Reverse
- \ keeppatterns <line1>,<line2>g/^/m<line1>-1
-
- <?php
+ <?php
declare(strict_types=0O1);
@@ -135,8 +134,7 @@
$👉, $👍, $👍, $📝,
$👉, $👎, $📝,
$👈, $📝,
- ]);
-
+ ]);
この問題は、単に適切なバージョンの PHP で動かせばトークンが得られる。 @@ -165,8 +163,7 @@ なお、brainf*ck プログラムを普通の書き方で書くと、次のようになる。
-
- + + + + + + + + + +
+ + + + + + + + + + +
[
> + + +
> + + + + +
@@ -187,8 +184,7 @@
< .
> + + .
> - .
-< .
-
+< .
実行結果はこちら:https://ideone.com/22VWmb @@ -271,9 +267,7 @@ ソースコードのライセンスを示したこの部分だが、
-
- https://creativecommons.org/publicdomain/zero/1.0/
-
+ https://creativecommons.org/publicdomain/zero/1.0/
完全に合法な PHP のコードである。https:部分はラベル、//以降は行コメントになっている。
@@ -286,13 +280,11 @@
ソースコード中に、ほとんど数値リテラルが書かれていないことにお気づきだろうか。 PHP では、型変換を利用することで任意の整数を作り出すことができる。
- assert(0 === +!![]);
+ assert(0 === +!![]);
assert(1 === +![]);
assert(2 === ![]+![]);
assert(3 === ![]+![]+![]);
-assert(10 === +(![].+!![]));
-
+assert(10 === +(![].+!![]));
[]に!を適用するとtrueが返ってくる。それに+を適用すると、boolからintヘの型変換が走り、1が生成される。10はさらにトリッキーだ。まず1と0を作り、.で文字列として結合する ('10')。これに+を適用すると、stringからintへの型変換が走り、10が生まれる (コード量に頓着しないなら、1を 10 個足し合わせてももちろん 10 が作れる)。
@@ -333,8 +325,7 @@ assert(10 === +(![].+!![]));
ソースコードはこちら。実行には PHP 8.0 以上が必要なので注意。
- <?php
+ <?php
/*********************************************************
* This program displays a PHPer token. *
@@ -367,8 +358,7 @@ assert(10 === +(![].+!![]));
$x = str_replace(search: ['0', '1'], replace: [' ', '#'], subject: $x);
$x = implode("\n", str_split($x, length: 5));
echo "{$x}\n\n";
- }
-
+ }
さて、この問題はさきほどのように単純に実行しただけでは、謎のブロックが表示されるだけでトークンは得られない。 トークンを得るためには、ソースコードを読み、定数Nを特定する必要がある。
@@ -384,43 +374,33 @@ assert(10 === +(![].+!![]));
まずはソースコードを読んでいく。
- $token = [
+ $token = [
// 略
- ];
-
+ ];
数値からなる$tokenがあり、各要素をループしている。
- $x = $x ^ N;
-
+ $x = $x ^ N;
まずは排他的論理和 (xor) を取り、
-
- $x = sprintf('%025b', $x);
-
+ $x = sprintf('%025b', $x);
二進数に変換して、
-
- $x = str_replace(search: ['0', '1'], replace: [' ', '#'], subject: $x);
-
+ $x = str_replace(search: ['0', '1'], replace: [' ', '#'], subject: $x);
0 を空白に、1 を#にし、
- $x = implode("\n", str_split($x, length: 5));
-
+ $x = implode("\n", str_split($x, length: 5));
5文字ごとに区切ったあと、改行で結合している。
@@ -474,16 +454,13 @@ assert(10 === +(![].+!![]));
Nは高々
- assert(0 <= N && N <= 0b11111_11111_11111_11111_11111);
-
+ assert(0 <= N && N <= 0b11111_11111_11111_11111_11111);
なのでブルートフォースしてもよいが、ここではブルートフォースしない方法を紹介する。
-
- <?php
+ <?php
$x = 0x14B499C;
@@ -498,15 +475,13 @@ assert(10 === +(![].+!![]));
"#####\n" .
" # # \n" .
"#####\n" .
- " # # ");
-
+ " # # ");
この一連の変換に対する逆変換を考えると、次のようになる。
-
- <?php
+ <?php
$x =
" # # \n" .
@@ -521,8 +496,7 @@ $x = bindec($x);
$n = $x ^ 0x14B499C;
-echo "N = $n\n";
-
+echo "N = $n\n";
これを実行すると、Nが得られる。
@@ -536,8 +510,7 @@ echo "N = $n\n";
ソースコードはこちら。
- <?php
+ <?php
// License: https://creativecommons.org/publicdomain/zero/1.0/
// This is a quine-like program to generate a PHPer token.
@@ -565,16 +538,13 @@ echo "N = $n\n";
$t = null.false; for ($i = 0; $i <= intdiv(__LINE__-035,6); ++$i) if (!isset($xs[$i])) break; else
$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));
-
+ printf($s, $t, str_rot13("<<<'D'\n{$s}\nD"), implode(",\n", $ws));
コメントにもあるとおり、次のようにして実行すれば答えがでてくる。
-
- $ php toquine.php | php | php | php | ...
-
+ $ php toquine.php | php | php | php | ...
実際にはもう少しパイプで繋げなければならない。 diff --git a/public/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html b/public/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html index bf174b8..be694e9 100644 --- a/public/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html +++ b/public/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html @@ -43,9 +43,7 @@ こんなものを作った。
-
- $ term-banner 'Hello, World!' 'こんにちは、' '世界!'
-
+ $ term-banner 'Hello, World!' 'こんにちは、' '世界!'
image::https://raw.githubusercontent.com/nsfisis/term-banner/main/screenshot.png[term-banner のスクリーンショット] diff --git a/public/posts/2022-08-27/php-conference-okinawa-code-golf/index.html b/public/posts/2022-08-27/php-conference-okinawa-code-golf/index.html index fbac1f0..ea60e83 100644 --- a/public/posts/2022-08-27/php-conference-okinawa-code-golf/index.html +++ b/public/posts/2022-08-27/php-conference-okinawa-code-golf/index.html @@ -111,9 +111,7 @@ 書いたものがこちら:
-
- [<?php $n=$argv[1];foreach([1e4,5e3,2e3,1e3,500,100,50,10,5,1]as$x)for(;$n>=$x;$n-=$x)$r[]=$x;echo implode(', ',$r??[]);?>]
-
+ [<?php $n=$argv[1];foreach([1e4,5e3,2e3,1e3,500,100,50,10,5,1]as$x)for(;$n>=$x;$n-=$x)$r[]=$x;echo implode(', ',$r??[]);?>]
しめて 123 バイトとなった (末尾改行を含めずにカウント)。 @@ -123,8 +121,7 @@ こちらは改行とスペースを追加したバージョン:
-
- [<?php
+ [<?php
$n = $argv[1];
foreach ([1e4, 5e3, 2e3, 1e3, 500, 100, 50, 10, 5, 1] as $x)
@@ -132,8 +129,7 @@
$r[] = $x;
echo implode(', ', $r ?? []);
- ?>]
-
+ ?>]
- #\
+ #\
i\
n\
c\
@@ -199,8 +198,7 @@
10
);
- /* あとは同じように普通のプログラムを変形するだけなので省略 */
-
+ /* あとは同じように普通のプログラムを変形するだけなので省略 */
バックスラッシュを使った行継続がトークンを区切らない、というのがポイントだ。
@@ -260,12 +258,10 @@
また、2文字だと文字列がまともに書けないのも辛い。''だけで 2文字使うので、「1文字の文字列リテラル」というものを書くことができない。PHP では文字列リテラル中に生の改行が書けるので
- $a
+ $a
='
a'
-;;
-
+;;
とすると$aは"\na"になるのだが、余計な改行が入ってしまう。
@@ -284,13 +280,11 @@ a'
まずは普通に書くとしよう。
- <?php
+ <?php
for ($i = 1; $i < 100; $i++) {
echo (($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n";
- }
-
+ }
素直に書いた fizzbuzz とは言い難いが、このくらいは普通だということにしておかないと、この先がやっていられないので許してほしい。
@@ -303,16 +297,14 @@ a'
forは、3文字もある長いキーワードである。こんなものは使えない。array_系の関数を使って、適当に置き換えるとしよう。
- <?php
+ <?php
$s = range(1, 100);
array_walk(
$s,
fn($i) =>
printf((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"),
- );
-
+ );
array_walkやrange、printfといったforよりも長いトークンが現れてしまったが、これは次節で直すことにする。なお、echoは文 (statement) であり式 (expression) ではないので、式であるprintfに置き換えた。
@@ -325,8 +317,7 @@ a'
range、array_walk、printfは長すぎるのでどうにかせねばならない。ここで、PHP の可変関数を使う。可変関数とは、関数名が文字列として入った変数を経由して、関数を呼び出す機能である。
- <?php
+ <?php
$r = 'range';
$w = 'array_walk';
@@ -337,8 +328,7 @@ a'
$s,
fn($i) =>
$p((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"),
- );
-
+ );
これで関数を呼び出している所は短くなった。では、$rや$wや$p、また'Fizz'や'Buzz'はどうやって 1行2文字に収めるのか。次のテクニックへ移ろう。
@@ -365,21 +355,18 @@ a'
というルールがない場合、「未定義の定数が評価された場合、その定数の名前が値になる」という PHP 7.x までの仕様が利用できる。例えば、Fizzという文字列が欲しければ、次のようにする。
- $f
+ $f
=F
.i
.z
.z
-;;
-
+;;
こうして簡単に文字列を作れる。なお、この仕様は 7.x 時点でも警告を受けるので、@演算子を使って抑制してやるとよい。
- $f
+ $f
=@
F.
@i
@@ -387,8 +374,7 @@ F.
@z
.#
@z
-;;
-
+;;
むしろ、このことがわかっていたからこそ PHP 8.x での動作を要件に課したところがある。
@@ -405,8 +391,7 @@ F.
ずばり、文字列同士のビット演算を使う。PHP では、文字列同士でビット演算 (&、|、^) をした場合、文字列の各バイトごとに指定したビット演算がなされ、それを結合したものが演算結果となる。
- $a = "12345";
+ $a = "12345";
$b = "world";
// $a ^ $b は次のコードと同じ
@@ -416,26 +401,22 @@ $result .= $a[$i] ^ $b[$i];
}
echo $result;
-// => F]AXQ
-
+// => F]AXQ
これを踏まえ、次のコードを見てみよう。
-
- $x = "x\nOm\n";
+ $x = "x\nOm\n";
$y = "\nk!\no";
$r = $x ^ $y;
-echo "$r\n";
-
+echo "$r\n";
実行すると、rangeが表示される。さて、PHP では文字列リテラル中に生の改行を直接書いてもよいのだった (「主な障害」の節を参照のこと)。書きかえてみよう。
- $x
+ $x
='x
Om
';
@@ -446,15 +427,13 @@ o'
;
$r = $x ^ $y;
-echo "$r\n";
-
+echo "$r\n";
さらに#を使って適当に調整すると、次のようになる。
- $x
+ $x
=#
'x
Om
@@ -471,8 +450,7 @@ $x
$y
;#
-echo "$r\n";
-
+echo "$r\n";
1行あたり2文字で、rangeという文字列を生成することに成功した。他の必要な文字列にも、同様の処理をほどこす。
@@ -490,8 +468,7 @@ echo "$r\n";
完成したものがこちら。
- <?php
+ <?php
$x
=#
@@ -639,8 +616,7 @@ echo "$r\n";
)#
.'
')
- );
-
+ );
shell_exec関数と等価である。さて、PHP ではバックスラッシュによる行継続が使えないと書いたが、シェルでは使える (当然だが、呼び出されるシェルに依存する。Bash なら大丈夫だろう。知らんけど)。
-
- <?php
+ <?php
printf(`
e\
@@ -672,8 +647,7 @@ echo "$r\n";
1\
2\
3\
- `);
-
+ `);
なお、ここでは簡単のため出力にprintfをそのまま使っているが、実際にはprintfという文字列を合成して可変関数で呼び出す。
@@ -701,8 +675,7 @@ echo "$r\n";
もうこれ以上は不可能だと思っていたのだが、この記事の執筆中に解決する方法を思いついたので載せておく。
- <?php
+ <?php
$c = 'chr';
@@ -730,33 +703,28 @@ ${
1\
2\
3\
-`);
-
+`);
先程と同じく、chrやprintfを生成する部分は長くなるので省いた。
- ${
+ ${
'_
-'}
-
+'}
は変数で、中にはスペースとエスケープが入っている (chr(32) . chr(92))。シェルに渡されている文字列は次のようになる。
- e\
+ e\
c\
h\
o\
\
1\
2\
-3\
-
+3\
これは、前掲したコマンドと同じだ。かくして、スペースを陽に書かずにシェルをおおよそ自由に扱えるようになった。Fizzbuzz のワンライナーくらいすぐ書けるだろうから、あとはなんとかなるだろう (試してないけど)。 @@ -770,11 +738,9 @@ o\ ちなみに、PHP 8.2 からは、この記法で Warning が出るようになるようだ。
-
- ${
+ ${
'_
-'}
-
+'}
最新版で警告が出るというのも美しくないので、私としては本編の解法を推す。 diff --git a/public/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/index.html b/public/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/index.html index a2bf44c..db0e7b6 100644 --- a/public/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/index.html +++ b/public/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/index.html @@ -68,8 +68,7 @@ 注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。
-
- <?php
+ <?php
$π = $argv[1] ?? null;
if ($π === null) {
@@ -89,8 +88,7 @@ if (md5($t) === '056e831a4146bf123e8ea16613303d2e') {
echo "Token: {$t}\n";
} else {
echo "Failed.\n";
-}
-
+}
$argv[1]を参照している。それを$πなる変数に代入しているので、円周率を渡してみる。
-
- $ php Q.php 3.14
- Failed.
-
+ $ php Q.php 3.14
+ Failed.
失敗してしまった。精度を上げてみる。
-
- $ php Q.php 3.1415
-Failed.
-
+ $ php Q.php 3.1415
+Failed.
だめだった。これを成功するまで繰り返す。 @@ -121,10 +115,8 @@ Failed. 最初にトークンが得られるのは、小数点以下 16 桁目まで入力したときで、こうなる。
-
- $ php Q.php 3.1415926535897932
-Token: #YO
-
+ $ php Q.php 3.1415926535897932
+Token: #YO
めでたくトークン「#YO」が手に入った。 @@ -137,24 +129,20 @@ Token: #YO 短いので頭から追っていく。
-
- $π = $argv[1] ?? null;
+ $π = $argv[1] ?? null;
if ($π === null) {
exit('No input.');
}
$π = trim($π);
if (!is_numeric($π)) {
exit('Invalid input.');
- }
-
+ }
入力のバリデーション部分。数値のみ受け付ける。
-
- $s = implode(array_map(chr(...), str_split($π, 2)));
-
+ $s = implode(array_map(chr(...), str_split($π, 2)));
$πを 2 文字ごとに区切り (str_split)、数値を ASCII コードと見做して文字に変換 (chr) して結合 (implode) している。
@@ -164,17 +152,13 @@ Token: #YO
例えば、$πが'656667'だったとすると、65、66、67に対応した'A'、'B'、'C'へと変換され、'ABC'になる。
- $π = '656667';
+ $π = '656667';
$s = implode(array_map(chr(...), str_split($π, 2)));
echo $s;
-// => ABC
-
+// => ABC
-
- preg_match('/(\x23.+?) /', $s, $m);
-$t = $m[1] ?? '';
-
+ preg_match('/(\x23.+?) /', $s, $m);
+$t = $m[1] ?? '';
正規表現でマッチングしている。\x23は#と同じであることに留意すると、この正規表現は「#から始まる 2 以上の長さ (含#) の文字列で、最初に現れるスペースまで」にマッチする。つまりこれは、PHPerKaigi におけるトークンである。
@@ -184,13 +168,11 @@ $t = $m[1] ?? '';
なお、#を直接書いていないのは、/#.+?) /と書くと、#.+?)という意図せぬトークンが登録されてしまうからである。
- if (md5($t) === '056e831a4146bf123e8ea16613303d2e') {
+ if (md5($t) === '056e831a4146bf123e8ea16613303d2e') {
echo "Token: {$t}\n";
} else {
echo "Failed.\n";
-}
-
+}
最後にトークンのハッシュ値を見て、想定解かどうかを確認する。 diff --git a/public/posts/2022-10-28/setup-server-for-this-site/index.html b/public/posts/2022-10-28/setup-server-for-this-site/index.html index 6e002c3..71c6e44 100644 --- a/public/posts/2022-10-28/setup-server-for-this-site/index.html +++ b/public/posts/2022-10-28/setup-server-for-this-site/index.html @@ -73,10 +73,8 @@ ローカルマシンで鍵を生成する。
-
- $ ssh-keygen -t ed25519 -b 521 -f ~/.ssh/teika.key
- $ ssh-keygen -t ed25519 -b 521 -f ~/.ssh/github2teika.key
-
+ $ ssh-keygen -t ed25519 -b 521 -f ~/.ssh/teika.key
+ $ ssh-keygen -t ed25519 -b 521 -f ~/.ssh/github2teika.key
teika.keyはローカルからサーバへの接続用、github2teika.keyは、GitHub Actions からサーバへのデプロイ用。
@@ -89,13 +87,11 @@
.ssh/configに設定しておく。
- Host teika
+ Host teika
HostName **********
User **********
Port **********
- IdentityFile ~/.ssh/teika.key
-
+ IdentityFile ~/.ssh/teika.key
sudoグループに追加してsudoできるようにし、suで切り替え。
-
- $ sudo adduser **********
+ $ sudo adduser **********
$ sudo adduser ********** sudo
$ su **********
- $ cd
-
+ $ cd
- $ sudo hostname teika
-
+ $ sudo hostname teika
- $ mkdir ~/.ssh
+ $ mkdir ~/.ssh
$ chmod 700 ~/.ssh
- $ vi ~/.ssh/authorized_keys
-
+ $ vi ~/.ssh/authorized_keys
authorized_keysには、ローカルで生成した~/.ssh/teika.key.pubと~/.ssh/github2teika.key.pubの内容をコピーする。
@@ -148,10 +138,8 @@
SSH の設定を変更し、少しでも安全にしておく。
- $ sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
- $ sudo vi /etc/ssh/sshd_config
-
+ $ sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
+ $ sudo vi /etc/ssh/sshd_config
- $ sudo systemctl restart sshd
-$ sudo systemctl status sshd
-
+ $ sudo systemctl restart sshd
+$ sudo systemctl status sshd
- $ ssh teika
-
+ $ ssh teika
- $ sudo ufw deny ssh
+ $ sudo ufw deny ssh
$ sudo ufw allow *******
$ sudo ufw enable
$ sudo ufw reload
- $ sudo ufw status
-
+ $ sudo ufw status
ここでもう一度 SSH の接続確認を挟む。 @@ -219,48 +201,38 @@ $ sudo systemctl status sshd GitHub に置いてある private リポジトリをサーバから clone したいので、SSH 鍵を生成して置いておく。
-
- $ ssh-keygen -t ed25519 -b 521 -f ~/.ssh/github.key
-$ cat ~/.ssh/github.key.pub
-
+ $ ssh-keygen -t ed25519 -b 521 -f ~/.ssh/github.key
+$ cat ~/.ssh/github.key.pub
GitHub の設定画面から、この公開鍵を追加する。
-
- $ vi ~/.ssh/config
-
+ $ vi ~/.ssh/config
設定はこう。
-
- Host github.com
+ Host github.com
HostName github.com
User git
-IdentityFile ~/.ssh/github.key
-
+IdentityFile ~/.ssh/github.key
最後に接続できるか確認しておく。
-
- ssh -T github.com
-
+ ssh -T github.com
- $ sudo apt update
+ $ sudo apt update
$ sudo apt upgrade
$ sudo apt update
$ sudo apt upgrade
- $ sudo apt autoremove
-
+ $ sudo apt autoremove
- $ sudo apt install docker docker-compose git make
-
+ $ sudo apt install docker docker-compose git make
- sudo adduser ********** docker
-
+ sudo adduser ********** docker
- $ sudo ufw allow 80/tcp
+ $ sudo ufw allow 80/tcp
$ sudo ufw allow 443/tcp
$ sudo ufw reload
- $ sudo ufw status
-
+ $ sudo ufw status
- $ cd
+ $ cd
$ git clone git@github.com:nsfisis/nsfisis.dev.git
$ cd nsfisis.dev
- $ git submodule update --init
-
+ $ git submodule update --init
- $ docker-compose up -d acme-challenge
- $ make setup
-
+ $ docker-compose up -d acme-challenge
+ $ make setup
- $ make serve
-
+ $ make serve
- <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
+ <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
@@ -82,8 +81,7 @@
<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
- <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
-
+ <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
"And Then There Were None" (そして誰もいなくなった) と名付けた作品。変則 quine (自分自身と同じソースコードを出力するプログラム) になっている。 @@ -96,8 +94,7 @@ 実行してみると、次のような出力が得られる。
-
- #
+ #
<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
@@ -106,15 +103,13 @@
<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
- <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
-
+ <?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
1 行目を除き、先ほどのコードとほぼ同じものが出てきた。もう一度実行してみる。
-
- #
+ #
W
<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
@@ -123,15 +118,13 @@ W
<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
-<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
-
+<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s='<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
今度は 2 行目が書き換えられた。すべての行が変化するまで繰り返すと次のようになる。
-
- #
+ #
W
E
L
@@ -140,8 +133,7 @@ V
E
P
H
-P
-
+P
トークン「#WELOVEPHP」が手に入った。 @@ -158,9 +150,7 @@ P Vim で開くと次のようになる (1 行目を抜粋)。
-
- <?php printf((isset($s)?fn($s)=>trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s='<200b><?php printf((isset($s)?fn($s)=>trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
-
+ <?php printf((isset($s)?fn($s)=>trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s='<200b><?php printf((isset($s)?fn($s)=>trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?>
<200b>と表示されているのは、Unicode の U+200b で、ゼロ幅スペースである。
@@ -185,17 +175,13 @@ P
続いて、トークンへの変換ロジックを解析する。注目すべきはこの部分だ。以下、ゼロ幅スペースは Vim での表示に合わせて<200b>と記載する。
- fn($s)=>chr(strlen($s)/3)
-
+ fn($s)=>chr(strlen($s)/3)
PHP のstrlen()は文字列のバイト数を返す。1 行目の$sは以下の内容となっており、
- $s='<200b><?php printf((isset($s)?fn($s)=>trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'
-
+ $s='<200b><?php printf((isset($s)?fn($s)=>trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'
このソースコードは UTF-8 で書かれているので、105 バイトになる。それを 3 で割ると 35 となり、これは#の ASCII コードと一致する。他の行も、同様にしてゼロ幅スペースを詰めることで文字列長を調整し、トークンをエンコードしている。
diff --git a/public/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html b/public/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html
index 3100b8e..81d673f 100644
--- a/public/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html
+++ b/public/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html
@@ -82,8 +82,7 @@
注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。
- <?php
+ <?php
try {
f(g() / __LINE__);
} catch (Throwable $e) {
@@ -194,8 +193,7 @@
function g() {
return __LINE__;
- }
-
+ }
"Catchline" と名付けた作品。実行するとトークン#base64_decode('SGVsbG8sIFdvcmxkIQ==')が得られる。
@@ -239,8 +237,7 @@
このうち 1つ目のケースは、finally節の中でエラーを投げると PHP 処理系が勝手に$previousを設定してくれる。
- <?php
+ <?php
try {
try {
@@ -253,8 +250,7 @@
// => Error 2
echo $e->getPrevious()->getMessage() . PHP_EOL;
// => Error 1
- }
-
+ }
この知識を元に、トークンの出力部を解析してみる。 @@ -267,8 +263,7 @@ 出力部をコメントや改行を追加して再掲する:
-
- <?php
+ <?php
try {
f(g() / __LINE__);
} catch (Throwable $e) {
@@ -276,8 +271,7 @@
printf('%c', $e->getLine() + 23);
}
echo "\n";
- }
-
+ }
出力をおこなうcatch節を見てみると、Throwable::getPrevious()を呼び出してエラーチェインを辿り、Throwable::getLine()でエラーが発生した行数を取得している。その行数に23なるマジックナンバーを足し、フォーマット指定子%cで出力している。
@@ -287,9 +281,7 @@
フォーマット指定子%cは、整数を ASCII コードと見做して印字する。トークン#base64_decode('SGVsbG8sIFdvcmxkIQ==')のbであれば、ASCII コード98なので、75 行目で発生したエラー、
- 1, 20 => 0 / 0,
-
+ 1, 20 => 0 / 0,
によって表現されている。エラーを起こす方法はいろいろと考えられるが、今回はゼロ除算を使った。
@@ -306,8 +298,7 @@
f()の定義を再掲する (エラーオブジェクトの行数を利用しているので、一部分だけ抜き出すと値が変わることに注意):
- function f(int $i) {
+ function f(int $i) {
if ($i < 0) f();
try {
match ($i) {
@@ -326,24 +317,19 @@
} finally {
f($i - 1);
}
- }
-
+ }
前述のように、finally節でエラーを投げると PHP 処理系が$previousを設定する。ここでは、エラーを繋げるためにf()を再帰呼び出ししている。最初にf()を呼び出している箇所を確認すると、
- <?php
+ <?php
try {
- f(g() / __LINE__); // 3 行目
-
+ f(g() / __LINE__); // 3 行目
-
- function g() {
+ function g() {
return __LINE__; // 111 行目
- }
-
+ }
f()には111 / 3で37が渡されることがわかる。そこから 1 ずつ減らして再帰呼び出ししていき、0 より小さくなったらf()を引数なしで呼び出す。引数の数が足りないと呼び出しに失敗するので、再帰はここで止まる。
--
cgit v1.2.3-70-g09d2