自定义一个 spotbugs 的插件,官方有比较详细的说明:
https://spotbugs.readthedocs.io/en/stable/implement-plugin.html
本篇是跟随官网demo的足迹,略显无聊,可跳过。
创建工程
执行maven 命令
mvn archetype:generate -DarchetypeArtifactId=spotbugs-archetype -DarchetypeGroupId=com.github.spotbugs -DarchetypeVersion=0.2.3
项目给出了很清晰的代码结构和单元测试用例:
pom的依赖引用,除了 spotbugs 之外,还有字节码工具 asm 等,也就是说依赖字节码的解析,需要定义插件的童鞋有一定的字节码功底。
实例
bug 判断区:
基本框架就已经搭好,如果有自定义需求,就可以根据demo改写自己的业务逻辑。
下面我们来看看,sawOpcode 是从什么地方调用,需要我们注意什么,能拿到什么信息:
这段代码非常长,看这段代码需要点耐心。
这个方法是CodeVisitor类的一个实现,用于遍历Java字节码并处理各种操作。
- 首先,代码定义了一些变量,如sizePrevOpcodeBuffer、currentPosInPrevOpcodeBuffer等,用于处理字节码的读取和操作。
- 然后,代码尝试读取一个字节,并将其存储在变量opcode中。这个字节表示当前操作码,用于后续的处理。
- 接下来,代码根据opcode的值进行不同的处理。如果opcode表示的是一个wide操作码(例如,Const.WIDE),那么需要读取下一个字节,并将两个字节组合成一个整数。
- 代码处理各种操作码,如加载(ILOAD)、存储(ISTORE)等。这些操作通常涉及到寄存器的操作,因此需要处理registerOperand变量。
- 如果opcode表示的是一个跳转操作(如Const.GOTO、Const.IF_A等),那么需要处理跳转目标。
- 如果opcode表示的是一个表跳转操作(如Const.TABLESWITCH、Const.LOOKUPSWITCH),那么需要处理表跳转的偏移量、标签等。
- 最后,代码处理完当前操作码后,继续处理下一个操作码。
需要注意的是,这段代码仅处理了Java字节码的读取和简单的操作,而没有处理字节码中的各种变量、方法、类等符号。这些符号需要通过其他方式(如Const.getOpcodeName(opcode))进行处理。
public void visit(Code obj) {
// if (getXMethod().usesInvokeDynamic()) {
// AnalysisContext.currentAnalysisContext().analysisSkippedDueToInvokeDynamic(getXMethod());
// return;
// }
sizePrevOpcodeBuffer = 0;
currentPosInPrevOpcodeBuffer = prevOpcode.length - 1;
int switchLow = 1000000;
int switchHigh = -1000000;
codeBytes = obj.getCode();
DataInputStream byteStream = new DataInputStream(new ByteArrayInputStream(codeBytes));
lineNumberTable = obj.getLineNumberTable();
try {
for (int i = 0; i < codeBytes.length;) {
resetState();
PC = i;
opcodeIsWide = false;
opcode = byteStream.readUnsignedByte();
sizePrevOpcodeBuffer++;
currentPosInPrevOpcodeBuffer++;
if (currentPosInPrevOpcodeBuffer >= prevOpcode.length) {
currentPosInPrevOpcodeBuffer = 0;
}
prevOpcode[currentPosInPrevOpcodeBuffer] = opcode;
i++;
// System.out.println(Const.getOpcodeName(opCode));
int byteStreamArgCount = Const.getNoOfOperands(opcode);
if (byteStreamArgCount == Const.UNPREDICTABLE) {
if (opcode == Const.LOOKUPSWITCH) {
int pad = 4 - (i & 3);
if (pad == 4) {
pad = 0;
}
int count = pad;
while (count > 0) {
count -= byteStream.skipBytes(count);
}
i += pad;
defaultSwitchOffset = byteStream.readInt();
branchOffset = defaultSwitchOffset;
branchTarget = branchOffset + PC;
i += 4;
int npairs = byteStream.readInt();
i += 4;
switchOffsets = new int[npairs];
switchLabels = new int[npairs];
for (int o = 0; o < npairs; o++) {
switchLabels[o] = byteStream.readInt();
switchOffsets[o] = byteStream.readInt();
i += 8;
}
sortByOffset(switchOffsets, switchLabels);
} else if (opcode == Const.TABLESWITCH) {
int pad = 4 - (i & 3);
if (pad == 4) {
pad = 0;
}
int count = pad;
while (count > 0) {
count -= byteStream.skipBytes(count);
}
i += pad;
defaultSwitchOffset = byteStream.readInt();
branchOffset = defaultSwitchOffset;
branchTarget = branchOffset + PC;
i += 4;
switchLow = byteStream.readInt();
i += 4;
switchHigh = byteStream.readInt();
i += 4;
int npairs = switchHigh - switchLow + 1;
switchOffsets = new int[npairs];
switchLabels = new int[npairs];
for (int o = 0; o < npairs; o++) {
switchLabels[o] = o + switchLow;
switchOffsets[o] = byteStream.readInt();
i += 4;
}
sortByOffset(switchOffsets, switchLabels);
} else if (opcode == Const.WIDE) {
opcodeIsWide = true;
opcode = byteStream.readUnsignedByte();
i++;
switch (opcode) {
case Const.ILOAD:
case Const.FLOAD:
case Const.ALOAD:
case Const.LLOAD:
case Const.DLOAD:
case Const.ISTORE:
case Const.FSTORE:
case Const.ASTORE:
case Const.LSTORE:
case Const.DSTORE:
case Const.RET:
registerOperand = byteStream.readUnsignedShort();
i += 2;
break;
case Const.IINC:
registerOperand = byteStream.readUnsignedShort();
i += 2;
intConstant = byteStream.readShort();
i += 2;
break;
default:
throw new IllegalStateException(String.format("bad wide bytecode %d: %s", opcode, Const.getOpcodeName(opcode)));
}
} else {
throw new IllegalStateException(String.format("bad unpredicatable bytecode %d: %s", opcode, Const.getOpcodeName(opcode)));
}
} else {
if (byteStreamArgCount < 0) {
throw new IllegalStateException(String.format("bad length for bytecode %d: %s", opcode, Const.getOpcodeName(opcode)));
}
for (int k = 0; k < Const.getOperandTypeCount(opcode); k++) {
int v;
int t = Const.getOperandType(opcode, k);
int m = MEANING_OF_OPERANDS[opcode][k];
boolean unsigned = (m == M_CP || m == M_R || m == M_UINT);
switch (t) {
case Const.T_BYTE:
if (unsigned) {
v = byteStream.readUnsignedByte();
} else {
v = byteStream.readByte();
}
/*
* System.out.print("Read byte " + v);
* System.out.println(" with meaning" + m);
*/
i++;
break;
case Const.T_SHORT:
if (unsigned) {
v = byteStream.readUnsignedShort();
} else {
v = byteStream.readShort();
}
i += 2;
break;
case Const.T_INT:
v = byteStream.readInt();
i += 4;
break;
default:
throw new IllegalStateException();
}
switch (m) {
case M_BR:
branchOffset = v;
branchTarget = v + PC;
branchFallThrough = i;
break;
case M_CP:
constantRefOperand = getConstantPool().getConstant(v);
if (constantRefOperand instanceof ConstantClass) {
ConstantClass clazz = (ConstantClass) constantRefOperand;
classConstantOperand = getStringFromIndex(clazz.getNameIndex());
referencedClass = DescriptorFactory.createClassDescriptor(classConstantOperand);
} else if (constantRefOperand instanceof ConstantInteger) {
intConstant = ((ConstantInteger) constantRefOperand).getBytes();
} else if (constantRefOperand instanceof ConstantLong) {
longConstant = ((ConstantLong) constantRefOperand).getBytes();
} else if (constantRefOperand instanceof ConstantFloat) {
floatConstant = ((ConstantFloat) constantRefOperand).getBytes();
} else if (constantRefOperand instanceof ConstantDouble) {
doubleConstant = ((ConstantDouble) constantRefOperand).getBytes();
} else if (constantRefOperand instanceof ConstantString) {
int s = ((ConstantString) constantRefOperand).getStringIndex();
stringConstantOperand = getStringFromIndex(s);
} else if (constantRefOperand instanceof ConstantInvokeDynamic) {
ConstantInvokeDynamic id = (ConstantInvokeDynamic) constantRefOperand;
ConstantNameAndType sig = (ConstantNameAndType) getConstantPool().getConstant(
id.getNameAndTypeIndex());
nameConstantOperand = getStringFromIndex(sig.getNameIndex());
sigConstantOperand = getStringFromIndex(sig.getSignatureIndex());
} else if (constantRefOperand instanceof ConstantCP) {
ConstantCP cp = (ConstantCP) constantRefOperand;
ConstantClass clazz = (ConstantClass) getConstantPool().getConstant(cp.getClassIndex());
classConstantOperand = getStringFromIndex(clazz.getNameIndex());
referencedClass = DescriptorFactory.createClassDescriptor(classConstantOperand);
referencedXClass = null;
ConstantNameAndType sig = (ConstantNameAndType) getConstantPool().getConstant(
cp.getNameAndTypeIndex());
nameConstantOperand = getStringFromIndex(sig.getNameIndex());
sigConstantOperand = getStringFromIndex(sig.getSignatureIndex());
refConstantOperand = null;
}
break;
case M_R:
registerOperand = v;
break;
case M_UINT:
case M_INT:
intConstant = v;
break;
case M_PAD:
break;
default:
throw new IllegalStateException("Unexpecting meaning " + m);
}
}
}
switch (opcode) {
case Const.IINC:
isRegisterLoad = true;
isRegisterStore = true;
break;
case Const.ILOAD_0:
case Const.ILOAD_1:
case Const.ILOAD_2:
case Const.ILOAD_3:
registerOperand = opcode - Const.ILOAD_0;
isRegisterLoad = true;
break;
case Const.ALOAD_0:
case Const.ALOAD_1:
case Const.ALOAD_2:
case Const.ALOAD_3:
registerOperand = opcode - Const.ALOAD_0;
isRegisterLoad = true;
break;
case Const.FLOAD_0:
case Const.FLOAD_1:
case Const.FLOAD_2:
case Const.FLOAD_3:
registerOperand = opcode - Const.FLOAD_0;
isRegisterLoad = true;
break;
case Const.DLOAD_0:
case Const.DLOAD_1:
case Const.DLOAD_2:
case Const.DLOAD_3:
registerOperand = opcode - Const.DLOAD_0;
isRegisterLoad = true;
break;
case Const.LLOAD_0:
case Const.LLOAD_1:
case Const.LLOAD_2:
case Const.LLOAD_3:
registerOperand = opcode - Const.LLOAD_0;
isRegisterLoad = true;
break;
case Const.ILOAD:
case Const.FLOAD:
case Const.ALOAD:
case Const.LLOAD:
case Const.DLOAD:
isRegisterLoad = true;
break;
case Const.ISTORE_0:
case Const.ISTORE_1:
case Const.ISTORE_2:
case Const.ISTORE_3:
registerOperand = opcode - Const.ISTORE_0;
isRegisterStore = true;
break;
case Const.ASTORE_0:
case Const.ASTORE_1:
case Const.ASTORE_2:
case Const.ASTORE_3:
registerOperand = opcode - Const.ASTORE_0;
isRegisterStore = true;
break;
case Const.FSTORE_0:
case Const.FSTORE_1:
case Const.FSTORE_2:
case Const.FSTORE_3:
registerOperand = opcode - Const.FSTORE_0;
isRegisterStore = true;
break;
case Const.DSTORE_0:
case Const.DSTORE_1:
case Const.DSTORE_2:
case Const.DSTORE_3:
registerOperand = opcode - Const.DSTORE_0;
isRegisterStore = true;
break;
case Const.LSTORE_0:
case Const.LSTORE_1:
case Const.LSTORE_2:
case Const.LSTORE_3:
registerOperand = opcode - Const.LSTORE_0;
isRegisterStore = true;
break;
case Const.ISTORE:
case Const.FSTORE:
case Const.ASTORE:
case Const.LSTORE:
case Const.DSTORE:
isRegisterStore = true;
break;
default:
break;
}
switch (opcode) {
case Const.ILOAD:
case Const.FLOAD:
case Const.ALOAD:
case Const.LLOAD:
case Const.DLOAD:
// registerKind = opcode - ILOAD;
break;
case Const.ISTORE:
case Const.FSTORE:
case Const.ASTORE:
case Const.LSTORE:
case Const.DSTORE:
// registerKind = opcode - ISTORE;
break;
case Const.RET:
// registerKind = R_REF;
break;
case Const.GETSTATIC:
case Const.PUTSTATIC:
refFieldIsStatic = true;
break;
case Const.GETFIELD:
case Const.PUTFIELD:
refFieldIsStatic = false;
break;
default:
break;
}
nextPC = i;
if (beforeOpcode(opcode)) {
sawOpcode(opcode);
}
afterOpcode(opcode);
if (opcode == Const.TABLESWITCH) {
sawInt(switchLow);
sawInt(switchHigh);
// int prevOffset = i - PC;
for (int o = 0; o <= switchHigh - switchLow; o++) {
sawBranchTo(switchOffsets[o] + PC);
// prevOffset = switchOffsets[o];
}
sawBranchTo(defaultSwitchOffset + PC);
} else if (opcode == Const.LOOKUPSWITCH) {
sawInt(switchOffsets.length);
// int prevOffset = i - PC;
for (int o = 0; o < switchOffsets.length; o++) {
sawBranchTo(switchOffsets[o] + PC);
// prevOffset = switchOffsets[o];
sawInt(switchLabels[o]);
}
sawBranchTo(defaultSwitchOffset + PC);
} else {
for (int k = 0; k < Const.getOperandTypeCount(opcode); k++) {
int m = MEANING_OF_OPERANDS[opcode][k];
switch (m) {
case M_BR:
sawBranchTo(branchOffset + PC);
break;
case M_CP:
if (constantRefOperand instanceof ConstantInteger) {
sawInt(intConstant);
} else if (constantRefOperand instanceof ConstantLong) {
sawLong(longConstant);
} else if (constantRefOperand instanceof ConstantFloat) {
sawFloat(floatConstant);
} else if (constantRefOperand instanceof ConstantDouble) {
sawDouble(doubleConstant);
} else if (constantRefOperand instanceof ConstantString) {
sawString(stringConstantOperand);
} else if (constantRefOperand instanceof ConstantFieldref) {
sawField();
} else if (constantRefOperand instanceof ConstantMethodref) {
sawMethod();
} else if (constantRefOperand instanceof ConstantInterfaceMethodref) {
sawIMethod();
} else if (constantRefOperand instanceof ConstantClass) {
sawClass();
}
break;
case M_R:
sawRegister(registerOperand);
break;
case M_INT:
sawInt(intConstant);
break;
default:
break;
}
}
}
}
} catch (IOException e) {
AnalysisContext.logError("Error while dismantling bytecode", e);
assert false;
}
try {
byteStream.close();
} catch (IOException e) {
assert false;
}
}
调用点就在这里,visit 方法是遍历codeBytes.length