diff mbox series

[committed] analyzer: add tests of boxed values [PR104943]

Message ID 20220318232356.67826-1-dmalcolm@redhat.com
State New
Headers show
Series [committed] analyzer: add tests of boxed values [PR104943] | expand

Commit Message

David Malcolm March 18, 2022, 11:23 p.m. UTC
This patch adds various regression tests as preparatory work for
purging irrelevant local decls from state (PR analyzer/104943)

Tested on x86_64-pc-linux-gnu.
Pushed to trunk as r12-7717-g1c1daca1cdf7bc0156d57bb2b9083ee70c66b000.

gcc/testsuite/ChangeLog:
	PR analyzer/104943
	* gcc.dg/analyzer/boxed-malloc-1-29.c: New test.
	* gcc.dg/analyzer/boxed-malloc-1.c: New test.
	* gcc.dg/analyzer/taint-alloc-5.c: New test.
	* gcc.dg/analyzer/torture/boxed-int-1.c: New test.
	* gcc.dg/analyzer/torture/boxed-ptr-1.c: New test.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
---
 .../gcc.dg/analyzer/boxed-malloc-1-29.c       |  36 ++
 .../gcc.dg/analyzer/boxed-malloc-1.c          | 476 ++++++++++++++++++
 gcc/testsuite/gcc.dg/analyzer/taint-alloc-5.c |  21 +
 .../gcc.dg/analyzer/torture/boxed-int-1.c     | 170 +++++++
 .../gcc.dg/analyzer/torture/boxed-ptr-1.c     |  82 +++
 5 files changed, 785 insertions(+)
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/boxed-malloc-1-29.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/boxed-malloc-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/taint-alloc-5.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/torture/boxed-int-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/torture/boxed-ptr-1.c
diff mbox series

Patch

diff --git a/gcc/testsuite/gcc.dg/analyzer/boxed-malloc-1-29.c b/gcc/testsuite/gcc.dg/analyzer/boxed-malloc-1-29.c
new file mode 100644
index 00000000000..9e38f97fc8e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/boxed-malloc-1-29.c
@@ -0,0 +1,36 @@ 
+/* Isolating this false positive from boxed-malloc-1.c since it's
+   reported within boxed_malloc.  */
+
+#include <stdlib.h>
+
+typedef struct boxed_ptr { void *value; } boxed_ptr;
+
+boxed_ptr
+boxed_malloc (size_t sz)
+{
+  boxed_ptr result;
+  result.value = malloc (sz);
+  return result; /* { dg-bogus "leak" "leak false +ve (PR analyzer/104979)" { xfail *-*-* } } */
+}
+
+boxed_ptr
+boxed_free (boxed_ptr ptr)
+{
+  free (ptr.value);
+}
+
+const boxed_ptr boxed_null = {NULL};
+
+struct link
+{
+  boxed_ptr m_ptr;
+};
+
+boxed_ptr test_29 (void)
+{
+  boxed_ptr res = boxed_malloc (sizeof (struct link));
+  if (!res.value)
+    return boxed_null;
+  ((struct link *)res.value)->m_ptr = boxed_malloc (sizeof (struct link));
+  return res;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/boxed-malloc-1.c b/gcc/testsuite/gcc.dg/analyzer/boxed-malloc-1.c
new file mode 100644
index 00000000000..5428f2baf49
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/boxed-malloc-1.c
@@ -0,0 +1,476 @@ 
+/* Adapted from malloc-1.c, but wrapping the pointers in a struct.  */
+
+/* { dg-require-effective-target alloca } */
+
+#include <stdlib.h>
+
+extern int foo (void);
+extern int bar (void);
+extern void could_free (void *);
+extern void cant_free (const void *); /* since it's a const void *.  */
+
+typedef struct boxed_ptr { void *value; } boxed_ptr;
+
+boxed_ptr
+boxed_malloc (size_t sz)
+{
+  boxed_ptr result;
+  result.value = malloc (sz);
+  return result;
+}
+
+boxed_ptr
+boxed_free (boxed_ptr ptr)
+{
+  free (ptr.value);
+}
+
+const boxed_ptr boxed_null = {NULL};
+
+void test_1 (void)
+{
+  boxed_ptr ptr;
+  ptr.value = malloc (1024);
+  free (ptr.value);
+  free (ptr.value); /* { dg-warning "double-'free' of 'ptr.value'" } */
+}
+
+void test_2 (boxed_ptr ptr)
+{
+  free (ptr.value);
+  free (ptr.value); /* { dg-warning "double-'free' of 'ptr.value'" } */
+}
+
+boxed_ptr
+test_3 (void)
+{
+  boxed_ptr ptr;
+  ptr.value = malloc (sizeof (int));
+  *(int *)ptr.value = 42; /* { dg-warning "dereference of possibly-NULL 'ptr.value' \\\[CWE-690\\\]" } */
+  return ptr;
+}
+
+boxed_ptr
+test_4 (void)
+{
+  boxed_ptr ptr;
+  ptr.value = malloc (sizeof (int));
+  int *iptr = (int *)ptr.value;
+  if (iptr)
+    *iptr = 42;
+  else
+    *iptr = 43; /* { dg-warning "dereference of NULL 'iptr' \\\[CWE-476\\\]" } */
+  return ptr;
+}
+
+int test_5 (boxed_ptr ptr)
+{
+  free (ptr.value);
+  return *(int *)ptr.value; /* { dg-warning "use after 'free' of 'ptr.value'" } */
+}
+
+void test_6 (void *ptr)
+{
+  boxed_ptr q;
+  q.value = ptr;
+  free (ptr);
+  free (q.value); /* { dg-warning "double-'free' of 'ptr'" } */
+}
+
+void test_6a (boxed_ptr ptr)
+{
+  boxed_ptr q;
+  q = ptr;
+  boxed_free (ptr);
+  free (q.value); /* { dg-warning "double-'free' of 'ptr.value'" } */
+}
+
+void test_7 (void)
+{
+  boxed_ptr ptr = boxed_malloc(4096);
+  if (!ptr.value)
+    return;
+  __builtin_memset(ptr.value, 0, 4096);
+  boxed_free(ptr);
+}
+
+boxed_ptr test_8 (void)
+{
+  boxed_ptr ptr = boxed_malloc(4096);
+  if (!ptr.value)
+    return boxed_null;
+  __builtin_memset(ptr.value, 0, 4096);
+  return ptr;
+}
+
+void test_9 (void)
+{
+  boxed_ptr ptr = boxed_malloc (1024);
+
+  int i;
+  for (i = 0; i < 1024; i++)
+    free (ptr.value); /* { dg-warning "double-'free' of 'ptr.value'" } */
+}
+
+void test_10 (void)
+{
+  boxed_ptr ptr = boxed_malloc (1024);
+
+  int i;
+  for (i = 0; i < 1024; i++)
+    foo ();
+
+  free (ptr.value);
+  free (ptr.value); /* { dg-warning "double-'free' of 'ptr.value'" } */ 
+}
+
+void test_11 (void)
+{
+  boxed_ptr ptr = boxed_malloc (1024);
+
+  while (foo ())
+    bar ();
+
+  free (ptr.value);
+  free (ptr.value); /* { dg-warning "double-'free' of 'ptr.value'" } */
+}
+
+void test_12 (void)
+{
+  boxed_ptr ptr = boxed_malloc (1024);
+
+  while (1)
+    {
+      free (ptr.value);
+      free (ptr.value); /* { dg-warning "double-'free' of 'ptr.value'" } */
+    }
+}
+
+void test_13 (void)
+{
+  boxed_ptr p = boxed_malloc (1024);
+  boxed_ptr q = boxed_malloc (1024);
+
+  foo ();
+  if (!q.value)
+    {
+      boxed_free (q);
+      return; /* { dg-warning "leak of 'p.value'" } */ 
+    }
+  bar ();
+  boxed_free (q);
+  boxed_free (p);
+}
+
+void test_14 (void)
+{
+  boxed_ptr p, q;
+  p = boxed_malloc (1024);
+  if (!p.value)
+    return;
+
+  q = boxed_malloc (1024);
+  if (!q.value)
+    {
+      boxed_free (p);
+      boxed_free (q);
+      /* oops: missing "return".  */
+    }
+  bar ();
+  boxed_free (q); /* Although this looks like a double-'free' of q,
+	       it's known to be NULL for the case where free is
+	       called twice on it.  */
+  free (p.value); /* { dg-warning "double-'free' of 'p.value'" } */
+}
+
+void test_15 (void)
+{
+  boxed_ptr p, q;
+  p.value = NULL;
+  q.value = NULL;
+
+  p = boxed_malloc (1024);
+  if (!p.value)
+    goto fail;
+
+  foo ();
+
+  q = boxed_malloc (1024);
+  if (!q.value)
+    goto fail;
+
+  bar ();
+
+ fail:
+  boxed_free (q);
+  boxed_free (p);
+}
+
+void test_16 (void)
+{
+  boxed_ptr p, q; /* { dg-message "region created on stack here" } */
+
+  p = boxed_malloc (1024);
+  if (!p.value)
+    goto fail;
+
+  foo ();
+
+  q = boxed_malloc (1024);
+  if (!q.value)
+    goto fail;
+
+  bar ();
+
+ fail:
+  boxed_free (q); /* { dg-warning "use of uninitialized value 'q'" } */
+  boxed_free (p);
+}
+
+void test_17 (void)
+{
+  boxed_ptr ptr = boxed_malloc (1024);
+} /* { dg-warning "leak of 'ptr.value'" } */ 
+
+void test_18 (void)
+{
+  boxed_ptr ptr = boxed_malloc (64);
+  ptr = boxed_null; /* { dg-warning "leak of 'ptr.value'" } */ 
+}
+
+void test_18a (void)
+{
+  boxed_ptr ptr = boxed_malloc (64);
+  ptr.value = NULL; /* { dg-warning "leak of 'ptr.value'" } */ 
+}
+
+void test_19 (void)
+{
+  boxed_ptr ptr = boxed_malloc (64);
+  free (ptr.value);
+  ptr.value = NULL;
+  free (ptr.value);
+}
+
+boxed_ptr global_ptr_20;
+
+void test_20 (void)
+{
+  global_ptr_20 = boxed_malloc (1024);
+}
+
+int *test_21 (int i)
+{
+  boxed_ptr ptr = boxed_malloc (sizeof (int));
+  if (!ptr.value)
+    abort ();
+  *(int *)ptr.value = i;
+  return ptr.value;
+}
+
+boxed_ptr test_21a (int i)
+{
+  boxed_ptr ptr = boxed_malloc (sizeof (int));
+  if (!ptr.value)
+    abort ();
+  *(int *)ptr.value = i;
+  return ptr;
+}
+
+void test_22 (void)
+{
+  boxed_ptr ptr = boxed_malloc (1024);
+
+  int i;
+  for (i = 5; i < 10; i++)
+    foo ();
+
+  free (ptr.value);
+  free (ptr.value); /* { dg-warning "double-'free' of 'ptr.value'" } */ 
+}
+
+int test_24 (void)
+{
+  boxed_ptr ptr;
+  ptr.value = __builtin_alloca (sizeof (int)); /* { dg-message "region created on stack here" } */
+  free (ptr.value); /* { dg-warning "'free' of 'ptr.value' which points to memory on the stack \\\[CWE-590\\\]" } */
+}
+
+int test_25 (void)
+{
+  char tmp[100]; /* { dg-message "region created on stack here" } */
+  boxed_ptr p;
+  p.value = tmp;
+  free (p.value); /* { dg-warning "'free' of '&tmp' which points to memory on the stack \\\[CWE-590\\\]" } */
+}
+
+char global_buffer[100]; /* { dg-message "region created here" } */
+
+int test_26 (void)
+{
+  boxed_ptr p;
+  p.value = global_buffer;
+  free (p.value); /* { dg-warning "'free' of '&global_buffer' which points to memory not on the heap \\\[CWE-590\\\]" } */
+}
+
+struct coord {
+  float x;
+  float y;
+};
+
+boxed_ptr test_27 (void)
+{
+  boxed_ptr p = boxed_malloc (sizeof (struct coord));
+  ((struct coord *)p.value)->x = 0.f;  /* { dg-warning "dereference of possibly-NULL 'p.value' \\\[CWE-690\\\]" } */
+
+  /* Only the first such usage should be reported: */
+  ((struct coord *)p.value)->y = 0.f;
+
+  return p;
+}
+
+struct link
+{
+  boxed_ptr m_ptr;
+};
+
+void test_31 (void)
+{
+  struct link tmp;
+  boxed_ptr ptr = boxed_malloc (sizeof (struct link));
+  tmp.m_ptr = ptr;
+} /* { dg-warning "leak" } */ 
+
+void test_32 (void)
+{
+  boxed_ptr ptr = boxed_malloc (1024);
+  could_free (ptr.value);
+} /* { dg-bogus "leak" } */
+
+void test_33 (void)
+{
+  boxed_ptr ptr = boxed_malloc (1024);
+  cant_free (ptr.value);
+} /* { dg-warning "leak of 'ptr.value'" } */ 
+
+void test_34 (void)
+{
+  float *q;
+  boxed_ptr p = boxed_malloc (sizeof (struct coord));
+  if (!p.value)
+    return;
+  ((struct coord *)p.value)->x = 0.0f;
+  q = &((struct coord *)p.value)->x;
+  boxed_free (p);
+  *q = 1.0f; /* { dg-warning "use after 'free' of 'q'" } */
+};
+
+int test_35 (void)
+{
+  boxed_ptr ptr = boxed_malloc(4096);
+  if (!ptr.value)
+    return -1;
+  __builtin_memset(ptr.value, 0, 4096);
+  boxed_free(ptr);
+  return 0;
+}
+
+void test_36 (void)
+{
+  boxed_ptr ptr = boxed_malloc(4096);
+  if (!ptr.value)
+    return;
+  __builtin_memset(ptr.value, 0, 4096);
+  boxed_free(ptr);
+}
+
+boxed_ptr test_37a (void)
+{
+  boxed_ptr ptr = boxed_malloc(4096);
+  __builtin_memset(ptr.value, 0, 4096); /* { dg-warning "use of possibly-NULL 'ptr.value' where non-null expected \\\[CWE-690\\\]" } */
+  return ptr;
+}
+
+int test_37b (void)
+{
+  boxed_ptr p = boxed_malloc(4096);
+  boxed_ptr q = boxed_malloc(4096);
+  if (p.value) {
+    __builtin_memset(p.value, 0, 4096); /* Not a bug: checked */
+  } else {
+    __builtin_memset(q.value, 0, 4096); /* { dg-warning "use of possibly-NULL 'q.value' where non-null expected \\\[CWE-690\\\]" } */
+  }
+  boxed_free(p);
+  boxed_free(q);
+  return 0;
+}
+
+extern void might_use_ptr (void *ptr);
+
+void test_38(int i)
+{
+  boxed_ptr p;
+
+  p = boxed_malloc(1024);
+  if (p.value) {
+    boxed_free(p);
+    might_use_ptr(p.value); /* { dg-warning "use after 'free' of 'p.value'" "" { xfail *-*-* } } */
+    // TODO: xfail
+  }
+}
+
+boxed_ptr
+test_39 (int i)
+{
+  boxed_ptr p = boxed_malloc(sizeof(int*));
+  *(int *)p.value = i; /* { dg-warning "dereference of possibly-NULL 'p.value' \\\[CWE-690\\\]" } */
+  return p;
+}
+
+boxed_ptr
+test_41 (int flag)
+{
+  boxed_ptr buffer;
+
+  if (flag) {
+    buffer = boxed_malloc(4096);
+  } else {
+    buffer = boxed_null;
+  }
+
+  ((char *)buffer.value)[0] = 'a'; /* { dg-warning "dereference of possibly-NULL 'buffer.value' \\\[CWE-690\\\]" "possibly-NULL" } */
+  /* { dg-warning "dereference of NULL" "NULL" { target *-*-* } .-1 } */
+
+  return buffer;
+}
+
+extern void might_take_ownership (boxed_ptr ptr);
+
+void test_45 (void)
+{
+  boxed_ptr p = boxed_malloc (1024);
+  might_take_ownership (p);
+}
+
+/* Free of function, and of label within function.  */
+
+void test_50a (void)
+{
+}
+
+void test_50b (void)
+{
+  boxed_ptr ptr;
+  ptr.value = test_50a;
+  free (ptr.value); /* { dg-warning "'free' of '&test_50a' which points to memory not on the heap \\\[CWE-590\\\]" } */
+}
+
+void test_50c (void)
+{
+ my_label:
+  boxed_ptr ptr;
+  ptr.value = &&my_label;
+  free (ptr.value); /* { dg-warning "'free' of '&my_label' which points to memory not on the heap \\\[CWE-590\\\]" } */
+}
+
+/* { dg-prune-output "\\\[-Wfree-nonheap-object" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/taint-alloc-5.c b/gcc/testsuite/gcc.dg/analyzer/taint-alloc-5.c
new file mode 100644
index 00000000000..9a159800c61
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/taint-alloc-5.c
@@ -0,0 +1,21 @@ 
+// TODO: remove need for this option
+/* { dg-additional-options "-fanalyzer-checker=taint" } */
+
+#include "analyzer-decls.h"
+
+struct foo
+{
+  int num;
+};
+
+/* malloc with tainted size from a field.  */
+
+void * __attribute__ ((tainted_args))
+test_1 (struct foo f)
+{
+  __analyzer_dump_state ("taint", f.num); /* { dg-warning "state: 'tainted'" } */
+  __analyzer_dump_state ("taint", f.num * 16); /* { dg-warning "state: 'tainted'" } */
+
+  return __builtin_malloc (f.num * 16); /* { dg-warning "use of attacker-controlled value 'f\\.num \\* 16' as allocation size without upper-bounds checking" "warning" } */
+  /* { dg-message "\\(\[0-9\]+\\) use of attacker-controlled value 'f\\.num \\* 16' as allocation size without upper-bounds checking" "final event with expr" { target *-*-* } .-1 } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/torture/boxed-int-1.c b/gcc/testsuite/gcc.dg/analyzer/torture/boxed-int-1.c
new file mode 100644
index 00000000000..94111e66a8b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/torture/boxed-int-1.c
@@ -0,0 +1,170 @@ 
+/* { dg-skip-if "" { *-*-* } { "-fno-fat-lto-objects" } { "" } } */
+
+#include "../analyzer-decls.h"
+
+typedef struct boxed_int { int value; } boxed_int;
+
+extern boxed_int boxed_int_add (boxed_int a, boxed_int b);
+extern boxed_int boxed_int_mul (boxed_int a, boxed_int b);
+
+boxed_int  __attribute__((noinline))
+noinline_boxed_int_add (boxed_int a, boxed_int b)
+{
+  boxed_int result;
+  result.value = a.value + b.value;
+  return result;
+}
+
+static inline boxed_int
+inline_boxed_int_add (boxed_int a, boxed_int b)
+{
+  boxed_int result;
+  result.value = a.value + b.value;
+  return result;
+}
+
+boxed_int
+test_1 (boxed_int a, boxed_int b)
+{
+  boxed_int result = boxed_int_add (boxed_int_mul (a, a),
+				    boxed_int_mul (b, b));
+  return result;
+}
+
+void
+test_2a (void)
+{
+  boxed_int arr[4];
+  arr[0].value = 1;
+  arr[1].value = 2;
+  arr[2].value = 3;
+  arr[3].value = 4;
+  boxed_int sum;
+  sum.value = arr[0].value + arr[1].value + arr[2].value + arr[3].value;
+  __analyzer_eval (sum.value == 10); /* { dg-warning "TRUE" } */
+}
+
+void
+test_2b (void)
+{
+  boxed_int a, b, c, d;
+  a.value = 1;
+  b.value = 2;
+  c.value = 3;
+  d.value = 4;
+  boxed_int sum;
+  sum.value = a.value + b.value + c.value + d.value;
+  __analyzer_eval (sum.value == 10); /* { dg-warning "TRUE" } */
+}
+
+void
+test_2c (void)
+{
+  boxed_int a, b, c, d;
+  a.value = 1;
+  b.value = 2;
+  c.value = 3;
+  d.value = 4;
+  boxed_int sum = inline_boxed_int_add (inline_boxed_int_add (a, b),
+					inline_boxed_int_add (c, d));
+  __analyzer_eval (sum.value == 10); /* { dg-warning "TRUE" } */
+}
+
+void
+test_2d (void)
+{
+  boxed_int a, b, c, d;
+  a.value = 1;
+  b.value = 2;
+  c.value = 3;
+  d.value = 4;
+  boxed_int sum = noinline_boxed_int_add (noinline_boxed_int_add (a, b),
+					  noinline_boxed_int_add (c, d));
+  __analyzer_eval (sum.value == 10); /* { dg-warning "TRUE" } */
+}
+
+/* Pointer to a local.  */
+
+void test_4 (void)
+{
+  boxed_int i;
+  int *p = &i.value;
+  i.value = 1;
+  *p = 2;
+  __analyzer_eval (i.value == 2); /* { dg-warning "TRUE" } */
+}
+
+/* Local array.  */
+
+void test_5 (void)
+{
+  boxed_int a[10];
+  a[3].value = 5; /* ARRAY_REF.  */
+  __analyzer_eval (a[3].value == 5); /* { dg-warning "TRUE" } */
+}
+
+/* Local array, but using an unknown index.  */
+
+void test_5a (int idx)
+{
+  boxed_int a[10];
+  a[idx].value = 5; /* ARRAY_REF.  */
+  __analyzer_eval (a[idx].value == 5); /* { dg-warning "TRUE" } */
+}
+
+/* Array passed in as a param.  */
+
+void test_6 (boxed_int a[10])
+{
+  /* POINTER_PLUS_EXPR then a MEM_REF.  */
+  __analyzer_eval (a[3].value == 42); /* { dg-warning "UNKNOWN" } */
+  a[3].value = 42;
+  __analyzer_eval (a[3].value == 42); /* { dg-warning "TRUE" } */
+}
+
+/* Array passed in as a param ptr.  */
+
+void test_7 (boxed_int *a)
+{
+  __analyzer_eval (a[3].value == 42); /* { dg-warning "UNKNOWN" } */
+  a[3].value = 42;
+  __analyzer_eval (a[3].value == 42); /* { dg-warning "TRUE" } */
+}
+
+/* Globals.  */
+
+boxed_int glob_a;
+
+void test_10 (void)
+{
+  __analyzer_eval (glob_a.value == 42); /* { dg-warning "UNKNOWN" } */
+  glob_a.value = 42;
+  __analyzer_eval (glob_a.value == 42); /* { dg-warning "TRUE" } */
+}
+
+/* Use of uninit value.  */
+int test_12a (void)
+{
+  boxed_int i; /* { dg-message "region created on stack here" } */
+  return i.value; /* { dg-warning "use of uninitialized value 'i.value'" } */
+}
+
+/* Use of uninit value.  */
+boxed_int test_12b (void)
+{
+  boxed_int i; /* { dg-message "region created on stack here" } */
+  return i; /* { dg-warning "use of uninitialized value '\[^\n\r\]*'" } */
+}
+
+void test_loop (void)
+{
+  boxed_int i;
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+
+  for (i.value=0; i.value<256; i.value++) {
+      __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 processed enodes" } */
+  }
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/torture/boxed-ptr-1.c b/gcc/testsuite/gcc.dg/analyzer/torture/boxed-ptr-1.c
new file mode 100644
index 00000000000..8db93f109c7
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/torture/boxed-ptr-1.c
@@ -0,0 +1,82 @@ 
+/* { dg-skip-if "" { *-*-* } { "-fno-fat-lto-objects" } { "" } } */
+
+#include <stdlib.h>
+#include "../analyzer-decls.h"
+
+typedef struct boxed_ptr { void *value; } boxed_ptr;
+
+boxed_ptr __attribute__((noinline))
+boxed_malloc (size_t sz)
+{
+  boxed_ptr result;
+  result.value = malloc (sz);
+  return result;
+}
+
+boxed_ptr __attribute__((noinline))
+boxed_free (boxed_ptr ptr)
+{
+  free (ptr.value);
+}
+
+const boxed_ptr boxed_null = {NULL};
+
+boxed_ptr test_1 (int flag)
+{
+  boxed_ptr ptr = boxed_malloc (sizeof (int));
+
+  if (flag) /* { dg-message "following 'false' branch" } */
+    if (!ptr.value)
+      return boxed_null;
+
+  *((int *)ptr.value) = 42; /* { dg-warning "dereference of possibly-NULL '\[^\n\r\]*'" } */
+
+  return ptr;
+}
+
+void test_2 (int flag)
+{
+  boxed_ptr ptr;
+
+  if (flag)
+    ptr = boxed_malloc (4096);
+  else
+    ptr = boxed_malloc (1024);
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 processed enodes" } */
+
+  boxed_free (ptr);  
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "2 processed enodes" } */
+  /* TODO: ideally we would have purged the state of "ptr", and there would be
+     just 1 enode here (PR analyzer/104943).  */
+}
+
+void test_3 (int kind)
+{
+  boxed_ptr ptr;
+
+  switch (kind)
+    {
+    default:
+      ptr = boxed_malloc (4096);
+      break;
+    case 0:
+      ptr = boxed_malloc (128);
+      break;
+    case 1:
+      ptr = boxed_malloc (1024);
+      break;
+    case 2:
+      ptr = boxed_malloc (65536);
+      break;
+    }
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "4 processed enodes" } */
+
+  boxed_free (ptr);  
+
+  __analyzer_dump_exploded_nodes (0); /* { dg-warning "4 processed enodes" } */
+  /* TODO: ideally we would have purged the state of "ptr", and there would be
+     just 1 enode here (PR analyzer/104943).  */  
+}