コードクローン検出技術とその利用法

Download Report

Transcript コードクローン検出技術とその利用法

コードクローン検出技術と
その利用法
神谷年洋‡, 植田泰士†, 肥後芳樹†, 楠本真二†, 井上克郎†
†大阪大学 大学院情報科学研究科
{y-ueda, y-higo, kusumoto, inoue}@ist.osaka-u.ac.jp
‡科学技術振興事業団 さきがけ研究21
[email protected]

コードクローンとは



コードクローン検出と開発プロセス
コードクローン検出手法
ツールCCFinder / Gemini / CCShaper


2002/11/15
アルゴリズム
適用事例
1
コードクローンとは

ソースコード中に類似したコード片があるとき,
それらをコードクローンという
クローンペア
クローンクラス
クローンが生じる理由:コピー&ペースト,定型処理,
意図的な繰り返し,偶然,プログラミング言語に適
切な機能がないため,など
2002/11/15
2
コードクローンが引き起こす問題

コードクローンはソフトウェア
保守を困難にする



コードクローンにフォールトが存
在する場合,すべてのコードク
ローンについて修正を行わなけ
ればならない
機能追加も同様
大規模なソースコードから
コードクローンを効率よく検出
する方法が必要
2002/11/15
3
コードクローン検出と開発プロセス

修正前チェック


リファクタリング



修正しようとする部分にコードクローンがあるかチェッ
クする → すべて修正
コードクローンを検出する → まとめる
「メソッドの抽出」「メソッドの引き上げ」
定型処理(イディオム)抽出

2002/11/15
よく使われるコード断片を抽出 → 部品化
4
コードクローン検出のその他の応
用

ソースコードの剽窃をチェックする


定量的な評価
バージョン間の変化を調べる

2002/11/15
よりよいdiffとして
5
既存のコードクローン検出手法
(1/2)
Bakerの手法

行単位でソースコードを比較してコードクローンを検
出する
Baxterらの手法

C言語のソースファイルを入力,構文解析して,解析
木(の部分木)を比較する
[Baker1995] B. S. Baker: “On finding Duplication and Near-Duplication in Large
Software
System,” Proc. Second IEEE Working Conf. Reverse Eng., pp. 8695, Tronto, Canada (Jul., 1995).
[Baxter1998] I. D. Baxter, A. Yahin, L. Moura, M. Sant’Anna, and L. Bier: “Clone
Detection Using Abstract Syntax Trees,” Proc. of ICSM ’98, pp. 368-377,
Bethesda, Maryland (Nov., 1998).
2002/11/15
6
既存のコードクローン検出手法
(2/2)

Merloらの手法

手続き(メソッドや関数)の特徴メトリクスを比較して,
計測値が似ていればコードクローンであると判定する
[Balazinska1999] M. Balazinska, E. Merlo, M. Dagenais, B. Lague, and K.A.
Kontogiannis, "Measuring Clone Based Reengineering Opportunities", Proc. 6th
IEEE Int'l Symposium on Software Metrics (METRICS '99), pp. 292-303, Boca
Raton, Florida, Nov. 1999.
2002/11/15
7
CCFinder:
コードクローン検出ツール
ソースコードを字句解析してトークンの列に直し
てからコードクローン検出を行う

(浅い)文法知識に基づいたソースコード変形




2002/11/15
クラススコープや名前空間による複雑な名前の正規化を
行う
初期化テーブルを取り除く
モジュールの区切りを認識する
言語依存部分を取り替えることで,さまざまなプログ
ラミング言語に対応
8
コードクローン検出手順
(1)ソースコードを入力し,トークンの列にする
(2)変形ルールにより,トークン列を変形する
(3)パラメータ置換を行う
(4)マッチングアルゴリズムによりコードクローンを
検出する
(5)コードクローンの位置(ファイル,行, カラム)を出
力する
2002/11/15
9
例題ソースコード
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
2002/11/15
static void foo() throws RESyntaxException {
String a[] = new String [] { "123,400", "abc", "orange 100" };
org.apache.regexp.RE pat = new org.apache.regexp.RE("[0-9,]+");
int sum = 0;
for (int i = 0; i < a.length; ++i)
if (pat.match(a[i]))
sum += Sample.parseNumber(pat.getParen(0));
System.out.println("sum = " + sum);
}
static void goo(String [] a) throws RESyntaxException {
RE exp = new RE("[0-9,]+");
int sum = 0;
for (int i = 0; i < a.length; ++i)
if (exp.match(a[i]))
sum += parseNumber(exp.getParen(0));
System.out.println("sum = " + sum);
}
10
ステップ(1):ソースコードを入力し,トークンの列にする
1. static void foo (
2. String a
[
]
) throws RESyntaxException {
= new String [
]
{ "123,400" , "abc" , "orange 100" }
;
3. org . apache . regexp . RE pat = new org . apache . regexp . RE ( "[0-9,]+" )
4. int sum =
0
5. for ( int i
6. if
;
=
0
;
( pat . match (
i
a
<
[
a
i
. length ; ++ i
]
)
)
)
7. sum += Sample . parseNumber ( pat . getParen (
8. System . out . println ( "sum = " + sum )
0
)
)
;
;
9. }
10. static void goo ( String a
[
]
11. RE exp = new RE ( "[0-9,]+" )
12. int sum =
0
13. for ( int i
14. if
) throws RESyntaxException {
;
;
=
0
;
i
<
a
. length ; ++ i
( exp . match (
a
[
i
]
)
15. sum += parseNumber ( exp . getParen (
)
)
0
16. System . out . println ( "sum = " + sum )
)
)
;
;
17. }
2002/11/15
11
;
ステップ(2):変形ルールにより,トークン列を変形する
1. static void foo (
2. String a
[
]
) throws RESyntaxException {
= new String [
]
{ "123,400"
$u } ; , "abc" , "orange 100" }
;
3. RE
org pat. apache
= new RE
. regexp
( "[0-9,]+"
. RE )pat ; = new org . apache . regexp . RE ( "[0-9,]+" )
4. int sum =
0
5. for ( int i
6. if
;
=
0
;
( pat . match (
i
a
<
[
a
i
. length ; ++ i
]
)
)
)
7. sum += Sample . parseNumber ( pat . getParen (
8. System . out . println ( "sum = " + sum )
0
)
)
;
;
9. }
10. static void goo ( String a
[
]
11. RE exp = new RE ( "[0-9,]+" )
12. int sum =
0
13. for ( int i
14. if
) throws RESyntaxException {
;
;
=
0
;
i
<
a
. length ; ++ i
( exp . match (
a
[
i
]
)
)
)
15. sum += parseNumber
$p . parseNumber
( exp (. getParen
exp . getParen
( 0 )( )0 ;)
16. System . out . println ( "sum = " + sum )
)
;
;
17. }
2002/11/15
12
;
ステップ(3):パラメータ置換を行う
throws$pRESyntaxException
{
1. static void
$p $pfoo ( ( ) ) throws
{
2. String
$p $p a [ [ ] ] = =newnew$pString
[ ] [ { ] $u{ }$u ; }
3. RE
$p pat
$p = new RE
$p ( "[0-9,]+"
$p ) ; )
;
;
4. int
$p sum
$p == $p0 ;;
5. for ( int
i = $p
0 ; $p
i < $p
a . length
; ++
$p $p
$p ; ++
$p i) )
6. if
( pat
$p . match
$p ( ($p a [ [$p i ] ] ) ) ) )
+= $p
Sample
7. sum
$p +=
. $p. parseNumber
( $p . $p ( ( pat$p . ) getParen
) ; (
. out
. println
8. System
$p . $p
. $p
( $p (+ "sum
$p =) " ;+ sum )
0
)
)
;
;
9. }
) throws
{
10. static void
$p $pgoo ( ( $pString
$p [a ][ )] throws
$p RESyntaxException
{
new $p
RE (( $p
"[0-9,]+"
11. RE
$p exp
$p == new
) ; )
;
12. int
$p sum
$p == $p0 ;;
13. for ( int
i = $p
0 ; $p
i < $p
a . length
; ++
$p $p
$p ; ++
$p i) )
14. if
( exp
match( $p
( a[ $p
[ i] ]) )) )
$p . . $p
15. sum
+= $p
$p . . $p
parseNumber
. getParen
$p +=
( $p . ($p exp( $p
) ) ;(
16. System
. out
. println
$p . $p
. $p
( $p (+ "sum
$p =) " ;+ sum )
0
)
)
;
;
17. }
2002/11/15
13
2002/11/15
static
$p
$p
(
)
throws
$p
{
$p
$p
[
]
"="
$p
$p
[
]
{
$u
}
;
$p
$p
"="
new
$p
(
$p
)
;
$p
$p
"="
$p
ステップ(4):
マッチングアル
ゴリズムにより,
コードクローン
を検出
static *
$p
* *
*
* *
* *
* *
*
*
* *
*
$p
* *
*
* *
* *
* *
*
*
* *
*
(
*
*
)
*
*
throws
*
$p
* *
*
* *
* *
* *
*
*
* *
*
{
*
*
$p
* *
*
* *
* *
* *
*
*
* *
*
$p
* *
*
* *
* *
* *
*
*
* *
*
[
*
*
]
*
*
"="
*
*
*
$p
* *
*
* *
* *
* *
*
*
* *
*
$p
* *
*
* *
* *
* *
*
*
* *
*
[
*
*
]
*
*
{
*
*
$u
*
}
*
;
*
*
$p
* *
*
* *
* *
* *
*
*
* *
*
$p
* *
*
* *
* *
* *
*
*
* *
*
"="
*
*
*
new
*
$p
* *
*
* *
* *
* *
*
*
* *
*
(
*
*
$p
* *
*
* *
* *
* *
*
*
* *
*
)
*
*
;
*
*
$p
* *
*
* *
* *
* *
*
*
* *
*
$p
* *
*
* *
* *
* *
*
*
* *
*
14
"="
*
*
*
$p
* *
*
* *
* *
* *
*
*
* *
*
ステップ(4):
マッチングアル
ゴリズムにより,
コードクローン
を検出
2002/11/15
***
* **
**
** * * ** *
** * * * * *
* * * *
* * * * * *
* * * * * * ** **
* **
**
** * * ** *
** * * * * *
* * * *
* * * * * *
* * * * *
*** * **
**
** *** ** * *** * * * * *
* *** *
* * *** ***
* * *** *
*****
* **
**
** ** * ** * * ** * * * * * * * ** * *
* * ** * ** *
* * ** * *
*
*
* *
**
**
*
*
*
*
**
**
*
*
** **
**
* **
**
** * * ** *
** * * * * *
* * * *
* * * * * *
* * * * *
** **
**
** * * ** *
** * * * * *
* * * *
* * * * * *
* * * * *
**
****
** *
** * * ** *
** * * * * * * * * * *
* * * * * *
* * * * *
** **
****
** *
** * * ** *
** * * * * * * * * * *
* * * * * *
* * * * *
**
* *** ***
** * * ** *
** * * * * *
* * ***
* * * * * *
* * * * *
** ***
* *** ***
** * * ** *
** * * * * *
* * * **
* * * * * *
* * * * *
*
*
*
*
*
*
*
**
* ** ***
*** * * ****
** ** * * * *
* * * *
* * * * * *
* * * * *
** **
* ** * **
*** * * *** *
*** * * * * *
* * * *
* * * * * *
* * * * *
**
* *** ***
** * * ** *
** * * * * *
* * ***
* * * * * *
* * * * *
** ** * * ** * ** *
** * * ** *
** * * * * *
* * * **
* * * * * *
* * * * *
*
*
*
*
*
*
*
*
**
*
*
**
*
**
**
*
*
*
*
**
**
*
*
*
*
**
**
**
*
**
**
** ** ** ** ** ** ** ** ** **
** ** ** ** **
**
** **
**
** ** ** ** ** ** ** ** ** **
** ** ** ** **
**
* **
** ***
**
*** *
* *
* **
****
*
***** ** ** ** **
** **
**
** ***
**
** * ** ** **
** *** **
** *** ** ** ** **
*
*
**
* **
**
** * * ** *
** * * * * *
* * * *
* * * * * *
* * * * *
** **
* **
**
** * * ** *
** * * * * *
* * * *
* * * * * *
* * * * *
** *** *
*** * **
**
** *** ** * *** * * * * *
* * * ** * **
* * * ** *
** ***
* **
**
** * ** ** * * ** * * * * * * * ** * *
* * ** * ** *
* * ** * *
*
**
**
*
*
**
**
*
*** * ***** ** ** *** * ** **
* ** * *** ** ** ** ** * * ** **
**
* **
**
* * * *
* * * * * * * * * * * * * ** **
* **
**
* * * *
* * * * * * * * * * * **
**
* ** ***
*** * * ****
**** * * * *
* * * *
* * * * * *
* * * * *
** **
* ** ***
*** * * ****
**** * * * *
* * * *
* * * * * *
* * * * *
**
* **
**
* * * *
* * * * * * * * * * * * * ** **
* **
**
* * * *
* * * * * * ** * * * * *
*** * * *** *** ** ** * * ** *
*** * * *** * ** ** * ** * * * *
* * ** * *
*** * **
**
** *** ** * *** * * * * *
* * ** * ** *
* * ** * *
*** **
* **
**
** ** * ** * * ** * * * * * * * ** * *
* * ** * ** *
* * ** * *
**
* ** ***
*** * * ****
**** * * * *
* * * *
* * * * * *
* * * * *
** **
* ** ***
*** * * *** *
*** * * * * *
* * * *
* * * * * *
* * * * *
**
* **
**
** * * *** ** ** *** * ** *
* * * *
* * * * * * * * * * * * * ** **
* **
**
** * * *** * * ** * ** * * * *
* * * *
* * * * * * ** * * * * *
*
*
**
* **
**
** * * ** *
** * * * * *
* * * *
* * * * * *
* * * * *
** **
* **
**
** * * ** *
** * * * * *
* * * *
* * * * * *
* * * * *
**
* **
**
** * * ** *
** * *** * *
* * * *
* * * * * *
* * * * *
** **
* **
**
** * * ** *
** * *** * *
* * * *
* * * * * *
* * * * *
** *
** * *
** * ** *
** ** * *
** *
** * *
* * ** * *
* ** * * *
**
* **
**
**
*
*
**
*
**
*
*
*
*
*
*
*
**
**
*
**
**
**
*
*
**
*
**
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
**
* **
**
** * * ** *
** * * * * **
* * * *
* * * * * *
* * * * *
** **
* **
**
** * * ** *
** * * * * * *
* * * *
* * * * * *
* * * * *
* *
*
**
**
**
*
* *
*
**
**
**
*
*
*
*
*
** *** *
** * ** *
*** * **
**
** *** ** * *** * * * * *
* * *** ***
* * *** *
*****
* **
**
** * ** ** * *** * * * * *
* * * ** * **
* * ** * *
**
* **
**
** * * ** *
** * * *** *
*** * *
* ** * ** * *
** ** * * *
** **
* **
**
** * * ** *
** * * ** * *
** * * *
* ** * ** * *
** ** * * *
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
**
* **
**
** * * ** *
** * * * * *
* * * *
* * * * * *
* * * * *
** **
* **
**
** * * ** *
** * * * * *
* * * *
* * * * * *
* * * ** *
*
*
*
*
*
*
*
**
* ** * ** *
** * * ** *
** * * * * *
* * * ** * * * * * *
* * * * *
** ** * * ** * ** *
** * * ** *
** * * * * *
* * * ** * * * * * *
* * * * *
*
*
**
**
**
**
**
**
**
**
**
**
*
*
**
**
**
**
**
* **
**
** * * ** *
** * * * * *
* * * *
*** * * * *
* * * * *
** **
* **
**
** * * ** *
** * * * * *
* * * *
** * * * * *
* * * * *
**
* **
**
** * * ** *
** * * * * *
* * * *
* * * * * *
* * * * *
** **
* **
**
** * * ** *
** * * * * *
* * * *
* * * * * *
* * * * *
**
* **
**
** * * ** *
** * * *** *
* ** * *
* * ** * ** *
* ** ** * *
** **
* **
**
** * * ** *
** * * ** * *
** * * *
* ** * ** * *
** ** * * *
** *** *
** *** *
*** * **
**
** *** ** * *** * * * * *
* * *** ***
* * *** *
*****
* **
**
** *** ** * *** * * * * *
* * *** ***
* * *** *
*
*
*
*
*
*
*
*
*
*
*** * **
**
** *** ** * *** * * * * *
* * * ** * **
* * * ** *
** ***
* **
**
** * ** ** * *** * * * * *
* * * ** * **
* ** ** ** *
** *** *
** * ** *
** * * **
**
** * ** ** *
** * * * * **
* * * * *** * * * * *** * * * * **
** ** * * **
**
** * ** ** *
** * * * * **
* * * * ** * * * * * *** * * * * **
*
*
*
**
**
*
*
*
*
**
**
**
*
*
*
*
*
*
*
*
*
*
*
*
*
* ** * * ** *
** * * * * * ** **
* ** * * ** *
** * * * * *
**
* **
**
** * * * ** *
* ** * *
* * ** * ** *
* **
**
** * * * ** *
* ** * *
* * ** * ** *
* *
* *
**
* **
**
** * * ** *
** * * *** *
*** * *
* *** *** *
***** * *
** **
* **
**
** * * ** *
** * * ** * *
** * * *
* ** * ** * *
** ** * * *
*** * **
**
** *** ** * *** * * * * *
* * *
* * * ** * **
* * * ** *
** ***
* **
**
** * ** ** * *** * * * * *
* * * *
* * * ** * **
* * * ** *
**
**
* **
**
** * * ** *
** * * * * *
* *** *
* * * * * *
* * * * *
** **
* **
**
** * * ** *
** * * * * * * * ** * *
* * * * * *
* * * * *
**
* **
**
** * * ** *
** * * * * *
* * * *
* * * * * *
* * * ***
** **
* **
**
** * * ** *
** * * * * *
* * * *
* * * * * *
* * * ***
*
**
**
***
**
*
**
**
** *
**
*
*
*
* *
*
*
*
* *
*
*
**
*
*
*
***
* **
**
** * * ** *
** * * * * *
* * * *
* * * * * *
* * * * *
** **
* **
**
** * * ** *
** * * * * *
* * * *
* * * * * *
* * * * *
*** * **
**
** *** ** * *** * * * * *
* * *** ***
* * *** *
*****
* **
**
** *** ** * *** * * * * *
* * *** ** *
* * ** * *
** *** *
** *** *
**
*
**
**
**
*
*
**
*
**
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
**
**
*
**
**
**
*
*
**
*
**
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
**
* **
**
** * * ** *
** * * * * *
* * * *
* * * * * *
* * * * *
** **
* **
**
** * * ** *
** * * * * *
* * * *
* * * * * *
* * * * *
**
**
* *
**
**
**
* *
*
*
*
**
**
*
*
*
*
**
**
*
** **
** ** **
**
** * * ** *
** * * * * *
* * * *
* * * * * *
* * * * *
** **
**
** * * ** *
** * * * * *
* * * *
* * * * * *
* * * * *
***
*
* * * * *
* **
*
* * * * *
**
*
**
**
*
*
**
*
**
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
**
**
*
**
**
*
*
**
*
**
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
**
* **
**
** * * ** *
** * * * * *
* * * *
* * * * * *
* * * * *
** **
* **
**
** * * ** *
** * * * * *
* * * *
* * * * * *
* * * * *
**
**
* *
**
** **
* *
**
* ** ***
*** * * ****
**** * * * *
* * * *
* * * * * *
* * * * *
** **
* ** ***
** * * * ** **
** ** * * * *
* * * *
* * * * * *
* * * * *
**
* *** ***
** * * ** *
** * * * * *
* * * **
* * * * * *
* * * * *
** ** * * ** * ** *
** * * ** *
** * * * * *
* * * **
* * * * * *
* * * * *
*
*
*
*
*
*
*
*
**
*
*
**
*
**
*
*
**
*
*
**
* **
**
** * * *** ** ** *** * ** *
* * * *
* * * * * * * * * * * * * ** **
* **
**
** * * *** * * ** * ** * * * *
* * * *
* * * * * * ** * * * * *
**
* ** ***
*** * * ****
**** * * * *
* * * *
* * * * * *
* * * * *
** **
* ** ***
** * * * ** **
** ** * * * *
* * * *
* * * * * *
* * * * *
*
*
**
* **
**
** * * ** *
** * * * * *
* * * *
* * * * * *
* * * * *
** **
* **
**
** * * ** *
** * * * * *
* * * *
* * * * * *
* * * * *
**** * **
**
** **** ** * *** * * * * ** ** * ** * *** * * ** * **** * * * ** **
** *** * * **
**
** ** ** ** * * ** * * * * ** * * ** * * ** * * ** * ** * ** * * ** * * *
*
*
*
*
*
*
*
*
*
*
*
*
*
**
* **
**
** * * ** *
** * * * * *
* * * *
* * * * * *
* * * * *
** **
* **
**
** * * ** *
** * * * * *
* * * *
* * * * * *
* * * * * *
**
* ** ***
*** * * ****
**** * * * *
* * * *
* * * * * *
* * * * *
** **
* ** ***
*** * * *** *
*** * * * * *
* * * *
* * * * * *
* * * * *
**
* **
**
**
*
*
**
*
**
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
**
**
*
**
**
**
*
*
**
*
**
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
**
*
*
*
*
*
*
**
*
*
* * * * * *
** *** *
*** * **
**
** *** ** * *** * * * * *
* * * ** * **
* * * ** *
** ***
* **
**
** * ** ** * *** * * * * * * * ** * *
* * ** * ** *
* * ** * *
**
* ** ***
** * * * ** **
** ** * * * *
* * * *
* * * * * *
* * * * *
** **
* ** ***
** * * * ** **
** ** * * * *
* * * *
* * * * * *
* * * * *
**
* **
**
** * * ** *
** * * * * *
* * * *
* * * * * *
* * * * *
** **
* **
**
** * * ** *
** * * * * *
* * * *
* * * * * *
* * * * *
*** * * *** ** ** *** * ** *
*** * * *** ** ** *** * ** *
**
* **
**
* * * *
* * * * * * * * * * * * * ** **
* **
**
* * * *
* * * * * * ** * * * * *
**
* **
**
** * * ** *
** * * ** * *
* * * *
* * * * * *
* * * * *
** **
* **
**
** * * ** *
** * ** * * *
* * * *
* * * * * *
* * * * *
** *
** * *
** *** *
**** * *
** *
** * *
**
* **
**
**
*
*
**
*
**
*
*
*
*
*
*
*
**
**
*
**
**
**
*
*
**
*
**
*
*
*
*
* *** *** * * ***** * * *
*
*
*
*
*
*
*
*
*
*
*
*
**
* **
**
** * * ** *
** * * * * **
* * * *
* * * * * *
* * * * *
** **
* **
**
** * * ** *
** * * * * **
* * * *
* * * * * *
* * * * *
* *
*
**
**
**
*
* *
*
**
**
**
*
*
*
*
*
*** * **
**
** *** ** * * ** * * * * * * * ** * *
* * ** * ** *
* * ** * *
*** **
* **
**
** ** * ** * * ** * * * * * * * ** * *
* * ** * ** *
* * ** * *
**
* **
**
** * * ** *
** * * *** *
*** * *
* *** *** *
***** * *
** **
* **
**
** * * ** *
** * * * ** *
* ** * *
* * ** * ** *
** ** * * *
* * ** * *
*** * *** ***
** *** ** * *** * * * * *
* * ** * ** *
* * ** * *
*** ***
* *** ***
** ** * ** * * ** * * * * * * * ** * **
* * ** * ** *
* * ** * *
*
**
* ** * ** *
** * * ** *
** * * * * *
* * * ** * * * * * *
* * * * *
** ** * * ** * ** *
** * * ** *
** * * * * *
* * * ** * * * * * *
* * * * *
*
*
*
**
**
**
**
**
**
*
*
*
**
**
**
**
**
**
**
**
* **
**
** * * ** *
** * * * * *
* * * *
*** * * * *
* * * * *
** **
* **
**
** * * ** *
** * * * * *
* * * *
*** * * * *
* * * * *
**
* **
**
** * * ** *
** * * * * *
* * * *
* * * * * *
* * * * *
** **
* **
**
** * * ** *
** * * * * *
* * * *
* * * * * *
* * * * *
*
*
*
*
*
*
*
*
*
*
*
*
**
* **
**
** * * ** *
** * * * * *
* * * *
* * * * * *
* * * * *
** **
* **
**
** * * ** *
** * * * * *
* * * *
* * * * * *
* * * * *
** * ** *
*** * **
**
** * ** ** * *** * * * * *
* * * ** * **
* * * ** *
*** **
* **
**
** ** * ** * * ** * * * * * * * ** * *
* * ** * ** *
* * ** * *
** * *
** *** *
**** * *
** *
** * *
** * ** *
*** * **
**
** *** ** * *** * * *** *
*
*
*
*
**
**
*
**
**
**
*
*
**
*
**
*
*
*
*
*
*
* ** ** ** *
*
*
*
*
*
*
*
*
*
*
*
*
** * * **
**
** * ** ** *
** * * * * **
* * * * *** * * * * *** * * * * **
** ** * * **
**
** * ** ** *
** * * * * **
* * * * ** * * * * * *** * * * * * *
*
**
**
** *
**
*
**
**
** *
**
*
*
* *
*
*
* *
**
*
*
**
*
*
**
* **
**
** * * ** *
** * * *** *
*** * *
* *** *** *
***** * *
** **
* **
**
** * * ** *
** * * *** *
*** * *
* *** *** *
***** * *
**
* **
**
** * * ** *
** * * *** *
*** * *
* *** *** *
** ** * * *
** **
* **
**
** * * ** *
** * * ** * *
* ** * *
* * ** * ** *
* ** ** * *
*** * **
**
** *** ** * *** * * * * *
* * *
* * * ** * **
* * * ** *
** ***
* **
**
** * ** ** * *** * * * * *
* * * *
* * * ** * **
* * * ** *
**
**
* **
**
** * * ** *
** * * * * *
* * ** *
* * * * * *
* * * * *
** **
* **
**
** * * ** *
** * * * * * * * ** * *
* * * * * *
* * * * *
**
* **
**
** * * ** *
** * * * * *
* * * *
* * * * * *
* * * ***
** **
* **
**
** * * ** *
** * * * * *
* * * *
* * * * * *
* * * * **
*
*
*
**
**
*
*
*
*
**
**
**
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
15
ステップ(5):コードクローンの位置を出力する
クローン
2002/11/15
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
static void foo() throws RESyntaxException {
String a[] = new String [] { "123,400", "abc", "orange 100" };
org.apache.regexp.RE pat = new org.apache.regexp.RE("[0-9,]+");
int sum = 0;
for (int i = 0; i < a.length; ++i)
if (pat.match(a[i]))
sum += Sample.parseNumber(pat.getParen(0));
System.out.println("sum = " + sum);
}
static void goo(String [] a) throws RESyntaxException {
RE exp = new RE("[0-9,]+");
int sum = 0;
for (int i = 0; i < a.length; ++i)
if (exp.match(a[i]))
sum += parseNumber(exp.getParen(0));
System.out.println("sum = " + sum);
}
16
変形ルールを用いない場合
4-6行目と
13-15行目,
8-10行目と
17-19行目
2002/11/15
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
static void foo() throws RESyntaxException {
String a[] = new String [] { "123,400", "abc", "orange 100" };
org.apache.regexp.RE pat = new org.apache.regexp.RE("[0-9,]+");
int sum = 0;
for (int i = 0; i < a.length; ++i) {
if (pat.match(a[i]))
sum += Sample.parseNumber(pat.getParen(0));
}
System.out.println("sum = " + sum);
}
static void goo(String [] a) throws RESyntaxException {
RE exp = new RE("[0-9,]+");
int sum = 0;
for (int i = 0; i < a.length; ++i) {
if (exp.match(a[i]))
sum += parseNumber(exp.getParen(0));
}
System.out.println("sum = " + sum);
}
17
Java向けの変形ルール
RJ1:パッケージ名除去
( PackageName ‘.’ )+ ClassName → ClassName
RJ2:Callee挿入
NDotOrNew NClassName ‘(‘
→ NDotOrNew CalleeIdentifier ‘.’ NClassName ‘(‘
RJ3:テーブル初期化除去
'=' '{' InitalizationList, '}' → '=' '{' UniqueIdentifier '}‘
']' '{' InitalizationList, '}' → ']' '{' UniqueIdentifier '}’
RJ4:モジュールの分離
トップレベルの定義や宣言の終わりにUniqueIdentifier を挿入する
RJ5:可視性キーワードの除去
protected, public,synchronizedなどのキーワードを取り除く
RJ6:コンパウンド・ブロックの処理
if (…), else, while (…) の直後が単文なら{}を補う
2002/11/15
18
適用例#1 JDKのライブラリ



JDK(Java Development Kit) 1.2.2(サンプルと
デモプログラムを除く)
入力ファイルは1648個,約50万行
ツールの実行には,Pentium III 650MHzおよび
1GBのRAMを持つPCで約3分を要した
2002/11/15
19
JDKのコードクローン散布図




両軸はソースファイ
ルを辞書順に並べた
もの
20行以上のコードク
ローンを図示
多くのコードクローン
が密集している(A)
最長のコードクローン
(B)
2002/11/15
B
A
20
コードクローンが密集している部分
(A)

src/javax/swing/plaf/multi/*.java(29個)


クラス名を除いてまったく同じクラスの定義
コード生成ツールによって生成された
31| */
32| public class MultiButtonUI extends ButtonUI {
33|
160|
161|
162|
163|
164|
165|
2002/11/15
public static ComponentUI createUI(JComponent a) {
ComponentUI mui = new MultiButtonUI();
return MultiLookAndFeel.createUIs(mui,
((MultiButtonUI) mui).uis,
a);
}
21
最長のコードクローン(B)




最長のコードクローン(349行)
src/java/util/Arrays.javaの18の“sort”メソッド
シグネチャ(引数の型と数)が異なる
アルゴリズムは同一
2002/11/15
22
適用例#2 FreeBSD, Linux,
NetBSDの比較

3つのOSの比較



FreeBSD 4.0 (C 220万行)
Linux 2.4.0 (C 240万行)
NetBSD 1.5 (C 260万行)

FreeBSDとNetBSDは同じソースコードから,
Linuxは異なるソースコード

実行には108分を要した
2002/11/15
23
NetBSD 1.5
NetBSD 1.5
Linux 2.4.0
FreeBSD 4.0
FreeBSD, Linux, NetBSDのコードクローン散布図
FreeBSD 4.0
Linux 2.4.0
2002/11/15
24
FreeBSDとLinuxのコードクローン


共通のソースから分
岐したソースファイ
ル
名前が付け替えら
れたソースファイル
あるソースファイル
を複数のファイルに
分割している →
2002/11/15
lib/inflate.c
fs/cramfs/inflate/
{adler32.c,
infblock.c,
infcodes.c,
inffast.c,
inflate.c,
inftrees.c,
infutil.c }
drivers/net/zlib.c
arch/ppc/coffboot/
zlib.c
sys/pc98/boot/
kzipboot/misc.c
sys/net/zlib.c
sys/kern/inflate.c
Linux 2.4.0
sys/i386/boot/
kzipboot/misc.c
sys/kern/inflate.c
FreeBSD 4.0

ドライバ部分に
多くのクローン
「ファイル」が存
在する
sys/net/zlib.c
sys/pc98/boot/
kzipboot/misc.c
arch/ppc/coffboot/
zlib.c
drivers/net/zlib.c
Linux 2.4.0

sys/i386/boot/
kzipboot/misc.c
FreeBSD 4.0
fs/cramfs/inflate/
{adler32.c,
infblock.c,
infcodes.c,
inffast.c,
inflate.c,
inftrees.c,
infutil.c }
lib/inflate.c
25
大学の演習(1)




Pascal風言語で書かれたコードをCASLに変換す
るコンパイラ
C言語で開発
教科書,指導書を参考にして作成
課題1:構文チェッカ, 課題2:意味チェッカ, 課題3:
コンパイラ,の順に作成


課題2と課題3では直前の課題で作成したプログラムを
拡張
27人分のソースコード(P1~P27)を利用
2002/11/15
26
大学の
演習(2)
P1
P2
P3
P4
P5
P1
P2
P3
P4
P5
2002/11/15
27
大学の演習(4)
拡張時における利用割合による分析

関数単位の利用のされ方による分類

拡張時の利用度が高いP17(71%)と低いP5(39%)について調査
CHECKER P22
変更なし
26%
変更有り
69%

P5 課題2→課題3
23%
26%
5%
利用されず

P17 課題2→課題3
13%
64%
69%
5%
P17では課題2で作成したプログラムを関数単位でうまく利用している
→ 課題2における設計がしっかりしていたと評価できる
P5では何らかの理由で大幅な作り変えが行われた
2002/11/15
→ 原因を調査し対処方法を指導に盛り込む
28
ツールGemini / CCShaper

インタラクティブな環境を提供(Gemini)


統計的な分析(Gemini)


メトリクスグラフ
Gappedクローン検出(Gemini)


スキャッタープロット表示,ズーム,ソート
コピー&ペースト&修正されたコード断片
CCFinderの出力から意味的なまとまりを取り出
す(CCShaper)
2002/11/15
29
Gemini:
コードクローン分析環境

CCFinderの出力はテキストベース


分析が大変
ソースコード分析を補助するツール


2002/11/15
直感的,対話的な操作
統計分析機能
30
Gappedクローン検出

コピー&ペーストの後,追加や削除が行われる


追加や削除された部分=ギャップ
Gappedクローン検出機能により,そのような場
合でもコードクローンとして検出することができる
2002/11/15
31
Gappedクローンの例
543| else {
544| t = NULL_STRING;
545| spaces.push_back(t);
546| errors.push_back(pos);
547| append_recover_to_last_space();
548| state = MS_INIT;
549| }
550|
551| bool bFirstTrial = true;
552| bool bGotAvailToken = false;
553| while (! bGotAvailToken && pos <
text.size()) {
554| if (get_word(&t, SpecialPrevChar))
{

340| else {
341| t = "";
342| //pool_thru(&pool, &t);
343| spaces.push_back(t);
344| errors.push_back(pos);
345| append_recover_to_last_space();
346| }
347|
348| bool bFirstTrial = true;
349| bool bGotAvailToken = false;
350| while (! bGotAvailToken && pos <
text.size()) {
351| if (get_word(&t)) {
Geminiで検出されたGappedクローン

2002/11/15
赤い文字の部分がギャップ
32
CCShaper:
「絞り込み」ツール

CCFinderが検出するコードクローンはトークン
の並び

必ずしも意味的な固まり(クラス,メソッド,etc…)には
なっていない

リファクタリング(「メソッドの抽出」や「メソッドの
引き上げ」)の対象としては不適当なコードク
ローンが多く含まれる

適切なフィルタリング
2002/11/15
33
メソッドの抽出
Void methodA(int i){
methodZ();
System.out.println(“name:” + name);
System.out.println(“amount:” + i);
}
Void methodB(int i){
methodY();
System.out.println(“name:” + name);
System.out.println(“amount:” + i);
}
2002/11/15
void methodA(int i){
methodZ();
methodC(i);
}
void methodB(int i){
methodY();
methodC(i);
}
Void methodC(int i){
System.out.println(“name:” + name);
System.out.println(“amount:” + i);
}
34
メソッドの引き上げ
クラスA
クラスA
メソッドA
クラスB
メソッドA
2002/11/15
クラスC
クラスB
クラスC
メソッドA
35
CCFinderで検出されるコードクローン(例1)
righttokennumber = c.getEndNumber() - c.getStartNumber() + 1;
return clone;
}
}
string getLeftClone() const
{
char temp[STRLENGTH];
snprintf(temp,STRLENGTH,
"%s\t%d,%d,%d\t%d,%d,%d\t",leftID.c_str(),
leftstartline,leftstartcolumn,leftstartnumber,
leftendline,leftendcolumn,leftendnumber);
string getRightClone() const
{
char temp[STRLENGTH];
snprintf(temp,STRLENGTH,
"%s\t%d,%d,%d\t%d,%d,%d\t",rightID.c_str(),
rightstartline,rightstartcolumn,rightstartnumber,
rightendline,rightendcolumn,rightendnumber);
string clone(temp);
return clone;
string clone(temp);
return clone;
}
}
string getRightClone() const
{
char temp[STRLENGTH];
snprintf(temp,STRLENGTH,
int getLeftTokenNumber() const
{
return lefttokennumber;
}
の部分を検出してほしい
2002/11/15
36
CCFinderで検出されるコードクローン(例2)
if(func->parameter!=NULL)
return error("Error:Parameter Exist");
curVertex->kind=ST_call;
lp=makeexptree(NULL,NULL,SIDENTIFIER,idtemp,SPROC
EDURE);
curVertex->tree=lp;
curVertex->refer=search_tree_postorder(lp);
curVertex->refer=uniq_RDlist(curVertex->refer);
cutSt();
}
break;
case 66:
#line 314 "subPascalParse.y"
{struct FUNCTION *func;
struct RD
*para;
2002/11/15
lp=makeexptree(&lp,NULL,SRPAREN,NULL,SNULL);
rp=makeexptree(NULL,&lp,SIDENTIFIER,
idtemp,SPROCEDURE);
curVertex->kind=ST_call;
curVertex->tree=rp;
curVertex->refer=search_tree_postorder(rp);
curVertex->refer=uniq_RDlist(curVertex->refer);
cutSt();
}
break;
case 67:
#line 355 "subPascalParse.y"
{yyval.exptree = yyvsp[0].exptree;}
break;
の部分を検出
CCFinderは
しかし,このコードクローンはリファクタリングの
対象にはならない
37
CCShaperの
アプローチ


コードクローンの存在する
ソースコード(Java)を構文解
析する
構文解析結果とCCFinder
の出力から意味的なまとまり
(ブロック構造)を抽出する
ソースコード
コードクローン検出(CCFinder)
絞り込み(CCShaper)
表示(Gemini)
意味的にまとまりの
あるコードクローン
2002/11/15
コードクローン
38
適用実験その1(ANTLR)(1/5)

ソースファイルは239個
総行数は約44000行

数値による結果比較

CCShaper無し
クローンペア数
クローンクラス数
CCShaperあり
388574
984
1072
148
解析時間:約2分
2002/11/15
39
適用実験その1(ANTLR)(2/5)

スキャタープロット図による比較
CCShaper無し
CCShaper有り
a
2002/11/15
40
適用実験その1(ANTLR)(3/5)

選択範囲のコードクローン
public final void mOPEN_ELEMENT_OPTION(boolean _createToken)
throws RecognitionException, CharStreamException, TokenStreamException {
int _ttype;
Token _token=null;
int _begin=text.length();
ttype = OPEN_ELEMENT_OPTION;
int _saveIndex;
match('<');
if ( _createToken && _token==null && _ttype!=Token.SKIP ) {
_token = makeToken(_ttype);
_token.setText(new String(text.getBuffer(), _begin, text.length()-_begin));
}
_returnToken = _token;
}
2002/11/15
41
適用実験その1(ANTLR)(4/5)

発見されたコードクローンの検証



異なっていたのは の部分のみ
このコードクローンが20箇所に現れていた
すべてのコードクローンは同じクラスのメソッドであった
引数を2つ増やすことで1つのメソッドにまと
めることができる
2002/11/15
42
適用実験その1(ANTLR)(5/5)

クラス図による比較
現状
ANTLRLexer
mOPEN_ELEMENT_OPTION(boolean _createToken)
mCLOSE_ELEMENT_OPTION(boolean _createToken)
mCOMMA(boolean _createToken)
mQUESTION(boolean _createToken)
mTREE_BEGIN(boolean _createToken)
mLPAREN(boolean _createToken)
mRPAREN(boolean _createToken)
mCOLON(boolean _createToken)
mSTAR(boolean _createToken)
mPLUS(boolean _createToken)
mASSIGN(boolean _createToken)
mIMPLIES(boolean _createToken)
mSEMI(boolean _createToken)
mCARET(boolean _createToken)
mBANG(boolean _createToken)
mOR(boolean _createToken)
mWILDCARD(boolean _createToken)
mRANGE(boolean _createToken)
mNOT_OP(boolean _createToken)
mRCURLY(boolean _createToken)
:
Other methods (22個)
:
2002/11/15
43
適用実験その1(ANTLR)(5/5)

クラス図による比較
リファクタリングした場合
ANTLRLexer
mTOKEN(boolean _createToken,int i,char c)
:
Other methods(22個)
:
2002/11/15
44
まとめ

コードクローン検出



ソフトウェア保守
ソースコードを調べるための手段
ツールCCFinder / Gemini / CCShaper


2002/11/15
より広範囲な意味での(Gapped)クローンを検出する
絞り込みによって,利用目的に応じたコードクローン
を提示する
45

END
2002/11/15
46