由Facebook開源的靜態分析工具Infer,現已支持使用RacerD檢測Java代碼中競爭條件。RacerD使用鎖機制或@ThreadSafe注解,識別類中各方法間的競爭條件。
去年,Facebook就已在生產代碼中使用了RacerD,并在代碼提交生產前檢測到了一千多處的多線程問題?,F在,如果Java開發人員使用Infer去檢測Java代碼中的軟件缺陷,同樣可以使用RacerD的并發檢測能力。
競爭條件是一種并發錯誤或軟件缺陷。如果兩個訪問同一對象的線程(其中至少有一個需要做寫操作)間沒有做適當的同步操作,這時就會引發競爭條件,進而導致線程的執行存在彼此重疊。并發問題難以調試,更難以在發生問題后重現現場。
RacerD可以大規??焖俨l地執行一些有用的分析。RacerD之所以可以做快速分析,原因在于它在檢測并發問題時并沒有非力圖去檢查整個代碼庫,而是僅檢查那些它認為是并發運行的代碼。
RacerD檢查的類、方法和接口定義中可并發運行的代碼。這些代碼或者是使用@ThreadSafe注解的、或者是根據關鍵字synchronized所創建鎖而識別的。如果一個類或結構使用了@ThreadSafe注解,那么RacerD也會評估該類或實現的所有子類。為增加代碼覆蓋,RacerD還額外添加了一些有用的注解,包括@ThreadConfined、@Functional、@ReturnsOwnership和@VisibleForTesting。
啟動RacerD分析,需要在命令行調用命令infer。該命令可與其它Infer分析一并運行,也可以與只允許RacerD運行的infer --racerd-only命令一并運行。例如,輸入命令infer --racerd-only -- javac StockPortfolio.java,將會對StockPortfolio.java運行RacerD。
下面給出一個例子代碼。RacerD在檢查該例子代碼時,會對其中的競爭條件給出警告。
@ThreadSafepublic class StockPortfolio { int shares = 0; public void buy(int count) { if (count > 0) { shares += count; } } public int sell(int count){ if (count >= 0 && shares - count >= 0) { shares -= count; return shares; } else { return 0; } }}RacerD會發現上面代碼中的軟件缺陷:
Read/Write race. Public method int StockPortfolio.sell(int) reads from field StockPortfolio.shares. Potentially races with writes in methods void StockPortfolio.buy(int), int StockPortfolio.sell(int)可以看到,RacerD對代碼中包含有未保護寫、讀寫競爭等給出了警告。當前RacerD具有局限性,它只檢測數據競爭情況,并不檢測其它一些并發問題,例如死鎖或原子性。在下面一些情況下,RacerD會漏掉其中的數據競爭問題:
別名(aliasing); 本地定義對象溢出了范圍; 使用不同的鎖訪問受保護對象; 本地對象包含有非屬主對象; 使用了弱引用內存,以及Java的volatile關鍵字。RacerD的這些局限性,源自于其設計目標針對的是降低誤報率,即便會導致一些漏報。
RacerD的共同作者Sam Blackshear和Peter O'Hearn在一份聲明中指出:
Infer當前已在Facebook使用,一種方式是批處理部署,另一種方式是作為參與代碼審核的機器人。部署用于代碼審核的Infer,是作為Facebook持續集成系統的一部分運行。對于開發人員提交的每次代碼更改,持續集成將Infer與其它一些編譯和測試任務一并運行。
RacerD的代碼開源提供在GitHub上。更多細節,可參見用戶指南。
查看英文原文: Facebook Open-Sources RacerD - Java Race Condition Detector