public inbox for gcc-cvs@sourceware.org
help / color / mirror / Atom feed
* [gcc r14-4958] Add attribute((null_terminated_string_arg(PARAM_IDX)))
@ 2023-10-26 19:58 David Malcolm
  0 siblings, 0 replies; only message in thread
From: David Malcolm @ 2023-10-26 19:58 UTC (permalink / raw)
  To: gcc-cvs

https://gcc.gnu.org/g:cd7dadcd2759d195b75f4dba3e17b638ed92db68

commit r14-4958-gcd7dadcd2759d195b75f4dba3e17b638ed92db68
Author: David Malcolm <dmalcolm@redhat.com>
Date:   Thu Oct 26 15:56:13 2023 -0400

    Add attribute((null_terminated_string_arg(PARAM_IDX)))
    
    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
    
    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 <dmalcolm@redhat.com>

Diff:
---
 gcc/analyzer/region-model.cc                       | 180 ++++++++++++++++++---
 gcc/analyzer/region-model.h                        |  27 +++-
 gcc/c-family/c-attribs.cc                          |  17 ++
 gcc/doc/extend.texi                                |  57 +++++++
 ...-null_terminated_string_arg-access-read_write.c |  15 ++
 ...ull_terminated_string_arg-access-without-size.c |  54 +++++++
 .../attr-null_terminated_string_arg-multiple.c     |  52 ++++++
 .../attr-null_terminated_string_arg-nonnull-2.c    |  33 ++++
 ...attr-null_terminated_string_arg-nonnull-sized.c |  69 ++++++++
 .../attr-null_terminated_string_arg-nonnull.c      |  34 ++++
 ...ttr-null_terminated_string_arg-nullable-sized.c |  69 ++++++++
 .../attr-null_terminated_string_arg-nullable.c     |  34 ++++
 .../c-c++-common/attr-null_terminated_string_arg.c |  16 ++
 13 files changed, 629 insertions(+), 28 deletions(-)

diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index c4e68661ef1e..067347ef8451 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 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 abf44d5426e8..a041c3b91ebf 100644
--- a/gcc/c-family/c-attribs.cc
+++ b/gcc/c-family/c-attribs.cc
@@ -178,6 +178,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)	\
@@ -572,6 +573,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 }
 };
 
@@ -4660,6 +4663,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 9923a18bde98..f5c44bb52b82 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -3755,6 +3755,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)));

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2023-10-26 19:58 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-10-26 19:58 [gcc r14-4958] Add attribute((null_terminated_string_arg(PARAM_IDX))) David Malcolm

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).