String
型と int
型は「一般的な型」であり、
さまざまな用途に使われている。
String
型: パス名, URL, ホスト名, SQL文
int
: ポート番号, (GUI上の)X座標,Y座標,幅,高さ, 年, 月, 日
path
、座標には
x
, y
などの識別子が使われていることがわかった。
よく「悪いコード」として primitive obsession と呼ばれる現象がある:
String username = getCurrentUserName();
String path = "/home/"+username+"/user.cfg";
// Unsafe path: extra check is needed!
File config = new File(path);
まともな (しかし面倒くさい) コード:
User user = getCurrentUser();
Path path = Paths.get(user.getHomeDirectory(), "user.cfg");
// Path is guaranteed to be safe.
File config = new File(path);
Primitive obsession はリスキーだが多くのプログラマが使っている。
プログラマは APIを呼び出すとき、その引数の用途 (c-type) を知っているはず:
String x = "foo/bar.txt";
var f = new java.io.File(x); // x はパス名
したがってAPI呼び出しとその引数を調べれば、
プログラマが特定の概念をどのように表現しているかがわかるはずだ。
いくつかの Java オープンソースプロジェクトを調査し、 以下の 12 の c-type を定めることにした:
C-Type | 言語の型 | 説明 | メソッド数 |
---|---|---|---|
PATH | String | パス名 | 14 |
URL | String | URL/URI | 4 |
SQL | String | SQL文 | 10 |
HOST | String | ホスト名 | 17 |
PORT | int | ポート番号 | 25 |
XCOORD | int | X座標 (GUI用) | 25 |
YCOORD | int | Y座標 (GUI用) | 25 |
WIDTH | int | 幅 (GUI用) | 24 |
HEIGHT | int | 高さ (GUI用) | 24 |
YEAR | int | 年 | 18 |
MONTH | int | 月 | 14 |
DAY | int | 日 | 18 |
これを選んだ基準は以下のとおりである:
つぎに Java Standard API を調べて、 これらの c-type を含む計218メソッド (重複あり) を列挙した:
new java.io.File(PATH)
new java.net.URI(URL)
java.sql.Statement.execute(SQL)
java.net.InetAddress.getByName(HOST)
new java.net.Socket(HOST, PORT)
new java.awt.Point(XCOORD, YCOORD)
new java.awt.Dimension(WIDTH, HEIGHT)
new java.util.Date(YEAR, MONTH, DAY)
java.util.Date.setYear(YEAR)
java.time.LocalDate.of(YEAR, MONTH, DAY)
実験は以下の手順でおこなった:
zookeeper/.../TxnLogToolkit.java:
File file = new File(dir.getPath() + File.separator + Util.makeLogName(zxid)[PATH]);
...
jitsi/.../SIPCommSplitPaneDivider.java: rightButton.setBounds((insets.left * 2) + leftSize.width + rightSize.width[XCOORD], y[YCOORD], rightSize.width[WIDTH], rightSize.height[HEIGHT]);
使用したプロジェクトとLoCは以下の通りである:
Project | Description | LoC |
---|---|---|
hadoop 3.3.1 | distributed computation | 1,789k |
ghidra 10.0 | binary analyzer | 1,588k |
ignite 2.10.0 | distributed database | 1,165k |
jetty 11.0.5 | web container | 441k |
kafka 2.7.1 | stream processing | 384k |
tomcat 8.5.68 | web server | 349k |
jitsi 2.10 | video conference | 327k |
binnavi 6.1.0 | binary analyzer | 309k |
netty 4.1.65 | network library | 303k |
libgdx 1.10.0 | game framework | 272k |
alluxio 2.5.0-3 | data orchestration | 228k |
plantuml 1.2021.7 | UML generator | 210k |
grpc 1.38.1 | RPC framework | 195k |
jenkins 2.299 | automation | 177k |
jmeter 5.4.1 | network analyzer | 145k |
jedit 5.6.0 | text editor | 125k |
gephi 0.9.2 | graph visualizer | 120k |
zookeeper 3.7.0 | distributed computation | 114k |
selenium 3.141.59 | browser automation | 91k |
okhttp 4.9.1 | HTTP client | 36k |
jhotdraw 7.0.6 | graph drawing | 32k |
arduino 1.8.15 | development environment | 27k |
gson 2.8.7 | serialization framework | 25k |
websocket 1.5.2 | network framework | 15k |
picasso 2.8 | image processing | 9k |
jpacman | action game | 3k |
Total | 8,480k |
各プロジェクト・c-typeごとの抽出された式の数は以下のとおりである。 出現する式の数は、プロジェクトの大きさにほぼ比例する。 しかし c-typeの種類はプロジェクトによって偏っている。
Project | LoC | PATH | URL | SQL | HOST | PORT | XCOORD | YCOORD | WIDTH | HEIGHT | YEAR | MONTH | DAY | OTHER | All |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
alluxio | 228k | 72 | 12 | 0 | 22 | 26 | 0 | 0 | 0 | 0 | 15 | 15 | 15 | 51 | 228 |
arduino | 27k | 39 | 5 | 0 | 12 | 9 | 6 | 6 | 42 | 42 | 0 | 0 | 0 | 1 | 162 |
binnavi | 309k | 42 | 7 | 1 | 2 | 1 | 23 | 23 | 67 | 67 | 1 | 0 | 0 | 1 | 235 |
gephi | 120k | 5 | 0 | 2 | 0 | 0 | 28 | 28 | 66 | 66 | 0 | 0 | 0 | 0 | 195 |
ghidra | 1,588k | 369 | 25 | 0 | 10 | 8 | 320 | 320 | 511 | 511 | 13 | 13 | 13 | 13 | 2,126 |
grpc | 195k | 33 | 16 | 0 | 68 | 71 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 75 | 263 |
gson | 25k | 0 | 4 | 0 | 2 | 0 | 0 | 0 | 0 | 0 | 6 | 6 | 6 | 7 | 31 |
hadoop | 1,789k | 978 | 634 | 9 | 288 | 259 | 0 | 0 | 0 | 0 | 2 | 2 | 2 | 124 | 2,298 |
ignite | 1,165k | 168 | 85 | 666 | 106 | 111 | 0 | 0 | 0 | 0 | 12 | 12 | 12 | 101 | 1,273 |
jedit | 125k | 130 | 19 | 0 | 3 | 3 | 50 | 50 | 112 | 112 | 0 | 0 | 0 | 3 | 482 |
jenkins | 117k | 82 | 28 | 0 | 6 | 6 | 1 | 1 | 1 | 1 | 102 | 102 | 102 | 237 | 669 |
jetty | 441k | 72 | 216 | 9 | 163 | 104 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 37 | 601 |
jhotdraw | 32k | 6 | 5 | 0 | 1 | 1 | 96 | 96 | 50 | 50 | 0 | 0 | 0 | 1 | 306 |
jitsi | 327k | 22 | 18 | 1 | 8 | 31 | 78 | 78 | 234 | 234 | 0 | 0 | 0 | 30 | 734 |
jmeter | 145k | 112 | 62 | 2 | 31 | 28 | 7 | 7 | 62 | 62 | 0 | 0 | 0 | 28 | 401 |
jpacman | 3k | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 2 |
kafka | 384k | 37 | 1 | 0 | 85 | 72 | 0 | 0 | 0 | 0 | 14 | 14 | 14 | 44 | 281 |
libgdx | 272k | 83 | 7 | 0 | 4 | 5 | 7 | 7 | 36 | 36 | 0 | 0 | 0 | 0 | 185 |
netty | 303k | 38 | 24 | 0 | 54 | 130 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 117 | 366 |
okhttp | 36k | 2 | 24 | 0 | 6 | 7 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 3 | 42 |
picasso | 9k | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
plantuml | 210k | 29 | 4 | 0 | 5 | 11 | 4 | 4 | 11 | 11 | 2 | 2 | 2 | 2 | 87 |
selenium | 91k | 44 | 66 | 0 | 15 | 12 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 21 | 163 |
tomcat | 349k | 207 | 64 | 22 | 38 | 52 | 0 | 0 | 10 | 10 | 0 | 0 | 0 | 47 | 450 |
websocket | 15k | 9 | 44 | 0 | 5 | 43 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 1 | 104 |
zookeeper | 114k | 88 | 4 | 0 | 126 | 192 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 47 | 459 |
Total | 8,480k | 2,668 | 1,374 | 712 | 1,060 | 1,182 | 621 | 621 | 1,205 | 1,205 | 169 | 168 | 168 | 991 | 12,144 |
以下は抽出した式に含まれていた PATH, URL, XCOORD, WIDTH 表現の抜粋である:
PATH | Top Expressions |
---|---|
alluxio | path, mLocalUfsPath+ufsBase, base |
arduino | path, PreferencesData.get("runtime.ide.path") |
binnavi | filename, directory, pathname |
gephi | System.getProperty("netbeans.user") |
ghidra | getTestDirectoryPath(), path, filename |
grpc | uri.getPath() |
hadoop | GenericTestUtils.getRandomizedTempPath() |
ignite | path, U.defaultWorkDirectory(), fileName |
jedit | path, dir, directory |
jenkins | System.getProperty("user.home"), war |
jetty | file.getParent() |
jhotdraw | prefs.get("projectFile", home) |
jitsi | path, localPath |
jmeter | filename, path, file |
kafka | storeDirectoryPath, argument |
libgdx | name, sourcePath, imagePath.replace('\textbackslash\textbackslash','/') |
netty | getClass().getResource("test.crt").getFile() |
plantuml | filename, newName |
selenium | System.getProperty("java.io.tmpdir"), logName |
tomcat | pathname, path, docBase |
zookeeper | path, KerberosTestUtils.getKeytabFile() |
URL | Top Expressions |
---|---|
alluxio | journalDirectory, folder, inputDir |
arduino | contribution.getUrl(), packageIndexURLString |
binnavi | url, urlString |
ghidra | ref, getAbsolutePath(), url.toExternalForm() |
grpc | target, TARGET, oobTarget |
gson | nextString, urlValue, uriValue |
hadoop | uri, url, s |
ignite | GridTestProperties.getProperty("p2p.uri.cls") |
jedit | path, str, fileIcon |
jenkins | url, site.getData().core.url, plugin.url |
jetty | uri, inputUrl.toString(), s |
jitsi | url, imagePath, sourceString |
jmeter | url, LOCAL_HOST, requestPath |
kafka | config.getString(METRICS_URL_CONFIG) |
libgdx | url, URI, httpRequest.getUrl()+queryString |
netty | URL, request.uri(), server |
selenium | url, baseUrl, (String)raw.get("uri") |
tomcat | url, location, path |
websocket | uriField.getText(), uriinput.getText() |
zookeeper | urlStr |
XCOORD | Top Expressions |
---|---|
arduino | noLeft, cancelLeft |
binnavi | x, m_x |
gephi | currentMouseX, x, bounds.x |
ghidra | x, center.x+deltaX, filterPanelBounds.x |
jedit | x, event.getX(), leftButtonWidth+leftWidth |
jhotdraw | evt.getX(), x, e.getX() |
jitsi | x, button.getX(), dx |
jmeter | graphPanel.getLocation().x, cellRect.x, x |
libgdx | upButtonX, getWidth()-buttonSize.width-5, x |
plantuml | e.getX() |
WIDTH | Top Expressions |
---|---|
arduino | width, imageW, Preferences.BUTTON_WIDTH |
binnavi | COLORPANEL_WIDTH, TEXTFIELD_WIDTH, width |
gephi | w, constraintWidth, DEPTH |
ghidra | width, center.width, filterPanelBounds.width |
jedit | width, buttonSize.width, colWidth |
jhotdraw | frameWidth, r.width, bounds.width |
jitsi | MAX_MSG_PANE_WIDTH, WIDTH, width |
jmeter | graphPanel.width |
libgdx | width, buttonSize.width |
plantuml | newWidth |
tomcat | WIDTH |
各c-typeにおける、複雑な式と演算子の組合せ例は以下のとおりである:
C-Type | Expressions |
---|---|
PATH | mLocalUfsPath + ufsBase selectedFile.getAbsolutePath() + PREFERENCES_FILE_EXTENSION dir.getPath() + DIR_FAILURE_SUFFIX U.defaultWorkDirectory() + separatorChar + DEFAULT_TARGET_FOLDER + separatorChar |
URL | url.toExternalForm().substring(GhidraURL.PROTOCOL.length() + 1) str + KMSRESTConstants.SERVICE_VERSION + "/" newOrigin(getScheme(),getHost(),getPort()).asString() + path base + configFile |
XCOORD | center.x + center.width leftButtonWidth + leftWidth evt.getX() - getInsets().left prefs.getInt(name+".x", 0) |
WIDTH | Math.max(contentWidth, menuWidth) + insets.left + insets.right TITLE_X_OFFSET + titlePreferredSize.width width + insets.left + insets.right + 2 (int)(bounds.getWidth() * percent) |
抽出した式の長さは以下のとおりである (nは式中の項の数を表す):
C-Type | n=1 | n=2 | n=3 | n=4 | n=5 | n=6 | n≥7 |
---|---|---|---|---|---|---|---|
PATH | 49.6% | 22.8% | 7.0% | 6.3% | 4.8% | 2.2% | 7.3% |
URL | 31.6% | 18.5% | 13.7% | 13.5% | 10.2% | 5.8% | 6.8% |
SQL | 47.5% | 12.1% | 8.7% | 3.1% | 4.4% | 5.5% | 18.8% |
HOST | 59.2% | 11.5% | 3.0% | 22.1% | 2.0% | 0.1% | 2.1% |
PORT | 68.4% | 27.5% | 2.1% | 1.2% | 0.3% | 0.4% | 0.0% |
XCOORD | 54.1% | 24.6% | 9.8% | 6.4% | 1.6% | 2.1% | 1.3% |
YCOORD | 52.5% | 22.4% | 10.1% | 9.0% | 2.6% | 1.9% | 1.4% |
WIDTH | 71.0% | 15.2% | 6.3% | 2.5% | 1.5% | 1.8% | 1.7% |
HEIGHT | 71.4% | 15.4% | 6.4% | 3.1% | 1.5% | 1.3% | 1.0% |
YEAR | 96.4% | 2.4% | 1.2% | 0.0% | 0.0% | 0.0% | 0.0% |
MONTH | 79.8% | 19.6% | 0.6% | 0.0% | 0.0% | 0.0% | 0.0% |
DAY | 99.4% | 0.0% | 0.6% | 0.0% | 0.0% | 0.0% | 0.0% |
次に、式の表現のみから c-type を推論できるかどうかを考えてみる。 今のところ、我々は c-type の判定は API 呼び出しに頼っている。 もしこれができれば API呼び出し以外の部分の c-type も判定することができる。
基本的な戦略は以下のとおりである:
機械学習アルゴリズムとして決定木を使ったのは、 記号的な特徴に向いており、かつ、できあがった規則が人間に読みやすいためである。
まず、式に出てきた識別子のうち、頻出の単語を取り出すと 以下のようになる:
C-Type | Top words (# Projects) |
---|---|
PATH | get (21), path (21), file (20) |
URL | url (19), get (18), string (18) |
SQL | get (6), query (5), create (3) |
HOST | host (21), get (17), address (17) |
PORT | port (22), get (18), local (10) |
XCOORD | width (9), x (9), get (9) |
YCOORD | height (9), y (9), get (8) |
WIDTH | width (13), get (11), size (10) |
HEIGHT | height (12), get (11), size (10) |
YEAR | year (4), get (2), int (2) |
MONTH | january (3), month (3), december (3) |
DAY | day (3), int (2), parse (2) |
本手法では、式の特徴を表現するのにデータフロー図を使った。
式を構文解析して項どうしの依存関係を抽出する。
たとえば new File(config.getPath(i))
のデータフロー図は
以下のようになる:
new File(config.getPath(i))
のデータフロー図
各式に対して、データフローから "primary identifier" および "secondary identifier(s)" を 特徴量として使用して学習する。 以下のような規則を使うと、以下の素性が抽出できる:
getPath()
config
, i
Expression | Dependency |
---|---|
# (constant) | # |
A (variable access) | A |
A.B (field access) | A → B |
B(A) (method call) | A → B() |
A.B() (instance method call) | A → B() |
op A (applying a unary operator) | A → op |
A op B (applying a binary operator) | A → op, B → op |
B = A (assignment) | A → B |
これらの素性を使って、各ctypeを判別可能かどうか実験した。 ここでは評価にプロジェクトごとの leave-one-project-out の クロスバリデーションを使っている:
以下のような結果が得られた:
C-Type | Precision | Recall | F-score |
---|---|---|---|
PATH | 68.9% | 91.8% | 78.8% |
URL | 61.3% | 53.0% | 56.8% |
SQL | 70.4% | 80.6% | 75.2% |
HOST | 70.0% | 73.8% | 71.8% |
PORT | 84.6% | 87.5% | 86.0% |
XCOORD | 95.7% | 82.1% | 88.3% |
YCOORD | 97.5% | 79.4% | 87.5% |
WIDTH | 92.0% | 92.5% | 92.2% |
HEIGHT | 90.4% | 93.4% | 91.9% |
YEAR | 100.0% | 83.7% | 91.1% |
MONTH | 100.0% | 77.0% | 87.0% |
DAY | 100.0% | 61.1% | 75.9% |
Average | 85.9% | 79.6% | 82.7% |
URL c-type は性能が悪い。 その理由は、URLの式は
などのように長くなる傾向があり、 さらに内部で Hostname や port など他の c-type と混同するような 項が多く表れるためであった。"https://"+getHostName()+":"+getPort()+"/"+getPath()
本研究では、Java standard API で使われている 12 の c-type を定義し、 それらが 26個のオープンソースプロジェクトでどのように使われているかを分析した。 ソースコード中の API 呼び出しを収集してそれらの傾向を調べた。 その後、データフロー解析と決定木を組み合わせた c-type 判定手法をテストした。
Research Question ふたたび: