diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc index 1ea9b30fa13d5594c627599b9b2e53e3b863fb21..16883d301d5b5b82df211f0de9b9688f4ba2e5ff 100644 --- a/gcc/analyzer/sm-malloc.cc +++ b/gcc/analyzer/sm-malloc.cc @@ -1498,8 +1498,11 @@ public: deref_before_check (const malloc_state_machine &sm, tree arg) : malloc_diagnostic (sm, arg), m_deref_enode (NULL), + m_deref_expr (NULL), m_check_enode (NULL) - {} + { + gcc_assert (arg); + } const char *get_kind () const final override { return "deref_before_check"; } @@ -1560,6 +1563,15 @@ public: if (linemap_location_from_macro_definition_p (line_table, check_loc)) return false; + /* Reject if m_deref_expr is sufficiently different from m_arg + for cases where the dereference is spelled differently from + the check, which is probably two different ways to get the + same svalue, and thus not worth reporting. */ + if (!m_deref_expr) + return false; + if (!sufficiently_similar_p (m_deref_expr, m_arg)) + return false; + /* Reject the warning if the deref's BB doesn't dominate that of the check, so that we don't warn e.g. for shared cleanup code that checks a pointer for NULL, when that code is sometimes @@ -1572,15 +1584,10 @@ public: m_deref_enode->get_supernode ()->m_bb)) return false; - if (m_arg) - return warning_at (rich_loc, get_controlling_option (), - "check of %qE for NULL after already" - " dereferencing it", - m_arg); - else - return warning_at (rich_loc, get_controlling_option (), - "check of pointer for NULL after already" - " dereferencing it"); + return warning_at (rich_loc, get_controlling_option (), + "check of %qE for NULL after already" + " dereferencing it", + m_arg); } label_text describe_state_change (const evdesc::state_change &change) @@ -1591,11 +1598,9 @@ public: { m_first_deref_event = change.m_event_id; m_deref_enode = change.m_event.get_exploded_node (); - if (m_arg) - return change.formatted_print ("pointer %qE is dereferenced here", - m_arg); - else - return label_text::borrow ("pointer is dereferenced here"); + m_deref_expr = change.m_expr; + return change.formatted_print ("pointer %qE is dereferenced here", + m_arg); } return malloc_diagnostic::describe_state_change (change); } @@ -1604,31 +1609,32 @@ public: { m_check_enode = ev.m_event.get_exploded_node (); if (m_first_deref_event.known_p ()) - { - if (m_arg) - return ev.formatted_print ("pointer %qE is checked for NULL here but" - " it was already dereferenced at %@", - m_arg, &m_first_deref_event); - else - return ev.formatted_print ("pointer is checked for NULL here but" - " it was already dereferenced at %@", - &m_first_deref_event); - } + return ev.formatted_print ("pointer %qE is checked for NULL here but" + " it was already dereferenced at %@", + m_arg, &m_first_deref_event); else - { - if (m_arg) - return ev.formatted_print ("pointer %qE is checked for NULL here but" - " it was already dereferenced", - m_arg); - else - return ev.formatted_print ("pointer is checked for NULL here but" - " it was already dereferenced"); - } + return ev.formatted_print ("pointer %qE is checked for NULL here but" + " it was already dereferenced", + m_arg); } private: + static bool sufficiently_similar_p (tree expr_a, tree expr_b) + { + pretty_printer *pp_a = global_dc->printer->clone (); + pretty_printer *pp_b = global_dc->printer->clone (); + pp_printf (pp_a, "%qE", expr_a); + pp_printf (pp_b, "%qE", expr_b); + bool result = (strcmp (pp_formatted_text (pp_a), pp_formatted_text (pp_b)) + == 0); + delete pp_a; + delete pp_b; + return result; + } + diagnostic_event_id_t m_first_deref_event; const exploded_node *m_deref_enode; + tree m_deref_expr; const exploded_node *m_check_enode; }; @@ -2141,9 +2147,10 @@ maybe_complain_about_deref_before_check (sm_context *sm_ctxt, return; tree diag_ptr = sm_ctxt->get_diagnostic_tree (ptr); - sm_ctxt->warn - (node, stmt, ptr, - make_unique<deref_before_check> (*this, diag_ptr)); + if (diag_ptr) + sm_ctxt->warn + (node, stmt, ptr, + make_unique<deref_before_check> (*this, diag_ptr)); sm_ctxt->set_next_state (stmt, ptr, m_stop); } diff --git a/gcc/testsuite/gcc.dg/analyzer/deref-before-check-pr108475-1.c b/gcc/testsuite/gcc.dg/analyzer/deref-before-check-pr108475-1.c new file mode 100644 index 0000000000000000000000000000000000000000..fa3beaaa15ffe2b466f163e22e3f1428b835b127 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/deref-before-check-pr108475-1.c @@ -0,0 +1,51 @@ +/* Reduced from haproxy-2.7.1: src/tcpcheck.c. */ + +#define NULL ((void *)0) + +int +test_1 (char **args, int cur_arg) +{ + char *p = NULL; + + if (*args[cur_arg]) { + p = args[cur_arg]; + } + + if (p) { /* { dg-bogus "check of 'p' for NULL after already dereferencing it" } */ + return 1; + } + return 0; +} + +int +test_2 (char **args, int cur_arg) +{ + char *p = NULL; + char *q = NULL; + + if (*args[cur_arg]) { + if (*args[cur_arg + 1]) { + p = args[cur_arg]; + } else { + q = args[cur_arg]; + } + } + + if (p) { /* { dg-bogus "check of 'p' for NULL after already dereferencing it" } */ + return 1; + } + if (q) { /* { dg-bogus "check of 'q' for NULL after already dereferencing it" } */ + return 2; + } + return 0; +} + +int test_3 (void **pp, int flag) +{ + void *p = NULL; + if (*pp && flag) + p = pp; + if (p) /* { dg-bogus "check of 'p' for NULL after already dereferencing it" } */ + return 1; + return 0; +} diff --git a/gcc/testsuite/gcc.dg/analyzer/deref-before-check-pr108475-haproxy-tcpcheck.c b/gcc/testsuite/gcc.dg/analyzer/deref-before-check-pr108475-haproxy-tcpcheck.c new file mode 100644 index 0000000000000000000000000000000000000000..1180e17e5552a12584d419c2eee79ca2c0843f8e --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/deref-before-check-pr108475-haproxy-tcpcheck.c @@ -0,0 +1,169 @@ +/* Reduced from haproxy-2.7.1: src/tcpcheck.c. */ + +/* { dg-additional-options "-Wno-analyzer-too-complex" } */ + +typedef __SIZE_TYPE__ size_t; +#define NULL ((void *)0) + +extern void *calloc(size_t __nmemb, size_t __size) + __attribute__((__nothrow__, __leaf__)) __attribute__((__malloc__)) + __attribute__((__alloc_size__(1, 2))); +extern char *strdup(const char *__s) __attribute__((__nothrow__, __leaf__)) +__attribute__((__malloc__)) __attribute__((__nonnull__(1))); +extern char *strstr(const char *__haystack, const char *__needle) + __attribute__((__nothrow__, __leaf__)) __attribute__((__pure__)) + __attribute__((__nonnull__(1, 2))); +extern size_t strlen(const char *__s) __attribute__((__nothrow__, __leaf__)) +__attribute__((__pure__)) __attribute__((__nonnull__(1))); +struct list { + struct list *n; + struct list *p; +}; +struct buffer { + size_t size; + char *area; + size_t data; + size_t head; +}; +struct proxy; +struct ist { + char *ptr; + size_t len; +}; +static inline int isttest(const struct ist ist) { return ist.ptr != NULL; } + +enum http_meth_t { + HTTP_METH_OPTIONS, + /* [...snip...] */ +} __attribute__((packed)); + +struct http_meth { + enum http_meth_t meth; + struct buffer str; +}; +enum tcpcheck_send_type { + /* [...snip...] */ + TCPCHK_SEND_HTTP, +}; +enum tcpcheck_rule_type { + TCPCHK_ACT_SEND = 0, + /* [...snip...] */ +}; +struct tcpcheck_http_hdr { + struct ist name; + struct list value; + struct list list; +}; +struct tcpcheck_send { + enum tcpcheck_send_type type; + union { + /* [...snip...] */ + struct { + unsigned int flags; + struct http_meth meth; + union { + struct ist uri; + /* [...snip...] */ + }; + struct ist vsn; + struct list hdrs; + /* [...snip...] */ + } http; + }; +}; +struct tcpcheck_rule { + /* [...snip...] */ + enum tcpcheck_rule_type action; + /* [...snip...] */ + union { + /* [...snip...] */ + struct tcpcheck_send send; + /* [...snip...] */ + }; +}; +enum http_meth_t find_http_meth(const char *str, const int len); +void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool); +void free_tcpcheck_http_hdr(struct tcpcheck_http_hdr *hdr); + +#define ist(str) ({ \ + char *__x = (void *)(str); \ + (struct ist){ \ + .ptr = __x, \ + .len = __builtin_constant_p(str) ? \ + ((void *)str == (void *)0) ? 0 : \ + __builtin_strlen(__x) : \ + ({ \ + size_t __l = 0; \ + if (__x) for (__l--; __x[++__l]; ) ; \ + __l; \ + }) \ + }; \ +}) + +struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, + struct proxy *px, char **errmsg) { + struct tcpcheck_rule *chk = NULL; + struct tcpcheck_http_hdr *hdr = NULL; + char *meth = NULL, *uri = NULL, *vsn = NULL; + char *hdrs, *body; + + hdrs = (*args[cur_arg + 2] ? strstr(args[cur_arg + 2], "\r\n") : NULL); + body = (*args[cur_arg + 2] ? strstr(args[cur_arg + 2], "\r\n\r\n") : NULL); + if (hdrs || body) { + /* [...snip...] */ + goto error; + } + + chk = calloc(1, sizeof(*chk)); + if (!chk) { + /* [...snip...] */ + goto error; + } + chk->action = TCPCHK_ACT_SEND; + chk->send.type = TCPCHK_SEND_HTTP; + chk->send.http.flags |= 0x0004; + chk->send.http.meth.meth = HTTP_METH_OPTIONS; + ((&chk->send.http.hdrs)->n = (&chk->send.http.hdrs)->p = + (&chk->send.http.hdrs)); + + if (*args[cur_arg]) { + if (!*args[cur_arg + 1]) + uri = args[cur_arg]; + else + meth = args[cur_arg]; + } + if (*args[cur_arg + 1]) + uri = args[cur_arg + 1]; + if (*args[cur_arg + 2]) + vsn = args[cur_arg + 2]; + + if (meth) { /* { dg-bogus "check of 'meth' for NULL after already dereferencing it" } */ + chk->send.http.meth.meth = find_http_meth(meth, strlen(meth)); + chk->send.http.meth.str.area = strdup(meth); + chk->send.http.meth.str.data = strlen(meth); + if (!chk->send.http.meth.str.area) { + /* [...snip...] */ + goto error; + } + } + if (uri) { + chk->send.http.uri = ist(strdup(uri)); + if (!isttest(chk->send.http.uri)) { + /* [...snip...] */ + goto error; + } + } + if (vsn) { /* { dg-bogus "check of 'vsn' for NULL after already dereferencing it" } */ + chk->send.http.vsn = ist(strdup(vsn)); + if (!isttest(chk->send.http.vsn)) { + /* [...snip...] */ + goto error; + } + } + return chk; + +error: + free_tcpcheck_http_hdr(hdr); + free_tcpcheck(chk, 0); + return NULL; +} diff --git a/gcc/testsuite/gcc.dg/analyzer/deref-before-check-pr109060-haproxy-cfgparse.c b/gcc/testsuite/gcc.dg/analyzer/deref-before-check-pr109060-haproxy-cfgparse.c new file mode 100644 index 0000000000000000000000000000000000000000..4f50882eb8ac85268d558631ffe7f5e4c1137841 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/deref-before-check-pr109060-haproxy-cfgparse.c @@ -0,0 +1,92 @@ +/* Reduced from haproxy-2.7.1's cfgparse.c. */ + +typedef __SIZE_TYPE__ size_t; + +extern int +strcmp(const char* __s1, const char* __s2) + __attribute__((__nothrow__, __leaf__)) __attribute__((__pure__)) + __attribute__((__nonnull__(1, 2))); + +extern int +strncmp(const char* __s1, const char* __s2, size_t __n) + __attribute__((__nothrow__, __leaf__)) __attribute__((__pure__)) + __attribute__((__nonnull__(1, 2))); + +enum +{ + /* [...snip...] */ + _ISdigit = ((3) < 8 ? ((1 << (3)) << 8) : ((1 << (3)) >> 8)), + /* [...snip...] */ +}; + +extern const unsigned short int** +__ctype_b_loc(void) __attribute__((__nothrow__, __leaf__)) + __attribute__((__const__)); + +unsigned int str2uic(const char* s); + +char* +memprintf(char** out, const char* format, ...) + __attribute__((format(printf, 2, 3))); + +int +parse_process_number(const char* arg, + unsigned long* proc, + int max, + int* autoinc, + char** err) +{ + if (autoinc) { + *autoinc = 0; + if (strncmp(arg, "auto:", 5) == 0) { + arg += 5; + *autoinc = 1; + } + } + + if (strcmp(arg, "all") == 0) /* { dg-bogus "pointer 'dash' is dereferenced here" } */ + *proc |= ~0UL; + else if (strcmp(arg, "odd") == 0) + *proc |= ~0UL / 3UL; + else if (strcmp(arg, "even") == 0) + *proc |= (~0UL / 3UL) << 1; + else { + const char *p, *dash = ((void*)0); + unsigned int low, high; + + for (p = arg; *p; p++) { + if (*p == '-' && !dash) /* { dg-bogus "check of 'dash' for NULL after already dereferencing it" } */ + dash = p; + else if (!((*__ctype_b_loc())[(int)(((unsigned char)*p))] & + (unsigned short int)_ISdigit)) { + memprintf(err, "'%s' is not a valid number/range.", arg); + return -1; + } + } + + low = high = str2uic(arg); + if (dash) /* { dg-bogus "check of 'dash' for NULL after already dereferencing it" } */ + high = ((!*(dash + 1)) ? max : str2uic(dash + 1)); + + if (high < low) { + unsigned int swap = low; + low = high; + high = swap; + } + + if (low < 1 || low > max || high > max) { + memprintf(err, + "'%s' is not a valid number/range." + " It supports numbers from 1 to %d.\n", + arg, + max); + return 1; + } + + for (; low <= high; low++) + *proc |= 1UL << (low - 1); + } + *proc &= ~0UL >> (((unsigned int)sizeof(long) * 8) - max); + + return 0; +}