/* * Bytecode Analysis Framework * Copyright (C) 2003-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.Map; import java.util.Set; import org.apache.bcel.generic.ACONST_NULL; import org.apache.bcel.generic.ANEWARRAY; import org.apache.bcel.generic.CHECKCAST; import org.apache.bcel.generic.ConstantPoolGen; import org.apache.bcel.generic.GETFIELD; import org.apache.bcel.generic.GETSTATIC; import org.apache.bcel.generic.INVOKEINTERFACE; import org.apache.bcel.generic.INVOKESPECIAL; import org.apache.bcel.generic.INVOKESTATIC; import org.apache.bcel.generic.INVOKEVIRTUAL; import org.apache.bcel.generic.Instruction; import org.apache.bcel.generic.InvokeInstruction; import org.apache.bcel.generic.LDC; import org.apache.bcel.generic.LDC2_W; import org.apache.bcel.generic.MULTIANEWARRAY; import org.apache.bcel.generic.NEW; import org.apache.bcel.generic.NEWARRAY; import org.apache.bcel.generic.PUTFIELD; import org.apache.bcel.generic.ReferenceType; import org.apache.bcel.generic.Type; import edu.umd.cs.findbugs.OpcodeStack.Item; import edu.umd.cs.findbugs.SystemProperties; import edu.umd.cs.findbugs.ba.AbstractFrameModelingVisitor; import edu.umd.cs.findbugs.ba.AnalysisContext; import edu.umd.cs.findbugs.ba.AssertionMethods; import edu.umd.cs.findbugs.ba.DataflowAnalysisException; import edu.umd.cs.findbugs.ba.Hierarchy2; import edu.umd.cs.findbugs.ba.Location; import edu.umd.cs.findbugs.ba.NullnessAnnotation; import edu.umd.cs.findbugs.ba.XFactory; import edu.umd.cs.findbugs.ba.XField; import edu.umd.cs.findbugs.ba.XMethod; import edu.umd.cs.findbugs.ba.deref.UnconditionalValueDerefAnalysis; import edu.umd.cs.findbugs.ba.type.TypeDataflow; import edu.umd.cs.findbugs.ba.type.TypeFrame; import edu.umd.cs.findbugs.ba.vna.AvailableLoad; import edu.umd.cs.findbugs.ba.vna.ValueNumber; import edu.umd.cs.findbugs.ba.vna.ValueNumberAnalysisFeatures; import edu.umd.cs.findbugs.ba.vna.ValueNumberDataflow; import edu.umd.cs.findbugs.ba.vna.ValueNumberFrame; public class IsNullValueFrameModelingVisitor extends AbstractFrameModelingVisitor<IsNullValue, IsNullValueFrame> { private static final boolean NO_ASSERT_HACK = SystemProperties.getBoolean("inva.noAssertHack"); private static final boolean MODEL_NONNULL_RETURN = SystemProperties.getBoolean("fnd.modelNonnullReturn", true); private final AssertionMethods assertionMethods; private final ValueNumberDataflow vnaDataflow; private final TypeDataflow typeDataflow; private final boolean trackValueNumbers; private int slotContainingNewNullValue; public IsNullValueFrameModelingVisitor(ConstantPoolGen cpg, AssertionMethods assertionMethods, ValueNumberDataflow vnaDataflow, TypeDataflow typeDataflow, boolean trackValueNumbers) { super(cpg); this.assertionMethods = assertionMethods; this.vnaDataflow = vnaDataflow; this.trackValueNumbers = trackValueNumbers; this.typeDataflow = typeDataflow; } /* * (non-Javadoc) * * @see * edu.umd.cs.findbugs.ba.AbstractFrameModelingVisitor#analyzeInstruction * (org.apache.bcel.generic.Instruction) */ @Override public void analyzeInstruction(Instruction ins) throws DataflowAnalysisException { if (!getFrame().isValid()) return; slotContainingNewNullValue = -1; super.analyzeInstruction(ins); if (!getFrame().isValid()) return; if (!NO_ASSERT_HACK) { if (assertionMethods.isAssertionHandle(getLocation().getHandle(), cpg)) { IsNullValueFrame frame = getFrame(); for (int i = 0; i < frame.getNumSlots(); ++i) { IsNullValue value = frame.getValue(i); if (value.isDefinitelyNull() || value.isNullOnSomePath()) { frame.setValue(i, IsNullValue.nonReportingNotNullValue()); } } for (Map.Entry<ValueNumber, IsNullValue> e : frame.getKnownValueMapEntrySet()) { IsNullValue value = e.getValue(); if (value.isDefinitelyNull() || value.isNullOnSomePath()) e.setValue(IsNullValue.nonReportingNotNullValue()); } } } } /** * @return Returns the slotContainingNewNullValue; or -1 if no new null * value was produced */ public int getSlotContainingNewNullValue() { return slotContainingNewNullValue; } @Override public IsNullValue getDefaultValue() { return IsNullValue.nonReportingNotNullValue(); } // Overrides of specific instruction visitor methods. // ACONST_NULL obviously produces a value that is DEFINITELY NULL. // LDC produces values that are NOT NULL. // NEW produces values that are NOT NULL. // Note that all instructions that have an implicit null // check (field access, invoke, etc.) are handled in IsNullValueAnalysis, // because handling them relies on control flow (the existence of // an ETB and exception edge prior to the block containing the // instruction with the null check.) // Note that we don't override IFNULL and IFNONNULL. // Those are handled in the analysis itself, because we need // to produce different values in each of the control successors. private void produce(IsNullValue value) { IsNullValueFrame frame = getFrame(); frame.pushValue(value); newValueOnTOS(); } private void produce2(IsNullValue value) { IsNullValueFrame frame = getFrame(); frame.pushValue(value); frame.pushValue(value); } /** * Handle method invocations. Generally, we want to get rid of null * information following a call to a likely exception thrower or assertion. */ private void handleInvoke(InvokeInstruction obj) { Type returnType = obj.getReturnType(getCPG()); Location location = getLocation(); if (trackValueNumbers) try { ValueNumberFrame vnaFrame = vnaDataflow.getFactAtLocation(location); Set<ValueNumber> nonnullParameters = UnconditionalValueDerefAnalysis.checkAllNonNullParams(location, vnaFrame, cpg, null, null, typeDataflow); if (!nonnullParameters.isEmpty()) { IsNullValue kaboom = IsNullValue.noKaboomNonNullValue(location); IsNullValueFrame frame = getFrame(); for (ValueNumber vn : nonnullParameters) { IsNullValue knownValue = frame.getKnownValue(vn); if (knownValue != null && knownValue.mightBeNull()) { if (knownValue.isDefinitelyNull()) { frame.setTop(); return; } frame.setKnownValue(vn, kaboom); } for (int i = 0; i < vnaFrame.getNumSlots(); i++) { IsNullValue value = frame.getValue(i); if (vnaFrame.getValue(i).equals(vn) && value.mightBeNull()) { frame.setValue(i, kaboom); if (value.isDefinitelyNull()) { frame.setTop(); return; } } } } } } catch (DataflowAnalysisException e) { AnalysisContext.logError("Error looking up nonnull parameters for invoked method", e); } // Determine if we are going to model the return value of this call. boolean modelCallReturnValue = MODEL_NONNULL_RETURN && returnType instanceof ReferenceType; if (!modelCallReturnValue) { // Normal case: Assume returned values are non-reporting non-null. handleNormalInstruction(obj); } else { // Special case: some special value is pushed on the stack for the // return value IsNullValue result = null; TypeFrame typeFrame; try { typeFrame = typeDataflow.getFactAtLocation(location); Set<XMethod> targetSet = Hierarchy2.resolveMethodCallTargets(obj, typeFrame, cpg); if (targetSet.isEmpty()) { XMethod calledMethod = XFactory.createXMethod(obj, getCPG()); result = getReturnValueNullness(calledMethod); } else for (XMethod calledMethod : targetSet) { IsNullValue pushValue = getReturnValueNullness(calledMethod); if (result == null) result = pushValue; else result = IsNullValue.merge(result, pushValue); } } catch (DataflowAnalysisException e) { result = IsNullValue.nonReportingNotNullValue(); } catch (ClassNotFoundException e) { result = IsNullValue.nonReportingNotNullValue(); } modelInstruction(obj, getNumWordsConsumed(obj), getNumWordsProduced(obj), result); newValueOnTOS(); } } public IsNullValue getReturnValueNullness(XMethod calledMethod) { IsNullValue pushValue; if (IsNullValueAnalysis.DEBUG) System.out.println("Check " + calledMethod + " for null return..."); NullnessAnnotation annotation = AnalysisContext.currentAnalysisContext().getNullnessAnnotationDatabase() .getResolvedAnnotation(calledMethod, false); Boolean alwaysNonNull = AnalysisContext.currentAnalysisContext().getReturnValueNullnessPropertyDatabase() .getProperty(calledMethod.getMethodDescriptor()); if (annotation == NullnessAnnotation.CHECK_FOR_NULL) { if (IsNullValueAnalysis.DEBUG) { System.out.println("Null value returned from " + calledMethod); } pushValue = IsNullValue.nullOnSimplePathValue().markInformationAsComingFromReturnValueOfMethod( calledMethod); } else if (annotation == NullnessAnnotation.NULLABLE) { pushValue = IsNullValue.nonReportingNotNullValue(); } else if (annotation == NullnessAnnotation.NONNULL || (alwaysNonNull != null && alwaysNonNull.booleanValue())) { // Method is declared NOT to return null if (IsNullValueAnalysis.DEBUG) { System.out.println("NonNull value return from " + calledMethod); } pushValue = IsNullValue.nonNullValue().markInformationAsComingFromReturnValueOfMethod(calledMethod); } else { pushValue = IsNullValue.nonReportingNotNullValue(); } return pushValue; } /** * Hook indicating that a new (possibly-null) value is on the top of the * stack. */ private void newValueOnTOS() { IsNullValueFrame frame = getFrame(); if (frame.getStackDepth() < 1) { return; } int tosSlot = frame.getNumSlots() - 1; IsNullValue tos = frame.getValue(tosSlot); if (tos.isDefinitelyNull()) { slotContainingNewNullValue = tosSlot; } if (trackValueNumbers) { try { ValueNumberFrame vnaFrameAfter = vnaDataflow.getFactAfterLocation(getLocation()); if (vnaFrameAfter.isValid()) { ValueNumber tosVN = vnaFrameAfter.getTopValue(); getFrame().setKnownValue(tosVN, tos); } } catch (DataflowAnalysisException e) { AnalysisContext.logError("error", e); } } } @Override public void visitPUTFIELD(PUTFIELD obj) { if (getNumWordsConsumed(obj) != 2) { super.visitPUTFIELD(obj); return; } IsNullValue nullValueStored = null; try { nullValueStored = getFrame().getTopValue(); } catch (DataflowAnalysisException e1) { AnalysisContext.logError("Oops", e1); } super.visitPUTFIELD(obj); XField field = XFactory.createXField(obj, cpg); if (nullValueStored != null && ValueNumberAnalysisFeatures.REDUNDANT_LOAD_ELIMINATION) try { ValueNumberFrame vnaFrameBefore = vnaDataflow.getFactAtLocation(getLocation()); ValueNumber refValue = vnaFrameBefore.getStackValue(1); AvailableLoad load = new AvailableLoad(refValue, field); ValueNumberFrame vnaFrameAfter = vnaDataflow.getFactAfterLocation(getLocation()); ValueNumber[] newValueNumbersForField = vnaFrameAfter.getAvailableLoad(load); if (newValueNumbersForField != null && trackValueNumbers) for (ValueNumber v : newValueNumbersForField) getFrame().setKnownValue(v, nullValueStored); } catch (DataflowAnalysisException e) { AnalysisContext.logError("Oops", e); } } @Override public void visitGETFIELD(GETFIELD obj) { if (getNumWordsProduced(obj) != 1) { super.visitGETFIELD(obj); return; } if (checkForKnownValue(obj)) { return; } XField field = XFactory.createXField(obj, cpg); NullnessAnnotation annotation = AnalysisContext.currentAnalysisContext().getNullnessAnnotationDatabase() .getResolvedAnnotation(field, false); if (annotation == NullnessAnnotation.NONNULL) { modelNormalInstruction(obj, getNumWordsConsumed(obj), 0); produce(IsNullValue.nonNullValue()); } else if (annotation == NullnessAnnotation.CHECK_FOR_NULL) { modelNormalInstruction(obj, getNumWordsConsumed(obj), 0); produce(IsNullValue.nullOnSimplePathValue().markInformationAsComingFromFieldValue(field)); } else { super.visitGETFIELD(obj); } } /* * (non-Javadoc) * * @see * edu.umd.cs.findbugs.ba.AbstractFrameModelingVisitor#visitGETSTATIC(org * .apache.bcel.generic.GETSTATIC) */ @Override public void visitGETSTATIC(GETSTATIC obj) { if (getNumWordsProduced(obj) != 1) { super.visitGETSTATIC(obj); return; } if (checkForKnownValue(obj)) { return; } XField field = XFactory.createXField(obj, cpg); if (field.isFinal()) { Item summary = AnalysisContext.currentAnalysisContext().getFieldSummary().getSummary(field); if (summary.isNull()) { produce(IsNullValue.nullValue()); return; } } if (field.getClassName().equals("java.util.logging.Level") && field.getName().equals("SEVERE") || field.getClassName().equals("org.apache.log4j.Level") && (field.getName().equals("ERROR") || field.getName().equals("FATAL"))) getFrame().toExceptionValues(); if (field.getName().startsWith("class$")) { produce(IsNullValue.nonNullValue()); return; } NullnessAnnotation annotation = AnalysisContext.currentAnalysisContext().getNullnessAnnotationDatabase() .getResolvedAnnotation(field, false); if (annotation == NullnessAnnotation.NONNULL) { modelNormalInstruction(obj, getNumWordsConsumed(obj), 0); produce(IsNullValue.nonNullValue()); } else if (annotation == NullnessAnnotation.CHECK_FOR_NULL) { modelNormalInstruction(obj, getNumWordsConsumed(obj), 0); produce(IsNullValue.nullOnSimplePathValue().markInformationAsComingFromFieldValue(field)); } else { super.visitGETSTATIC(obj); } } /** * Check given Instruction to see if it produces a known value. If so, model * the instruction and return true. Otherwise, do nothing and return false. * Should only be used for instructions that produce a single value on the * top of the stack. * * @param obj * the Instruction the instruction * @return true if the instruction produced a known value and was modeled, * false otherwise */ private boolean checkForKnownValue(Instruction obj) { if (trackValueNumbers) { try { // See if the value number loaded here is a known value ValueNumberFrame vnaFrameAfter = vnaDataflow.getFactAfterLocation(getLocation()); if (vnaFrameAfter.isValid()) { ValueNumber tosVN = vnaFrameAfter.getTopValue(); IsNullValue knownValue = getFrame().getKnownValue(tosVN); if (knownValue != null) { // System.out.println("Produce known value!"); // The value produced by this instruction is known. // Push the known value. modelNormalInstruction(obj, getNumWordsConsumed(obj), 0); produce(knownValue); return true; } } } catch (DataflowAnalysisException e) { // Ignore... } } return false; } @Override public void visitACONST_NULL(ACONST_NULL obj) { produce(IsNullValue.nullValue()); } @Override public void visitNEW(NEW obj) { produce(IsNullValue.nonNullValue()); } @Override public void visitNEWARRAY(NEWARRAY obj) { modelNormalInstruction(obj, getNumWordsConsumed(obj), 0); produce(IsNullValue.nonNullValue()); } @Override public void visitANEWARRAY(ANEWARRAY obj) { modelNormalInstruction(obj, getNumWordsConsumed(obj), 0); produce(IsNullValue.nonNullValue()); } @Override public void visitMULTIANEWARRAY(MULTIANEWARRAY obj) { modelNormalInstruction(obj, getNumWordsConsumed(obj), 0); produce(IsNullValue.nonNullValue()); } @Override public void visitLDC(LDC obj) { produce(IsNullValue.nonNullValue()); } @Override public void visitLDC2_W(LDC2_W obj) { produce2(IsNullValue.nonNullValue()); } @Override public void visitCHECKCAST(CHECKCAST obj) { // Do nothing } @Override public void visitINVOKESTATIC(INVOKESTATIC obj) { handleInvoke(obj); } @Override public void visitINVOKESPECIAL(INVOKESPECIAL obj) { handleInvoke(obj); } @Override public void visitINVOKEINTERFACE(INVOKEINTERFACE obj) { handleInvoke(obj); } @Override public void visitINVOKEVIRTUAL(INVOKEVIRTUAL obj) { handleInvoke(obj); } } // vim:ts=4