private void checkStateForLeakedObligations( State state, Map<Obligation, State> leakedObligationMap) throws IllegalStateException { if (DEBUG) { Path path = state.getPath(); if (path.getLength() > 0 && path.getBlockIdAt(path.getLength() - 1) != cfg.getExit().getLabel()) { throw new IllegalStateException( "path " + path + " at cfg exit has no label for exit block"); } } for (int id = 0; id < database.getFactory().getMaxObligationTypes(); ++id) { Obligation obligation = database.getFactory().getObligationById(id); // If the raw count produced by the analysis // for this obligation type is 0, // assume everything is ok on this state's path. int rawLeakCount = state.getObligationSet().getCount(id); if (rawLeakCount == 0) { continue; } // Apply the false-positive suppression heuristics int leakCount = getAdjustedLeakCount(state, id); if (leakCount > 0) { leakedObligationMap.put(obligation, state); } // TODO: if the leak count is less than 0, then a nonexistent // resource was closed } }
private State getTransferState(InstructionHandle handle) { StateSet stateSet; try { stateSet = dataflow.getFactAtLocation(new Location(handle, curBlock)); } catch (DataflowAnalysisException e) { bugReporter.logError("Error checking obligation state at " + handle, e); return null; } List<State> prefixes = stateSet.getPrefixStates(state.getPath()); if (prefixes.size() != 1) { // Could this happen? if (DEBUG_FP) { System.out.println( "at " + handle + " in " + xmethod + " found " + prefixes.size() + " states which are prefixes of error state"); } return null; } return prefixes.get(0); }
/** * Get the adjusted leak count for the given State and obligation type. Use heuristics to * account for: * * <ul> * <li>null checks (count the number of times the supposedly leaked obligation is compared to * null, and subtract those from the leak count) * <li>field assignments (count number of times obligation type is assigned to a field, and * subtract those from the leak count) * <li>return statements (if an instance of the obligation type is returned from the method, * subtract one from leak count) * </ul> * * @return the adjusted leak count (positive if leaked obligation, negative if attempt to * release an un-acquired obligation) */ private int getAdjustedLeakCount(State state, int obligationId) { final Obligation obligation = database.getFactory().getObligationById(obligationId); Path path = state.getPath(); PostProcessingPathVisitor visitor = new PostProcessingPathVisitor(obligation, state); path.acceptVisitor(cfg, visitor); if (visitor.couldNotAnalyze()) { return 0; } else { return visitor.getAdjustedLeakCount(); } }
public PostProcessingPathVisitor(Obligation possiblyLeakedObligation /* * , * int * initialLeakCount */, State state) { this.possiblyLeakedObligation = possiblyLeakedObligation; this.state = state; this.adjustedLeakCount = state.getObligationSet().getCount(possiblyLeakedObligation.getId()); if (COMPUTE_TRANSFERS) { this.transferList = new LinkedList<PossibleObligationTransfer>(); } }
private void reportWarning(Obligation obligation, State state, StateSet factAtExit) { String className = obligation.getClassName(); if (methodDescriptor.isStatic() && methodDescriptor.getName().equals("main") && methodDescriptor.getSignature().equals("([Ljava/lang/String;)V") && (className.contains("InputStream") || className.contains("Reader") || factAtExit.isOnExceptionPath())) { // Don't report unclosed input streams and readers in main() // methods return; } String bugPattern = factAtExit.isOnExceptionPath() ? "OBL_UNSATISFIED_OBLIGATION_EXCEPTION_EDGE" : "OBL_UNSATISFIED_OBLIGATION"; BugInstance bugInstance = new BugInstance(FindUnsatisfiedObligation.this, bugPattern, NORMAL_PRIORITY) .addClassAndMethod(methodDescriptor) .addClass(className) .describe("CLASS_REFTYPE"); // Report how many instances of the obligation are remaining bugInstance .addInt(state.getObligationSet().getCount(obligation.getId())) .describe(IntAnnotation.INT_OBLIGATIONS_REMAINING); // Add source line information annotateWarningWithSourceLineInformation(state, obligation, bugInstance); if (REPORT_OBLIGATION_SET) { bugInstance .addString(state.getObligationSet().toString()) .describe(StringAnnotation.REMAINING_OBLIGATIONS_ROLE); } bugReporter.reportBug(bugInstance); }
private void applyPossibleObligationTransfers() { // // See if we recorded any possible obligation transfers // that might have created a "wrapper" object. // In many cases, it is correct to close either // the "wrapped" or "wrapper" object. // So, if we see a possible transfer, and we see // a +1/-1 obligation count for the pair // (consumed and produced obligation types), // rather than 0/0, // then we will assume that which resource was closed // (wrapper or wrapped) was the opposite of what // we expected. // for (PossibleObligationTransfer transfer : transferList) { if (DEBUG_FP) { System.out.println("Checking possible transfer " + transfer + "..."); } boolean matches = transfer.matches(possiblyLeakedObligation); if (DEBUG_FP) { System.out.println(" matches: " + possiblyLeakedObligation); } if (matches) { boolean balanced = transfer.balanced(state); if (DEBUG_FP) { System.out.println(" balanced: " + balanced + " in " + state.getObligationSet()); } if (balanced) { if (DEBUG_FP) { System.out.println( " Suppressing path because " + "a transfer appears to result in balanced " + "outstanding obligations"); } adjustedLeakCount = 0; break; } } } }
private void reportPath( final BugInstance bugInstance, final Obligation obligation, final State state) { Path path = state.getPath(); // This PathVisitor will traverse the Path and add appropriate // SourceLineAnnotations to the BugInstance. PathVisitor visitor = new PathVisitor() { boolean sawFirstCreation; SourceLineAnnotation lastSourceLine; // = creationSourceLine; BasicBlock curBlock; @Override public void visitBasicBlock(BasicBlock basicBlock) { curBlock = basicBlock; // See if the initial instance of the leaked resource // is in the entry fact due to a @WillClose annotation. if (curBlock == cfg.getEntry()) { // Get the entry fact - it should have precisely one // state StateSet entryFact = dataflow.getResultFact(curBlock); Iterator<State> i = entryFact.stateIterator(); if (i.hasNext()) { State entryState = i.next(); if (entryState.getObligationSet().getCount(obligation.getId()) > 0) { lastSourceLine = SourceLineAnnotation.forFirstLineOfMethod(methodDescriptor); lastSourceLine.setDescription( SourceLineAnnotation.ROLE_OBLIGATION_CREATED_BY_WILLCLOSE_PARAMETER); bugInstance.add(lastSourceLine); sawFirstCreation = true; if (REPORT_PATH_DEBUG) { System.out.println( " " + obligation + " created by @WillClose parameter at " + lastSourceLine); } } } } } @Override public void visitInstructionHandle(InstructionHandle handle) { boolean isCreation = (dataflow .getAnalysis() .getActionCache() .addsObligation(curBlock, handle, obligation)); if (!sawFirstCreation && !isCreation) { return; } SourceLineAnnotation sourceLine = SourceLineAnnotation.fromVisitedInstruction( methodDescriptor, new Location(handle, curBlock)); sourceLine.setDescription( isCreation ? SourceLineAnnotation.ROLE_OBLIGATION_CREATED : SourceLineAnnotation.ROLE_PATH_CONTINUES); boolean isInteresting = (sourceLine.getStartLine() > 0) && (lastSourceLine == null || isCreation || sourceLine.getStartLine() != lastSourceLine.getStartLine()); if (REPORT_PATH_DEBUG) { System.out.println( " " + handle.getPosition() + " --> " + sourceLine + (isInteresting ? " **" : "")); } if (isInteresting) { bugInstance.add(sourceLine); lastSourceLine = sourceLine; if (isCreation) { sawFirstCreation = true; } } } @Override public void visitEdge(Edge edge) { if (REPORT_PATH_DEBUG) { System.out.println( "Edge of type " + Edge.edgeTypeToString(edge.getType()) + " to " + edge.getTarget().getLabel()); if (edge.getTarget().getFirstInstruction() != null) { System.out.println( " First instruction in target: " + edge.getTarget().getFirstInstruction()); } if (edge.getTarget().isExceptionThrower()) { System.out.println( " exception thrower for " + edge.getTarget().getExceptionThrower()); } if (edge.isExceptionEdge()) { System.out.println( " exceptions thrown: " + typeDataflow.getEdgeExceptionSet(edge)); } } } }; // Visit the Path path.acceptVisitor(cfg, visitor); }
private void checkForPossibleObligationTransfer( InvokeInstruction inv, InstructionHandle handle) throws ClassNotFoundException { // // We will assume that a method invocation might transfer // an obligation from one type to another if // 1. either // - it's a constructor where the constructed // type and exactly one param type // are obligation types, or // - it's a method where the return type and // exactly one param type are obligation types // 2. at least one instance of the resource "consumed" // by the transfer exists at the point of the transfer. // E.g., if we see a transfer of InputStream->Reader, // there must be an instance of InputStream at // the transfer point. // if (DEBUG_FP) { System.out.println("Checking " + handle + " as possible obligation transfer...:"); } // Find the State which is a prefix of the error state // at the location of this (possible) transfer. State transferState = getTransferState(handle); if (transferState == null) { if (DEBUG_FP) { System.out.println("No transfer state???"); } return; } String methodName = inv.getMethodName(cpg); Type producedType = methodName.equals("<init>") ? inv.getReferenceType(cpg) : inv.getReturnType(cpg); if (DEBUG_FP && !(producedType instanceof ObjectType)) { System.out.println("Produced type " + producedType + " not an ObjectType"); } if (producedType instanceof ObjectType) { Obligation produced = database.getFactory().getObligationByType((ObjectType) producedType); if (DEBUG_FP && produced == null) { System.out.println("Produced type " + producedType + " not an obligation type"); } if (produced != null) { XMethod calledMethod = XFactory.createXMethod(inv, cpg); Obligation[] params = database.getFactory().getParameterObligationTypes(calledMethod); for (int i = 0; i < params.length; i++) { Obligation consumed = params[i]; if (DEBUG_FP && consumed == null) { System.out.println("Param " + i + " not an obligation type"); } if (DEBUG_FP && consumed != null && consumed.equals(produced)) { System.out.println("Consumed type is the same as produced type"); } if (consumed != null && !consumed.equals(produced)) { // See if an instance of the consumed obligation // type // exists here. if (transferState.getObligationSet().getCount(consumed.getId()) > 0) { transferList.add(new PossibleObligationTransfer(consumed, produced)); if (DEBUG_FP) { System.out.println( "===> Possible transfer of " + consumed + " to " + produced + " at " + handle); } } else if (DEBUG_FP) { System.out.println( handle + " not a transfer " + "of " + consumed + "->" + produced + " because no instances of " + consumed); System.out.println("I see " + transferState.getObligationSet()); } } } } } }
/** * Determine whether the state has "balanced" obligation counts for the consumed and produced * Obligation types. * * @param state a State * @return true if the obligation counts are balanced, false otherwise */ private boolean balanced(State state) { int consumedCount = state.getObligationSet().getCount(consumed.getId()); int producedCount = state.getObligationSet().getCount(produced.getId()); return (consumedCount + producedCount == 0) && (consumedCount == 1 || producedCount == 1); }