This patch adds a new function attribute to GCC for marking that an
argument is expected to be a null-terminated string.
For example, consider:

  void test_a (const char *p)
    __attribute__((null_terminated_string_arg (1)));

which would indicate to humans and compilers that argument 1 of "test_a"
is expected to be a null-terminated string, with the idea:

- we should complain if it's not valid to read from *p up to the first
  '\0' character in the buffer

- we should complain if *p is not terminated, or if it's uninitialized
  before the first '\0' character

This is independent of the nonnull-ness of the pointer: if you also want
to express that the argument must be non-null, we already have
__attribute__((nonnull (N))), so the user can write e.g.:

  void test_b (const char *p)
    __attribute__((null_terminated_string_arg (1))
    __attribute__((nonnull (1)));

which can also be spelled as:

  void test_b (const char *p)
     __attribute__((null_terminated_string_arg (1),
                    nonnull (1)));

For a function similar to strncpy, we can use the "access" attribute to
express a maximum size of the read:

  void test_c (const char *p, size_t sz)
     __attribute__((null_terminated_string_arg (1),
                    nonnull (1),
                    access (read_only, 1, 2)));

The patch implements:
(a) C/C++ frontends: recognition of this attribute
(b) analyzer: usage of this attribute

The name is rather long; a shorter name might be "c_string_arg".

Does anything like this already exist in GCC, or in any other
compilers or analysis tools?

Thoughts?

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.

gcc/analyzer/ChangeLog:
        * region-model.cc
        (region_model::check_external_function_for_access_attr): Split
        out, replacing with...
        (region_model::check_function_attr_access): ...this new function
        and...
        (region_model::check_function_attrs): ...this new function.
        (region_model::check_one_function_attr_null_terminated_string_arg):
        New.
        (region_model::check_function_attr_null_terminated_string_arg):
        New.
        (region_model::handle_unrecognized_call): Update for renaming of
        check_external_function_for_access_attr to check_function_attrs.
        (region_model::check_for_null_terminated_string_arg): Add return
        value to one overload.  Make both overloads const.
        * region-model.h: Include "stringpool.h" and "attribs.h".
        (region_model::check_for_null_terminated_string_arg): Add return
        value to one overload.  Make both overloads const.
        (region_model::check_external_function_for_access_attr): Delete
        decl.
        (region_model::check_function_attr_access): New decl.
        (region_model::check_function_attr_null_terminated_string_arg):
        New decl.
        (region_model::check_one_function_attr_null_terminated_string_arg):
        New decl.
        (region_model::check_function_attrs): New decl.

gcc/c-family/ChangeLog:
        * c-attribs.cc (c_common_attribute_table): Add
        "null_terminated_string_arg".
        (handle_null_terminated_string_arg_attribute): New.

gcc/ChangeLog:
        * doc/extend.texi (Common Function Attributes): Add
        null_terminated_string_arg.

gcc/testsuite/ChangeLog:
        * 
c-c++-common/analyzer/attr-null_terminated_string_arg-access-read_write.c:
        New test.
        * 
c-c++-common/analyzer/attr-null_terminated_string_arg-access-without-size.c:
        New test.
        * c-c++-common/analyzer/attr-null_terminated_string_arg-multiple.c:
        New test.
        * c-c++-common/analyzer/attr-null_terminated_string_arg-nonnull-2.c:
        New test.
        * c-c++-common/analyzer/attr-null_terminated_string_arg-nonnull-sized.c:
        New test.
        * c-c++-common/analyzer/attr-null_terminated_string_arg-nonnull.c:
        New test.
        * 
c-c++-common/analyzer/attr-null_terminated_string_arg-nullable-sized.c:
        New test.
        * c-c++-common/analyzer/attr-null_terminated_string_arg-nullable.c:
        New test.
        * c-c++-common/attr-null_terminated_string_arg.c: New test.

Signed-off-by: David Malcolm <dmalc...@redhat.com>
---
 gcc/analyzer/region-model.cc                  | 180 +++++++++++++++---
 gcc/analyzer/region-model.h                   |  27 ++-
 gcc/c-family/c-attribs.cc                     |  17 ++
 gcc/doc/extend.texi                           |  57 ++++++
 ..._terminated_string_arg-access-read_write.c |  15 ++
 ...erminated_string_arg-access-without-size.c |  54 ++++++
 ...attr-null_terminated_string_arg-multiple.c |  52 +++++
 ...ttr-null_terminated_string_arg-nonnull-2.c |  33 ++++
 ...null_terminated_string_arg-nonnull-sized.c |  69 +++++++
 .../attr-null_terminated_string_arg-nonnull.c |  34 ++++
 ...ull_terminated_string_arg-nullable-sized.c |  69 +++++++
 ...attr-null_terminated_string_arg-nullable.c |  34 ++++
 .../attr-null_terminated_string_arg.c         |  16 ++
 13 files changed, 629 insertions(+), 28 deletions(-)
 create mode 100644 
gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-access-read_write.c
 create mode 100644 
gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-access-without-size.c
 create mode 100644 
gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-multiple.c
 create mode 100644 
gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nonnull-2.c
 create mode 100644 
gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nonnull-sized.c
 create mode 100644 
gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nonnull.c
 create mode 100644 
gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nullable-sized.c
 create mode 100644 
gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nullable.c
 create mode 100644 gcc/testsuite/c-c++-common/attr-null_terminated_string_arg.c

diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index c4e68661ef1e..fb84f1f5b009 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -1781,26 +1781,17 @@ private:
    attribute.  */
 
 void
-region_model::
-check_external_function_for_access_attr (const gcall *call,
-                                        tree callee_fndecl,
-                                        region_model_context *ctxt) const
+region_model::check_function_attr_access (const gcall *call,
+                                         tree callee_fndecl,
+                                         region_model_context *ctxt,
+                                         rdwr_map &rdwr_idx) const
 {
   gcc_assert (call);
   gcc_assert (callee_fndecl);
   gcc_assert (ctxt);
 
   tree fntype = TREE_TYPE (callee_fndecl);
-  if (!fntype)
-    return;
-
-  if (!TYPE_ATTRIBUTES (fntype))
-    return;
-
-  /* Initialize a map of attribute access specifications for arguments
-     to the function call.  */
-  rdwr_map rdwr_idx;
-  init_attr_rdwr_indices (&rdwr_idx, TYPE_ATTRIBUTES (fntype));
+  gcc_assert (fntype);
 
   unsigned argno = 0;
 
@@ -1854,6 +1845,151 @@ check_external_function_for_access_attr (const gcall 
*call,
     }
 }
 
+/* Subroutine of region_model::check_function_attr_null_terminated_string_arg,
+   checking one instance of __attribute__((null_terminated_string_arg)).  */
+
+void
+region_model::
+check_one_function_attr_null_terminated_string_arg (const gcall *call,
+                                                   tree callee_fndecl,
+                                                   region_model_context *ctxt,
+                                                   rdwr_map &rdwr_idx,
+                                                   tree attr)
+{
+  gcc_assert (call);
+  gcc_assert (callee_fndecl);
+  gcc_assert (ctxt);
+  gcc_assert (attr);
+
+  tree arg = TREE_VALUE (attr);
+  if (!arg)
+    return;
+
+  /* Convert from 1-based to 0-based index.  */
+  unsigned int arg_idx = TREE_INT_CST_LOW (TREE_VALUE (arg)) - 1;
+
+  /* If there's also an "access" attribute on the ptr param
+     for reading with a size param specified, then that size
+     limits the size of the possible read from the pointer.  */
+  if (const attr_access* access = rdwr_idx.get (arg_idx))
+    if ((access->mode == access_read_only
+        || access->mode == access_read_write)
+       && access->sizarg != UINT_MAX)
+      {
+       /* First, check for a null-terminated string *without*
+          emitting emitting warnings (via a null context), to
+          get an svalue for the strlen of the buffer (possibly
+          nullptr if there would be an issue).  */
+       call_details cd_unchecked (call, this, nullptr);
+       const svalue *strlen_sval
+         = check_for_null_terminated_string_arg (cd_unchecked,
+                                                 arg_idx);
+
+       /* Get svalue for the size limit argument.  */
+       call_details cd_checked (call, this, ctxt);
+       const svalue *limit_sval
+         = cd_checked.get_arg_svalue (access->sizarg);
+       const svalue *ptr_sval
+         = cd_checked.get_arg_svalue (arg_idx);
+       /* Try reading all of the bytes expressed by the size param,
+          but without checking (via a null context).  */
+       const svalue *limited_sval
+         = read_bytes (deref_rvalue (ptr_sval, NULL_TREE, nullptr),
+                       NULL_TREE,
+                       limit_sval,
+                       nullptr);
+       if (limited_sval->get_kind () == SK_POISONED)
+         {
+           /* Reading up to the truncation limit caused issues.
+              Assume that the string is meant to be terminated
+              before then, so perform a *checked* check for the
+              terminator.  */
+           check_for_null_terminated_string_arg (cd_checked,
+                                                 arg_idx);
+         }
+       else
+         {
+           /* Reading up to the truncation limit seems OK; repeat
+              the read, but with checking enabled.  */
+           const svalue *limited_sval
+             = read_bytes (deref_rvalue (ptr_sval, NULL_TREE, ctxt),
+                           NULL_TREE,
+                           limit_sval,
+                           ctxt);
+         }
+       return;
+      }
+
+  /* Otherwise, we don't have an access-attribute limiting the read.
+     Simulate a read up to the null terminator (if any).  */
+
+  call_details cd (call, this, ctxt);
+  check_for_null_terminated_string_arg (cd, arg_idx);
+}
+
+/* Check CALL a call to external function CALLEE_FNDECL for any uses
+   of __attribute__ ((null_terminated_string_arg)), compaining
+   to CTXT about any issues.
+
+   Use RDWR_IDX for tracking uses of __attribute__ ((access, ....).  */
+
+void
+region_model::
+check_function_attr_null_terminated_string_arg (const gcall *call,
+                                               tree callee_fndecl,
+                                               region_model_context *ctxt,
+                                               rdwr_map &rdwr_idx)
+{
+  gcc_assert (call);
+  gcc_assert (callee_fndecl);
+  gcc_assert (ctxt);
+
+  tree fntype = TREE_TYPE (callee_fndecl);
+  gcc_assert (fntype);
+
+  /* A function declaration can specify multiple attribute
+     null_terminated_string_arg, each with one argument.  */
+  for (tree attr = TYPE_ATTRIBUTES (fntype); attr; attr = TREE_CHAIN (attr))
+    {
+      attr = lookup_attribute ("null_terminated_string_arg", attr);
+      if (!attr)
+       return;
+
+      check_one_function_attr_null_terminated_string_arg (call, callee_fndecl,
+                                                         ctxt, rdwr_idx,
+                                                         attr);
+    }
+}
+
+/* Check CALL a call to external function CALLEE_FNDECL for any
+   function attributes, complaining to CTXT about any issues.  */
+
+void
+region_model::check_function_attrs (const gcall *call,
+                                   tree callee_fndecl,
+                                   region_model_context *ctxt)
+{
+  gcc_assert (call);
+  gcc_assert (callee_fndecl);
+  gcc_assert (ctxt);
+
+  tree fntype = TREE_TYPE (callee_fndecl);
+  if (!fntype)
+    return;
+
+  if (!TYPE_ATTRIBUTES (fntype))
+    return;
+
+  /* Initialize a map of attribute access specifications for arguments
+     to the function call.  */
+  rdwr_map rdwr_idx;
+  init_attr_rdwr_indices (&rdwr_idx, TYPE_ATTRIBUTES (fntype));
+
+  check_function_attr_access (call, callee_fndecl, ctxt, rdwr_idx);
+  check_function_attr_null_terminated_string_arg (call, callee_fndecl,
+                                                 ctxt, rdwr_idx);
+}
+
 /* Handle a call CALL to a function with unknown behavior.
 
    Traverse the regions in this model, determining what regions are
@@ -1870,7 +2006,7 @@ region_model::handle_unrecognized_call (const gcall *call,
   tree fndecl = get_fndecl_for_call (call, ctxt);
 
   if (fndecl && ctxt)
-    check_external_function_for_access_attr (call, fndecl, ctxt);
+    check_function_attrs (call, fndecl, ctxt);
 
   reachable_regions reachable_regs (this);
 
@@ -3768,14 +3904,14 @@ region_model::scan_for_null_terminator (const region 
*reg,
    TODO: we should also complain if:
    - the pointer is NULL (or could be).  */
 
-void
+const svalue *
 region_model::check_for_null_terminated_string_arg (const call_details &cd,
-                                                   unsigned arg_idx)
+                                                   unsigned arg_idx) const
 {
-  check_for_null_terminated_string_arg (cd,
-                                       arg_idx,
-                                       false, /* include_terminator */
-                                       nullptr); // out_sval
+  return check_for_null_terminated_string_arg (cd,
+                                              arg_idx,
+                                              false, /* include_terminator */
+                                              nullptr); // out_sval
 }
 
 
@@ -3805,7 +3941,7 @@ const svalue *
 region_model::check_for_null_terminated_string_arg (const call_details &cd,
                                                    unsigned arg_idx,
                                                    bool include_terminator,
-                                                   const svalue **out_sval)
+                                                   const svalue **out_sval) 
const
 {
   class null_terminator_check_event : public custom_event
   {
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index 6946c688cbbc..8bfb06880ff4 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -27,6 +27,8 @@ along with GCC; see the file COPYING3.  If not see
      http://lcs.ios.ac.cn/~xuzb/canalyze/memmodel.pdf  */
 
 #include "bitmap.h"
+#include "stringpool.h"
+#include "attribs.h" // for rdwr_map
 #include "selftest.h"
 #include "analyzer/svalue.h"
 #include "analyzer/region.h"
@@ -527,14 +529,14 @@ class region_model
                               const svalue *sval_hint,
                               region_model_context *ctxt) const;
 
-  void
+  const svalue *
   check_for_null_terminated_string_arg (const call_details &cd,
-                                       unsigned idx);
+                                       unsigned idx) const;
   const svalue *
   check_for_null_terminated_string_arg (const call_details &cd,
                                        unsigned idx,
                                        bool include_terminator,
-                                       const svalue **out_sval);
+                                       const svalue **out_sval) const;
 
   const builtin_known_function *
   get_builtin_kf (const gcall *call,
@@ -644,9 +646,22 @@ private:
   void check_call_args (const call_details &cd) const;
   void check_call_format_attr (const call_details &cd,
                               tree format_attr) const;
-  void check_external_function_for_access_attr (const gcall *call,
-                                               tree callee_fndecl,
-                                               region_model_context *ctxt) 
const;
+  void check_function_attr_access (const gcall *call,
+                                  tree callee_fndecl,
+                                  region_model_context *ctxt,
+                                  rdwr_map &rdwr_idx) const;
+  void check_function_attr_null_terminated_string_arg (const gcall *call,
+                                                      tree callee_fndecl,
+                                                      region_model_context 
*ctxt,
+                                                      rdwr_map &rdwr_idx);
+  void check_one_function_attr_null_terminated_string_arg (const gcall *call,
+                                                          tree callee_fndecl,
+                                                          region_model_context 
*ctxt,
+                                                          rdwr_map &rdwr_idx,
+                                                          tree attr);
+  void check_function_attrs (const gcall *call,
+                            tree callee_fndecl,
+                            region_model_context *ctxt);
 
   static auto_vec<pop_frame_callback> pop_frame_callbacks;
   /* Storing this here to avoid passing it around everywhere.  */
diff --git a/gcc/c-family/c-attribs.cc b/gcc/c-family/c-attribs.cc
index dca7548b2c6a..44dd19a6c8cc 100644
--- a/gcc/c-family/c-attribs.cc
+++ b/gcc/c-family/c-attribs.cc
@@ -177,6 +177,7 @@ static tree handle_signed_bool_precision_attribute (tree *, 
tree, tree, int,
                                                    bool *);
 static tree handle_retain_attribute (tree *, tree, tree, int, bool *);
 static tree handle_fd_arg_attribute (tree *, tree, tree, int, bool *);
+static tree handle_null_terminated_string_arg_attribute (tree *, tree, tree, 
int, bool *);
 
 /* Helper to define attribute exclusions.  */
 #define ATTR_EXCL(name, function, type, variable)      \
@@ -569,6 +570,8 @@ const struct attribute_spec c_common_attribute_table[] =
             handle_fd_arg_attribute, NULL},
   { "fd_arg_write",       1, 1, false, true, true, false,
             handle_fd_arg_attribute, NULL},         
+  { "null_terminated_string_arg", 1, 1, false, true, true, false,
+                             handle_null_terminated_string_arg_attribute, 
NULL},
   { NULL,                     0, 0, false, false, false, false, NULL, NULL }
 };
 
@@ -4657,6 +4660,20 @@ handle_fd_arg_attribute (tree *node, tree name, tree 
args,
   return NULL_TREE;
 }
 
+/* Handle the "null_terminated_string_arg" attribute.  */
+
+static tree
+handle_null_terminated_string_arg_attribute (tree *node, tree name, tree args,
+                                            int ARG_UNUSED (flags),
+                                            bool *no_add_attrs)
+{
+  if (positional_argument (*node, name, TREE_VALUE (args), POINTER_TYPE))
+    return NULL_TREE;
+
+  *no_add_attrs = true;
+  return NULL_TREE;
+}
+
 /* Handle the "nonstring" variable attribute.  */
 
 static tree
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index b82497f00e42..9152385e08aa 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -3744,6 +3744,63 @@ my_memcpy (void *dest, const void *src, size_t len)
         __attribute__((nonnull));
 @end smallexample
 
+@cindex @code{null_terminated_string_arg} function attribute
+@item null_terminated_string_arg
+@itemx null_terminated_string_arg (@var{N})
+The @code{null_terminated_string_arg} attribute may be applied to a
+function that takes a @code{char *} or @code{const char *} at
+referenced argument @var{N}.
+
+It indicates that the passed argument must be a C-style null-terminated
+string.  Specifically, the presence of the attribute implies that, if
+the pointer is non-null, the function may scan through the referenced
+buffer looking for the first zero byte.
+
+In particular, when the analyzer is enabled (via @option{-fanalyzer}),
+if the pointer is non-null, it will simulate scanning for the first
+zero byte in the referenced buffer, and potentially emit
+@option{-Wanalyzer-use-of-uninitialized-value}
+or @option{-Wanalyzer-out-of-bounds} on improperly terminated buffers.
+
+For example, given the following:
+
+@smallexample
+char *example_1 (const char *p)
+  __attribute__((null_terminated_string_arg (1)));
+@end smallexample
+
+the analyzer will check that any non-null pointers passed to the function
+are validly terminated.
+
+If the parameter must be non-null, it is appropriate to use both this
+attribute and the attribute @code{nonnull}, such as in:
+
+@smallexample
+extern char *example_2 (const char *p)
+  __attribute__((null_terminated_string_arg (1),
+                 nonnull (1)));
+@end smallexample
+
+See the @code{nonnull} attribute for more information and
+caveats.
+
+If the pointer argument is also referred to by an @code{access} attribute on 
the
+function with @var{access-mode} either @code{read_only} or @code{read_write}
+and the latter attribute has the optional @var{size-index} argument
+referring to a size argument, this expressses the maximum size of the access.
+For example, given:
+
+@smallexample
+extern char *example_fn (const char *p, size_t n)
+  __attribute__((null_terminated_string_arg (1),
+                 access (read_only, 1, 2),
+                 nonnull (1)));
+@end smallexample
+
+the analyzer will require the first parameter to be non-null, and either
+be validly null-terminated, or validly readable up to the size specified by
+the second parameter.
+
 @cindex @code{noplt} function attribute
 @item noplt
 The @code{noplt} attribute is the counterpart to option @option{-fno-plt}.
diff --git 
a/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-access-read_write.c
 
b/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-access-read_write.c
new file mode 100644
index 000000000000..dc5da22a1bfd
--- /dev/null
+++ 
b/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-access-read_write.c
@@ -0,0 +1,15 @@
+/* { dg-additional-options "-Wno-stringop-overflow" } */
+/* { dg-additional-options "-fpermissive" { target c++ } } */
+
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+char *example_fn (char *p, __SIZE_TYPE__ n)
+  __attribute__((null_terminated_string_arg (1)))
+  __attribute__((access (read_write, 1, 2)));
+
+char *
+test_unterminated_str (void)
+{
+  char str[3] = "abc"; /* { dg-warning "initializer-string for '\[^\n\]*' is 
too long" "" { target c++ } } */
+  return example_fn (str, 4); /* { dg-warning "stack-based buffer over-read" } 
*/
+}
diff --git 
a/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-access-without-size.c
 
b/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-access-without-size.c
new file mode 100644
index 000000000000..83ec24a49ca2
--- /dev/null
+++ 
b/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-access-without-size.c
@@ -0,0 +1,54 @@
+/* { dg-additional-options "-Wno-stringop-overread" } */
+/* { dg-additional-options "-fpermissive" { target c++ } } */
+
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+char *example_fn (const char *p, __SIZE_TYPE__ n) /* { dg-message "argument 1 
of '\[^\n\r\]*' must be a pointer to a null-terminated string" } */
+  __attribute__((null_terminated_string_arg (1)))
+  __attribute__((access (read_only, 1))); // but doesn't identify an argument 
for a size limit
+
+char *
+test_passthrough (const char* str, __SIZE_TYPE__ n)
+{
+  return example_fn (str, n);
+}
+
+char *
+test_NULL_str_a (void)
+{
+  return example_fn (NULL, 0); /* { dg-bogus "use of NULL where non-null 
expected" } */
+}
+
+char *
+test_NULL_str_b (void)
+{
+  return example_fn (NULL, 4); /* { dg-bogus "use of NULL where non-null 
expected" } */
+}
+
+char *
+test_unterminated_str (void)
+{
+  char str[3] = "abc"; /* { dg-warning "initializer-string for '\[^\n\]*' is 
too long" "" { target c++ } } */
+  return example_fn (str, 4); /* { dg-warning "stack-based buffer over-read" } 
*/
+}
+
+char *
+test_uninitialized_str_a (void)
+{
+  char str[16];
+  return example_fn (str, 1); /* { dg-warning "use of uninitialized value 
'str\\\[0\\\]'" } */
+}
+
+char *
+test_uninitialized_str_b (void)
+{
+  char str[16];
+  return example_fn (str, 16); /* { dg-warning "use of uninitialized value 
'str\\\[0\\\]'" } */
+}
+
+char *
+test_uninitialized_str_c (void)
+{
+  char str[16];
+  return example_fn (str, 17); /* { dg-warning "use of uninitialized value 
'str\\\[0\\\]'" } */
+}
diff --git 
a/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-multiple.c
 
b/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-multiple.c
new file mode 100644
index 000000000000..a5418b4b5ace
--- /dev/null
+++ 
b/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-multiple.c
@@ -0,0 +1,52 @@
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+/* { dg-additional-options "-fpermissive" { target c++ } } */
+
+/* Example with multiple params with attribute null_terminated_string_arg.  */
+
+char *example_fn (const char *p, const char *q)
+  __attribute__((null_terminated_string_arg (1)))
+  __attribute__((null_terminated_string_arg (2)));
+// but can be NULL
+
+char *
+test_passthrough (const char *a, const char *b)
+{
+  return example_fn (a, b);
+}
+
+char *
+test_NULL_str (void)
+{
+  return example_fn (NULL, NULL); /* { dg-bogus "use of NULL where non-null 
expected" } */
+}
+
+char *
+test_unterminated_str_1 (void)
+{
+  char str[3] = "abc"; /* { dg-warning "initializer-string for '\[^\n\]*' is 
too long" "" { target c++ } } */
+  return example_fn (str, NULL); /* { dg-warning "stack-based buffer 
over-read" } */
+  /* { dg-message "while looking for null terminator for argument 1" "note" { 
target *-*-* } .-1 } */
+}
+
+char *
+test_unterminated_str_2 (void)
+{
+  char str[3] = "abc"; /* { dg-warning "initializer-string for '\[^\n\]*' is 
too long" "" { target c++ } } */
+  return example_fn (NULL, str); /* { dg-warning "stack-based buffer 
over-read" } */
+  /* { dg-message "while looking for null terminator for argument 2" "note" { 
target *-*-* } .-1 } */
+}
+
+char *
+test_uninitialized_str_1 (void)
+{
+  char str[16];
+  return example_fn (str, NULL); /* { dg-warning "use of uninitialized value 
'str\\\[0\\\]'" } */
+}
+
+char *
+test_uninitialized_str_2 (void)
+{
+  char str[16];
+  return example_fn (NULL, str); /* { dg-warning "use of uninitialized value 
'str\\\[0\\\]'" } */
+}
diff --git 
a/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nonnull-2.c
 
b/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nonnull-2.c
new file mode 100644
index 000000000000..2614633318ec
--- /dev/null
+++ 
b/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nonnull-2.c
@@ -0,0 +1,33 @@
+/* { dg-additional-options "-fpermissive" { target c++ } } */
+
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+extern char *example_fn (const char *p) /* { dg-message "argument 1 of 
'\[^\n\r\]*' must be a pointer to a null-terminated string" } */
+  __attribute__((null_terminated_string_arg (1), nonnull (1)));
+
+char *
+test_passthrough (const char* str)
+{
+  return example_fn (str);
+}
+
+char *
+test_NULL_str (void)
+{
+  return example_fn (NULL); /* { dg-warning "use of NULL where non-null 
expected" } */
+}
+
+char *
+test_unterminated_str (void)
+{
+  char str[3] = "abc"; /* { dg-warning "initializer-string for '\[^\n\]*' is 
too long" "" { target c++ } } */
+  return example_fn (str); /* { dg-warning "stack-based buffer over-read" } */
+  /* { dg-message "while looking for null terminator for argument 1" "note" { 
target *-*-* } .-1 } */
+}
+
+char *
+test_uninitialized_str (void)
+{
+  char str[16];
+  return example_fn (str); /* { dg-warning "use of uninitialized value 
'str\\\[0\\\]'" } */
+}
diff --git 
a/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nonnull-sized.c
 
b/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nonnull-sized.c
new file mode 100644
index 000000000000..c539f29a5c7b
--- /dev/null
+++ 
b/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nonnull-sized.c
@@ -0,0 +1,69 @@
+/* { dg-additional-options "-Wno-stringop-overread" } */
+/* { dg-additional-options "-fpermissive" { target c++ } } */
+
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+char *example_fn (const char *p, __SIZE_TYPE__ n) /* { dg-message "argument 1 
of '\[^\n\r\]*' must be a pointer to a null-terminated string" } */
+  __attribute__((null_terminated_string_arg (1)))
+  __attribute__((access (read_only, 1, 2)))
+  __attribute__((nonnull));
+
+char *
+test_passthrough (const char* str, __SIZE_TYPE__ n)
+{
+  return example_fn (str, n);
+}
+
+char *
+test_NULL_str_a (void)
+{
+  return example_fn (NULL, 0); /* { dg-warning "use of NULL where non-null 
expected" } */
+}
+
+char *
+test_NULL_str_b (void)
+{
+  return example_fn (NULL, 4); /* { dg-warning "use of NULL where non-null 
expected" } */
+}
+
+char *
+test_unterminated_str (void)
+{
+  char str[3] = "abc"; /* { dg-warning "initializer-string for '\[^\n\]*' is 
too long" "" { target c++ } } */
+  return example_fn (str, 4); /* { dg-warning "stack-based buffer over-read" } 
*/
+}
+
+char *
+test_unterminated_str_truncated (void)
+{
+  char str[3] = "abc"; /* { dg-warning "initializer-string for '\[^\n\]*' is 
too long" "" { target c++ } } */
+  return example_fn (str, 3); /* { dg-bogus "stack-based buffer over-read" } */
+}
+
+char *
+test_uninitialized_str_a (void)
+{
+  char str[16];
+  return example_fn (str, 1); /* { dg-warning "use of uninitialized value 
'str\\\[0\\\]'" } */
+}
+
+char *
+test_uninitialized_str_b (void)
+{
+  char str[16];
+  return example_fn (str, 16); /* { dg-warning "use of uninitialized value 
'str\\\[0\\\]'" } */
+}
+
+char *
+test_uninitialized_str_c (void)
+{
+  char str[16];
+  return example_fn (str, 17); /* { dg-warning "use of uninitialized value 
'str\\\[0\\\]'" } */
+}
+
+char *
+test_uninitialized_str_truncated (void)
+{
+  char str[16];
+  return example_fn (str, 0); /* { dg-bogus "use of uninitialized value" } */
+}
diff --git 
a/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nonnull.c 
b/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nonnull.c
new file mode 100644
index 000000000000..6f805d5105ff
--- /dev/null
+++ 
b/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nonnull.c
@@ -0,0 +1,34 @@
+/* { dg-additional-options "-fpermissive" { target c++ } } */
+
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+char *example_fn (const char *p) /* { dg-message "argument 1 of '\[^\n\r\]*' 
must be a pointer to a null-terminated string" } */
+  __attribute__((null_terminated_string_arg (1)))
+  __attribute__((nonnull));
+
+char *
+test_passthrough (const char* str)
+{
+  return example_fn (str);
+}
+
+char *
+test_NULL_str (void)
+{
+  return example_fn (NULL); /* { dg-warning "use of NULL where non-null 
expected" } */
+}
+
+char *
+test_unterminated_str (void)
+{
+  char str[3] = "abc"; /* { dg-warning "initializer-string for '\[^\n\]*' is 
too long" "" { target c++ } } */
+  return example_fn (str); /* { dg-warning "stack-based buffer over-read" } */
+  /* { dg-message "while looking for null terminator for argument 1" "note" { 
target *-*-* } .-1 } */
+}
+
+char *
+test_uninitialized_str (void)
+{
+  char str[16];
+  return example_fn (str); /* { dg-warning "use of uninitialized value 
'str\\\[0\\\]'" } */
+}
diff --git 
a/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nullable-sized.c
 
b/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nullable-sized.c
new file mode 100644
index 000000000000..28df4b53f6f6
--- /dev/null
+++ 
b/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nullable-sized.c
@@ -0,0 +1,69 @@
+/* { dg-additional-options "-Wno-stringop-overread" } */
+/* { dg-additional-options "-fpermissive" { target c++ } } */
+
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+char *example_fn (const char *p, __SIZE_TYPE__ n) /* { dg-message "argument 1 
of '\[^\n\r\]*' must be a pointer to a null-terminated string" } */
+  __attribute__((null_terminated_string_arg (1)))
+  __attribute__((access (read_only, 1, 2)));
+// can be NULL
+
+char *
+test_passthrough (const char* str, __SIZE_TYPE__ n)
+{
+  return example_fn (str, n);
+}
+
+char *
+test_NULL_str_a (void)
+{
+  return example_fn (NULL, 0); /* { dg-bogus "use of NULL where non-null 
expected" } */
+}
+
+char *
+test_NULL_str_b (void)
+{
+  return example_fn (NULL, 4); /* { dg-bogus "use of NULL where non-null 
expected" } */
+}
+
+char *
+test_unterminated_str (void)
+{
+  char str[3] = "abc"; /* { dg-warning "initializer-string for '\[^\n\]*' is 
too long" "" { target c++ } } */
+  return example_fn (str, 4); /* { dg-warning "stack-based buffer over-read" } 
*/
+}
+
+char *
+test_unterminated_str_truncated (void)
+{
+  char str[3] = "abc"; /* { dg-warning "initializer-string for '\[^\n\]*' is 
too long" "" { target c++ } } */
+  return example_fn (str, 3); /* { dg-bogus "stack-based buffer over-read" } */
+}
+
+char *
+test_uninitialized_str_a (void)
+{
+  char str[16];
+  return example_fn (str, 1); /* { dg-warning "use of uninitialized value 
'str\\\[0\\\]'" } */
+}
+
+char *
+test_uninitialized_str_b (void)
+{
+  char str[16];
+  return example_fn (str, 16); /* { dg-warning "use of uninitialized value 
'str\\\[0\\\]'" } */
+}
+
+char *
+test_uninitialized_str_c (void)
+{
+  char str[16];
+  return example_fn (str, 17); /* { dg-warning "use of uninitialized value 
'str\\\[0\\\]'" } */
+}
+
+char *
+test_uninitialized_str_truncated (void)
+{
+  char str[16];
+  return example_fn (str, 0); /* { dg-bogus "use of uninitialized value" } */
+}
diff --git 
a/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nullable.c
 
b/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nullable.c
new file mode 100644
index 000000000000..8bbe0b6a8130
--- /dev/null
+++ 
b/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nullable.c
@@ -0,0 +1,34 @@
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+/* { dg-additional-options "-fpermissive" { target c++ } } */
+
+char *example_fn (const char *p) /* { dg-message "argument 1 of '\[^\n\r\]*' 
must be a pointer to a null-terminated string" } */
+  __attribute__((null_terminated_string_arg (1)));
+// but can be NULL
+
+char *
+test_passthrough (const char* str)
+{
+  return example_fn (str);
+}
+
+char *
+test_NULL_str (void)
+{
+  return example_fn (NULL); /* { dg-bogus "use of NULL where non-null 
expected" } */
+}
+
+char *
+test_unterminated_str (void)
+{
+  char str[3] = "abc"; /* { dg-warning "initializer-string for '\[^\n\]*' is 
too long" "" { target c++ } } */
+  return example_fn (str); /* { dg-warning "stack-based buffer over-read" } */
+  /* { dg-message "while looking for null terminator for argument 1" "note" { 
target *-*-* } .-1 } */
+}
+
+char *
+test_uninitialized_str (void)
+{
+  char str[16];
+  return example_fn (str); /* { dg-warning "use of uninitialized value 
'str\\\[0\\\]'" } */
+}
diff --git a/gcc/testsuite/c-c++-common/attr-null_terminated_string_arg.c 
b/gcc/testsuite/c-c++-common/attr-null_terminated_string_arg.c
new file mode 100644
index 000000000000..f2f1d4895804
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/attr-null_terminated_string_arg.c
@@ -0,0 +1,16 @@
+extern int not_a_function __attribute__((null_terminated_string_arg(1))); /* { 
dg-warning "'null_terminated_string_arg' attribute only applies to function 
types" } */
+
+extern void no_arg (void) __attribute__((null_terminated_string_arg)); /* { 
dg-error "wrong number of arguments specified for 'null_terminated_string_arg' 
attribute" } */
+
+extern void arg_idx_not_an_int (int) __attribute__((null_terminated_string_arg 
("foo"))); /* { dg-warning "'null_terminated_string_arg' attribute argument has 
type" } */
+
+extern void arg_not_a_pointer (int) __attribute__((null_terminated_string_arg 
(1))); /* { dg-warning "'null_terminated_string_arg' attribute argument value 
'1' refers to parameter type 'int'" } */
+
+extern void arg_not_a_char_pointer (int) 
__attribute__((null_terminated_string_arg (1))); /* { dg-warning 
"'null_terminated_string_arg' attribute argument value '1' refers to parameter 
type 'int'" } */
+
+extern void arg_idx_too_low (const char *) 
__attribute__((null_terminated_string_arg (0))); /* { dg-warning 
"'null_terminated_string_arg' attribute argument value '0' does not refer to a 
function parameter" } */
+
+extern void arg_idx_too_high (const char *) 
__attribute__((null_terminated_string_arg (2))); /* { dg-warning 
"'null_terminated_string_arg' attribute argument value '2' exceeds the number 
of function parameters 1" } */
+
+extern void valid_non_const (char *) __attribute__((null_terminated_string_arg 
(1)));
+extern void valid_const (const char *) 
__attribute__((null_terminated_string_arg (1)));
-- 
2.26.3

Reply via email to