策劃編輯 | Natalie
做者 | JOHANNES BADER 等
譯者 | 核子可樂
編輯 | Vincent算法
AI 前線導讀:Facebook 開發了一款名爲 Getafix 的工具,能夠自動查找出 bug 的修復方案,並提供給工程師審批,這極大提升了工程師的工做效率和總體代碼質量。Getafix 不只可以利用強大的聚類算法,分析問題代碼的上下文找到更合適的修復方案,並且給出的方案對於人類工程師來講很容易理解。Getafix 是第一款被大規模部署到 Facebook 生產環境中的自動修復工具,它進步提高了 Facebook 擁有數十億用戶的應用程序的穩定性和性能。a小程序
現代的生產環境代碼庫很是複雜,而且一直持續不斷地更新。爲了建立一個能夠自動查找 bug 修復方案的系統——在沒有工程師幫助的狀況下——咱們構建了一個工具,能夠從工程師以前對代碼庫的更改中學習如何修復 bug。它找到了一些隱藏的模式,並用這些模式來識別最有可能修復新 bug 的補救措施。api
這個工具叫做 Getafix,已經被部署到 Facebook 的生產環境中,進一步提高數十億人使用的應用程序的穩定性。Getafix 通常與 Facebook 其餘兩個工具結合使用,不過這項技術也能夠用於其餘地方。它目前可以爲 Infer 發現的 bug 提供修復建議,Infer 是咱們的靜態分析工具,可識別 Android 和 Java 代碼中的 null 指針異常等問題。它還經過 SapFix 提供修復建議——針對咱們的智能自動化測試系統 Sapienz 檢測到的 bug。如今,咱們將深刻了解 Getafix 是如何學習修復 bug(指 任意代碼問題,而不只僅是致使應用程序崩潰的問題)的。網絡
Getafix 的目標是讓計算機處理平常工做,不過是在人類的監督之下,由於一個 bug 是否須要複雜的修復仍然須要由人類作出決定。這個工具將一種新的層次聚類方法應用於以前的數千個代碼變動上,同時檢查代碼變動自己及其上下文。它能夠檢測 bug 的基礎模式,並提供以前的自動修復工具沒法檢測到的修復方案。框架
Getafix 還可以在 bug 修復過程中,顯著縮小程序當中可能須要更改的具體空間,從而更快地選擇適當的修復手段 ; 此外,其再也不像以往暴力破解及基於邏輯型技術那樣對計算時間提出極高的要求。這種更爲高效的方法使得 Getafix 被成功部署至生產環境當中。與此同時,因爲 Getafix 可以以以往代碼變化爲基礎進行學習,所以足以產生讓人類工程師更容易理解的修復結論。工具
Getafix 目前已經在 Facebook 生產環境中部署完成,負責自動對 Infer 報告提供 null 解引用 bug 進行修復,同時亦可爲 Sapienz 標記的與 null 解引用相關的崩潰錯誤提供修復建議。此外,Getafix 還被用於解決在較新版本 Infer 從新訪問現有代碼時所發現的代碼質量問題。佈局
在目前的行業實踐當中,自動修復功能主要用於各種基礎性問題,而代碼修復則更爲簡單。舉例來講,分析器可能會提出「致命異常」警告,強調開發人員可能忘記在新的 Exception(…) 以前添加一個 throw。自動修復工具可以直接完成調整,而具體調整方式則可經過 lint 規則進行定義——換言之,其並不須要瞭解操做應用的特定情景。性能
Getafix 則徹底不一樣,它提供更多通用性功能,並可結合上下文相關因素來解決問題。在如下代碼示例當中,對應第 22 行中的 Infer 錯誤,Getafix 給出了下列修復結論:學習
須要注意的是,此修復方法不只取決於變量 ctx,同時也與方法的返回類型相關。與簡單的 lint 修復方法不一樣,此類修復程序沒法被歸入 Infer 自己。測試
下圖所示爲 Getafix 爲 Infer bug 提供的修復方法 ; 儘管來自 Infer 的 bug 老是相同的(null 方法調用,有可能引起 NullPointerException 風險),但每一項具體修復操做仍然獨一無二。另外須要強調一點,Getafix 的修復方法與人類開發者的常見操做徹底一致。
Getafix 的組織形式以下圖內工具鏈所示。在本節中,咱們將描述 Getafix 的三大主要組件及其各自的功能與挑戰。
基於抽象語法樹的 Differencer 首先負責在兩個源文件之間識別實際的編輯痕跡,例如針對同一文件的連續修訂。舉例來講,它會檢測如下粒度的編輯:使用 if 打包語句、添加的 @Nullableannotation 或者 import,以及將條件提早返回至某一現有方法以內等等。在如下示例中,插入條件判斷語句 if dog is null 並提早返回、將 public 從新命名爲 private、方法的移動都會被檢測爲實際編輯。而基於行的 diffing 工具只會將方法標記爲徹底移除與插入,Tree Differencer 則可以檢測到這一移動並將移動方法以內的插入操做視爲實際編輯。
Tree Differencer 的主要挑戰在於如何有效且精確地對樹級別中的「以前」與「以後」部分進行對齊,從而識別出正確的實際編輯及其映射關係。
Getafix 經過利用新的層次聚類技術以及反合一方法(即一種可以在不一樣符號表達式之間實現泛化的現有方法)進行模式挖掘。在此以後,它會創建可能相關的樹差別集合,進而選擇該集合中最爲常見的程序並轉換爲修復模式。這些模式多是抽象的,且包含程序轉換所面向的不一樣「漏洞」。
如下示例圖像展現了一組層次結構,即樹狀圖,其經過一組編輯生成。(在本示例中,咱們直接採用上個示例中的編輯結果。)每一行皆展現出一種編輯模式——其中紫色表明「以前」,藍色表明「以後」——以及一些元數據。每一個垂直黑條對應於層次結構中的具體層級,其中黑條頂部的編輯模式表明着經過對該結構中全部同一層級的其它編輯進行反合一所得到的模式。其它編輯由較細的黑色線條鏈接。反合一未來自上一示例中的「若是 dog 爲 null 則提早返回」條件與另外一條編輯相結合——後者的惟一區別在於「dog 正在飲水」。結果是,其將生成一個表明共性的抽象修復模式。由反合一引入的符號 h0 表明着能夠基於上下文實現實例化的「漏洞」。
接下來,該編輯模式能夠與其它變量名稱更爲多樣但仍然具備相同總體結構的編輯模式相結合。在根據梳理樹狀脈絡時,整個流程將產生愈來愈抽象的編輯模式。舉例來講,其可以將此編輯與同貓相關的編輯組合在一塊兒,從而得到位於圖表上方位置的抽象編輯。
更值得強調的是,這種分層匹配流程爲 Getafix 提供一套強大的框架,足以在代碼變動中發現各種可複用模式。如下圖片所示,將總計 2288 項用於修復咱們代碼庫內 Infer 報告 null 指針錯誤的編輯彙總爲一套樹狀圖(橫向佈局,小型化)。咱們但願挖掘的修復模式,無疑正隱藏在這份樹狀圖內。
基於反合一方法的模式挖掘並不是什麼新鮮事物,但要想以儘量少的修復操做解決新 bug,咱們還須要對挖掘得出的模式結果作進一步強化。
其中的變化之一就是引入一部分周邊代碼,即編輯結果當中沒有變動的部分。如此一來,咱們不只可以發現人們在變動中採起的模式,同時也能發現應用變動時上下文中存在的某些模式。舉例來講,在上面的第一份樹狀圖中,咱們注意到有兩項不一樣的編輯會在 dog.drink(…); 以前添加 if(dog==null)return。儘管 dog.drink(…); 沒有變動,但其應被做爲模式「以前」與「以後」部分的上下文信息進行考量,從而幫助咱們理解這項修復的應用情景。從更高的編輯層級上考慮,dog.drink() 這一上下文與其它上下文合併成爲了抽象的上下文 h0.h1(),用以限制模式的適用位置。在下一節中,咱們將介紹另外一個更具現實意義的示例。
根據以往的自動修復工具文獻所述,貪婪聚類算法每每不太可能學習到上述狀況。這是由於貪婪聚類算法傾向於維持各個聚類的單一表示,所以若是上下文不存在於訓練數據的所有編輯當中,則該算法將不會引入該上下文。例如,若是某項編輯會在 do(list.get()); 與以上示例中提到的 dog.drink() 合併時插入 if (list != null) return,那麼貪婪聚類算法會丟棄所有關於提早返回具體插入位置的上下文。與此相反,Getafix 的分層聚類方法則儘量在各層級上保留上下文,從而確保總體結構的通用性水平。在某種程度上講,雖然咱們但願學習的某些常規上下文可能丟失,但其仍將存在於結構當中的某些底層位置。
除了周邊代碼以外,咱們還將編輯與提示這些編輯的 Infer bug 報告關聯起來,從而瞭解編輯模式與對應的 bug 報告之間的映射關係。在前文第一份樹狀圖中,能夠看到 Infer 在 bug 報告中將「errorVar」視爲 bug 來源變量,並在進行反合一以後給出漏洞 h0。以此爲基礎,咱們接下來便可在發佈新的 Infer bug 報告時將須要關注的變量修改成 h0,從而使得整個修復模式更爲具體。
最後一步,咱們須要考慮如何獲取存在 bug 的源代碼並從挖掘到的結論中生成修復模式,從而針對源代碼生成修復補丁。在這方面,咱們每每擁有多種修復模式能夠選擇(如前文樹狀圖所示)。所以,接下來的挑戰就是如何選擇正確的模式以修復特定 bug。若是該模式適用於多個位置,Getafix 還須要選擇出正確的匹配項目。如下示例說明了咱們採用的常規方法以及如何在 Getafix 當中切實解決這項挑戰。
示例 1:考慮咱們以前挖掘到的模式: h0.h1(); → if (h0 == null) return; h0.h1();
下面,咱們將簡要介紹如何爲徹底陌生的代碼生成如下補丁。
Getafix 經過如下步驟建立補丁:
找到與「以前」部分匹配的 sub-AST: mListView.clearListeners();
對漏洞 h0 與 h1 進行實例化
利用實例化以後的部分替換 sub-AST
請注意,以後部分中的 h0 是綁定的,由於其中包含了未修改的上下文 h0.h1();,這將有助於限制模式適用的位置數量。若是不修改上下文,則該模式將爲→ if (h0 == null) return;。很明顯,這種模式將適用於衆多與預期無關的位置,例如 mListView.clearListeners(); 以後、甚至是 mListView = null; 以後。
實際上,僅插入模式也有可能出如今樹狀圖中的某些較高位置,其中具備 h0.h1(); 這一上下文的模式已經經過負責向另外一不一樣語句以前插入 return 的模式完成了反合一。如下示例說明了 Getafix 如何處理這類模式適用範圍過廣的狀況。
示例 2:請考慮如下模式: h0.h1() → h0!=null && h0.h1()
一般狀況下,此補丁應該來自對 if 條件或者 return 表達式的修復模式,所以咱們固然但願其適用於這類上下文。但其同時也適用於其它一些狀況,例如以上示例當中提到的調用語句:mListView.clearListeners();。Getafix 的排名策略會嘗試對模式的修復效能作出估算,併爲其分配最可能實現修復效果的上下文。這項策略使得該系統可以在以後的運行當中再也不依賴於驗證步驟,從而顯著下降計算時間。
以上模式將與其它模式競爭,例如更爲具體的 if (h0.h1()) { ... } → if (h0!=null && h0.h1()) { ... }或者示例 1 中僅適用於調用語句而非表達式的模式。因爲具體程度更高的模式每每擁有更少的匹配位置數量,所以 Getafix 會將其視爲更適合當前狀況的解決方案併爲其分配更高的排名。
Getafix 現已部署在 Facebook 的生產環境中,負責爲 Infer 報告的 null 解引用 bug 提供自動修復建議。順帶一提,Infer 是咱們的一款統計分析工具,負責爲 Sapienz 發現的、與 null 解引用相關的崩潰 bug 提供修復建議。此外,Getafix 還負責解決 Infer 以往提出的某些重要 bug。
在一次實驗當中,咱們將 Getafix 計算出的修復建議與以往人工編寫的修復方法進行了比較,咱們發現,在對大約包含 200 項小型編輯的數據集內各類 Infer null 方法調用 bug 進行修復時,須要修改的內容不足 5 行。此外,在大約四分之一的案例當中,Getafix 提出的排名最高修復補丁與人工建立的補丁徹底匹配。
在另外一項實驗中,咱們着眼於 Instagram 代碼庫中的一套子集,並嘗試批量修復其中存在的約 2000 個 null 方法調用問題。Getafix 可以在大約六成 bug 中嘗試使用某個補丁,且其中 90% 的嘗試都經過了自動驗證——這意味着其可編譯且 Infer 將再也不發出警告。整體來說,Getafix 成功以自動方式修復了 1077 條(佔比約 53%)的 null 方法調用錯誤。
除了針對新 Infer bug 提供修復建議以外,咱們還利用相同的方式清理在原先代碼審查中積壓的舊有 Infer bug。咱們已經清理了數百個返回不可爲空的 Infer bug 以及字段不可爲空的 Infer bug。有趣的是,在這項工做完成以後,Getafix 在自動修復建議中開始愈來愈擅長處理返回不可爲空以及字段不可爲空類問題,兩者的成功修復佔比分別由 56% 與 51% 增加至 62% 與 59%。整體而言,在過去三個月中,Getafix 提供的一系列建議幫助咱們成功修復了數百項額外 bug。
Getafix 還爲 SapFix 生成了修復建議,用以處理 Sapienz 檢測到的崩潰問題。過去幾個月以來,SapFix 所採用的修復方法中有約半數來自 Getafix 且實際有效(經過所有測試)。而在 Getafix 提供給 SapFix 的所有修復建議中,約 80% 經過了所有測試。
Getafix 幫助咱們實現了讓計算機處理常規 bug 修復工做這一重大目標。隨着咱們對自身測試及驗證工具的不斷完善,預計 Getafix 將可以在將來更好地防止各種部署後故障問題。
咱們還注意到,Getafix 所挖掘出的修復模式不只僅是在響應 Infer 報告的 bug; 實際上,其同時也可以針對手動代碼檢查結果給出修復建議。這種額外的修復模式源將給自動重複代碼審查帶來使人興奮的可能性。換句話說,將來咱們有可能會將代碼庫中曾被屢次標記及修復的 bug 直接交給自動化工具處理,而再也不須要任何人工篩查。
Getafix 是咱們構建大型代碼語料庫以及相關元數據統計分析智能化工具這一總體性舉措中的組成部分。此類工具的出現,有望改善軟件開發生命週期中的各個層面,包括代碼發現、代碼質量與執行效率等等。咱們從 Getafix 當中得到的寶貴看法,也將幫助咱們在這一領域構建並部署更多其它與之相似的重要工具。
聲明:推送內容及圖片來源於網絡,部份內容會有所改動,版權歸原做者全部,如來源信息有誤或侵犯權益,請聯繫咱們刪除或受權事宜。