package accrue.analysis.interprocanalysis;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import polyglot.ast.ArrayAccess;
import polyglot.ast.Block;
import polyglot.ast.CodeNode;
import polyglot.ast.ConstructorCall;
import polyglot.ast.ConstructorDecl;
import polyglot.ast.Expr;
import polyglot.ast.Field;
import polyglot.ast.Formal;
import polyglot.ast.ProcedureDecl;
import polyglot.ast.Stmt;
import polyglot.frontend.ExtensionInfo;
import polyglot.main.Report;
import polyglot.types.ClassType;
import polyglot.types.CodeInstance;
import polyglot.types.ConstructorInstance;
import polyglot.types.FieldInstance;
import polyglot.types.InitializerInstance;
import polyglot.types.MemberInstance;
import polyglot.types.MethodInstance;
import polyglot.types.SemanticException;
import polyglot.types.Type;
import polyglot.types.TypeSystem;
import polyglot.util.InternalCompilerError;
import accrue.AccrueExtensionInfo;
import accrue.analysis.MissingCodeReporter;
import accrue.analysis.ext.AccrueExt_c;
import accrue.analysis.ext.ExtConstructorCall;
import accrue.analysis.ext.ExtExpr;
import accrue.analysis.ext.ExtFormal;
import accrue.analysis.ext.ExtInitializerOrFieldDecl;
import accrue.analysis.pointer.HContext;
import accrue.analysis.pointer.ObjectField;
import accrue.analysis.pointer.PointerAnalysisPass;
import accrue.analysis.pointer.PointsToGraph;
import accrue.analysis.pointer.ReferenceVariable;
import accrue.analysis.pointer.ReferenceVariableReplica;
import accrue.analysis.pointer.analyses.HeapAbstractionFactory;

/**
 * Abstract utility class for performing an object sensitive analysis.
 * 
 * @param <A> Type of the analysis results
 */
public abstract class AnalysisUtil<A extends Ordered<A>> {

    /**
     * Analysis item work queue
     */
    protected final WorkQueue<A> workQueue;
    /**
     * Compiler extension info
     */
    protected final AccrueExtensionInfo extInfo;

    private boolean inStaticInitializer;

    protected boolean instanceInitializersChecked = false;

    /**
     * The AnalysisUnit currently being analyzed.
     */
    private final AnalysisUnit currentAnalysisUnit;

    /**
     * Track whether this analysis is sound, i.e., all
     * calls to other analysis units produce sound information.
     * This will be set to false if we call the WorkQueue to get analysis
     * results for an AnalysisUnit, and unsound results are returned, e.g.,
     * because the WorkQueue decided that it wouldn't process the AnalysisUnit
     * immediately. (This may happen due to recursive calls.) If this run
     * is unsound, it means that when the callee analysis results are updated,
     * then we will need to re-run the analysis of this unit.
     */
    boolean soundSoFarThisRun = true;

    /**
     * Create a new utility class
     * 
     * @param workQueue Queue of analysis units
     * @param currentAnalysisUnit Current analysis unit from the queue
     * @param extInfo compiler analysis unit
     */
    public AnalysisUtil(WorkQueue<A> workQueue,
            AnalysisUnit currentAnalysisUnit, AccrueExtensionInfo extInfo) {
        this.workQueue = workQueue;
        this.currentAnalysisUnit = currentAnalysisUnit;
        this.extInfo = extInfo;
        if (currentAnalysisUnit.cn.codeInstance() instanceof InitializerInstance) {
            InitializerInstance ii =
                    (InitializerInstance) currentAnalysisUnit.cn.codeInstance();
            this.inStaticInitializer = ii.flags().isStatic();
        }
    }

    /**
     * @return the compiler extension info
     */
    public AccrueExtensionInfo extensionInfo() {
        return extInfo;
    }

    /**
     * @return the Polyglot {@link TypeSystem}
     */
    public TypeSystem typeSystem() {
        return extInfo.typeSystem();
    }

    /**
     * @return queue of analysis units
     */
    public WorkQueue<A> workQueue() {
        return workQueue;
    }

    /**
     * @return analysis unit currently being analyzed
     */
    public AnalysisUnit currentAnalysisUnit() {
        return this.currentAnalysisUnit;
    }

    /**
     * @return the context of the current analysis unit
     */
    public AnalysisContext currentContext() {
        return this.currentAnalysisUnit.context();
    }

    /**
     * @return the Polyglot {@link ClassType} for the current analysis unit
     */
    public ClassType currentClass() {
        if (this.inStaticInitializer) {
            throw new InternalCompilerError("Can't call currentClass from in a static initializer, since the information doesn't apply");
        }
        CodeInstance pi = currentAnalysisUnit.codeNode().codeInstance();
        if (pi instanceof ConstructorInstance) {
            return (ClassType) ((ConstructorInstance) pi).container();
        }
        else if (pi instanceof MethodInstance) {
            return (ClassType) ((MethodInstance) pi).container();
        }
        else if (pi instanceof InitializerInstance) {
            return (ClassType) ((InitializerInstance) pi).container();
        }
        else throw new InternalCompilerError("Unknown code instance " + pi);
    }

    /**
     * Create a map from exit key to the given analysis result
     * 
     * @param input
     *            analysis result to put into the exit map
     */
    public ExitMap<A> createExitMap(A input) {
        return workQueue.createExitMap(input);
    }

    /**
     * Check if a call to the given analysis unit from the current analysis unit
     * could be recursive
     * 
     * @param callee
     *            target of the potentially recursive call
     * @return <code>true</code> if the current analysis unit make a recursive call on the
     *         callee
     */
    protected boolean maybeRecursiveCall(AnalysisUnit callee) {
        AnalysisUnit caller = this.currentAnalysisUnit;
        return this.workQueue.maybeRecursiveCall(caller, callee);
    }

    /**
     * @return Upper bound of <code>x</code> and <code>y</code>
     */
    public static <O extends Ordered<O>> O upperBound(O x, O y) {
        if (x == null) return y;
        if (y == null) return x;
        return x.upperBound(y);
    }

    /**
     * @return <code>true</code> if <code>x</code> is less than <code>y</code>
     */
    public static <O extends Ordered<O>> boolean leq(O x, O y) {
        if (x == null) return true;
        return x.leq(y);
    }

    /**
     * Run the analysis on the given code and current results (
     * <code>input</code>)
     * 
     * @param cn
     *            Code to analyze
     * @param input
     *            current analysis results
     * @return Map from exit condition to analysis result
     * @throws SemanticException
     *             thrown if the analysis fails
     */
    public ExitMap<A> process(CodeNode cn, A input) throws SemanticException {
        ExitMap<A> res;

        if (cn instanceof ProcedureDecl
                && Registrar.isStartProc(((ProcedureDecl) cn).procedureInstance())) {
            // we are a start procedure, so analyze the static initializers first
            res = analyzeStaticInitializers(input);
        }
        else {
            res = workQueue.createExitMap(input);
        }
        A nt = res.normalTermination();
        res = res.removeNormalTermination();

        res = res.upperBound(analyze(cn, nt));

        if (cn instanceof ConstructorDecl) {
            if (!this.instanceInitializersChecked) {
                // should have analyzed initializers if constructor contains a call to super.
                if (containsCallToSuper((ConstructorDecl) cn)) {
                    // find the super, and check if it can terminate normally.
                    ConstructorCall cc =
                            findConstructorCall(((ConstructorDecl) cn).body());
                    ExtConstructorCall extcc =
                            (ExtConstructorCall) AccrueExt_c.ext(cc);
                    if (extcc.canTerminateNormally(this.currentContext())) {
                        // the super call can terminate normally, but we
                        // haven't checked the initializers.
                        throw new InternalCompilerError("Instance initializers were not checked during the "
                                                                + "analysis of a constructor in the analysis "
                                                                + this.analysisName()
                                                                + ". You have probably forgotten to call AnalysisUtil.analyzeInitializers "
                                                                + "after analyzing the constructor call to the super class.",
                                                        cn.position());
                    }
                }
            }
        }

        return res;
    }

    /**
     * Check if a constructor contains a call to the <code>super</code> class constructor
     * @param pd Constructor to check
     * @return true if the constructor contains a call to the <code>super</code> class constructor
     */
    private boolean containsCallToSuper(ConstructorDecl pd) {
        if (findConstructorCall(pd.body()) == null) {
            return false;
        }
        return ConstructorCall.SUPER.equals(findConstructorCall(pd.body()).kind());
    }

    /**
     * Find the constructor call in the given statement if there is any
     * @param s statement to search
     * @return {@link ConstructorCall} in the given statement if any, null otherwise
     */
    private ConstructorCall findConstructorCall(Stmt s) {
        if (s instanceof ConstructorCall) return (ConstructorCall) s;
        if (s instanceof Block) {
            List<Stmt> statements = ((Block) s).statements();
            for (Stmt t : statements) {
                ConstructorCall cc = findConstructorCall(t);
                if (cc != null) return cc;
            }
        }
        return null;
    }

    /**
     * Analyze the initializers for the current analysis unit given the current
     * analysis results
     * 
     * @param input
     *            current analysis results
     * @return Map from exit condition to analysis result
     * @throws SemanticException
     *             thrown if the analysis fails
     */
    public ExitMap<A> analyzeInitializers(A input) throws SemanticException {
        if (Report.should_report(this.workQueue.factory().reportTopics(), 3))
            Report.report(1, "Analyzing initializers in " + currentClass()
                    + " for " + analysisName());

        this.instanceInitializersChecked = true;
        List<CodeNode> toProcess =
                workQueue.registrar().getInstanceInitializers(currentClass());

        ExitMap<A> res = workQueue().emptyExitMap();
        A nt = input;
        if (Report.should_report(this.workQueue.factory().reportTopics(), 2))
            Report.report(1, "Processing " + toProcess.size()
                    + " instance initializers");

        for (CodeNode i : toProcess) {
            if (Report.should_report(this.workQueue.factory().reportTopics(), 3))
                Report.report(2, "Processing an instance initializer " + i);

            if (i.codeInstance().flags().isStatic()) {
                throw new InternalCompilerError("Should only have instance initializers here");
            }
            // analyze the initializer using an analysis context that is the same as this.currentContext(),
            // but with the construct context updated to be the current constructor.
            // In particular, this should ensure that the "this" local node in the constructor refers
            // to the same abstract object(s) that the "this" local node in the initializer
            // refers to.
            AnalysisUnit calleeAUnit =
                    new AnalysisUnit(i,
                                     this.currentContext()
                                         .constructorContext((ConstructorInstance) this.currentAnalysisUnit()
                                                                                       .procedureDecl()
                                                                                       .procedureInstance()));
            res =
                    res.upperBound(this.workQueue.getAnalysisResult(calleeAUnit,
                                                                    nt,
                                                                    this));
            nt = res.normalTermination();
            res = res.removeNormalTermination();
        }
        return res.normalTermination(nt);
    }

    /**
     * Analyze the static initializers for the current analysis unit given the current
     * analysis results
     * 
     * @param input
     *            current analysis results
     * @return Map from exit condition to analysis result
     * @throws SemanticException
     *             thrown if the analysis fails
     */
    protected ExitMap<A> analyzeStaticInitializers(A input)
            throws SemanticException {
        if (Report.should_report(this.workQueue.factory().reportTopics(), 2))
            Report.report(1, "Analyzing all static initializers for "
                    + analysisName());

        List<CodeNode> toProcess =
                workQueue.registrar().getAllOrderedStaticInitializers();
        if (toProcess == null) {
            // doesn't exist yet, let's create the initial list
            toProcess = new ArrayList<CodeNode>();
            for (ClassType ct : workQueue.registrar()
                                         .getClassesWithStaticInitializers()) {
                for (CodeNode i : workQueue.registrar()
                                           .getStaticInitializers(ct)) {
                    toProcess.add(i);
                }
            }
        }

        List<CodeNode> processOrder = new ArrayList<CodeNode>(toProcess.size());
        ExitMap<A> res = workQueue().emptyExitMap();
        A nt = input;
        int lastSize = 0;
        while (lastSize != toProcess.size()) {
            // we're still making progress reducing the size of initializers
            if (Report.should_report(this.workQueue.factory().reportTopics(), 2))
                Report.report(1, "Processing " + toProcess.size()
                        + " static initializers");

            lastSize = toProcess.size();
            List<CodeNode> processNow = toProcess;
            toProcess = new ArrayList<CodeNode>();
            for (CodeNode i : processNow) {
                this.inStaticInitializer = true;
                if (Report.should_report(this.workQueue.factory()
                                                       .reportTopics(), 3))
                    Report.report(2, "Processing a static initializer " + i);

                if (!i.codeInstance().flags().isStatic()) {
                    throw new InternalCompilerError("Should only have static initializers here");
                }
                // analyze the initializer             
                AnalysisUnit calleeAUnit =
                        new AnalysisUnit(i, this.currentContext());
                res =
                        res.upperBound(this.workQueue.getAnalysisResult(calleeAUnit,
                                                                        nt,
                                                                        this));
                nt = res.normalTermination();

                if (nt == null) {
                    System.err.println("OK, craziness for "
                            + calleeAUnit.codeNode().position() + " in "
                            + calleeAUnit.context());
                }
                res = res.removeNormalTermination();

                processOrder.add(i);

                this.inStaticInitializer = false;
            }
        }

        // now we have successfully processed everything in processOrder,
        // but have failed to process things in toProcess.
        if (!toProcess.isEmpty()) {
            // there were initializers we couldn't finish.
            throw new SemanticException("Unable to finish analyzing static initializers, there is no linear order suitable. Sorry.");
        }
        if (Report.should_report(this.workQueue.factory().reportTopics(), 3))
            Report.report(1,
                          "Finished trying to process static initializers, there are "
                                  + toProcess.size() + " left to do");

        processOrder.addAll(toProcess);
        workQueue().registrar().setAllOrderedStaticInitializers(processOrder);

        return res.normalTermination(nt);
    }

    /**
     * Analyze the given code with the given current analysis results
     * 
     * @param n
     *            Code to analyze
     * @param before
     *            analysis result before analyzing the given code
     * @return Map from exit condition to analysis result
     * @throws SemanticException
     *             thrown if the analysis fails
     */
    public abstract ExitMap<A> analyze(CodeNode n, A before)
            throws SemanticException;

    /**
     * @return Name of the current analysis
     */
    public String analysisName() {
        return workQueue().factory().analysisName();
    }

    /**
     * Get the set of abstract locations the given field could point to
     * 
     * @param f
     *            field to check
     * @return Set of abstract locations the given field could point to
     */
    public Set<AbstractLocation> abstractLocations(Field f) {
        Set<AbstractLocation> s;
        FieldInstance fi = f.fieldInstance();
        if (fi.flags().isStatic()) {
            s = Collections.singleton(AbstractLocation.create(fi));
        }
        else {
            Expr receiver = (Expr) f.target();
            s = new LinkedHashSet<AbstractLocation>();
            for (HContext on : pointsTo(receiver)) {
                s.add(AbstractLocation.create(on, fi));
            }
        }
        return s;
    }

    /**
     * Get the set of abstract locations the given field could point to. 
     * NOTE: if the field is an instance field, then we assume that the
     * target is implicitly "this". 
     * 
     * @param fi
     *            field to check
     * @return Set of abstract locations the given field could point to
     */
    public Set<AbstractLocation> abstractLocationsImplicitTarget(
            FieldInstance fi) {
        Set<AbstractLocation> s;
        if (fi.flags().isStatic()) {
            s = Collections.singleton(AbstractLocation.create(fi));
        }
        else {
            s = new LinkedHashSet<AbstractLocation>();
            // Is this a field of the current class?
            CodeNode cn = this.currentAnalysisUnit.codeNode();
            MemberInstance mi = (MemberInstance) cn.codeInstance();
            if (mi.container().isSubtype(fi.container())
                    || fi.container().isSubtype(mi.container())) {
                // The containers of the field and the code instance are in a subtype relation.
                // So it's OK to use the field instance of the "this" objects.
                for (HContext on : pointsToThis()) {
                    s.add(AbstractLocation.create(on, fi));
                }
            }
            else {
                throw new InternalCompilerError("Are you sure you want to use this method for field instance "
                        + fi
                        + " when the current analysis unit is in "
                        + mi.container() + "?");
            }
        }
        return s;
    }

    /**
     * Get the set of abstract locations the given field in the given receiver
     * could reference
     * 
     * @param receiver
     *            Expression containing the field
     * @param f
     *            field to check
     * @return Set of abstract locations the given field could point to
     */
    public Set<AbstractLocation> abstractLocations(Expr receiver,
            FieldInstance fi) {
        if (fi.flags().isStatic()) {
            throw new InternalCompilerError("Do you really want to use this method for a static field?");
        }
        Set<AbstractLocation> s = new LinkedHashSet<AbstractLocation>();
        for (HContext on : pointsTo(receiver)) {
            s.add(AbstractLocation.create(on, fi));
        }
        return s;
    }

    /**
     * Get the set of abstract locations the given field in the given context
     * could reference
     * 
     * @param receiver
     *            Context to evaluate the points-to set in
     * @param f
     *            field to check
     * @return Set of abstract locations the given field could point to
     */
    public Set<AbstractLocation> abstractLocations(HContext receiver,
            FieldInstance fi) {
        if (fi.flags().isStatic()) {
            throw new InternalCompilerError("Do you really want to use this method for a static field?");
        }
        Set<AbstractLocation> s = new LinkedHashSet<AbstractLocation>();
        s.add(AbstractLocation.create(receiver, fi));
        return s;
    }

    /**
     * Get the set of abstract locations the given field in the given context
     * could reference
     * 
     * @param receiver
     *            Context to evaluate the points-to set in
     * @param f
     *            name of field to check
     * @return Set of abstract locations the given field could point to
     */
    public Set<AbstractLocation> abstractLocations(HContext receiver,
            String fieldName) {
        FieldInstance fi = receiver.type().toReference().fieldNamed(fieldName);
        if (fi != null) {
            throw new InternalCompilerError("You used a field name to look up the field, but there is a "
                    + "field instance. Should probably use another method to do this.");
        }
        Set<AbstractLocation> s = new LinkedHashSet<AbstractLocation>();
        s.add(AbstractLocation.create(receiver, fieldName));
        return s;
    }

    /**
     * Get the set of abstract locations the given field in the given formal parameter
     * could reference
     * 
     * @param f
     *            formal parameter containing the field
     * @param fi
     *            field to check
     * @return Set of abstract locations the given field could point to
     */
    public Set<AbstractLocation> abstractLocations(Formal f, FieldInstance fi) {
        if (fi.flags().isStatic()) {
            throw new InternalCompilerError("Do you really want to use this method for a static field?");
        }
        Set<AbstractLocation> s = new LinkedHashSet<AbstractLocation>();
        for (HContext on : pointsTo(f)) {
            s.add(AbstractLocation.create(on, fi));
        }
        return s;
    }

    /**
     * if path = f1, f2, f3, then returns abstract locations for e.f1.f2.f3
     * if types = t1.t2.t3, then e.f1 is of type t1, e.f1.f2 is of type t2, etc.
     */
    public Set<AbstractLocation> abstractLocations(Expr e, List<String> path,
            List<Type> types) {
        Set<AbstractLocation> s = new LinkedHashSet<AbstractLocation>();
        Set<HContext> objectNames = pointsTo(e);
        Type t = e.type();

        for (int i = 0; i < path.size() - 1; i++) {
            String fieldName = path.get(i);
            try {
                FieldInstance fi =
                        t.typeSystem().findField(t.toReference(), fieldName);
                objectNames = pointsTo(objectNames, fi);
                t = fi.type();
            }
            catch (SemanticException notFound) {
                objectNames = pointsTo(objectNames, fieldName, types.get(i));
                t = types.get(i);
            }
        }
        for (HContext on : objectNames) {
            String fieldName = path.get(path.size() - 1);
            try {
                FieldInstance fi =
                        t.typeSystem().findField(t.toReference(), fieldName);
                s.add(AbstractLocation.create(on, fi));
            }
            catch (SemanticException notFound) {
                s.add(AbstractLocation.create(on, fieldName));
            }
        }
        return s;
    }

    /**
     * @return The abstract locations the array being accessed could point to
     */
    public Set<AbstractLocation> abstractLocations(ArrayAccess aa) {
        Expr receiver = aa.array();
        Set<AbstractLocation> s = new LinkedHashSet<AbstractLocation>();
        for (HContext on : pointsTo(receiver)) {
            s.add(AbstractLocation.create(on, AbstractLocation.ARRAY_CONTENTS));
        }
        return s;
    }

    /**
     * @param array
     *            Expression representing the array to get the points-to set for
     * 
     * @return The abstract locations for the contents of the array
     */
    public Set<AbstractLocation> abstractLocationsForArray(Expr array) {
        if (!array.type().isArray()) {
            throw new InternalCompilerError("Only suitable for arrays",
                                            array.position());
        }
        return abstractLocationsForArray(pointsTo(array));
    }

    /**
     * @return The abstract locations for the contents of the array
     */
    public Set<AbstractLocation> abstractLocationsForArray(
            Set<HContext> arrayObjects) {
        Set<AbstractLocation> s = new LinkedHashSet<AbstractLocation>();
        for (HContext on : arrayObjects) {
            if (!on.type().isArray()) {
                throw new InternalCompilerError("Only suitable for arrays");
            }
            s.add(AbstractLocation.create(on, AbstractLocation.ARRAY_CONTENTS));
        }
        return s;
    }

    /**
     * @return The abstract locations for the contents of the array
     */
    public Set<AbstractLocation> abstractLocationsForArray(Formal f) {
        if (!f.localInstance().type().isArray()) {
            throw new InternalCompilerError("Only suitable for arrays",
                                            f.position());
        }
        return abstractLocationsForArray(pointsTo(f));
    }

    /**
     * The set of heap objects the node could point to given the
     * {@link AnalysisContext}
     * 
     * @param ln
     *            node to get the points-to set for
     * @param context
     *            Procedure analysis context
     * @param extInfo
     *            Compiler extension info
     * @return points-to set for the given node
     */
    protected static Set<HContext> pointsTo(ReferenceVariable ln,
            AnalysisContext context, AccrueExtensionInfo extInfo) {
        PointsToGraph g =
                PointerAnalysisPass.singleton(extInfo).pointsToGraph();
        HeapAbstractionFactory af = extInfo.heapAbstractionFactory();
        return g.pointsTo(ReferenceVariableReplica.create(context.getCContext(),
                                                          ln,
                                                          af));
    }

    /**
     * @param Compiler extension info
     * @return The entire points-to graph
     */
    public static PointsToGraph pointsToGraph(ExtensionInfo extInfo) {
        PointsToGraph g =
                PointerAnalysisPass.singleton(extInfo).pointsToGraph();
        return g;
    }

    /**
     * Get the points-to set for the given expression in the current analysis context
     * @param e expression
     * @return points-to set for <code>e</code>
     */
    public Set<HContext> pointsTo(Expr e) {
        return pointsTo(e, this.currentContext(), this.extInfo);
    }

    /**
     * The set of heap objects the expression could point to given the
     * {@link AnalysisContext}
     * 
     * @param e
     *            expression to get the points-to set for
     * @param context
     *            Procedure analysis context
     * @param extInfo
     *            Compiler extension info
     * @return points-to set for the given expression
     */
    public static Set<HContext> pointsTo(Expr e, AnalysisContext context,
            AccrueExtensionInfo extInfo) {
        ExtExpr ee = (ExtExpr) AccrueExt_c.ext(e);
        ReferenceVariable ln =
                ee.getReferenceVariable(context.getConstructorContext());
        if (ln == null) {
            throw new InternalCompilerError("No local node for pointsTo for "
                    + e.getClass().getName() + " : " + e
                    + " (in constructor context "
                    + context.getConstructorContext() + ")", e.position());
        }
        return pointsTo(ln, context, extInfo);
    }

    /**
     * Return the pointsto set for "this" in the current analysis context.
     * Will throw an exception if the current analysis context is
     * a static context.
     * @return points-to set for the "this" variable in the current scope.
     * 
     */
    public Set<HContext> pointsToThis() {
        CodeNode cn = this.currentAnalysisUnit.codeNode();
        // Get the LocalNode representing "this" for the CodeNode.

        ExtInitializerOrFieldDecl ext =
                (ExtInitializerOrFieldDecl) AccrueExt_c.ext(cn);

        return pointsTo(ext.getThisNode(this.currentContext()
                                            .getConstructorContext()),
                        this.currentContext(),
                        this.extInfo);
    }

    /**
     * @return points-to set for the given formal in the current context
     */
    public Set<HContext> pointsTo(Formal f) {
        return pointsTo(f, this.currentContext(), this.extInfo);
    }

    /**
     * The set of heap objects the formal could point to given the
     * {@link AnalysisContext}
     * 
     * @param f
     *            formal to get the points-to set for
     * @param context
     *            Procedure analysis context
     * @param extInfo
     *            Compiler extension info
     * @return points-to set for the given expression
     */
    public static Set<HContext> pointsTo(Formal f, AnalysisContext context,
            AccrueExtensionInfo extInfo) {
        ExtFormal ee = (ExtFormal) AccrueExt_c.ext(f);
        ReferenceVariable ln =
                ee.getReferenceVariable(context.getConstructorContext());
        if (ln == null) {
            throw new InternalCompilerError("No local node for pointsTo",
                                            f.position());
        }
        return pointsTo(ln, context, extInfo);
    }

    /**
     * Return the set of abstract objects that may be pointed to by the
     * field <code>field</code> of type <code>type</type> of the abstract objects os. 
     * That is, if o is in os, and
     * the field <code>field</code> of the abstract object o may point to object m, and m is a subtype of
     * <code>type</code> then m is in the
     * set returned by this method. The pointsTo graph associated with the extInfo field 
     * of this AnalysisUtil is used.
     */
    public Set<HContext> pointsTo(Set<HContext> os, String field, Type type) {
        return pointsTo(os, field, type, extInfo);
    }

    /**
     * Return the set of abstract objects that may be pointed to by the
     * field <code>field</code> of type <code>type</type> of the abstract objects os. 
     * That is, if o is in os, and
     * the field <code>field</code> of the abstract object o may point to object m, and m is a subtype of
     * <code>type</code> then m is in the
     * set returned by this method. The pointsTo graph associated with extInfo is used.
     */
    public static Set<HContext> pointsTo(Set<HContext> os, String field,
            Type type, ExtensionInfo extInfo) {
        PointsToGraph g =
                PointerAnalysisPass.singleton(extInfo).pointsToGraph();
        Set<HContext> pointsTo = new LinkedHashSet<HContext>();
        for (HContext o : os) {
            pointsTo.addAll(g.pointsTo(ObjectField.create(o, field, type)));
        }
        return pointsTo;
    }

    /**
     * Return the set of abstract objects that may be pointed to by the
     * field instance fi of the abstract objects os. That is, if o is in os, and
     * the field fi of the abstract object o may point to object m, then m is in the
     * set returned by this method. The pointsTo graph associated with the extInfo field 
     * of this AnalysisUtil is used.
     */
    public Set<HContext> pointsTo(Set<HContext> os, FieldInstance fi) {
        return pointsTo(os, fi, extInfo);
    }

    /**
     * Return the set of abstract objects that may be pointed to by the
     * field instance fi of the abstract objects os. That is, if o is in os, and
     * the field fi of the abstract object o may point to object m, then m is in the
     * set returned by this method. The pointsTo graph associated with extInfo is used.
     */
    public static Set<HContext> pointsTo(Set<HContext> os, FieldInstance fi,
            ExtensionInfo extInfo) {
        PointsToGraph g =
                PointerAnalysisPass.singleton(extInfo).pointsToGraph();
        Set<HContext> pointsTo = new LinkedHashSet<HContext>();
        for (HContext o : os) {
            pointsTo.addAll(g.pointsTo(ObjectField.create(o, fi)));
        }
        return pointsTo;
    }

    /**
     * Get the set of abstract locations the given field in the given context
     * parameter could reference
     * 
     * @param extInfo
     *            Compiler extension info
     * @param context
     *            Procedure analysis context
     * @param f
     *            field to check
     * @return Set of abstract locations the given field could point to
     */
    public static Set<AbstractLocation> abstractLocations(
            AccrueExtensionInfo extInfo, AnalysisContext context, Field f) {
        Set<AbstractLocation> s;
        FieldInstance fi = f.fieldInstance();
        if (fi.flags().isStatic()) {
            s = Collections.singleton(AbstractLocation.create(fi));
        }
        else {
            Expr receiver = (Expr) f.target();
            s = new LinkedHashSet<AbstractLocation>();
            for (HContext on : pointsTo(receiver, context, extInfo)) {
                s.add(AbstractLocation.create(on, fi));
            }
        }
        return s;
    }

    /**
     * Get the set of abstract locations the given array access in the given context
     * parameter could reference
     * 
     * @param extInfo
     *            Compiler extension info
     * @param context
     *            Procedure analysis context
     * @param aa
     *            array access to check
     * @return Set of abstract locations the given field could point to
     */
    public static Set<AbstractLocation> abstractLocations(
            AccrueExtensionInfo extInfo, AnalysisContext context, ArrayAccess aa) {
        Expr receiver = aa.array();
        Set<AbstractLocation> s = new LinkedHashSet<AbstractLocation>();
        for (HContext on : pointsTo(receiver, context, extInfo)) {
            s.add(AbstractLocation.create(on, AbstractLocation.ARRAY_CONTENTS));
        }
        return s;
    }

    /**
     * @return List of instance initializers and non-static field declarations
     */
    public List<CodeNode> instanceInitializers(ClassType container) {
        return workQueue.registrar().getInstanceInitializers(container);
    }

    /**
     * Add the missing code to the compiler debug report
     * 
     * @param ci
     *            missing code
     */
    public void recordMissingCode(CodeInstance ci) {
        if (ci == null) throw new InternalCompilerError("null ci");
        MissingCodeReporter.reportMissingCode(analysisName(), ci);
        if (Report.should_report(this.workQueue().factory().reportTopics(), 3)) {
            Report.report(3, " *** missing code for " + ci + " "
                    + "in analysis " + analysisName());
        }
    }

}
