FindBugs的檢測器大多以下面五種方式來實現,且這五種實現方式findbugs都提供了接口:
- 檢查類、方法、字段結構
- 微模式,簡單的字節碼模式
- 基于棧的模式
- 數據流分析
- 內部過程的分析
本文將介紹findbugs中stack-based pattern的實現過程和需要用到的接口。在這之前,我們必須要有一些必備的java知識,如JVM棧內存、JVM字節碼指令、 class文件結構 。
?
字節碼指令
JVM為每一個線程都分配一個java棧,且棧以棧幀的形式進行管理,每調用一個方法都向棧中添加一個棧幀,棧幀由局部變量區、操作數區、和幀數據區組成。看一個簡單的字節碼指令示例:
Code:
0:
new
#28
//
class java/math/BigDecimal
3
: dup
4: ldc2_w #30
//
double 0.11d
7: invokespecial #32
//
Method java/math/BigDecimal."<init>":(D)V
10
: astore_1
11:
return
|
public
static
void
main(String[] args) {
BigDecimal a
=
new
BigDecimal(0.11
);
}
|
main方法中僅有一條語句,它創建了一個BigDecimal類實例,并把它賦值給本地變量a。左邊方框是main函數的字節碼指令,我們來看看這些指令對main方法棧幀的操作。(new)創建BigDecimal對象,并向棧頂壓入引用值;(dup)復制棧頂引用,壓棧;(ldc2_w)從常量池中將0.11d推送到棧頂;(invokespecial)調用構造函數,并彈出對象引用和參數,調用結束將對象引用壓棧;(astore_1)彈出對象引用,并存儲在main函數的局部變量1位置(0位置為main方法參數);(return)返回。了解JVM的棧結構和字節碼指令對棧的操作,這樣我們才能使用stack來分析java代碼中一些不好的模式。
?
OpcodeStackDetector
使用Stack-based來實現findbugs的檢測器都要繼承OpcodeStackDetector這個類,并且實現sawOpcode(int)方法。這個方法傳入的操作碼的值,根據這個值我們可以得到操作數的信息,如操作碼是函數調用,則能獲取到函數的名稱、描述符等信息。另外我們還能獲取到方法棧數據,程序計數器等數據,使用這些數據便能實現想要檢測的代碼模式。
先來看下OpcodeStackDetector的繼承結構:
這些類的主要作用是:
BetterVisitor 定義了許多visit方法,這些方法用來實現對class文件對象的訪問(JavaClass,Method等)。
DismantleBytecode 用來分析字節碼,提取操作碼、操作數、計數器等數據。這個類實現了visit(Code)方法,并為每一個操作碼調用sawOpcode(int)方法。
OpcodeStackDetector 類有操作數stack數據,對于stack based模式檢測是必不可少的。另外還定義了sawOpcode(int)方法,我們的檢測代碼在該方法中實現。
?
檢測器例子
仍以 從定義最簡單Findbugs Detector做起 提到的檢測器做為例子,用來檢測BigDecimal實例使用Double進行構造,另外每次調用sawOpcode函數,都打印出方法棧信息:
public
void
sawOpcode(
int
seen) {
//
System.out.println("visit seen:" + seen);
//
TODO Auto-generated method stub
if
(seen == INVOKESPECIAL && getClassConstantOperand().equals("java/math/BigDecimal"
)
&& getNameConstantOperand().equals("<init>") && getSigConstantOperand().equals("(D)V"
)) {
OpcodeStack.Item top
= stack.getStackItem(0
);
Object value
=
top.getConstant();
System.out.println(
"stack num local values:" + stack.getNumLocalValues() +
" stack depth"+
stack.getStackDepth());
if
(value
instanceof
Double) {
double
arg =
((Double) value).doubleValue();
String dblString
=
Double.toString(arg);
String bigDecimalString
=
new
BigDecimal(arg).toString();
boolean
ok = dblString.equals(bigDecimalString) || dblString.equals(bigDecimalString + ".0"
);
if
(!
ok) {
boolean
scary = dblString.length() <= 8 && dblString.toUpperCase().indexOf("E") == -1
;
bugReporter.reportBug(
new
BugInstance(
this
, "TUTORIAL_BUG", scary ?
NORMAL_PRIORITY : LOW_PRIORITY)
.addClassAndMethod(
this
).addString(dblString).addSourceLine(
this
));
}
}
}
System.out.println(
"stack num local values in return:" + stack.getNumLocalValues() +
" stack depth:"+
stack.getStackDepth());
for
(
int
i=0; i<stack.getStackDepth(); ++
i)
System.out.println(
"stack item "+i+":" +
stack.getStackItem(i));
}
首先if語句判斷操作碼是一個構造函數調用,且方法名、類名、方法描述符都符合,以驗證這是一個BigDecimal初始化;再取出執行此字節命令時的棧頂操作數stack.getStackItem(0),上面的代碼用該double構造BigDecimal實例,判斷dblString.equals(bigDecimalString)來確定是否報告這個警告。
將該檢測器放到findbugs中,檢測第一部分提到的字節碼,棧信息輸出如下:
前面提到了visit方法,這些方法在findbugs分析到相應的class文件部分時會被調用,如visit(JavaClass obj)方法在分析class文件時調用;visit(Method me)方法在findbugs分析方法時調用;visit(Code code)在findbugs分析字節碼指令是調用。需要注意的是,調用visit(Code code)時,一定要調用super.visit(code)方法,否則我們實現的sawOpcode方法將不會調用,因為sawOpcode是在super.visit(code)方法中調用的。
public
void
visit(Code code){
System.out.println(
"visit code!!!!!!!"
);
//
System.out.println("code:" + code.toString());
//
yi ding yao fang wen zhe ge
super
.visit(code);
}
使用visit方法可以實現必要的初始化操作,如某些變量在對字節碼檢測之前設置出事狀態,那么可以把這些操作放在visit(Code)中,這樣在每一次分析方法字節碼時,狀態都將被重置。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

