/* Modeling API uses and misuses via state machines.
   Copyright (C) 2019-2026 Free Software Foundation, Inc.
   Contributed by David Malcolm <dmalcolm@redhat.com>.

This file is part of GCC.

GCC is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.

GCC 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 for more details.

You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3.  If not see
<http://www.gnu.org/licenses/>.  */

#ifndef GCC_ANALYZER_SM_H
#define GCC_ANALYZER_SM_H

#include "analyzer/analyzer-logging.h"

/* Utility functions for use by state machines.  */

namespace ana {

class state_machine;
class sm_context;
class pending_diagnostic;
class analyzer_state_graph;

extern bool any_pointer_p (tree expr);
extern bool any_pointer_p (const svalue *sval);

/* An abstract base class for a state machine describing an API.
   Manages a set of state objects, and has various virtual functions
   for pattern-matching on statements.  */

class state_machine : public log_user
{
public:
  /* States are represented by immutable objects, owned by the state
     machine.  */
  class state
  {
  public:
    state (const char *name, unsigned id) : m_name (name), m_id (id) {}
    virtual ~state () {}

    const char *get_name () const { return m_name; }
    virtual void dump_to_pp (pretty_printer *pp) const;
    virtual std::unique_ptr<json::value> to_json () const;

    unsigned get_id () const { return m_id; }

  private:
    const char *m_name;
    unsigned m_id;
  };
  typedef const state_machine::state *state_t;

  state_machine (const char *name, logger *logger);
  virtual ~state_machine () {}

  /* Should states be inherited from a parent region to a child region,
     when first accessing a child region?
     For example we should inherit the taintedness of a subregion,
     but we should not inherit the "malloc:non-null" state of a field
     within a heap-allocated struct.  */
  virtual bool inherited_state_p () const = 0;

  /* A vfunc for more general handling of inheritance.  */
  virtual state_t
  alt_get_inherited_state (const sm_state_map &,
			   const svalue *,
			   const extrinsic_state &) const
  {
    return nullptr;
  }

  virtual bool
  has_alt_get_inherited_state_p () const
  {
    return false;
  }

  virtual state_machine::state_t get_default_state (const svalue *) const
  {
    return m_start;
  }

  const char *get_name () const { return m_name; }

  state_t get_state_by_name (const char *name) const;

  /* Return true if STMT is a function call recognized by this sm.  */
  virtual bool on_stmt (sm_context &sm_ctxt,
			const gimple *stmt) const = 0;

  virtual void on_phi (sm_context &sm_ctxt ATTRIBUTE_UNUSED,
		       const gphi *phi ATTRIBUTE_UNUSED,
		       tree rhs ATTRIBUTE_UNUSED) const
  {
  }

  virtual void
  check_call_preconditions (sm_context &sm_ctxt ATTRIBUTE_UNUSED,
			    const call_details &cd ATTRIBUTE_UNUSED) const
  {
  }

  virtual void on_condition (sm_context &sm_ctxt ATTRIBUTE_UNUSED,
			     const svalue *lhs ATTRIBUTE_UNUSED,
			     enum tree_code op ATTRIBUTE_UNUSED,
			     const svalue *rhs ATTRIBUTE_UNUSED) const
  {
  }

  virtual void
  on_bounded_ranges (sm_context &sm_ctxt ATTRIBUTE_UNUSED,
		     const svalue &sval ATTRIBUTE_UNUSED,
		     const bounded_ranges &ranges ATTRIBUTE_UNUSED) const
  {
  }

  virtual void
  on_pop_frame (sm_state_map *smap ATTRIBUTE_UNUSED,
		const frame_region *frame_reg ATTRIBUTE_UNUSED) const
  {
  }

  /* Return true if it safe to discard the given state (to help
     when simplifying state objects).
     States that need leak detection should return false.  */
  virtual bool can_purge_p (state_t s) const = 0;

  /* Called when VAR leaks (and !can_purge_p).  */
  virtual std::unique_ptr<pending_diagnostic>
  on_leak (tree var ATTRIBUTE_UNUSED,
	   const program_state *old_state,
	   const program_state *new_state) const;

  /* Return true if S should be reset to "start" for values passed (or reachable
     from) calls to unknown functions.  IS_MUTABLE is true for pointers as
     non-const, false if only passed as const-pointers.

     For example, in sm-malloc.cc, an on-stack ptr doesn't stop being
     stack-allocated when passed to an unknown fn, but a malloc-ed pointer
     could be freed when passed to an unknown fn (unless passed as "const").  */
  virtual bool reset_when_passed_to_unknown_fn_p (state_t s ATTRIBUTE_UNUSED,
						  bool is_mutable) const
  {
    return is_mutable;
  }

  /* Attempt to get a state for the merger of STATE_A and STATE_B,
     or return nullptr if merging shouldn't occur, so that differences
     between sm-state will lead to separate exploded nodes.

     Most state machines will only merge equal states, but can
     override maybe_get_merged_states_nonequal to support mergers
     of certain non-equal states.  */
  state_t maybe_get_merged_state (state_t state_a,
				  state_t state_b) const
  {
    if (state_a == state_b)
      return state_a;
    return maybe_get_merged_states_nonequal (state_a, state_b);
  }

  /* Base implementation of hook for maybe_get_merged_state on non-equal
     states.  */
  virtual state_t
  maybe_get_merged_states_nonequal (state_t state_a ATTRIBUTE_UNUSED,
				    state_t state_b ATTRIBUTE_UNUSED) const
  {
    /* By default, non-equal sm states should inhibit merger of enodes.  */
    return nullptr;
  }

  void validate (state_t s) const;

  void dump_to_pp (pretty_printer *pp) const;

  std::unique_ptr<json::object> to_json () const;

  state_t get_start_state () const { return m_start; }

  virtual void
  add_state_to_state_graph (analyzer_state_graph &out_state_graph,
			    const svalue &sval,
			    state_machine::state_t state) const;

  virtual void
  add_global_state_to_state_graph (analyzer_state_graph &out_state_graph,
				   state_machine::state_t state) const;

protected:
  state_t add_state (const char *name);
  state_t add_custom_state (state *s)
  {
    m_states.safe_push (s);
    return s;
  }

  unsigned alloc_state_id () { return m_next_state_id++; }

private:
  DISABLE_COPY_AND_ASSIGN (state_machine);

  const char *m_name;

  /* States are owned by the state_machine.  */
  auto_delete_vec<state> m_states;

  unsigned m_next_state_id;

protected:
  /* Must be inited after m_next_state_id.  */
  state_t m_start;
};

/* Abstract base class for state machines to pass to
   sm_context::on_custom_transition for handling non-standard transitions
   (e.g. adding a node and edge to simulate registering a callback and having
   the callback be called later).  */

class custom_transition
{
public:
  virtual ~custom_transition () {}
  virtual void impl_transition (exploded_graph *eg,
				exploded_node *src_enode,
				int sm_idx) = 0;
};

/* Abstract base class giving an interface for the state machine to call
   the checker engine, at a particular code location.  */

class sm_context
{
public:
  virtual ~sm_context () {}

  /* Get the fndecl used at call, or NULL_TREE.
     Use in preference to gimple_call_fndecl (and gimple_call_addr_fndecl),
     since it can look through function pointer assignments and
     other callback handling.  */
  virtual tree get_fndecl_for_call (const gcall &call) = 0;

  /* Get the old state of VAR.  */
  virtual state_machine::state_t get_state (tree var) = 0;
  virtual state_machine::state_t get_state (const svalue *) = 0;

  /* Set the next state of VAR to be TO, recording the "origin" of the
     state as ORIGIN.  */
  virtual void set_next_state (tree var,
			       state_machine::state_t to,
			       tree origin = NULL_TREE) = 0;
  virtual void set_next_state (const svalue *var,
			       state_machine::state_t to,
			       tree origin = NULL_TREE) = 0;

  /* Called by state_machine in response to pattern matches:
     if VAR is in state FROM, transition it to state TO, potentially
     recording the "origin" of the state as ORIGIN.  */
  void on_transition (tree var,
		      state_machine::state_t from,
		      state_machine::state_t to,
		      tree origin = NULL_TREE)
  {
    state_machine::state_t current = get_state (var);
    if (current == from)
      set_next_state (var, to, origin);
  }

  void on_transition (const svalue *var,
		      state_machine::state_t from,
		      state_machine::state_t to,
		      tree origin = NULL_TREE)
  {
    state_machine::state_t current = get_state (var);
    if (current == from)
      set_next_state (var, to, origin);
  }

  /* Called by state_machine in response to pattern matches:
     issue a diagnostic D about VAR.  */
  virtual void warn (tree var,
		     std::unique_ptr<pending_diagnostic> d) = 0;
  virtual void warn (const svalue *var,
		     std::unique_ptr<pending_diagnostic> d) = 0;

  /* For use when generating trees when creating pending_diagnostics, so that
     rather than e.g.
       "double-free of '<unknown>'"
     we can print:
       "double-free of 'inbuf.data'".  */
  virtual tree get_diagnostic_tree (tree expr)
  {
    return expr;
  }
  virtual tree get_diagnostic_tree (const svalue *) = 0;

  virtual state_machine::state_t get_global_state () const = 0;
  virtual void set_global_state (state_machine::state_t) = 0;

  virtual void clear_all_per_svalue_state () = 0;

  /* A vfunc for handling custom transitions, such as when registering
     a signal handler.  */
  virtual void on_custom_transition (custom_transition *transition) = 0;

  /* If STMT is an assignment known to assign zero to its LHS, return
     the LHS.
     Otherwise return NULL_TREE.  */
  virtual tree is_zero_assignment (const gimple *stmt) = 0;

  virtual path_context *get_path_context () const
  {
    return nullptr;
  }

  /* Are we handling an external function with unknown side effects?  */
  virtual bool unknown_side_effects_p () const { return false; }

  virtual const program_state *get_old_program_state () const = 0;
  virtual const program_state *get_new_program_state () const = 0;

  const region_model *get_old_region_model () const;

  /* Get the location a diagnostic would be emitted at.  */
  virtual location_t get_emission_location () const = 0;

protected:
  sm_context (int sm_idx, const state_machine &sm)
  : m_sm_idx (sm_idx), m_sm (sm) {}

  int m_sm_idx;
  const state_machine &m_sm;
};


/* The various state_machine subclasses are hidden in their respective
   implementation files.  */

extern std::vector<std::unique_ptr<state_machine>>
make_checkers (logger *logger);

extern std::unique_ptr<state_machine> make_malloc_state_machine (logger *);
extern std::unique_ptr<state_machine> make_fileptr_state_machine (logger *);
extern std::unique_ptr<state_machine> make_taint_state_machine (logger *);
extern std::unique_ptr<state_machine> make_sensitive_state_machine (logger *);
extern std::unique_ptr<state_machine> make_signal_state_machine (logger *);
extern std::unique_ptr<state_machine> make_pattern_test_state_machine (logger *);
extern std::unique_ptr<state_machine> make_va_list_state_machine (logger *);
extern std::unique_ptr<state_machine> make_fd_state_machine (logger *);

} // namespace ana

#endif /* GCC_ANALYZER_SM_H */
