aboutsummaryrefslogtreecommitdiff
path: root/gcc/testsuite
diff options
context:
space:
mode:
authorDavid Malcolm <dmalcolm@redhat.com>2022-10-04 20:19:07 -0400
committerDavid Malcolm <dmalcolm@redhat.com>2022-10-04 20:19:07 -0400
commitbfca9505f6fce631c2488f89aa156d56c7fae9ed (patch)
tree655ae3b5c555f4ff9d12b96cdaebafd0b4d02e38 /gcc/testsuite
parent0167154cdd02c9508239982ea7568a7a8cee080e (diff)
downloadgcc-bfca9505f6fce631c2488f89aa156d56c7fae9ed.zip
gcc-bfca9505f6fce631c2488f89aa156d56c7fae9ed.tar.gz
gcc-bfca9505f6fce631c2488f89aa156d56c7fae9ed.tar.bz2
analyzer: revamp side-effects of call summaries [PR107072]
With -fanalyzer-call-summaries the analyzer canl attempt to summarize the effects of some function calls at their call site, rather than simulate the call directly, which can avoid big slowdowns during analysis. Previously, this summarization was extremely simplistic: no attempt was made to update sm-state, and region_model::update_for_call_summary would simply set the return value of the function to UNKNOWN, and assume the function had no side effects. This patch implements less simplistic summarizations: it tracks each possible return enode from the called function, and attempts to generate a successor enode from the callsite for each that have compatible conditions, mapping state changes in the summary to state changes at the callsite. It also implements the beginnings of heuristics for generating user-facing descriptions of a summary e.g. "when 'foo' returns NULL" versus: "when 'foo' returns a heap-allocated buffer" This still has some bugs, but much more accurately tracks the effects of a call, and so is an improvement; it should only have an effect when -fanalyzer-call-summaries is enabled. As before, -fanalyzer-call-summaries is disabled by default in analyzer.opt (but enabled by default in the test suite). gcc/ChangeLog: PR analyzer/107072 * Makefile.in (ANALYZER_OBJS): Add analyzer/call-summary.o. gcc/analyzer/ChangeLog: PR analyzer/107072 * analyzer-logging.h: Include "diagnostic-core.h". * analyzer.h: Include "function.h". (class call_summary): New forward decl. (class call_summary_replay): New forward decl. (struct per_function_data): New forward decl. (struct interesting_t): New forward decl. (custom_edge_info::update_state): New vfunc. * call-info.cc (custom_edge_info::update_state): New. * call-summary.cc: New file. * call-summary.h: New file. * constraint-manager.cc: Include "analyzer/call-summary.h". (class replay_fact_visitor): New. (constraint_manager::replay_call_summary): New. * constraint-manager.h (constraint_manager::replay_call_summary): New. * engine.cc: Include "analyzer/call-summary.h". (exploded_node::on_stmt): Handle call summaries. (class call_summary_edge_info): New. (exploded_node::replay_call_summaries): New. (exploded_node::replay_call_summary): New. (per_function_data::~per_function_data): New. (per_function_data::add_call_summary): Move here from header and reimplement. (exploded_graph::process_node): Call update_state rather than update_model when handling bifurcation (viz_callgraph_node::dump_dot): Use a regular label rather than an HTML table; add summaries to dump. * exploded-graph.h: Include "alloc-pool.h", "fibonacci_heap.h", "supergraph.h", "sbitmap.h", "shortest-paths.h", "analyzer/sm.h", "analyzer/program-state.h", and "analyzer/diagnostic-manager.h". (exploded_node::replay_call_summaries): New decl. (exploded_node::replay_call_summary): New decl. (per_function_data::~per_function_data): New decl. (per_function_data::add_call_summary): Move implemention from header. (per_function_data::m_summaries): Update type of element. * known-function-manager.h: Include "analyzer/analyzer-logging.h". * program-point.h: Include "pretty-print.h" and "analyzer/call-string.h". * program-state.cc: Include "analyzer/call-summary.h". (sm_state_map::replay_call_summary): New. (program_state::replay_call_summary): New. * program-state.h (sm_state_map::replay_call_summary): New decl. (program_state::replay_call_summary): New decl. * region-model-manager.cc (region_model_manager::get_or_create_asm_output_svalue): New overload. * region-model-manager.h (region_model_manager::get_or_create_asm_output_svalue): New overload decl. * region-model.cc: Include "analyzer/call-summary.h". (region_model::maybe_update_for_edge): Remove call to region_model::update_for_call_summary on SUPEREDGE_INTRAPROCEDURAL_CALL. (region_model::update_for_call_summary): Delete. (region_model::replay_call_summary): New. * region-model.h (region_model::replay_call_summary): New decl. (region_model::update_for_call_summary): Delete decl. * store.cc: Include "analyzer/call-summary.h". (store::replay_call_summary): New. (store::replay_call_summary_cluster): New. * store.h: Include "tristate.h". (is_a_helper <const ana::concrete_binding *>::test): New. (store::replay_call_summary): New decl. (store::replay_call_summary_cluster): New decl. * supergraph.cc (get_ultimate_function_for_cgraph_edge): Remove "static" from decl. (supergraph_call_edge): Make stmt param const. * supergraph.h: Include "ordered-hash-map.h", "cfg.h", "basic-block.h", "gimple.h", "gimple-iterator.h", and "digraph.h". (supergraph_call_edge): Make stmt param const. (get_ultimate_function_for_cgraph_edge): New decl. * svalue.cc (compound_svalue::compound_svalue): Assert that we're not nesting compound_svalues. * svalue.h: Include "json.h", "analyzer/store.h", and "analyzer/program-point.h". (asm_output_svalue::get_num_outputs): New accessor. gcc/testsuite/ChangeLog: PR analyzer/107072 * gcc.dg/analyzer/call-summaries-2.c: New test. * gcc.dg/analyzer/call-summaries-3.c: New test. * gcc.dg/analyzer/call-summaries-asm-x86.c: New test. * gcc.dg/analyzer/call-summaries-malloc.c: New test. * gcc.dg/analyzer/call-summaries-pr107072.c: New test. Signed-off-by: David Malcolm <dmalcolm@redhat.com>
Diffstat (limited to 'gcc/testsuite')
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/call-summaries-2.c646
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/call-summaries-3.c29
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/call-summaries-asm-x86.c20
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/call-summaries-malloc.c80
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/call-summaries-pr107072.c90
5 files changed, 865 insertions, 0 deletions
diff --git a/gcc/testsuite/gcc.dg/analyzer/call-summaries-2.c b/gcc/testsuite/gcc.dg/analyzer/call-summaries-2.c
new file mode 100644
index 0000000..0aaf67b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/call-summaries-2.c
@@ -0,0 +1,646 @@
+/* { dg-additional-options "-fanalyzer-call-summaries --param analyzer-min-snodes-for-call-summary=0" } */
+
+/* There need to be at least two calls to a function for the
+ call-summarization code to be used.
+ TODO: add some kind of test that summarization *was* used. */
+
+#include <stdlib.h>
+#include "analyzer-decls.h"
+
+extern int external_fn (void *);
+
+int returns_const (void)
+{
+ return 42;
+}
+
+void test_summarized_returns_const (void)
+{
+ __analyzer_eval (returns_const () == 42); /* { dg-warning "TRUE" } */
+ __analyzer_eval (returns_const () == 42); /* { dg-warning "TRUE" } */
+}
+
+void test_summarized_returns_const_2 (void)
+{
+ returns_const (); /* { dg-message "when 'returns_const' returns" } */
+ __analyzer_dump_path (); /* { dg-message "path" } */
+}
+
+int returns_param (int i)
+{
+ return i;
+}
+
+void test_summarized_returns_param (int j)
+{
+ __analyzer_eval (returns_param (j) == j); /* { dg-warning "TRUE" } */
+ __analyzer_eval (returns_param (j) == j); /* { dg-warning "TRUE" } */
+}
+
+void writes_const_to_ptr (int *p)
+{
+ *p = 42;
+}
+
+void test_summarized_writes_const_to_ptr (void)
+{
+ int i, j;
+ writes_const_to_ptr (&i);
+ writes_const_to_ptr (&j);
+ __analyzer_eval (i == 42); /* { dg-warning "TRUE" } */
+ __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */
+}
+
+// TODO: we should complain about this:
+
+void test_summarized_write_through_null (void)
+{
+ writes_const_to_ptr (NULL);
+}
+
+void writes_param_to_ptr (int i, int *p)
+{
+ *p = i;
+}
+
+void test_summarized_writes_param_to_ptr (int j)
+{
+ int x, y;
+ writes_param_to_ptr (j, &x);
+ writes_param_to_ptr (j, &y);
+ __analyzer_eval (x == j); /* { dg-warning "TRUE" } */
+ __analyzer_eval (y == j); /* { dg-warning "TRUE" } */
+}
+
+int g;
+
+void writes_to_global (int i)
+{
+ g = i;
+}
+
+void test_writes_to_global (int x, int y)
+{
+ writes_to_global (x);
+ __analyzer_eval (g == x); /* { dg-warning "TRUE" } */
+
+ writes_to_global (y);
+ __analyzer_eval (g == y); /* { dg-warning "TRUE" } */
+}
+
+int reads_from_global (void)
+{
+ return g;
+}
+
+void test_reads_from_global (int x, int y)
+{
+ g = x;
+ __analyzer_eval (reads_from_global () == x); /* { dg-warning "TRUE" } */
+
+ g = y;
+ __analyzer_eval (reads_from_global () == y); /* { dg-warning "TRUE" } */
+}
+
+/* Example of a unary op. */
+
+int returns_negation (int i)
+{
+ return -i;
+}
+
+void test_returns_negation (int x)
+{
+ __analyzer_eval (returns_negation (5) == -5); /* { dg-warning "TRUE" } */
+ __analyzer_eval (returns_negation (x) == -x); /* { dg-warning "TRUE" } */
+ __analyzer_eval (returns_negation (-x) == x); /* { dg-warning "TRUE" } */
+}
+
+/* Example of a binary op. */
+
+int returns_sum (int i, int j)
+{
+ return i + j;
+}
+
+void test_returns_sum (int x, int y)
+{
+ __analyzer_eval (returns_sum (5, 3) == 8); /* { dg-warning "TRUE" } */
+ __analyzer_eval (returns_sum (7, 2) == 9); /* { dg-warning "TRUE" } */
+ __analyzer_eval (returns_sum (x, y) == x + y); /* { dg-warning "TRUE" } */
+}
+
+struct coord
+{
+ int x;
+ int y;
+};
+
+struct coord make_coord (int x, int y)
+{
+ struct coord result = {x, y};
+ return result;
+}
+
+void test_make_coord (int i, int j)
+{
+ struct coord c1 = make_coord (3, 4);
+ __analyzer_eval (c1.x == 3); /* { dg-warning "TRUE" } */
+ __analyzer_eval (c1.y == 4); /* { dg-warning "TRUE" } */
+
+ struct coord c2 = make_coord (i, j);
+ __analyzer_eval (c2.x == i); /* { dg-warning "TRUE" } */
+ __analyzer_eval (c2.y == j); /* { dg-warning "TRUE" } */
+}
+
+/* Example of nested usage of summaries. */
+
+struct rect
+{
+ struct coord nw;
+ struct coord se;
+};
+
+struct rect make_rect (int top, int bottom, int left, int right)
+{
+ struct rect result = {make_coord (left, top),
+ make_coord (right, bottom)};
+ return result;
+}
+
+void test_make_rect (int top, int bottom, int left, int right)
+{
+ struct rect r1 = make_rect (3, 4, 5, 6);
+ __analyzer_eval (r1.nw.y == 3); /* { dg-warning "TRUE" } */
+ __analyzer_eval (r1.se.y == 4); /* { dg-warning "TRUE" } */
+ __analyzer_eval (r1.nw.x == 5); /* { dg-warning "TRUE" } */
+ __analyzer_eval (r1.se.x == 6); /* { dg-warning "TRUE" } */
+
+ struct rect r2 = make_rect (top, bottom, left, right);
+ __analyzer_eval (r2.nw.y == top); /* { dg-warning "TRUE" } */
+ __analyzer_eval (r2.se.y == bottom); /* { dg-warning "TRUE" } */
+ __analyzer_eval (r2.nw.x == left); /* { dg-warning "TRUE" } */
+ __analyzer_eval (r2.se.x == right); /* { dg-warning "TRUE" } */
+}
+
+const char *returns_str (void)
+{
+ return "abc";
+}
+
+void test_returns_str (void)
+{
+ __analyzer_eval (returns_str () != NULL); /* { dg-warning "TRUE" } */
+ __analyzer_eval (returns_str ()[0] == 'a'); /* { dg-warning "TRUE" } */
+ __analyzer_eval (returns_str ()[1] == 'b'); /* { dg-warning "TRUE" } */
+ __analyzer_eval (returns_str ()[2] == 'c'); /* { dg-warning "TRUE" } */
+ __analyzer_eval (returns_str ()[3] == '\0'); /* { dg-warning "TRUE" } */
+}
+
+int returns_field (struct coord *p)
+{
+ return p->y;
+}
+
+void test_returns_field (struct coord *q)
+{
+ __analyzer_eval (returns_field (q) == q->y); /* { dg-warning "TRUE" } */
+ __analyzer_eval (returns_field (q) == q->y); /* { dg-warning "TRUE" } */
+}
+
+void writes_to_fields (struct coord *p, int x, int y)
+{
+ p->x = x;
+ p->y = y;
+}
+
+void test_writes_to_field (struct coord *q, int qx, int qy)
+{
+ struct coord a, b;
+ writes_to_fields (&a, 1, 2);
+ __analyzer_eval (a.x == 1); /* { dg-warning "TRUE" } */
+ __analyzer_eval (a.y == 2); /* { dg-warning "TRUE" } */
+ writes_to_fields (&b, 3, 4);
+ __analyzer_eval (b.x == 3); /* { dg-warning "TRUE" } */
+ __analyzer_eval (b.y == 4); /* { dg-warning "TRUE" } */
+ writes_to_fields (q, qx, qy);
+ __analyzer_eval (q->x == qx); /* { dg-warning "TRUE" } */
+ __analyzer_eval (q->y == qy); /* { dg-warning "TRUE" } */
+}
+
+/* Example of nested function summarization. */
+
+int get_min_x (struct rect *p)
+{
+ return p->nw.x;
+}
+
+int get_min_y (struct rect *p)
+{
+ return p->nw.y;
+}
+
+int get_max_x (struct rect *p)
+{
+ return p->se.x;
+}
+
+int get_max_y (struct rect *p)
+{
+ return p->se.y;
+}
+
+int get_area (struct rect *p)
+{
+ return ((get_max_x (p) - get_min_x (p))
+ * (get_max_y (p) - get_min_y (p)));
+}
+
+void test_get_area (int top, int bottom, int left, int right, struct rect *p)
+{
+ {
+ /* 1x1 at origin. */
+ struct rect a = make_rect (0, 1, 0, 1);
+ __analyzer_eval (get_min_y (&a) == 0); /* { dg-warning "TRUE" } */
+ __analyzer_eval (get_max_y (&a) == 1); /* { dg-warning "TRUE" } */
+ __analyzer_eval (get_min_x (&a) == 0); /* { dg-warning "TRUE" } */
+ __analyzer_eval (get_max_x (&a) == 1); /* { dg-warning "TRUE" } */
+ __analyzer_eval (get_area (&a) == 1); /* { dg-warning "TRUE" } */
+ }
+
+ {
+ /* 4x5. */
+ struct rect b = make_rect (3, 7, 4, 9);
+ __analyzer_eval (get_min_y (&b) == 3); /* { dg-warning "TRUE" } */
+ __analyzer_eval (get_max_y (&b) == 7); /* { dg-warning "TRUE" } */
+ __analyzer_eval (get_min_x (&b) == 4); /* { dg-warning "TRUE" } */
+ __analyzer_eval (get_max_x (&b) == 9); /* { dg-warning "TRUE" } */
+ __analyzer_eval (get_area (&b) == 20); /* { dg-warning "TRUE" } */
+ }
+
+ {
+ /* Symbolic. */
+ struct rect c = make_rect (top, bottom, left, right);
+ __analyzer_eval (get_min_y (&c) == top); /* { dg-warning "TRUE" } */
+ __analyzer_eval (get_max_y (&c) == bottom); /* { dg-warning "TRUE" } */
+ __analyzer_eval (get_min_x (&c) == left); /* { dg-warning "TRUE" } */
+ __analyzer_eval (get_max_x (&c) == right); /* { dg-warning "TRUE" } */
+ __analyzer_eval (get_area (&c) == ((right - left) * (bottom - top))); /* { dg-warning "TRUE" } */
+ }
+
+ /* Symbolic via ptr. */
+ __analyzer_eval (get_min_y (p) == p->nw.y); /* { dg-warning "TRUE" } */
+ __analyzer_eval (get_max_y (p) == p->se.y); /* { dg-warning "TRUE" } */
+ __analyzer_eval (get_min_x (p) == p->nw.x); /* { dg-warning "TRUE" } */
+ __analyzer_eval (get_max_x (p) == p->se.x); /* { dg-warning "TRUE" } */
+ __analyzer_eval (get_area (p) == ((p->se.x - p->nw.x) * (p->se.y - p->nw.y))); /* { dg-warning "TRUE" } */
+}
+
+int returns_element (int i)
+{
+ static const int arr[3] = {7, 8, 9};
+ return arr[i];
+}
+
+void test_returns_element (int j)
+{
+ __analyzer_eval (returns_element (0) == 7); /* { dg-warning "UNKNOWN" } */
+ __analyzer_eval (returns_element (1) == 8); /* { dg-warning "UNKNOWN" } */
+ __analyzer_eval (returns_element (2) == 9); /* { dg-warning "UNKNOWN" } */
+ __analyzer_eval (returns_element (3) == 10); /* { dg-warning "UNKNOWN" } */
+ // TODO: out of bounds
+}
+
+const int *returns_element_ptr (int i)
+{
+ static const int arr[3] = {7, 8, 9};
+ return &arr[i];
+}
+
+int test_returns_element_ptr (int j)
+{
+ __analyzer_eval (*returns_element_ptr (0) == 7); /* { dg-warning "TRUE" } */
+ __analyzer_eval (*returns_element_ptr (1) == 8); /* { dg-warning "TRUE" } */
+ __analyzer_eval (*returns_element_ptr (2) == 9); /* { dg-warning "TRUE" } */
+ return *returns_element_ptr (3); /* { dg-warning "buffer overread" } */
+}
+
+int returns_offset (int arr[3], int i)
+{
+ return arr[i];
+}
+
+void test_returns_offset (int outer_arr[3], int idx)
+{
+ int a[3] = {4, 5, 6};
+ __analyzer_eval (returns_offset (a, 0) == 4); /* { dg-warning "TRUE" } */
+ __analyzer_eval (returns_offset (a, 1) == 5); /* { dg-warning "TRUE" } */
+ __analyzer_eval (returns_offset (a, 2) == 6); /* { dg-warning "TRUE" } */
+ __analyzer_eval (returns_offset (a, idx) == a[idx]); /* { dg-warning "UNKNOWN" } */
+ __analyzer_eval (returns_offset (outer_arr, 0) == outer_arr[0]); /* { dg-warning "TRUE" } */
+ __analyzer_eval (returns_offset (outer_arr, idx) == outer_arr[idx]); /* { dg-warning "TRUE" } */
+}
+
+int returns_offset_2 (int arr[], int i)
+{
+ return arr[i];
+}
+
+void test_returns_offset_2 (int outer_arr[], int idx)
+{
+ int a[3] = {4, 5, 6};
+ __analyzer_eval (returns_offset_2 (a, 0) == 4); /* { dg-warning "TRUE" } */
+ __analyzer_eval (returns_offset_2 (a, 1) == 5); /* { dg-warning "TRUE" } */
+ __analyzer_eval (returns_offset_2 (a, 2) == 6); /* { dg-warning "TRUE" } */
+ __analyzer_eval (returns_offset_2 (a, idx) == a[idx]); /* { dg-warning "UNKNOWN" } */
+ __analyzer_eval (returns_offset_2 (outer_arr, 0) == outer_arr[0]); /* { dg-warning "TRUE" } */
+ __analyzer_eval (returns_offset_2 (outer_arr, idx) == outer_arr[idx]); /* { dg-warning "TRUE" } */
+}
+
+int returns_offset_3 (int *p, int i)
+{
+ return p[i];
+}
+
+void test_returns_offset_3 (int *q, int j)
+{
+ __analyzer_eval (returns_offset_3 (q, j) == q[j]); /* { dg-warning "TRUE" } */
+ __analyzer_eval (returns_offset_3 (q, j) == q[j]); /* { dg-warning "TRUE" } */
+}
+
+/* With state merging, this is summarized as returning "UNKNOWN". */
+
+int two_outcomes (int flag, int x, int y)
+{
+ if (flag)
+ return x;
+ else
+ return y;
+}
+
+void test_two_outcomes (int outer_flag, int a, int b)
+{
+ int r;
+ __analyzer_eval (two_outcomes (1, a, b) == a); /* { dg-warning "UNKNOWN" } */
+ __analyzer_eval (two_outcomes (0, a, b) == b); /* { dg-warning "UNKNOWN" } */
+ r = two_outcomes (outer_flag, a, b);
+ if (outer_flag)
+ __analyzer_eval (r == a); /* { dg-warning "UNKNOWN" } */
+ else
+ __analyzer_eval (r == b); /* { dg-warning "UNKNOWN" } */
+}
+
+/* Verify that summary replays capture postconditions. */
+
+void check_int_nonnegative (int i)
+{
+ if (i < 0)
+ __builtin_unreachable ();
+}
+
+void test_check_int_nonnegative (int i, int j)
+{
+ __analyzer_eval (i >= 0); /* { dg-warning "UNKNOWN" } */
+ check_int_nonnegative (i);
+ __analyzer_eval (i >= 0); /* { dg-warning "TRUE" } */
+
+ __analyzer_eval (j >= 0); /* { dg-warning "UNKNOWN" } */
+ check_int_nonnegative (j);
+ __analyzer_eval (j >= 0); /* { dg-warning "TRUE" } */
+}
+
+void calls_external_fn (void)
+{
+ external_fn (NULL);
+}
+
+void test_calls_external_fn (void)
+{
+ g = 1;
+ __analyzer_eval (g == 1); /* { dg-warning "TRUE" } */
+ calls_external_fn ();
+ calls_external_fn ();
+ __analyzer_eval (g == 1); /* { dg-warning "UNKNOWN" "expected" { xfail *-*-* } } */
+ /* { dg-bogus "TRUE" "bogus" { xfail *-*-* } .-1 } */
+ // TODO(xfails)
+}
+
+int returns_iterator (int n)
+{
+ int i;
+ for (i = 0; i < n; i++)
+ {
+ }
+ return i;
+}
+
+void test_returns_iterator (int j, int k)
+{
+ __analyzer_eval (returns_iterator (j) == j); /* { dg-warning "UNKNOWN" } */
+ __analyzer_eval (returns_iterator (k) == k); /* { dg-warning "UNKNOWN" } */
+ /* TODO: ideally we'd capture these equalities, but this is an issue
+ with how we handle loops. */
+}
+
+int returns_external_result (void)
+{
+ return external_fn (NULL);
+}
+
+int test_returns_external_result (void)
+{
+ int i, j;
+ i = returns_external_result ();
+ j = returns_external_result ();
+ __analyzer_eval (i == j); /* { dg-warning "UNKNOWN" } */
+ return i * j;
+}
+
+int uses_alloca (int i)
+{
+ int *p = alloca (sizeof (int));
+ *p = i;
+ return *p;
+}
+
+void test_uses_alloca (int x)
+{
+ __analyzer_eval (uses_alloca (42) == 42); /* { dg-warning "TRUE" } */
+ __analyzer_eval (uses_alloca (x) == x); /* { dg-warning "TRUE" } */
+}
+
+struct bits
+{
+ unsigned b0 : 1;
+ unsigned b1 : 1;
+ unsigned b2 : 1;
+ unsigned b3 : 1;
+ unsigned b4 : 1;
+ unsigned b5 : 1;
+ unsigned b6 : 1;
+ unsigned b7 : 1;
+};
+
+int returns_bitfield (struct bits b)
+{
+ return b.b3;
+}
+
+void test_returns_bitfield (struct bits s)
+{
+ __analyzer_eval (returns_bitfield (s) == s.b3); /* { dg-warning "UNKNOWN" } */
+ __analyzer_eval (returns_bitfield (s) == s.b3); /* { dg-warning "UNKNOWN" } */
+ // TODO: ideally it would figure out that these are equal
+}
+
+int consume_two_ints_from_va_list (__builtin_va_list ap)
+{
+ int i, j;
+ i = __builtin_va_arg (ap, int);
+ j = __builtin_va_arg (ap, int);
+ return i * j;
+}
+
+int test_consume_two_ints_from_va_list (__builtin_va_list ap1)
+{
+ int p1, p2;
+ __builtin_va_list ap2;
+ __builtin_va_copy (ap2, ap1);
+ p1 = consume_two_ints_from_va_list (ap1);
+ p2 = consume_two_ints_from_va_list (ap2);
+ __analyzer_eval (p1 == p2); /* { dg-warning "UNKNOWN" } */
+ // TODO: ideally it would figure out these are equal
+ __builtin_va_end (ap2);
+}
+
+int consume_two_ints_from_varargs (int placeholder, ...)
+{
+ int i, j;
+ __builtin_va_list ap;
+ __builtin_va_start (ap, placeholder);
+ i = __builtin_va_arg (ap, int);
+ j = __builtin_va_arg (ap, int);
+ __builtin_va_end (ap);
+ return i * j;
+}
+
+void test_consume_two_ints_from_varargs (int x, int y)
+{
+ __analyzer_eval (consume_two_ints_from_varargs (0, 4, 5) == 20); /* { dg-warning "UNKNOWN" } */
+ __analyzer_eval (consume_two_ints_from_varargs (0, 3, 6) == 18); /* { dg-warning "UNKNOWN" } */
+ __analyzer_eval (consume_two_ints_from_varargs (0, x, 6) == x * y); /* { dg-warning "UNKNOWN" } */
+ // TODO: ideally it would figure these out
+}
+
+extern int const_fn_1 (int) __attribute__ ((const));
+int calls_const_fn (int i)
+{
+ return const_fn_1 (i);
+}
+
+void test_calls_const_fn (int x)
+{
+ int r1, r2;
+ r1 = calls_const_fn (x);
+ r2 = calls_const_fn (x);
+ __analyzer_eval (r1 == r2); /* { dg-warning "TRUE" } */
+}
+
+typedef struct iv2 { int arr[2]; } iv2_t;
+typedef struct iv4 { int arr[4]; } iv4_t;
+
+iv2_t returns_iv2_t (int x, int y)
+{
+ iv2_t result = {x, y};
+ return result;
+}
+
+void test_returns_iv2_t (int a, int b)
+{
+ __analyzer_eval (returns_iv2_t (a, b).arr[0] == a); /* { dg-warning "TRUE" } */
+ __analyzer_eval (returns_iv2_t (a, b).arr[1] == b); /* { dg-warning "TRUE" } */
+}
+
+iv4_t returns_iv4_t (int a, iv2_t bc, int d)
+{
+ iv4_t result = {a, bc.arr[0], bc.arr[1], d};
+ return result;
+}
+
+void test_returns_iv4_t (int p, iv2_t qr, int s)
+{
+ __analyzer_eval (returns_iv4_t (p, qr, s).arr[0] == p); /* { dg-warning "TRUE" } */
+ __analyzer_eval (returns_iv4_t (p, qr, s).arr[1] == qr.arr[0]); /* { dg-warning "UNKNOWN" } */
+ __analyzer_eval (returns_iv4_t (p, qr, s).arr[2] == qr.arr[1]); /* { dg-warning "UNKNOWN" } */
+ __analyzer_eval (returns_iv4_t (p, qr, s).arr[3] == s); /* { dg-warning "TRUE" } */
+ // TODO: ideally the UNKNOWNs would be TRUEs.
+}
+
+void copies_iv2_t (int *p, iv2_t xy)
+{
+ __builtin_memcpy (p, &xy, sizeof (xy));
+}
+
+void test_copies_iv2_t (iv2_t ab, iv2_t cd)
+{
+ iv4_t t;
+ copies_iv2_t (&t.arr[0], ab);
+ copies_iv2_t (&t.arr[2], cd);
+ __analyzer_eval (t.arr[0] = ab.arr[0]); /* { dg-warning "UNKNOWN" } */
+ __analyzer_eval (t.arr[1] = ab.arr[1]); /* { dg-warning "UNKNOWN" } */
+ __analyzer_eval (t.arr[2] = cd.arr[0]); /* { dg-warning "UNKNOWN" } */
+ __analyzer_eval (t.arr[3] = cd.arr[1]); /* { dg-warning "UNKNOWN" } */
+ // TODO: ideally the UNKNOWNs would be TRUEs.
+}
+
+void partially_inits (int *p, int v)
+{
+ p[1] = v;
+}
+
+void test_partially_inits (int x)
+{
+ int arr[2];
+ partially_inits (arr, x);
+ partially_inits (arr, x);
+
+ __analyzer_eval (arr[0]); /* { dg-warning "UNKNOWN" "eval" } */
+ /* { dg-warning "use of uninitialized value 'arr\\\[0\\\]'" "uninit" { target *-*-* } .-1 } */
+
+ __analyzer_eval (arr[1] == x); /* { dg-warning "UNKNOWN" "eval" } */
+ /* { dg-bogus "use of uninitialized value 'arr\\\[1\\\]'" "uninit" { xfail *-*-* } .-1 } */
+ // TODO(xfail), and eval should be "TRUE"
+}
+
+int uses_switch (int i)
+{
+ switch (i)
+ {
+ case 0:
+ return 42;
+ case 1:
+ return 17;
+ default:
+ return i * 2;
+ }
+}
+
+void test_uses_switch (int x)
+{
+ __analyzer_eval (uses_switch (0) == 42); /* { dg-warning "UNKNOWN" } */
+ __analyzer_eval (uses_switch (1) == 17); /* { dg-warning "UNKNOWN" } */
+ __analyzer_eval (uses_switch (2) == x * 2); /* { dg-warning "UNKNOWN" } */
+ // TODO: ideally the UNKNOWNs would be TRUEs.
+}
+
+int *returns_ptr_to_first_field (struct coord *p)
+{
+ return &p->x;
+}
+
+void test_returns_ptr_to_first_field (struct coord *q)
+{
+ __analyzer_eval (returns_ptr_to_first_field (q) == (int *)q); /* { dg-warning "UNKNOWN" } */
+ __analyzer_eval (returns_ptr_to_first_field (q) == (int *)q); /* { dg-warning "UNKNOWN" } */
+ // TODO: ideally the UNKNOWNs would be TRUEs.
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/call-summaries-3.c b/gcc/testsuite/gcc.dg/analyzer/call-summaries-3.c
new file mode 100644
index 0000000..d63eb0c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/call-summaries-3.c
@@ -0,0 +1,29 @@
+/* { dg-additional-options "-fanalyzer-call-summaries --param analyzer-min-snodes-for-call-summary=0 -fno-analyzer-state-merge" } */
+
+/* There need to be at least two calls to a function for the
+ call-summarization code to be used.
+ TODO: add some kind of test that summarization *was* used. */
+
+#include "analyzer-decls.h"
+
+/* With state merging disabled, we get two summaries here. */
+
+int two_outcomes (int flag, int x, int y)
+{
+ if (flag)
+ return x;
+ else
+ return y;
+}
+
+void test_two_outcomes (int outer_flag, int a, int b)
+{
+ int r;
+ __analyzer_eval (two_outcomes (1, a, b) == a); /* { dg-warning "TRUE" } */
+ __analyzer_eval (two_outcomes (0, a, b) == b); /* { dg-warning "TRUE" } */
+ r = two_outcomes (outer_flag, a, b);
+ if (outer_flag)
+ __analyzer_eval (r == a); /* { dg-warning "TRUE" } */
+ else
+ __analyzer_eval (r == b); /* { dg-warning "TRUE" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/call-summaries-asm-x86.c b/gcc/testsuite/gcc.dg/analyzer/call-summaries-asm-x86.c
new file mode 100644
index 0000000..cc23283
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/call-summaries-asm-x86.c
@@ -0,0 +1,20 @@
+/* { dg-do compile { target x86_64-*-* } } */
+/* { dg-additional-options "-fanalyzer-call-summaries --param analyzer-min-snodes-for-call-summary=0" } */
+
+#include "analyzer-decls.h"
+
+int returns_asm_value (void)
+{
+ int dst;
+ asm ("mov 42, %0"
+ : "=r" (dst));
+ return dst;
+}
+
+void test_returns_asm_value (void)
+{
+ int a, b;
+ a = returns_asm_value ();
+ b = returns_asm_value ();
+ __analyzer_eval (a == b); /* { dg-warning "TRUE" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/call-summaries-malloc.c b/gcc/testsuite/gcc.dg/analyzer/call-summaries-malloc.c
new file mode 100644
index 0000000..87173a0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/call-summaries-malloc.c
@@ -0,0 +1,80 @@
+/* { dg-additional-options "-fanalyzer-call-summaries --param analyzer-min-snodes-for-call-summary=0" } */
+
+/* There need to be at least two calls to a function for the
+ call-summarization code to be used.
+ TODO: add some kind of test that summarization *was* used. */
+
+#include <stdlib.h>
+#include <string.h>
+#include "analyzer-decls.h"
+
+int *malloc_int (int i)
+{
+ int *res = malloc (sizeof (int));
+ if (!res)
+ return NULL;
+ *res = i;
+ return res;
+}
+
+void test_malloc_int (int x)
+{
+ int *p, *q;
+
+ p = malloc_int (42);
+ if (p)
+ __analyzer_eval (*p == 42); /* { dg-warning "TRUE" } */
+ free (p);
+
+ q = malloc_int (x);
+ if (q)
+ __analyzer_eval (*q == x); /* { dg-warning "TRUE" } */
+ free (q);
+}
+
+void test_leak (int x)
+{
+ int *p = malloc_int (x); /* { dg-message "when 'malloc_int' returns pointer to heap-allocated buffer" } */
+} /* { dg-message "leak of 'p'" } */
+
+void *wrapped_malloc (size_t sz)
+{
+ return malloc (sz);
+}
+
+void wrapped_free (void *p)
+{
+ free (p);
+}
+
+void test_wrapped_malloc_and_free (size_t sz)
+{
+ void *p = wrapped_malloc (100);
+ void *q = wrapped_malloc (sz);
+ __analyzer_dump_capacity (p); /* { dg-warning "capacity: '\\(\[^\n\r\]*\\)100'" } */
+ __analyzer_dump_capacity (q); /* { dg-warning "capacity: 'INIT_VAL\\(sz_\[^\n\r\]*\\)'" } */
+ wrapped_free (p);
+ wrapped_free (q);
+}
+
+void test_use_after_free (void)
+{
+ // TODO
+}
+
+void test_use_without_check (size_t sz)
+{
+ char *buf = wrapped_malloc (sz); /* { dg-message "this call could return NULL" } */
+ memset (buf, 'x', sz); /* { dg-warning "use of possibly-NULL 'buf' where non-null expected" } */
+ wrapped_free (buf);
+}
+
+void test_out_of_bounds (size_t sz)
+{
+ char *buf = wrapped_malloc (sz);
+ if (!buf)
+ return;
+ memset (buf, 'x', sz);
+ buf[sz] = '\0'; /* { dg-warning "heap-based buffer overflow" } */
+ wrapped_free (buf);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/call-summaries-pr107072.c b/gcc/testsuite/gcc.dg/analyzer/call-summaries-pr107072.c
new file mode 100644
index 0000000..1e689b3
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/call-summaries-pr107072.c
@@ -0,0 +1,90 @@
+/* { dg-additional-options "-fanalyzer-call-summaries --param analyzer-min-snodes-for-call-summary=0" } */
+
+/* There need to be at least two calls to a function for the
+ call-summarization code to be used.
+ TODO: add some kind of test that summarization *was* used. */
+
+/* Reduced from an example in Emacs in which string_char_and_length
+ was being incorrectly summarized, failing to see the write to *length. */
+
+typedef long int ptrdiff_t;
+typedef struct Lisp_X *Lisp_Word;
+typedef Lisp_Word Lisp_Object;
+extern _Bool STRING_MULTIBYTE(Lisp_Object str);
+extern unsigned char *SDATA(Lisp_Object string);
+enum { MAX_2_BYTE_CHAR = 0x7FF };
+enum { MAX_3_BYTE_CHAR = 0xFFFF };
+enum { MAX_4_BYTE_CHAR = 0x1FFFFF };
+enum { MAX_5_BYTE_CHAR = 0x3FFF7F };
+extern int make_char_multibyte(int c);
+static inline int string_char_and_length(unsigned char const *p, int *length) {
+ int c = p[0];
+ if (!(c & 0x80)) {
+ *length = 1;
+ return c;
+ }
+ ((0xC0 <= c) ? (void)0 : __builtin_unreachable());
+ int d = (c << 6) + p[1] - ((0xC0 << 6) + 0x80);
+ if (!(c & 0x20)) {
+ *length = 2;
+ return d + (c < 0xC2 ? 0x3FFF80 : 0);
+ }
+ d = (d << 6) + p[2] - ((0x20 << 12) + 0x80);
+ if (!(c & 0x10)) {
+ *length = 3;
+ ((MAX_2_BYTE_CHAR < d && d <= MAX_3_BYTE_CHAR)
+ ? (void)0
+ : __builtin_unreachable());
+ return d;
+ }
+ d = (d << 6) + p[3] - ((0x10 << 18) + 0x80);
+ if (!(c & 0x08)) {
+ *length = 4;
+ ((MAX_3_BYTE_CHAR < d && d <= MAX_4_BYTE_CHAR)
+ ? (void)0
+ : __builtin_unreachable());
+ return d;
+ }
+ d = (d << 6) + p[4] - ((0x08 << 24) + 0x80);
+ *length = 5;
+ ((MAX_4_BYTE_CHAR < d && d <= MAX_5_BYTE_CHAR)
+ ? (void)0
+ : __builtin_unreachable());
+ return d;
+}
+int fetch_string_char_advance(Lisp_Object string,
+ ptrdiff_t *charidx,
+ ptrdiff_t *byteidx) {
+ int output;
+ ptrdiff_t b = *byteidx;
+ unsigned char *chp = SDATA(string) + b;
+ if (STRING_MULTIBYTE(string)) {
+ int chlen;
+ output = string_char_and_length(chp, &chlen);
+ b += chlen;
+ } else {
+ output = *chp;
+ b++;
+ }
+ (*charidx)++;
+ *byteidx = b;
+ return output;
+}
+int fetch_string_char_as_multibyte_advance(Lisp_Object string,
+ ptrdiff_t *charidx,
+ ptrdiff_t *byteidx) {
+ int output;
+ ptrdiff_t b = *byteidx;
+ unsigned char *chp = SDATA(string) + b;
+ if (STRING_MULTIBYTE(string)) {
+ int chlen;
+ output = string_char_and_length(chp, &chlen);
+ b += chlen;
+ } else {
+ output = make_char_multibyte(*chp);
+ b++;
+ }
+ (*charidx)++;
+ *byteidx = b;
+ return output;
+}