到達不能コード

出典: フリー百科事典『ウィキペディア(Wikipedia)』
Jump to navigation Jump to search

到達不能コード(とうたつふのうコード、unreachable code)は、コンピュータ・プログラムの一部として存在するが、決して実行されないコードのことである。たとえば機械語プログラムであれば、プログラムカウンタ(インストラクションポインタ)が、そのアドレスに到達することは、決してない(到達不能である)。

以下のような理由から好ましくない。

  • ソースコードにおいて、作者がそのコードが実行されると思っていた場合、それが実行されることがない、ということは、バグによってそうなっている、ということである。
  • そういったコードは、ほぼ確実に、作者以外には理解困難となり、メンテナンス不可能となる。

デッドコードとも言う、ないし、デッドコードの一種である(デッドコード削除も参照)。似たものに、「冗長コード」などがある。冗長コードは例えば、同じアドレスに同じ内容を繰り返し書き込む、あるいは内容が変化する可能性が無いのに繰り返し読み出す、などといったコードで[1]、実行しても意味のある影響が無いコードである。他に、宣言されても使われない変数などの宣言を「冗長宣言」などと呼ぶこともある。

到達不能コードの検出[編集]

静的コード解析の一種としては、変数の値やその他の実行時の条件がどうであっても決して実行されないコードを、単純なものでは制御構造の分析によって、より高度には抽象解釈などにより[2]検出する。一部の言語(Javaなど)では、ある種の[3]到達不能コードは言語の仕様上許容されない。デッドコードの削除について詳しくはデッドコード削除の記事を参照のこと。

しかし、静的に決定が可能なパターンの到達不能コードの検出は、そういったコードを意図的に書くことは無いといった意味では、最適化よりも、バグ(の可能性)を警告する、といった目的のためのほうが有用かもしれない。

また、実際のところ、静的解析よりも、実行時の動的な最適化(例えばコマンドライン引数に対応して、動的コード生成を行うプログラムなどがある)の際のほうが、if文の条件式などが恒真式になったりして、到達不能コードが現れやすいかもしれない。

コンパイラによる高度な最適化などの結果によっては、例えば共通式削除などで、ソースコード中には複数あらわれる表現であるのに、オブジェクトコード中で相当するのは一箇所だけ、といったような場合も、一種のデッドコードのようなものとして、デバッガなどからは見える場合もある。

到達不能コードが生まれる原因[編集]

以下では、到達不能コードが生じる原因をいくつか解説する。

ソフトウェア開発の際、プログラマは一時的にコードが実行されないようにすることがある(すなわち、明示的に実行されないように書き換える)。例えば、次のようなコードがある。

 while (condition)
 {
   foo();
   bar();
 }

ここで、一時的に bar() を実行しないようにしたい場合、例えば次のように書き換える。

 while (condition)
 {
   foo();
   continue;
   bar();
 }

この例では、bar() が到達不能コードになる(continue はループの次の反復に直ちに移行させる)。このような一時的な修正がリリース時まで残ってしまうことがある。

他の到達不能コードが生じる原因として、条件判断が冗長化している場合や、デバッグ用コードを削除せずに残してしまう場合がある。

関数やサブルーチンは、どこからも呼び出されない場合はデッドコードになるし、到達不能コードからのみ呼び出されている関数もデッドコードになる。

到達不能コードの存在は、プログラムの修正時の論理的誤りや、プログラムの前提や環境が大幅に変更されたことを示す場合もある。優れたコンパイラはデッドコードの存在を通知し、保守者がその意味を考えられるようにする。

到達不能性の検証[編集]

任意のコードが到達不能コードかどうかを判断することは停止性問題を解くことと等価である。すなわち、あらゆる到達不能コードを正しく把握することは不可能である。

実際には分析手法は精巧化しており、到達不能コードを検出する量は大幅に改善されている。例えば以下のコードで、定数畳み込みや単純なフロー解析では、xyz という文が到達不能であることを示すことができる。

 int n = 2 + 1;
 if (n == 4)
 {
   xyz
 }

しかし、以下のコードで xyz という文が到達不能であることを検出するにはかなりの努力を要する。

 double x = sqrt(2);
 if (x > 2)
 {
   xyz
 }

到達不能性とプロファイリング[編集]

場合によっては、静的コード解析による通常の手法と同時に、プロファイラを使って複雑なケースを検出できる場合もある。プロファイラはコードの到達不能性を「証明」できるわけではないが、到達不能コードを見つけ出すヒューリスティクスとしては優れている。疑わしい部分が見つかったら、もっと強力な分析ツールを使ったり、人間が目と手で調査して、そのコードが本当に到達不能かどうかを判断する。

脆弱性と到達不能性[編集]

著名な例は、AppleによるTLS/SSLの実装上のバグであり、CVE番号として CVE-2014-1266 が与えられているほか、"goto fail bug" とも呼ばれている[4][5]。実際のコードは以下の通りである[6]

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;というgoto文が、2行連続している所がある。そして、2行目のほうのgoto文により、本来到達すべき final の呼び出しが到達不能コードとなっていて、final によるチェックが常にスキップされ、err は SHA1 update 操作の成功の後の値を保持することとなり、final による署名検証が行われない状態となっていた[4]

あきらかにこれは「到達不能コードによってセキュリティ上の脆弱性が齎された」のではない(到達不能コードは、一般に何もしない。それが何か悪さをしたと言えるのは、例えばコードサイズの増加であるとか、言語仕様で未定義なソースコードの記述を利用して実行させる、などした場合であろう)。真に「セキュリティ上の脆弱性を齎した」のは、「一見すると到達不能コードのように見えるためか、放置されたものと思われる、2行目のほうのgoto文(によって、到達されねばならないコードが到達不能になっていたこと)」である。教訓は「到達不能コードを放置するな。なぜなら、そういうコードの存在を見慣れるせいで、到達不能であってはいけないコードが到達不能になっていることを見逃す原因になるから」という点である。

脚注[編集]

  1. ^ この例の場合では、メモリマップドI/Oの場合は意味が異なってくるが、一般にそういう場合は volatile を付けるなど、特別扱いが必要である。
  2. ^ たとえば「この部分を実行している時には、この変数の値は絶対に負ではない」といったような情報を使う。
  3. ^ 典型的には、if文のthen節とelse節の両方にreturnがあるのに、そのif文の後にも文が続いている、というような、意味的な分析を必要とせずに明らかなものについて。
  4. ^ a b Adam Langley (2014年). “Apple's SSL/TLS bug”. 2015年3月15日閲覧。
  5. ^ Arie van Deursen (2014年). “Learning from Apple’s #gotofail Security Bug”. 2015年3月15日閲覧。
  6. ^ sslKeyExchange.c - Source code for support for key exchange and server key exchange”. 2015年3月15日閲覧。

関連項目[編集]