package exercises; import java.util.*; import gov.nasa.jpf.*; import gov.nasa.jpf.util.*; import gov.nasa.jpf.jvm.*; import gov.nasa.jpf.jvm.bytecode.*; import gov.nasa.jpf.search.*; public class NonNullChecker extends PropertyListenerAdapter { /** * Visitor for instructions. This way, we do not have to do any * instanceof checks. The compiler does all that for use with a little * runtime overhead. */ private class NonNullPostVisitor extends InstructionVisitorAdapter { public void visit(PUTFIELD insn) { checkFieldInsn(insn); } public void visit(PUTSTATIC insn) { checkFieldInsn(insn); } private void checkFieldInsn(FieldInstruction insn) { if (isRelevantField(insn)) { if (isNullFieldStore(insn)) { storeError(vm, insn); vm.breakTransition(); } } } public void visit(ASTORE insn) { checkLocalVarInsn(insn); } private void checkLocalVarInsn(ASTORE insn) { if (isRelevantVar(insn)) { if (isNullVarStore(insn)) { storeError(vm, insn); vm.breakTransition(); } } } } /// The non-nullable fields FieldSpec[] nonNullableFields; /// The non-nullable vars VarSpec[] nonNullableVars; /// Error string String error; /// --- For easy visiting --- /// The VM of the last executed instruction JVM vm; /// The visitor to use InstructionVisitor visitor; /** * Parse configuration and get what we are interested in. * @param conf JPF-Configuration. */ public NonNullChecker(Config conf) { Set spec = conf.getStringSet("nnc.fields"); if (spec == null) spec = Collections.emptySet(); nonNullableFields = new FieldSpec[spec.size()]; int i = -1; for (String field : spec) nonNullableFields[++i] = FieldSpec.createFieldSpec(field); spec = conf.getStringSet("nnc.vars"); if (spec == null) spec = Collections.emptySet(); nonNullableVars = new VarSpec[spec.size()]; i = -1; for (String var : spec) nonNullableVars[++i] = VarSpec.createVarSpec(var); visitor = new NonNullPostVisitor(); } /** * The JVM just executed an instruction and notifies us about it. If this * instruction was a write into a field we are interested in, do a null * check. * @param vm JPF-JVM */ public void instructionExecuted(JVM vm) { // This is the lecture version //this.vm = vm; //vm.getLastInstruction().accept(visitor); } public void executeInstruction(JVM vm) { // TODO Implement in the exercise } private boolean isRelevantVar(ASTORE insn) { int slotIdx = insn.getLocalVariableIndex(); MethodInfo mi = insn.getMethodInfo(); int pc = insn.getPosition() + 1; for (VarSpec varSpec : nonNullableVars) { if (varSpec.getMatchingLocalVarInfo(mi, pc, slotIdx) != null) return true; } return false; } /** * Was the write to the field we are monitoring. * @param insn Instruction that just modified a field. * @return true if the write was to * {@link #fieldSpec fieldSpec} */ private boolean isRelevantField(FieldInstruction insn) { if (!insn.isReferenceField()) return false; FieldInfo fi = insn.getFieldInfo(); for (FieldSpec fieldSpec : nonNullableFields) { if (fieldSpec.matches(fi)) { return true; } } return false; } // POST EXECUTION METHODS /** * Did this field instruction store null into a relevant field? * @param insn Field instruction (should be PUTFIELD or PUTSTATIC). * @return true iff the store wrote null */ private boolean isNullFieldStore(FieldInstruction insn) { FieldInfo fi = insn.getFieldInfo(); ElementInfo ei = insn.getLastElementInfo(); return ei.getFieldValueObject(fi.getName()) == null; } /** * Did this instruction store null in a relevant local var? * @param insn The instruction. * @return true iff the store wrote null */ private boolean isNullVarStore(ASTORE insn) { // Get the thread info ThreadInfo ti = vm.getLastThreadInfo(); // Get the index of the local variable on the stack frame int slotIdx = insn.getLocalVariableIndex(); // We either get null for null, or a valid ElementInfo return ti.getObjectLocal(slotIdx) == null; } /** * Store an error message. * @param vm JPF-JVM * @param insn Field instruction that wrote null to a * non_nullable field */ private void storeError(JVM vm, FieldInstruction insn) { ThreadInfo ti = vm.getLastThreadInfo(); FieldInfo fi = insn.getFieldInfo(); error = String.format("field %s assigned to null in thread %s at %s", fi.getFullName(), ti.getName(), insn.getSourceLocation()); } /** * Store an error message. * @param vm JPF-JVM * @param insn Field instruction that wrote null to a * non_nullable field */ private void storeError(JVM vm, LocalVariableInstruction insn) { ThreadInfo ti = vm.getLastThreadInfo(); LocalVarInfo vi = insn.getLocalVarInfo(); error = String.format("variable %s assigned to null in thread %s at %s", vi.getName(), ti.getName(), insn.getSourceLocation()); } public boolean check(Search search, JVM vm) { return error == null; } public String getErrorMessage() { return error; } public void reset() { error = null; } }