此文翻譯自Facebook博客,地址:code.fb.com/developer-t…html
譯者:LeoEatlegit
Facebook的這個Getafix確實能作到自動修復bug,不過目前來看能修復的bug很是有限,在文中詳細介紹了null pointer這種bug的解決方案,但在現實中不少bug是跟業務相關的,計算機幾乎不能理解。github
因此在譯者看來,目前這個工具只能算做一個增強版的Lint工具,而且還要依賴大量的代碼庫提交做爲機器學習的原料,纔可以作到修復一些常常出現的常規bug。文中也提到了Facebook內部的多種代碼檢查工具,這其中可以獲取到的大量代碼提交數據,是通常公司根本獲取不到的。機器學習也就無從談起。算法
不過可以將機器學習用於自動修復bug,的確是一個創新的嘗試,但願以後這類工具能改進得愈來愈實用,甚至你們都能爲之貢獻修復代碼的案例讓它學習,最後成爲通用的自動修復工具。api
Getafix
的工具,它能夠自動找到bug的解決方案而且提供給工程師讓他們去改,這能大大提升工程師的工做效率和代碼質量。Getafix
在同類工具中是第一個達到Facebook這樣規模的,而且它已應用於生產環境,它爲億萬用戶的app不斷改進穩定性和性能.Getafix
加強了Sapfix
的能力,Sapfix
是一個用於尋找bug的測試工具。一樣,Getafix
也能爲靜態工具Infer
提供解決方案。Getafix
會去學習工程師以前的代碼,因此他提供的bug fix方案易讀性很是強。Getafix
相比以前的自動糾錯工具,最大的提高點在於它可以從過去提交的代碼中尋找到一種修bug的模式,它用到了一種強大的聚類算法(譯者注:hierarchical clustering,一種機器學習算法),而且,它還會分析出bug行數的上下文,來給出一個最恰當的解決方案。對於一個已經成熟的項目來講,代碼庫都無比複雜並且常常要更新。爲了可以創造一個自動修bug的工具,咱們可讓它去學習以前的代碼提交,它就能從中學到一些套路併爲新bug提供最佳的解決方案。app
這個工具就是Getafix
,它已經被應用到Facebook的生產環境,而且正在被應用於有億萬用戶的app。它經過配合其餘兩個Facebook內部的測試工具來運做,不過理論上這個技術能夠用於任何源代碼。目前在Facebook,Infer
做爲靜態分析工具,能夠先找到bug的位置,例如在Android和Java中常見的null point錯誤,另外還有個自動測試系統,叫作SapFix
,以前已經有介紹過,也能夠發現很多bug。這篇文章會專一於Getafux
如何自動修bug,不會對如何找bug作更多的闡述。機器學習
Getafix
的目的是爲了讓計算機去處理那些常規、固定的bug。固然依然還存在一些須要工程師親自解決的複雜bug。這個工具分析數以千計的人類工程師提交的代碼,以及這些代碼的各類語境,從而發現一些隱藏的bug邏輯,修復以前的自動修復工具修不了的bug。函數
Getafix
一樣可以縮小修bug所作的代碼改動的範圍,這樣它就能快速創造一個補丁,而不須要去經過遍歷暴力破解。這種高效的實踐才得以讓它可以用於生產環境,同時,由於Getafix
會從過去的代碼中自動學習,因此它提交的代碼改動對於人類來講都是簡單易懂的。工具
對於Infer
找到的null dereference bug,Getafix能夠作到自動修復,同時,他也能經過對比新舊版本代碼來解決一些代碼質量問題。性能
目前業界中的自動修復工具主要被用來解決基本的問題,而且它們的修復方案都十分簡單直觀。好比,某個分析工具可能只會警告一些"dead exception",開發者可能會忘了在new Exception(...)
前面添加throw
。這些均可以經過lint規則解決,並不須要知道代碼的上下文。
Getafix顯然提供了一個更通用的能力,它能經過分析代碼上下文來提出解決方案。下面這個例子中,Getafix提供了一個PR來解決Infer在22行發現的bug
一個簡單的bug report,包括了Getafix生成的PR
注意這個修復不只僅依賴於ctx
,也一樣須要關注這個函數的返回類型。不像簡單的lint修復,這種修復是Infer這種工具沒法獨自完成的。
下面這個圖展現了另一個Getafix修復bug的例子。儘管這些bug都同樣(都屬於null method call),每種修復方式卻不同。注意這些修復方式跟平時開發者所作的修復幾乎沒什麼兩樣。
Getafix的工具鏈由下圖所示,在這個章節,咱們會介紹下面三種主要組件的主要功能和所遇到的挑戰。
首先一個抽象語法樹比較器會比較兩個版本的代碼。它會檢測一些常常出現的改bug的模式,好比在if
語句前添加@Nullable
或者import
的註明,或者在一個return
語句前面添加條件判斷。下面這個例子中,若是dog
是null
提早return
、public
改成private
、以及代碼的刪除都會被視爲一個有效修改(concrete edits),這類修改都會被標註出來,
語法樹比較器中一個難點是高效並準確地區分好先後兩個樹,這樣才能正確找到咱們要找的有效修改。
Getafix經過使用層次聚類(hierarchical clustering)技術,加上anti-unification——一種用來歸納不一樣表達式的方法(譯者注:能夠訪問wikipedia查看更多關於這個方法的介紹,它就可以創造一個包含了全部樹對比數據以及所隱含的修復模式集合。有了這個集合,咱們就能抽象出可能會出現的「漏洞」。
下面的這個動圖表現了分析出來的層次聚類解構樹狀圖(和以前舉的例子一致)。每一行都展示了一次修改,「修改前」的是紫色,「修改後」的是藍色,而且還包括了一些其餘數據。每一個垂直的黑色條表示了層級,最頂部的黑色條包含了全部修改模式。次層級的被包含在更小的黑條中。Anti-unification把「若是dog是null,提早返回」這樣的修改和以前的一個修改結合起來,他們惟一的區別是以前的修改是dog.drink(water)
。這樣的結果是產生了一個新的修改模式。圖中的h0
,表明了一個修改模式「漏洞」。
接下來咱們就能夠用這樣的修改模式解決相同結構的問題。當咱們繼續分析整個語法樹的時候,更多這樣的修改模式會被找出來。好比它能夠把這種修改和cat
相關的結合起來,解決動圖中更上一層的問題。
這種層級匹配確確實實地幫助Getafix發現了很多可複用的代碼改動。下面這張圖展現了一個包含了Infer報告的2288次對於null指針的修復。咱們所要尋找的bugfix模式,就隱含在這張圖表內。
其實用anti-unification去尋找可複用模型以前就有人嘗試過,可是有幾個關鍵的改進使得Getafix可以爲新bug提供有效的解決方案。
其中一個改進是咱們把代碼改動的上下文做爲學習的重要依據。好比在前面的例子中,咱們發現有兩個修改都是在dog.drink(...)
前面加上了if (dog == null) return;
,儘管dog.drink(..);
沒有發生任何改變,這句代碼依然被包含在了要先後對比的代碼中,在更高層級的改動中,dog.drink(...)
被合入了一個抽象層h0.h1()
,後面咱們會介紹一個更詳細的例子。
一個傳統的貪婪聚類算法是沒有辦法像這樣去學習上下文的。由於貪婪聚類算法只會維護每個聚類單獨的信息,沒有包括未改動的代碼。好比,若是咱們在do(list.get())
前面加上了if (list != null) return
,這類改動和前面的dog.drink()
放到了一塊兒,貪婪算法不知道要在什麼地方加上return。而Getafix的算法就會保留這些上下文,從而找到修復方案。
除了上下文,咱們還會將Infer的代碼報告與這些修改結合在一塊兒。這樣咱們就可以從相關的bug report中學習如何修復bug。Infer在報告中的"erroVar"會變成h0
。這樣咱們就可以把代碼中具體的變量名替換成h0
,從而表示一種具體修改模式。
最後一步是把bug修復好。顯然有不少種修復bug的方式。因此難點在於咱們如何去選擇一種最合適的方式去修一個bug。下面這個例子解釋了一個咱們是怎麼解決這個難題的。
例子1 假設咱們如今已經發現了前面找的這種修復:h0.h1();
-> if (h0 == null) return; h0.h1();
Getafix會經過下面步驟建立一個補丁
mListView.clearListeners();
前面找到子語法樹h0
和h1
注意這裏面的mListView.clearListeners();
,若是沒有這種未修改代碼,有可能會變成<nothing> → if (h0 == null) return;
,這可能會致使代碼被加到mListView.clearListeners();
後面,甚至是mListView = null;
後面。
這種插入的模式其實也一樣會出如今高層,好比h0.h1()
。下個例子會介紹Getafix如何處理可能插入多個位置的狀況。
例子2: 假設如今是這種模式:h0.h1() → h0!=null && h0.h1()
顯然,這種狀況也可使用if
條件語句和return
表達式解決,因此咱們固然也能夠用這種方式去替換原來的代碼。可是這樣會使得像mListView.clearListeners();
也會被匹配到,Getafix的分級策略會根據以前的數據推薦更顯著的修改方案,好比對比例子1的這種修改和if (h0.h1()) { ... } → if (h0!=null && h0.h1()) { ... }
這種修改,前者只會用於語句中而不是表達式中,那麼前者獲勝,由於它的描述更爲具體,在分級策略中得分更高。
Getafix在Facebook中被用於爲Infer找到的空指針錯誤自動提交修復,也一樣被用於解決一些比較明顯的其餘bug。
在一次測驗中,咱們對比爲了解決空指針問題Getafix提交的fix和人類工程師提交的fix,這其中包含了大概200個提交而且每一個提交改動不超過5行,結果發現,大概25%的Getafix的提交和人類的提交徹底一致。
另外一個測驗是關於Instagram的代碼庫的,包含了大概2000個null method調用問題。Getafix能夠嘗試修復大概60%的bug。其中90%的修改都經過了自動測試。整體來講,Getafix成功地修復了1077(大概53%)個null method調用問題。
除了這種測試工具發現的bug,咱們也將它應用到了以前code review中發現的bug中。結果是咱們解決了幾百個return not nullable以及field not nullable的bug。而且這些bug的解決率前者從56%提升到了62%,後者從51%提升到了59%。
Getafix也一樣能夠用於解決SapFix發現的crash問題,過去的幾個月中,Getafix已經提供了超過一半的修復方案(所有測試經過)。從整個歷史上來講,Getafix提供給SapFix的修復經過測試的成功率已經達到了80%。
Getafix已經幫助咱們達到了讓計算機處理常規bug的目的。但咱們依然不斷地改進測試和驗證工具,咱們但願能有一天Getafix能夠解決更大型的問題。
咱們也注意到不能只讓Getafix處理Infer找到的那些bug,其實它也能夠處理那些人工發現的bug,這能大大提升解決code review中發現的問題的效率。也就是說,一個曾經在代碼庫中屢次出現的錯誤,能夠將來的提交中自動修復,並不須要一我的去手動提交。
Getafix是咱們基於靜態分析工具以及大型代碼庫創造出來的智能工具。這種工具對於改進軟件開發週期、提升開發效率頗有幫助。未來,咱們在開發Getafix中得到的經驗也必定能幫助咱們創造更好的同類工具。
《IVWEB 技術週刊》 震撼上線了,關注公衆號:IVWEB社區,每週定時推送優質文章。