From f2044d8786c9a5e624345ffbda4c64f890cbb983 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Fri, 4 Jan 2019 07:34:18 -0500
Subject: [PATCH 4/4] FIXME: WIP on parsing checkers

---
 checkers/address_of_function.sm |    2 +-
 checkers/buffer_overflow.sm     |    4 +-
 checkers/fd_checker.sm          |   12 +-
 checkers/fileptr.sm             |    8 +-
 checkers/malloc_checker.sm      |   38 +-
 checkers/points_to_stack.sm     |    4 +-
 checkers/python.sm              |   12 +-
 checkers/signal_handler.sm      |    4 +-
 checkers/sizeof_allocation.sm   |    6 +-
 checkers/taint.sm               |   26 +-
 gcc/Makefile.in                 |    1 +
 gcc/checker-script.cc           | 1378 +++++++++++++++++++++++++++++++++++++++
 gcc/selftest-run-tests.c        |    3 +
 gcc/selftest.h                  |    4 +
 14 files changed, 1444 insertions(+), 58 deletions(-)
 create mode 100644 gcc/checker-script.cc

diff --git a/checkers/address_of_function.sm b/checkers/address_of_function.sm
index 13866c2..9917ab8 100644
--- a/checkers/address_of_function.sm
+++ b/checkers/address_of_function.sm
@@ -4,7 +4,7 @@ sm address_of_function {
    decl any_function_ptr fnptr;
 
    fnptr.*:
-      { fnptr } =>
+      { fnptr } ->
           {{
              # how to properly characterize?
              if is_simple_reference(fnptr):
diff --git a/checkers/buffer_overflow.sm b/checkers/buffer_overflow.sm
index e27a9e2..47c2470 100644
--- a/checkers/buffer_overflow.sm
+++ b/checkers/buffer_overflow.sm
@@ -61,12 +61,12 @@ sm buffer_overflow {
     | { ptr = g_try_malloc0(sz) }
     | { ptr = g_try_realloc(other_ptr, sz) }
       /* etc */
-      => {{
+      -> {{
     set_state('ptr', size=sz, strlen=None)
          }};
 
     | {sz = strlen(ptr) }
-      => {{
+      -> {{
           set_state('ptr',
                     size=get_size(state),
                     strlen=sz)
diff --git a/checkers/fd_checker.sm b/checkers/fd_checker.sm
index ac11d3d..cef881e 100644
--- a/checkers/fd_checker.sm
+++ b/checkers/fd_checker.sm
@@ -8,19 +8,19 @@ sm fd_checker {
    
    /* open() and creat return a new fd, or -1, setting errno */
    fd.all:
-       { fd = open() } => fd.unknown
-     | { fd = create() } => fd.unknown
+       { fd = open() } -> fd.unknown
+     | { fd = create() } -> fd.unknown
      ;
 
    fd.unknown:
-       { fd == -1 } => true=fd.error, false=fd.open
-     | { fd != -1 } => true=fd.open, false=fd.error
+       { fd == -1 } -> true=fd.error, false=fd.open
+     | { fd != -1 } -> true=fd.open, false=fd.error
      ;
 
    fd.open:
-       { close(fd) } => fd.maybeclosed;
+       { close(fd) } -> fd.maybeclosed;
 
    fd.maybeclosed:
-       { close(fd) } => {error('double close of %s' % fd)}
+       { close(fd) } -> {error('double close of %s' % fd)}
 
 }
diff --git a/checkers/fileptr.sm b/checkers/fileptr.sm
index ee8e50a..ae1a9e1 100644
--- a/checkers/fileptr.sm
+++ b/checkers/fileptr.sm
@@ -6,12 +6,12 @@ sm fileptr_checker {
    decl any_expr stream;
    
    fp.all:
-       { fp = fopen(path, mode) } => fp.unchecked
-     | { fp = fdopen(fd, mode) } => fp.unchecked
-     | { fp = freopen(path, mode, stream) } => fp.unchecked
+       { fp = fopen(path, mode) } -> fp.unchecked
+     | { fp = fdopen(fd, mode) } -> fp.unchecked
+     | { fp = freopen(path, mode, stream) } -> fp.unchecked
      ;
 
    fp.unchecked:
-       { fclose(fp) } => { error('use of possibly NULL %s' % fp) };
+       { fclose(fp) } -> { error('use of possibly NULL %s' % fp) };
 
 }
diff --git a/checkers/malloc_checker.sm b/checkers/malloc_checker.sm
index f206d9c..2bb7912 100644
--- a/checkers/malloc_checker.sm
+++ b/checkers/malloc_checker.sm
@@ -5,65 +5,65 @@ sm malloc_checker {
   stateful decl any_pointer ptr;
 
   ptr.*:
-    { ptr = malloc() } =>  ptr.unchecked;
+    { ptr = malloc() } ->  ptr.unchecked;
 
   ptr.*:
-    { ptr = 0 } =>  ptr.null;
+    { ptr = 0 } ->  ptr.null;
 
   ptr.*:
-      { ptr == 0 } => true=ptr.null, false=ptr.nonnull
-    | { ptr != 0 } => true=ptr.nonnull, false=ptr.null
+      { ptr == 0 } -> true=ptr.null, false=ptr.nonnull
+    | { ptr != 0 } -> true=ptr.nonnull, false=ptr.null
     ;
 
   ptr.unchecked:
     { *ptr }
-      => {{
+      -> {{
             error('dereference of possibly-NULL pointer %s' % ptr,
-                  # "CWE-690: Unchecked Return Value to NULL Pointer Dereference"
+                  // "CWE-690: Unchecked Return Value to NULL Pointer Dereference"
                   cwe='CWE-690')
          }}, ptr.nonnull;
 
   ptr.null:
     { *ptr }
-      => {{
+      -> {{
             error('dereference of NULL pointer %s' % ptr,
-                  # "CWE-690: Unchecked Return Value to NULL Pointer Dereference"
+                  // "CWE-690: Unchecked Return Value to NULL Pointer Dereference"
                   cwe='CWE-690')
          }}, ptr.stop;
 
   ptr.start, ptr.unchecked, ptr.null, ptr.nonnull:
-    { free(ptr) } => ptr.free;
+    { free(ptr) } -> ptr.free;
 
   ptr.free:
       { free(ptr) }
-          => {{
+          -> {{
                 error('double-free of %s' % ptr,
-                      # "CWE-415: Double Free"
+                      // "CWE-415: Double Free"
                       cwe='CWE-415')
              }}, ptr.stop
     | { ptr }
-          => {{
+          -> {{
                 error('use-after-free of %s' % ptr,
-                      # "CWE-416: Use After Free"
+                      // "CWE-416: Use After Free"
                       cwe='CWE-416')
              }}, ptr.stop
     ;
 
   ptr.unchecked:
-    $arg_must_not_be_null$
-      => {{
+    @arg_must_not_be_null
+      -> {{
             error('possibly-NULL pointer %s passed as argument %i to %s'
                   % (ptr, argnumber, function),
-                  # "CWE-690: Unchecked Return Value to NULL Pointer Dereference"
+                  // "CWE-690: Unchecked Return Value to NULL Pointer Dereference"
                   cwe='CWE-690')
           }}, ptr.nonnull;
 
   ptr.null:
-    $arg_must_not_be_null$
-      => {{
+    @arg_must_not_be_null
+      -> {{
             error('NULL pointer %s passed as argument %i to %s'
                   % (ptr, argnumber, function),
-                  # "CWE-690: Unchecked Return Value to NULL Pointer Dereference"
+                  // "CWE-690: Unchecked Return Value to NULL Pointer Dereference"
                   cwe='CWE-690')
          }}, ptr.stop;
 }
diff --git a/checkers/points_to_stack.sm b/checkers/points_to_stack.sm
index 6b52d91..63e0058 100644
--- a/checkers/points_to_stack.sm
+++ b/checkers/points_to_stack.sm
@@ -43,7 +43,7 @@ sm points_to_stack {
    }}
 
    ptr.*:
-      { ptr = &var } =>
+      { ptr = &var } ->
         {{
             if is_on_stack(var):
                 set_state('ptr.points_to_stack', var_pointed_to=var)
@@ -51,7 +51,7 @@ sm points_to_stack {
 
    ptr.points_to_stack:
        { return ptr }
-         => {{
+         -> {{
                 error('return of "%s" returns address within stack of local variable "%s"'
                       % (ptr, state.var_pointed_to),
                       # "CWE-562: Return of Stack Variable Address":
diff --git a/checkers/python.sm b/checkers/python.sm
index f065167..86b1b0a 100644
--- a/checkers/python.sm
+++ b/checkers/python.sm
@@ -27,7 +27,7 @@ sm python {
 
   ptr.all:
       { ptr = PyList_New(sz) }
-        => {{
+        -> {{
                 set_state('ptr.unchecked_newref',
                           ob_type='PyList_Type')
                 # How much can we link states?
@@ -41,16 +41,16 @@ sm python {
 
   ptr.unchecked_newref:
       /* FIXME: we'll want to inherit the kv pairs in the new states: */
-      { ptr == 0 } => true=ptr.null, false=ptr.nonnull_newref
-    | { ptr != 0 } => true=ptr.nonnull_rewref, false=ptr.null
-    | { *ptr } => {{ warning('use of possibly-NULL pointer %s' % ptr) }}, ptr.nonnull_newref
+      { ptr == 0 } -> true=ptr.null, false=ptr.nonnull_newref
+    | { ptr != 0 } -> true=ptr.nonnull_rewref, false=ptr.null
+    | { *ptr } -> {{ warning('use of possibly-NULL pointer %s' % ptr) }}, ptr.nonnull_newref
     ;
 
   ptr.null:
-    { *ptr } => {{ error('use of NULL pointer %s' % ptr) }}
+    { *ptr } -> {{ error('use of NULL pointer %s' % ptr) }}
 
   ptr.unchecked_newref, ptr.nonnull_newref:
-      $leaked$ => {{ error('leak of %s' % ptr) }};
+      $leaked$ -> {{ error('leak of %s' % ptr) }};
 
   /* how to handle Py_INCREF and Py_DECREF ? 
        Idea: track the ob_refcnt as an entirely separate checker, with
diff --git a/checkers/signal_handler.sm b/checkers/signal_handler.sm
index 1bb9e6c..8ef96de 100644
--- a/checkers/signal_handler.sm
+++ b/checkers/signal_handler.sm
@@ -16,7 +16,7 @@ sm find_signal_handlers {
 
    fnptr.*:
       { signal(signum, fnptr) }
-        => {{
+        -> {{
               raise foo
               # this isn't getting reached; I *think* it's because "handler" isn't in the smexprs
               # scope for the Context
@@ -49,7 +49,7 @@ sm async_unsafe {
 /* FIXME:
    within_signal_handler:
       {{ fprintf(arg, ...) }}
-      => {{
+      -> {{
             error('use of signal-unsafe function %s from within a signal-handler' % function,
                   other_ids=[Id('SIG30-C',
                                  url='https://www.securecoding.cert.org/confluence/display/seccode/SIG30-C.+Call+only+asynchronous-safe+functions+within+signal+handlers')])
diff --git a/checkers/sizeof_allocation.sm b/checkers/sizeof_allocation.sm
index 48e472f..ca63e1d 100644
--- a/checkers/sizeof_allocation.sm
+++ b/checkers/sizeof_allocation.sm
@@ -61,7 +61,7 @@ sm sizeof_allocation {
     | { ptr = g_try_malloc0(sz) }
     | { ptr = g_try_realloc(other_ptr, sz) }
       /* etc */
-      => {{
+      -> {{
             if is_known_int(sz):
                 check_size(ptr, sz)
          }};
@@ -76,14 +76,14 @@ sm sizeof_allocation {
     | { ptr = g_try_malloc_n(nmemb, sz) }
     | { ptr = g_try_malloc0_n(nmemb, sz) }
     | { ptr = g_try_realloc_n(other_pointer, nmemb, sz) }
-      => {{
+      -> {{
             if is_known_int(nmemb):
                 if is_known_int(sz):
                     check_size(ptr, int(nmemb) * int(sz))
          }};
 
   ptr.sized:
-     { other_ptr = ptr } =>
+     { other_ptr = ptr } ->
        {{
             check_size(other_ptr, allocated_size=state.size)
        }};
diff --git a/checkers/taint.sm b/checkers/taint.sm
index 03ac0c9..7b305ab 100644
--- a/checkers/taint.sm
+++ b/checkers/taint.sm
@@ -16,8 +16,8 @@ sm taint {
   /* Sources of tainted data: */
   x.*:
     /* reading data from files: */
-      {fread(x, size, nmemb, stream)} => x.tainted
-    | {read(fd, x, count)} => x.tainted
+      {fread(x, size, nmemb, stream)} -> x.tainted
+    | {read(fd, x, count)} -> x.tainted
     ;
 
   /*
@@ -25,44 +25,44 @@ sm taint {
     an upper-bound, or a lower-bound:
   */
   x.tainted:
-      check_ub => true=x.has_ub, false=x.has_lb
-    | check_lb => true=x.has_lb, false=x.has_ub
+      check_ub -> true=x.has_ub, false=x.has_lb
+    | check_lb -> true=x.has_lb, false=x.has_ub
     ;
 
   /*
     If a value has only one bound, check to see if it gains the other bound:
   */
    x.has_lb:
-      check_ub => true=x.ok
-    | check_lb => false=x.ok
+      check_ub -> true=x.ok
+    | check_lb -> false=x.ok
     ;
    x.has_ub:
-      check_ub => false=x.ok
-    | check_lb => true=x.ok
+      check_ub -> false=x.ok
+    | check_lb -> true=x.ok
     ;
 
   /* If a value is successfully compared against a specific value, it's OK */
   x.tainted, x.has_lb, x.has_ub:
-      check_eq => true=x.ok
-    | check_ne => false=x.ok
+      check_eq -> true=x.ok
+    | check_ne -> false=x.ok
     ;
 
   /* Detect when a tainted value is used in a dangerous way: */
   x.tainted:
-      { arr[x] } => {{
+      { arr[x] } -> {{
           error('use of tainted value %s in array lookup without bounds checking' % x,
                 cwe='CWE-129') # "Improper Validation of Array Index"
         }};
 
   x.has_lb:
-      { arr[x] } => {{
+      { arr[x] } -> {{
           error('use of tainted value %s in array lookup without upper-bound check' % x,
                 cwe='CWE-129') # "Improper Validation of Array Index"
         }};
 
   /* Unsigned types have an implicit lower bound: */
   x.has_ub:
-      { arr[x] } => {{
+      { arr[x] } -> {{
           if not x.type.unsigned:
              error('use of tainted value %s in array lookup without lower-bound check' % x,
                    cwe='CWE-129') # "Improper Validation of Array Index"
diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index dc0eba7..af28f9e 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1259,6 +1259,7 @@ OBJS = \
 	cgraphbuild.o \
 	cgraphunit.o \
 	cgraphclones.o \
+	checker-script.o \
 	combine.o \
 	combine-stack-adj.o \
 	compare-elim.o \
diff --git a/gcc/checker-script.cc b/gcc/checker-script.cc
new file mode 100644
index 0000000..2504c70
--- /dev/null
+++ b/gcc/checker-script.cc
@@ -0,0 +1,1378 @@
+/*
+  FIXME: compare with:
+  ~/coding/gcc-python/gcc-python/sm-more-checkers/sm/parser.py
+  ~/coding/gcc-python/gcc-python/sm-more-checkers/docs/sm.rst
+  Changes:
+    "=>" became "->"
+    "$id$" became "@id".  */
+
+/* FIXME.
+   Copyright (C) 2019 Free Software Foundation, Inc.
+
+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 "toplev.h"
+#include "cpplib.h"
+#include "diagnostic-core.h"
+#include "pretty-print.h"
+#include "selftest.h"
+
+namespace checker
+{
+
+class type
+{
+public:
+  type (const char *fixme) {}
+
+  void print (pretty_printer *pp) const;
+};
+
+class decl
+{
+public:
+  decl (const char *name, type *t, bool stateful);
+
+  void print (pretty_printer *pp) const;
+
+private:
+  char *m_name;
+  type *m_type;
+  bool m_stateful;
+};
+
+/* state_name:
+     id.id
+     | id
+     | id ".*"
+     | "*"  */
+
+class state_name
+{
+public:
+  state_name (const char *decl, const char *state, bool dot_star);
+  ~state_name ();
+
+  void print (pretty_printer *pp) const;
+
+private:
+  char *m_decl;
+  char *m_state;
+  bool m_dot_star;
+};
+
+class pattern
+{
+public:
+  virtual void print (pretty_printer *pp) const = 0;
+};
+
+class cpattern : public pattern
+{
+public:
+  void print (pretty_printer *pp) const FINAL OVERRIDE;
+
+private:
+  virtual void print_body (pretty_printer *pp) const;
+};
+
+class result_of_fncall : public cpattern
+{
+public:
+  result_of_fncall (const char *lhs, const char *fnname);
+
+private:
+  void print_body (pretty_printer *pp) const FINAL OVERRIDE;
+
+  char *m_lhs;
+  char *m_fnname;
+};
+
+class dereference : public cpattern
+{
+public:
+  dereference (const char *rhs);
+
+private:
+  void print_body (pretty_printer *pp) const FINAL OVERRIDE;
+
+  char *m_rhs;
+};
+
+class at_pattern : public pattern
+{
+public:
+  at_pattern (const char *id);
+  ~at_pattern ();
+
+  void print (pretty_printer *pp) const FINAL OVERRIDE;
+
+private:
+  char *m_id;
+};
+
+class use_named_pattern : public pattern
+{
+public:
+  use_named_pattern (const char *id);
+  ~use_named_pattern ();
+
+  void print (pretty_printer *pp) const FINAL OVERRIDE;
+
+private:
+  char *m_id;
+};
+
+class pattern_list
+{
+public:
+  void print (pretty_printer *pp) const;
+
+  void add_pattern (pattern *p);
+
+private:
+  auto_vec <pattern *> m_patterns;
+};
+
+class named_pattern_list
+{
+public:
+  named_pattern_list (const char *name, pattern_list *pl);
+
+  void print (pretty_printer *pp) const;
+
+private:
+  char *m_name;
+  pattern_list *m_pattern_list;
+};
+
+class outcome
+{
+public:
+  virtual void print (pretty_printer *pp) const = 0;
+};
+
+class script_outcome : public outcome
+{
+public:
+  void print (pretty_printer *pp) const FINAL OVERRIDE;
+};
+
+class state_outcome : public outcome
+{
+public:
+  state_outcome (state_name *sn);
+  void print (pretty_printer *pp) const FINAL OVERRIDE;
+
+private:
+  state_name *m_sn;
+};
+
+class boolean_outcome : public outcome
+{
+public:
+  boolean_outcome (outcome *wrapped, bool guard);
+  void print (pretty_printer *pp) const FINAL OVERRIDE;
+
+private:
+  outcome *m_wrapped;
+  bool m_guard;
+};
+
+class pattern_rule
+{
+public:
+  pattern_rule (pattern_list *pl) : m_pattern_list (pl) {}
+
+  void add_outcome (outcome *o);
+
+  void print (pretty_printer *pp) const;
+
+private:
+  pattern_list *m_pattern_list;
+  auto_vec <outcome *> m_outcomes;
+};
+
+class state_clause
+{
+public:
+  void print (pretty_printer *pp) const;
+
+  void add_state_name (state_name *sn);
+  void add_pattern_rule (pattern_rule *pr);
+
+private:
+  auto_vec <state_name *> m_sns;
+  auto_vec <pattern_rule *> m_prs;
+};
+
+class state_machine
+{
+public:
+  /* Parser methods.  */
+  state_machine (const char *id);
+  ~state_machine ();
+  void add_decl (const char *name, type *t, bool stateful);
+  void add_named_pattern_list (const char *name, pattern_list *p);
+  void add_state_clause (state_clause *sc);
+
+  void print (pretty_printer *pp) const;
+
+private:
+  char *m_id;
+  auto_vec <decl *> m_decls;
+  auto_vec <named_pattern_list *> m_named_pattern_lists;
+  auto_vec <state_clause *> m_scs;
+};
+
+class script
+{
+public:
+  script (const char *path);
+  ~script ();
+
+  void add_state_machine (state_machine *sm);
+
+  void dump (FILE *f) const;
+  void print (pretty_printer *pp) const;
+
+private:
+  char *m_path;
+  auto_vec <state_machine *> m_sms;
+};
+
+} // namespace checker
+
+using namespace checker;
+
+/* class checker::script.  */
+
+// FIXME
+
+script::script (const char *path)
+: m_path (xstrdup (path))
+{
+}
+
+script::~script ()
+{
+  free (m_path);
+
+  unsigned i;
+  state_machine *sm;
+  FOR_EACH_VEC_ELT (m_sms, i, sm)
+    delete sm;
+}
+
+void
+script::add_state_machine (state_machine *sm)
+{
+  m_sms.safe_push (sm);
+}
+
+void
+script::dump (FILE *outf) const
+{
+  pretty_printer pp;
+  pp_buffer (&pp)->stream = outf;
+  print (&pp);
+  pp_flush (&pp);
+}
+
+void
+script::print (pretty_printer *pp) const
+{
+  pp_printf (pp, "/* path: %s */\n", m_path);
+
+  unsigned i;
+  state_machine *sm;
+  FOR_EACH_VEC_ELT (m_sms, i, sm)
+    sm->print (pp);
+}
+
+/* class checker::type.  */
+
+void
+type::print (pretty_printer *pp) const
+{
+  // FIXME
+}
+
+/* class checker::decl.  */
+
+decl::decl (const char *name, type *t, bool stateful)
+: m_name (xstrdup (name)), m_type (t), m_stateful (stateful)
+{
+}
+
+void
+decl::print (pretty_printer *pp) const
+{
+  pp_string (pp, "  ");
+  if (m_stateful)
+    pp_printf (pp, "stateful ");
+  pp_printf (pp, "decl ");
+  m_type->print (pp);
+  pp_printf (pp, "%s;\n", m_name);
+}
+
+/* class checker::state_name.  */
+
+state_name::state_name (const char *decl, const char *state, bool dot_star)
+: m_decl (decl ? xstrdup (decl) : NULL),
+  m_state (state ? xstrdup (state) : NULL),
+  m_dot_star (dot_star)
+{
+}
+
+state_name::~state_name ()
+{
+  free (m_decl);
+  free (m_state);
+}
+
+void
+state_name::print (pretty_printer *pp) const
+{
+  if (m_decl)
+    {
+      pp_printf (pp, "%s", m_decl);
+      if (m_state)
+	pp_printf (pp, ".%s", m_state);
+      else if (m_dot_star)
+	pp_printf (pp, ".*");
+    }
+  else
+    pp_printf (pp, "*");
+}
+
+/* class checker::pattern.  */
+
+/* class checker::cpattern.  */
+
+void
+cpattern::print (pretty_printer *pp) const
+{
+  pp_printf (pp, "{{ ");
+  print_body (pp);
+  pp_printf (pp, " }}");
+}
+
+void
+cpattern::print_body (pretty_printer *pp) const
+{
+  pp_printf (pp, "FIXME");
+}
+
+/* class checker::result_of_fncall.  */
+
+result_of_fncall::result_of_fncall (const char *lhs, const char *fnname)
+: m_lhs (xstrdup (lhs)), m_fnname (xstrdup (fnname))
+{
+}
+
+void
+result_of_fncall::print_body (pretty_printer *pp) const
+{
+  pp_printf (pp, "%s = %s()", m_lhs, m_fnname);
+}
+
+/* class checker::dereference.  */
+
+dereference::dereference (const char *rhs)
+: m_rhs (xstrdup (rhs))
+{
+}
+
+void
+dereference::print_body (pretty_printer *pp) const
+{
+  pp_printf (pp, "*%s", m_rhs);
+}
+
+/* class checker::at_pattern.  */
+
+at_pattern::at_pattern (const char *id)
+: m_id (xstrdup (id))
+{
+}
+
+at_pattern::~at_pattern ()
+{
+  free (m_id);
+}
+
+void
+at_pattern::print (pretty_printer *pp) const
+{
+  pp_printf (pp, "@%s", m_id);
+}
+
+/* class checker::use_named_pattern.  */
+use_named_pattern::use_named_pattern (const char *id)
+: m_id (xstrdup (id))
+{
+}
+
+use_named_pattern::~use_named_pattern ()
+{
+  free (m_id);
+}
+
+void
+use_named_pattern::print (pretty_printer *pp) const
+{
+  pp_printf (pp, "%s", m_id);
+}
+
+/* class checker::pattern_list.  */
+void
+pattern_list::print (pretty_printer *pp) const
+{
+  unsigned i;
+  pattern *p;
+  FOR_EACH_VEC_ELT (m_patterns, i, p)
+    {
+      if (i > 0)
+	pp_string (pp, "| ");
+      p->print (pp);
+    }
+}
+
+void
+pattern_list::add_pattern (pattern *p)
+{
+  m_patterns.safe_push (p);
+}
+
+/* class checker::named_pattern_list.  */
+
+named_pattern_list::named_pattern_list (const char *name, pattern_list *pl)
+: m_name (xstrdup (name)),
+  m_pattern_list (pl)
+{
+}
+
+void
+named_pattern_list::print (pretty_printer *pp) const
+{
+  pp_printf (pp, "  pat %s ", m_name);
+  m_pattern_list->print (pp);
+  pp_string (pp, ";\n");
+}
+
+/* class checker::outcome.  */
+
+/* class checker::script_outcome.  */
+
+void
+script_outcome::print (pretty_printer *pp) const
+{
+  pp_string (pp, "{{ FIXME }}");
+}
+
+/* class checker::state_outcome.  */
+
+state_outcome::state_outcome (state_name *sn)
+: m_sn (sn)
+{
+}
+
+void
+state_outcome::print (pretty_printer *pp) const
+{
+  m_sn->print (pp);
+}
+
+/* class checker::boolean_outcome.  */
+
+boolean_outcome::boolean_outcome (outcome *wrapped, bool guard)
+: m_wrapped (wrapped), m_guard (guard)
+{
+}
+
+void
+boolean_outcome::print (pretty_printer *pp) const
+{
+  pp_printf (pp, "%s=", m_guard ? "true" : "false");
+  m_wrapped->print (pp);
+}
+
+/* class checker::pattern_rule.  */
+
+void
+pattern_rule::add_outcome (outcome *o)
+{
+  m_outcomes.safe_push (o);
+}
+
+void
+pattern_rule::print (pretty_printer *pp) const
+{
+  m_pattern_list->print (pp);
+  pp_printf (pp, " -> ");
+
+  unsigned i;
+  outcome *o;
+  FOR_EACH_VEC_ELT (m_outcomes, i, o)
+    {
+      if (i > 0)
+	pp_string (pp, ", ");
+      o->print (pp);
+    }
+}
+
+/* class checker::state_clause.  */
+
+/* statelist ":" patternrulelist ";".  */
+
+void
+state_clause::print (pretty_printer *pp) const
+{
+  pp_string (pp, "  ");
+  unsigned i;
+  state_name *n;
+  FOR_EACH_VEC_ELT (m_sns, i, n)
+    {
+      if (i > 0)
+	pp_string (pp, ", ");
+      n->print (pp);
+    }
+  pp_string (pp, ":\n");
+
+  pattern_rule *pr;
+  FOR_EACH_VEC_ELT (m_prs, i, pr)
+    {
+      if (i > 0)
+	pp_string (pp, "    | ");
+      else
+	pp_string (pp, "      ");
+      pr->print (pp);
+      pp_string (pp, "\n");
+    }
+
+  pp_string (pp, "    ;\n");
+}
+
+void
+state_clause::add_state_name (state_name *sn)
+{
+  m_sns.safe_push (sn);
+}
+
+void
+state_clause::add_pattern_rule (pattern_rule *pr)
+{
+  m_prs.safe_push (pr);
+}
+
+/* class checker::state_machine.  */
+
+// FIXME
+
+state_machine::state_machine (const char *id)
+: m_id (xstrdup (id))
+{
+}
+
+// FIXME
+
+state_machine::~state_machine ()
+{
+  free (m_id);
+}
+
+void
+state_machine::add_decl (const char *name, type *t, bool stateful)
+{
+  decl *d = new decl (name, t, stateful);
+  m_decls.safe_push (d);
+}
+
+void
+state_machine::add_named_pattern_list (const char *name, pattern_list *pl)
+{
+  m_named_pattern_lists.safe_push (new named_pattern_list (name, pl));
+}
+
+void
+state_machine::add_state_clause (state_clause *sc)
+{
+  m_scs.safe_push (sc);
+}
+
+void
+state_machine::print (pretty_printer *pp) const
+{
+  pp_printf (pp, "sm %s {\n", m_id);
+
+  unsigned i;
+
+  decl *d;
+  FOR_EACH_VEC_ELT (m_decls, i, d)
+    d->print (pp);
+
+  named_pattern_list *npl;
+  FOR_EACH_VEC_ELT (m_named_pattern_lists, i, npl)
+    npl->print (pp);
+
+  state_clause *sc;
+  FOR_EACH_VEC_ELT (m_scs, i, sc)
+    sc->print (pp);
+
+  pp_printf (pp, "}\n");
+}
+
+static void
+#if GCC_VERSION >= 4001
+  ATTRIBUTE_GCC_DIAG (2,3)
+#endif
+fatal_at (const cpp_token *tk, const char *msg, ...)
+{
+  va_list ap;
+  va_start (ap, msg);
+  emit_diagnostic_valist (DK_FATAL, tk->src_loc, 0, msg, &ap);
+  va_end (ap);
+}
+
+namespace checker
+{
+
+class parser
+{
+public:
+  parser ();
+  ~parser ();
+
+  script *parse (const char *path);
+
+private:
+  script *parse_toplevel (const char *path);
+  state_machine *parse_sm (script *s);
+  void parse_sm_clauses (state_machine *sm);
+  void parse_sm_clause (state_machine *sm);
+  void parse_decl (state_machine *sm, bool stateful);
+  void parse_pat (state_machine *sm);
+  void parse_statelist_smclause (state_machine *sm);
+  void parse_statelist (state_machine *sm, state_clause *sc);
+  state_name *parse_statename (state_machine *sm);
+  void parse_patternrulelist (state_machine *sm, state_clause *sc);
+  pattern_rule *parse_patternrule (state_machine *sm);
+  pattern_list *parse_pattern_list (state_machine *sm);
+  pattern *parse_pattern (state_machine *sm);
+  pattern *parse_cpattern (state_machine *sm);
+  void parse_outcomes (state_machine *sm, pattern_rule *pr);
+  outcome *parse_outcome (state_machine *sm);
+  outcome *parse_boolean_outcome (state_machine *sm, bool guard);
+
+  const cpp_token *next ();
+  const cpp_token *peek (unsigned = 1);
+  const cpp_token *peek_ident (const char * = NULL, unsigned = 1);
+  const cpp_token *expect (enum cpp_ttype);
+  const char *get_ident ();
+  const cpp_token *eat_ident (const char *);
+
+  cpp_reader *r;
+};
+
+} // namespace checker
+
+/* class checker::parser.  */
+
+/* FIXME.  */
+
+parser::parser ()
+{
+  r = cpp_create_reader (CLK_GNUCXX, /*c_dialect_cxx () ? CLK_GNUCXX: CLK_GNUC89 */
+			 ident_hash, line_table);
+
+  cpp_options *cpp_opts = cpp_get_options (r);
+  cpp_opts->warn_dollars = 0;
+
+#if 0
+  // FIXME: wire up to c-common and reuse c_cpp_diagnostic
+  cpp_callbacks *cb = cpp_get_callbacks (r);
+  cb->diagnostic = c_cpp_diagnostic;
+#endif
+}
+
+/* FIXME.  */
+
+parser::~parser ()
+{
+  // TODO: m_lexer
+}
+
+/* FIXME.  */
+
+script *
+parser::parse (const char *path)
+{
+  /* FIXME: should we reuse c-lex.c? */
+
+  if (!cpp_read_main_file (r, path))
+    return NULL;
+
+  return parse_toplevel (path);
+}
+
+/* One or more "sm":
+     checker:
+       sm
+       | sm checker  */
+
+script *
+parser::parse_toplevel (const char *path)
+{
+  script *s = new script (path);
+  const cpp_token *token = next ();
+  while (token->type != CPP_EOF)
+    {
+      _cpp_backup_tokens (r, 1);
+      state_machine *sm = parse_sm (s);
+      if (sm)
+	s->add_state_machine (sm);
+      token = next ();
+    }
+  return s;
+}
+
+/* Parse an "sm".
+
+   sm:
+     "sm" id "{" smclauses "}"  */
+
+state_machine *
+parser::parse_sm (script *s)
+{
+  if (!peek_ident ("sm"))
+    {
+      fatal_error (peek ()->src_loc, "expected %qs", "sm");
+      return NULL;
+    }
+
+  eat_ident ("sm");
+  const char *id = get_ident ();
+  expect (CPP_OPEN_BRACE);
+
+  state_machine *sm = new state_machine (id);
+
+  parse_sm_clauses (sm);
+
+  expect (CPP_CLOSE_BRACE);
+
+  return sm;
+}
+
+/* Parse smclauses: one or more smclause.
+
+   smclauses:
+     smclause
+     | smclauses smclause  */
+
+void
+parser::parse_sm_clauses (state_machine *sm)
+{
+  const cpp_token *token = peek ();
+  while (token->type != CPP_CLOSE_BRACE)
+    {
+      parse_sm_clause (sm);
+      token = peek ();
+    }
+}
+
+/* Parse smclause.
+
+   smclause:
+     "stateful"? "decl" declkind id ";"
+     | "pat" id pattern ";"
+     | statelist ":" patternrulelist ";"  */
+
+void
+parser::parse_sm_clause (state_machine *sm)
+{
+  const cpp_token *id = peek_ident ();
+  if (!id)
+    fatal_at (peek (),
+	      "expected %<stateful%>, %<decl%>, %<pat%>, or identifier");
+  //inform (input_location, "got id: %qs", id);
+
+  /* Handle decls.  */
+  if (peek_ident ("stateful"))
+    {
+      eat_ident ("stateful");
+      parse_decl (sm, true);
+      return;
+    }
+  if (peek_ident ("decl"))
+    {
+      parse_decl (sm, false);
+      return;
+    }
+  if (peek_ident ("pat"))
+    {
+      parse_pat (sm);
+      return;
+    }
+
+  parse_statelist_smclause (sm);
+}
+
+// FIXME
+/* Any "stateful" has already been consumed.  */
+
+void
+parser::parse_decl (state_machine *sm, bool stateful)
+{
+  eat_ident ("decl");
+
+  // TODO: parse declkind;
+  const char *id_type = get_ident ();
+  type *t = new type (id_type);
+
+  const char *id = get_ident ();
+
+  expect (CPP_SEMICOLON);
+
+  sm->add_decl (id, t, stateful);
+}
+
+void
+parser::parse_pat (state_machine *sm)
+{
+  eat_ident ("pat");
+
+  const char *id = get_ident ();
+
+  pattern_list *pl = parse_pattern_list (sm);
+
+  expect (CPP_SEMICOLON);
+
+  sm->add_named_pattern_list (id, pl);
+}
+
+// FIXME
+/*
+    smclause:
+      statelist ":" patternrulelist ";"  */
+
+void
+parser::parse_statelist_smclause (state_machine *sm)
+{
+  state_clause *sc = new state_clause ();
+
+  parse_statelist (sm, sc);
+  expect (CPP_COLON);
+  parse_patternrulelist (sm, sc);
+  expect (CPP_SEMICOLON);
+
+  sm->add_state_clause (sc);
+}
+
+/* Parse one or more comma-separated statename.
+
+   statelist:
+     statename
+     | statename "," statelist  */
+
+void
+parser::parse_statelist (state_machine *sm, state_clause *sc)
+{
+  while (1)
+    {
+      state_name *sn = parse_statename (sm);
+      if (sn)
+	sc->add_state_name (sn);
+      if (peek ()->type != CPP_COMMA)
+	break;
+      expect (CPP_COMMA);
+    }
+}
+
+/* FIXME:
+   statename:
+     id.id
+     | id
+     | id ".*"
+     | "*"  */
+
+state_name *
+parser::parse_statename (state_machine *sm)
+{
+  const char *id = get_ident ();
+
+  // TODO: look up the decl
+
+  const cpp_token *tok = peek ();
+  if (tok->type == CPP_DOT_STAR)
+    {
+      next ();
+      return new state_name (id, NULL, true);
+    }
+  if (tok->type == CPP_DOT)
+    {
+      next ();
+      const char *id2 = get_ident ();
+      inform (input_location, "got id2: %qs", id2);
+      return new state_name (id, id2, false);
+    }
+
+  // FIXME
+  return NULL;
+}
+
+/* Parse one or more "|"-separated patternrule.
+
+   patternrulelist:
+     patternrule
+     | patternrule "|" patternrulelist  */
+
+void
+parser::parse_patternrulelist (state_machine *sm, state_clause *sc)
+{
+  while (1)
+    {
+      pattern_rule *pr = parse_patternrule (sm);
+      if (pr)
+	sc->add_pattern_rule (pr);
+      if (peek ()->type != CPP_OR)
+	break;
+      expect (CPP_OR);
+    }
+}
+
+/* FIXME.
+   patternrule:
+     pattern-list "->" outcomes  */
+
+pattern_rule *
+parser::parse_patternrule (state_machine *sm)
+{
+  pattern_list *pl = parse_pattern_list (sm);
+  pattern_rule *pr = new pattern_rule (pl);
+  expect (CPP_DEREF);
+  parse_outcomes (sm, pr);
+  return pr;
+}
+
+/* pattern-list:
+      pattern
+      pattern-list "|" pattern  */
+
+pattern_list *
+parser::parse_pattern_list (state_machine *sm)
+{
+  pattern_list *pl = new pattern_list ();
+
+  while (1)
+    {
+      pattern *p = parse_pattern (sm);
+      if (p)
+	pl->add_pattern (p);
+      if (peek ()->type != CPP_OR)
+	break;
+      expect (CPP_OR);
+    }
+
+  return pl;
+}
+
+/* FIXME:
+    pattern:
+      "{" cpattern "}"
+      | id
+      | "@" id  */
+
+pattern *
+parser::parse_pattern (state_machine *sm)
+{
+  const cpp_token *tok = peek ();
+  if (tok->type == CPP_OPEN_BRACE)
+    {
+      expect (CPP_OPEN_BRACE);
+      pattern *p = parse_cpattern (sm);
+      expect (CPP_CLOSE_BRACE);
+      return p;
+    }
+  if (tok->type == CPP_ATSIGN)
+    {
+      expect (CPP_ATSIGN);
+      const char *id = get_ident ();
+      return new at_pattern (id);
+    }
+  const char *id = get_ident ();
+  return new use_named_pattern (id);
+}
+
+/* FIXME.
+    cpattern
+    : assignment-cpattern
+    | addressof-cpattern
+    | return-cpattern
+    | result-of-fncall-cpattern
+    | comparison-cpattern
+
+   assignment-cpattern:
+     id "=" literal
+     | id "=" literal
+     | id "=" id
+   e.g. "q = 0"
+
+   addressof-cpattern:
+     id "=" "&" id
+   e.g. "&var"
+
+   return-cpattern:
+     "return" id
+     | "return" literal
+     | "return"
+   e.g. "return var"
+
+   result-of-fncall-cpattern:
+     id "=" id "(" fncall_args ")"
+   e.g. "ptr = malloc()".
+
+   dereference-cpattern:
+     "*" id
+   e.g. "*ptr"
+
+   comparison-cpattern:
+     id comparison literal
+     id comparison id
+   e.g. "ptr == 0".  */
+
+/*
+def p_fncall_arg(p):
+    '''
+    fncall_arg : ID
+               | LITERAL_STRING
+               | LITERAL_NUMBER
+    '''
+    p[0] = p[1]
+
+def p_nonempty_fncall_args(p):
+    '''
+    nonempty_fncall_args : fncall_arg
+                         | fncall_args COMMA fncall_arg
+    '''
+    if len(p) == 2:
+        p[0] = [p[1]]
+    else:
+        p[0] = p[1] + [p[3]]
+
+def p_fncall_args_from_nonempty(p):
+    '''
+    fncall_args : nonempty_fncall_args
+    '''
+    p[0] = p[1]
+
+def p_fncall_args_from_empty(p):
+    '''
+    fncall_args : empty
+    '''
+    p[0] = []
+
+def p_cpattern_arg_of_fn_call(p):
+    'cpattern : ID LPAREN fncall_args RPAREN'
+    # e.g. "free(ptr)"
+    p[0] = ArgsOfFnCall(fnname=p[1], args=p[3])
+
+def p_cpattern_arraylookup(p):
+    'cpattern : ID LSQUARE ID RSQUARE'
+    # e.g. "arr[x]"
+    p[0] = ArrayLookup(array=p[1], index=p[3])
+
+def p_cpattern_usage(p):
+    'cpattern : ID'
+*/
+
+pattern *
+parser::parse_cpattern (state_machine *sm)
+{
+  const cpp_token *tok1 = peek ();
+  const cpp_token *tok2 = peek (2);
+  if (tok1->type == CPP_NAME && tok2 && tok2->type == CPP_EQ)
+    {
+      const char *lhs = get_ident ();
+      expect (CPP_EQ);
+      /* id "=" ...  */
+      const cpp_token *rhs = peek ();
+      inform (rhs->src_loc, "FIXME");
+      if (rhs->type == CPP_NAME)
+	{
+	  const char *rhs_name = get_ident ();
+	  if (peek ()->type == CPP_OPEN_PAREN)
+	    {
+	      /* result-of-fncall-cpattern.  */
+
+	      /* FIXME: for now, consume until close paren.  */
+	      while (1)
+		{
+		  const cpp_token *tok = peek ();
+		  if (tok->type == CPP_CLOSE_PAREN || tok->type == CPP_EOF)
+		    break;
+		  next ();
+		}
+	      next ();
+	      return new result_of_fncall (lhs, rhs_name);
+	    }
+	  else
+	    inform (rhs->src_loc, "assignment");
+	}
+    }
+  if (tok1->type == CPP_MULT)
+    {
+      /* dereference-cpattern.  */
+      expect (CPP_MULT);
+      const char *rhs = get_ident ();
+      return new dereference (rhs);
+    }
+
+  /* FIXME: for now, consume until close brace.  */
+  while (1)
+    {
+      const cpp_token *tok = peek ();
+      if (tok->type == CPP_CLOSE_BRACE || tok->type == CPP_EOF)
+	break;
+      next ();
+    }
+
+  // FIXME:
+  return new cpattern ();
+}
+
+/* FIXME.
+   outcomes:
+     outcome
+     | outcome "," outcomes */
+
+void
+parser::parse_outcomes (state_machine *sm, pattern_rule *pr)
+{
+  while (1)
+    {
+      outcome *o = parse_outcome (sm);
+      if (o)
+	pr->add_outcome (o);
+      if (peek ()->type != CPP_COMMA)
+	break;
+      expect (CPP_COMMA);
+    }
+}
+
+/* FIXME.
+
+   outcome:
+     statename     e.g. "ptr.unknown"
+     | "true" "=" outcome    e.g. "true=ptr.null"
+     | "false" "=" outcome
+     | PYTHON  FIXME
+       e.g. "{ error('use of possibly-NULL pointer %s', ptr) }"  */
+
+outcome *
+parser::parse_outcome (state_machine *sm)
+{
+  const cpp_token *tok = peek_ident ();
+
+  if (peek_ident ("true"))
+    return parse_boolean_outcome (sm, true);
+  else if (peek_ident ("false"))
+    return parse_boolean_outcome (sm, false);
+  else if (peek ()->type == CPP_OPEN_BRACE)
+    {
+      expect (CPP_OPEN_BRACE);
+      expect (CPP_OPEN_BRACE);
+
+      /* FIXME: for now, consume until close brace.  */
+      while (1)
+	{
+	  const cpp_token *tok = peek ();
+	  if (tok->type == CPP_CLOSE_BRACE || tok->type == CPP_EOF)
+	    break;
+	  next ();
+	}
+
+      expect (CPP_CLOSE_BRACE);
+      expect (CPP_CLOSE_BRACE);
+      return new script_outcome ();
+    }
+
+  // FIXME:
+  state_name *sn = parse_statename (sm);
+  return new state_outcome (sn);
+}
+
+/* Parse a guarded outcome:
+
+   outcome:
+     "true" "=" outcome
+     | "false" "=" outcome  */
+
+outcome *
+parser::parse_boolean_outcome (state_machine *sm, bool guard)
+{
+  next ();
+  expect (CPP_EQ);
+  return new boolean_outcome (parse_outcome (sm), guard);
+}
+
+/* Lexing helpers.  */
+
+/* Read the next non-whitespace token from R.  */
+
+const cpp_token *
+parser::next ()
+{
+  const cpp_token *token;
+  do
+    {
+      token = cpp_get_token (r);
+    }
+  while (token->type == CPP_PADDING);
+  return token;
+}
+
+/* Peek at the next non-whitespace token from R.  */
+
+const cpp_token *
+parser::peek (unsigned num)
+{
+  const cpp_token *token;
+  unsigned i = 0;
+  do
+    {
+      token = cpp_peek_token (r, i++);
+    }
+  while (token->type == CPP_PADDING
+	 || (--num > 0));
+  /* If we peek at EOF this is a fatal error as it leaves the
+     cpp_reader in unusable state.  Assume we really wanted a
+     token and thus this EOF is unexpected.  */
+  if (token->type == CPP_EOF)
+    ; // fatal_at (token, "unexpected end of file");
+  return token;
+}
+
+/* Peek at the next identifier token (or return NULL if the next
+   token is not an identifier or equal to ID if supplied).  */
+
+const cpp_token *
+parser::peek_ident (const char *id, unsigned num)
+{
+  const cpp_token *token = peek (num);
+  if (token->type != CPP_NAME)
+    return 0;
+
+  if (id == 0)
+    return token;
+
+  const char *t = (const char *) CPP_HASHNODE (token->val.node.node)->ident.str;
+  if (strcmp (id, t) == 0)
+    return token;
+
+  return 0;
+}
+
+/* Read the next token from R and assert it is of type TK.  */
+
+const cpp_token *
+parser::expect (enum cpp_ttype tk)
+{
+  const cpp_token *token = next ();
+  if (token->type != tk)
+    fatal_at (token, "expected %s, got %s",
+	      cpp_type2name (tk, 0), cpp_type2name (token->type, 0));
+
+  return token;
+}
+
+/* Read the next token from R and assert it is of type CPP_NAME and
+   return its value.  */
+
+const char *
+parser::get_ident ()
+{
+  const cpp_token *token = expect (CPP_NAME);
+  return (const char *)CPP_HASHNODE (token->val.node.node)->ident.str;
+}
+
+/* Eat an identifier token with value S from R.  */
+
+const cpp_token *
+parser::eat_ident (const char *s)
+{
+  const cpp_token *token = peek ();
+  const char *t = get_ident ();
+  if (strcmp (s, t) != 0)
+    fatal_at (token, "expected '%s' got '%s'\n", s, t);
+  return token;
+}
+
+static script *
+parse_script (const char *path)
+{
+  parser p;
+  return p.parse (path);
+}
+
+void
+debug (script *s)
+{
+  s->dump (stderr);
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Selftests for checker scripts.  */
+
+/* FIXME.  */
+
+static void
+test_parsing_malloc_checker ()
+{
+  // FIXME:
+  script *s = parse_script ("../../src/checkers/malloc_checker.sm");
+  debug (s);
+}
+
+/* FIXME.  */
+
+static void
+test_parsing_taint ()
+{
+  // FIXME:
+  script *s = parse_script ("../../src/checkers/taint.sm");
+  debug (s);
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+checker_script_cc_tests ()
+{
+  test_parsing_malloc_checker ();
+  test_parsing_taint ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
diff --git a/gcc/selftest-run-tests.c b/gcc/selftest-run-tests.c
index c2e0ee2..38d5098 100644
--- a/gcc/selftest-run-tests.c
+++ b/gcc/selftest-run-tests.c
@@ -108,6 +108,9 @@ selftest::run_tests ()
   predict_c_tests ();
   simplify_rtx_c_tests ();
 
+  /* Checker selftests.  */
+  checker_script_cc_tests ();
+
   /* Run any lang-specific selftests.  */
   lang_hooks.run_lang_selftests ();
 
diff --git a/gcc/selftest.h b/gcc/selftest.h
index 3e00e75..600b2be 100644
--- a/gcc/selftest.h
+++ b/gcc/selftest.h
@@ -253,6 +253,10 @@ extern void vec_perm_indices_c_tests ();
 extern void wide_int_cc_tests ();
 extern void opt_proposer_c_tests ();
 
+/* Declarations for specific families of tests (by source file), in
+   alphabetical order, for the checker plugin.  */
+extern void checker_script_cc_tests ();
+
 extern int num_passes;
 
 } /* end of namespace selftest.  */
-- 
1.8.5.3

