/* * Bytecode analysis framework * Copyright (C) 2005, University of Maryland * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package edu.umd.cs.findbugs.ba.npe; import java.util.BitSet; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import org.apache.bcel.Constants; import org.apache.bcel.classfile.CodeException; import org.apache.bcel.classfile.LineNumberTable; import org.apache.bcel.classfile.Method; import org.apache.bcel.generic.Instruction; import org.apache.bcel.generic.InstructionHandle; import edu.umd.cs.findbugs.BugAnnotation; import edu.umd.cs.findbugs.FieldAnnotation; import edu.umd.cs.findbugs.LocalVariableAnnotation; import edu.umd.cs.findbugs.SystemProperties; import edu.umd.cs.findbugs.ba.AnalysisContext; import edu.umd.cs.findbugs.ba.AnalysisFeatures; import edu.umd.cs.findbugs.ba.AssertionMethods; import edu.umd.cs.findbugs.ba.BasicBlock; import edu.umd.cs.findbugs.ba.CFG; import edu.umd.cs.findbugs.ba.CFGBuilderException; import edu.umd.cs.findbugs.ba.ClassContext; import edu.umd.cs.findbugs.ba.DataflowAnalysisException; import edu.umd.cs.findbugs.ba.DominatorsAnalysis; import edu.umd.cs.findbugs.ba.Edge; import edu.umd.cs.findbugs.ba.EdgeTypes; import edu.umd.cs.findbugs.ba.Location; import edu.umd.cs.findbugs.ba.MissingClassException; import edu.umd.cs.findbugs.ba.PostDominatorsAnalysis; import edu.umd.cs.findbugs.ba.XField; import edu.umd.cs.findbugs.ba.deref.UnconditionalValueDerefDataflow; import edu.umd.cs.findbugs.ba.deref.UnconditionalValueDerefSet; import edu.umd.cs.findbugs.ba.vna.ValueNumber; import edu.umd.cs.findbugs.ba.vna.ValueNumberDataflow; import edu.umd.cs.findbugs.ba.vna.ValueNumberFrame; import edu.umd.cs.findbugs.ba.vna.ValueNumberSourceInfo; import edu.umd.cs.findbugs.bcel.generic.NullnessConversationInstruction; import edu.umd.cs.findbugs.classfile.CheckedAnalysisException; import edu.umd.cs.findbugs.classfile.Global; import edu.umd.cs.findbugs.log.Profiler; /** * A user-friendly front end for finding null pointer dereferences and redundant * null comparisions. * * @see IsNullValueAnalysis * @author David Hovemeyer */ public class NullDerefAndRedundantComparisonFinder { private static final boolean DEBUG = SystemProperties.getBoolean("fnd.debug"); private static final boolean PRUNE_GUARANTEED_DEREFERENCES = SystemProperties.getBoolean("fnd.prune", true); private static final boolean DEBUG_DEREFS = SystemProperties.getBoolean("fnd.derefs.debug"); private final ClassContext classContext; private final Method method; private final NullDerefAndRedundantComparisonCollector collector; private final boolean findGuaranteedDerefs; private final List<RedundantBranch> redundantBranchList; private final BitSet definitelySameBranchSet; private final BitSet definitelyDifferentBranchSet; private final BitSet undeterminedBranchSet; private final BitSet lineMentionedMultipleTimes; private IsNullValueDataflow invDataflow; private ValueNumberDataflow vnaDataflow; private UnconditionalValueDerefDataflow uvdDataflow; private final AssertionMethods assertionMethods; static { if (DEBUG) System.out.println("fnd.debug enabled"); } /** * Constructor. * * @param classContext * the ClassContext * @param method * the method to analyze * @param collector * the NullDerefAndRedundantComparisonCollector used to report * null derefs and redundant null comparisons */ public NullDerefAndRedundantComparisonFinder(ClassContext classContext, Method method, NullDerefAndRedundantComparisonCollector collector) { this.classContext = classContext; this.method = method; this.collector = collector; this.findGuaranteedDerefs = classContext.getAnalysisContext().getBoolProperty( AnalysisFeatures.TRACK_GUARANTEED_VALUE_DEREFS_IN_NULL_POINTER_ANALYSIS); this.lineMentionedMultipleTimes = classContext.linesMentionedMultipleTimes(method); this.redundantBranchList = new LinkedList<RedundantBranch>(); this.definitelySameBranchSet = new BitSet(); this.definitelyDifferentBranchSet = new BitSet(); this.undeterminedBranchSet = new BitSet(); this.assertionMethods = classContext.getAssertionMethods(); } public void execute() { Profiler profiler = Global.getAnalysisCache().getProfiler(); profiler.start(this.getClass()); try { // Do the null-value analysis this.invDataflow = classContext.getIsNullValueDataflow(method); this.vnaDataflow = classContext.getValueNumberDataflow(method); if (findGuaranteedDerefs) { if (DEBUG_DEREFS) { System.out.println("Checking for guaranteed derefs in " + classContext.getClassDescriptor().getDottedClassName() + "." + method.getName() + method.getSignature()); } this.uvdDataflow = classContext.getUnconditionalValueDerefDataflow(method); } // Check method and report potential null derefs and // redundant null comparisons. examineBasicBlocks(); if (findGuaranteedDerefs) { examineNullValues(); } examineRedundantBranches(); } catch (MissingClassException e) { AnalysisContext.reportMissingClass(e.getClassNotFoundException()); } catch (CheckedAnalysisException e) { AnalysisContext.logError("Error while checking guaranteed derefs in " + classContext.getClassDescriptor().getDottedClassName() + "." + method.getName() + method.getSignature(), e); } finally { profiler.end(this.getClass()); } } /** * Examine basic blocks for null checks and potentially-redundant null * comparisons. * * @throws DataflowAnalysisException * @throws CFGBuilderException */ private void examineBasicBlocks() throws DataflowAnalysisException, CFGBuilderException { // Look for null check blocks where the reference being checked // is definitely null, or null on some path Iterator<BasicBlock> bbIter = invDataflow.getCFG().blockIterator(); while (bbIter.hasNext()) { BasicBlock basicBlock = bbIter.next(); if (basicBlock.isNullCheck()) { analyzeNullCheck(invDataflow, basicBlock); } else if (!basicBlock.isEmpty()) { // Look for all reference comparisons where // - both values compared are definitely null, or // - one value is definitely null and one is definitely not null // These cases are not null dereferences, // but they are quite likely to indicate an error, so while // we've got // information about null values, we may as well report them. InstructionHandle lastHandle = basicBlock.getLastInstruction(); Instruction last = lastHandle.getInstruction(); switch (last.getOpcode()) { case Constants.IF_ACMPEQ: case Constants.IF_ACMPNE: analyzeRefComparisonBranch(basicBlock, lastHandle); break; case Constants.IFNULL: case Constants.IFNONNULL: analyzeIfNullBranch(basicBlock, lastHandle); break; } } } } /** * Examine null values. Report any that are guaranteed to be dereferenced on * non-implicit-exception paths. * * @throws CFGBuilderException * @throws DataflowAnalysisException */ private void examineNullValues() throws CFGBuilderException, DataflowAnalysisException { Set<LocationWhereValueBecomesNull> locationWhereValueBecomesNullSet = invDataflow.getAnalysis() .getLocationWhereValueBecomesNullSet(); if (DEBUG_DEREFS) { System.out.println("----------------------- examineNullValues " + locationWhereValueBecomesNullSet.size()); } Map<ValueNumber, SortedSet<Location>> bugStatementLocationMap = new HashMap<ValueNumber, SortedSet<Location>>(); // Inspect the method for locations where a null value is guaranteed to // be dereferenced. Add the dereference locations Map<ValueNumber, NullValueUnconditionalDeref> nullValueGuaranteedDerefMap = new HashMap<ValueNumber, NullValueUnconditionalDeref>(); // Check every location CFG cfg = classContext.getCFG(method); for (Iterator<Location> i = cfg.locationIterator(); i.hasNext();) { Location location = i.next(); if (DEBUG_DEREFS) { System.out.println("At location " + location); } checkForUnconditionallyDereferencedNullValues(location, bugStatementLocationMap, nullValueGuaranteedDerefMap, vnaDataflow.getFactAtLocation(location), invDataflow.getFactAtLocation(location), uvdDataflow.getFactAfterLocation(location), false); } HashSet<ValueNumber> npeIfStatementCovered = new HashSet<ValueNumber>(nullValueGuaranteedDerefMap.keySet()); Map<ValueNumber, SortedSet<Location>> bugEdgeLocationMap = new HashMap<ValueNumber, SortedSet<Location>>(); checkEdges(cfg, nullValueGuaranteedDerefMap, bugEdgeLocationMap); Map<ValueNumber, SortedSet<Location>> bugLocationMap = bugEdgeLocationMap; bugLocationMap.putAll(bugStatementLocationMap); // For each value number that is null somewhere in the // method, collect the set of locations where it becomes null. // FIXME: we may see some locations that are not guaranteed to be // dereferenced (how to fix this?) Map<ValueNumber, Set<Location>> nullValueAssignmentMap = findNullAssignments(locationWhereValueBecomesNullSet); reportBugs(nullValueGuaranteedDerefMap, npeIfStatementCovered, bugLocationMap, nullValueAssignmentMap); } /** * @param locationWhereValueBecomesNullSet * @return */ public Map<ValueNumber, Set<Location>> findNullAssignments(Set<LocationWhereValueBecomesNull> locationWhereValueBecomesNullSet) { Map<ValueNumber, Set<Location>> nullValueAssignmentMap = new HashMap<ValueNumber, Set<Location>>(); for (LocationWhereValueBecomesNull lwvbn : locationWhereValueBecomesNullSet) { if (DEBUG_DEREFS) System.out.println("OOO " + lwvbn); Set<Location> locationSet = nullValueAssignmentMap.get(lwvbn.getValueNumber()); if (locationSet == null) { locationSet = new HashSet<Location>(4); nullValueAssignmentMap.put(lwvbn.getValueNumber(), locationSet); } locationSet.add(lwvbn.getLocation()); if (DEBUG_DEREFS) System.out.println(lwvbn.getValueNumber() + " becomes null at " + lwvbn.getLocation()); } return nullValueAssignmentMap; } /** * @param nullValueGuaranteedDerefMap * @param npeIfStatementCovered * @param bugLocationMap * @param nullValueAssignmentMap * @throws CFGBuilderException * @throws DataflowAnalysisException */ public void reportBugs(Map<ValueNumber, NullValueUnconditionalDeref> nullValueGuaranteedDerefMap, HashSet<ValueNumber> npeIfStatementCovered, Map<ValueNumber, SortedSet<Location>> bugLocationMap, Map<ValueNumber, Set<Location>> nullValueAssignmentMap) throws CFGBuilderException, DataflowAnalysisException { // Report for (Map.Entry<ValueNumber, NullValueUnconditionalDeref> e : nullValueGuaranteedDerefMap.entrySet()) { ValueNumber valueNumber = e.getKey(); Set<Location> derefLocationSet = e.getValue().getDerefLocationSet(); Set<Location> assignedNullLocationSet = nullValueAssignmentMap.get(valueNumber); if (assignedNullLocationSet == null) { if (DEBUG_DEREFS) { String where = classContext.getJavaClass().getClassName() + "." + method.getName() + ":" + method.getSignature(); System.out.println("Problem at " + where); System.out.println("Value number " + valueNumber); for (Location loc : derefLocationSet) { System.out.println("Dereference at " + loc); } } // TODO: figure out why this is failing if (false) assert false : "No assigned NullLocationSet for " + valueNumber + " in " + nullValueAssignmentMap.keySet() + " while analyzing " + classContext.getJavaClass().getClassName() + "." + method.getName(); assignedNullLocationSet = Collections.<Location> emptySet(); } SortedSet<Location> knownNullAndDoomedAt = bugLocationMap.get(valueNumber); BugAnnotation variableAnnotation = null; try { for (Location loc : derefLocationSet) { variableAnnotation = ValueNumberSourceInfo.findAnnotationFromValueNumber(method, loc, valueNumber, vnaDataflow.getFactAtLocation(loc), "VALUE_OF"); if (variableAnnotation != null) break; } if (variableAnnotation == null) for (Location loc : knownNullAndDoomedAt) { variableAnnotation = ValueNumberSourceInfo.findAnnotationFromValueNumber(method, loc, valueNumber, vnaDataflow.getFactAtLocation(loc), "VALUE_OF"); if (variableAnnotation != null) break; } if (variableAnnotation == null) for (Location loc : assignedNullLocationSet) { variableAnnotation = ValueNumberSourceInfo.findAnnotationFromValueNumber(method, loc, valueNumber, vnaDataflow.getFactAtLocation(loc), "VALUE_OF"); if (variableAnnotation != null) break; } } catch (DataflowAnalysisException e2) { } if (variableAnnotation == null) variableAnnotation = new LocalVariableAnnotation("?", -1, derefLocationSet.iterator().next().getHandle() .getPosition()); if (PRUNE_GUARANTEED_DEREFERENCES) { PostDominatorsAnalysis postDomAnalysis = classContext.getNonExceptionPostDominatorsAnalysis(method); removeStrictlyPostDominatedLocations(derefLocationSet, postDomAnalysis); removeStrictlyPostDominatedLocations(assignedNullLocationSet, postDomAnalysis); DominatorsAnalysis domAnalysis = classContext.getNonExceptionDominatorsAnalysis(method); removeStrictlyDominatedLocations(knownNullAndDoomedAt, domAnalysis); } collector.foundGuaranteedNullDeref(assignedNullLocationSet, derefLocationSet, knownNullAndDoomedAt, vnaDataflow, valueNumber, variableAnnotation, e.getValue(), npeIfStatementCovered.contains(valueNumber)); } } /** * @param cfg * @param nullValueGuaranteedDerefMap * @param bugEdgeLocationMap * @throws DataflowAnalysisException */ public void checkEdges(CFG cfg, Map<ValueNumber, NullValueUnconditionalDeref> nullValueGuaranteedDerefMap, Map<ValueNumber, SortedSet<Location>> bugEdgeLocationMap) throws DataflowAnalysisException { // Check every non-exception control edge for (Iterator<Edge> i = cfg.edgeIterator(); i.hasNext();) { Edge edge = i.next(); UnconditionalValueDerefSet uvdFact = uvdDataflow.getFactOnEdge(edge); if (uvdFact.isEmpty()) continue; if (edge.isExceptionEdge()) { if (DEBUG_DEREFS) { System.out.println("On exception edge " + edge.formatAsString(false)); } // continue; } if (DEBUG_DEREFS) { System.out.println("On edge " + edge.formatAsString(false)); } BasicBlock source = edge.getSource(); ValueNumberFrame vnaFact = vnaDataflow.getResultFact(source); IsNullValueFrame invFact = invDataflow.getFactAtMidEdge(edge); Location location = null; if (edge.isExceptionEdge()) { BasicBlock b = cfg.getSuccessorWithEdgeType(source, EdgeTypes.FALL_THROUGH_EDGE); if (b != null) location = new Location(source.getExceptionThrower(), b); } else location = Location.getLastLocation(source); if (location != null) { Instruction in = location.getHandle().getInstruction(); if (assertionMethods.isAssertionInstruction(in, classContext.getConstantPoolGen())) { if (DEBUG_DEREFS) System.out.println("Skipping because it is an assertion method "); continue; } checkForUnconditionallyDereferencedNullValues(location, bugEdgeLocationMap, nullValueGuaranteedDerefMap, vnaFact, invFact, uvdFact, true); } } } private void removeStrictlyPostDominatedLocations(Set<Location> locations, PostDominatorsAnalysis postDomAnalysis) { BitSet strictlyDominated = new BitSet(); for (Location loc : locations) { BitSet allDominatedBy = postDomAnalysis.getAllDominatedBy(loc.getBasicBlock()); allDominatedBy.clear(loc.getBasicBlock().getLabel()); strictlyDominated.or(allDominatedBy); } LinkedList<Location> locations2 = new LinkedList<Location>(locations); for (Iterator<Location> i = locations.iterator(); i.hasNext();) { Location loc = i.next(); if (strictlyDominated.get(loc.getBasicBlock().getLabel())) { i.remove(); continue; } for (Location loc2 : locations2) { if (loc.getBasicBlock().equals(loc2.getBasicBlock()) && loc.getHandle().getPosition() > loc2.getHandle().getPosition()) { i.remove(); break; } } } } private void removeStrictlyDominatedLocations(Set<Location> locations, DominatorsAnalysis domAnalysis) { BitSet strictlyDominated = new BitSet(); for (Location loc : locations) { BitSet allDominatedBy = domAnalysis.getAllDominatedBy(loc.getBasicBlock()); allDominatedBy.clear(loc.getBasicBlock().getLabel()); strictlyDominated.or(allDominatedBy); } LinkedList<Location> locations2 = new LinkedList<Location>(locations); for (Iterator<Location> i = locations.iterator(); i.hasNext();) { Location loc = i.next(); if (strictlyDominated.get(loc.getBasicBlock().getLabel())) { i.remove(); continue; } for (Location loc2 : locations2) { if (loc.getBasicBlock().equals(loc2.getBasicBlock()) && loc.getHandle().getPosition() > loc2.getHandle().getPosition()) { i.remove(); break; } } } } private static final boolean MY_DEBUG = false; /** * Check for unconditionally dereferenced null values at a particular * location in the CFG. * * @param thisLocation * TODO * @param knownNullAndDoomedAt * TODO * @param nullValueGuaranteedDerefMap * map to be populated with null values and where they are * derefed * @param vnaFrame * value number frame to check * @param invFrame * null-value frame to check * @param derefSet * set of unconditionally derefed values at this location * @param isEdge * TODO */ private void checkForUnconditionallyDereferencedNullValues(Location thisLocation, Map<ValueNumber, SortedSet<Location>> knownNullAndDoomedAt, Map<ValueNumber, NullValueUnconditionalDeref> nullValueGuaranteedDerefMap, ValueNumberFrame vnaFrame, IsNullValueFrame invFrame, UnconditionalValueDerefSet derefSet, boolean isEdge) { if (DEBUG_DEREFS) { System.out.println("vna *** " + vnaFrame); System.out.println("inv *** " + invFrame); System.out.println("deref * " + derefSet); } // Make sure the frames contain meaningful information if (!vnaFrame.isValid() || !invFrame.isValid() || vnaFrame.getNumLocals() != invFrame.getNumLocals() || derefSet.isEmpty()) { return; } int slots; if (vnaFrame.getNumSlots() == invFrame.getNumSlots()) slots = vnaFrame.getNumSlots(); else slots = vnaFrame.getNumLocals(); if (false) { InstructionHandle handle = thisLocation.getHandle(); if (handle != null && handle.getInstruction() instanceof NullnessConversationInstruction) { try { IsNullValue isNullValue = invFrame.getStackValue(0); ValueNumber valueNumber = vnaFrame.getStackValue(0); if (!isNullValue.isDefinitelyNotNull() && !isNullValue.isDefinitelyNull() && derefSet.isUnconditionallyDereferenced(valueNumber)) { Location where = thisLocation; SortedSet<Location> doomedAt = knownNullAndDoomedAt.get(valueNumber); if (doomedAt == null) knownNullAndDoomedAt.put(valueNumber, doomedAt = new TreeSet<Location>()); doomedAt.add(thisLocation); NullValueUnconditionalDeref nullValueUnconditionalDeref = nullValueGuaranteedDerefMap.get(valueNumber); if (nullValueUnconditionalDeref != null) { Set<Location> derefLocationSet = nullValueUnconditionalDeref.getDerefLocationSet(); if (derefLocationSet.size() == 1) where = derefLocationSet.iterator().next(); } noteUnconditionallyDereferencedNullValue(where, knownNullAndDoomedAt, nullValueGuaranteedDerefMap, derefSet, isNullValue, valueNumber); } } catch (DataflowAnalysisException e) { AnalysisContext.logError("huh", e); } } } // See if there are any definitely-null values in the frame for (int j = 0; j < slots; j++) { IsNullValue isNullValue = invFrame.getValue(j); ValueNumber valueNumber = vnaFrame.getValue(j); if ((isNullValue.isDefinitelyNull() || isNullValue.isNullOnSomePath() && isNullValue.isReturnValue()) && (derefSet.isUnconditionallyDereferenced(valueNumber))) { if (MY_DEBUG) { System.out.println("Found NP bug"); System.out.println("Location: " + thisLocation); System.out.println("Value number frame: " + vnaFrame); System.out.println("Value number: " + valueNumber); System.out.println("IsNullValue frame: " + invFrame); System.out.println("IsNullValue value: " + isNullValue); System.out.println("Unconditional dere framef: " + derefSet); System.out.println("Unconditionally dereferenced: " + derefSet.isUnconditionallyDereferenced(valueNumber)); } Location where = thisLocation; if (!isEdge && isNullValue.isNullOnSomePath() && isNullValue.isReturnValue()) { try { where = classContext.getCFG(method).getPreviousLocation(where); } catch (CFGBuilderException e) { AnalysisContext.logError( "Error looking for previous instruction to " + where + " in " + classContext.getFullyQualifiedMethodName(method), e); } } noteUnconditionallyDereferencedNullValue(where, knownNullAndDoomedAt, nullValueGuaranteedDerefMap, derefSet, isNullValue, valueNumber); } } // See if there are any known-null values in the heap that // will be dereferenced in the future. for (Map.Entry<ValueNumber, IsNullValue> entry : invFrame.getKnownValueMapEntrySet()) { ValueNumber valueNumber = entry.getKey(); IsNullValue isNullValue = entry.getValue(); if ((isNullValue.isDefinitelyNull() || isNullValue.isNullOnSomePath() && (isNullValue.isReturnValue() || isNullValue.isFieldValue())) && derefSet.isUnconditionallyDereferenced(valueNumber)) { noteUnconditionallyDereferencedNullValue(thisLocation, knownNullAndDoomedAt, nullValueGuaranteedDerefMap, derefSet, isNullValue, valueNumber); } } } /** * Note the locations where a known-null value is unconditionally * dereferenced. * * @param thisLocation * TODO * @param bugLocations * TODO * @param nullValueGuaranteedDerefMap * map of null values to sets of Locations where they are derefed * @param derefSet * set of values known to be unconditionally dereferenced * @param isNullValue * the null value * @param valueNumber * the value number of the null value */ private void noteUnconditionallyDereferencedNullValue(Location thisLocation, Map<ValueNumber, SortedSet<Location>> bugLocations, Map<ValueNumber, NullValueUnconditionalDeref> nullValueGuaranteedDerefMap, UnconditionalValueDerefSet derefSet, IsNullValue isNullValue, ValueNumber valueNumber) { if (DEBUG) { System.out.println("%%% HIT for value number " + valueNumber + " @ " + thisLocation); } Set<Location> unconditionalDerefLocationSet = derefSet.getUnconditionalDerefLocationSet(valueNumber); if (unconditionalDerefLocationSet.isEmpty()) { AnalysisContext.logError("empty set of unconditionally dereferenced locations at " + thisLocation.getHandle().getPosition() + " in " + classContext.getClassDescriptor() + "." + method.getName() + method.getSignature()); return; } // OK, we have a null value that is unconditionally // derferenced. Make a note of the locations where it // will be dereferenced. NullValueUnconditionalDeref thisNullValueDeref = nullValueGuaranteedDerefMap.get(valueNumber); if (thisNullValueDeref == null) { thisNullValueDeref = new NullValueUnconditionalDeref(); nullValueGuaranteedDerefMap.put(valueNumber, thisNullValueDeref); } thisNullValueDeref.add(isNullValue, unconditionalDerefLocationSet); if (thisLocation != null) { SortedSet<Location> locationsForThisBug = bugLocations.get(valueNumber); if (locationsForThisBug == null) { locationsForThisBug = new TreeSet<Location>(); bugLocations.put(valueNumber, locationsForThisBug); } locationsForThisBug.add(thisLocation); } } /** * Examine redundant branches. */ private void examineRedundantBranches() { for (RedundantBranch redundantBranch : redundantBranchList) { if (DEBUG) System.out.println("Redundant branch: " + redundantBranch); int lineNumber = redundantBranch.lineNumber; // The source to bytecode compiler may sometimes duplicate blocks of // code along different control paths. So, to report the bug, // we check to ensure that the branch is REALLY determined each // place it is duplicated, and that it is determined in the same // way. boolean confused = undeterminedBranchSet.get(lineNumber) || (definitelySameBranchSet.get(lineNumber) && definitelyDifferentBranchSet.get(lineNumber)); // confused if there is JSR confusion or multiple null checks with // different results on the same line boolean reportIt = true; if (lineMentionedMultipleTimes.get(lineNumber) && confused) reportIt = false; else if (redundantBranch.location.getBasicBlock().isInJSRSubroutine() /* * occurs * in * a * JSR */ && confused) reportIt = false; else { int pc = redundantBranch.location.getHandle().getPosition(); for (CodeException e : method.getCode().getExceptionTable()) { if (e.getCatchType() == 0 && e.getStartPC() != e.getHandlerPC() && e.getEndPC() <= pc && pc <= e.getEndPC() + 5) reportIt = false; } } if (reportIt) { collector.foundRedundantNullCheck(redundantBranch.location, redundantBranch); } } } private void analyzeRefComparisonBranch(BasicBlock basicBlock, InstructionHandle lastHandle) throws DataflowAnalysisException { Location location = new Location(lastHandle, basicBlock); IsNullValueFrame frame = invDataflow.getFactAtLocation(location); if (!frame.isValid()) { // Probably dead code due to pruning infeasible exception edges. return; } if (frame.getStackDepth() < 2) throw new DataflowAnalysisException("Stack underflow at " + lastHandle); // Find the line number. int lineNumber = getLineNumber(method, lastHandle); if (lineNumber < 0) return; int numSlots = frame.getNumSlots(); IsNullValue top = frame.getValue(numSlots - 1); IsNullValue topNext = frame.getValue(numSlots - 2); boolean definitelySame = top.isDefinitelyNull() && topNext.isDefinitelyNull(); boolean definitelyDifferent = (top.isDefinitelyNull() && topNext.isDefinitelyNotNull()) || (top.isDefinitelyNotNull() && topNext.isDefinitelyNull()); if (definitelySame || definitelyDifferent) { if (definitelySame) { if (DEBUG) System.out.println("Line " + lineNumber + " always same"); definitelySameBranchSet.set(lineNumber); } if (definitelyDifferent) { if (DEBUG) System.out.println("Line " + lineNumber + " always different"); definitelyDifferentBranchSet.set(lineNumber); } RedundantBranch redundantBranch = new RedundantBranch(location, lineNumber, top, topNext); // Figure out which control edge is made infeasible by the redundant // comparison boolean wantSame = (lastHandle.getInstruction().getOpcode() == Constants.IF_ACMPEQ); int infeasibleEdgeType = (wantSame == definitelySame) ? EdgeTypes.FALL_THROUGH_EDGE : EdgeTypes.IFCMP_EDGE; Edge infeasibleEdge = invDataflow.getCFG().getOutgoingEdgeWithType(basicBlock, infeasibleEdgeType); redundantBranch.setInfeasibleEdge(infeasibleEdge); if (DEBUG) System.out.println("Adding redundant branch: " + redundantBranch); redundantBranchList.add(redundantBranch); } else { if (DEBUG) System.out.println("Line " + lineNumber + " undetermined"); undeterminedBranchSet.set(lineNumber); } } // This is called for both IFNULL and IFNONNULL instructions. private void analyzeIfNullBranch(BasicBlock basicBlock, InstructionHandle lastHandle) throws DataflowAnalysisException { Location location = new Location(lastHandle, basicBlock); IsNullValueFrame frame = invDataflow.getFactAtLocation(location); if (!frame.isValid()) { // This is probably dead code due to an infeasible exception edge. return; } IsNullValue top = frame.getTopValue(); // Find the line number. int lineNumber = getLineNumber(method, lastHandle); if (lineNumber < 0) return; if (!(top.isDefinitelyNull() || top.isDefinitelyNotNull())) { if (DEBUG) System.out.println("Line " + lineNumber + " undetermined"); undeterminedBranchSet.set(lineNumber); return; } // Figure out if the branch is always taken // or always not taken. short opcode = lastHandle.getInstruction().getOpcode(); boolean definitelySame = top.isDefinitelyNull(); if (opcode != Constants.IFNULL) definitelySame = !definitelySame; if (definitelySame) { if (DEBUG) System.out.println("Line " + lineNumber + " always same"); definitelySameBranchSet.set(lineNumber); } else { if (DEBUG) System.out.println("Line " + lineNumber + " always different"); definitelyDifferentBranchSet.set(lineNumber); } RedundantBranch redundantBranch = new RedundantBranch(location, lineNumber, top); // Determine which control edge is made infeasible by the redundant // comparison boolean wantNull = (opcode == Constants.IFNULL); int infeasibleEdgeType = (wantNull == top.isDefinitelyNull()) ? EdgeTypes.FALL_THROUGH_EDGE : EdgeTypes.IFCMP_EDGE; Edge infeasibleEdge = invDataflow.getCFG().getOutgoingEdgeWithType(basicBlock, infeasibleEdgeType); redundantBranch.setInfeasibleEdge(infeasibleEdge); if (DEBUG) System.out.println("Adding redundant branch: " + redundantBranch); redundantBranchList.add(redundantBranch); } private void analyzeNullCheck(IsNullValueDataflow invDataflow, BasicBlock basicBlock) throws DataflowAnalysisException, CFGBuilderException { // Look for null checks where the value checked is definitely // null or null on some path. InstructionHandle exceptionThrowerHandle = basicBlock.getExceptionThrower(); Instruction exceptionThrower = exceptionThrowerHandle.getInstruction(); // Get the stack values at entry to the null check. IsNullValueFrame frame = invDataflow.getStartFact(basicBlock); if (!frame.isValid()) return; // Could the reference be null? IsNullValue refValue = frame.getInstance(exceptionThrower, classContext.getConstantPoolGen()); if (DEBUG) { System.out.println("For basic block " + basicBlock + " value is " + refValue); } if (refValue.isDefinitelyNotNull()) return; if (false && !refValue.mightBeNull()) return; if (!refValue.isDefinitelyNull()) return; // Get the value number ValueNumberFrame vnaFrame = classContext.getValueNumberDataflow(method).getStartFact(basicBlock); if (!vnaFrame.isValid()) return; ValueNumber valueNumber = vnaFrame.getInstance(exceptionThrower, classContext.getConstantPoolGen()); Location location = new Location(exceptionThrowerHandle, basicBlock); if (DEBUG) System.out.println("Warning: VN " + valueNumber + " invf: " + frame + " @ " + location); boolean isConsistent = true; Iterator<BasicBlock> bbIter = invDataflow.getCFG().blockIterator(); LineNumberTable table = method.getLineNumberTable(); int position = exceptionThrowerHandle.getPosition(); int line = table == null ? 0 : table.getSourceLine(position); while (bbIter.hasNext()) { BasicBlock bb = bbIter.next(); if (!bb.isNullCheck()) continue; InstructionHandle eth = bb.getExceptionThrower(); if (eth == exceptionThrowerHandle) continue; if (eth.getInstruction().getOpcode() != exceptionThrower.getOpcode()) continue; int ePosition = eth.getPosition(); if (ePosition == position || table != null && line == table.getSourceLine(ePosition)) { IsNullValueFrame frame2 = invDataflow.getStartFact(bb); if (!frame2.isValid()) continue; // apparent clone IsNullValue rv = frame2.getInstance(eth.getInstruction(), classContext.getConstantPoolGen()); if (!rv.equals(refValue)) isConsistent = false; } } // Issue a warning collector.foundNullDeref(location, valueNumber, refValue, vnaFrame, isConsistent); } /** * @deprecated Use * {@link ValueNumberSourceInfo#findXFieldFromValueNumber(Method,Location,ValueNumber,ValueNumberFrame)} * instead */ @Deprecated public static XField findXFieldFromValueNumber(Method method, Location location, ValueNumber valueNumber, ValueNumberFrame vnaFrame) { return ValueNumberSourceInfo.findXFieldFromValueNumber(method, location, valueNumber, vnaFrame); } /** * @deprecated Use * {@link ValueNumberSourceInfo#findFieldAnnotationFromValueNumber(Method,Location,ValueNumber,ValueNumberFrame)} * instead */ @Deprecated public static FieldAnnotation findFieldAnnotationFromValueNumber(Method method, Location location, ValueNumber valueNumber, ValueNumberFrame vnaFrame) { return ValueNumberSourceInfo.findFieldAnnotationFromValueNumber(method, location, valueNumber, vnaFrame); } /** * @deprecated Use * {@link ValueNumberSourceInfo#findLocalAnnotationFromValueNumber(Method,Location,ValueNumber,ValueNumberFrame)} * instead */ @Deprecated public static LocalVariableAnnotation findLocalAnnotationFromValueNumber(Method method, Location location, ValueNumber valueNumber, ValueNumberFrame vnaFrame) { return ValueNumberSourceInfo.findLocalAnnotationFromValueNumber(method, location, valueNumber, vnaFrame); } /** * @param method * TODO * @param location * @param valueNumber * @param vnaFrame * @return the annotation * @deprecated Use * {@link ValueNumberSourceInfo#findRequiredAnnotationFromValueNumber(Method,Location,ValueNumber,ValueNumberFrame, String)} * instead */ @Deprecated public static BugAnnotation findAnnotationFromValueNumber(Method method, Location location, ValueNumber valueNumber, ValueNumberFrame vnaFrame) { return ValueNumberSourceInfo.findRequiredAnnotationFromValueNumber(method, location, valueNumber, vnaFrame, null); } private static int getLineNumber(Method method, InstructionHandle handle) { LineNumberTable table = method.getCode().getLineNumberTable(); if (table == null) return -1; return table.getSourceLine(handle.getPosition()); } }