/*
 * fb-contrib - Auxiliary detectors for Java programs
 * Copyright (C) 2005-2016 Dave Brosius
 *
 * 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 com.mebigfatguy.fbcontrib.detect;

import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.Type;

import com.mebigfatguy.fbcontrib.utils.BugType;

import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.BytecodeScanningDetector;
import edu.umd.cs.findbugs.OpcodeStack;
import edu.umd.cs.findbugs.ba.ClassContext;

/**
 * looks for methods that are defined to return Boolean, but return null. This
 * thus allows three return values, Boolean.FALSE, Boolean.TRUE and null. If
 * three values intended, it would be more clear to just create an enumeration
 * with three values and return that type.
 */
public class TristateBooleanPattern extends BytecodeScanningDetector {
    private BugReporter bugReporter;
    private OpcodeStack stack;
    private boolean methodReported;

    /**
     * constructs a TBP detector given the reporter to report bugs on
     *
     * @param bugReporter
     *            the sync of bug reports
     */
    public TristateBooleanPattern(BugReporter bugReporter) {
        this.bugReporter = bugReporter;
    }

    /**
     * implements the visitor to allocate the opcode stack
     *
     * @param classContext
     *            the context object of the currently parsed class
     */
    @Override
    public void visitClassContext(ClassContext classContext) {
        try {
            stack = new OpcodeStack();
            super.visitClassContext(classContext);
        } finally {
            stack = null;
        }
    }

    /**
     * implements the visitor to filter out methods that don't return Boolean,
     * and to reset the methodReported flag
     *
     * @param obj
     *            the context object for the currently parsed code block
     */
    @Override
    public void visitCode(Code obj) {
        Method m = getMethod();
        Type retType = m.getReturnType();
        if ("Ljava/lang/Boolean;".equals(retType.getSignature())) {
            stack.resetForMethodEntry(this);
            methodReported = false;
            super.visitCode(obj);
        }
    }

    /**
     * implements the visitor to look for null returns
     */
    @Override
    public void sawOpcode(int seen) {
        try {
            if (methodReported)
                return;

            stack.precomputation(this);

            if ((seen == ARETURN) && (stack.getStackDepth() > 0)) {
                OpcodeStack.Item item = stack.getStackItem(0);
                if (item.isNull()) {
                    bugReporter.reportBug(new BugInstance(this, BugType.TBP_TRISTATE_BOOLEAN_PATTERN.name(), NORMAL_PRIORITY).addClass(this).addMethod(this)
                            .addSourceLine(this));
                    methodReported = true;
                }
            }
        } finally {
            stack.sawOpcode(this, seen);
        }
    }

}

@@ -1037,24 +1037,91 @@
 ils.BugType;
+%0Aimport com.mebigfatguy.fbcontrib.utils.StopOpcodeParsingException;
 %0A%0Aimport edu
@@ -1410,19 +1410,16 @@
 ll. This
-%0A *
  thus al
@@ -1488,17 +1488,17 @@
 . If
-%0A *
  three
+%0A *
  val
@@ -1563,19 +1563,16 @@
 meration
-%0A *
  with th
@@ -1750,44 +1750,8 @@
 ack;
-%0A    private boolean methodReported;
 %0A%0A  
@@ -2534,45 +2534,8 @@
    *
- and to reset the methodReported flag
 %0A   
@@ -2685,24 +2685,42 @@
 Code obj) %7B%0A
+        try %7B%0A    
         Meth
@@ -2747,16 +2747,20 @@
         
+   
+ 
 Type ret
@@ -2781,24 +2781,28 @@
 turnType();%0A
+    
         if (
@@ -2865,24 +2865,28 @@
             
+    
 stack.resetF
@@ -2922,65 +2922,146 @@
     
+  
-methodReported = false;%0A            super.visitCode(obj);
+  super.visitCode(obj);%0A            %7D%0A        %7D catch (StopOpcodeParsingException e) %7B%0A            // if the method was already reported
 %0A   
@@ -3214,64 +3214,8 @@
 ry %7B
-%0A            if (methodReported)%0A                return;
 %0A%0A  
@@ -3653,29 +3653,46 @@
     
-methodReported = true
+throw new StopOpcodeParsingException()
 ;%0A  

throw StopOpcodeParsingException if a tristate boolean is found and reported