到達不能コード
到達不能コード(とうたつふのうコード、英: unreachable code)は、コンピュータ・プログラムの一部として存在するが、決して実行されないコードのことである。たとえば機械語プログラムであれば、プログラムカウンタ(インストラクションポインタ)が、そのアドレスに到達することは決してない(到達不能である)。
到達不能コードは以下のような理由から好ましくない。
- ソースコードにおいて、そのコードが実行されるものと作者が思い込んでいた場合、実際にはそれが実行されることがない、ということは、本来必要な処理が実行されないという何らかの不具合(バグ)があることを示唆している。
- そういったコードは、ほぼ確実に、作者以外には理解困難となり、メンテナンス不可能となる。
到達不能コードはデッドコード(英: dead code)の一種である(デッドコード削除も参照)。デッドコードには、「冗長コード」なども含まれる。冗長コードは例えば、同じアドレスに同じ内容を繰り返し書き込む、あるいは内容が変化する可能性が無いのに繰り返し読み出す、などといったコードで[注釈 1]、実行しても意味のある影響が無いコードである。他に、宣言されているが実際には使われない変数などの宣言を「冗長宣言」などと呼ぶこともある。
到達不能コードの検出
[編集]静的コード解析の一種としては、変数の値やその他の実行時の条件がどうであっても決して実行されないコードを、単純なものでは制御構造の分析によって、より高度には抽象解釈などにより[注釈 2]検出する。一部のプログラミング言語(Javaなど)では、ある種の[注釈 3]到達不能コードは言語の仕様上許容されない。デッドコードの削除について詳しくはデッドコード削除の記事を参照のこと。
しかし、静的に決定が可能なパターンの到達不能コードの検出は、そういったコードを意図的に書くことは無いといった意味では、最適化よりも、バグ(の可能性)を警告する、といった目的のためのほうが有用かもしれない。
また、実際のところ、静的解析よりも、実行時の動的な最適化(例えばコマンドライン引数に対応して、動的コード生成を行うプログラムなどがある)の際のほうが、if文の条件式などが恒真式になったりして、到達不能コードが現れやすいかもしれない。
コンパイラによる高度な最適化などの結果によっては、例えば共通式削除などで、ソースコード中には複数あらわれる表現であるのに、オブジェクトコード中で相当するのは一箇所だけ、といったような場合も、一種のデッドコードのようなものとして、デバッガなどからは見える場合もある。
到達不能コードが生まれる原因
[編集]以下では、到達不能コードが生じる原因をいくつか解説する。
ソフトウェア開発の際、プログラマは一時的にコードが実行されないようにすることがある(すなわち、明示的に実行されないように書き換える)。例えば、次のようなC言語のコードがある。
while (condition)
{
foo();
bar();
}
ここで、一時的に bar()
を実行しないようにしたい場合、例えば次のように書き換える。
while (condition)
{
foo();
continue;
bar();
}
この例では、bar()
が到達不能コードになる(continue文はループの次の反復に直ちに移行させる)。このような一時的な修正がリリース時まで残ってしまうことがある。
他の到達不能コードが生じる原因として、条件判断が冗長化している場合や、デバッグ用コードを削除せずに残してしまう場合がある。
関数やサブルーチンは、どこからも呼び出されない場合はデッドコードになるし、到達不能コードからのみ呼び出されている関数もデッドコードになる。
到達不能コードの存在は、プログラムの修正時の論理的誤りや、プログラムの前提や環境が大幅に変更されたことを示す場合もある。到達不能コードの存在を許容する言語もあれば、許容しない言語もあるが、前者であっても優れたコンパイラはデッドコードの存在を通知し、保守者がその意味を考えられるようにする[注釈 4]。なお、到達不能コードを許容する言語であっても、ほとんどのコンパイラは、決して実行されることのないコードは最適化の際に除去してしまうため、最終的にリリース用に生成される機械語には痕跡も残らない。
到達不能性の検証
[編集]任意のコードが到達不能コードかどうかを判断することは停止性問題を解くことと等価である。すなわち、あらゆる到達不能コードを正しく把握することは不可能である。
実際には分析手法は精巧化しており、到達不能コードを検出する量は大幅に改善されている。例えば以下のコードで、定数畳み込みや単純なフロー解析では、xyz
という文が到達不能であることを示すことができる。
int n = 2 + 1; // 右辺は 3 に定数畳み込みされる。つまりコンパイル時定数となる。
// 3 != 4 なので常に実行されない。
if (n == 4)
{
xyz;
}
しかし、以下のコードで xyz
という文が到達不能であることを検出するには(sqrt
関数が平方根の計算を行うかどうかチェックしなければいけないため)努力を要する。
double x = sqrt(2.0);
if (x > 2.0)
{
xyz;
}
到達不能性とプロファイリング
[編集]場合によっては、静的コード解析による通常の手法と同時に、プロファイラを使って複雑なケースを検出できる場合もある。プロファイラはコードの到達不能性を「証明」できるわけではないが、到達不能コードを見つけ出すヒューリスティクスとしては優れている。疑わしい部分が見つかったら、もっと強力な分析ツールを使ったり、人間が目と手で調査して、そのコードが本当に到達不能かどうかを判断する。
脆弱性と到達不能性
[編集]以下の例は、AppleによるTLS/SSLの実装上のバグであり、CVE番号として CVE-2014-1266 が与えられているほか、"goto fail bug" とも呼ばれている[3][4]。実際のコードは以下の通りである[5]。
static OSStatus
SSLVerifySignedServerKeyExchange(SSLContext *ctx, bool isRsa, SSLBuffer signedParams,
uint8_t *signature, UInt16 signatureLen)
{
OSStatus err;
...
if ((err = SSLHashSHA1.update(&hashCtx, &serverRandom)) != 0)
goto fail;
if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
goto fail;
goto fail;
if ((err = SSLHashSHA1.final(&hashCtx, &hashOut)) != 0)
goto fail;
...
fail:
SSLFreeBuffer(&signedHashes);
SSLFreeBuffer(&hashCtx);
return err;
}
goto fail;
という文が2行連続している所がある。そして、2行目のほうのgoto文により、本来到達すべき final
の呼び出しが到達不能コードとなっていて、final
によるチェックが「常に」スキップされ、err
は SHA1 update 操作の成功の後の値を保持することとなり、final
による署名検証が行われない状態となっていた[3]。
明らかにこれは「到達不能コードによってセキュリティ上の脆弱性がもたらされた」のではない(到達不能コードは、一般に何もしない。それが何か悪さをしたと言えるのは、例えばコードサイズの増加であるとか、言語仕様で未定義なソースコードの記述を利用して実行させる、などした場合であろう)。真に「セキュリティ上の脆弱性をもたらした」のは、「一見すると到達不能コードのように見えるためか、放置されたものと思われる、2行目のほうのgoto文(によって、到達されねばならないコードが到達不能になっていたこと)」である。教訓は「到達不能コードを放置するな。なぜなら、そういうコードの存在を見慣れるせいで、到達不能であってはいけないコードが到達不能になっていることを見逃す原因になるから」という点である。[独自研究?]
脚注
[編集]注釈
[編集]- ^ この例の場合では、メモリマップドI/Oの場合は意味が異なってくるが、一般にそういう場合は
volatile
を付けるなど、特別扱いが必要である。 - ^ たとえば「この部分を実行しているときには、この変数の値は絶対に負ではない」といったような情報を使う。
- ^ 典型的には、if文のthen節とelse節の両方にreturn文があるのに、そのif文の後にも何らかの別の文が続いている、というような、意味的な分析を必要とせずに構文上で明らかなものについて。
- ^ CおよびC++の言語仕様上は、到達不能コード自体は合法だが、Microsoft Visual C++は到達不能コードに対してC4702の警告を出す[1]。Clangはデフォルトでは到達不能コードに対して警告を出さないが、
-Wunreachable-code
オプションや-Wunreachable-code-aggressive
オプションを有効にすることで警告を出すようになる(これらは-Weverything
オプションに含まれる)[2]。
出典
[編集]- ^ Compiler Warning (level 4) C4702 | Microsoft Learn
- ^ Diagnostic flags in Clang — Clang git documentation
- ^ a b Adam Langley (2014年). “Apple's SSL/TLS bug”. 2015年3月15日閲覧。
- ^ Arie van Deursen (2014年). “Learning from Apple’s #gotofail Security Bug”. 2015年3月15日閲覧。
- ^ “sslKeyExchange.c - Source code for support for key exchange and server key exchange”. 2015年3月15日閲覧。