前書き

今日も今日とて、バグった Common Lisp コードを走らせてしまい、 SBCL を落としてしまいました。 自省のために一筆書きます。

対象のバージョンは以下です。

落としてしまったコード

処理系を落としてしまったコードと同じ状況を再現するコードが、以下になります。

( defun string-to-char-code-array ( str ) ( declare ( optimize ( speed 3 ) ( safety 0 ) ( debug 0 )) ( type string str )) ( with-input-from-string ( in str ) ( loop for c of-type character = ( read-char in nil :eof ) for i of-type fixnum from 0 until ( eq c :eof ) collect ( char-code c ) into cc finally ( return ( make-array i :element-type ' fixnum :initial-contents ( the list cc ))))))

このコードのやっていることは、引数で受けとった文字列 ( string ) を、 char-code の配列に変換するというだけで、つまり以下と同様です。

( defun string-to-char-code-array-simple ( str ) ( map ' vector #'char-code str ))

実際のコードでは、 char-code を取ったあとに、細かな処理をするのですが、その実コードはちょっと出せないので、そこを省いた疑似コードでご容赦ください。

何が起こったのか

上記の string-to-char-code-array 関数をコンパイルすると、こんなメッセージが出てきます:

; file: /private/var/tmp/tmp.T3teOn ; in: DEFUN STRING-TO-CHAR-CODE-ARRAY ; (MAKE-ARRAY I :ELEMENT-TYPE 'FIXNUM :INITIAL-CONTENTS (THE LIST CC)) ; --> LOCALLY MAKE-ARRAY ; ==> ; I ; ; note: deleting unreachable code ; ; compilation unit finished ; printed 1 note

「 I は使われてないっぽいから消したからね」 って言われてます。 「えっ。 I で数を数えてるし、 make-array も呼んでるじゃない。なんだろう？」と思いながら、走らせてみると…

CL-USER> (string-to-char-code-array "abcd")

いつまで経っても値は返ってこず、それどころか、 Ctrl-C の割り込みさえも効かなくなってしまいました。 処理系の出力を見ると、以下のようなメッセージがありました。

fatal error encountered in SBCL pid 94418(tid 2953408512): Heap exhausted, game over.

「ヒープ使い切っちゃったんで終了です。」

どうしてこうなったのか

結論から言うと、 read-char の戻り値を受ける変数の型宣言が間違ってました。

今回の呼び出しでは、 read-char は character か :eof というシンボルを返すので、それを受ける変数への型宣言は (or character symbol) が正しいです。

以下が修正済コードです。

( defun string-to-char-code-array ( str ) ( declare ( optimize ( speed 3 ) ( safety 0 ) ( debug 0 )) ( type string str )) ( with-input-from-string ( in str ) ( loop for c of-type ( or character symbol ) = ( read-char in nil :eof ) for i of-type fixnum from 0 until ( eq c :eof ) collect ( char-code c ) into cc finally ( return ( make-array i :element-type ' fixnum :initial-contents ( the list cc ))))))

間違っていたコードでは、以下のような最適化がされてしまっていたのでしょう:

変数 C は character しか取らない （と、私が書いてしまった。） ということは、 C は symbol ではないので、 (eq c :eof) は絶対に成立しない。無限ループだ。 なので、 finally 以下のコードには到達しない。 じゃあ I っていう変数は使わなくても大丈夫だよね 「 I は使われてないっぽいから消したからね」 というメッセージ

まとめ

(safety 0) にしてたので悲惨な結果になりましたが、 (safety 3) で実行すると、以下のエラーを出してちゃんと止まります。

The value :EOF is not of type CHARACTER. [Condition of type TYPE-ERROR]