@@ -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
{
@@ -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. */
@@ -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
@@ -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}.
new file mode 100644
@@ -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" } */
+}
new file mode 100644
@@ -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\\\]'" } */
+}
new file mode 100644
@@ -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\\\]'" } */
+}
new file mode 100644
@@ -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\\\]'" } */
+}
new file mode 100644
@@ -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" } */
+}
new file mode 100644
@@ -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\\\]'" } */
+}
new file mode 100644
@@ -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" } */
+}
new file mode 100644
@@ -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\\\]'" } */
+}
new file mode 100644
@@ -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)));