analyzer: eliminate region_model::impl_call_* special cases

Eliminate all of the remaining special cases in class region_model that
handle various specific functions, replacing them with uses of
known_function subclasses.

Add various type-checks that ought to prevent ICEs for cases where
functions match the name of a standard C library or POSIX function, but
have incompatible arguments.

gcc/analyzer/ChangeLog:
	* analyzer.h (class internal_known_function): New.
	(register_varargs_builtins): New decl.
	* engine.cc (exploded_node::on_stmt_pre): Remove
	"out_terminate_path" param from call to region_model::on_stmt_pre.
	(feasibility_state::maybe_update_for_edge): Likewise.
	* known-function-manager.cc: Include "basic-block.h", "gimple.h",
	and "analyzer/region-model.h".
	(known_function_manager::known_function_manager): Initialize
	m_combined_fns_arr.
	(known_function_manager::~known_function_manager): Clean up
	m_combined_fns_arr.
	(known_function_manager::get_by_identifier): Make const.
	(known_function_manager::add): New overloaded definitions for
	enum built_in_function and enum internal_fn.
	(known_function_manager::get_by_fndecl): Delete.
	(known_function_manager::get_match): New.
	(known_function_manager::get_internal_fn): New.
	(known_function_manager::get_normal_builtin): New.
	* known-function-manager.h
	(known_function_manager::get_by_identifier): Make private and
	add const qualifier.
	(known_function_manager::get_by_fndecl): Delete.
	(known_function_manager::add): Add overloaded decls for
	enum built_in_function name and enum internal_fn.
	(known_function_manager::get_match): New decl.
	(known_function_manager::get_internal_fn): New decl.
	(known_function_manager::get_normal_builtin): New decl.
	(known_function_manager::m_combined_fns_arr): New field.
	* region-model-impl-calls.cc (call_details::arg_is_size_p): New.
	(class kf_alloca): New.
	(region_model::impl_call_alloca): Convert to...
	(kf_alloca::impl_call_pre): ...this.
	(kf_analyzer_dump_capacity::matches_call_types_p): Rewrite check
	to use call_details::arg_is_pointer_p.
	(region_model::impl_call_builtin_expect): Convert to...
	(class kf_expect): ...this.
	(class kf_calloc): New, adding check that both arguments are
	size_t.
	(region_model::impl_call_calloc): Convert to...
	(kf_calloc::impl_call_pre): ...this.
	(kf_connect::matches_call_types_p): Rewrite check to use
	call_details::arg_is_pointer_p.
	(region_model::impl_call_error): Convert to...
	(class kf_error): ...this, and...
	(kf_error::impl_call_pre): ...this.
	(class kf_fgets): New, adding checks that args 0 and 2 are
	pointers.
	(region_model::impl_call_fgets): Convert to...
	(kf_fgets::impl_call_pre): ...this.
	(class kf_fread): New, adding checks on the argument types.
	(region_model::impl_call_fread): Convert to...
	(kf_fread::impl_call_pre): ...this.
	(class kf_free): New, adding check that the argument is a pointer.
	(region_model::impl_call_free): Convert to...
	(kf_free::impl_call_post): ...this.
	(class kf_getchar): New.
	(class kf_malloc): New, adding check that the argument is a
	size_t.
	(region_model::impl_call_malloc): Convert to...
	(kf_malloc::impl_call_pre): ...this.
	(class kf_memcpy): New, adding checks on arguments.
	(region_model::impl_call_memcpy): Convert to...
	(kf_memcpy::impl_call_pre): ...this.
	(class kf_memset): New.
	(region_model::impl_call_memset): Convert to...
	(kf_memset::impl_call_pre): ...this.
	(kf_pipe::matches_call_types_p): Rewrite check to use
	call_details::arg_is_pointer_p.
	(kf_putenv::matches_call_types_p): Likewise.
	(class kf_realloc): New, adding checks on the argument types.
	(region_model::impl_call_realloc): Convert to...
	(kf_realloc::impl_call_post): ...this.
	(class kf_strchr): New.
	(region_model::impl_call_strchr): Convert to...
	(kf_strchr::impl_call_post): ...this.
	(class kf_stack_restore): New.
	(class kf_stack_save): New.
	(class kf_stdio_output_fn): New.
	(class kf_strcpy): New,
	(region_model::impl_call_strcpy): Convert to...
	(kf_strcpy::impl_call_pre): ...this.
	(class kf_strlen): New.
	(region_model::impl_call_strlen): Convert to...
	(kf_strlen::impl_call_pre): ...this.
	(class kf_ubsan_bounds): New.
	(region_model::impl_deallocation_call): Reimplement to avoid call
	to impl_call_free.
	(register_known_functions): Add handlers for IFN_BUILTIN_EXPECT
	and IFN_UBSAN_BOUNDS.  Add handlers for BUILT_IN_ALLOCA,
	BUILT_IN_ALLOCA_WITH_ALIGN, BUILT_IN_CALLOC, BUILT_IN_EXPECT,
	BUILT_IN_EXPECT_WITH_PROBABILITY, BUILT_IN_FPRINTF,
	BUILT_IN_FPRINTF_UNLOCKED, BUILT_IN_FPUTC,
	BUILT_IN_FPUTC_UNLOCKED, BUILT_IN_FPUTS, BUILT_IN_FPUTS_UNLOCKED,
	BUILT_IN_FREE, BUILT_IN_FWRITE, BUILT_IN_FWRITE_UNLOCKED,
	BUILT_IN_MALLOC, BUILT_IN_MEMCPY, BUILT_IN_MEMCPY_CHK,
	BUILT_IN_MEMSET, BUILT_IN_MEMSET_CHK, BUILT_IN_PRINTF,
	BUILT_IN_PRINTF_UNLOCKED, BUILT_IN_PUTC, BUILT_IN_PUTCHAR,
	BUILT_IN_PUTCHAR_UNLOCKED, BUILT_IN_PUTC_UNLOCKED, BUILT_IN_PUTS,
	BUILT_IN_PUTS_UNLOCKED, BUILT_IN_REALLOC, BUILT_IN_STACK_RESTORE,
	BUILT_IN_STACK_SAVE, BUILT_IN_STRCHR, BUILT_IN_STRCPY,
	BUILT_IN_STRCPY_CHK, BUILT_IN_STRLEN, BUILT_IN_VFPRINTF, and
	BUILT_IN_VPRINTF. Call register_varargs_builtins.  Add handlers
	for "getchar", "memset", "fgets", "fgets_unlocked", "fread",
	"error", and "error_at_line".
	* region-model.cc (region_model::on_stmt_pre): Drop
	"out_terminate_path" param.
	(region_model::get_known_function): Reimplement by calling
	known_function_manager::get_match, passing new "cd" param.
	Add overload taking enum internal_fn.
	(region_model::on_call_pre): Drop "out_terminate_path" param.
	Remove special-case handling of internal fns IFN_BUILTIN_EXPECT,
	IFN_UBSAN_BOUNDS, and IFN_VA_ARG, of built-in fns BUILT_IN_ALLOCA,
	BUILT_IN_ALLOCA_WITH_ALIGN, BUILT_IN_CALLOC, BUILT_IN_EXPECT,
	BUILT_IN_EXPECT_WITH_PROBABILITY, BUILT_IN_FREE, BUILT_IN_MALLOC,
	BUILT_IN_MEMCPY, BUILT_IN_MEMCPY_CHK, BUILT_IN_MEMSET,
	BUILT_IN_MEMSET_CHK, BUILT_IN_REALLOC, BUILT_IN_STRCHR,
	BUILT_IN_STRCPY, BUILT_IN_STRCPY_CHK, BUILT_IN_STRLEN,
	BUILT_IN_STACK_SAVE, BUILT_IN_STACK_RESTORE, BUILT_IN_FPRINTF,
	BUILT_IN_FPRINTF_UNLOCKED, BUILT_IN_PUTC, BUILT_IN_PUTC_UNLOCKED,
	BUILT_IN_FPUTC, BUILT_IN_FPUTC_UNLOCKED, BUILT_IN_FPUTS,
	BUILT_IN_FPUTS_UNLOCKED, BUILT_IN_FWRITE,
	BUILT_IN_FWRITE_UNLOCKED, BUILT_IN_PRINTF,
	BUILT_IN_PRINTF_UNLOCKED, BUILT_IN_PUTCHAR,
	BUILT_IN_PUTCHAR_UNLOCKED, BUILT_IN_PUTS, BUILT_IN_PUTS_UNLOCKED,
	BUILT_IN_VFPRINTF, BUILT_IN_VPRINTF, BUILT_IN_VA_START, and
	BUILT_IN_VA_COPY, and of named functions "malloc", "calloc",
	"alloca", "realloc", "error", "error_at_line", "fgets",
	"fgets_unlocked", "fread", "getchar", "memset", "strchr", and
	"strlen".  Replace all this special-casing with calls to
	get_known_function for internal fns and for fn decls.
	(region_model::on_call_post): Remove special-casing handling for
	"free" and "strchr", and for BUILT_IN_REALLOC, BUILT_IN_STRCHR,
	and BUILT_IN_VA_END.  Replace by consolidating on usage of
	get_known_function.
	* region-model.h (call_details::arg_is_size_p): New.
	(region_model::on_stmt_pre): Drop "out_terminate_path" param.
	(region_model::on_call_pre): Likewise.
	(region_model::impl_call_alloca): Delete.
	(region_model::impl_call_builtin_expect): Delete.
	(region_model::impl_call_calloc): Delete.
	(region_model::impl_call_error): Delete.
	(region_model::impl_call_fgets): Delete.
	(region_model::impl_call_fread): Delete.
	(region_model::impl_call_free): Delete.
	(region_model::impl_call_malloc): Delete.
	(region_model::impl_call_memcpy): Delete.
	(region_model::impl_call_memset): Delete.
	(region_model::impl_call_realloc): Delete.
	(region_model::impl_call_strchr): Delete.
	(region_model::impl_call_strcpy): Delete.
	(region_model::impl_call_strlen): Delete.
	(region_model::impl_call_va_start): Delete.
	(region_model::impl_call_va_copy): Delete.
	(region_model::impl_call_va_arg): Delete.
	(region_model::impl_call_va_end): Delete.
	(region_model::check_region_for_write): Public.
	(region_model::get_known_function): Add "cd" param.  Add
	overloaded decl taking enum internal_fn.
	* sm-malloc.cc: Update comments.
	* varargs.cc (class kf_va_start): New.
	(region_model::impl_call_va_start): Convert to...
	(kf_va_start::impl_call_pre): ...this.
	(class kf_va_copy): New.
	(region_model::impl_call_va_copy): Convert to...
	(kf_va_copy::impl_call_pre): ...this.
	(class kf_va_arg): New.
	(region_model::impl_call_va_arg): Convert to...
	(kf_va_arg::impl_call_pre): ...this.
	(class kf_va_end): New.
	(region_model::impl_call_va_end): Delete.
	(register_varargs_builtins): New.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
index 83bb001..d424b43 100644
--- a/gcc/analyzer/analyzer.h
+++ b/gcc/analyzer/analyzer.h
@@ -245,7 +245,20 @@
   }
 };
 
+/* Subclass of known_function for IFN_* functions.  */
+
+class internal_known_function : public known_function
+{
+public:
+  bool matches_call_types_p (const call_details &) const final override
+  {
+    /* Types are assumed to be correct.  */
+    return true;
+  }
+};
+
 extern void register_known_functions (known_function_manager &mgr);
+extern void register_varargs_builtins (known_function_manager &kfm);
 
 /* Passed by pointer to PLUGIN_ANALYZER_INIT callbacks.  */
 
diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc
index db1881cd..0c49bb2 100644
--- a/gcc/analyzer/engine.cc
+++ b/gcc/analyzer/engine.cc
@@ -1538,7 +1538,6 @@
 
   /* Otherwise, defer to m_region_model.  */
   state->m_region_model->on_stmt_pre (stmt,
-				      out_terminate_path,
 				      out_unknown_side_effects,
 				      ctxt);
 }
@@ -4839,9 +4838,7 @@
 	m_model.on_asm_stmt (asm_stmt, NULL);
       else if (const gcall *call = dyn_cast <const gcall *> (stmt))
 	{
-	  bool terminate_path;
-	  bool unknown_side_effects
-	    = m_model.on_call_pre (call, NULL, &terminate_path);
+	  bool unknown_side_effects = m_model.on_call_pre (call, NULL);
 	  m_model.on_call_post (call, unknown_side_effects, NULL);
 	}
       else if (const greturn *return_ = dyn_cast <const greturn *> (stmt))
diff --git a/gcc/analyzer/known-function-manager.cc b/gcc/analyzer/known-function-manager.cc
index 7341b06..e17350d 100644
--- a/gcc/analyzer/known-function-manager.cc
+++ b/gcc/analyzer/known-function-manager.cc
@@ -27,7 +27,10 @@
 #include "diagnostic-core.h"
 #include "analyzer/analyzer-logging.h"
 #include "stringpool.h"
+#include "basic-block.h"
+#include "gimple.h"
 #include "analyzer/known-function-manager.h"
+#include "analyzer/region-model.h"
 
 #if ENABLE_ANALYZER
 
@@ -38,6 +41,7 @@
 known_function_manager::known_function_manager (logger *logger)
 : log_user (logger)
 {
+  memset (m_combined_fns_arr, 0, sizeof (m_combined_fns_arr));
 }
 
 known_function_manager::~known_function_manager ()
@@ -45,6 +49,8 @@
   /* Delete all owned kfs.  */
   for (auto iter : m_map_id_to_kf)
     delete iter.second;
+  for (auto iter : m_combined_fns_arr)
+    delete iter;
 }
 
 void
@@ -56,24 +62,85 @@
   m_map_id_to_kf.put (id, kf.release ());
 }
 
-const known_function *
-known_function_manager::get_by_identifier (tree identifier)
+void
+known_function_manager::add (enum built_in_function name,
+			     std::unique_ptr<known_function> kf)
 {
-  known_function **slot = m_map_id_to_kf.get (identifier);
+  gcc_assert (name < END_BUILTINS);
+  delete m_combined_fns_arr[name];
+  m_combined_fns_arr[name] = kf.release ();
+}
+
+void
+known_function_manager::add (enum internal_fn ifn,
+			     std::unique_ptr<known_function> kf)
+{
+  gcc_assert (ifn < IFN_LAST);
+  delete m_combined_fns_arr[ifn + END_BUILTINS];
+  m_combined_fns_arr[ifn + END_BUILTINS] = kf.release ();
+}
+
+/* Get any known_function for FNDECL for call CD.
+
+   The call must match all assumptions made by the known_function (such as
+   e.g. "argument 1's type must be a pointer type").
+
+   Return NULL if no known_function is found, or it does not match the
+   assumption(s).  */
+
+const known_function *
+known_function_manager::get_match (tree fndecl, const call_details &cd) const
+{
+  if (fndecl_built_in_p (fndecl, BUILT_IN_NORMAL))
+    {
+      if (const known_function *candidate
+	  = get_normal_builtin (DECL_FUNCTION_CODE (fndecl)))
+	if (gimple_builtin_call_types_compatible_p (cd.get_call_stmt (),
+						    fndecl))
+	  return candidate;
+    }
+  if (tree identifier = DECL_NAME (fndecl))
+      if (const known_function *candidate = get_by_identifier (identifier))
+	if (candidate->matches_call_types_p (cd))
+	  return candidate;
+  return NULL;
+}
+
+/* Get any known_function for IFN, or NULL.  */
+
+const known_function *
+known_function_manager::get_internal_fn (enum internal_fn ifn) const
+{
+  gcc_assert (ifn < IFN_LAST);
+  return m_combined_fns_arr[ifn + END_BUILTINS];
+}
+
+/* Get any known_function for NAME, without type-checking.
+   Return NULL if there isn't one.  */
+
+const known_function *
+known_function_manager::get_normal_builtin (enum built_in_function name) const
+{
+  /* The numbers for built-in functions in enum combined_fn are the same as
+     for the built_in_function enum.  */
+  gcc_assert (name < END_BUILTINS);
+  return m_combined_fns_arr[name];
+}
+
+/* Get any known_function matching IDENTIFIER, without type-checking.
+   Return NULL if there isn't one.  */
+
+const known_function *
+known_function_manager::get_by_identifier (tree identifier) const
+{
+  known_function_manager *mut_this = const_cast<known_function_manager *>(this);
+  known_function **slot = mut_this->m_map_id_to_kf.get (identifier);
   if (slot)
     return *slot;
   else
     return NULL;
 }
 
-const known_function *
-known_function_manager::get_by_fndecl (tree fndecl)
-{
-  if (tree identifier = DECL_NAME (fndecl))
-    return get_by_identifier (identifier);
-  return NULL;
-}
-
 } // namespace ana
 
 #endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/known-function-manager.h b/gcc/analyzer/known-function-manager.h
index daf1bc5..188cb8e 100644
--- a/gcc/analyzer/known-function-manager.h
+++ b/gcc/analyzer/known-function-manager.h
@@ -30,16 +30,26 @@
 public:
   known_function_manager (logger *logger);
   ~known_function_manager ();
+
   void add (const char *name, std::unique_ptr<known_function> kf);
-  const known_function *get_by_identifier (tree identifier);
-  const known_function *get_by_fndecl (tree fndecl);
+  void add (enum built_in_function name, std::unique_ptr<known_function> kf);
+  void add (enum internal_fn ifn, std::unique_ptr<known_function> kf);
+
+  const known_function *get_match (tree fndecl, const call_details &cd) const;
+  const known_function *get_internal_fn (enum internal_fn) const;
 
 private:
   DISABLE_COPY_AND_ASSIGN (known_function_manager);
 
+  const known_function *get_normal_builtin (enum built_in_function name) const;
+  const known_function *get_by_identifier (tree identifier) const;
+
   /* Map from identifier to known_function instance.
      Has ownership of the latter.  */
   hash_map<tree, known_function *> m_map_id_to_kf;
+
+  /* Array of known builtins.  */
+  known_function *m_combined_fns_arr[CFN_LAST];
 };
 
 } // namespace ana
diff --git a/gcc/analyzer/region-model-impl-calls.cc b/gcc/analyzer/region-model-impl-calls.cc
index 8a44c97..6962ffd 100644
--- a/gcc/analyzer/region-model-impl-calls.cc
+++ b/gcc/analyzer/region-model-impl-calls.cc
@@ -133,6 +133,14 @@
   return gimple_call_num_args (m_call);
 }
 
+/* Return true if argument IDX is a size_t (or compatible with it).  */
+
+bool
+call_details::arg_is_size_p (unsigned idx) const
+{
+  return types_compatible_p (get_arg_type (idx), size_type_node);
+}
+
 /* Get the location of the call statement.  */
 
 location_t
@@ -242,15 +250,30 @@
 
 /* Implementations of specific functions.  */
 
-/* Handle the on_call_pre part of "alloca".  */
+/* Handler for "alloca".  */
+
+class kf_alloca : public known_function
+{
+public:
+  bool matches_call_types_p (const call_details &cd) const final override
+  {
+    return cd.num_args () == 1;
+  }
+  void impl_call_pre (const call_details &cd) const final override;
+};
 
 void
-region_model::impl_call_alloca (const call_details &cd)
+kf_alloca::impl_call_pre (const call_details &cd) const
 {
   const svalue *size_sval = cd.get_arg_svalue (0);
-  const region *new_reg = create_region_for_alloca (size_sval, cd.get_ctxt ());
+
+  region_model *model = cd.get_model ();
+  region_model_manager *mgr = cd.get_manager ();
+
+  const region *new_reg
+    = model->create_region_for_alloca (size_sval, cd.get_ctxt ());
   const svalue *ptr_sval
-    = m_mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg);
+    = mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg);
   cd.maybe_set_lhs (ptr_sval);
 }
 
@@ -308,7 +331,7 @@
   bool matches_call_types_p (const call_details &cd) const final override
   {
     return (cd.num_args () == 1
-	    && POINTER_TYPE_P (cd.get_arg_type (0)));
+	    && cd.arg_is_pointer_p (0));
   }
 
   void impl_call_pre (const call_details &cd) const final override
@@ -649,36 +672,53 @@
   }
 };
 
-/* Handle the on_call_pre part of "__builtin_expect" etc.  */
+/* Handler for "__builtin_expect" etc.  */
+
+class kf_expect : public internal_known_function
+{
+public:
+  void impl_call_pre (const call_details &cd) const final override
+  {
+    /* __builtin_expect's return value is its initial argument.  */
+    const svalue *sval = cd.get_arg_svalue (0);
+    cd.maybe_set_lhs (sval);
+  }
+};
+
+/* Handler for "calloc".  */
+
+class kf_calloc : public known_function
+{
+public:
+  bool matches_call_types_p (const call_details &cd) const final override
+  {
+    return (cd.num_args () == 2
+	    && cd.arg_is_size_p (0)
+	    && cd.arg_is_size_p (1));
+  }
+  void impl_call_pre (const call_details &cd) const final override;
+};
 
 void
-region_model::impl_call_builtin_expect (const call_details &cd)
+kf_calloc::impl_call_pre (const call_details &cd) const
 {
-  /* __builtin_expect's return value is its initial argument.  */
-  const svalue *sval = cd.get_arg_svalue (0);
-  cd.maybe_set_lhs (sval);
-}
-
-/* Handle the on_call_pre part of "calloc".  */
-
-void
-region_model::impl_call_calloc (const call_details &cd)
-{
+  region_model *model = cd.get_model ();
+  region_model_manager *mgr = cd.get_manager ();
   const svalue *nmemb_sval = cd.get_arg_svalue (0);
   const svalue *size_sval = cd.get_arg_svalue (1);
   /* TODO: check for overflow here?  */
   const svalue *prod_sval
-    = m_mgr->get_or_create_binop (size_type_node, MULT_EXPR,
-				  nmemb_sval, size_sval);
+    = mgr->get_or_create_binop (size_type_node, MULT_EXPR,
+				nmemb_sval, size_sval);
   const region *new_reg
-    = create_region_for_heap_alloc (prod_sval, cd.get_ctxt ());
+    = model->create_region_for_heap_alloc (prod_sval, cd.get_ctxt ());
   const region *sized_reg
-    = m_mgr->get_sized_region (new_reg, NULL_TREE, prod_sval);
-  zero_fill_region (sized_reg);
+    = mgr->get_sized_region (new_reg, NULL_TREE, prod_sval);
+  model->zero_fill_region (sized_reg);
   if (cd.get_lhs_type ())
     {
       const svalue *ptr_sval
-	= m_mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg);
+	= mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg);
       cd.maybe_set_lhs (ptr_sval);
     }
 }
@@ -708,7 +748,7 @@
   bool matches_call_types_p (const call_details &cd) const final override
   {
     return (cd.num_args () == 3
-	    && POINTER_TYPE_P (cd.get_arg_type (1)));
+	    && cd.arg_is_pointer_p (1));
   }
 
   void impl_call_post (const call_details &cd) const final override
@@ -745,67 +785,102 @@
   }
 };
 
-/* Handle the on_call_pre part of "error" and "error_at_line" from
-   GNU's non-standard <error.h>.
+/* Handler for "error" and "error_at_line" from GNU's non-standard <error.h>.
    MIN_ARGS identifies the minimum number of expected arguments
-   to be consistent with such a call (3 and 5 respectively).
-   Return true if handling it as one of these functions.
-   Write true to *OUT_TERMINATE_PATH if this execution path should be
-   terminated (e.g. the function call terminates the process).  */
+   to be consistent with such a call (3 and 5 respectively).  */
 
-bool
-region_model::impl_call_error (const call_details &cd, unsigned min_args,
-			       bool *out_terminate_path)
+class kf_error : public known_function
 {
-  /* Bail if not enough args.  */
-  if (cd.num_args () < min_args)
-    return false;
+public:
+  kf_error (unsigned min_args) : m_min_args (min_args) {}
 
-  /* Initial argument ought to be of type "int".  */
-  if (cd.get_arg_type (0) != integer_type_node)
-    return false;
+  bool matches_call_types_p (const call_details &cd) const final override
+  {
+    return (cd.num_args () >= m_min_args
+	    && cd.get_arg_type (0) == integer_type_node);
+  }
 
+  void impl_call_pre (const call_details &cd) const final override;
+
+private:
+  unsigned m_min_args;
+};
+
+void
+kf_error::impl_call_pre (const call_details &cd) const
+{
   /* The process exits if status != 0, so it only continues
      for the case where status == 0.
      Add that constraint, or terminate this analysis path.  */
   tree status = cd.get_arg_tree (0);
-  if (!add_constraint (status, EQ_EXPR, integer_zero_node, cd.get_ctxt ()))
-    *out_terminate_path = true;
-
-  return true;
+  region_model_context *ctxt = cd.get_ctxt ();
+  region_model *model = cd.get_model ();
+  if (!model->add_constraint (status, EQ_EXPR, integer_zero_node, ctxt))
+    if (ctxt)
+      ctxt->terminate_path ();
 }
 
-/* Handle the on_call_pre part of "fgets" and "fgets_unlocked".  */
+/* Handler for "fgets" and "fgets_unlocked".  */
+
+class kf_fgets : public known_function
+{
+public:
+  bool matches_call_types_p (const call_details &cd) const final override
+  {
+    return (cd.num_args () == 3
+	    && cd.arg_is_pointer_p (0)
+	    && cd.arg_is_pointer_p (2));
+  }
+
+  void impl_call_pre (const call_details &cd) const final override;
+};
 
 void
-region_model::impl_call_fgets (const call_details &cd)
+kf_fgets::impl_call_pre (const call_details &cd) const
 {
   /* Ideally we would bifurcate state here between the
      error vs no error cases.  */
+  region_model *model = cd.get_model ();
   const svalue *ptr_sval = cd.get_arg_svalue (0);
   if (const region *reg = ptr_sval->maybe_get_region ())
     {
       const region *base_reg = reg->get_base_region ();
       const svalue *new_sval = cd.get_or_create_conjured_svalue (base_reg);
-      set_value (base_reg, new_sval, cd.get_ctxt ());
+      model->set_value (base_reg, new_sval, cd.get_ctxt ());
     }
 }
 
-/* Handle the on_call_pre part of "fread".  */
+/* Handler for "fread"".  */
+
+class kf_fread : public known_function
+{
+public:
+  bool matches_call_types_p (const call_details &cd) const final override
+  {
+    return (cd.num_args () == 4
+	    && cd.arg_is_pointer_p (0)
+	    && cd.arg_is_size_p (1)
+	    && cd.arg_is_size_p (2)
+	    && cd.arg_is_pointer_p (3));
+  }
+
+  void impl_call_pre (const call_details &cd) const final override;
+};
 
 void
-region_model::impl_call_fread (const call_details &cd)
+kf_fread::impl_call_pre (const call_details &cd) const
 {
+  region_model *model = cd.get_model ();
   const svalue *ptr_sval = cd.get_arg_svalue (0);
   if (const region *reg = ptr_sval->maybe_get_region ())
     {
       const region *base_reg = reg->get_base_region ();
       const svalue *new_sval = cd.get_or_create_conjured_svalue (base_reg);
-      set_value (base_reg, new_sval, cd.get_ctxt ());
+      model->set_value (base_reg, new_sval, cd.get_ctxt ());
     }
 }
 
-/* Handle the on_call_post part of "free", after sm-handling.
+/* Handler for "free", after sm-handling.
 
    If the ptr points to an underlying heap region, delete the region,
    poisoning pointers to it and regions within it.
@@ -820,19 +895,44 @@
    all pointers to the region to the "freed" state together, regardless
    of casts.  */
 
+class kf_free : public known_function
+{
+public:
+  bool matches_call_types_p (const call_details &cd) const final override
+  {
+    return (cd.num_args () == 0 && cd.arg_is_pointer_p (0));
+  }
+  void impl_call_post (const call_details &cd) const final override;
+};
+
 void
-region_model::impl_call_free (const call_details &cd)
+kf_free::impl_call_post (const call_details &cd) const
 {
   const svalue *ptr_sval = cd.get_arg_svalue (0);
   if (const region *freed_reg = ptr_sval->maybe_get_region ())
     {
       /* If the ptr points to an underlying heap region, delete it,
 	 poisoning pointers.  */
-      unbind_region_and_descendents (freed_reg, POISON_KIND_FREED);
-      m_dynamic_extents.remove (freed_reg);
+      region_model *model = cd.get_model ();
+      model->unbind_region_and_descendents (freed_reg, POISON_KIND_FREED);
+      model->unset_dynamic_extents (freed_reg);
     }
 }
 
+/* Handler for "getchar"".  */
+
+class kf_getchar : public known_function
+{
+public:
+  bool matches_call_types_p (const call_details &cd) const final override
+  {
+    return cd.num_args () == 0;
+  }
+
+  /* Empty.  No side-effects (tracking stream state is out-of-scope
+     for the analyzer).  */
+};
+
 /* Handle calls to "listen".
    See e.g. https://man7.org/linux/man-pages/man3/listen.3p.html  */
 
@@ -872,66 +972,109 @@
 
 /* Handle the on_call_pre part of "malloc".  */
 
-void
-region_model::impl_call_malloc (const call_details &cd)
+class kf_malloc : public known_function
 {
+public:
+  bool matches_call_types_p (const call_details &cd) const final override
+  {
+    return (cd.num_args () == 1
+	    && cd.arg_is_size_p (0));
+  }
+  void impl_call_pre (const call_details &cd) const final override;
+};
+
+void
+kf_malloc::impl_call_pre (const call_details &cd) const
+{
+  region_model *model = cd.get_model ();
+  region_model_manager *mgr = cd.get_manager ();
   const svalue *size_sval = cd.get_arg_svalue (0);
   const region *new_reg
-    = create_region_for_heap_alloc (size_sval, cd.get_ctxt ());
+    = model->create_region_for_heap_alloc (size_sval, cd.get_ctxt ());
   if (cd.get_lhs_type ())
     {
       const svalue *ptr_sval
-	= m_mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg);
+	= mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg);
       cd.maybe_set_lhs (ptr_sval);
     }
 }
 
-/* Handle the on_call_pre part of "memcpy" and "__builtin_memcpy".  */
+/* Handler for "memcpy" and "__builtin_memcpy".  */
 // TODO: complain about overlapping src and dest.
 
+class kf_memcpy : public known_function
+{
+public:
+  bool matches_call_types_p (const call_details &cd) const final override
+  {
+    return (cd.num_args () == 3
+	    && cd.arg_is_pointer_p (0)
+	    && cd.arg_is_pointer_p (1)
+	    && cd.arg_is_size_p (2));
+  }
+  void impl_call_pre (const call_details &cd) const final override;
+};
+
 void
-region_model::impl_call_memcpy (const call_details &cd)
+kf_memcpy::impl_call_pre (const call_details &cd) const
 {
   const svalue *dest_ptr_sval = cd.get_arg_svalue (0);
   const svalue *src_ptr_sval = cd.get_arg_svalue (1);
   const svalue *num_bytes_sval = cd.get_arg_svalue (2);
 
-  const region *dest_reg = deref_rvalue (dest_ptr_sval, cd.get_arg_tree (0),
-					 cd.get_ctxt ());
-  const region *src_reg = deref_rvalue (src_ptr_sval, cd.get_arg_tree (1),
-					cd.get_ctxt ());
+  region_model *model = cd.get_model ();
+  region_model_manager *mgr = cd.get_manager ();
+
+  const region *dest_reg
+    = model->deref_rvalue (dest_ptr_sval, cd.get_arg_tree (0), cd.get_ctxt ());
+  const region *src_reg
+    = model->deref_rvalue (src_ptr_sval, cd.get_arg_tree (1), cd.get_ctxt ());
 
   cd.maybe_set_lhs (dest_ptr_sval);
 
   const region *sized_src_reg
-    = m_mgr->get_sized_region (src_reg, NULL_TREE, num_bytes_sval);
+    = mgr->get_sized_region (src_reg, NULL_TREE, num_bytes_sval);
   const region *sized_dest_reg
-    = m_mgr->get_sized_region (dest_reg, NULL_TREE, num_bytes_sval);
+    = mgr->get_sized_region (dest_reg, NULL_TREE, num_bytes_sval);
   const svalue *src_contents_sval
-    = get_store_value (sized_src_reg, cd.get_ctxt ());
-  set_value (sized_dest_reg, src_contents_sval, cd.get_ctxt ());
+    = model->get_store_value (sized_src_reg, cd.get_ctxt ());
+  model->set_value (sized_dest_reg, src_contents_sval, cd.get_ctxt ());
 }
 
-/* Handle the on_call_pre part of "memset" and "__builtin_memset".  */
+/* Handler for "memset" and "__builtin_memset".  */
+
+class kf_memset : public known_function
+{
+public:
+  bool matches_call_types_p (const call_details &cd) const final override
+  {
+    return (cd.num_args () == 3 && cd.arg_is_pointer_p (0));
+  }
+
+  void impl_call_pre (const call_details &cd) const final override;
+};
 
 void
-region_model::impl_call_memset (const call_details &cd)
+kf_memset::impl_call_pre (const call_details &cd) const
 {
   const svalue *dest_sval = cd.get_arg_svalue (0);
   const svalue *fill_value_sval = cd.get_arg_svalue (1);
   const svalue *num_bytes_sval = cd.get_arg_svalue (2);
 
-  const region *dest_reg = deref_rvalue (dest_sval, cd.get_arg_tree (0),
-					  cd.get_ctxt ());
+  region_model *model = cd.get_model ();
+  region_model_manager *mgr = cd.get_manager ();
+
+  const region *dest_reg
+    = model->deref_rvalue (dest_sval, cd.get_arg_tree (0), cd.get_ctxt ());
 
   const svalue *fill_value_u8
-    = m_mgr->get_or_create_cast (unsigned_char_type_node, fill_value_sval);
+    = mgr->get_or_create_cast (unsigned_char_type_node, fill_value_sval);
 
-  const region *sized_dest_reg = m_mgr->get_sized_region (dest_reg,
-							  NULL_TREE,
-							  num_bytes_sval);
-  check_region_for_write (sized_dest_reg, cd.get_ctxt ());
-  fill_region (sized_dest_reg, fill_value_u8);
+  const region *sized_dest_reg = mgr->get_sized_region (dest_reg,
+							NULL_TREE,
+							num_bytes_sval);
+  model->check_region_for_write (sized_dest_reg, cd.get_ctxt ());
+  model->fill_region (sized_dest_reg, fill_value_u8);
 }
 
 /* Handler for calls to "pipe" and "pipe2".
@@ -989,7 +1132,6 @@
 						  p);
 	  model->set_value (element_reg, fd_sval, cd.get_ctxt ());
 	  model->mark_as_valid_fd (fd_sval, cd.get_ctxt ());
-
 	}
       return true;
     }
@@ -1004,8 +1146,7 @@
 
   bool matches_call_types_p (const call_details &cd) const final override
   {
-    return (cd.num_args () == m_num_args
-	    && POINTER_TYPE_P (cd.get_arg_type (0)));
+    return (cd.num_args () == m_num_args && cd.arg_is_pointer_p (0));
   }
 
   void impl_call_post (const call_details &cd) const final override
@@ -1117,8 +1258,7 @@
 public:
   bool matches_call_types_p (const call_details &cd) const final override
   {
-    return (cd.num_args () == 1
-	    && POINTER_TYPE_P (cd.get_arg_type (0)));
+    return (cd.num_args () == 1 && cd.arg_is_pointer_p (0));
   }
 
   void impl_call_pre (const call_details &cd) const final override
@@ -1205,7 +1345,7 @@
   unsigned m_num_args;
 };
 
-/* Handle the on_call_post part of "realloc":
+/* Handler for "realloc":
 
      void *realloc(void *ptr, size_t size);
 
@@ -1228,8 +1368,20 @@
    Each of these has a custom_edge_info subclass, which updates
    the region_model and sm-state of the destination state.  */
 
+class kf_realloc : public known_function
+{
+public:
+  bool matches_call_types_p (const call_details &cd) const final override
+  {
+    return (cd.num_args () == 2
+	    && cd.arg_is_pointer_p (0)
+	    && cd.arg_is_size_p (1));
+  }
+  void impl_call_post (const call_details &cd) const final override;
+};
+
 void
-region_model::impl_call_realloc (const call_details &cd)
+kf_realloc::impl_call_post (const call_details &cd) const
 {
   /* Three custom subclasses of custom_edge_info, for handling the various
      outcomes of "realloc".  */
@@ -1249,10 +1401,11 @@
     {
       /* Return NULL; everything else is unchanged.  */
       const call_details cd (get_call_details (model, ctxt));
+      region_model_manager *mgr = cd.get_manager ();
       if (cd.get_lhs_type ())
 	{
 	  const svalue *zero
-	    = model->m_mgr->get_or_create_int_cst (cd.get_lhs_type (), 0);
+	    = mgr->get_or_create_int_cst (cd.get_lhs_type (), 0);
 	  model->set_value (cd.get_lhs_region (),
 			    zero,
 			    cd.get_ctxt ());
@@ -1284,13 +1437,14 @@
     {
       /* Update size of buffer and return the ptr unchanged.  */
       const call_details cd (get_call_details (model, ctxt));
+      region_model_manager *mgr = cd.get_manager ();
       const svalue *ptr_sval = cd.get_arg_svalue (0);
       const svalue *size_sval = cd.get_arg_svalue (1);
 
       /* We can only grow in place with a non-NULL pointer.  */
       {
 	const svalue *null_ptr
-	  = model->m_mgr->get_or_create_int_cst (ptr_sval->get_type (), 0);
+	  = mgr->get_or_create_int_cst (ptr_sval->get_type (), 0);
 	if (!model->add_constraint (ptr_sval, NE_EXPR, null_ptr,
 				    cd.get_ctxt ()))
 	  return false;
@@ -1304,8 +1458,8 @@
 	{
 	  model->set_value (cd.get_lhs_region (), ptr_sval, cd.get_ctxt ());
 	  const svalue *zero
-	    = model->m_mgr->get_or_create_int_cst (cd.get_lhs_type (), 0);
-	  return model->add_constraint (ptr_sval, NE_EXPR, zero, cd.get_ctxt ());
+	    = mgr->get_or_create_int_cst (cd.get_lhs_type (), 0);
+	  return model->add_constraint (ptr_sval, NE_EXPR, zero, ctxt);
 	}
       else
 	return true;
@@ -1334,6 +1488,7 @@
 		       region_model_context *ctxt) const final override
     {
       const call_details cd (get_call_details (model, ctxt));
+      region_model_manager *mgr = cd.get_manager ();
       const svalue *old_ptr_sval = cd.get_arg_svalue (0);
       const svalue *new_size_sval = cd.get_arg_svalue (1);
 
@@ -1341,7 +1496,7 @@
       const region *new_reg
 	= model->create_region_for_heap_alloc (new_size_sval, ctxt);
       const svalue *new_ptr_sval
-	= model->m_mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg);
+	= mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg);
       if (!model->add_constraint (new_ptr_sval, NE_EXPR, old_ptr_sval,
 				  cd.get_ctxt ()))
 	return false;
@@ -1359,13 +1514,11 @@
 	      const svalue *copied_size_sval
 		= get_copied_size (model, old_size_sval, new_size_sval);
 	      const region *copied_old_reg
-		= model->m_mgr->get_sized_region (freed_reg, NULL,
-						  copied_size_sval);
+		= mgr->get_sized_region (freed_reg, NULL, copied_size_sval);
 	      const svalue *buffer_content_sval
 		= model->get_store_value (copied_old_reg, cd.get_ctxt ());
 	      const region *copied_new_reg
-		= model->m_mgr->get_sized_region (new_reg, NULL,
-						  copied_size_sval);
+		= mgr->get_sized_region (new_reg, NULL, copied_size_sval);
 	      model->set_value (copied_new_reg, buffer_content_sval,
 				cd.get_ctxt ());
 	    }
@@ -1383,7 +1536,7 @@
 	  /* If the ptr points to an underlying heap region, delete it,
 	     poisoning pointers.  */
 	  model->unbind_region_and_descendents (freed_reg, POISON_KIND_FREED);
-	  model->m_dynamic_extents.remove (freed_reg);
+	  model->unset_dynamic_extents (freed_reg);
 	}
 
       /* Update the sm-state: mark the old_ptr_sval as "freed",
@@ -1393,7 +1546,7 @@
       if (cd.get_lhs_type ())
 	{
 	  const svalue *zero
-	    = model->m_mgr->get_or_create_int_cst (cd.get_lhs_type (), 0);
+	    = mgr->get_or_create_int_cst (cd.get_lhs_type (), 0);
 	  return model->add_constraint (new_ptr_sval, NE_EXPR, zero,
 					cd.get_ctxt ());
 	}
@@ -1423,7 +1576,7 @@
     }
   };
 
-  /* Body of region_model::impl_call_realloc.  */
+  /* Body of kf_realloc::impl_call_post.  */
 
   if (cd.get_ctxt ())
     {
@@ -1472,10 +1625,20 @@
   }
 };
 
-/* Handle the on_call_post part of "strchr" and "__builtin_strchr".  */
+/* Handler for "strchr" and "__builtin_strchr".  */
+
+class kf_strchr : public known_function
+{
+public:
+  bool matches_call_types_p (const call_details &cd) const final override
+  {
+    return (cd.num_args () == 2 && cd.arg_is_pointer_p (0));
+  }
+  void impl_call_post (const call_details &cd) const final override;
+};
 
 void
-region_model::impl_call_strchr (const call_details &cd)
+kf_strchr::impl_call_post (const call_details &cd) const
 {
   class strchr_call_info : public call_info
   {
@@ -1534,7 +1697,7 @@
     bool m_found;
   };
 
-  /* Body of region_model::impl_call_strchr.  */
+  /* Body of kf_strchr::impl_call_post.  */
   if (cd.get_ctxt ())
     {
       cd.get_ctxt ()->bifurcate (make_unique<strchr_call_info> (cd, false));
@@ -1543,41 +1706,116 @@
     }
 }
 
-/* Handle the on_call_pre part of "strcpy" and "__builtin_strcpy_chk".  */
+/* Handler for "__builtin_stack_restore".  */
+
+class kf_stack_restore : public known_function
+{
+public:
+  bool matches_call_types_p (const call_details &) const final override
+  {
+    return true;
+  }
+
+  /* Currently a no-op.  */
+};
+
+/* Handler for "__builtin_stack_save".  */
+
+class kf_stack_save : public known_function
+{
+public:
+  bool matches_call_types_p (const call_details &) const final override
+  {
+    return true;
+  }
+
+  /* Currently a no-op.  */
+};
+
+/* Handler for various stdio-related builtins that merely have external
+   effects that are out of scope for the analyzer: we only want to model
+   the effects on the return value.  */
+
+class kf_stdio_output_fn : public known_function
+{
+public:
+  bool matches_call_types_p (const call_details &) const final override
+  {
+    return true;
+  }
+
+  /* A no-op; we just want the conjured return value.  */
+};
+
+/* Handler for "strcpy" and "__builtin_strcpy_chk".  */
+
+class kf_strcpy : public known_function
+{
+public:
+  kf_strcpy (unsigned int num_args) : m_num_args (num_args) {}
+  bool matches_call_types_p (const call_details &cd) const final override
+  {
+    return (cd.num_args () == m_num_args
+	    && cd.arg_is_pointer_p (0)
+	    && cd.arg_is_pointer_p (1));
+  }
+
+  void impl_call_pre (const call_details &cd) const final override;
+
+private:
+  unsigned int m_num_args;
+};
 
 void
-region_model::impl_call_strcpy (const call_details &cd)
+kf_strcpy::impl_call_pre (const call_details &cd) const
 {
+  region_model *model = cd.get_model ();
+  region_model_manager *mgr = cd.get_manager ();
+
   const svalue *dest_sval = cd.get_arg_svalue (0);
-  const region *dest_reg = deref_rvalue (dest_sval, cd.get_arg_tree (0),
+  const region *dest_reg = model->deref_rvalue (dest_sval, cd.get_arg_tree (0),
 					 cd.get_ctxt ());
   const svalue *src_sval = cd.get_arg_svalue (1);
-  const region *src_reg = deref_rvalue (src_sval, cd.get_arg_tree (1),
+  const region *src_reg = model->deref_rvalue (src_sval, cd.get_arg_tree (1),
 					cd.get_ctxt ());
-  const svalue *src_contents_sval = get_store_value (src_reg,
-						     cd.get_ctxt ());
+  const svalue *src_contents_sval = model->get_store_value (src_reg,
+							    cd.get_ctxt ());
 
   cd.maybe_set_lhs (dest_sval);
 
   /* Try to get the string size if SRC_REG is a string_region.  */
-  const svalue *copied_bytes_sval = get_string_size (src_reg);
+  const svalue *copied_bytes_sval = model->get_string_size (src_reg);
   /* Otherwise, check if the contents of SRC_REG is a string.  */
   if (copied_bytes_sval->get_kind () == SK_UNKNOWN)
-    copied_bytes_sval = get_string_size (src_contents_sval);
+    copied_bytes_sval = model->get_string_size (src_contents_sval);
 
   const region *sized_dest_reg
-    = m_mgr->get_sized_region (dest_reg, NULL_TREE, copied_bytes_sval);
-  set_value (sized_dest_reg, src_contents_sval, cd.get_ctxt ());
+    = mgr->get_sized_region (dest_reg, NULL_TREE, copied_bytes_sval);
+  model->set_value (sized_dest_reg, src_contents_sval, cd.get_ctxt ());
 }
 
 /* Handle the on_call_pre part of "strlen".  */
 
+class kf_strlen : public known_function
+{
+public:
+  bool matches_call_types_p (const call_details &cd) const final override
+  {
+    return (cd.num_args () == 1 && cd.arg_is_pointer_p (0));
+  }
+  void impl_call_pre (const call_details &cd) const final override;
+};
+
 void
-region_model::impl_call_strlen (const call_details &cd)
+kf_strlen::impl_call_pre (const call_details &cd) const
 {
   region_model_context *ctxt = cd.get_ctxt ();
+  region_model *model = cd.get_model ();
+  region_model_manager *mgr = cd.get_manager ();
+
   const svalue *arg_sval = cd.get_arg_svalue (0);
-  const region *buf_reg = deref_rvalue (arg_sval, cd.get_arg_tree (0), ctxt);
+  const region *buf_reg
+    = model->deref_rvalue (arg_sval, cd.get_arg_tree (0), ctxt);
   if (const string_region *str_reg
       = buf_reg->dyn_cast_string_region ())
     {
@@ -1589,7 +1827,7 @@
 	{
 	  tree t_cst = build_int_cst (cd.get_lhs_type (), strlen_cst);
 	  const svalue *result_sval
-	    = m_mgr->get_or_create_constant_svalue (t_cst);
+	    = mgr->get_or_create_constant_svalue (t_cst);
 	  cd.maybe_set_lhs (result_sval);
 	  return;
 	}
@@ -1597,13 +1835,19 @@
   /* Otherwise a conjured value.  */
 }
 
+class kf_ubsan_bounds : public internal_known_function
+{
+  /* Empty.  */
+};
+
 /* Handle calls to functions referenced by
    __attribute__((malloc(FOO))).  */
 
 void
 region_model::impl_deallocation_call (const call_details &cd)
 {
-  impl_call_free (cd);
+  kf_free kf;
+  kf.impl_call_post (cd);
 }
 
 /* Populate KFM with instances of known functions supported by the core of the
@@ -1612,6 +1856,54 @@
 void
 register_known_functions (known_function_manager &kfm)
 {
+  /* Internal fns the analyzer has known_functions for.  */
+  {
+    kfm.add (IFN_BUILTIN_EXPECT, make_unique<kf_expect> ());
+    kfm.add (IFN_UBSAN_BOUNDS, make_unique<kf_ubsan_bounds> ());
+  }
+
+  /* Built-ins the analyzer has known_functions for.  */
+  {
+    kfm.add (BUILT_IN_ALLOCA, make_unique<kf_alloca> ());
+    kfm.add (BUILT_IN_ALLOCA_WITH_ALIGN, make_unique<kf_alloca> ());
+    kfm.add (BUILT_IN_CALLOC, make_unique<kf_calloc> ());
+    kfm.add (BUILT_IN_EXPECT, make_unique<kf_expect> ());
+    kfm.add (BUILT_IN_EXPECT_WITH_PROBABILITY, make_unique<kf_expect> ());
+    kfm.add (BUILT_IN_FPRINTF, make_unique<kf_stdio_output_fn> ());
+    kfm.add (BUILT_IN_FPRINTF_UNLOCKED, make_unique<kf_stdio_output_fn> ());
+    kfm.add (BUILT_IN_FPUTC, make_unique<kf_stdio_output_fn> ());
+    kfm.add (BUILT_IN_FPUTC_UNLOCKED, make_unique<kf_stdio_output_fn> ());
+    kfm.add (BUILT_IN_FPUTS, make_unique<kf_stdio_output_fn> ());
+    kfm.add (BUILT_IN_FPUTS_UNLOCKED, make_unique<kf_stdio_output_fn> ());
+    kfm.add (BUILT_IN_FREE, make_unique<kf_free> ());
+    kfm.add (BUILT_IN_FWRITE, make_unique<kf_stdio_output_fn> ());
+    kfm.add (BUILT_IN_FWRITE_UNLOCKED, make_unique<kf_stdio_output_fn> ());
+    kfm.add (BUILT_IN_MALLOC, make_unique<kf_malloc> ());
+    kfm.add (BUILT_IN_MEMCPY, make_unique<kf_memcpy> ());
+    kfm.add (BUILT_IN_MEMCPY_CHK, make_unique<kf_memcpy> ());
+    kfm.add (BUILT_IN_MEMSET, make_unique<kf_memset> ());
+    kfm.add (BUILT_IN_MEMSET_CHK, make_unique<kf_memset> ());
+    kfm.add (BUILT_IN_PRINTF, make_unique<kf_stdio_output_fn> ());
+    kfm.add (BUILT_IN_PRINTF_UNLOCKED, make_unique<kf_stdio_output_fn> ());
+    kfm.add (BUILT_IN_PUTC, make_unique<kf_stdio_output_fn> ());
+    kfm.add (BUILT_IN_PUTCHAR, make_unique<kf_stdio_output_fn> ());
+    kfm.add (BUILT_IN_PUTCHAR_UNLOCKED, make_unique<kf_stdio_output_fn> ());
+    kfm.add (BUILT_IN_PUTC_UNLOCKED, make_unique<kf_stdio_output_fn> ());
+    kfm.add (BUILT_IN_PUTS, make_unique<kf_stdio_output_fn> ());
+    kfm.add (BUILT_IN_PUTS_UNLOCKED, make_unique<kf_stdio_output_fn> ());
+    kfm.add (BUILT_IN_REALLOC, make_unique<kf_realloc> ());
+    kfm.add (BUILT_IN_STACK_RESTORE, make_unique<kf_stack_restore> ());
+    kfm.add (BUILT_IN_STACK_SAVE, make_unique<kf_stack_save> ());
+    kfm.add (BUILT_IN_STRCHR, make_unique<kf_strchr> ());
+    kfm.add (BUILT_IN_STRCPY, make_unique<kf_strcpy> (2));
+    kfm.add (BUILT_IN_STRCPY_CHK, make_unique<kf_strcpy> (3));
+    kfm.add (BUILT_IN_STRLEN, make_unique<kf_strlen> ());
+    kfm.add (BUILT_IN_VFPRINTF, make_unique<kf_stdio_output_fn> ());
+    kfm.add (BUILT_IN_VPRINTF, make_unique<kf_stdio_output_fn> ());
+
+    register_varargs_builtins (kfm);
+  }
+
   /* Debugging/test support functions, all  with a "__analyzer_" prefix.  */
   {
     kfm.add ("__analyzer_break", make_unique<kf_analyzer_break> ());
@@ -1633,11 +1925,20 @@
 	     make_unique<kf_analyzer_get_unknown_ptr> ());
   }
 
-  /* Known POSIX functions.  */
+  /* Known builtins and C standard library functions.  */
+  {
+    kfm.add ("getchar", make_unique<kf_getchar> ());
+    kfm.add ("memset", make_unique<kf_memset> ());
+  }
+
+  /* Known POSIX functions, and some non-standard extensions.  */
   {
     kfm.add ("accept", make_unique<kf_accept> ());
     kfm.add ("bind", make_unique<kf_bind> ());
     kfm.add ("connect", make_unique<kf_connect> ());
+    kfm.add ("fgets", make_unique<kf_fgets> ());
+    kfm.add ("fgets_unlocked", make_unique<kf_fgets> ()); // non-standard
+    kfm.add ("fread", make_unique<kf_fread> ());
     kfm.add ("listen", make_unique<kf_listen> ());
     kfm.add ("pipe", make_unique<kf_pipe> (1));
     kfm.add ("pipe2", make_unique<kf_pipe> (2));
@@ -1648,6 +1949,8 @@
   /* glibc functions.  */
   {
     kfm.add ("__errno_location", make_unique<kf_errno_location> ());
+    kfm.add ("error", make_unique<kf_error> (3));
+    kfm.add ("error_at_line", make_unique<kf_error> (5));
   }
 
   /* C++ support functions.  */
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index e71fd41..92f8b94 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -1160,13 +1160,11 @@
 }
 
 /* Handle the pre-sm-state part of STMT, modifying this object in-place.
-   Write true to *OUT_TERMINATE_PATH if the path should be terminated.
    Write true to *OUT_UNKNOWN_SIDE_EFFECTS if the stmt has unknown
    side effects.  */
 
 void
 region_model::on_stmt_pre (const gimple *stmt,
-			   bool *out_terminate_path,
 			   bool *out_unknown_side_effects,
 			   region_model_context *ctxt)
 {
@@ -1196,8 +1194,7 @@
 	   anything, for which we don't have a function body, or for which we
 	   don't know the fndecl.  */
 	const gcall *call = as_a <const gcall *> (stmt);
-	*out_unknown_side_effects
-	  = on_call_pre (call, ctxt, out_terminate_path);
+	*out_unknown_side_effects = on_call_pre (call, ctxt);
       }
       break;
 
@@ -2030,13 +2027,28 @@
   return NULL;
 }
 
-/* Get any known_function for FNDECL, or NULL if there is none.  */
+/* Get any known_function for FNDECL for call CD.
+
+   The call must match all assumptions made by the known_function (such as
+   e.g. "argument 1's type must be a pointer type").
+
+   Return NULL if no known_function is found, or it does not match the
+   assumption(s).  */
 
 const known_function *
-region_model::get_known_function (tree fndecl) const
+region_model::get_known_function (tree fndecl, const call_details &cd) const
 {
   known_function_manager *known_fn_mgr = m_mgr->get_known_function_manager ();
-  return known_fn_mgr->get_by_fndecl (fndecl);
+  return known_fn_mgr->get_match (fndecl, cd);
+}
+
+/* Get any known_function for IFN, or NULL.  */
+
+const known_function *
+region_model::get_known_function (enum internal_fn ifn) const
+{
+  known_function_manager *known_fn_mgr = m_mgr->get_known_function_manager ();
+  return known_fn_mgr->get_internal_fn (ifn);
 }
 
 /* Update this model for the CALL stmt, using CTXT to report any
@@ -2048,14 +2060,10 @@
 
    Return true if the function call has unknown side effects (it wasn't
    recognized and we don't have a body for it, or are unable to tell which
-   fndecl it is).
-
-   Write true to *OUT_TERMINATE_PATH if this execution path should be
-   terminated (e.g. the function call terminates the process).  */
+   fndecl it is).  */
 
 bool
-region_model::on_call_pre (const gcall *call, region_model_context *ctxt,
-			   bool *out_terminate_path)
+region_model::on_call_pre (const gcall *call, region_model_context *ctxt)
 {
   call_details cd (call, this, ctxt);
 
@@ -2099,188 +2107,28 @@
     }
 
   if (gimple_call_internal_p (call))
-    {
-      switch (gimple_call_internal_fn (call))
-       {
-       default:
-	 break;
-       case IFN_BUILTIN_EXPECT:
-	 impl_call_builtin_expect (cd);
-	 return false;
-       case IFN_UBSAN_BOUNDS:
-	 return false;
-       case IFN_VA_ARG:
-	 impl_call_va_arg (cd);
-	 return false;
-       }
-    }
+    if (const known_function *kf
+	  = get_known_function (gimple_call_internal_fn (call)))
+      {
+	kf->impl_call_pre (cd);
+	return false;
+      }
 
   if (tree callee_fndecl = get_fndecl_for_call (call, ctxt))
     {
-      /* The various impl_call_* member functions are implemented
-	 in region-model-impl-calls.cc.
-	 Having them split out into separate functions makes it easier
-	 to put breakpoints on the handling of specific functions.  */
       int callee_fndecl_flags = flags_from_decl_or_type (callee_fndecl);
 
-      if (fndecl_built_in_p (callee_fndecl, BUILT_IN_NORMAL)
+      if (const known_function *kf = get_known_function (callee_fndecl, cd))
+	{
+	  kf->impl_call_pre (cd);
+	  return false;
+	}
+      else if (fndecl_built_in_p (callee_fndecl, BUILT_IN_NORMAL)
 	  && gimple_builtin_call_types_compatible_p (call, callee_fndecl))
-	switch (DECL_UNCHECKED_FUNCTION_CODE (callee_fndecl))
-	  {
-	  default:
-	    if (!(callee_fndecl_flags & (ECF_CONST | ECF_PURE)))
-	      unknown_side_effects = true;
-	    break;
-	  case BUILT_IN_ALLOCA:
-	  case BUILT_IN_ALLOCA_WITH_ALIGN:
-	    impl_call_alloca (cd);
-	    return false;
-	  case BUILT_IN_CALLOC:
-	    impl_call_calloc (cd);
-	    return false;
-	  case BUILT_IN_EXPECT:
-	  case BUILT_IN_EXPECT_WITH_PROBABILITY:
-	    impl_call_builtin_expect (cd);
-	    return false;
-	  case BUILT_IN_FREE:
-	    /* Handle in "on_call_post".  */
-	    break;
-	  case BUILT_IN_MALLOC:
-	    impl_call_malloc (cd);
-	    return false;
-	  case BUILT_IN_MEMCPY:
-	  case BUILT_IN_MEMCPY_CHK:
-	    impl_call_memcpy (cd);
-	    return false;
-	  case BUILT_IN_MEMSET:
-	  case BUILT_IN_MEMSET_CHK:
-	    impl_call_memset (cd);
-	    return false;
-	    break;
-	  case BUILT_IN_REALLOC:
-	    return false;
-	  case BUILT_IN_STRCHR:
-	    /* Handle in "on_call_post".  */
-	    return false;
-	  case BUILT_IN_STRCPY:
-	  case BUILT_IN_STRCPY_CHK:
-	    impl_call_strcpy (cd);
-	    return false;
-	  case BUILT_IN_STRLEN:
-	    impl_call_strlen (cd);
-	    return false;
-
-	  case BUILT_IN_STACK_SAVE:
-	  case BUILT_IN_STACK_RESTORE:
-	    return false;
-
-	  /* Stdio builtins.  */
-	  case BUILT_IN_FPRINTF:
-	  case BUILT_IN_FPRINTF_UNLOCKED:
-	  case BUILT_IN_PUTC:
-	  case BUILT_IN_PUTC_UNLOCKED:
-	  case BUILT_IN_FPUTC:
-	  case BUILT_IN_FPUTC_UNLOCKED:
-	  case BUILT_IN_FPUTS:
-	  case BUILT_IN_FPUTS_UNLOCKED:
-	  case BUILT_IN_FWRITE:
-	  case BUILT_IN_FWRITE_UNLOCKED:
-	  case BUILT_IN_PRINTF:
-	  case BUILT_IN_PRINTF_UNLOCKED:
-	  case BUILT_IN_PUTCHAR:
-	  case BUILT_IN_PUTCHAR_UNLOCKED:
-	  case BUILT_IN_PUTS:
-	  case BUILT_IN_PUTS_UNLOCKED:
-	  case BUILT_IN_VFPRINTF:
-	  case BUILT_IN_VPRINTF:
-	    /* These stdio builtins have external effects that are out
-	       of scope for the analyzer: we only want to model the effects
-	       on the return value.  */
-	    break;
-
-	  case BUILT_IN_VA_START:
-	    impl_call_va_start (cd);
-	    return false;
-	  case BUILT_IN_VA_COPY:
-	    impl_call_va_copy (cd);
-	    return false;
-	  }
-      else if (is_named_call_p (callee_fndecl, "malloc", call, 1))
 	{
-	  impl_call_malloc (cd);
-	  return false;
-	}
-      else if (is_named_call_p (callee_fndecl, "calloc", call, 2))
-	{
-	  impl_call_calloc (cd);
-	  return false;
-	}
-      else if (is_named_call_p (callee_fndecl, "alloca", call, 1))
-	{
-	  impl_call_alloca (cd);
-	  return false;
-	}
-      else if (is_named_call_p (callee_fndecl, "realloc", call, 2))
-	{
-	  impl_call_realloc (cd);
-	  return false;
-	}
-      else if (is_named_call_p (callee_fndecl, "error"))
-	{
-	  if (impl_call_error (cd, 3, out_terminate_path))
-	    return false;
-	  else
+	  if (!(callee_fndecl_flags & (ECF_CONST | ECF_PURE)))
 	    unknown_side_effects = true;
 	}
-      else if (is_named_call_p (callee_fndecl, "error_at_line"))
-	{
-	  if (impl_call_error (cd, 5, out_terminate_path))
-	    return false;
-	  else
-	    unknown_side_effects = true;
-	}
-      else if (is_named_call_p (callee_fndecl, "fgets", call, 3)
-	       || is_named_call_p (callee_fndecl, "fgets_unlocked", call, 3))
-	{
-	  impl_call_fgets (cd);
-	  return false;
-	}
-      else if (is_named_call_p (callee_fndecl, "fread", call, 4))
-	{
-	  impl_call_fread (cd);
-	  return false;
-	}
-      else if (is_named_call_p (callee_fndecl, "getchar", call, 0))
-	{
-	  /* No side-effects (tracking stream state is out-of-scope
-	     for the analyzer).  */
-	}
-      else if (is_named_call_p (callee_fndecl, "memset", call, 3)
-	       && POINTER_TYPE_P (cd.get_arg_type (0)))
-	{
-	  impl_call_memset (cd);
-	  return false;
-	}
-      else if (is_named_call_p (callee_fndecl, "strchr", call, 2)
-	       && POINTER_TYPE_P (cd.get_arg_type (0)))
-	{
-	  /* Handle in "on_call_post".  */
-	  return false;
-	}
-      else if (is_named_call_p (callee_fndecl, "strlen", call, 1)
-	       && POINTER_TYPE_P (cd.get_arg_type (0)))
-	{
-	  impl_call_strlen (cd);
-	  return false;
-	}
-      else if (const known_function *kf = get_known_function (callee_fndecl))
-	{
-	  if (kf->matches_call_types_p (cd))
-	    {
-	      kf->impl_call_pre (cd);
-	      return false;
-	    }
-	}
       else if (!fndecl_has_gimple_body_p (callee_fndecl)
 	       && (!(callee_fndecl_flags & (ECF_CONST | ECF_PURE)))
 	       && !fndecl_built_in_p (callee_fndecl))
@@ -2310,25 +2158,11 @@
   if (tree callee_fndecl = get_fndecl_for_call (call, ctxt))
     {
       call_details cd (call, this, ctxt);
-      if (is_named_call_p (callee_fndecl, "free", call, 1))
+      if (const known_function *kf = get_known_function (callee_fndecl, cd))
 	{
-	  impl_call_free (cd);
+	  kf->impl_call_post (cd);
 	  return;
 	}
-      else if (is_named_call_p (callee_fndecl, "strchr", call, 2)
-	       && POINTER_TYPE_P (cd.get_arg_type (0)))
-	{
-	  impl_call_strchr (cd);
-	  return;
-	}
-      else if (const known_function *kf = get_known_function (callee_fndecl))
-	{
-	  if (kf->matches_call_types_p (cd))
-	    {
-	      kf->impl_call_post (cd);
-	      return;
-	    }
-	}
       /* Was this fndecl referenced by
 	 __attribute__((malloc(FOO)))?  */
       if (lookup_attribute ("*dealloc", DECL_ATTRIBUTES (callee_fndecl)))
@@ -2336,24 +2170,6 @@
 	  impl_deallocation_call (cd);
 	  return;
 	}
-      if (fndecl_built_in_p (callee_fndecl, BUILT_IN_NORMAL)
-	  && gimple_builtin_call_types_compatible_p (call, callee_fndecl))
-	switch (DECL_UNCHECKED_FUNCTION_CODE (callee_fndecl))
-	  {
-	  default:
-	    break;
-	  case BUILT_IN_REALLOC:
-	    impl_call_realloc (cd);
-	    return;
-
-	  case BUILT_IN_STRCHR:
-	    impl_call_strchr (cd);
-	    return;
-
-	  case BUILT_IN_VA_END:
-	    impl_call_va_end (cd);
-	    return;
-	  }
     }
 
   if (unknown_side_effects)
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index 244780e..8e4616c 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -260,6 +260,7 @@
   {
     return POINTER_TYPE_P (get_arg_type (idx));
   }
+  bool arg_is_size_p (unsigned idx) const;
 
   const gcall *get_call_stmt () const { return m_call; }
   location_t get_location () const;
@@ -326,7 +327,6 @@
 
   void
   on_stmt_pre (const gimple *stmt,
-	       bool *out_terminate_path,
 	       bool *out_unknown_side_effects,
 	       region_model_context *ctxt);
 
@@ -334,38 +334,15 @@
   const svalue *get_gassign_result (const gassign *assign,
 				    region_model_context *ctxt);
   void on_asm_stmt (const gasm *asm_stmt, region_model_context *ctxt);
-  bool on_call_pre (const gcall *stmt, region_model_context *ctxt,
-		    bool *out_terminate_path);
+  bool on_call_pre (const gcall *stmt, region_model_context *ctxt);
   void on_call_post (const gcall *stmt,
 		     bool unknown_side_effects,
 		     region_model_context *ctxt);
 
   void purge_state_involving (const svalue *sval, region_model_context *ctxt);
 
-  /* Specific handling for on_call_pre.  */
-  void impl_call_alloca (const call_details &cd);
-  void impl_call_builtin_expect (const call_details &cd);
-  void impl_call_calloc (const call_details &cd);
-  bool impl_call_error (const call_details &cd, unsigned min_args,
-			bool *out_terminate_path);
-  void impl_call_fgets (const call_details &cd);
-  void impl_call_fread (const call_details &cd);
-  void impl_call_free (const call_details &cd);
-  void impl_call_malloc (const call_details &cd);
-  void impl_call_memcpy (const call_details &cd);
-  void impl_call_memset (const call_details &cd);
-  void impl_call_realloc (const call_details &cd);
-  void impl_call_strchr (const call_details &cd);
-  void impl_call_strcpy (const call_details &cd);
-  void impl_call_strlen (const call_details &cd);
   void impl_deallocation_call (const call_details &cd);
 
-  /* Implemented in varargs.cc.  */
-  void impl_call_va_start (const call_details &cd);
-  void impl_call_va_copy (const call_details &cd);
-  void impl_call_va_arg (const call_details &cd);
-  void impl_call_va_end (const call_details &cd);
-
   const svalue *maybe_get_copy_bounds (const region *src_reg,
 				       const svalue *num_bytes_sval);
   void update_for_int_cst_return (const call_details &cd,
@@ -562,6 +539,9 @@
 				  tree expr,
 				  region_model_context *ctxt) const;
 
+  void check_region_for_write (const region *dest_reg,
+			       region_model_context *ctxt) const;
+
 private:
   const region *get_lvalue_1 (path_var pv, region_model_context *ctxt) const;
   const svalue *get_rvalue_1 (path_var pv, region_model_context *ctxt) const;
@@ -573,7 +553,9 @@
   get_representative_path_var_1 (const region *reg,
 				 svalue_set *visited) const;
 
-  const known_function *get_known_function (tree fndecl) const;
+  const known_function *get_known_function (tree fndecl,
+					    const call_details &cd) const;
+  const known_function *get_known_function (enum internal_fn) const;
 
   bool add_constraints_from_binop (const svalue *outer_lhs,
 				   enum tree_code outer_op,
@@ -622,8 +604,6 @@
   void check_region_access (const region *reg,
 			    enum access_direction dir,
 			    region_model_context *ctxt) const;
-  void check_region_for_write (const region *dest_reg,
-			       region_model_context *ctxt) const;
   void check_region_for_read (const region *src_reg,
 			      region_model_context *ctxt) const;
   void check_region_size (const region *lhs_reg, const svalue *rhs_sval,
diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc
index dd10356..94ca295 100644
--- a/gcc/analyzer/sm-malloc.cc
+++ b/gcc/analyzer/sm-malloc.cc
@@ -2175,7 +2175,7 @@
    Check for free of non-heap or mismatching allocators,
    transitioning to the "stop" state for such cases.
 
-   Otherwise, region_model::impl_call_realloc will later
+   Otherwise, kf_realloc::impl_call_post will later
    get called (which will handle other sm-state transitions
    when the state is bifurcated).  */
 
@@ -2432,7 +2432,7 @@
 }
 
 /* Specialcase hook for handling realloc, for use by
-   region_model::impl_call_realloc::success_with_move::update_model.  */
+   kf_realloc::impl_call_post::success_with_move::update_model.  */
 
 void
 region_model::on_realloc_with_move (const call_details &cd,
diff --git a/gcc/analyzer/varargs.cc b/gcc/analyzer/varargs.cc
index 6fc20f0..1da5a46 100644
--- a/gcc/analyzer/varargs.cc
+++ b/gcc/analyzer/varargs.cc
@@ -647,33 +647,45 @@
   return new va_list_state_machine (logger);
 }
 
-/* Handle the on_call_pre part of "__builtin_va_start".  */
+/* Handler for "__builtin_va_start".  */
+
+class kf_va_start : public known_function
+{
+public:
+  bool matches_call_types_p (const call_details &) const
+  {
+    return true;
+  }
+  void impl_call_pre (const call_details &cd) const final override;
+};
 
 void
-region_model::impl_call_va_start (const call_details &cd)
+kf_va_start::impl_call_pre (const call_details &cd) const
 {
+  region_model *model = cd.get_model ();
+  region_model_manager *mgr = cd.get_manager ();
   const svalue *out_ptr = cd.get_arg_svalue (0);
   const region *out_reg
-    = deref_rvalue (out_ptr, cd.get_arg_tree (0), cd.get_ctxt ());
+    = model->deref_rvalue (out_ptr, cd.get_arg_tree (0), cd.get_ctxt ());
+  const frame_region *frame = model->get_current_frame ();
 
   /* "*out_ptr = &IMPL_REGION;".  */
-  const region *impl_reg = m_mgr->create_region_for_alloca (m_current_frame);
+  const region *impl_reg = mgr->create_region_for_alloca (frame);
 
   /* We abuse the types here, since va_list_type isn't
      necessarily anything to do with a pointer.  */
-  const svalue *ptr_to_impl_reg = m_mgr->get_ptr_svalue (NULL_TREE, impl_reg);
-  set_value (out_reg, ptr_to_impl_reg, cd.get_ctxt ());
+  const svalue *ptr_to_impl_reg = mgr->get_ptr_svalue (NULL_TREE, impl_reg);
+  model->set_value (out_reg, ptr_to_impl_reg, cd.get_ctxt ());
 
-  if (get_stack_depth () > 1)
+  if (model->get_stack_depth () > 1)
     {
       /* The interprocedural case: the frame containing the va_start call
 	 will have been populated with any variadic aruguments.
 	 Initialize IMPL_REGION with a ptr to var_arg_region 0.  */
-      const region *init_var_arg_reg
-	= m_mgr->get_var_arg_region (get_current_frame (), 0);
+      const region *init_var_arg_reg = mgr->get_var_arg_region (frame, 0);
       const svalue *ap_sval
-	= m_mgr->get_ptr_svalue (NULL_TREE, init_var_arg_reg);
-      set_value (impl_reg, ap_sval, cd.get_ctxt ());
+	= mgr->get_ptr_svalue (NULL_TREE, init_var_arg_reg);
+      model->set_value (impl_reg, ap_sval, cd.get_ctxt ());
     }
   else
     {
@@ -682,40 +694,52 @@
 	 Initialize IMPL_REGION as the UNKNOWN_SVALUE to avoid state
 	 explosions on repeated calls to va_arg.  */
       const svalue *unknown_sval
-	= m_mgr->get_or_create_unknown_svalue (NULL_TREE);
-      set_value (impl_reg, unknown_sval, cd.get_ctxt ());
+	= mgr->get_or_create_unknown_svalue (NULL_TREE);
+      model->set_value (impl_reg, unknown_sval, cd.get_ctxt ());
     }
 }
 
-/* Handle the on_call_pre part of "__builtin_va_copy".  */
+/* Handler for "__builtin_va_copy".  */
+
+class kf_va_copy : public known_function
+{
+public:
+  bool matches_call_types_p (const call_details &cd) const
+  {
+    return true;
+  }
+  void impl_call_pre (const call_details &cd) const final override;
+};
 
 void
-region_model::impl_call_va_copy (const call_details &cd)
+kf_va_copy::impl_call_pre (const call_details &cd) const
 {
+  region_model *model = cd.get_model ();
+  region_model_manager *mgr = cd.get_manager ();
   const svalue *out_dst_ptr = cd.get_arg_svalue (0);
   const svalue *in_va_list
-    = get_va_copy_arg (this, cd.get_ctxt (), cd.get_call_stmt (), 1);
-  in_va_list = check_for_poison (in_va_list,
-				 get_va_list_diag_arg (cd.get_arg_tree (1)),
-				 cd.get_ctxt ());
+    = get_va_copy_arg (model, cd.get_ctxt (), cd.get_call_stmt (), 1);
+  in_va_list
+    = model->check_for_poison (in_va_list,
+			       get_va_list_diag_arg (cd.get_arg_tree (1)),
+			       cd.get_ctxt ());
 
   const region *out_dst_reg
-    = deref_rvalue (out_dst_ptr, cd.get_arg_tree (0), cd.get_ctxt ());
+    = model->deref_rvalue (out_dst_ptr, cd.get_arg_tree (0), cd.get_ctxt ());
 
   /* "*out_dst_ptr = &NEW_IMPL_REGION;".  */
   const region *new_impl_reg
-    = m_mgr->create_region_for_alloca (m_current_frame);
+    = mgr->create_region_for_alloca (model->get_current_frame ());
   const svalue *ptr_to_new_impl_reg
-    = m_mgr->get_ptr_svalue (NULL_TREE, new_impl_reg);
-  set_value (out_dst_reg, ptr_to_new_impl_reg, cd.get_ctxt ());
+    = mgr->get_ptr_svalue (NULL_TREE, new_impl_reg);
+  model->set_value (out_dst_reg, ptr_to_new_impl_reg, cd.get_ctxt ());
 
   if (const region *old_impl_reg = in_va_list->maybe_get_region ())
     {
-
       /* "(NEW_IMPL_REGION) = (OLD_IMPL_REGION);".  */
       const svalue *existing_sval
-	= get_store_value (old_impl_reg, cd.get_ctxt ());
-      set_value (new_impl_reg, existing_sval, cd.get_ctxt ());
+	= model->get_store_value (old_impl_reg, cd.get_ctxt ());
+      model->set_value (new_impl_reg, existing_sval, cd.get_ctxt ());
     }
 }
 
@@ -956,26 +980,35 @@
   return NULL;
 }
 
-/* Handle the on_call_pre part of "__builtin_va_arg".  */
+/* Handler for "__builtin_va_arg".  */
+
+class kf_va_arg : public internal_known_function
+{
+public:
+  void impl_call_pre (const call_details &cd) const final override;
+};
 
 void
-region_model::impl_call_va_arg (const call_details &cd)
+kf_va_arg::impl_call_pre (const call_details &cd) const
 {
   region_model_context *ctxt = cd.get_ctxt ();
+  region_model *model = cd.get_model ();
+  region_model_manager *mgr = cd.get_manager ();
 
   const svalue *in_ptr = cd.get_arg_svalue (0);
-  const region *ap_reg = deref_rvalue (in_ptr, cd.get_arg_tree (0), ctxt);
+  const region *ap_reg
+    = model->deref_rvalue (in_ptr, cd.get_arg_tree (0), ctxt);
 
-  const svalue *ap_sval = get_store_value (ap_reg, ctxt);
+  const svalue *ap_sval = model->get_store_value (ap_reg, ctxt);
   if (const svalue *cast = ap_sval->maybe_undo_cast ())
     ap_sval = cast;
 
   tree va_list_tree = get_va_list_diag_arg (cd.get_arg_tree (0));
-  ap_sval = check_for_poison (ap_sval, va_list_tree, ctxt);
+  ap_sval = model->check_for_poison (ap_sval, va_list_tree, ctxt);
 
   if (const region *impl_reg = ap_sval->maybe_get_region ())
     {
-      const svalue *old_impl_sval = get_store_value (impl_reg, ctxt);
+      const svalue *old_impl_sval = model->get_store_value (impl_reg, ctxt);
       if (const var_arg_region *arg_reg
 	  = maybe_get_var_arg_region (old_impl_sval))
 	{
@@ -992,8 +1025,8 @@
 		 has a conjured_svalue), or warn if there's a problem
 		 (incompatible types, or if we've run out of args).  */
 	      if (const svalue *arg_sval
-		  = m_store.get_any_binding (m_mgr->get_store_manager (),
-					     arg_reg))
+		  = model->get_store ()->get_any_binding
+		      (mgr->get_store_manager (), arg_reg))
 		{
 		  tree lhs_type = cd.get_lhs_type ();
 		  tree arg_type = arg_sval->get_type ();
@@ -1031,28 +1064,42 @@
 	    {
 	      /* Set impl_reg to UNKNOWN to suppress further warnings.  */
 	      const svalue *new_ap_sval
-		= m_mgr->get_or_create_unknown_svalue (impl_reg->get_type ());
-	      set_value (impl_reg, new_ap_sval, ctxt);
+		= mgr->get_or_create_unknown_svalue (impl_reg->get_type ());
+	      model->set_value (impl_reg, new_ap_sval, ctxt);
 	    }
 	  else
 	    {
 	      /* Update impl_reg to advance to the next arg.  */
 	      const region *next_var_arg_region
-		= m_mgr->get_var_arg_region (frame_reg, next_arg_idx + 1);
+		= mgr->get_var_arg_region (frame_reg, next_arg_idx + 1);
 	      const svalue *new_ap_sval
-		= m_mgr->get_ptr_svalue (NULL_TREE, next_var_arg_region);
-	      set_value (impl_reg, new_ap_sval, ctxt);
+		= mgr->get_ptr_svalue (NULL_TREE, next_var_arg_region);
+	      model->set_value (impl_reg, new_ap_sval, ctxt);
 	    }
 	}
     }
 }
 
-/* Handle the on_call_post part of "__builtin_va_end".  */
+/* Handler for "__builtin_va_end".  */
+
+class kf_va_end : public known_function
+{
+public:
+  bool matches_call_types_p (const call_details &) const
+  {
+    return true;
+  }
+};
+
+/* Populate KFM with instances of known functions relating to varargs.  */
 
 void
-region_model::impl_call_va_end (const call_details &)
+register_varargs_builtins (known_function_manager &kfm)
 {
-  /* No-op.  */
+  kfm.add (BUILT_IN_VA_START, make_unique<kf_va_start> ());
+  kfm.add (BUILT_IN_VA_COPY, make_unique<kf_va_copy> ());
+  kfm.add (IFN_VA_ARG, make_unique<kf_va_arg> ());
+  kfm.add (BUILT_IN_VA_END, make_unique<kf_va_end> ());
 }
 
 } // namespace ana