// See the file "COPYING" in the main distribution directory for copyright.

// Class for generating identifier definition information by traversing
// a function body's AST.

#pragma once

#include "zeek/script_opt/IDOptInfo.h"
#include "zeek/script_opt/ProfileFunc.h"

namespace zeek::detail {

class GenIDDefs : public TraversalCallback {
public:
    GenIDDefs(std::shared_ptr<ProfileFunc> _pf, const FuncPtr& f, ScopePtr scope, StmtPtr body);

private:
    // Traverses the given function body, using the first two
    // arguments for context.
    void TraverseFunction(const FuncPtr& f, ScopePtr scope, StmtPtr body);

    TraversalCode PreStmt(const Stmt*) override;
    void AnalyzeSwitch(const SwitchStmt* sw);

    TraversalCode PostStmt(const Stmt*) override;
    TraversalCode PreExpr(const Expr*) override;
    TraversalCode PostExpr(const Expr*) override;

    // Analyzes the target of an assignment.  Returns true if the LHS
    // was an expression for which we can track it as a definition
    // (e.g., assignments to variables, but not to elements of
    // aggregates).  "rhs" gives the expression used for simple direct
    // assignments.
    bool CheckLHS(const ExprPtr& lhs, const ExprPtr& rhs = nullptr);

    // True if the given expression directly represents an aggregate.
    bool IsAggr(const ExprPtr& e) const { return IsAggr(e.get()); }
    bool IsAggr(const Expr* e) const;

    // If -u is active, checks for whether the given identifier present
    // in the given expression is undefined at that point.
    void CheckVarUsage(const Expr* e, const ID* id);

    // Begin a new confluence block with the given statement.
    void StartConfluenceBlock(const Stmt* s);

    // Finish up the current confluence block.  If no_orig_flow is true,
    // then there's no control flow from the origin (the statement that
    // starts the block).
    void EndConfluenceBlock(bool no_orig_flow = false);

    // Note branches from the given "from" statement back up to the
    // beginning of, or just past, the "to" statement.  If "close_all"
    // is true then the nature of the branch is that it terminates
    // all pending confluence blocks.
    void BranchBackTo(const Stmt* from, const Stmt* to, bool close_all);
    void BranchBeyond(const Stmt* from, const Stmt* to, bool close_all);

    // These search back through the active confluence blocks looking
    // for either the innermost loop, or the innermost block for which
    // a "break" would target going beyond that block.
    const Stmt* FindLoop();
    const Stmt* FindBreakTarget();

    // Note that the given statement executes a "return" (which could
    // instead be an outer "break" for a hook).
    void ReturnAt(const Stmt* s);

    // Tracks that the given identifier is defined at the current
    // statement in the current confluence block.  'e' is the
    // expression used to define the identifier, for simple direct
    // assignments.
    void TrackID(const IDPtr& id, const ExprPtr& e = nullptr) { TrackID(id.get(), e); }
    void TrackID(const ID* id, const ExprPtr& e = nullptr);

    // Profile for the function.  Currently, all we actually need from
    // this is the list of globals and locals.
    std::shared_ptr<ProfileFunc> pf;

    // Whether the Func is an event/hook/function.  We currently only
    // need to know whether it's a hook, so we correctly interpret an
    // outer "break" in that context.
    FunctionFlavor func_flavor;

    // The most recently traversed statement.
    const Stmt* last_stmt_traversed = nullptr;

    // Used to number Stmt objects found during AST traversal.
    int stmt_num;

    // A stack of confluence blocks, with the innermost at the top/back.
    std::vector<const Stmt*> confluence_blocks;

    // Stack of confluence blocks corresponding to activate catch-return
    // statements.  We used to stop identifier definitions at these
    // boundaries, but given there's limited confluence (i.e., the body of
    // the catch-return *will* execute, up through its first return), we
    // can do broader optimization if we don't treat them as their own
    // (new) confluence blocks.
    std::vector<zeek_uint_t> cr_active;

    // The following is parallel to confluence_blocks except
    // the front entry tracks identifiers at the outermost
    // (non-confluence) scope.  Thus, to index it for a given
    // confluence block i, we need to use i+1.
    std::vector<IDSet> modified_IDs;

    // If non-zero, indicates we should suspend any generation
    // of usage errors.  A counter rather than a boolean because
    // such situations might nest.
    int suppress_usage = 0;
};

} // namespace zeek::detail
