Upload
kamiya-toshihiro
View
607
Download
4
Embed Size (px)
DESCRIPTION
神谷年洋, "任意粒度機能モデルに基づくコードクローン検出手法の大規模プログラムの適用に向けた改善", 電子情報通信学会 2013年7月 ソフトウェアサイエンス研究会
Citation preview
2013/07/26 IPSJ SIGSS 2013 7年 月研究会
任意粒度機能モデルに基づくコードクローン検出手法の改善
神谷年洋公立はこだて未来大学
大規模プログラムへの適用に向けた
論文のwordle.www.wordle.netにて作成
2013/07/26 IPSJ SIGSS 2013 7年 月研究会
任意粒度機能モデルに基づくコードクローン検出手法とは
● ソースコードのリファクタリングの機会を探すため● ソフトウェアのソースコード内の類似した機能を持つコード断片を検出
する
● 類似した機能の判定: 2つのコード断片で呼び出す手続きの列が同じである
ただし
● 任意の粒度: 手続きは入れ子になって定義される。任意の階層で一致すれば一致と判定する
● 全実行パス: プログラムのあらゆる実行を考えて、そのいずれかの実行で一致すれば一致と判定する
2013/07/26 IPSJ SIGSS 2013 7年 月研究会
字面の一致を用いない理由
● なぜ「任意の粒度」「全実行パス」とか面倒なことをいってるのか– ⇔ 字面の一致を利用したコードクローン検出ツール
CCFinder(および-X)
● コードが書かれた後、書き換えられることを想定しているため– リファクタリング=ソースコードの機能を変えずに構造を
変更する
リファクタリングの例
2013/07/26 IPSJ SIGSS 2013 7年 月研究会
お題: FizzBuzz
● FizzBuzzとは– 1から順に数えあえげ。ただし
– 3で割り切れる数字ならfizz
– 5で割り切れる数字ならbuzz
– 3と5の両方で割り切れる数字ならfizzbuzz
● 1から20までのFizzBuzz– 1, 2, fizz, 4, buzz, fizz, 7, 8, fizz, buzz,
– 11, fizz, 13, 14, fizzbuzz, 16, 17, fizz, 19, buzz
● 元々は英語圏の子供の遊び。転じて、プログラミングの例題
http://rosettacode.org/wiki/FizzBuzz
170以上の言語で記述したfizzbuzz
2013/07/26 IPSJ SIGSS 2013 7年 月研究会
FizzBuzzWrong
● 最初の実装– 3で割り切れたら→fizz()
– 5で割り切れたら→buzz()
– それ以外なら→数字
● 仕様に合致せず – 「3と5の両方で割り切れ
る数字ならfizzbuzz」になっていない
2013/07/26 IPSJ SIGSS 2013 7年 月研究会
FizzBuzz1
● 仕様通り– 3で割り切れたらfizz()
● fizz(): fizzを出力してさらに5で割り切れたら→・・・
● 構造が汚い– テストを通すため、あまり考
えずにfizz()メソッドに仕事を押し込んだ
– fizz()が名前からは想像できない仕事をやっている
– fizz()とbuzz()の対称性が無くなった
2013/07/26 IPSJ SIGSS 2013 7年 月研究会
FizzBuzz3
● 仕様通り– 3と5で割り切れたら
→fizz()とbuzz()
● 構造もキレイ– fizz()内の処理を外側
に追い出した
– fizz()とbuzz()の対称性が回復した
2013/07/26 IPSJ SIGSS 2013 7年 月研究会
RQ(前回の)
● FizzBuzz1とFizzBuzz3から、等価な処理(「3と5の両方で割り切れる数字ならfizzbuzz」)を見つけてくることができるか?– 字面は全く異なる
2013/07/26 IPSJ SIGSS 2013 7年 月研究会
ope: java/lang/Integer.valueOf:(I)Ljava/lang/Integer; java/io/PrintStream.format:(Ljava/lang/String;⇩ [Ljava/lang/Object;)Ljava/io/PrintStream; java/io/PrintStream.print:(Ljava/lang/String;)V buzz:(Ljava/io/PrintStream;)V java/io/PrintStream.println:(Ljava/lang/String;)Vtrace: FizzBuzz1.java: 16 >0 // main:([Ljava/lang/String;)V FizzBuzz1.java: 16 >0 // main:([Ljava/lang/String;)V FizzBuzz1.java: 6 >1 // fizz:(Ljava/io/PrintStream;I)V FizzBuzz1.java: 8 >1 // fizz:(Ljava/io/PrintStream;I)V FizzBuzz1.java: 24 >0 // main:([Ljava/lang/String;)Vtrace: FizzBuzz3.java: 14 >0 // main:([Ljava/lang/String;)V FizzBuzz3.java: 14 >0 // main:([Ljava/lang/String;)V FizzBuzz3.java: 6 >1 // fizz:(Ljava/io/PrintStream;)V FizzBuzz3.java: 18 >0 // main:([Ljava/lang/String;)V FizzBuzz3.java: 25 >0 // main:([Ljava/lang/String;)V
・・・できます
● メソッド呼び出し列としてみれば一致する
● 右はツールの出力の一部– 「3と5の両方で割り切れる
数字ならfizzbuzz」の部分– 「trace:」の行で始まる2つの
実行系パス– 「ope:」で始まる実行がメ
ソッド呼び出し列
●「任意の粒度」「全実行パス」を考慮する手法により● このような書き換えを同じコード断片として検出
全て解決...?
* 神谷年洋, "任意粒度機能モデルに基づくバイトコードからのコードクローン検出手法", 電子情報通信学会技術研究報告書, vol. 113, no. 24, pp. 43-48 (2013/05/10).
2013/07/26 IPSJ SIGSS 2013 7年 月研究会
≒ 104倍
入力:15.2MiB(Java逆アセンブルコード)
中間ファイル:1.54GiB(n-gramデータ:後述)
出力:0.278MiB(コードクローン)
≒ 1/5600
バイト数
問題: 組み合わせ爆発
● 「任意の粒度」の「全実行パス」を生成
⇨ 容易に組み合わせ爆発を起こす– 小さなプログラムでも膨大な量の実
行パスを生成する
● 右は後述の実験の例– jEdit 5.0
● クラス数1122個● メソッド数6417個
– 本発表で説明する手法を活用して組み合わせ爆発を抑えた結果
2013/07/26 IPSJ SIGSS 2013 7年 月研究会
任意粒度機能モデルに基づくコードクローン検出手法とは
● ソースコードのリファクタリングの機会を探すため● ソフトウェアのソースコード内の類似した機能を持つコード断片を検出
する
● 類似した機能の判定: 2つのコード断片で呼び出す手続きの列が同じである
ただし
● 任意の粒度: 手続きは入れ子になって定義される。任意の階層で一致すれば一致と判定する
● 全実行パス: プログラムのあらゆる実行を考えて、そのいずれかの実行で一致すれば一致と判定する
ただし、現実的な時間で実行できること
2013/07/26 IPSJ SIGSS 2013 7年 月研究会
RQ
● 任意の粒度、全実行パスから同じ手続き呼び出し部分列を検出するコードクローン検出手法
を
● 組み合わせ爆発をしないように枝刈りを取り入れられるか– 大規模なプログラムに適用できるように
● ただし、興味深い対象を逃すことなく– 字面は異なるが機能は同じ(リファクタリングの適用対象になりそうな)
コードクローンを検出できるか
より正確には
2013/07/26 IPSJ SIGSS 2013 7年 月研究会
任意の粒度の全実行パスを考慮するコードクローン検出手法
理想的(ナイーブ)な定式化
1. プログラムのエントリポイントから制御構造を辿る– 分岐 ⇨ 分岐の数だけ実行パスを分ける
– 手続き(m)の呼び出し ⇨ mを呼び出す実行パスと、mの定義の中を辿る実行パスに分ける
2. 特定されたすべての実行パスから、すべての一致 部分列を探す
3. 一致部分列をコードクローンとして出力するこれがどのように組み合わせ爆発するのか
2013/07/26 IPSJ SIGSS 2013 7年 月研究会
Fizzbuzzの例から実行パスを作るFizzBuzz1.main()のfor文の中身
fizz() buzz() print()
fizz()
print()
buzz()
print()
buzz()
println()
2013/07/26 IPSJ SIGSS 2013 7年 月研究会
Fizzbuzzの例から実行パスを作るmain()のfor文の中身
fizz() buzz() print()
print()
buzz()
println()
{fizz, println}
{{print}, println}
{{print, buzz}, println}
{{print, {print}}, println}
{buzz, println}
{{print}, println}
{print, println}
実行パスの総数= O((分岐 * 2^呼び出し) ^ 呼び出し階層)
fizz()
print()
buzz()
分岐
呼び出し
呼び出し階層
→ ∞
2013/07/26 IPSJ SIGSS 2013 7年 月研究会
導入した枝刈り
● メソッドディスパッチの絞り込み (3.3節)● 実行パスの窓による取りつくし (3.2節)● ファンイン-ファンアウトクローンの除去 (3.3節)● ループに関するヒューリスティクス (3.4節)
2013/07/26 IPSJ SIGSS 2013 7年 月研究会
実行パスの窓による取りつくし(3.2)
● すべての実行パスを作り、それらの任意の長さの部分列から一致している部分を探す
● 実行パス上の n-gram (固定長の部分列)から一致している部分を探す(「窓」)
● 実行パスを辿りながらn-gramを生成する(「窓」の取りつくし)– 実行パスを作った後でn-gram を生成する、ではなく
・・・のをやめて
2013/07/26 IPSJ SIGSS 2013 7年 月研究会
「すべての実行パスを作る」
b1
s11 s12
b2
s21 s22
b3
s31 s32
b1
s11
b2
s21
b3
s31
b1
s12
b2
s21
b3
s31
b1
s11
b2
s22
b3
s31
b1
s12
b2
s22
b3
s31
b1
s11
b2
s21
b3
s32
b1
s12
b2
s21
b3
s32
b1
s11
b2
s22
b3
s32
b1
s12
b2
s22
b3
s32
2013/07/26 IPSJ SIGSS 2013 7年 月研究会
「窓の取りつくし」
b1
s11 s12
b2
s21 s22
b3
s31 s32
分岐があることを記録しておく
b1
s11 s12
b2
s21 s22
b3
s31 s32
b1
s11 s12
b2
s21 s22
b3
s31 s32
分岐から初めて合流したら終わり b1
s11 s12
b2
s21 s22
b3
s31 s32
2013/07/26 IPSJ SIGSS 2013 7年 月研究会
任意の粒度の全実行パスを考慮するコードクローン検出手法
現ツールの実装
1. プログラムのすべての手続きから制御構造を辿って n-gramを生成する
– 実行パスの窓による取りつくし(3.2)
2. 特定されたすべての n-gram を分類する
3. ある分類に2つ以上n-gramがあるものをコード クローンとして出力する
– ファンイン-ファンアウトクローンの除去(3.3)
2013/07/26 IPSJ SIGSS 2013 7年 月研究会
ファンイン-ファンアウト クローンの除去(3.3)
a
m
a
m
a
m
a
m
b
n
m
p q
C.n D.n
・・・
・・・
c c
● いくつかの実行パスが途中で同じ箇所を通る– 手続きp, q, ・・・ が手続きmを呼
び出す
– 手続きmはC.n, D.n, ...のいずれかを呼び出す
⇨ 生成される n-gram {a,{b,{c}}} はすべて手続きmを通る
● そのようなコードクローン(FFクローン)をフィルタリングによって取り除く
≒ 自然言語処理でコーパスを使って自立語と付属語を特定する手法
ファンイン
ファンアウト
2013/07/26 IPSJ SIGSS 2013 7年 月研究会
実験による評価
● (比較に用いた適用対象は1例のみ)● n = 4
実行パスの窓による取りつくし⇩
実行時間を約84倍短縮
ファンイン-ファンアウトクローンの除去
⇩コードクローンの数が約18分の1
2013/07/26 IPSJ SIGSS 2013 7年 月研究会
int dragStart = xyToOffset(x,y,!(painter.isBlockCaretEnabled()|| isOverwriteEnabled()));
if(getSelectionCount() == 0 || multi)moveCaretPosition(dragStart,false);
GUIUtilities.showPopupMenu(popup,painter,x,y);
JEditTextArea.handlePopupTrigger:(Ljava/awt/event/MouseEvent;)V
Dimension size = popup.getPreferredSize();
Rectangle screenSize = getScreenBounds();
GUIUtilities.showPopupMenu:(Ljavax/swing/JPopupMenu; Ljava/awt/Component;IIZ)V
Rectangle bounds = GraphicsEnvironment. getLocalGraphicsEnvironment().
getMaximumWindowBounds();
GUIUtilities.getScreenBounds:()Ljava/awt/Rectangle;
int dragStart = xyToOffset(x,y,!(painter.isBlockCaretEnabled()|| isOverwriteEnabled()));
if(getSelectionCount() == 0 || multi)moveCaretPosition(dragStart,false);
showPopupMenu(popup,this,x,y,false);
TextArea.handlePopupTrigger:(Ljava/awt/event/MouseEvent;)V
Dimension size = popup.getPreferredSize();
Rectangle screenSize = GraphicsEnvironment.getLocalGraphicsEnvironment()
.getMaximumWindowBounds();
TextArea.showPopupMenu:(Ljavax/swing/JPopupMenu; Ljava/awt/Component;IIZ)V
図 4 jEdit から検出されたコードクローンの例 (1)Fig. 4 A sample code clone (1) detected from jEdit
検出されたコードクローンの例 (n=6)
呼び出しの深さが異なる
2013/07/26 IPSJ SIGSS 2013 7年 月研究会
まとめ
● 大規模プログラムに適用することを目的とした● 任意粒度機能モデルに基づくコードクローン検出手法への
様々な枝刈り手法の導入を説明● 呼び出しの深さが異なるコード断片のコードクロー
ンの例を紹介
+ アルファ
2013/07/26 IPSJ SIGSS 2013 7年 月研究会
● タイトルの「大規模プログラムへの適用に向けた」はどうなった?
● 最新の実装を、より大きなソースコード(6938個のクラス)に適用しました– n = 4
追補
プロダクト
1122 6417 104 372 534414 36 469 1305
1700 8888 192 530 1118364 68 574 3415
6938 34335 5956 1357 715282 79 1701 2875
ステップ1 ステップ2 クラス
(個)メソッド定義(個)
時間(秒)
メモリ(MiB)
生成 n-gram (個)
時間(秒)
メモリ(MiB)
検出クローン(クラス数)
JEdit 5.0(テキストエディタ)ArgoUML 0.28.1(UMLはモデリング)Vuze 5.0.0.0(BitTorrentクライアント)
2013/07/26 IPSJ SIGSS 2013 7年 月研究会
研究の方向
● 小さなn-gramから段階的に伸長するように変更する– 例えば n = 4 → n = 8
– n-gram生成→分類→(n-gram再生成→分類)*● ≒たくさんあるオートマトン(すべての状態が初期状態となりうる)から共通の語を受
理するものを探す
● 曖昧な一致(順序の入れ替え、ギャップ)?– n-gramの段階的な伸長と整合性のあるアルゴリズム?
● シンボリック実行の導入?意味的な曖昧性の導入?– int ⇔ Integer
– /2 ⇔ >> 1 (メソッド呼び出し以外の命令も見ることを前提として)
– “ab” + “c” ⇔ “abc” (式の簡約をオンザフライに行う?)
2013/07/26 IPSJ SIGSS 2013 7年 月研究会
最新の実装
● 論文以降さらに様々な最適化を導入しています– 分岐やジャンプを最適化
● ジャンプした直後にジャンプ→ジャンプ
– 上記を利用して、ループの検出ルーチンを改良
● 現在)ブロックを作って、ブロックに入る辺を監視する
● 以前)若いアドレスへのジャンプを「ループの戻り」とみなしていた
– 上記ブロック入り辺の監視を利用すれば「最大2回ループを回る」実行パスをたどることが可能
● まだ実装はしてません
– クラス改装を利用してメソッドディスパッチの解析を改善
● 現在)クラス階層とメソッド定義の情報を利用して、レシーバーが簡単に特定できる場合には、呼び出されるメソッドを限定する
– そのクラスが導出階層の末端でなくても、最適化可能
● 以前)クラスが導出の階層の末端であるか/ないかだけを利用していた
– そのクラスに属するメソッドの呼び出しはすべてそのクラスで定義しているメソッドとしていた
github.com/tos-kamiya/agec2 に公開予定