diff mbox series

[committed] analyzer: check for writes to consts via access attr [PR104793]

Message ID 20220310141831.3402489-1-dmalcolm@redhat.com
State New
Headers show
Series [committed] analyzer: check for writes to consts via access attr [PR104793] | expand

Commit Message

David Malcolm March 10, 2022, 2:18 p.m. UTC
This patch extends:
  -Wanalyzer-write-to-const
  -Wanalyzer-write-to-string-literal
so that they will check for __attribute__ ((access, ....) on calls to
externally-defined functions, and complain about read-only regions
pointed to by arguments marked with a "write_only" or "read_write"
attribute.

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
Pushed to trunk as r12-7595-gb6eaf90c64f915.

gcc/analyzer/ChangeLog:
	PR analyzer/104793
	* region-model.cc
	(region_model::check_external_function_for_access_attr): New.
	(region_model::handle_unrecognized_call): Call it.
	* region-model.h
	(region_model::check_external_function_for_access_attr): New decl.
	(region_model::handle_unrecognized_call): New decl.

gcc/testsuite/ChangeLog:
	PR analyzer/104793
	* gcc.dg/analyzer/write-to-const-2.c: New test.
	* gcc.dg/analyzer/write-to-function-1.c: New test.
	* gcc.dg/analyzer/write-to-string-literal-2.c: New test.
	* gcc.dg/analyzer/write-to-string-literal-3.c: New test.
	* gcc.dg/analyzer/write-to-string-literal-4.c: New test.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
---
 gcc/analyzer/region-model.cc                  | 58 ++++++++++++++++
 gcc/analyzer/region-model.h                   |  3 +
 .../gcc.dg/analyzer/write-to-const-2.c        | 60 +++++++++++++++++
 .../gcc.dg/analyzer/write-to-function-1.c     | 15 +++++
 .../analyzer/write-to-string-literal-2.c      | 19 ++++++
 .../analyzer/write-to-string-literal-3.c      | 66 +++++++++++++++++++
 .../analyzer/write-to-string-literal-4.c      | 23 +++++++
 7 files changed, 244 insertions(+)
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/write-to-const-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/write-to-function-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/write-to-string-literal-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/write-to-string-literal-3.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/write-to-string-literal-4.c
diff mbox series

Patch

diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index 5cfa3543f17..5760ff70938 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -1583,6 +1583,61 @@  region_model::purge_state_involving (const svalue *sval,
     ctxt->purge_state_involving (sval);
 }
 
+/* Check CALL a call to external function CALLEE_FNDECL based on
+   any __attribute__ ((access, ....) on the latter, complaining to
+   CTXT about any issues.
+
+   Currently we merely call check_region_for_write on any regions
+   pointed to by arguments marked with a "write_only" or "read_write"
+   attribute.  */
+
+void
+region_model::
+check_external_function_for_access_attr (const gcall *call,
+					 tree callee_fndecl,
+					 region_model_context *ctxt) 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));
+
+  unsigned argno = 0;
+
+  for (tree iter = TYPE_ARG_TYPES (fntype); iter;
+       iter = TREE_CHAIN (iter), ++argno)
+    {
+      const attr_access* access = rdwr_idx.get (argno);
+      if (!access)
+	continue;
+
+      /* Ignore any duplicate entry in the map for the size argument.  */
+      if (access->ptrarg != argno)
+	continue;
+
+      if (access->mode == access_write_only
+	  || access->mode == access_read_write)
+	{
+	  tree ptr_tree = gimple_call_arg (call, access->ptrarg);
+	  const svalue *ptr_sval = get_rvalue (ptr_tree, ctxt);
+	  const region *reg = deref_rvalue (ptr_sval, ptr_tree, ctxt);
+	  check_region_for_write (reg, ctxt);
+	  /* We don't use the size arg for now.  */
+	}
+    }
+}
+
 /* Handle a call CALL to a function with unknown behavior.
 
    Traverse the regions in this model, determining what regions are
@@ -1598,6 +1653,9 @@  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);
+
   reachable_regions reachable_regs (this);
 
   /* Determine the reachable regions and their mutability.  */
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index aa489d06a38..788d0c22bca 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -846,6 +846,9 @@  class region_model
 			      region_model_context *ctxt) const;
 
   void check_call_args (const call_details &cd) const;
+  void check_external_function_for_access_attr (const gcall *call,
+						tree callee_fndecl,
+						region_model_context *ctxt) const;
 
   /* Storing this here to avoid passing it around everywhere.  */
   region_model_manager *const m_mgr;
diff --git a/gcc/testsuite/gcc.dg/analyzer/write-to-const-2.c b/gcc/testsuite/gcc.dg/analyzer/write-to-const-2.c
new file mode 100644
index 00000000000..d0f2f29e985
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/write-to-const-2.c
@@ -0,0 +1,60 @@ 
+typedef __SIZE_TYPE__ size_t;
+
+void read_only (void *)
+  __attribute__ ((access (read_only, 1)));
+void write_only (void *)
+  __attribute__ ((access (write_only, 1)));
+void read_write (void *)
+  __attribute__ ((access (read_write, 1)));
+void none (void *)
+  __attribute__ ((access (none, 1)));
+void read_only_with_size (void *, size_t)
+  __attribute__ ((access (read_only, 1, 2)));
+void write_only_with_size (void *, size_t)
+  __attribute__ ((access (write_only, 1, 2)));
+void read_write_with_size (void *, size_t)
+  __attribute__ ((access (read_write, 1, 2)));
+void none_with_size (void *, size_t)
+  __attribute__ ((access (none, 1, 2)));
+
+const char buf[5] = { 0 }; /* { dg-message "declared here" } */
+
+void test_read_only (void)
+{
+  read_only ((char *)buf);
+}
+
+void test_write_only (void)
+{
+  write_only ((char *)buf); /* { dg-warning "write to 'const' object 'buf'" } */
+}
+
+void test_read_write (void)
+{
+  read_write ((char *)buf); /* { dg-warning "write to 'const' object 'buf'" } */
+}
+
+void test_none (void)
+{
+  none ((char *)buf);
+}
+
+void test_read_only_with_size (void)
+{
+  read_only_with_size ((char *)buf, sizeof (buf));
+}
+
+void test_write_only_with_size (void)
+{
+  write_only_with_size ((char *)buf, sizeof (buf)); /* { dg-warning "write to 'const' object 'buf'" } */
+}
+
+void test_read_write_with_size (void)
+{
+  read_write_with_size ((char *)buf, sizeof (buf)); /* { dg-warning "write to 'const' object 'buf'" } */
+}
+
+void test_none_with_size (void)
+{
+  none_with_size ((char *)buf, sizeof (buf));
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/write-to-function-1.c b/gcc/testsuite/gcc.dg/analyzer/write-to-function-1.c
new file mode 100644
index 00000000000..38374ddf9e4
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/write-to-function-1.c
@@ -0,0 +1,15 @@ 
+typedef __SIZE_TYPE__ size_t;
+
+int getrandom (void *__buffer, size_t __length,
+	       unsigned int __flags)
+  __attribute__ ((access (__write_only__, 1, 2)));
+
+#define GRND_RANDOM 0x02
+
+void test (void)
+{
+  char buf[16];
+
+  if (getrandom(test, 16, GRND_RANDOM)) /* { dg-warning "write to function 'test'" } */
+    __builtin_printf("%s\n", buf);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/write-to-string-literal-2.c b/gcc/testsuite/gcc.dg/analyzer/write-to-string-literal-2.c
new file mode 100644
index 00000000000..e5906cd5546
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/write-to-string-literal-2.c
@@ -0,0 +1,19 @@ 
+typedef __SIZE_TYPE__ size_t;
+
+int getrandom (void *__buffer, size_t __length,
+	       unsigned int __flags)
+  __attribute__ ((access (__write_only__, 1, 2)));
+
+#define GRND_RANDOM 0x02
+
+const char *test = "test";
+
+int main(void)
+{
+	const char buf[5] = { 0 };
+
+	if (getrandom((char *)test, sizeof(buf), GRND_RANDOM)) /* { dg-warning "write to string literal" } */
+		__builtin_printf("%s\n", buf);
+
+	return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/write-to-string-literal-3.c b/gcc/testsuite/gcc.dg/analyzer/write-to-string-literal-3.c
new file mode 100644
index 00000000000..7f4fb4f3aa0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/write-to-string-literal-3.c
@@ -0,0 +1,66 @@ 
+typedef __SIZE_TYPE__ size_t;
+
+void read_only (void *)
+  __attribute__ ((access (read_only, 1)));
+void write_only (void *)
+  __attribute__ ((access (write_only, 1)));
+void read_write (void *)
+  __attribute__ ((access (read_write, 1)));
+void none (void *)
+  __attribute__ ((access (none, 1)));
+void read_only_with_size (void *, size_t)
+  __attribute__ ((access (read_only, 1, 2)));
+void write_only_with_size (void *, size_t)
+  __attribute__ ((access (write_only, 1, 2)));
+void read_write_with_size (void *, size_t)
+  __attribute__ ((access (read_write, 1, 2)));
+void none_with_size (void *, size_t)
+  __attribute__ ((access (none, 1, 2)));
+
+void test_read_only (void)
+{
+  const char *str = "hello world";
+  read_only ((char *)str);
+}
+
+void test_write_only (void)
+{
+  const char *str = "hello world";
+  write_only ((char *)str); /* { dg-warning "write to string literal" } */
+}
+
+void test_read_write (void)
+{
+  const char *str = "hello world";
+  read_write ((char *)str); /* { dg-warning "write to string literal" } */
+}
+
+void test_none (void)
+{
+  const char *str = "hello world";
+  none ((char *)str);
+}
+
+void test_read_only_with_size (void)
+{
+  const char *str = "hello world";
+  read_only_with_size ((char *)str, sizeof (str));
+}
+
+void test_write_only_with_size (void)
+{
+  const char *str = "hello world";
+  write_only_with_size ((char *)str, sizeof (str)); /* { dg-warning "write to string literal" } */
+}
+
+void test_read_write_with_size (void)
+{
+  const char *str = "hello world";
+  read_write_with_size ((char *)str, sizeof (str)); /* { dg-warning "write to string literal" } */
+}
+
+void test_none_with_size (void)
+{
+  const char *str = "hello world";
+  none_with_size ((char *)str, sizeof (str));
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/write-to-string-literal-4.c b/gcc/testsuite/gcc.dg/analyzer/write-to-string-literal-4.c
new file mode 100644
index 00000000000..3d3270ffdbb
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/write-to-string-literal-4.c
@@ -0,0 +1,23 @@ 
+typedef __SIZE_TYPE__ size_t;
+
+int getrandom (void *__buffer, size_t __length,
+	       unsigned int __flags)
+  __attribute__ ((access (__write_only__, 1, 2)));
+
+#define GRND_RANDOM 0x02
+
+void test (int flag)
+{
+  char *buf;
+
+  if (flag)
+    buf = __builtin_malloc (1024);
+  else
+    buf = (char *)""; /* { dg-message "here" } */
+
+  if (getrandom(buf, 16, GRND_RANDOM)) /* { dg-warning "write to string literal" } */
+    __builtin_printf("%s\n", buf);
+
+  if (flag)
+    __builtin_free (buf);
+}