aboutsummaryrefslogtreecommitdiff
path: root/gcc/analyzer/region-model.cc
diff options
context:
space:
mode:
authorDavid Malcolm <dmalcolm@redhat.com>2023-10-26 15:56:13 -0400
committerDavid Malcolm <dmalcolm@redhat.com>2023-10-26 15:57:40 -0400
commitcd7dadcd2759d195b75f4dba3e17b638ed92db68 (patch)
tree39d04e8f1d5b6025ee5e47a4842c1d92a6c265ae /gcc/analyzer/region-model.cc
parent46f51bd73b77e572eb647eb56eddff663dd5e954 (diff)
downloadgcc-cd7dadcd2759d195b75f4dba3e17b638ed92db68.zip
gcc-cd7dadcd2759d195b75f4dba3e17b638ed92db68.tar.gz
gcc-cd7dadcd2759d195b75f4dba3e17b638ed92db68.tar.bz2
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>
Diffstat (limited to 'gcc/analyzer/region-model.cc')
-rw-r--r--gcc/analyzer/region-model.cc180
1 files changed, 158 insertions, 22 deletions
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index c4e68661..067347e 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
{