Control Flow basedなdiffをとりたい。

皆さんはコードを先祖返りさせたことはありますか? 私はあります。*1
簡単に書くとこんな感じのことをやらかしました。

int a() {
    int type;

    type = getParam();

    if (type == 1) {
        hoge();
        fuga();
    } else {
        piyo();
        poyo();
    }

    foo();
    bar();

    return 0;
}

上記のコードを

int a() {
    int type;

    type = getParam();

    switch (type) {
        case 1:
            hoge();
            // oops
            break;
        case 2:
            newHoge();
            break;
        default:
            piyo();
            poyo();
            break;
    }

    foo();
    bar();

    return 0;
}

こんな感じにしてしまいました。
見た感じでわかると思いますが、

  1. メインブランチでtype = 1の処理にfugaの呼び出しが追加される
  2. type = 2の処理追加のマージ時にfugaの呼び出し追加の取り込みが漏れる

という事故です。この程度ならすぐわかりそうなものだと思いますが、このときは1KL超のマージ作業があり、かつif-elseがswitch-caseになることでインデントレベルがずれ、DIffも(空白を詰めるなどしなければ)まともに機能しないという状況でした。

で、このような事故をどのようにしたら防げるかなのですが、ある静的解析ツールにてControl Flowをベースにした比較機能があるということを知り、その方向で何かできないか模索しています。*2

どうやらGNUのツールでCflowというものがあるようなのでそれを試してみます。Ubuntuだとaptでインストールできます。
インストールして、冒頭の例の対応前コードについて出してみたものがこちら。

$ cflow bef.c
a() <int a () at bef.c:1>:
    getParam()
    hoge()
    fuga()
    piyo()
    poyo()
    foo()
    bar()

なんか思っていたのと違う気がしますが続行します。

$ diff <(cflow bef.c) <(cflow aft.c)
1c1
< a() <int a () at bef.c:1>:
---
> a() <int a () at aft.c:1>:
4c4
<     fuga()
---
>     newHoge()

流石にこのレベルだと単純増加ではなく関数呼び出しが置き換えられたという形で検出はできますね。
軽く触ってみた感じでは

  • 大規模なコードに対して比較ができるのかどうか
  • 計算式の一部が消えたなどのロジックの差分がとれないので、そこをどうするか

の2つが実用上の懸念点でしょうか。ロジックもControl Flowに出してくれるようなもので、かつテキストとして処理しやすい形で出せるものがあればいいのですが。

*1:ちょうどお盆の時期ですしね

*2:ぶっちゃけそれっぽいワードでググれば製品名出てくるのですが、試用版触った感じだと大規模な変更に対して機械的に漏れなく検出できるかが微妙なところでした。