/*
 * Copyright (c) 2002 - 2012 IBM Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 */
package com.ibm.wala.cast.js.callgraph.fieldbased;

import com.ibm.wala.cast.js.callgraph.fieldbased.flowgraph.FlowGraph;
import com.ibm.wala.cast.js.callgraph.fieldbased.flowgraph.vertices.FuncVertex;
import com.ibm.wala.cast.js.callgraph.fieldbased.flowgraph.vertices.VertexFactory;
import com.ibm.wala.cast.js.ssa.JavaScriptInvoke;
import com.ibm.wala.cast.js.types.JavaScriptTypes;
import com.ibm.wala.cast.loader.AstMethod;
import com.ibm.wala.cast.types.AstMethodReference;
import com.ibm.wala.classLoader.IClass;
import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.ipa.callgraph.AnalysisOptions;
import com.ibm.wala.ipa.callgraph.IAnalysisCacheView;
import com.ibm.wala.ipa.cha.IClassHierarchy;
import com.ibm.wala.ssa.DefUse;
import com.ibm.wala.ssa.IR;
import com.ibm.wala.ssa.SSAInstruction;
import com.ibm.wala.ssa.SymbolTable;
import com.ibm.wala.types.TypeReference;
import com.ibm.wala.util.MonitorUtil.IProgressMonitor;
import com.ibm.wala.util.collections.Iterator2Iterable;

/**
 * Call graph builder for building pessimistic call graphs, where inter-procedural flows are not
 * tracked except in the trivial case of local calls. This builder is fast, but in general less
 * sound than {@link OptimisticCallgraphBuilder}.
 *
 * @author mschaefer
 */
public class PessimisticCallGraphBuilder extends FieldBasedCallGraphBuilder {
  public PessimisticCallGraphBuilder(
      IClassHierarchy cha,
      AnalysisOptions options,
      IAnalysisCacheView iAnalysisCacheView,
      boolean supportFullPointerAnalysis) {
    super(cha, options, iAnalysisCacheView, supportFullPointerAnalysis);
  }

  @Override
  public FlowGraph buildFlowGraph(IProgressMonitor monitor) {
    FlowGraph flowgraph = flowGraphFactory();
    resolveLocalCalls(flowgraph);
    return flowgraph;
  }

  protected boolean filterFunction(IMethod function) {
    return function.getDescriptor().equals(AstMethodReference.fnDesc);
  }

  // add inter-procedural flow for local calls
  private void resolveLocalCalls(FlowGraph flowgraph) {
    for (IClass klass : cha) {
      for (IMethod method : klass.getDeclaredMethods()) {
        if (filterFunction(method)) {
          IR ir = cache.getIR(method);
          ir.visitAllInstructions(
              new LocalCallSSAVisitor(method, ir.getSymbolTable(), cache.getDefUse(ir), flowgraph));
        }
      }
    }
  }

  /**
   * This visitor looks for calls where the callee can be determined locally by a def-use graph, and
   * adds inter-procedural edges.
   *
   * @author mschaefer
   */
  private class LocalCallSSAVisitor extends JSMethodInstructionVisitor {
    private final FlowGraph flowgraph;
    private final VertexFactory factory;
    private final FuncVertex caller;

    public LocalCallSSAVisitor(IMethod method, SymbolTable symtab, DefUse du, FlowGraph flowgraph) {
      super(method, symtab, du);
      this.flowgraph = flowgraph;
      this.factory = flowgraph.getVertexFactory();
      this.caller = this.factory.makeFuncVertex(method.getDeclaringClass());
    }

    @Override
    public void visitJavaScriptInvoke(JavaScriptInvoke invk) {
      // check whether this instruction corresponds to a function expression/declaration
      if (isFunctionConstructorInvoke(invk)) {
        int defn = invk.getDef();

        // the name of the function
        String fnName = symtab.getStringValue(invk.getUse(1));
        IClass fnClass =
            cha.lookupClass(TypeReference.findOrCreate(JavaScriptTypes.jsLoader, fnName));
        if (fnClass == null) {
          System.err.println(
              "cannot find " + fnName + " at " + ((AstMethod) method).getSourcePosition());
          return;
        }
        IMethod fn = fnClass.getMethod(AstMethodReference.fnSelector);
        FuncVertex callee = factory.makeFuncVertex(fnClass);

        // look at all uses
        for (SSAInstruction use : Iterator2Iterable.make(du.getUses(defn))) {

          // check whether this is a local call
          if (use instanceof JavaScriptInvoke && ((JavaScriptInvoke) use).getFunction() == defn) {
            JavaScriptInvoke use_invk = (JavaScriptInvoke) use;

            // yes, so add edges from arguments to parameters...
            for (int i = 2; i < use_invk.getNumberOfPositionalParameters(); ++i)
              flowgraph.addEdge(
                  factory.makeVarVertex(caller, use_invk.getUse(i)),
                  factory.makeParamVertex(callee, i));

            // ...and from return to result
            flowgraph.addEdge(
                factory.makeRetVertex(callee), factory.makeVarVertex(caller, use.getDef()));

            // note: local calls are never qualified, so there is no flow into the receiver vertex
          } else {
            // no, it's a more complicated use, so add flows from/to unknown
            for (int i = 1; i < fn.getNumberOfParameters(); ++i)
              flowgraph.addEdge(factory.makeUnknownVertex(), factory.makeParamVertex(callee, i));
            flowgraph.addEdge(factory.makeRetVertex(callee), factory.makeUnknownVertex());
          }
        }
      } else {
        // this is a genuine function call; find out where the function came from
        SSAInstruction def = du.getDef(invk.getFunction());

        // if it's not a local call, add flows from/to unknown
        if (!(def instanceof JavaScriptInvoke)
            || !isFunctionConstructorInvoke((JavaScriptInvoke) def)) {
          for (int i = 1; i < invk.getNumberOfPositionalParameters(); ++i)
            flowgraph.addEdge(
                factory.makeVarVertex(caller, invk.getUse(i)), factory.makeUnknownVertex());
          flowgraph.addEdge(
              factory.makeUnknownVertex(), factory.makeVarVertex(caller, invk.getDef()));
        }
      }
    }
  }
}
