10/22/2019

[note] gccの内部仕様変更に伴う不具合に悩まされた話

今回はバグ、というかプログラムのミスの話です。それ自体は別段珍しくもないのですが、その原因がちょっと今まで遭遇したことのない類のものだったのと、それを引き起こしたミスが、かれこれ数十年プログラムを書いてきて、今更こんな・・・と驚いたという中々珍しい組み合わせだったので、戒め代わりに晒してみようかと思った次第です。

何かというと、画像関連の自作ツールで、十年以上前からちょくちょく修正や拡張を積み重ねて来て未だに現役のプログラムがあるんですが、そのLinux版について、速度の最適化をサボっていたところを改善しようと、久しぶりにリビルドしたところ、落ちるようになってしまったのです。

使用言語はCとC++の混在、主なライブラリはgtk3(glade含)とあとgnu関連色々、コンパイラは普通にgcc・・・ということで、特殊なものは使っていないのですが、この辺をそれなりに使っている人はよくご存知の通り、これらの環境は頻繁に仕様変更が入ります。gtkなんか数年で関数や構造ががんがん廃止されますから、その度に修正が必要になってとても面倒なのです。

で、まあ今回もその類の内部仕様の変更による不具合だろう、ということでデバッグを始めたのですが・・・何か妙なのです。どういう風に妙かというと、ステップ実行していると、とある関数で、その関数の実行が終わって最後まで到達すると、通常なら当然呼び出し先に戻る・・・筈なところ、そうならずに関数の途中に戻っていたのです。数十行巻き戻る感じですね。当然、色々と齟齬が出ますので、すぐにバイオレーションになって死亡、というわけです。

正直参りました。この手の挙動は、往々にしてオーバーランや初期化漏れ等のバグに起因する事が多く、その原因箇所が落ちている場所とは全く別の場所である事が殆どで、特定は困難極まるからです。とはいえ直さないわけにもいかず、仕方がないので、まずは絞り込み、ということで、その関数周辺の処理を大まかなブロックに分けて、ブロック別に有効/無効を色々と切り替えて実行を繰り返し、挙動に変化が見られるか観察しました。

結果は、ほとんど変化なし。巻き戻る場所が変わる位で、相変わらず落ちます。絞り込みも出来ない、というのは初めてで、非常に困惑させられたのですね。

埒が明かないので、最適化オプションを変えてみたらどうだろう、とMakefileをいじって試したところ、-O0、すなわち最適化なしだと落ちない事がわかりました。一歩前進です。ただ、完全に治るわけではなく、落ちないながらも無限ループは発生することもあります。こういう再現性が不安定なのは一番困るのですよね・・・ますます困惑は広がるばかり。

とはいえ、この事から、原因の一端が最適化にあるものと推測する事が出来ました。ので、オプションを-O1,-O2等に切り替えたりしつつ、ステップ実行を繰り返して、観察出来る変数の内容をトレースして絞り込みを試みました。が、ご存知の通り最適化が入ると変数(の大半)がデバッガからトレース出来なくなるんですよね。このため、なかなか上手くいきません。

ろくに進捗のないまま、試行錯誤を繰り返す事数時間、これは詰んだか・・・と思い始めた頃、ふと気づいたのです。何にかというと、問題の関数はint型の返り値を返すものなところ、その最後にreturn文がない事に、です。明らかにバグです。

私は基本、関数を書く時は、関数名とその型の次にまずreturn文を書くところから始めるのが常(voidの時は除く)なので、return文が無いというのは個人的には非常に珍しいミスです。あるいは何処かの修正時に誤って消してしまったのかもしれませんけれども、その辺りは不明です。

ともあれ、こんなミスが・・・と驚き、まさかreturn文の有無がそんな致命的な挙動に影響しないだろうし、今回の不具合とは関係ないだろうな、と思いつつも、せっかく気づいたんだし直しておくか、とreturn文を追加したのです。

すると、あにはからんや、件のループが発生しなくなっているではありませんか。どの最適化オプションを付けても問題なく、以前と同様に動作します。図らずも解決した事になります。

正直、嘘だろうと目を疑いました。しかし、色々テストをしてみても、完全に直っています。認めざるを得ませんでした。これが原因だったのです。

この修正で直った、ということは、逆に考えると、今回の不具合は、"現バージョンのgccでは、return文が無い場合、特に最適化をすると関数の終わりを関数内部のループの終わりと誤認する場合がある"、という事なのかと。

なんじゃそら、と思わずにはいられなかったわけです。

無論、返り値が返されない、という状況は正しいものではなく、というか紛うことなきバグで、その状況すなわちバグを作ったのは自分自身である以上、それに苦しめられるのも自業自得であることはわかっているのです。

ただ、常識的に考えれば、その不具合が影響するのは通常その返り値を参照する箇所であるのが普通だと思うし、今回のような不具合の事象からこの原因を連想ないし特定するのは極めて困難、というか不可能だと思うのですね。。。本件解決法は、本当に偶然ふと気づいただけですし、あの気付きがなければ、今でもありもしない、いかにもやらかしそうな類のバグを探して無駄に苦しんでいたのかと思うと、ぞっとします。

gccもgtkも、サイレント修正なんて日常茶飯事ですし、またこういう修正困難なバグに遭遇する可能性も否定できません。そうでなくとも容赦なく行われる改廃に、その際の種々のトラブルの予防を図る事も出来ず、その度に対応を迫られ続けるのだ、という悲しい事実を、改めて噛み締めさせられた一件でした。どうにかならないものなのでしょうか。少なくとも自分で作る部分のバグは0にするしかない?そうですね。。。とほほ。