view gcc/analyzer/sm-malloc.cc @ 145:1830386684a0

gcc-9.2.0
author anatofuz
date Thu, 13 Feb 2020 11:34:05 +0900
parents
children
line wrap: on
line source

/* A state machine for detecting misuses of the malloc/free API.
   Copyright (C) 2019-2020 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/>.  */

#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "tree.h"
#include "function.h"
#include "basic-block.h"
#include "gimple.h"
#include "options.h"
#include "bitmap.h"
#include "diagnostic-path.h"
#include "diagnostic-metadata.h"
#include "function.h"
#include "analyzer/analyzer.h"
#include "diagnostic-event-id.h"
#include "analyzer/analyzer-logging.h"
#include "analyzer/sm.h"
#include "analyzer/pending-diagnostic.h"

#if ENABLE_ANALYZER

namespace ana {

namespace {

/* A state machine for detecting misuses of the malloc/free API.

   See sm-malloc.dot for an overview (keep this in-sync with that file).  */

class malloc_state_machine : public state_machine
{
public:
  malloc_state_machine (logger *logger);

  bool inherited_state_p () const FINAL OVERRIDE { return false; }

  bool on_stmt (sm_context *sm_ctxt,
		const supernode *node,
		const gimple *stmt) const FINAL OVERRIDE;

  void on_phi (sm_context *sm_ctxt,
	       const supernode *node,
	       const gphi *phi,
	       tree rhs) const FINAL OVERRIDE;

  void on_condition (sm_context *sm_ctxt,
		     const supernode *node,
		     const gimple *stmt,
		     tree lhs,
		     enum tree_code op,
		     tree rhs) const FINAL OVERRIDE;

  bool can_purge_p (state_t s) const FINAL OVERRIDE;
  pending_diagnostic *on_leak (tree var) const FINAL OVERRIDE;

  /* Start state.  */
  state_t m_start;

  /* State for a pointer returned from malloc that hasn't been checked for
     NULL.
     It could be a pointer to heap-allocated memory, or could be NULL.  */
  state_t m_unchecked;

  /* State for a pointer that's known to be NULL.  */
  state_t m_null;

  /* State for a pointer to heap-allocated memory, known to be non-NULL.  */
  state_t m_nonnull;

  /* State for a pointer to freed memory.  */
  state_t m_freed;

  /* State for a pointer that's known to not be on the heap (e.g. to a local
     or global).  */
  state_t m_non_heap; // TODO: or should this be a different state machine?
  // or do we need child values etc?

  /* Stop state, for pointers we don't want to track any more.  */
  state_t m_stop;

private:
  void on_zero_assignment (sm_context *sm_ctxt,
			   const supernode *node,
			   const gimple *stmt,
			   tree lhs) const;
};

/* Class for diagnostics relating to malloc_state_machine.  */

class malloc_diagnostic : public pending_diagnostic
{
public:
  malloc_diagnostic (const malloc_state_machine &sm, tree arg)
  : m_sm (sm), m_arg (arg)
  {}

  bool subclass_equal_p (const pending_diagnostic &base_other) const OVERRIDE
  {
    return same_tree_p (m_arg, ((const malloc_diagnostic &)base_other).m_arg);
  }

  label_text describe_state_change (const evdesc::state_change &change)
    OVERRIDE
  {
    if (change.m_old_state == m_sm.m_start
	&& change.m_new_state == m_sm.m_unchecked)
      // TODO: verify that it's the allocation stmt, not a copy
      return label_text::borrow ("allocated here");
    if (change.m_old_state == m_sm.m_unchecked
	&& change.m_new_state == m_sm.m_nonnull)
      return change.formatted_print ("assuming %qE is non-NULL",
				     change.m_expr);
    if (change.m_new_state == m_sm.m_null)
      return change.formatted_print ("assuming %qE is NULL",
				     change.m_expr);
    return label_text ();
  }

protected:
  const malloc_state_machine &m_sm;
  tree m_arg;
};

/* Concrete subclass for reporting double-free diagnostics.  */

class double_free : public malloc_diagnostic
{
public:
  double_free (const malloc_state_machine &sm, tree arg)
  : malloc_diagnostic (sm, arg)
  {}

  const char *get_kind () const FINAL OVERRIDE { return "double_free"; }

  bool emit (rich_location *rich_loc) FINAL OVERRIDE
  {
    auto_diagnostic_group d;
    diagnostic_metadata m;
    m.add_cwe (415); /* CWE-415: Double Free.  */
    return warning_meta (rich_loc, m, OPT_Wanalyzer_double_free,
			 "double-%<free%> of %qE", m_arg);
  }

  label_text describe_state_change (const evdesc::state_change &change)
    FINAL OVERRIDE
  {
    if (change.m_new_state == m_sm.m_freed)
      {
	m_first_free_event = change.m_event_id;
	return change.formatted_print ("first %qs here", "free");
      }
    return malloc_diagnostic::describe_state_change (change);
  }

  label_text describe_call_with_state (const evdesc::call_with_state &info)
    FINAL OVERRIDE
  {
    if (info.m_state == m_sm.m_freed)
      return info.formatted_print
	("passing freed pointer %qE in call to %qE from %qE",
	 info.m_expr, info.m_callee_fndecl, info.m_caller_fndecl);
    return label_text ();
  }

  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
  {
    if (m_first_free_event.known_p ())
      return ev.formatted_print ("second %qs here; first %qs was at %@",
				 "free", "free",
				 &m_first_free_event);
    return ev.formatted_print ("second %qs here", "free");
  }

private:
  diagnostic_event_id_t m_first_free_event;
};

/* Abstract subclass for describing possible bad uses of NULL.
   Responsible for describing the call that could return NULL.  */

class possible_null : public malloc_diagnostic
{
public:
  possible_null (const malloc_state_machine &sm, tree arg)
  : malloc_diagnostic (sm, arg)
  {}

  label_text describe_state_change (const evdesc::state_change &change)
    FINAL OVERRIDE
  {
    if (change.m_old_state == m_sm.m_start
	&& change.m_new_state == m_sm.m_unchecked)
      {
	m_origin_of_unchecked_event = change.m_event_id;
	return label_text::borrow ("this call could return NULL");
      }
    return malloc_diagnostic::describe_state_change (change);
  }

  label_text describe_return_of_state (const evdesc::return_of_state &info)
    FINAL OVERRIDE
  {
    if (info.m_state == m_sm.m_unchecked)
      return info.formatted_print ("possible return of NULL to %qE from %qE",
				   info.m_caller_fndecl, info.m_callee_fndecl);
    return label_text ();
  }

protected:
  diagnostic_event_id_t m_origin_of_unchecked_event;
};

/* Concrete subclass for describing dereference of a possible NULL
   value.  */

class possible_null_deref : public possible_null
{
public:
  possible_null_deref (const malloc_state_machine &sm, tree arg)
  : possible_null (sm, arg)
  {}

  const char *get_kind () const FINAL OVERRIDE { return "possible_null_deref"; }

  bool emit (rich_location *rich_loc) FINAL OVERRIDE
  {
    /* CWE-690: Unchecked Return Value to NULL Pointer Dereference.  */
    diagnostic_metadata m;
    m.add_cwe (690);
    return warning_meta (rich_loc, m,
			 OPT_Wanalyzer_possible_null_dereference,
			 "dereference of possibly-NULL %qE", m_arg);
  }

  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
  {
    if (m_origin_of_unchecked_event.known_p ())
      return ev.formatted_print ("%qE could be NULL: unchecked value from %@",
				 ev.m_expr,
				 &m_origin_of_unchecked_event);
    else
      return ev.formatted_print ("%qE could be NULL", ev.m_expr);
  }

};

/* Subroutine for use by possible_null_arg::emit and null_arg::emit.
   Issue a note informing that the pertinent argument must be non-NULL.  */

static void
inform_nonnull_attribute (tree fndecl, int arg_idx)
{
  inform (DECL_SOURCE_LOCATION (fndecl),
	  "argument %u of %qD must be non-null",
	  arg_idx + 1, fndecl);
  /* Ideally we would use the location of the parm and underline the
     attribute also - but we don't have the location_t values at this point
     in the middle-end.
     For reference, the C and C++ FEs have get_fndecl_argument_location.  */
}

/* Concrete subclass for describing passing a possibly-NULL value to a
   function marked with __attribute__((nonnull)).  */

class possible_null_arg : public possible_null
{
public:
  possible_null_arg (const malloc_state_machine &sm, tree arg,
		     tree fndecl, int arg_idx)
  : possible_null (sm, arg),
    m_fndecl (fndecl), m_arg_idx (arg_idx)
  {}

  const char *get_kind () const FINAL OVERRIDE { return "possible_null_arg"; }

  bool subclass_equal_p (const pending_diagnostic &base_other) const
  {
    const possible_null_arg &sub_other
      = (const possible_null_arg &)base_other;
    return (same_tree_p (m_arg, sub_other.m_arg)
	    && m_fndecl == sub_other.m_fndecl
	    && m_arg_idx == sub_other.m_arg_idx);
  }


  bool emit (rich_location *rich_loc) FINAL OVERRIDE
  {
    /* CWE-690: Unchecked Return Value to NULL Pointer Dereference.  */
    auto_diagnostic_group d;
    diagnostic_metadata m;
    m.add_cwe (690);
    bool warned
      = warning_meta (rich_loc, m, OPT_Wanalyzer_possible_null_argument,
		      "use of possibly-NULL %qE where non-null expected",
		      m_arg);
    if (warned)
      inform_nonnull_attribute (m_fndecl, m_arg_idx);
    return warned;
  }

  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
  {
    if (m_origin_of_unchecked_event.known_p ())
      return ev.formatted_print ("argument %u (%qE) from %@ could be NULL"
				 " where non-null expected",
				 m_arg_idx + 1, ev.m_expr,
				 &m_origin_of_unchecked_event);
    else
      return ev.formatted_print ("argument %u (%qE) could be NULL"
				 " where non-null expected",
				 m_arg_idx + 1, ev.m_expr);
  }

private:
  tree m_fndecl;
  int m_arg_idx;
};

/* Concrete subclass for describing a dereference of a NULL value.  */

class null_deref : public malloc_diagnostic
{
public:
  null_deref (const malloc_state_machine &sm, tree arg)
  : malloc_diagnostic (sm, arg) {}

  const char *get_kind () const FINAL OVERRIDE { return "null_deref"; }

  bool emit (rich_location *rich_loc) FINAL OVERRIDE
  {
    /* CWE-690: Unchecked Return Value to NULL Pointer Dereference.  */
    diagnostic_metadata m;
    m.add_cwe (690);
    return warning_meta (rich_loc, m,
			 OPT_Wanalyzer_null_dereference,
			 "dereference of NULL %qE", m_arg);
  }

  label_text describe_return_of_state (const evdesc::return_of_state &info)
    FINAL OVERRIDE
  {
    if (info.m_state == m_sm.m_null)
      return info.formatted_print ("return of NULL to %qE from %qE",
				   info.m_caller_fndecl, info.m_callee_fndecl);
    return label_text ();
  }

  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
  {
    return ev.formatted_print ("dereference of NULL %qE", ev.m_expr);
  }
};

/* Concrete subclass for describing passing a NULL value to a
   function marked with __attribute__((nonnull)).  */

class null_arg : public malloc_diagnostic
{
public:
  null_arg (const malloc_state_machine &sm, tree arg,
	    tree fndecl, int arg_idx)
  : malloc_diagnostic (sm, arg),
    m_fndecl (fndecl), m_arg_idx (arg_idx)
  {}

  const char *get_kind () const FINAL OVERRIDE { return "null_arg"; }

  bool subclass_equal_p (const pending_diagnostic &base_other) const
  {
    const null_arg &sub_other
      = (const null_arg &)base_other;
    return (same_tree_p (m_arg, sub_other.m_arg)
	    && m_fndecl == sub_other.m_fndecl
	    && m_arg_idx == sub_other.m_arg_idx);
  }

  bool emit (rich_location *rich_loc) FINAL OVERRIDE
  {
    /* CWE-690: Unchecked Return Value to NULL Pointer Dereference.  */
    auto_diagnostic_group d;
    diagnostic_metadata m;
    m.add_cwe (690);
    bool warned = warning_meta (rich_loc, m, OPT_Wanalyzer_null_argument,
				"use of NULL %qE where non-null expected",
				m_arg);
    if (warned)
      inform_nonnull_attribute (m_fndecl, m_arg_idx);
    return warned;
  }

  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
  {
    return ev.formatted_print ("argument %u (%qE) NULL"
			       " where non-null expected",
			       m_arg_idx + 1, ev.m_expr);
  }

private:
  tree m_fndecl;
  int m_arg_idx;
};

class use_after_free : public malloc_diagnostic
{
public:
  use_after_free (const malloc_state_machine &sm, tree arg)
  : malloc_diagnostic (sm, arg)
  {}

  const char *get_kind () const FINAL OVERRIDE { return "use_after_free"; }

  bool emit (rich_location *rich_loc) FINAL OVERRIDE
  {
    /* CWE-416: Use After Free.  */
    diagnostic_metadata m;
    m.add_cwe (416);
    return warning_meta (rich_loc, m, OPT_Wanalyzer_use_after_free,
			 "use after %<free%> of %qE", m_arg);
  }

  label_text describe_state_change (const evdesc::state_change &change)
    FINAL OVERRIDE
  {
    if (change.m_new_state == m_sm.m_freed)
      {
	m_free_event = change.m_event_id;
	return label_text::borrow ("freed here");
      }
    return malloc_diagnostic::describe_state_change (change);
  }

  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
  {
    if (m_free_event.known_p ())
      return ev.formatted_print ("use after %<free%> of %qE; freed at %@",
				 ev.m_expr, &m_free_event);
    else
      return ev.formatted_print ("use after %<free%> of %qE", ev.m_expr);
  }

private:
  diagnostic_event_id_t m_free_event;
};

class malloc_leak : public malloc_diagnostic
{
public:
  malloc_leak (const malloc_state_machine &sm, tree arg)
  : malloc_diagnostic (sm, arg) {}

  const char *get_kind () const FINAL OVERRIDE { return "malloc_leak"; }

  bool emit (rich_location *rich_loc) FINAL OVERRIDE
  {
    diagnostic_metadata m;
    m.add_cwe (401);
    return warning_meta (rich_loc, m, OPT_Wanalyzer_malloc_leak,
			 "leak of %qE", m_arg);
  }

  label_text describe_state_change (const evdesc::state_change &change)
    FINAL OVERRIDE
  {
    if (change.m_new_state == m_sm.m_unchecked)
      {
	m_malloc_event = change.m_event_id;
	return label_text::borrow ("allocated here");
      }
    return malloc_diagnostic::describe_state_change (change);
  }

  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
  {
    if (m_malloc_event.known_p ())
      return ev.formatted_print ("%qE leaks here; was allocated at %@",
				 ev.m_expr, &m_malloc_event);
    else
      return ev.formatted_print ("%qE leaks here", ev.m_expr);
  }

private:
  diagnostic_event_id_t m_malloc_event;
};

class free_of_non_heap : public malloc_diagnostic
{
public:
  free_of_non_heap (const malloc_state_machine &sm, tree arg)
  : malloc_diagnostic (sm, arg), m_kind (KIND_UNKNOWN)
  {
  }

  const char *get_kind () const FINAL OVERRIDE { return "free_of_non_heap"; }

  bool subclass_equal_p (const pending_diagnostic &base_other) const
    FINAL OVERRIDE
  {
    const free_of_non_heap &other = (const free_of_non_heap &)base_other;
    return (same_tree_p (m_arg, other.m_arg) && m_kind == other.m_kind);
  }

  bool emit (rich_location *rich_loc) FINAL OVERRIDE
  {
    auto_diagnostic_group d;
    diagnostic_metadata m;
    m.add_cwe (590); /* CWE-590: Free of Memory not on the Heap.  */
    switch (m_kind)
      {
      default:
	gcc_unreachable ();
      case KIND_UNKNOWN:
	return warning_meta (rich_loc, m, OPT_Wanalyzer_free_of_non_heap,
			     "%<free%> of %qE which points to memory"
			     " not on the heap",
			     m_arg);
	break;
      case KIND_ALLOCA:
	return warning_meta (rich_loc, m, OPT_Wanalyzer_free_of_non_heap,
			     "%<free%> of memory allocated on the stack by"
			     " %qs (%qE) will corrupt the heap",
			     "alloca", m_arg);
	break;
      }
  }

  label_text describe_state_change (const evdesc::state_change &change)
    FINAL OVERRIDE
  {
    /* Attempt to reconstruct what kind of pointer it is.
       (It seems neater for this to be a part of the state, though).  */
    if (TREE_CODE (change.m_expr) == SSA_NAME)
      {
	gimple *def_stmt = SSA_NAME_DEF_STMT (change.m_expr);
	if (gcall *call = dyn_cast <gcall *> (def_stmt))
	  {
	    if (is_special_named_call_p (call, "alloca", 1)
		|| is_special_named_call_p (call, "__builtin_alloca", 1))
	      {
		m_kind = KIND_ALLOCA;
		return label_text::borrow
		  ("memory is allocated on the stack here");
	      }
	  }
      }
    return label_text::borrow ("pointer is from here");
  }

  label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
  {
    return ev.formatted_print ("call to %qs here", "free");
  }

private:
  enum kind
  {
    KIND_UNKNOWN,
    KIND_ALLOCA
  };
  enum kind m_kind;
};

/* malloc_state_machine's ctor.  */

malloc_state_machine::malloc_state_machine (logger *logger)
: state_machine ("malloc", logger)
{
  m_start = add_state ("start");
  m_unchecked = add_state ("unchecked");
  m_null = add_state ("null");
  m_nonnull = add_state ("nonnull");
  m_freed = add_state ("freed");
  m_non_heap = add_state ("non-heap");
  m_stop = add_state ("stop");
}

/* Implementation of state_machine::on_stmt vfunc for malloc_state_machine.  */

bool
malloc_state_machine::on_stmt (sm_context *sm_ctxt,
			       const supernode *node,
			       const gimple *stmt) const
{
  if (const gcall *call = dyn_cast <const gcall *> (stmt))
    if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call))
      {
	if (is_named_call_p (callee_fndecl, "malloc", call, 1)
	    || is_named_call_p (callee_fndecl, "calloc", call, 2)
	    || is_named_call_p (callee_fndecl, "__builtin_malloc", call, 1)
	    || is_named_call_p (callee_fndecl, "__builtin_calloc", call, 2))
	  {
	    tree lhs = gimple_call_lhs (call);
	    if (lhs)
	      {
		lhs = sm_ctxt->get_readable_tree (lhs);
		sm_ctxt->on_transition (node, stmt, lhs, m_start, m_unchecked);
	      }
	    else
	      {
		/* TODO: report leak.  */
	      }
	    return true;
	  }

	if (is_named_call_p (callee_fndecl, "alloca", call, 1)
	    || is_named_call_p (callee_fndecl, "__builtin_alloca", call, 1))
	  {
	    tree lhs = gimple_call_lhs (call);
	    if (lhs)
	      {
		lhs = sm_ctxt->get_readable_tree (lhs);
		sm_ctxt->on_transition (node, stmt, lhs, m_start, m_non_heap);
	      }
	    return true;
	  }

	if (is_named_call_p (callee_fndecl, "free", call, 1)
	    || is_named_call_p (callee_fndecl, "__builtin_free", call, 1))
	  {
	    tree arg = gimple_call_arg (call, 0);

	    arg = sm_ctxt->get_readable_tree (arg);

	    /* start/unchecked/nonnull -> freed.  */
	    sm_ctxt->on_transition (node, stmt, arg, m_start, m_freed);
	    sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_freed);
	    sm_ctxt->on_transition (node, stmt, arg, m_nonnull, m_freed);

	    /* Keep state "null" as-is, rather than transitioning to "free";
	       we don't want want to complain about double-free of NULL.  */

	    /* freed -> stop, with warning.  */
	    sm_ctxt->warn_for_state (node, stmt, arg, m_freed,
				     new double_free (*this, arg));
	    sm_ctxt->on_transition (node, stmt, arg, m_freed, m_stop);

	    /* non-heap -> stop, with warning.  */
	    sm_ctxt->warn_for_state (node, stmt, arg, m_non_heap,
				     new free_of_non_heap (*this, arg));
	    sm_ctxt->on_transition (node, stmt, arg, m_non_heap, m_stop);
	    return true;
	  }

	/* Handle "__attribute__((nonnull))".   */
	{
	  tree fntype = TREE_TYPE (callee_fndecl);
	  bitmap nonnull_args = get_nonnull_args (fntype);
	  if (nonnull_args)
	    {
	      for (unsigned i = 0; i < gimple_call_num_args (stmt); i++)
		{
		  tree arg = gimple_call_arg (stmt, i);
		  if (TREE_CODE (TREE_TYPE (arg)) != POINTER_TYPE)
		    continue;
		  /* If we have a nonnull-args, and either all pointers, or just
		     the specified pointers.  */
		  if (bitmap_empty_p (nonnull_args)
		      || bitmap_bit_p (nonnull_args, i))
		    {
		      sm_ctxt->warn_for_state
			(node, stmt, arg, m_unchecked,
			 new possible_null_arg (*this, arg, callee_fndecl, i));
		      sm_ctxt->on_transition (node, stmt, arg, m_unchecked,
					      m_nonnull);

		      sm_ctxt->warn_for_state
			(node, stmt, arg, m_null,
			 new null_arg (*this, arg, callee_fndecl, i));
		      sm_ctxt->on_transition (node, stmt, arg, m_null, m_stop);
		    }
		}
	      BITMAP_FREE (nonnull_args);
	    }
	}
      }

  if (tree lhs = is_zero_assignment (stmt))
    if (any_pointer_p (lhs))
      on_zero_assignment (sm_ctxt, node, stmt,lhs);

  if (const gassign *assign_stmt = dyn_cast <const gassign *> (stmt))
    {
      enum tree_code op = gimple_assign_rhs_code (assign_stmt);
      if (op == ADDR_EXPR)
	{
	  tree lhs = gimple_assign_lhs (assign_stmt);
	  if (lhs)
	    {
	      lhs = sm_ctxt->get_readable_tree (lhs);
	      sm_ctxt->on_transition (node, stmt, lhs, m_start, m_non_heap);
	    }
	}
    }

  /* Handle dereferences.  */
  for (unsigned i = 0; i < gimple_num_ops (stmt); i++)
    {
      tree op = gimple_op (stmt, i);
      if (!op)
	continue;
      if (TREE_CODE (op) == COMPONENT_REF)
	op = TREE_OPERAND (op, 0);

      if (TREE_CODE (op) == MEM_REF)
	{
	  tree arg = TREE_OPERAND (op, 0);
	  arg = sm_ctxt->get_readable_tree (arg);

	  sm_ctxt->warn_for_state (node, stmt, arg, m_unchecked,
				   new possible_null_deref (*this, arg));
	  sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_nonnull);

	  sm_ctxt->warn_for_state (node, stmt, arg, m_null,
				   new null_deref (*this, arg));
	  sm_ctxt->on_transition (node, stmt, arg, m_null, m_stop);

	  sm_ctxt->warn_for_state (node, stmt, arg, m_freed,
				   new use_after_free (*this, arg));
	  sm_ctxt->on_transition (node, stmt, arg, m_freed, m_stop);
	}
    }
  return false;
}

/* Implementation of state_machine::on_phi vfunc for malloc_state_machine.  */

void
malloc_state_machine::on_phi (sm_context *sm_ctxt,
			      const supernode *node,
			      const gphi *phi,
			      tree rhs) const
{
  if (zerop (rhs))
    {
      tree lhs = gimple_phi_result (phi);
      on_zero_assignment (sm_ctxt, node, phi, lhs);
    }
}

/* Implementation of state_machine::on_condition vfunc for malloc_state_machine.
   Potentially transition state 'unchecked' to 'nonnull' or to 'null'.  */

void
malloc_state_machine::on_condition (sm_context *sm_ctxt,
				    const supernode *node,
				    const gimple *stmt,
				    tree lhs,
				    enum tree_code op,
				    tree rhs) const
{
  if (!zerop (rhs))
    return;

  if (!any_pointer_p (lhs))
    return;
  if (!any_pointer_p (rhs))
    return;

  if (op == NE_EXPR)
    {
      log ("got 'ARG != 0' match");
      sm_ctxt->on_transition (node, stmt,
			      lhs, m_unchecked, m_nonnull);
    }
  else if (op == EQ_EXPR)
    {
      log ("got 'ARG == 0' match");
      sm_ctxt->on_transition (node, stmt,
			      lhs, m_unchecked, m_null);
    }
}

/* Implementation of state_machine::can_purge_p vfunc for malloc_state_machine.
   Don't allow purging of pointers in state 'unchecked' or 'nonnull'
   (to avoid false leak reports).  */

bool
malloc_state_machine::can_purge_p (state_t s) const
{
  return s != m_unchecked && s != m_nonnull;
}

/* Implementation of state_machine::on_leak vfunc for malloc_state_machine
   (for complaining about leaks of pointers in state 'unchecked' and
   'nonnull').  */

pending_diagnostic *
malloc_state_machine::on_leak (tree var) const
{
  return new malloc_leak (*this, var);
}

/* Shared logic for handling GIMPLE_ASSIGNs and GIMPLE_PHIs that
   assign zero to LHS.  */

void
malloc_state_machine::on_zero_assignment (sm_context *sm_ctxt,
					  const supernode *node,
					  const gimple *stmt,
					  tree lhs) const
{
  sm_ctxt->on_transition (node, stmt, lhs, m_start, m_null);
  sm_ctxt->on_transition (node, stmt, lhs, m_unchecked, m_null);
  sm_ctxt->on_transition (node, stmt, lhs, m_nonnull, m_null);
  sm_ctxt->on_transition (node, stmt, lhs, m_freed, m_null);
}

} // anonymous namespace

/* Internal interface to this file. */

state_machine *
make_malloc_state_machine (logger *logger)
{
  return new malloc_state_machine (logger);
}

} // namespace ana

#endif /* #if ENABLE_ANALYZER */