/*
 * IntraproceduralCFG.java - This file is part of the Jakstab project.
 * Copyright 2007-2015 Johannes Kinder <jk@jakstab.org>
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code 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 General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, see <http://www.gnu.org/licenses/>.
 */
package org.jakstab.cfa;

import java.util.HashSet;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import org.jakstab.Options;
import org.jakstab.Program;
import org.jakstab.asm.AbsoluteAddress;
import org.jakstab.rtl.statements.RTLAssume;
import org.jakstab.rtl.statements.RTLCallReturn;
import org.jakstab.rtl.statements.RTLStatement;
import org.jakstab.util.FastSet;
import org.jakstab.util.Logger;
import org.jakstab.util.Worklist;

public class IntraproceduralCFG extends ControlFlowGraph {

	private static final Logger logger = Logger.getLogger(IntraproceduralCFG.class);
	
	public IntraproceduralCFG(ControlFlowGraph cfg, String procName) {
		this(cfg, Program.getProgram().getAddressForSymbol(procName));
	}

	public IntraproceduralCFG(ControlFlowGraph cfg, AbsoluteAddress procedureHead) {
		super();
		if (procedureHead == null) {
			logger.error("IntraproceduralCFG constructor called with NULL procedure head");
			return;
		}
		
		Location headLoc = findLocation(procedureHead, cfg);
		if (headLoc == null)
			return;
		Set<CFAEdge> edges = filterEdges(cfg, headLoc);
		buildFromEdgeSet(edges);
	}
	
	private Location findLocation(AbsoluteAddress a, ControlFlowGraph cfg) {
		SortedSet<Location> candidates = new TreeSet<Location>();
		for (Location l : cfg.getNodes()) {
			if (a.equals(l.getAddress()))
				candidates.add(l);
		}
		logger.verbose("Found " + candidates.size() + " candidates for procedure entry, returning first one of " + candidates);
		if (candidates.size() == 0) {
			logger.error("Did not find procedure in CFG!");
			return null;
		}
		return candidates.iterator().next();
	}

	private Set<CFAEdge> filterEdges(ControlFlowGraph cfg, Location procHead) {
		Worklist<Location> worklist = new FastSet<Location>(procHead);
		Set<Location> visited = new HashSet<Location>();
		Set<CFAEdge> result = new HashSet<CFAEdge>();
		
		while (!worklist.isEmpty()) {
			Location loc = worklist.pick(); 
			for (CFAEdge e : cfg.getOutEdges(loc)) {
				
				RTLStatement stmt = (RTLStatement)e.getTransformer();
				if (stmt instanceof RTLAssume) {
					RTLAssume a = (RTLAssume)stmt;
					if (a.isCall()) {
						if (Options.procedureAbstraction.getValue() != 1) {
							if (a.getSource().getNextLabel() != null) {
								Location returnLoc = findReturnLocation(cfg, e, a.getSource().getNextLabel());
								
								if (returnLoc == null) {
									logger.verbose("Non-returning call to procedure " + 
											Program.getProgram().getSymbolFor(e.getTarget().getLabel()));
								} else {
									RTLCallReturn callReturn = new RTLCallReturn();
									callReturn.setLabel(a.getLabel());
									callReturn.setNextLabel(returnLoc.getLabel());
									CFAEdge fallThroughEdge = new CFAEdge(e.getSource(), returnLoc, callReturn);
									logger.debug("Generated fall-through edge " + fallThroughEdge);
									
									// Replace e with fallthrough edge to add it further down
									e = fallThroughEdge;
								}
							}
						} else {
							// don't add fall through in interproc analysis, the callReturn edge should be enough
							// TODO: Check if this works with VPC CFGs
							continue;
						}						
					} else if (a.isReturn()) {
						// don't add/follow the return edge, it delimits the procedure
						continue;
					}					
				} 
				
				result.add(e);
				
				if (visited.contains(e.getTarget()))
					continue;
				else {
					visited.add(e.getTarget());
					worklist.add(e.getTarget());
				}
			}
		}
		
		return result;
	}
	
	private Location findReturnLocation(ControlFlowGraph cfg, CFAEdge callEdge, RTLLabel returnLabel) {
		Worklist<Location> worklist = new FastSet<Location>(callEdge.getTarget());
		Set<Location> visited = new HashSet<Location>();
		while (!worklist.isEmpty()) {
			Location l = worklist.pick();
			for (CFAEdge e : cfg.getOutEdges(l)) {
				Location targetLoc = e.getTarget(); 
				if (visited.contains(targetLoc))
					continue;
				visited.add(targetLoc);
				
				if (targetLoc.getLabel().equals(returnLabel))
					return targetLoc;
				
				worklist.add(targetLoc);
			}
		}
		logger.debug("Could not find return location for call " + callEdge + " supposed to return to " + returnLabel);
		return null;
	}

}
