引用元:https://akrzemi1.wordpress.com/2017/01/02/not-detecting-bugs/
概要
GCC5.3にて、std::prevにboostのイテレータを渡すと制御が戻らない不具合に遭遇した。
これは、std.iteratorとBoost.Iteratorは別のiterator hierarchyを持つことに起因しており、std::prevが内部で利用しているstd::advanceがiterator categoryを認識せずtag dispatchingが正しく行われないことによるものだった。
しかし仕様上はイテレータ要件を満たさない値を渡すことはundefined behaviorであり、この挙動は合法であり厳密にはバグではない。(不親切だけど)
解説
boostのイテレータはC++標準のイテレータとは互換性を持たない異なるiterator hierarcyを持つため、std::prevを通してstd::advanceに渡されたzip_iteratorはbidirectional iteratorではなくinput iteratorとして解釈されtag dispathされる。input iteratorに対するstd::advanceの実装は、distanceとして非負が渡されることしか想定しておらず、判定の境界を飛び越してしまっていた。
しかし、明らかな不具合に対して、GCCでは特段の警告もエラーも出なかった。
C++標準を見ると、この状況に対して言及している部分として以下が存在する:
[res.on.functions]: In certain cases (replacement functions, handler functions, operations on types used to instantiate standard library template components), the C++ standard library depends on components supplied by a C++ program. If these components do not meet their requirements, the Standard places no requirements on the implementation.
In particular, the effects are undefined in the following cases:
[…]
for types used as template arguments when instantiating a template component, if the operations on the type do not implement the semantics of the applicable Requirements subclause.
(雑訳:テンプレート要素をインスタンス化するときにテンプレート引数として使われる型に対して、その型のoperationsが適切なRequirements subclauseのsemanticsを実装していない場合、その影響は定義されていない。)
すなわち、zip_iteratorがC++標準のイテレータ要件を満たしていないので、std::prevはUBとなりGCCは何をしてもよいことになる。なので、別段エラーを出さずともC++コンパイラとしては合法でありバグではないと言える。しかしGCCが行っていることはいささか非倫理的(somewhat unethical)である。
ちなみに、clangとmsvcでは同様の状況でエラーを吐く。
まとめ
コンパイラのバグ発見率を高めるため:
- 複数のコンパイラを使おう
- 複数の標準ライブラリ実装を使おう
- GCC 6なら
-D_GLIBCXX_CONCEPT_CHECKSを使おう