data flow constraints雑記

めも。

range of primitive values

プリミティブの値の範囲は比較的計算しやすい。

void f(int a) {
    if (/* (1) */a < 0) {
        return /* (2) */ - /* (3) */a;
    }
    else {
        return /* (4) */a;
    }
}

これをまじめにやるとSSA変換してマーキングしながら値の範囲を絞らないといけない。結構めんどい。ともあれint型の式は以下のような範囲になる。

(n) expr range
1 a [Integer.MIN_VALUE..Integer.MAX_VALUE]
2 -a (0..Integer.MAX_VALUE], Integer.MIN_VALUE
3 a [Integer.MIN_VALUE..0)
4 a [0..Integer.MAX_VALUE]

「必ず0以上」ってものを探すとしたら、>= 0 を修飾するのが読みやすいか?

/**
 * @when
 *     expr must >= 0
 */
public void found(CtExpression<Integer> expr) {
    // exprは(4)のみ
}

クエリを走らせるたびにSSA変換というのはちょっと無理っぽいぞ。逆SSAが要らないだけまだマシだけど。
まぁ、φ関数は「domainの和集合」って意味にとればいいから、解釈は楽だ。

ちなみに、属性付き型+SSAでかなり見やすくなる。

void f(int a) {
    if (/* (1) */a < 0) {
        int:[< 0] a:0 = a;
        return -a:0;
    }
    else {
        int:[! < 0] a:1 = a;
        return a:1;
    }
}

nullable

Eclipseが比較的頑張ってる。局所的な解析はnullかそうでないかというレベルなら、プリミティブの計算と大差ない。

void f(Object a, Object b) {
    if (/* (1) */a == /* (2) */null) {
        // control flow BREAK
        throw /* (3) */new NullPointerException(/* (4) */"a");
    }
    System.out.println(/* (5) */a.toString());
    System.out.println(/* (6) */b.toString());
    System.out.println(/* (7) */a);
    System.out.println(/* (8) */b);
    System.out.println(/* (9) */this.toString);
}
(n) expr null remarks
1 a unknonw 初回
2 null true trivial
3 new.. false newなので
4 "a" false 文字列リテラルなので
5 a false (1)以降なので
6 b unknown 初回
7 a false (1)以降なので
8 b false (6)以降なので
9 this false thisはnullにならない

3値論理の意味は以下。

意味
true must null
false must NOT null
unknown may null

3値論理を使うとすっきり。「必ず〜である」「必ず〜でない」「不定」のいずれか。nullableに関する三値論理にヒットするクエリの演算子を考えてみると、分類としてはこうなるか?

true false unkwnon
must == null OK
must != null OK
may == null OK OK
may != null OK OK

表記も同様。

/**
 * @when
 *     expr may == null
 */
public void found(CtVariableAccess<?> expr) {
    // 変数参照でmay = nullなのは(1), (6)のみ
}

解析自体は拡張基本ブロックを使ってデータフローを計算すれば自明*1

こいつも参考程度に。

void f(Object a, Object b) {
    Self:[! null] this:0 = this;
    if (a == null) {
        Object:[null] a:0 = a;
        throw new NullPointerException("a");
    }
    Object:[! null] a:1 = a;
    System.out.println(a:1.toString());
    if (b == null) {
        Object:[null] b:0 = b;
        b:0.toString(); // error: cflow BREAK
    }
    Object:[! null] b:1 = b;
    System.out.println(b:1.toString());
    System.out.println(a:1);
    System.out.println(b:1);
    System.out.println(this:0.toString);
}

まとめ

ある地点でのデータの範囲はmust, may程度で記述できそう。

*1:a.toString()は(function(a) { if (a == null) throw new NullPointerException(); return a; })().toString()のようなものだと考えとく。