/*
 * Bytecode Analysis Framework
 * Copyright (C) 2003,2004 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;

import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.apache.bcel.generic.MethodGen;

import edu.umd.cs.findbugs.SystemProperties;
import edu.umd.cs.findbugs.ba.ch.Subtypes2;
import edu.umd.cs.findbugs.ba.type.ExceptionSet;
import edu.umd.cs.findbugs.ba.type.TypeDataflow;

/**
 * Prune a CFG to remove infeasible exception edges.
 * In order to determine what kinds of exceptions can be thrown by
 * explicit ATHROW instructions, type analysis must first be
 * performed on the unpruned CFG.
 *
 * @author David Hovemeyer
 * @see CFG
 * @see edu.umd.cs.findbugs.ba.type.TypeAnalysis
 */
public class PruneInfeasibleExceptionEdges implements EdgeTypes {
	private static final boolean DEBUG = SystemProperties.getBoolean("cfg.prune.debug");
	private static final boolean STATS = SystemProperties.getBoolean("cfg.prune.stats");
	private static int numEdgesPruned = 0;

	static {
		if (STATS) {
			Runtime.getRuntime().addShutdownHook(new Thread() {
				@Override
				public void run() {
					System.err.println("Exception edges pruned: " + numEdgesPruned);
				}
			});
		}
	}

	/**
	 * A momento to remind us of how we classified a particular
	 * exception edge.  If pruning and classifying succeeds,
	 * then these momentos can be applied to actually change
	 * the state of the edges.  The issue is that the entire
	 * pruning/classifying operation must either fail or succeed
	 * as a whole.  Thus, we don't commit any CFG changes until
	 * we know everything was successful.
	 */
	private static class MarkedEdge {
		private Edge edge;
		private int flag;

		public MarkedEdge(Edge edge, int flag) {
			this.edge = edge;
			this.flag = flag;
		}

		public void apply() {
			int flags = edge.getFlags();
			flags |= this.flag;
			edge.setFlags(flags);
		}
	}

	private CFG cfg;
	private TypeDataflow typeDataflow;
	private boolean cfgModified;

	/**
	 * Constructor.
	 *
	 * @param cfg          the CFG to prune
	 * @param methodGen    the method
	 * @param typeDataflow initialized TypeDataflow object for the CFG,
	 *                     indicating the types of all stack locations
	 */
	public PruneInfeasibleExceptionEdges(CFG cfg, MethodGen methodGen, TypeDataflow typeDataflow) {
		this.cfg = cfg;
		this.typeDataflow = typeDataflow;
	}

	/**
	 * Prune infeasible exception edges from the CFG.
	 * If the method returns normally, then the operation
	 * was successful, and the CFG should no longer contain infeasible
	 * exception edges.  If ClassNotFoundException or DataflowAnalysisException
	 * are thrown, then the operation was unsuccessful,
	 * <em>but the CFG is still valid because it was not modified</em>.
	 * If a runtime exception is thrown, then the CFG may be
	 * partially modified and should be considered invalid.
	 */
	public void execute() throws ClassNotFoundException {
		Set<Edge> deletedEdgeSet = new HashSet<Edge>();
		List<MarkedEdge> markedEdgeList = new LinkedList<MarkedEdge>();

		// Mark edges to delete,
		// mark edges to set properties of
		for (Iterator<Edge> i = cfg.edgeIterator(); i.hasNext();) {
			Edge edge = i.next();
			if (!edge.isExceptionEdge())
				continue;

			ExceptionSet exceptionSet = typeDataflow.getEdgeExceptionSet(edge);
			if (exceptionSet.isEmpty()) {
				// No exceptions are actually thrown on this edge,
				// so we can delete the edge.
				deletedEdgeSet.add(edge);
			} else {
				if (exceptionSet.isSingleton("java.lang.CloneNotSupportedException") && cfg.getMethodName().endsWith(".clone()")){
					String className = cfg.getMethodGen().getClassName();
					if (Subtypes2.instanceOf(className,"java.lang.Cloneable")) {
						deletedEdgeSet.add(edge);
						continue;
					}
				} 
				// Some exceptions appear to be thrown on the edge.
				// Mark to indicate if any of the exceptions are checked,
				// and if any are explicit (checked or explicitly declared
				// or thrown unchecked).
				boolean someChecked = exceptionSet.containsCheckedExceptions();
				boolean someExplicit = exceptionSet.containsExplicitExceptions();

				int flags = 0;
				if (someChecked) flags |= CHECKED_EXCEPTIONS_FLAG;
				if (someExplicit) flags |= EXPLICIT_EXCEPTIONS_FLAG;

				markedEdgeList.add(new MarkedEdge(edge, flags));
			}
		}

		// Remove deleted edges
		for (Edge edge : deletedEdgeSet) {
			cfg.removeEdge(edge);
			if (STATS) ++numEdgesPruned;
			cfgModified = true;
		}

		// Mark edges
		for (MarkedEdge markedEdge : markedEdgeList) {
			markedEdge.apply();
		}
	}

	/**
	 * @return true if modified
	 */
	public boolean wasCFGModified() {
		return cfgModified;
	}
}

// vim:ts=4
